@@ -13,6 +13,59 @@ type PdfInput = {
|
13 | 13 | }; |
14 | 14 | |
15 | 15 | const NATIVE_PDF_PROVIDER_FETCH_TIMEOUT_MS = 120_000; |
| 16 | +const NATIVE_PDF_ERROR_BODY_MAX_BYTES = 8 * 1024; |
| 17 | +const NATIVE_PDF_ERROR_BODY_MAX_CHARS = 400; |
| 18 | + |
| 19 | +async function readErrorBodySnippet(res: Response): Promise<string> { |
| 20 | +try { |
| 21 | +const body = res.body; |
| 22 | +if (!body || typeof body.getReader !== "function") { |
| 23 | +return (await res.text()).slice(0, NATIVE_PDF_ERROR_BODY_MAX_CHARS); |
| 24 | +} |
| 25 | + |
| 26 | +const reader = body.getReader(); |
| 27 | +const chunks: Uint8Array[] = []; |
| 28 | +let total = 0; |
| 29 | +let truncated = false; |
| 30 | +try { |
| 31 | +while (true) { |
| 32 | +const { done, value } = await reader.read(); |
| 33 | +if (done || !value?.byteLength) { |
| 34 | +break; |
| 35 | +} |
| 36 | +const remaining = NATIVE_PDF_ERROR_BODY_MAX_BYTES - total; |
| 37 | +if (remaining <= 0) { |
| 38 | +truncated = true; |
| 39 | +break; |
| 40 | +} |
| 41 | +if (value.byteLength > remaining) { |
| 42 | +chunks.push(value.subarray(0, remaining)); |
| 43 | +total += remaining; |
| 44 | +truncated = true; |
| 45 | +break; |
| 46 | +} |
| 47 | +chunks.push(value); |
| 48 | +total += value.byteLength; |
| 49 | +if (total >= NATIVE_PDF_ERROR_BODY_MAX_BYTES) { |
| 50 | +truncated = true; |
| 51 | +break; |
| 52 | +} |
| 53 | +} |
| 54 | +} finally { |
| 55 | +if (truncated) { |
| 56 | +await reader.cancel().catch(() => undefined); |
| 57 | +} |
| 58 | +try { |
| 59 | +reader.releaseLock(); |
| 60 | +} catch {} |
| 61 | +} |
| 62 | +return new TextDecoder() |
| 63 | +.decode(Buffer.concat(chunks, total)) |
| 64 | +.slice(0, NATIVE_PDF_ERROR_BODY_MAX_CHARS); |
| 65 | +} catch { |
| 66 | +return ""; |
| 67 | +} |
| 68 | +} |
16 | 69 | |
17 | 70 | // --------------------------------------------------------------------------- |
18 | 71 | // Anthropic – native PDF via Messages API |
@@ -80,9 +133,9 @@ export async function anthropicAnalyzePdf(params: {
|
80 | 133 | }); |
81 | 134 | |
82 | 135 | if (!res.ok) { |
83 | | -const body = await res.text().catch(() => ""); |
| 136 | +const body = await readErrorBodySnippet(res); |
84 | 137 | throw new Error( |
85 | | -`Anthropic PDF request failed (${res.status} ${res.statusText})${body ? `: ${body.slice(0, 400)}` : ""}`, |
| 138 | +`Anthropic PDF request failed (${res.status} ${res.statusText})${body ? `: ${body}` : ""}`, |
86 | 139 | ); |
87 | 140 | } |
88 | 141 | |
@@ -165,9 +218,9 @@ export async function geminiAnalyzePdf(params: {
|
165 | 218 | }); |
166 | 219 | |
167 | 220 | if (!res.ok) { |
168 | | -const body = await res.text().catch(() => ""); |
| 221 | +const body = await readErrorBodySnippet(res); |
169 | 222 | throw new Error( |
170 | | -`Gemini PDF request failed (${res.status} ${res.statusText})${body ? `: ${body.slice(0, 400)}` : ""}`, |
| 223 | +`Gemini PDF request failed (${res.status} ${res.statusText})${body ? `: ${body}` : ""}`, |
171 | 224 | ); |
172 | 225 | } |
173 | 226 | |
|