mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
test: isolate media factory planning imports
This commit is contained in:
@@ -12,9 +12,18 @@ import type { PluginManifestRecord } from "../plugins/manifest-registry.js";
|
||||
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||
import { clearSecretsRuntimeSnapshot } from "../secrets/runtime.js";
|
||||
import type { AuthProfileStore } from "./auth-profiles/types.js";
|
||||
import { __testing, createOpenClawTools } from "./openclaw-tools.js";
|
||||
import { resolveOptionalMediaToolFactoryPlan } from "./openclaw-tools.media-factory-plan.js";
|
||||
import * as pdfModelConfigModule from "./tools/pdf-tool.model-config.js";
|
||||
|
||||
type CreateOpenClawToolsOptions = Parameters<
|
||||
typeof import("./openclaw-tools.js").createOpenClawTools
|
||||
>[0];
|
||||
|
||||
async function createOpenClawToolsForTest(options?: CreateOpenClawToolsOptions) {
|
||||
const { createOpenClawTools } = await import("./openclaw-tools.js");
|
||||
return createOpenClawTools(options);
|
||||
}
|
||||
|
||||
function createAuthStore(providers: string[] = []): AuthProfileStore {
|
||||
return {
|
||||
version: 1,
|
||||
@@ -180,7 +189,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(["github-copilot"]),
|
||||
}),
|
||||
@@ -206,7 +215,7 @@ describe("optional media tool factory planning", () => {
|
||||
installSnapshot(config, []);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(),
|
||||
}),
|
||||
@@ -234,7 +243,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(["image-owner", "anthropic"]),
|
||||
toolAllowlist: ["image_generate"],
|
||||
@@ -263,7 +272,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(["image-owner", "anthropic"]),
|
||||
toolDenylist: ["image_generate", "pdf"],
|
||||
@@ -287,7 +296,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(["anthropic"]),
|
||||
}).pdf,
|
||||
@@ -320,7 +329,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(["image-owner", "video-owner", "music-owner", "anthropic"]),
|
||||
toolDenylist: ["*_generate", "p*"],
|
||||
@@ -360,7 +369,7 @@ describe("optional media tool factory planning", () => {
|
||||
vi.stubEnv("VIDEO_OWNER_API_KEY", "video-key");
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(["image-owner", "music-owner", "media-owner"]),
|
||||
}),
|
||||
@@ -372,12 +381,12 @@ describe("optional media tool factory planning", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("defers PDF model resolution from the tool-prep hot path", () => {
|
||||
it("defers PDF model resolution from the tool-prep hot path", async () => {
|
||||
const config: OpenClawConfig = {};
|
||||
installSnapshot(config, []);
|
||||
const resolveSpy = vi.spyOn(pdfModelConfigModule, "resolvePdfModelConfigForTool");
|
||||
|
||||
const tools = createOpenClawTools({
|
||||
const tools = await createOpenClawToolsForTest({
|
||||
config,
|
||||
agentDir: "/tmp/openclaw-agent-main",
|
||||
authProfileStore: createAuthStore(["anthropic"]),
|
||||
@@ -417,7 +426,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore([
|
||||
"external-image",
|
||||
@@ -434,7 +443,7 @@ describe("optional media tool factory planning", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps manifest-declared image provider auth aliases on the factory path", () => {
|
||||
it("keeps manifest-declared image provider auth aliases on the factory path", async () => {
|
||||
const config: OpenClawConfig = {};
|
||||
const plugins = [
|
||||
createPlugin({
|
||||
@@ -463,7 +472,7 @@ describe("optional media tool factory planning", () => {
|
||||
installSnapshot(config, plugins);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(["openai-codex"]),
|
||||
}),
|
||||
@@ -472,12 +481,14 @@ describe("optional media tool factory planning", () => {
|
||||
});
|
||||
installSnapshot(config, plugins, undefined, process.cwd());
|
||||
expect(
|
||||
createOpenClawTools({
|
||||
config,
|
||||
workspaceDir: process.cwd(),
|
||||
authProfileStore: createAuthStore(["openai-codex"]),
|
||||
pluginToolAllowlist: ["image_generate"],
|
||||
}).map((tool) => tool.name),
|
||||
(
|
||||
await createOpenClawToolsForTest({
|
||||
config,
|
||||
workspaceDir: process.cwd(),
|
||||
authProfileStore: createAuthStore(["openai-codex"]),
|
||||
pluginToolAllowlist: ["image_generate"],
|
||||
})
|
||||
).map((tool) => tool.name),
|
||||
).toContain("image_generate");
|
||||
});
|
||||
|
||||
@@ -528,7 +539,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(),
|
||||
}),
|
||||
@@ -539,7 +550,7 @@ describe("optional media tool factory planning", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not expose manifest-backed generation providers when plugins are globally disabled", () => {
|
||||
it("does not expose manifest-backed generation providers when plugins are globally disabled", async () => {
|
||||
const config: OpenClawConfig = {
|
||||
plugins: {
|
||||
enabled: false,
|
||||
@@ -587,7 +598,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(),
|
||||
}),
|
||||
@@ -598,15 +609,17 @@ describe("optional media tool factory planning", () => {
|
||||
pdf: false,
|
||||
});
|
||||
expect(
|
||||
createOpenClawTools({
|
||||
config,
|
||||
authProfileStore: createAuthStore(),
|
||||
pluginToolAllowlist: ["image_generate", "video_generate", "music_generate"],
|
||||
}).map((tool) => tool.name),
|
||||
(
|
||||
await createOpenClawToolsForTest({
|
||||
config,
|
||||
authProfileStore: createAuthStore(),
|
||||
pluginToolAllowlist: ["image_generate", "video_generate", "music_generate"],
|
||||
})
|
||||
).map((tool) => tool.name),
|
||||
).not.toEqual(expect.arrayContaining(["image_generate", "video_generate", "music_generate"]));
|
||||
});
|
||||
|
||||
it("does not count unresolved SecretRef config signals as configured", () => {
|
||||
it("does not count unresolved SecretRef config signals as configured", async () => {
|
||||
vi.stubEnv("COMFY_TEST_API_KEY", "");
|
||||
const workspaceDir = process.cwd();
|
||||
const config: OpenClawConfig = {
|
||||
@@ -660,7 +673,7 @@ describe("optional media tool factory planning", () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
workspaceDir,
|
||||
authStore: createAuthStore(),
|
||||
@@ -672,12 +685,14 @@ describe("optional media tool factory planning", () => {
|
||||
pdf: false,
|
||||
});
|
||||
expect(
|
||||
createOpenClawTools({
|
||||
config,
|
||||
workspaceDir,
|
||||
authProfileStore: createAuthStore(),
|
||||
pluginToolAllowlist: ["image_generate", "video_generate", "music_generate"],
|
||||
}).map((tool) => tool.name),
|
||||
(
|
||||
await createOpenClawToolsForTest({
|
||||
config,
|
||||
workspaceDir,
|
||||
authProfileStore: createAuthStore(),
|
||||
pluginToolAllowlist: ["image_generate", "video_generate", "music_generate"],
|
||||
})
|
||||
).map((tool) => tool.name),
|
||||
).not.toEqual(expect.arrayContaining(["image_generate", "video_generate", "music_generate"]));
|
||||
});
|
||||
|
||||
@@ -737,7 +752,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(),
|
||||
}),
|
||||
@@ -748,7 +763,7 @@ describe("optional media tool factory planning", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not register the image tool without cheap vision availability evidence", () => {
|
||||
it("does not register the image tool without cheap vision availability evidence", async () => {
|
||||
const config: OpenClawConfig = {};
|
||||
installSnapshot(config, [
|
||||
createPlugin({
|
||||
@@ -759,12 +774,14 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
createOpenClawTools({
|
||||
config,
|
||||
agentDir: "/tmp/openclaw-agent",
|
||||
authProfileStore: createAuthStore(),
|
||||
disablePluginTools: true,
|
||||
}).map((tool) => tool.name),
|
||||
(
|
||||
await createOpenClawToolsForTest({
|
||||
config,
|
||||
agentDir: "/tmp/openclaw-agent",
|
||||
authProfileStore: createAuthStore(),
|
||||
disablePluginTools: true,
|
||||
})
|
||||
).map((tool) => tool.name),
|
||||
).not.toContain("image");
|
||||
});
|
||||
|
||||
@@ -804,14 +821,16 @@ describe("optional media tool factory planning", () => {
|
||||
},
|
||||
])(
|
||||
"registers generation tools from Comfy $name without a current metadata snapshot",
|
||||
({ config }) => {
|
||||
async ({ config }) => {
|
||||
setBundledPluginsDirOverrideForTest(path.join(process.cwd(), "extensions"));
|
||||
|
||||
const toolNames = createOpenClawTools({
|
||||
config,
|
||||
authProfileStore: createAuthStore(),
|
||||
pluginToolAllowlist: ["image_generate", "video_generate", "music_generate"],
|
||||
}).map((tool) => tool.name);
|
||||
const toolNames = (
|
||||
await createOpenClawToolsForTest({
|
||||
config,
|
||||
authProfileStore: createAuthStore(),
|
||||
pluginToolAllowlist: ["image_generate", "video_generate", "music_generate"],
|
||||
})
|
||||
).map((tool) => tool.name);
|
||||
|
||||
expect(toolNames).toContain("image_generate");
|
||||
expect(toolNames).toContain("video_generate");
|
||||
@@ -853,7 +872,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(["openai-codex"]),
|
||||
}),
|
||||
@@ -878,7 +897,7 @@ describe("optional media tool factory planning", () => {
|
||||
]);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(["external-image"]),
|
||||
}),
|
||||
@@ -895,7 +914,7 @@ describe("optional media tool factory planning", () => {
|
||||
installSnapshot(config, []);
|
||||
|
||||
expect(
|
||||
__testing.resolveOptionalMediaToolFactoryPlan({
|
||||
resolveOptionalMediaToolFactoryPlan({
|
||||
config,
|
||||
authStore: createAuthStore(),
|
||||
}),
|
||||
|
||||
237
src/agents/openclaw-tools.media-factory-plan.ts
Normal file
237
src/agents/openclaw-tools.media-factory-plan.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import {
|
||||
resolveAgentModelFallbackValues,
|
||||
resolveAgentModelPrimaryValue,
|
||||
} from "../config/model-input.js";
|
||||
import type { AgentModelConfig } from "../config/types.agents-shared.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||
import { listProfilesForProvider } from "./auth-profiles/profile-list.js";
|
||||
import type { AuthProfileStore } from "./auth-profiles/types.js";
|
||||
import { isToolAllowedByPolicyName } from "./tool-policy-match.js";
|
||||
import {
|
||||
hasSnapshotCapabilityAvailability,
|
||||
hasSnapshotProviderEnvAvailability,
|
||||
loadCapabilityMetadataSnapshot,
|
||||
} from "./tools/manifest-capability-availability.js";
|
||||
|
||||
export type OptionalMediaToolFactoryPlan = {
|
||||
imageGenerate: boolean;
|
||||
videoGenerate: boolean;
|
||||
musicGenerate: boolean;
|
||||
pdf: boolean;
|
||||
};
|
||||
|
||||
type ToolModelConfig = { primary?: string; fallbacks?: string[] };
|
||||
|
||||
function coerceFactoryToolModelConfig(model?: AgentModelConfig): ToolModelConfig {
|
||||
const primary = resolveAgentModelPrimaryValue(model);
|
||||
const fallbacks = resolveAgentModelFallbackValues(model);
|
||||
return {
|
||||
...(primary?.trim() ? { primary: primary.trim() } : {}),
|
||||
...(fallbacks.length > 0 ? { fallbacks } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function hasToolModelConfig(model: ToolModelConfig | undefined): boolean {
|
||||
return Boolean(
|
||||
model?.primary?.trim() || (model?.fallbacks ?? []).some((entry) => entry.trim().length > 0),
|
||||
);
|
||||
}
|
||||
|
||||
function hasExplicitToolModelConfig(modelConfig: AgentModelConfig | undefined): boolean {
|
||||
return hasToolModelConfig(coerceFactoryToolModelConfig(modelConfig));
|
||||
}
|
||||
|
||||
function hasExplicitImageModelConfig(config: OpenClawConfig | undefined): boolean {
|
||||
return hasExplicitToolModelConfig(config?.agents?.defaults?.imageModel);
|
||||
}
|
||||
|
||||
function hasExplicitPdfModelConfig(config: OpenClawConfig | undefined): boolean {
|
||||
return (
|
||||
hasExplicitToolModelConfig(config?.agents?.defaults?.pdfModel) ||
|
||||
hasExplicitImageModelConfig(config)
|
||||
);
|
||||
}
|
||||
|
||||
function isToolAllowedByFactoryPolicy(params: {
|
||||
toolName: string;
|
||||
allowlist?: string[];
|
||||
denylist?: string[];
|
||||
}): boolean {
|
||||
return isToolAllowedByPolicyName(params.toolName, {
|
||||
allow: params.allowlist,
|
||||
deny: params.denylist,
|
||||
});
|
||||
}
|
||||
|
||||
export function isToolExplicitlyAllowedByFactoryPolicy(params: {
|
||||
toolName: string;
|
||||
allowlist?: string[];
|
||||
denylist?: string[];
|
||||
}): boolean {
|
||||
if (!params.allowlist?.some((entry) => typeof entry === "string" && entry.trim().length > 0)) {
|
||||
return false;
|
||||
}
|
||||
return isToolAllowedByFactoryPolicy(params);
|
||||
}
|
||||
|
||||
export function mergeFactoryPolicyList(
|
||||
...lists: Array<string[] | undefined>
|
||||
): string[] | undefined {
|
||||
const merged = lists.flatMap((list) => (Array.isArray(list) ? list : []));
|
||||
return merged.length > 0 ? Array.from(new Set(merged)) : undefined;
|
||||
}
|
||||
|
||||
export function resolveImageToolFactoryAvailable(params: {
|
||||
config?: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
modelHasVision?: boolean;
|
||||
authStore?: AuthProfileStore;
|
||||
}): boolean {
|
||||
if (!params.agentDir?.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (params.modelHasVision || hasExplicitImageModelConfig(params.config)) {
|
||||
return true;
|
||||
}
|
||||
const snapshot = loadCapabilityMetadataSnapshot({
|
||||
config: params.config,
|
||||
});
|
||||
return (
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "mediaUnderstandingProviders",
|
||||
config: params.config,
|
||||
}) ||
|
||||
hasConfiguredVisionModelAuthSignal({
|
||||
config: params.config,
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function hasConfiguredVisionModelAuthSignal(params: {
|
||||
config?: OpenClawConfig;
|
||||
snapshot: Pick<PluginMetadataSnapshot, "index" | "plugins">;
|
||||
authStore?: AuthProfileStore;
|
||||
}): boolean {
|
||||
const providers = params.config?.models?.providers;
|
||||
if (!providers || typeof providers !== "object") {
|
||||
return false;
|
||||
}
|
||||
for (const [providerId, providerConfig] of Object.entries(providers)) {
|
||||
if (
|
||||
!providerConfig?.models?.some(
|
||||
(model) => Array.isArray(model?.input) && model.input.includes("image"),
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (params.authStore && listProfilesForProvider(params.authStore, providerId).length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
hasSnapshotProviderEnvAvailability({
|
||||
snapshot: params.snapshot,
|
||||
providerId,
|
||||
config: params.config,
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function resolveOptionalMediaToolFactoryPlan(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
authStore?: AuthProfileStore;
|
||||
toolAllowlist?: string[];
|
||||
toolDenylist?: string[];
|
||||
}): OptionalMediaToolFactoryPlan {
|
||||
const defaults = params.config?.agents?.defaults;
|
||||
const toolAllowlist = mergeFactoryPolicyList(params.config?.tools?.allow, params.toolAllowlist);
|
||||
const toolDenylist = mergeFactoryPolicyList(params.config?.tools?.deny, params.toolDenylist);
|
||||
const allowImageGenerate = isToolAllowedByFactoryPolicy({
|
||||
toolName: "image_generate",
|
||||
allowlist: toolAllowlist,
|
||||
denylist: toolDenylist,
|
||||
});
|
||||
const allowVideoGenerate = isToolAllowedByFactoryPolicy({
|
||||
toolName: "video_generate",
|
||||
allowlist: toolAllowlist,
|
||||
denylist: toolDenylist,
|
||||
});
|
||||
const allowMusicGenerate = isToolAllowedByFactoryPolicy({
|
||||
toolName: "music_generate",
|
||||
allowlist: toolAllowlist,
|
||||
denylist: toolDenylist,
|
||||
});
|
||||
const allowPdf = isToolAllowedByFactoryPolicy({
|
||||
toolName: "pdf",
|
||||
allowlist: toolAllowlist,
|
||||
denylist: toolDenylist,
|
||||
});
|
||||
const explicitImageGeneration = hasExplicitToolModelConfig(defaults?.imageGenerationModel);
|
||||
const explicitVideoGeneration = hasExplicitToolModelConfig(defaults?.videoGenerationModel);
|
||||
const explicitMusicGeneration = hasExplicitToolModelConfig(defaults?.musicGenerationModel);
|
||||
const explicitPdf = hasExplicitPdfModelConfig(params.config);
|
||||
if (params.config?.plugins?.enabled === false) {
|
||||
return {
|
||||
imageGenerate: false,
|
||||
videoGenerate: false,
|
||||
musicGenerate: false,
|
||||
pdf: false,
|
||||
};
|
||||
}
|
||||
const snapshot = loadCapabilityMetadataSnapshot({
|
||||
config: params.config,
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
});
|
||||
return {
|
||||
imageGenerate:
|
||||
allowImageGenerate &&
|
||||
(explicitImageGeneration ||
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "imageGenerationProviders",
|
||||
config: params.config,
|
||||
})),
|
||||
videoGenerate:
|
||||
allowVideoGenerate &&
|
||||
(explicitVideoGeneration ||
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "videoGenerationProviders",
|
||||
config: params.config,
|
||||
})),
|
||||
musicGenerate:
|
||||
allowMusicGenerate &&
|
||||
(explicitMusicGeneration ||
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "musicGenerationProviders",
|
||||
config: params.config,
|
||||
})),
|
||||
pdf:
|
||||
allowPdf &&
|
||||
(explicitPdf ||
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "mediaUnderstandingProviders",
|
||||
config: params.config,
|
||||
}) ||
|
||||
hasConfiguredVisionModelAuthSignal({
|
||||
config: params.config,
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { selectApplicableRuntimeConfig } from "../config/config.js";
|
||||
import type { AgentModelConfig } from "../config/types.agents-shared.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { callGateway } from "../gateway/call.js";
|
||||
import { isEmbeddedMode } from "../infra/embedded-mode.js";
|
||||
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.types.js";
|
||||
import {
|
||||
getActiveRuntimeWebToolsMetadata,
|
||||
getActiveSecretsRuntimeSnapshot,
|
||||
@@ -11,9 +9,14 @@ import {
|
||||
import { normalizeDeliveryContext } from "../utils/delivery-context.js";
|
||||
import type { GatewayMessageChannel } from "../utils/message-channel.js";
|
||||
import { resolveAgentWorkspaceDir, resolveSessionAgentIds } from "./agent-scope.js";
|
||||
import { listProfilesForProvider } from "./auth-profiles.js";
|
||||
import type { AuthProfileStore } from "./auth-profiles/types.js";
|
||||
import { resolveOpenClawPluginToolsForOptions } from "./openclaw-plugin-tools.js";
|
||||
import {
|
||||
isToolExplicitlyAllowedByFactoryPolicy,
|
||||
mergeFactoryPolicyList,
|
||||
resolveImageToolFactoryAvailable,
|
||||
resolveOptionalMediaToolFactoryPlan,
|
||||
} from "./openclaw-tools.media-factory-plan.js";
|
||||
import { applyNodesToolWorkspaceGuard } from "./openclaw-tools.nodes-workspace-guard.js";
|
||||
import {
|
||||
collectPresentOpenClawTools,
|
||||
@@ -22,7 +25,6 @@ import {
|
||||
import type { SandboxFsBridge } from "./sandbox/fs-bridge.js";
|
||||
import type { SpawnedToolContext } from "./spawned-context.js";
|
||||
import type { ToolFsPolicy } from "./tool-fs-policy.js";
|
||||
import { isToolAllowedByPolicyName } from "./tool-policy-match.js";
|
||||
import { createAgentsListTool } from "./tools/agents-list-tool.js";
|
||||
import { createCanvasTool } from "./tools/canvas-tool.js";
|
||||
import type { AnyAgentTool } from "./tools/common.js";
|
||||
@@ -31,18 +33,10 @@ import { createEmbeddedCallGateway } from "./tools/embedded-gateway-stub.js";
|
||||
import { createGatewayTool } from "./tools/gateway-tool.js";
|
||||
import { createHeartbeatResponseTool } from "./tools/heartbeat-response-tool.js";
|
||||
import { createImageGenerateTool } from "./tools/image-generate-tool.js";
|
||||
import { coerceImageModelConfig } from "./tools/image-tool.helpers.js";
|
||||
import { createImageTool } from "./tools/image-tool.js";
|
||||
import {
|
||||
hasSnapshotCapabilityAvailability,
|
||||
hasSnapshotProviderEnvAvailability,
|
||||
loadCapabilityMetadataSnapshot,
|
||||
} from "./tools/manifest-capability-availability.js";
|
||||
import { createMessageTool } from "./tools/message-tool.js";
|
||||
import { coerceToolModelConfig, hasToolModelConfig } from "./tools/model-config.helpers.js";
|
||||
import { createMusicGenerateTool } from "./tools/music-generate-tool.js";
|
||||
import { createNodesTool } from "./tools/nodes-tool.js";
|
||||
import { coercePdfModelConfig } from "./tools/pdf-tool.helpers.js";
|
||||
import { createPdfTool } from "./tools/pdf-tool.js";
|
||||
import { createSessionStatusTool } from "./tools/session-status-tool.js";
|
||||
import { createSessionsHistoryTool } from "./tools/sessions-history-tool.js";
|
||||
@@ -68,204 +62,6 @@ const defaultOpenClawToolsDeps: OpenClawToolsDeps = {
|
||||
|
||||
let openClawToolsDeps: OpenClawToolsDeps = defaultOpenClawToolsDeps;
|
||||
|
||||
type OptionalMediaToolFactoryPlan = {
|
||||
imageGenerate: boolean;
|
||||
videoGenerate: boolean;
|
||||
musicGenerate: boolean;
|
||||
pdf: boolean;
|
||||
};
|
||||
|
||||
function hasExplicitToolModelConfig(modelConfig: AgentModelConfig | undefined): boolean {
|
||||
return hasToolModelConfig(coerceToolModelConfig(modelConfig));
|
||||
}
|
||||
|
||||
function hasExplicitImageModelConfig(config: OpenClawConfig | undefined): boolean {
|
||||
return hasToolModelConfig(coerceImageModelConfig(config));
|
||||
}
|
||||
|
||||
function isToolAllowedByFactoryPolicy(params: {
|
||||
toolName: string;
|
||||
allowlist?: string[];
|
||||
denylist?: string[];
|
||||
}): boolean {
|
||||
return isToolAllowedByPolicyName(params.toolName, {
|
||||
allow: params.allowlist,
|
||||
deny: params.denylist,
|
||||
});
|
||||
}
|
||||
|
||||
function isToolExplicitlyAllowedByFactoryPolicy(params: {
|
||||
toolName: string;
|
||||
allowlist?: string[];
|
||||
denylist?: string[];
|
||||
}): boolean {
|
||||
if (!params.allowlist?.some((entry) => typeof entry === "string" && entry.trim().length > 0)) {
|
||||
return false;
|
||||
}
|
||||
return isToolAllowedByFactoryPolicy(params);
|
||||
}
|
||||
|
||||
function mergeFactoryPolicyList(...lists: Array<string[] | undefined>): string[] | undefined {
|
||||
const merged = lists.flatMap((list) => (Array.isArray(list) ? list : []));
|
||||
return merged.length > 0 ? Array.from(new Set(merged)) : undefined;
|
||||
}
|
||||
|
||||
function resolveImageToolFactoryAvailable(params: {
|
||||
config?: OpenClawConfig;
|
||||
agentDir?: string;
|
||||
modelHasVision?: boolean;
|
||||
authStore?: AuthProfileStore;
|
||||
}): boolean {
|
||||
if (!params.agentDir?.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (params.modelHasVision || hasExplicitImageModelConfig(params.config)) {
|
||||
return true;
|
||||
}
|
||||
const snapshot = loadCapabilityMetadataSnapshot({
|
||||
config: params.config,
|
||||
});
|
||||
return (
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "mediaUnderstandingProviders",
|
||||
config: params.config,
|
||||
}) ||
|
||||
hasConfiguredVisionModelAuthSignal({
|
||||
config: params.config,
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function hasConfiguredVisionModelAuthSignal(params: {
|
||||
config?: OpenClawConfig;
|
||||
snapshot: Pick<PluginMetadataSnapshot, "index" | "plugins">;
|
||||
authStore?: AuthProfileStore;
|
||||
}): boolean {
|
||||
const providers = params.config?.models?.providers;
|
||||
if (!providers || typeof providers !== "object") {
|
||||
return false;
|
||||
}
|
||||
for (const [providerId, providerConfig] of Object.entries(providers)) {
|
||||
if (
|
||||
!providerConfig?.models?.some(
|
||||
(model) => Array.isArray(model?.input) && model.input.includes("image"),
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (params.authStore && listProfilesForProvider(params.authStore, providerId).length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
hasSnapshotProviderEnvAvailability({
|
||||
snapshot: params.snapshot,
|
||||
providerId,
|
||||
config: params.config,
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function resolveOptionalMediaToolFactoryPlan(params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
authStore?: AuthProfileStore;
|
||||
toolAllowlist?: string[];
|
||||
toolDenylist?: string[];
|
||||
}): OptionalMediaToolFactoryPlan {
|
||||
const defaults = params.config?.agents?.defaults;
|
||||
const toolAllowlist = mergeFactoryPolicyList(params.config?.tools?.allow, params.toolAllowlist);
|
||||
const toolDenylist = mergeFactoryPolicyList(params.config?.tools?.deny, params.toolDenylist);
|
||||
const allowImageGenerate = isToolAllowedByFactoryPolicy({
|
||||
toolName: "image_generate",
|
||||
allowlist: toolAllowlist,
|
||||
denylist: toolDenylist,
|
||||
});
|
||||
const allowVideoGenerate = isToolAllowedByFactoryPolicy({
|
||||
toolName: "video_generate",
|
||||
allowlist: toolAllowlist,
|
||||
denylist: toolDenylist,
|
||||
});
|
||||
const allowMusicGenerate = isToolAllowedByFactoryPolicy({
|
||||
toolName: "music_generate",
|
||||
allowlist: toolAllowlist,
|
||||
denylist: toolDenylist,
|
||||
});
|
||||
const allowPdf = isToolAllowedByFactoryPolicy({
|
||||
toolName: "pdf",
|
||||
allowlist: toolAllowlist,
|
||||
denylist: toolDenylist,
|
||||
});
|
||||
const explicitImageGeneration = hasExplicitToolModelConfig(defaults?.imageGenerationModel);
|
||||
const explicitVideoGeneration = hasExplicitToolModelConfig(defaults?.videoGenerationModel);
|
||||
const explicitMusicGeneration = hasExplicitToolModelConfig(defaults?.musicGenerationModel);
|
||||
const explicitPdf =
|
||||
hasToolModelConfig(coercePdfModelConfig(params.config)) ||
|
||||
hasToolModelConfig(coerceImageModelConfig(params.config));
|
||||
if (params.config?.plugins?.enabled === false) {
|
||||
return {
|
||||
imageGenerate: false,
|
||||
videoGenerate: false,
|
||||
musicGenerate: false,
|
||||
pdf: false,
|
||||
};
|
||||
}
|
||||
const snapshot = loadCapabilityMetadataSnapshot({
|
||||
config: params.config,
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
});
|
||||
return {
|
||||
imageGenerate:
|
||||
allowImageGenerate &&
|
||||
(explicitImageGeneration ||
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "imageGenerationProviders",
|
||||
config: params.config,
|
||||
})),
|
||||
videoGenerate:
|
||||
allowVideoGenerate &&
|
||||
(explicitVideoGeneration ||
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "videoGenerationProviders",
|
||||
config: params.config,
|
||||
})),
|
||||
musicGenerate:
|
||||
allowMusicGenerate &&
|
||||
(explicitMusicGeneration ||
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "musicGenerationProviders",
|
||||
config: params.config,
|
||||
})),
|
||||
pdf:
|
||||
allowPdf &&
|
||||
(explicitPdf ||
|
||||
hasSnapshotCapabilityAvailability({
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
key: "mediaUnderstandingProviders",
|
||||
config: params.config,
|
||||
}) ||
|
||||
hasConfiguredVisionModelAuthSignal({
|
||||
config: params.config,
|
||||
snapshot,
|
||||
authStore: params.authStore,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function createOpenClawTools(
|
||||
options?: {
|
||||
sandboxBrowserBridgeUrl?: string;
|
||||
|
||||
Reference in New Issue
Block a user