fix(anthropic): preserve base URL path prefix for PDF document requests#325
fix(anthropic): preserve base URL path prefix for PDF document requests#325wangwllu wants to merge 2 commits into
Conversation
completeAnthropicDocument built its request URL with
new URL("/v1/messages", baseUrl), which treats the path as
root-relative and discards any prefix on a custom ANTHROPIC_BASE_URL.
A gateway exposed at https://host/anthropic would lose /anthropic and
POST to https://host/v1/messages, returning 400/404.
Join onto the base instead, mirroring completeGoogleDocument in
google.ts. The default https://api.anthropic.com path is unchanged.
Adds a regression test asserting the request URL for a path-prefixed
override, a trailing-slash override, and the default base.
|
Codex review: needs maintainer review before merge. Reviewed June 24, 2026, 11:18 AM ET / 15:18 UTC. Summary Reproducibility: yes. source inspection plus a focused Node URL check confirms current Review metrics: 2 noteworthy metrics.
Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Preserve the prefixed-gateway PDF fix while making the Anthropic base URL contract explicit: merge as written for SDK/text-path parity, or normalize already-versioned bases consistently across both text and document paths before merge. Do we have a high-confidence way to reproduce the issue? Yes: source inspection plus a focused Node URL check confirms current Is this the best way to solve the issue? Mostly yes: the changed line is the narrow right place for the path-prefix bug and matches the existing Google document join pattern. The remaining question is whether SDK/text-path parity for already-versioned Anthropic bases is the contract maintainers want to ship. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 75c8fb594629. Label changesLabel justifications:
Evidence reviewedWhat I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
… path Adds a regression case asserting that an ANTHROPIC_BASE_URL already ending in /v1 resolves to /v1/v1/messages, matching what the streaming text path produces via @anthropic-ai/sdk (which concatenates baseURL + /v1/messages). This documents that the document path keeps parity with the SDK path rather than special-casing /v1 only for PDFs.
|
Thanks for the review. Addressed both points:
So a document-only |
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
Fixes #324.
Problem
completeAnthropicDocument()(the PDF document path for Anthropic models) built its request URL with:new URL("/v1/messages", base)treats the path as root-relative, discarding any path prefix on a customANTHROPIC_BASE_URL. A gateway exposed athttps://host/anthropicwould lose/anthropicand POST tohttps://host/v1/messages→ 400/404. The defaulthttps://api.anthropic.com(no prefix) is unaffected, and the streaming/text path (via pi-ai, which concatenatesbaseURL + path) was never affected — only this hand-rolledfetch.Fix
Join onto the base instead, mirroring
completeGoogleDocumentinsrc/llm/providers/google.tswhich already does this correctly:https://api.anthropic.com→https://api.anthropic.com/v1/messages(unchanged)https://host/anthropic→https://host/anthropic/v1/messages(fixed)https://host/anthropic/→ same (trailing slash stripped)This keeps the existing contract shared with
google.ts:ANTHROPIC_BASE_URLis a provider/gateway base (host + optional prefix) onto which the code appends/v1/messages, not a fully-versioned Messages endpoint. The default remains exactly correct.Test
Adds
tests/llm.anthropic-document-url.test.ts(mirroring thetests/llm.google-document.test.tsmockFetch style) asserting the captured request URL for:https://gateway.example/anthropic),Verification
vitest run— new test (3) +llm.google-document(4) pass.tsgo) +oxlintclean.distand tested end-to-end against a live Anthropic-compatible gateway exposed under/anthropic: PDF summarize went fromAnthropic API error (400)(before) to a real summary (after).Scope
One-line change in
src/llm/providers/anthropic.ts+ a regression test. I deliberately kept this minimal rather than refactoringgoogle.tsandanthropic.tsinto a sharedjoinUrl()helper; happy to do that as a follow-up if you'd prefer.Behavior proof (redacted, generic example values)
A local logging proxy stood in for the gateway and printed the path it received before forwarding. Same PDF input, same config; only the build differs:
(Tested against
@anthropic-ai/sdk@0.91.1/pi-ai@0.80.2, the versions this PR ships with.)On the
/v1-base shapeThis PR intentionally does not special-case a base already ending in
/v1. The streaming/text path storesANTHROPIC_BASE_URLverbatim as the modelbaseUrl(resolveAnthropicModel,src/llm/providers/models.ts) and lets@anthropic-ai/sdkappend/v1/messages, so it produces the same URLs the document path now produces — including/v1/v1/messagesfor a/v1base. Verified end-to-end against the real SDK:ANTHROPIC_BASE_URLhttps://api.anthropic.com…/v1/messageshttps://anthropic.example/v1…/v1/v1/messageshttps://gateway.example/anthropic…/anthropic/v1/messageshttps://gateway.example/anthropic/…/anthropic/v1/messagesSo
/v1was never a working end-to-end contract on the dominant Anthropic path; the document path only appeared to tolerate it because the oldnew URL(absolute, base)discarded the path. A document-only/v1-as-root heuristic would make PDF summaries diverge from text/streaming for the same base. The added test documents this parity (including the/v1case) so it's explicit, not accidental. Happy to normalize the base once for both paths as a separate change if maintainers prefer supporting endpoint-style/v1overrides.