Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
238 lines
5.1 KiB
TypeScript
238 lines
5.1 KiB
TypeScript
/**
|
|
* Brave Search request normalization and result mapping. It validates Brave
|
|
* country/language params and converts LLM-context responses into web results.
|
|
*/
|
|
import {
|
|
normalizeLowercaseStringOrEmpty,
|
|
normalizeOptionalString,
|
|
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
|
|
type BraveConfig = {
|
|
baseUrl?: unknown;
|
|
mode?: string;
|
|
};
|
|
|
|
type BraveLlmContextResult = { url: string; title: string; snippets: string[] };
|
|
/** Brave LLM Context API response subset used by OpenClaw. */
|
|
export type BraveLlmContextResponse = {
|
|
grounding: { generic?: BraveLlmContextResult[] };
|
|
sources?: { url?: string; hostname?: string; date?: string }[];
|
|
};
|
|
|
|
const BRAVE_COUNTRY_CODES = new Set([
|
|
"AR",
|
|
"AU",
|
|
"AT",
|
|
"BE",
|
|
"BR",
|
|
"CA",
|
|
"CL",
|
|
"DK",
|
|
"FI",
|
|
"FR",
|
|
"DE",
|
|
"GR",
|
|
"HK",
|
|
"IN",
|
|
"ID",
|
|
"IT",
|
|
"JP",
|
|
"KR",
|
|
"MY",
|
|
"MX",
|
|
"NL",
|
|
"NZ",
|
|
"NO",
|
|
"CN",
|
|
"PL",
|
|
"PT",
|
|
"PH",
|
|
"RU",
|
|
"SA",
|
|
"ZA",
|
|
"ES",
|
|
"SE",
|
|
"CH",
|
|
"TW",
|
|
"TR",
|
|
"GB",
|
|
"US",
|
|
"ALL",
|
|
]);
|
|
|
|
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;
|
|
|
|
function normalizeBraveSearchLang(value: string | undefined): string | undefined {
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
return undefined;
|
|
}
|
|
const lower = normalizeLowercaseStringOrEmpty(trimmed);
|
|
const canonical = BRAVE_SEARCH_LANG_ALIASES[lower] ?? lower;
|
|
if (!BRAVE_SEARCH_LANG_CODES.has(canonical)) {
|
|
return undefined;
|
|
}
|
|
return canonical;
|
|
}
|
|
|
|
/** Normalize Brave country filter values. */
|
|
export function normalizeBraveCountry(value: string | undefined): string | undefined {
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
return undefined;
|
|
}
|
|
const canonical = trimmed.toUpperCase();
|
|
return BRAVE_COUNTRY_CODES.has(canonical) ? canonical : "ALL";
|
|
}
|
|
|
|
function normalizeBraveUiLang(value: string | undefined): string | undefined {
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
return undefined;
|
|
}
|
|
const match = trimmed.match(BRAVE_UI_LANG_LOCALE);
|
|
if (!match) {
|
|
return undefined;
|
|
}
|
|
const [, language, region] = match;
|
|
return `${normalizeLowercaseStringOrEmpty(language)}-${region.toUpperCase()}`;
|
|
}
|
|
|
|
/** Resolve Brave-specific web-search config from scoped search config. */
|
|
export function resolveBraveConfig(searchConfig?: Record<string, unknown>): BraveConfig {
|
|
const brave = searchConfig?.brave;
|
|
return brave && typeof brave === "object" && !Array.isArray(brave) ? (brave as BraveConfig) : {};
|
|
}
|
|
|
|
/** Resolve whether Brave should use web search or LLM Context API mode. */
|
|
export function resolveBraveMode(brave?: BraveConfig): "web" | "llm-context" {
|
|
return brave?.mode === "llm-context" ? "llm-context" : "web";
|
|
}
|
|
|
|
/** Normalize Brave search and UI language params, detecting swapped fields. */
|
|
export function normalizeBraveLanguageParams(params: { search_lang?: string; ui_lang?: string }): {
|
|
search_lang?: string;
|
|
ui_lang?: string;
|
|
invalidField?: "search_lang" | "ui_lang";
|
|
} {
|
|
const rawSearchLang = normalizeOptionalString(params.search_lang);
|
|
const rawUiLang = normalizeOptionalString(params.ui_lang);
|
|
let searchLangCandidate = rawSearchLang;
|
|
let uiLangCandidate = rawUiLang;
|
|
|
|
if (normalizeBraveUiLang(rawSearchLang) && normalizeBraveSearchLang(rawUiLang)) {
|
|
searchLangCandidate = rawUiLang;
|
|
uiLangCandidate = rawSearchLang;
|
|
}
|
|
|
|
const search_lang = normalizeBraveSearchLang(searchLangCandidate);
|
|
if (searchLangCandidate && !search_lang) {
|
|
return { invalidField: "search_lang" };
|
|
}
|
|
|
|
const ui_lang = normalizeBraveUiLang(uiLangCandidate);
|
|
if (uiLangCandidate && !ui_lang) {
|
|
return { invalidField: "ui_lang" };
|
|
}
|
|
|
|
return { search_lang, ui_lang };
|
|
}
|
|
|
|
function resolveSiteName(url: string | undefined): string | undefined {
|
|
if (!url) {
|
|
return undefined;
|
|
}
|
|
try {
|
|
return new URL(url).hostname;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
/** Map Brave LLM Context API grounding results into web-search result rows. */
|
|
export function mapBraveLlmContextResults(
|
|
data: BraveLlmContextResponse,
|
|
): { url: string; title: string; snippets: string[]; siteName?: string }[] {
|
|
const genericResults = Array.isArray(data.grounding?.generic) ? data.grounding.generic : [];
|
|
return genericResults.map((entry) => ({
|
|
url: entry.url ?? "",
|
|
title: entry.title ?? "",
|
|
snippets: (entry.snippets ?? []).filter(
|
|
(snippet) => typeof snippet === "string" && snippet.length > 0,
|
|
),
|
|
siteName: resolveSiteName(entry.url) || undefined,
|
|
}));
|
|
}
|