mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
refactor(brave): share provider search lifecycle (#129046)
This commit is contained in:
committed by
GitHub
parent
27c0c8582c
commit
88f2b8b755
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user