mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 02:26:22 +00:00
fix(agents): bound native pdf error bodies
This commit is contained in:
@@ -112,6 +112,65 @@ describe("native PDF provider API calls", () => {
|
||||
).rejects.toThrow("Anthropic PDF request failed");
|
||||
});
|
||||
|
||||
it("bounds large Anthropic API error bodies", async () => {
|
||||
let canceled = false;
|
||||
const body = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode(`${"x".repeat(9_000)}tail-marker`));
|
||||
},
|
||||
cancel() {
|
||||
canceled = true;
|
||||
},
|
||||
});
|
||||
mockFetchResponse(
|
||||
new Response(body, {
|
||||
status: 400,
|
||||
statusText: "Bad Request",
|
||||
}),
|
||||
);
|
||||
|
||||
const error = await pdfNativeProviders
|
||||
.anthropicAnalyzePdf(makeAnthropicAnalyzeParams())
|
||||
.catch((caught: unknown) => caught as Error);
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toContain("Anthropic PDF request failed");
|
||||
expect(error.message).not.toContain("tail-marker");
|
||||
expect(error.message.length).toBeLessThan(500);
|
||||
expect(canceled).toBe(true);
|
||||
});
|
||||
|
||||
it("cancels Anthropic API error bodies that exactly fill the byte cap", async () => {
|
||||
let canceled = false;
|
||||
const body = new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode("x".repeat(8 * 1024)));
|
||||
},
|
||||
cancel() {
|
||||
canceled = true;
|
||||
},
|
||||
});
|
||||
mockFetchResponse(
|
||||
new Response(body, {
|
||||
status: 400,
|
||||
statusText: "Bad Request",
|
||||
}),
|
||||
);
|
||||
|
||||
const error = await Promise.race([
|
||||
pdfNativeProviders
|
||||
.anthropicAnalyzePdf(makeAnthropicAnalyzeParams())
|
||||
.catch((caught: unknown) => caught as Error),
|
||||
new Promise<Error>((_resolve, reject) => {
|
||||
setTimeout(() => reject(new Error("timed out waiting for bounded error body")), 500);
|
||||
}),
|
||||
]);
|
||||
|
||||
expect(error).toBeInstanceOf(Error);
|
||||
expect(error.message).toContain("Anthropic PDF request failed");
|
||||
expect(canceled).toBe(true);
|
||||
});
|
||||
|
||||
it("anthropicAnalyzePdf throws when response has no text", async () => {
|
||||
mockFetchResponse({
|
||||
ok: true,
|
||||
|
||||
@@ -13,6 +13,59 @@ type PdfInput = {
|
||||
};
|
||||
|
||||
const NATIVE_PDF_PROVIDER_FETCH_TIMEOUT_MS = 120_000;
|
||||
const NATIVE_PDF_ERROR_BODY_MAX_BYTES = 8 * 1024;
|
||||
const NATIVE_PDF_ERROR_BODY_MAX_CHARS = 400;
|
||||
|
||||
async function readErrorBodySnippet(res: Response): Promise<string> {
|
||||
try {
|
||||
const body = res.body;
|
||||
if (!body || typeof body.getReader !== "function") {
|
||||
return (await res.text()).slice(0, NATIVE_PDF_ERROR_BODY_MAX_CHARS);
|
||||
}
|
||||
|
||||
const reader = body.getReader();
|
||||
const chunks: Uint8Array[] = [];
|
||||
let total = 0;
|
||||
let truncated = false;
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done || !value?.byteLength) {
|
||||
break;
|
||||
}
|
||||
const remaining = NATIVE_PDF_ERROR_BODY_MAX_BYTES - total;
|
||||
if (remaining <= 0) {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
if (value.byteLength > remaining) {
|
||||
chunks.push(value.subarray(0, remaining));
|
||||
total += remaining;
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
chunks.push(value);
|
||||
total += value.byteLength;
|
||||
if (total >= NATIVE_PDF_ERROR_BODY_MAX_BYTES) {
|
||||
truncated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (truncated) {
|
||||
await reader.cancel().catch(() => undefined);
|
||||
}
|
||||
try {
|
||||
reader.releaseLock();
|
||||
} catch {}
|
||||
}
|
||||
return new TextDecoder()
|
||||
.decode(Buffer.concat(chunks, total))
|
||||
.slice(0, NATIVE_PDF_ERROR_BODY_MAX_CHARS);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Anthropic – native PDF via Messages API
|
||||
@@ -80,9 +133,9 @@ export async function anthropicAnalyzePdf(params: {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.text().catch(() => "");
|
||||
const body = await readErrorBodySnippet(res);
|
||||
throw new Error(
|
||||
`Anthropic PDF request failed (${res.status} ${res.statusText})${body ? `: ${body.slice(0, 400)}` : ""}`,
|
||||
`Anthropic PDF request failed (${res.status} ${res.statusText})${body ? `: ${body}` : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,9 +218,9 @@ export async function geminiAnalyzePdf(params: {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.text().catch(() => "");
|
||||
const body = await readErrorBodySnippet(res);
|
||||
throw new Error(
|
||||
`Gemini PDF request failed (${res.status} ${res.statusText})${body ? `: ${body.slice(0, 400)}` : ""}`,
|
||||
`Gemini PDF request failed (${res.status} ${res.statusText})${body ? `: ${body}` : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user