From 2205153ee8f76cdda043d3d7f45bebdc615bcc01 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 8 Apr 2026 08:01:10 +0100 Subject: [PATCH] perf(secrets): fast-path exact bundled web providers --- src/secrets/runtime-web-tools.test.ts | 47 +++++++++++++++++++++++++++ src/secrets/runtime-web-tools.ts | 34 ++++++++++++++++--- 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/secrets/runtime-web-tools.test.ts b/src/secrets/runtime-web-tools.test.ts index 955ba896a00..50ab1202c3b 100644 --- a/src/secrets/runtime-web-tools.test.ts +++ b/src/secrets/runtime-web-tools.test.ts @@ -822,6 +822,53 @@ describe("runtime web tools resolution", () => { expect(resolvePluginWebSearchProvidersMock).not.toHaveBeenCalled(); }); + it("uses exact plugin-id hints for configured bundled provider entries without manifest owner lookup", async () => { + const { metadata } = await runRuntimeWebTools({ + config: asConfig({ + tools: { + web: { + search: { + enabled: true, + provider: "brave", + }, + }, + }, + plugins: { + entries: { + brave: { + enabled: true, + config: { + webSearch: { + apiKey: { source: "env", provider: "default", id: "BRAVE_PROVIDER_REF" }, + }, + }, + }, + google: { + enabled: true, + config: { + webSearch: { + apiKey: { source: "env", provider: "default", id: "GOOGLE_PROVIDER_REF" }, + }, + }, + }, + }, + }, + }), + env: { + BRAVE_PROVIDER_REF: "brave-provider-key", + GOOGLE_PROVIDER_REF: "google-provider-key", + }, + }); + + expect(metadata.search.selectedProvider).toBe("brave"); + expect(resolveBundledExplicitWebSearchProvidersFromPublicArtifactsMock).toHaveBeenCalledWith({ + onlyPluginIds: ["brave"], + }); + expect(resolveManifestContractOwnerPluginIdMock).not.toHaveBeenCalled(); + expect(resolveBundledWebSearchProvidersFromPublicArtifactsMock).not.toHaveBeenCalled(); + expect(resolvePluginWebSearchProvidersMock).not.toHaveBeenCalled(); + }); + it("limits legacy top-level web search apiKey auto-detect to compatibility owners", async () => { const { metadata } = await runRuntimeWebTools({ config: asConfig({ diff --git a/src/secrets/runtime-web-tools.ts b/src/secrets/runtime-web-tools.ts index 999afdc276b..a056bf94610 100644 --- a/src/secrets/runtime-web-tools.ts +++ b/src/secrets/runtime-web-tools.ts @@ -107,6 +107,19 @@ function inferSingleBundledPluginScopedWebToolConfigOwner( return matches[0]; } +function inferExactBundledPluginScopedWebToolConfigOwner(params: { + config: OpenClawConfig; + key: "webSearch" | "webFetch"; + pluginId: string; +}): string | undefined { + const entry = params.config.plugins?.entries?.[params.pluginId]; + if (!isRecord(entry) || entry.enabled === false) { + return undefined; + } + const pluginConfig = isRecord(entry.config) ? entry.config : undefined; + return isRecord(pluginConfig?.[params.key]) ? params.pluginId : undefined; +} + function hasCustomWebSearchPluginRisk(config: OpenClawConfig): boolean { const plugins = config.plugins; if (!plugins) { @@ -459,6 +472,11 @@ 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); + return hasCustomWebSearchRisk; + }; const legacyXSearchSource = isRecord(sourceWeb?.x_search) ? sourceWeb.x_search : undefined; const legacyXSearchResolved = isRecord(resolvedWeb?.x_search) ? resolvedWeb.x_search : undefined; @@ -501,7 +519,6 @@ export async function resolveRuntimeWebTools(params: { } const search = isRecord(sourceWeb?.search) ? sourceWeb.search : undefined; const fetch = isRecord(sourceWeb?.fetch) ? (sourceWeb.fetch as FetchConfig) : undefined; - const hasCustomWebSearchRisk = hasCustomWebSearchPluginRisk(params.sourceConfig); if (!search && !fetch && !hasPluginWebSearchConfig && !hasPluginWebFetchConfig) { return { search: { @@ -517,8 +534,15 @@ export async function resolveRuntimeWebTools(params: { } const rawProvider = normalizeLowercaseStringOrEmpty(search?.provider); const configuredBundledWebSearchPluginIdHint = - rawProvider && hasPluginWebSearchConfig && !hasCustomWebSearchRisk - ? inferSingleBundledPluginScopedWebToolConfigOwner(params.sourceConfig, "webSearch") + rawProvider && hasPluginWebSearchConfig + ? (inferExactBundledPluginScopedWebToolConfigOwner({ + config: params.sourceConfig, + key: "webSearch", + pluginId: rawProvider, + }) ?? + (!getHasCustomWebSearchRisk() + ? inferSingleBundledPluginScopedWebToolConfigOwner(params.sourceConfig, "webSearch") + : undefined)) : undefined; const searchMetadata: RuntimeWebSearchMetadata = { providerSource: "none", @@ -557,10 +581,10 @@ export async function resolveRuntimeWebTools(params: { onlyPluginIds: configuredBundledPluginId === undefined && searchCompatibilityOnlyPluginIds.length > 0 && - !hasCustomWebSearchRisk + !getHasCustomWebSearchRisk() ? searchCompatibilityOnlyPluginIds : undefined, - hasCustomWebSearchPluginRisk: hasCustomWebSearchRisk, + hasCustomWebSearchPluginRisk: getHasCustomWebSearchRisk(), }), sortProviders: sortWebSearchProvidersForAutoDetect, readConfiguredCredential: ({ provider, config, toolConfig }) =>