fix(secrets): use bundled web search fast path during reload

This commit is contained in:
Shakker 2026-03-20 03:26:16 +00:00
parent 2d24f35016
commit 218f8d74b6
No known key found for this signature in database
2 changed files with 104 additions and 8 deletions

View File

@ -12,7 +12,12 @@ const { resolvePluginWebSearchProvidersMock } = vi.hoisted(() => ({
resolvePluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
const { resolveBundledPluginWebSearchProvidersMock } = vi.hoisted(() => ({
resolveBundledPluginWebSearchProvidersMock: vi.fn(() => buildTestWebSearchProviders()),
}));
vi.mock("../plugins/web-search-providers.js", () => ({
resolveBundledPluginWebSearchProviders: resolveBundledPluginWebSearchProvidersMock,
resolvePluginWebSearchProviders: resolvePluginWebSearchProvidersMock,
}));
@ -177,6 +182,7 @@ function expectInactiveFirecrawlSecretRef(params: {
describe("runtime web tools resolution", () => {
beforeEach(() => {
vi.mocked(webSearchProviders.resolvePluginWebSearchProviders).mockClear();
vi.mocked(webSearchProviders.resolveBundledPluginWebSearchProviders).mockClear();
});
afterEach(() => {
@ -531,6 +537,48 @@ describe("runtime web tools resolution", () => {
);
});
it("uses bundled provider resolution for configured bundled providers", async () => {
const bundledSpy = vi.mocked(webSearchProviders.resolveBundledPluginWebSearchProviders);
const genericSpy = vi.mocked(webSearchProviders.resolvePluginWebSearchProviders);
const { metadata } = await runRuntimeWebTools({
config: asConfig({
tools: {
web: {
search: {
enabled: true,
provider: "gemini",
},
},
},
plugins: {
entries: {
google: {
enabled: true,
config: {
webSearch: {
apiKey: { source: "env", provider: "default", id: "GEMINI_PROVIDER_REF" },
},
},
},
},
},
}),
env: {
GEMINI_PROVIDER_REF: "gemini-provider-key",
},
});
expect(metadata.search.selectedProvider).toBe("gemini");
expect(bundledSpy).toHaveBeenCalledWith(
expect.objectContaining({
bundledAllowlistCompat: true,
onlyPluginIds: ["google"],
}),
);
expect(genericSpy).not.toHaveBeenCalled();
});
it("does not resolve Firecrawl SecretRef when Firecrawl is inactive", async () => {
const resolveSpy = vi.spyOn(secretResolve, "resolveSecretRefValues");
const { metadata, context } = await runRuntimeWebTools({

View File

@ -1,10 +1,17 @@
import type { OpenClawConfig } from "../config/config.js";
import { resolveSecretInputRef } from "../config/types.secrets.js";
import {
BUNDLED_WEB_SEARCH_PLUGIN_IDS,
resolveBundledWebSearchPluginId,
} from "../plugins/bundled-web-search.js";
import type {
PluginWebSearchProviderEntry,
WebSearchCredentialResolutionSource,
} from "../plugins/types.js";
import { resolvePluginWebSearchProviders } from "../plugins/web-search-providers.js";
import {
resolveBundledPluginWebSearchProviders,
resolvePluginWebSearchProviders,
} from "../plugins/web-search-providers.js";
import { normalizeSecretInput } from "../utils/normalize-secret-input.js";
import { secretRefKey } from "./ref-contract.js";
import { resolveSecretRefValues } from "./resolve.js";
@ -65,6 +72,33 @@ function normalizeProvider(
return undefined;
}
function hasCustomWebSearchPluginRisk(config: OpenClawConfig): boolean {
const plugins = config.plugins;
if (!plugins) {
return false;
}
if (Array.isArray(plugins.load?.paths) && plugins.load.paths.length > 0) {
return true;
}
if (plugins.installs && Object.keys(plugins.installs).length > 0) {
return true;
}
const bundledPluginIds = new Set<string>(BUNDLED_WEB_SEARCH_PLUGIN_IDS);
const hasNonBundledPluginId = (pluginId: string) => !bundledPluginIds.has(pluginId.trim());
if (Array.isArray(plugins.allow) && plugins.allow.some(hasNonBundledPluginId)) {
return true;
}
if (Array.isArray(plugins.deny) && plugins.deny.some(hasNonBundledPluginId)) {
return true;
}
if (plugins.entries && Object.keys(plugins.entries).some(hasNonBundledPluginId)) {
return true;
}
return false;
}
function readNonEmptyEnvValue(
env: NodeJS.ProcessEnv,
names: string[],
@ -261,12 +295,28 @@ export async function resolveRuntimeWebTools(params: {
const tools = isRecord(params.sourceConfig.tools) ? params.sourceConfig.tools : undefined;
const web = isRecord(tools?.web) ? tools.web : undefined;
const search = isRecord(web?.search) ? web.search : undefined;
const rawProvider =
typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : "";
const configuredBundledPluginId = resolveBundledWebSearchPluginId(rawProvider);
const providers = search
? resolvePluginWebSearchProviders({
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
bundledAllowlistCompat: true,
})
? configuredBundledPluginId
? resolveBundledPluginWebSearchProviders({
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
bundledAllowlistCompat: true,
onlyPluginIds: [configuredBundledPluginId],
})
: !hasCustomWebSearchPluginRisk(params.sourceConfig)
? resolveBundledPluginWebSearchProviders({
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
bundledAllowlistCompat: true,
})
: resolvePluginWebSearchProviders({
config: params.sourceConfig,
env: { ...process.env, ...params.context.env },
bundledAllowlistCompat: true,
})
: [];
const searchMetadata: RuntimeWebSearchMetadata = {
@ -275,8 +325,6 @@ export async function resolveRuntimeWebTools(params: {
};
const searchEnabled = search?.enabled !== false;
const rawProvider =
typeof search?.provider === "string" ? search.provider.trim().toLowerCase() : "";
const configuredProvider = normalizeProvider(rawProvider, providers);
if (rawProvider && !configuredProvider) {