refactor(brave): share provider search lifecycle (#129046)

This commit is contained in:
Peter Steinberger
2026-08-25 00:02:33 -07:00
committed by GitHub
parent 27c0c8582c
commit 88f2b8b755
3 changed files with 67 additions and 64 deletions
@@ -483,74 +483,46 @@ export async function executeBraveSearch(
const start = Date.now();
const timeoutSeconds = resolveSearchTimeoutSeconds(searchConfig);
const cacheTtlMs = resolveSearchCacheTtlMs(searchConfig);
if (braveMode === "llm-context") {
const { results, sources } = await runBraveLlmContextSearch({
baseUrl: braveBaseUrl,
endpointMode: braveEndpointMode,
query,
apiKey,
timeoutSeconds,
diagnostics,
signal: options?.signal,
country: country ?? undefined,
search_lang: normalizedLanguage.search_lang,
freshness,
dateAfter,
dateBefore,
});
options?.signal?.throwIfAborted();
const payload = {
query,
provider: "brave",
mode: "llm-context" as const,
count: results.length,
tookMs: Date.now() - start,
externalContent: {
untrusted: true,
source: "web_search",
provider: "brave",
wrapped: true,
},
results: results.map((entry) => ({
title: entry.title ? wrapWebContent(entry.title, "web_search") : "",
url: entry.url,
snippets: entry.snippets.map((snippet) => wrapWebContent(snippet, "web_search")),
siteName: entry.siteName,
})),
sources,
};
writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
logBraveHttp(diagnostics, "cache write", {
mode: "llm-context",
query,
cacheKey,
ttlMs: cacheTtlMs,
count: results.length,
});
return payload;
}
const results = await runBraveWebSearch({
const request = {
baseUrl: braveBaseUrl,
endpointMode: braveEndpointMode,
query,
count: resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
apiKey,
timeoutSeconds,
diagnostics,
signal: options?.signal,
country: country ?? undefined,
search_lang: normalizedLanguage.search_lang,
ui_lang: normalizedLanguage.ui_lang,
freshness,
dateAfter,
dateBefore,
});
};
const response =
braveMode === "llm-context"
? { ...(await runBraveLlmContextSearch(request)), mode: "llm-context" as const }
: {
results: await runBraveWebSearch({
...request,
count: resolveSearchCount(count, DEFAULT_SEARCH_COUNT),
ui_lang: normalizedLanguage.ui_lang,
}),
mode: "web" as const,
};
// A completed upstream response must not write cache state after its caller aborts.
options?.signal?.throwIfAborted();
const results =
response.mode === "llm-context"
? response.results.map((entry) => ({
title: entry.title ? wrapWebContent(entry.title, "web_search") : "",
url: entry.url,
snippets: entry.snippets.map((snippet) => wrapWebContent(snippet, "web_search")),
siteName: entry.siteName,
}))
: response.results;
const payload = {
query,
provider: "brave",
...(response.mode === "llm-context" ? { mode: response.mode } : {}),
count: results.length,
tookMs: Date.now() - start,
externalContent: {
@@ -560,10 +532,11 @@ export async function executeBraveSearch(
wrapped: true,
},
results,
...(response.mode === "llm-context" ? { sources: response.sources } : {}),
};
writeCachedSearchPayload(cacheKey, payload, cacheTtlMs);
logBraveHttp(diagnostics, "cache write", {
mode: "web",
mode: response.mode,
query,
cacheKey,
ttlMs: cacheTtlMs,
@@ -2,6 +2,7 @@
* Brave Search request normalization and result mapping. It validates Brave
* country/language params and converts LLM-context responses into web results.
*/
import { resolveSiteName } from "openclaw/plugin-sdk/provider-web-search";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
@@ -213,17 +214,6 @@ export function normalizeBraveLanguageParams(params: { search_lang?: string; ui_
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,
@@ -228,6 +228,46 @@ describe("brave web search provider", () => {
},
);
it.each(["web", "llm-context"] as const)(
"does not cache a %s response completed after caller cancellation",
async (mode) => {
const controller = new AbortController();
const reason = new Error(`Brave ${mode} canceled after response`);
const payload =
mode === "web" ? { web: { results: [] } } : { grounding: { generic: [] }, sources: [] };
let firstRequest = true;
const fetchMock = vi.fn(async () => {
if (!firstRequest) {
return jsonResponse(payload);
}
firstRequest = false;
let emitted = false;
return new Response(
new ReadableStream<Uint8Array>({
pull(stream) {
if (!emitted) {
emitted = true;
stream.enqueue(new TextEncoder().encode(JSON.stringify(payload)));
return;
}
stream.close();
controller.abort(reason);
},
}),
{ headers: { "content-type": "application/json" } },
);
});
global.fetch = fetchMock as typeof global.fetch;
const tool = createBraveTool({ webSearch: { apiKey: "brave-test-key", mode } });
const args = { query: `brave post-response cancellation ${mode}` };
await expect(tool.execute(args, { signal: controller.signal })).rejects.toBe(reason);
await tool.execute(args);
expect(fetchMock).toHaveBeenCalledTimes(2);
},
);
it("normalizes brave language parameters and swaps reversed ui/search inputs", () => {
expect(
testing.normalizeBraveLanguageParams({