mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +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
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
@@ -40,7 +40,67 @@ const KIMI_WEB_SEARCH_TOOL = {
|
||||
const SEARCH_CACHE = new Map<string, CacheEntry<Record<string, unknown>>>();
|
||||
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_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 PERPLEXITY_RECENCY_VALUES = new Set(["day", "week", "month", "year"]);
|
||||
|
||||
@@ -127,7 +187,7 @@ function createWebSearchSchema(provider: (typeof SEARCH_PROVIDERS)[number]) {
|
||||
search_lang: Type.Optional(
|
||||
Type.String({
|
||||
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(
|
||||
@@ -731,10 +791,14 @@ function normalizeBraveSearchLang(value: string | undefined): string | undefined
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || !BRAVE_SEARCH_LANG_CODE.test(trimmed)) {
|
||||
if (!trimmed) {
|
||||
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 {
|
||||
@@ -1473,7 +1537,7 @@ export function createWebSearchTool(options?: {
|
||||
return jsonResult({
|
||||
error: "invalid_search_lang",
|
||||
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",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -155,6 +155,8 @@ describe("web_search country and language parameters", () => {
|
||||
async function runBraveSearchAndGetUrl(
|
||||
params: Partial<{
|
||||
country: string;
|
||||
language: string;
|
||||
search_lang: string;
|
||||
ui_lang: string;
|
||||
freshness: string;
|
||||
}>,
|
||||
@@ -185,6 +187,30 @@ describe("web_search country and language parameters", () => {
|
||||
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 () => {
|
||||
const mockFetch = installMockFetch({ web: { results: [] } });
|
||||
const tool = createWebSearchTool({ config: undefined, sandboxed: true });
|
||||
|
||||
Reference in New Issue
Block a user