fix(brave): bound search error bodies

This commit is contained in:
Vincent Koc
2026-05-28 13:28:26 +02:00
parent 259796dc3d
commit eb273a8a4a
2 changed files with 83 additions and 13 deletions

View File

@@ -1,4 +1,7 @@
import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http";
import {
assertOkOrThrowProviderError,
readProviderJsonResponse,
} from "openclaw/plugin-sdk/provider-http";
import type { SearchConfigRecord } from "openclaw/plugin-sdk/provider-web-search";
import {
buildSearchCacheKey,
@@ -225,12 +228,7 @@ async function runBraveLlmContextSearch(params: {
ok: response.ok,
durationMs: Date.now() - startedAt,
});
if (!response.ok) {
const detail = await response.text();
throw new Error(
`Brave LLM Context API error (${response.status}): ${detail || response.statusText}`,
);
}
await assertOkOrThrowProviderError(response, "Brave LLM Context API error");
const data = await readProviderJsonResponse<BraveLlmContextResponse>(
response,
@@ -312,12 +310,7 @@ async function runBraveWebSearch(params: {
ok: response.ok,
durationMs: Date.now() - startedAt,
});
if (!response.ok) {
const detail = await response.text();
throw new Error(
`Brave Search API error (${response.status}): ${detail || response.statusText}`,
);
}
await assertOkOrThrowProviderError(response, "Brave Search API error");
const data = await readProviderJsonResponse<BraveSearchResponse>(
response,

View File

@@ -88,6 +88,23 @@ function fetchRequestInit(mockFetch: { mock: { calls: Array<Array<unknown>> } },
return fetchCall(mockFetch, index)[1];
}
function createBodyOnlyErrorResponse(params: { body: string; status: number }): Response {
const bytes = new TextEncoder().encode(params.body);
const body = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(bytes);
controller.close();
},
});
return {
ok: false,
status: params.status,
statusText: "Too Many Requests",
headers: new Headers(),
body,
} as Response;
}
describe("brave web search provider", () => {
const priorFetch = global.fetch;
@@ -347,6 +364,66 @@ describe("brave web search provider", () => {
);
});
it("bounds Brave web error bodies without using response.text", async () => {
vi.stubEnv("BRAVE_API_KEY", "");
const mockFetch = vi.fn(async (_input?: unknown, _init?: unknown) =>
createBodyOnlyErrorResponse({
status: 429,
body: `${"x".repeat(24 * 1024)}tail-marker`,
}),
);
global.fetch = mockFetch as typeof global.fetch;
const provider = createBraveWebSearchProvider();
const tool = provider.createTool({
config: {},
searchConfig: {
apiKey: "brave-test-key",
brave: { mode: "web" },
},
});
if (!tool) {
throw new Error("Expected tool definition");
}
const error = await tool.execute({ query: "latest ai news" }).catch((value: unknown) => value);
expect(error).toBeInstanceOf(Error);
const message = error instanceof Error ? error.message : String(error);
expect(message).toContain("Brave Search API error (429):");
expect(message).not.toContain("tail-marker");
expect(message.length).toBeLessThan(700);
});
it("bounds Brave llm-context error bodies without using response.text", async () => {
vi.stubEnv("BRAVE_API_KEY", "");
const mockFetch = vi.fn(async (_input?: unknown, _init?: unknown) =>
createBodyOnlyErrorResponse({
status: 429,
body: `${"x".repeat(24 * 1024)}tail-marker`,
}),
);
global.fetch = mockFetch as typeof global.fetch;
const provider = createBraveWebSearchProvider();
const tool = provider.createTool({
config: {},
searchConfig: {
apiKey: "brave-test-key",
brave: { mode: "llm-context" },
},
});
if (!tool) {
throw new Error("Expected tool definition");
}
const error = await tool.execute({ query: "latest ai news" }).catch((value: unknown) => value);
expect(error).toBeInstanceOf(Error);
const message = error instanceof Error ? error.message : String(error);
expect(message).toContain("Brave LLM Context API error (429):");
expect(message).not.toContain("tail-marker");
expect(message.length).toBeLessThan(700);
});
it("keeps Brave cache entries isolated by baseUrl", async () => {
vi.stubEnv("BRAVE_API_KEY", "");
const mockFetch = vi.fn(async (_input?: unknown, _init?: unknown) => {