Skip to content

Fix: fetch tool PDFs server-side and attach as base64 (fixes Ultimaker 400)#14

Merged
philosophercode merged 1 commit into
mainfrom
philosophercode/fix-pdf-attach-base64
May 28, 2026
Merged

Fix: fetch tool PDFs server-side and attach as base64 (fixes Ultimaker 400)#14
philosophercode merged 1 commit into
mainfrom
philosophercode/fix-pdf-attach-base64

Conversation

@philosophercode

Copy link
Copy Markdown
Owner

Summary

  • Fetch each focused tool's PDF manuals server-side (from the Vercel function) with a browser-like User-Agent and an 8s AbortSignal.timeout, base64-encode them, and send them as the data of the file content part — instead of handing Anthropic the PDF URL via data: new URL(...).
  • Enforce a 10MB ceiling per PDF; oversized PDFs are skipped (logged) and left to web_fetch.
  • Per-PDF graceful fallback: any fetch that fails (non-200, timeout, too large, network error) is skipped, not attached, so the model reaches it via the existing web_fetch tool. The "(attached)" marker in the system prompt now reflects only PDFs actually base64-encoded.
  • Outer guard around manual collection so it can never throw up to the request — chat proceeds with zero attachments + web_fetch on total failure.
  • Logs [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 on um-support-files.ultimaker.com, whose WAF/bot rule blocks Anthropic's fetcher. Anthropic then returned 400 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 build in v5/ — compiles + TypeScript pass clean.
  • Context7-confirmed AI SDK v6 FilePart.data accepts a base64 string (also Uint8Array/ArrayBuffer/Buffer/URL); used base64 string.
  • Smoke-test on a preview deploy: open chat on the Ultimaker S5 tool — chat should respond (no 400), with the SOP either attached or reached via web_fetch.
  • Confirm logs show manuals attached / manuals skipped counts.

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings May 28, 2026 22:08
@vercel

vercel Bot commented May 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
makerlab-tools Error Error May 28, 2026 10:09pm
makerlab-tools-g4gb Ready Ready Preview, Comment May 28, 2026 10:09pm

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cap total attached PDF bytes

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 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.data from new URL(...) to base64 bytes for attached PDFs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +300 to +303
const res = await fetch(url, {
headers: { "User-Agent": PDF_FETCH_UA },
signal: AbortSignal.timeout(8000),
});
`status ${res.status}`
);
return null;
}
Comment on lines +313 to +322
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}`);
@philosophercode philosophercode merged commit 7324d4f into main May 28, 2026
3 of 4 checks passed
@philosophercode philosophercode deleted the philosophercode/fix-pdf-attach-base64 branch May 28, 2026 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants