fix(web-search): keep runtime legacy merge out of validation (#86818) · openclaw/openclaw@4a85cd7
luoyanglang
·
2026-05-27
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import { resolvePluginWebSearchConfig } from "../../config/plugin-web-search-config.js"; |
2 | 2 | import type { OpenClawConfig } from "../../config/types.openclaw.js"; |
| 3 | +import { isLegacyWebSearchProviderConfigKey } from "../../config/web-search-legacy-provider-keys.js"; |
3 | 4 | |
4 | 5 | export function getTopLevelCredentialValue(searchConfig?: Record<string, unknown>): unknown { |
5 | 6 | return searchConfig?.apiKey; |
@@ -52,13 +53,22 @@ export function mergeScopedSearchConfig(
|
52 | 53 | !Array.isArray(searchConfig[key]) |
53 | 54 | ? (searchConfig[key] as Record<string, unknown>) |
54 | 55 | : {}; |
55 | | -const next: Record<string, unknown> = { |
56 | | - ...searchConfig, |
57 | | -[key]: { |
| 56 | +const next: Record<string, unknown> = { ...searchConfig }; |
| 57 | +const existingDescriptor = searchConfig |
| 58 | + ? Object.getOwnPropertyDescriptor(searchConfig, key) |
| 59 | + : undefined; |
| 60 | +const shouldHideRuntimeInjectedLegacyShape = |
| 61 | +isLegacyWebSearchProviderConfigKey(key) && existingDescriptor === undefined; |
| 62 | + |
| 63 | +Object.defineProperty(next, key, { |
| 64 | +value: { |
58 | 65 | ...currentScoped, |
59 | 66 | ...pluginConfig, |
60 | 67 | }, |
61 | | -}; |
| 68 | +enumerable: !shouldHideRuntimeInjectedLegacyShape, |
| 69 | +configurable: true, |
| 70 | +writable: true, |
| 71 | +}); |
62 | 72 | |
63 | 73 | if (options?.mirrorApiKeyToTopLevel && pluginConfig.apiKey !== undefined) { |
64 | 74 | next.apiKey = pluginConfig.apiKey; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -144,4 +144,15 @@ describe("web_search scoped config merge", () => {
|
144 | 144 | brave: { count: 5, apiKey: "brave-test-key" }, |
145 | 145 | }); |
146 | 146 | }); |
| 147 | + |
| 148 | +it("keeps newly injected legacy provider config runtime-only for validation", () => { |
| 149 | +const merged = mergeScopedSearchConfig({ enabled: true, provider: "gemini" }, "perplexity", { |
| 150 | +apiKey: "perplexity-test-key", |
| 151 | +}); |
| 152 | + |
| 153 | +expect(merged?.perplexity).toEqual({ apiKey: "perplexity-test-key" }); |
| 154 | +expect(Object.keys(merged ?? {})).toEqual(["enabled", "provider"]); |
| 155 | + |
| 156 | +expect(Object.getOwnPropertyDescriptor(merged, "perplexity")?.enumerable).toBe(false); |
| 157 | +}); |
147 | 158 | }); |
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; |
2 | 2 | import { describe, expect, it } from "vitest"; |
| 3 | +import { mergeScopedSearchConfig } from "../agents/tools/web-search-provider-config.js"; |
3 | 4 | import { validateConfigObjectRaw } from "./validation.js"; |
4 | 5 | |
5 | 6 | describe("web search Codex native config validation", () => { |
@@ -54,6 +55,23 @@ describe("web search Codex native config validation", () => {
|
54 | 55 | } |
55 | 56 | }); |
56 | 57 | |
| 58 | +it("accepts runtime-only legacy provider entries injected by web search merge", () => { |
| 59 | +const search = mergeScopedSearchConfig({ enabled: true, provider: "gemini" }, "perplexity", { |
| 60 | +apiKey: "perplexity-test-key", |
| 61 | +}); |
| 62 | +const result = validateConfigObjectRaw({ |
| 63 | +tools: { |
| 64 | +web: { |
| 65 | + search, |
| 66 | +}, |
| 67 | +}, |
| 68 | +}); |
| 69 | + |
| 70 | +expect(search?.perplexity).toEqual({ apiKey: "perplexity-test-key" }); |
| 71 | +expect(Object.keys(search ?? {})).toEqual(["enabled", "provider"]); |
| 72 | +expect(result.ok).toBe(true); |
| 73 | +}); |
| 74 | + |
57 | 75 | it.each(["__proto__", "prototype", "constructor"])( |
58 | 76 | "rejects blocked tools.web.search key %s", |
59 | 77 | (key) => { |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
| 1 | +export const LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS = new Set([ |
| 2 | +"brave", |
| 3 | +"duckduckgo", |
| 4 | +"exa", |
| 5 | +"firecrawl", |
| 6 | +"gemini", |
| 7 | +"grok", |
| 8 | +"kimi", |
| 9 | +"minimax", |
| 10 | +"ollama", |
| 11 | +"perplexity", |
| 12 | +"searxng", |
| 13 | +"tavily", |
| 14 | +]); |
| 15 | + |
| 16 | +export function isLegacyWebSearchProviderConfigKey(key: string): boolean { |
| 17 | +return LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS.has(key); |
| 18 | +} |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -10,6 +10,7 @@ import {
|
10 | 10 | } from "../shared/string-coerce.js"; |
11 | 11 | import { uniqueStrings } from "../shared/string-normalization.js"; |
12 | 12 | import { isBlockedObjectKey } from "./prototype-keys.js"; |
| 13 | +import { LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS } from "./web-search-legacy-provider-keys.js"; |
13 | 14 | import { AgentModelSchema, AgentToolModelSchema } from "./zod-schema.agent-model.js"; |
14 | 15 | import { |
15 | 16 | GroupChatSchema, |
@@ -352,21 +353,6 @@ const CodexUserLocationSchema = z
|
352 | 353 | }) |
353 | 354 | .optional(); |
354 | 355 | |
355 | | -const LEGACY_WEB_SEARCH_PROVIDER_CONFIG_KEYS = new Set([ |
356 | | -"brave", |
357 | | -"duckduckgo", |
358 | | -"exa", |
359 | | -"firecrawl", |
360 | | -"gemini", |
361 | | -"grok", |
362 | | -"kimi", |
363 | | -"minimax", |
364 | | -"ollama", |
365 | | -"perplexity", |
366 | | -"searxng", |
367 | | -"tavily", |
368 | | -]); |
369 | | - |
370 | 356 | const BLOCKED_WEB_SEARCH_KEYS_ISSUE_FIELD = "__openclawBlockedWebSearchKeys"; |
371 | 357 | |
372 | 358 | const ToolsWebSearchSchema = z |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。