mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 01:52:44 +00:00
fix(web_search): align brave language codes with API
This commit is contained in:
@@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
|
- Tools/web_search Brave language-code validation: align `search_lang` handling with Brave-supported codes (including `zh-hans`, `zh-hant`, `en-gb`, and `pt-br`), map common alias inputs (`zh`, `ja`) to valid Brave values, and reject unsupported codes before upstream requests to prevent 422 failures. (#37260) Thanks @heyanming.
|
||||||
- Models/openai-completions streaming compatibility: force `compat.supportsUsageInStreaming=false` for non-native OpenAI-compatible endpoints during model normalization, preventing usage-only stream chunks from triggering `choices[0]` parser crashes in provider streams. (#8714) Thanks @nonanon1.
|
- Models/openai-completions streaming compatibility: force `compat.supportsUsageInStreaming=false` for non-native OpenAI-compatible endpoints during model normalization, preventing usage-only stream chunks from triggering `choices[0]` parser crashes in provider streams. (#8714) Thanks @nonanon1.
|
||||||
- Tools/xAI native web-search collision guard: drop OpenClaw `web_search` from tool registration when routing to xAI/Grok model providers (including OpenRouter `x-ai/*`) to avoid duplicate tool-name request failures against provider-native `web_search`. (#14749) Thanks @realsamrat.
|
- Tools/xAI native web-search collision guard: drop OpenClaw `web_search` from tool registration when routing to xAI/Grok model providers (including OpenRouter `x-ai/*`) to avoid duplicate tool-name request failures against provider-native `web_search`. (#14749) Thanks @realsamrat.
|
||||||
- TUI/token copy-safety rendering: treat long credential-like mixed alphanumeric tokens (including quoted forms) as copy-sensitive in render sanitization so formatter hard-wrap guards no longer inject visible spaces into auth-style values before display. (#26710) Thanks @jasonthane.
|
- TUI/token copy-safety rendering: treat long credential-like mixed alphanumeric tokens (including quoted forms) as copy-sensitive in render sanitization so formatter hard-wrap guards no longer inject visible spaces into auth-style values before display. (#26710) Thanks @jasonthane.
|
||||||
|
|||||||
@@ -40,7 +40,67 @@ const KIMI_WEB_SEARCH_TOOL = {
|
|||||||
const SEARCH_CACHE = new Map<string, CacheEntry<Record<string, unknown>>>();
|
const SEARCH_CACHE = new Map<string, CacheEntry<Record<string, unknown>>>();
|
||||||
const BRAVE_FRESHNESS_SHORTCUTS = new Set(["pd", "pw", "pm", "py"]);
|
const BRAVE_FRESHNESS_SHORTCUTS = new Set(["pd", "pw", "pm", "py"]);
|
||||||
const BRAVE_FRESHNESS_RANGE = /^(\d{4}-\d{2}-\d{2})to(\d{4}-\d{2}-\d{2})$/;
|
const BRAVE_FRESHNESS_RANGE = /^(\d{4}-\d{2}-\d{2})to(\d{4}-\d{2}-\d{2})$/;
|
||||||
const BRAVE_SEARCH_LANG_CODE = /^[a-z]{2}$/i;
|
const BRAVE_SEARCH_LANG_CODES = new Set([
|
||||||
|
"ar",
|
||||||
|
"eu",
|
||||||
|
"bn",
|
||||||
|
"bg",
|
||||||
|
"ca",
|
||||||
|
"zh-hans",
|
||||||
|
"zh-hant",
|
||||||
|
"hr",
|
||||||
|
"cs",
|
||||||
|
"da",
|
||||||
|
"nl",
|
||||||
|
"en",
|
||||||
|
"en-gb",
|
||||||
|
"et",
|
||||||
|
"fi",
|
||||||
|
"fr",
|
||||||
|
"gl",
|
||||||
|
"de",
|
||||||
|
"el",
|
||||||
|
"gu",
|
||||||
|
"he",
|
||||||
|
"hi",
|
||||||
|
"hu",
|
||||||
|
"is",
|
||||||
|
"it",
|
||||||
|
"jp",
|
||||||
|
"kn",
|
||||||
|
"ko",
|
||||||
|
"lv",
|
||||||
|
"lt",
|
||||||
|
"ms",
|
||||||
|
"ml",
|
||||||
|
"mr",
|
||||||
|
"nb",
|
||||||
|
"pl",
|
||||||
|
"pt-br",
|
||||||
|
"pt-pt",
|
||||||
|
"pa",
|
||||||
|
"ro",
|
||||||
|
"ru",
|
||||||
|
"sr",
|
||||||
|
"sk",
|
||||||
|
"sl",
|
||||||
|
"es",
|
||||||
|
"sv",
|
||||||
|
"ta",
|
||||||
|
"te",
|
||||||
|
"th",
|
||||||
|
"tr",
|
||||||
|
"uk",
|
||||||
|
"vi",
|
||||||
|
]);
|
||||||
|
const BRAVE_SEARCH_LANG_ALIASES: Record<string, string> = {
|
||||||
|
ja: "jp",
|
||||||
|
zh: "zh-hans",
|
||||||
|
"zh-cn": "zh-hans",
|
||||||
|
"zh-hk": "zh-hant",
|
||||||
|
"zh-sg": "zh-hans",
|
||||||
|
"zh-tw": "zh-hant",
|
||||||
|
};
|
||||||
const BRAVE_UI_LANG_LOCALE = /^([a-z]{2})-([a-z]{2})$/i;
|
const BRAVE_UI_LANG_LOCALE = /^([a-z]{2})-([a-z]{2})$/i;
|
||||||
const PERPLEXITY_RECENCY_VALUES = new Set(["day", "week", "month", "year"]);
|
const PERPLEXITY_RECENCY_VALUES = new Set(["day", "week", "month", "year"]);
|
||||||
|
|
||||||
@@ -127,7 +187,7 @@ function createWebSearchSchema(provider: (typeof SEARCH_PROVIDERS)[number]) {
|
|||||||
search_lang: Type.Optional(
|
search_lang: Type.Optional(
|
||||||
Type.String({
|
Type.String({
|
||||||
description:
|
description:
|
||||||
"Short ISO language code for search results (e.g., 'de', 'en', 'fr', 'tr'). Must be a 2-letter code, NOT a locale.",
|
"Brave language code for search results (e.g., 'en', 'de', 'en-gb', 'zh-hans', 'zh-hant', 'pt-br').",
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
ui_lang: Type.Optional(
|
ui_lang: Type.Optional(
|
||||||
@@ -731,10 +791,14 @@ function normalizeBraveSearchLang(value: string | undefined): string | undefined
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const trimmed = value.trim();
|
const trimmed = value.trim();
|
||||||
if (!trimmed || !BRAVE_SEARCH_LANG_CODE.test(trimmed)) {
|
if (!trimmed) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return trimmed.toLowerCase();
|
const canonical = BRAVE_SEARCH_LANG_ALIASES[trimmed.toLowerCase()] ?? trimmed.toLowerCase();
|
||||||
|
if (!BRAVE_SEARCH_LANG_CODES.has(canonical)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return canonical;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeBraveUiLang(value: string | undefined): string | undefined {
|
function normalizeBraveUiLang(value: string | undefined): string | undefined {
|
||||||
@@ -1473,7 +1537,7 @@ export function createWebSearchTool(options?: {
|
|||||||
return jsonResult({
|
return jsonResult({
|
||||||
error: "invalid_search_lang",
|
error: "invalid_search_lang",
|
||||||
message:
|
message:
|
||||||
"search_lang must be a 2-letter ISO language code like 'en' (not a locale like 'en-US').",
|
"search_lang must be a Brave-supported language code like 'en', 'en-gb', 'zh-hans', or 'zh-hant'.",
|
||||||
docs: "https://docs.openclaw.ai/tools/web",
|
docs: "https://docs.openclaw.ai/tools/web",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ describe("web_search country and language parameters", () => {
|
|||||||
async function runBraveSearchAndGetUrl(
|
async function runBraveSearchAndGetUrl(
|
||||||
params: Partial<{
|
params: Partial<{
|
||||||
country: string;
|
country: string;
|
||||||
|
language: string;
|
||||||
|
search_lang: string;
|
||||||
ui_lang: string;
|
ui_lang: string;
|
||||||
freshness: string;
|
freshness: string;
|
||||||
}>,
|
}>,
|
||||||
@@ -185,6 +187,30 @@ describe("web_search country and language parameters", () => {
|
|||||||
expect(url.searchParams.get("search_lang")).toBe("de");
|
expect(url.searchParams.get("search_lang")).toBe("de");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("maps legacy zh language code to Brave zh-hans search_lang", async () => {
|
||||||
|
const url = await runBraveSearchAndGetUrl({ language: "zh" });
|
||||||
|
expect(url.searchParams.get("search_lang")).toBe("zh-hans");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("maps ja language code to Brave jp search_lang", async () => {
|
||||||
|
const url = await runBraveSearchAndGetUrl({ language: "ja" });
|
||||||
|
expect(url.searchParams.get("search_lang")).toBe("jp");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("passes Brave extended language code variants unchanged", async () => {
|
||||||
|
const url = await runBraveSearchAndGetUrl({ search_lang: "zh-hant" });
|
||||||
|
expect(url.searchParams.get("search_lang")).toBe("zh-hant");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects unsupported Brave search_lang values before upstream request", async () => {
|
||||||
|
const mockFetch = installMockFetch({ web: { results: [] } });
|
||||||
|
const tool = createWebSearchTool({ config: undefined, sandboxed: true });
|
||||||
|
const result = await tool?.execute?.("call-1", { query: "test", search_lang: "xx" });
|
||||||
|
|
||||||
|
expect(mockFetch).not.toHaveBeenCalled();
|
||||||
|
expect(result?.details).toMatchObject({ error: "invalid_search_lang" });
|
||||||
|
});
|
||||||
|
|
||||||
it("rejects invalid freshness values", async () => {
|
it("rejects invalid freshness values", async () => {
|
||||||
const mockFetch = installMockFetch({ web: { results: [] } });
|
const mockFetch = installMockFetch({ web: { results: [] } });
|
||||||
const tool = createWebSearchTool({ config: undefined, sandboxed: true });
|
const tool = createWebSearchTool({ config: undefined, sandboxed: true });
|
||||||
|
|||||||
Reference in New Issue
Block a user