mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-30 02:28:44 +00:00
fix(web-search): keep runtime legacy merge out of validation (#86818)
Runtime-injected web_search provider config from plugins.entries.<plugin>.config.webSearch now stays available to provider execution without being validated as user-authored legacy tools.web.search.<provider> config. Co-authored-by: luoyanglang <hanwanlonga@gmail.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { resolvePluginWebSearchConfig } from "../../config/plugin-web-search-config.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { isLegacyWebSearchProviderConfigKey } from "../../config/web-search-legacy-provider-keys.js";
|
||||
|
||||
export function getTopLevelCredentialValue(searchConfig?: Record<string, unknown>): unknown {
|
||||
return searchConfig?.apiKey;
|
||||
@@ -52,13 +53,22 @@ export function mergeScopedSearchConfig(
|
||||
!Array.isArray(searchConfig[key])
|
||||
? (searchConfig[key] as Record<string, unknown>)
|
||||
: {};
|
||||
const next: Record<string, unknown> = {
|
||||
...searchConfig,
|
||||
[key]: {
|
||||
const next: Record<string, unknown> = { ...searchConfig };
|
||||
const existingDescriptor = searchConfig
|
||||
? Object.getOwnPropertyDescriptor(searchConfig, key)
|
||||
: undefined;
|
||||
const shouldHideRuntimeInjectedLegacyShape =
|
||||
isLegacyWebSearchProviderConfigKey(key) && existingDescriptor === undefined;
|
||||
|
||||
Object.defineProperty(next, key, {
|
||||
value: {
|
||||
...currentScoped,
|
||||
...pluginConfig,
|
||||
},
|
||||
};
|
||||
enumerable: !shouldHideRuntimeInjectedLegacyShape,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
if (options?.mirrorApiKeyToTopLevel && pluginConfig.apiKey !== undefined) {
|
||||
next.apiKey = pluginConfig.apiKey;
|
||||
|
||||
@@ -144,4 +144,15 @@ describe("web_search scoped config merge", () => {
|
||||
brave: { count: 5, apiKey: "brave-test-key" },
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps newly injected legacy provider config runtime-only for validation", () => {
|
||||
const merged = mergeScopedSearchConfig({ enabled: true, provider: "gemini" }, "perplexity", {
|
||||
apiKey: "perplexity-test-key",
|
||||
});
|
||||
|
||||
expect(merged?.perplexity).toEqual({ apiKey: "perplexity-test-key" });
|
||||
expect(Object.keys(merged ?? {})).toEqual(["enabled", "provider"]);
|
||||
|
||||
expect(Object.getOwnPropertyDescriptor(merged, "perplexity")?.enumerable).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { mergeScopedSearchConfig } from "../agents/tools/web-search-provider-config.js";
|
||||
import { validateConfigObjectRaw } from "./validation.js";
|
||||
|
||||
describe("web search Codex native config validation", () => {
|
||||
@@ -54,6 +55,23 @@ describe("web search Codex native config validation", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts runtime-only legacy provider entries injected by web search merge", () => {
|
||||
const search = mergeScopedSearchConfig({ enabled: true, provider: "gemini" }, "perplexity", {
|
||||
apiKey: "perplexity-test-key",
|
||||
});
|
||||
const result = validateConfigObjectRaw({
|
||||
tools: {
|
||||
web: {
|
||||
search,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(search?.perplexity).toEqual({ apiKey: "perplexity-test-key" });
|
||||
expect(Object.keys(search ?? {})).toEqual(["enabled", "provider"]);
|
||||
expect(result.ok).toBe(true);
|
||||
});
|
||||
|
||||
it.each(["__proto__", "prototype", "constructor"])(
|
||||
"rejects blocked tools.web.search key %s",
|
||||
(key) => {
|
||||
|
||||
18
src/config/web-search-legacy-provider-keys.ts
Normal file
18
src/config/web-search-legacy-provider-keys.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export const LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS = new Set([
|
||||
"brave",
|
||||
"duckduckgo",
|
||||
"exa",
|
||||
"firecrawl",
|
||||
"gemini",
|
||||
"grok",
|
||||
"kimi",
|
||||
"minimax",
|
||||
"ollama",
|
||||
"perplexity",
|
||||
"searxng",
|
||||
"tavily",
|
||||
]);
|
||||
|
||||
export function isLegacyWebSearchProviderConfigKey(key: string): boolean {
|
||||
return LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS.has(key);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "../shared/string-coerce.js";
|
||||
import { uniqueStrings } from "../shared/string-normalization.js";
|
||||
import { isBlockedObjectKey } from "./prototype-keys.js";
|
||||
import { LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS } from "./web-search-legacy-provider-keys.js";
|
||||
import { AgentModelSchema, AgentToolModelSchema } from "./zod-schema.agent-model.js";
|
||||
import {
|
||||
GroupChatSchema,
|
||||
@@ -352,21 +353,6 @@ const CodexUserLocationSchema = z
|
||||
})
|
||||
.optional();
|
||||
|
||||
const LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS = new Set([
|
||||
"brave",
|
||||
"duckduckgo",
|
||||
"exa",
|
||||
"firecrawl",
|
||||
"gemini",
|
||||
"grok",
|
||||
"kimi",
|
||||
"minimax",
|
||||
"ollama",
|
||||
"perplexity",
|
||||
"searxng",
|
||||
"tavily",
|
||||
]);
|
||||
|
||||
const BLOCKED_WEB_SEARCH_KEYS_ISSUE_FIELD = "__openclawBlockedWebSearchKeys";
|
||||
|
||||
const ToolsWebSearchSchema = z
|
||||
|
||||
Reference in New Issue
Block a user