Fix: fetch tool PDFs server-side and attach as base64 (fixes Ultimaker 400)#14
Conversation
The chat route handed Anthropic the PDF *URL* via `data: new URL(pdfUrl)`,
letting Anthropic's server fetch it. Hosts with a WAF/bot rule (e.g. the
Ultimaker SOP on um-support-files.ultimaker.com) block that fetcher, so
Anthropic returned a 400 ("Unable to download the file") that took down the
entire chat request.
Now each manual is fetched from the Vercel function itself (browser-like UA,
8s timeout), capped at 10MB, base64-encoded, and sent as the `data` of the
file part. Any PDF that fails (non-200, timeout, too large, network error) is
skipped rather than attached, so the model falls back to `web_fetch` for it
and one bad PDF can no longer 400 the whole conversation. Collection is fully
guarded so it can never throw up to the request.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 10e9e244db
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| import type { ResourceRecord } from "../../../lib/types"; | ||
|
|
||
| const MAX_PDFS_PER_CHAT = 3; | ||
| const MAX_PDF_BYTES = 10 * 1024 * 1024; // 10MB ceiling |
There was a problem hiding this comment.
When a focused tool has three PDFs that each pass this 10 MB per-file check, attachManualsToFirstUserMessage sends roughly 40 MB of base64 before adding the system prompt and tool definitions. Anthropic's Messages API standard request limit is 32 MB, so those chats will fail with 413 request_too_large instead of falling back to web_fetch. Please enforce an aggregate encoded/request-size budget or lower the per-file/count limits so multiple valid attachments cannot exceed the provider limit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR updates the v5 chat API to avoid provider-side PDF URL fetching failures (e.g., WAF-blocked manufacturer hosts) by fetching tool PDF manuals server-side, base64-encoding them, and attaching the bytes directly to the first user message—while gracefully falling back to web_fetch when attachment fails.
Changes:
- Added server-side PDF fetching with browser-like
User-Agent, timeout, and per-PDF size cap; failures are skipped. - Updated manual collection to return both attached manuals and a skipped count, with new logging.
- Switched
FilePart.datafromnew URL(...)to base64 bytes for attached PDFs.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const res = await fetch(url, { | ||
| headers: { "User-Agent": PDF_FETCH_UA }, | ||
| signal: AbortSignal.timeout(8000), | ||
| }); |
| `status ${res.status}` | ||
| ); | ||
| return null; | ||
| } |
| const buf = await res.arrayBuffer(); | ||
| if (buf.byteLength > MAX_PDF_BYTES) { | ||
| console.warn( | ||
| "[chat] PDF too large, will rely on web_fetch:", | ||
| title, | ||
| url, | ||
| `${buf.byteLength} bytes` | ||
| ); | ||
| return null; | ||
| } |
| console.info(`[chat] PDF cap reached (${MAX_PDFS_PER_CHAT}); skipping: ${r.fields.title}`); | ||
| continue; | ||
| if (manuals.length >= MAX_PDFS_PER_CHAT) { | ||
| console.info(`[chat] PDF cap reached (${MAX_PDFS_PER_CHAT}); skipping: ${r.fields.title}`); |
Summary
User-Agentand an 8sAbortSignal.timeout, base64-encode them, and send them as thedataof thefilecontent part — instead of handing Anthropic the PDF URL viadata: new URL(...).web_fetch.web_fetchtool. The "(attached)" marker in the system prompt now reflects only PDFs actually base64-encoded.[chat] manuals attached: N(actual count) and a new[chat] manuals skipped (will web_fetch): M.Root cause
The route attached manuals with
data: new URL(pdfUrl), so Anthropic's server fetched the PDF. The Ultimaker S5 SOP lives onum-support-files.ultimaker.com, whose WAF/bot rule blocks Anthropic's fetcher. Anthropic then returned400 invalid_request_error: "Unable to download the file. Please verify the URL and try again.", which failed the entire chat request (broken chat for the user). Same risk applied to Trotec and any other manufacturer host. One un-fetchable PDF took down the whole conversation.Test plan
npm run buildinv5/— compiles + TypeScript pass clean.FilePart.dataaccepts a base64 string (also Uint8Array/ArrayBuffer/Buffer/URL); used base64 string.web_fetch.manuals attached/manuals skippedcounts.🤖 Generated with Claude Code