diff --git a/src/secrets/runtime-web-tools-manifest.runtime.ts b/src/secrets/runtime-web-tools-manifest.runtime.ts index e7cec417d1f..4d4a657b0dd 100644 --- a/src/secrets/runtime-web-tools-manifest.runtime.ts +++ b/src/secrets/runtime-web-tools-manifest.runtime.ts @@ -1 +1,5 @@ -export { resolveManifestContractOwnerPluginId } from "../plugins/manifest-registry.js"; +export { + resolveManifestContractOwnerPluginId, + resolveManifestContractPluginIds, + resolveManifestContractPluginIdsByCompatibilityRuntimePath, +} from "../plugins/manifest-registry.js"; diff --git a/src/secrets/runtime-web-tools.test.ts b/src/secrets/runtime-web-tools.test.ts index a07660415d9..c946b6eb170 100644 --- a/src/secrets/runtime-web-tools.test.ts +++ b/src/secrets/runtime-web-tools.test.ts @@ -34,10 +34,12 @@ const { ), resolveBundledWebFetchProvidersFromPublicArtifactsMock: vi.fn(() => buildTestWebFetchProviders()), })); -const { resolveManifestContractPluginIdsByCompatibilityRuntimePathMock } = vi.hoisted(() => ({ +const { + resolveManifestContractPluginIdsByCompatibilityRuntimePathMock, + resolveManifestContractOwnerPluginIdMock, + runtimeManifestActual, +} = vi.hoisted(() => ({ resolveManifestContractPluginIdsByCompatibilityRuntimePathMock: vi.fn(() => ["brave"]), -})); -const { resolveManifestContractOwnerPluginIdMock, runtimeManifestActual } = vi.hoisted(() => ({ resolveManifestContractOwnerPluginIdMock: vi.fn(), runtimeManifestActual: { resolveManifestContractOwnerPluginId: undefined as @@ -77,17 +79,6 @@ vi.mock("./runtime-web-tools-public-artifacts.runtime.js", () => ({ resolveBundledWebFetchProvidersFromPublicArtifactsMock, })); -vi.mock("../plugins/manifest-registry.js", async () => { - const actual = await vi.importActual( - "../plugins/manifest-registry.js", - ); - return { - ...actual, - resolveManifestContractPluginIdsByCompatibilityRuntimePath: - resolveManifestContractPluginIdsByCompatibilityRuntimePathMock, - }; -}); - vi.mock("./runtime-web-tools-manifest.runtime.js", async () => { const actual = await vi.importActual( "./runtime-web-tools-manifest.runtime.js", @@ -100,6 +91,8 @@ vi.mock("./runtime-web-tools-manifest.runtime.js", async () => { return { ...actual, resolveManifestContractOwnerPluginId: resolveManifestContractOwnerPluginIdMock, + resolveManifestContractPluginIdsByCompatibilityRuntimePath: + resolveManifestContractPluginIdsByCompatibilityRuntimePathMock, }; }); diff --git a/src/secrets/runtime-web-tools.ts b/src/secrets/runtime-web-tools.ts index 8cb85a855fd..3d517513414 100644 --- a/src/secrets/runtime-web-tools.ts +++ b/src/secrets/runtime-web-tools.ts @@ -1,9 +1,5 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { resolveSecretInputRef } from "../config/types.secrets.js"; -import { - resolveManifestContractPluginIds, - resolveManifestContractPluginIdsByCompatibilityRuntimePath, -} from "../plugins/manifest-registry.js"; import type { PluginWebFetchProviderEntry, PluginWebSearchProviderEntry, @@ -54,6 +50,10 @@ const loadRuntimeWebToolsPublicArtifacts = createLazyRuntimeSurface( () => import("./runtime-web-tools-public-artifacts.runtime.js"), (mod) => mod, ); +const loadRuntimeWebToolsManifest = createLazyRuntimeSurface( + () => import("./runtime-web-tools-manifest.runtime.js"), + (mod) => mod, +); type FetchConfig = NonNullable["web"] extends infer Web ? Web extends { fetch?: infer Fetch } @@ -120,8 +120,11 @@ function inferExactBundledPluginScopedWebToolConfigOwner(params: { return isRecord(pluginConfig?.[params.key]) ? params.pluginId : undefined; } -function hasCustomWebSearchPluginRisk(config: OpenClawConfig): boolean { - const plugins = config.plugins; +async function hasCustomWebSearchPluginRisk(params: { + config: OpenClawConfig; + env: NodeJS.ProcessEnv; +}): Promise { + const plugins = params.config.plugins; if (!plugins) { return false; } @@ -132,12 +135,13 @@ function hasCustomWebSearchPluginRisk(config: OpenClawConfig): boolean { return true; } + const { resolveManifestContractPluginIds } = await loadRuntimeWebToolsManifest(); const bundledPluginIds = new Set( resolveManifestContractPluginIds({ contract: "webSearchProviders", origin: "bundled", - config, - env: process.env, + config: params.config, + env: params.env, }), ); const hasNonBundledPluginId = (pluginId: string) => !bundledPluginIds.has(pluginId.trim()); @@ -465,6 +469,7 @@ export async function resolveRuntimeWebTools(params: { }): Promise { const defaults = params.sourceConfig.secrets?.defaults; const diagnostics: RuntimeWebDiagnostic[] = []; + const env = { ...process.env, ...params.context.env }; const sourceTools = isRecord(params.sourceConfig.tools) ? params.sourceConfig.tools : undefined; const sourceWeb = isRecord(sourceTools?.web) ? sourceTools.web : undefined; @@ -472,9 +477,12 @@ export async function resolveRuntimeWebTools(params: { ? params.resolvedConfig.tools : undefined; const resolvedWeb = isRecord(resolvedTools?.web) ? resolvedTools.web : undefined; - let hasCustomWebSearchRisk: boolean | undefined; - const getHasCustomWebSearchRisk = (): boolean => { - hasCustomWebSearchRisk ??= hasCustomWebSearchPluginRisk(params.sourceConfig); + let hasCustomWebSearchRisk: Promise | undefined; + const getHasCustomWebSearchRisk = (): Promise => { + hasCustomWebSearchRisk ??= hasCustomWebSearchPluginRisk({ + config: params.sourceConfig, + env, + }); return hasCustomWebSearchRisk; }; const legacyXSearchSource = isRecord(sourceWeb?.x_search) ? sourceWeb.x_search : undefined; @@ -533,35 +541,44 @@ export async function resolveRuntimeWebTools(params: { }; } const rawProvider = normalizeLowercaseStringOrEmpty(search?.provider); - const configuredBundledWebSearchPluginIdHint = - rawProvider && hasPluginWebSearchConfig - ? (inferExactBundledPluginScopedWebToolConfigOwner({ - config: params.sourceConfig, - key: "webSearch", - pluginId: rawProvider, - }) ?? - (!getHasCustomWebSearchRisk() - ? inferSingleBundledPluginScopedWebToolConfigOwner(params.sourceConfig, "webSearch") - : undefined)) - : undefined; + let configuredBundledWebSearchPluginIdHint: string | undefined; + if (rawProvider && hasPluginWebSearchConfig) { + configuredBundledWebSearchPluginIdHint = inferExactBundledPluginScopedWebToolConfigOwner({ + config: params.sourceConfig, + key: "webSearch", + pluginId: rawProvider, + }); + if (!configuredBundledWebSearchPluginIdHint && !(await getHasCustomWebSearchRisk())) { + configuredBundledWebSearchPluginIdHint = inferSingleBundledPluginScopedWebToolConfigOwner( + params.sourceConfig, + "webSearch", + ); + } + } const searchMetadata: RuntimeWebSearchMetadata = { providerSource: "none", diagnostics: [], }; if (search || hasPluginWebSearchConfig) { - const searchCompatibilityOnlyPluginIds = + let searchCompatibilityOnlyPluginIds: string[] = []; + if ( !rawProvider && !hasPluginWebSearchConfig && isRecord(search) && Object.prototype.hasOwnProperty.call(search, "apiKey") - ? resolveManifestContractPluginIdsByCompatibilityRuntimePath({ - contract: "webSearchProviders", - path: "tools.web.search.apiKey", - origin: "bundled", - config: params.sourceConfig, - env: { ...process.env, ...params.context.env }, - }) - : []; + ) { + const { resolveManifestContractPluginIdsByCompatibilityRuntimePath } = + await loadRuntimeWebToolsManifest(); + searchCompatibilityOnlyPluginIds = resolveManifestContractPluginIdsByCompatibilityRuntimePath( + { + contract: "webSearchProviders", + path: "tools.web.search.apiKey", + origin: "bundled", + config: params.sourceConfig, + env, + }, + ); + } const searchSurface = await resolveRuntimeWebProviderSurface({ contract: "webSearchProviders", rawProvider, @@ -573,7 +590,7 @@ export async function resolveRuntimeWebTools(params: { sourceConfig: params.sourceConfig, context: params.context, configuredBundledPluginIdHint: configuredBundledWebSearchPluginIdHint, - resolveProviders: ({ configuredBundledPluginId }) => + resolveProviders: async ({ configuredBundledPluginId }) => resolveBundledWebSearchProviders({ sourceConfig: params.sourceConfig, context: params.context, @@ -581,10 +598,10 @@ export async function resolveRuntimeWebTools(params: { onlyPluginIds: configuredBundledPluginId === undefined && searchCompatibilityOnlyPluginIds.length > 0 && - !getHasCustomWebSearchRisk() + !(await getHasCustomWebSearchRisk()) ? searchCompatibilityOnlyPluginIds : undefined, - hasCustomWebSearchPluginRisk: getHasCustomWebSearchRisk(), + hasCustomWebSearchPluginRisk: await getHasCustomWebSearchRisk(), }), sortProviders: sortWebSearchProvidersForAutoDetect, readConfiguredCredential: ({ provider, config, toolConfig }) =>