Description
The Anthropic Messages route (POST /v1/messages, providers/proxy_routes.py) does not honor the x-headroom-base-url per-request upstream override header. The OpenAI-compatible routes already read this header via _resolve_openai_upstream_base / x-headroom-original-path (proxy/handlers/openai.py), and the generic passthrough route honors it too (providers/proxy_routes.py:953), but handle_anthropic_messages is always called without upstream_base_url, so it unconditionally forwards to api.anthropic.com.
This breaks any client that speaks the Anthropic Messages wire format but authenticates against a different, non-Anthropic gateway — for example OpenCode's opencode-go provider (GLM-5.2 / MiniMax served through OpenCode Zen's "Go" tier, https://opencode.ai/zen/go). The client sends anthropic-version / x-api-key exactly like a native Anthropic client, headroom forwards it to api.anthropic.com, and the (Zen) API key is rejected there.
To Reproduce
- Point an Anthropic-Messages-format client at the headroom proxy with a per-request upstream override:
curl -i http://127.0.0.1:8787/v1/messages \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-H "x-headroom-base-url: https://opencode.ai/zen/go" \
-H "x-api-key: <a Zen Go API key>" \
-d '{"model":"glm-5.2","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'
- Observe the proxy log: the request is forwarded to
https://api.anthropic.com/v1/messages, not https://opencode.ai/zen/go/v1/messages.
- The upstream (real Anthropic) rejects the Zen key.
Expected Behavior
When x-headroom-base-url is present on a /v1/messages request, the proxy should forward to <x-headroom-base-url>/v1/messages, the same way the OpenAI-compatible handlers and the generic passthrough route already honor the header.
Actual Behavior
x-headroom-base-url is ignored on /v1/messages; the request always goes to the hardcoded ANTHROPIC_API_URL, and the response is:
{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}
(the real Anthropic API rejecting a key meant for a different gateway).
Code Sample
# providers/proxy_routes.py — current
@app.post("/v1/messages")
async def anthropic_messages(request: Request):
return await proxy.handle_anthropic_messages(request)
handle_anthropic_messages(request, upstream_base_url=None, ...) already supports an upstream_base_url override (proxy/handlers/anthropic.py) and, when passed, builds the upstream URL via build_copilot_upstream_url(upstream_base_url, request.url.path) — the same helper the Azure Anthropic routing path already reuses. The route just never passes it.
Local workaround (confirmed working) — read x-headroom-base-url in the route and pass it through:
@app.post("/v1/messages")
async def anthropic_messages(request: Request):
custom_base = request.headers.get("x-headroom-base-url", "").strip()
if custom_base:
return await proxy.handle_anthropic_messages(request, upstream_base_url=custom_base)
return await proxy.handle_anthropic_messages(request)
Error Output
HTTP/1.1 401
{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}
Environment
- Headroom version: 0.29.0 (pipx)
- Python version: 3.12.13
- OS: macOS 26.5.1
- LLM Provider: OpenCode Zen "Go" tier (
opencode-go provider, GLM-5.2 / MiniMax), Anthropic-Messages wire format
Additional Context
Related but distinct from #1503 (dedicated OpenAI handlers ignoring x-headroom-base-url) — this is the Anthropic Messages equivalent of that gap, and appears unreported. Also related to #1651 (which covers the OpenAI-compatible route, already fixed by #1596).
build_copilot_upstream_url is already a generic base+path concatenation helper reused for Vertex AI Anthropic routing (proxy_routes.py), so wiring it into /v1/messages should be a small, low-risk change consistent with the existing OpenAI-route pattern.
Description
The Anthropic Messages route (
POST /v1/messages,providers/proxy_routes.py) does not honor thex-headroom-base-urlper-request upstream override header. The OpenAI-compatible routes already read this header via_resolve_openai_upstream_base/x-headroom-original-path(proxy/handlers/openai.py), and the generic passthrough route honors it too (providers/proxy_routes.py:953), buthandle_anthropic_messagesis always called withoutupstream_base_url, so it unconditionally forwards toapi.anthropic.com.This breaks any client that speaks the Anthropic Messages wire format but authenticates against a different, non-Anthropic gateway — for example OpenCode's
opencode-goprovider (GLM-5.2 / MiniMax served through OpenCode Zen's "Go" tier,https://opencode.ai/zen/go). The client sendsanthropic-version/x-api-keyexactly like a native Anthropic client, headroom forwards it toapi.anthropic.com, and the (Zen) API key is rejected there.To Reproduce
https://api.anthropic.com/v1/messages, nothttps://opencode.ai/zen/go/v1/messages.Expected Behavior
When
x-headroom-base-urlis present on a/v1/messagesrequest, the proxy should forward to<x-headroom-base-url>/v1/messages, the same way the OpenAI-compatible handlers and the generic passthrough route already honor the header.Actual Behavior
x-headroom-base-urlis ignored on/v1/messages; the request always goes to the hardcodedANTHROPIC_API_URL, and the response is:(the real Anthropic API rejecting a key meant for a different gateway).
Code Sample
handle_anthropic_messages(request, upstream_base_url=None, ...)already supports anupstream_base_urloverride (proxy/handlers/anthropic.py) and, when passed, builds the upstream URL viabuild_copilot_upstream_url(upstream_base_url, request.url.path)— the same helper the Azure Anthropic routing path already reuses. The route just never passes it.Local workaround (confirmed working) — read
x-headroom-base-urlin the route and pass it through:Error Output
Environment
opencode-goprovider, GLM-5.2 / MiniMax), Anthropic-Messages wire formatAdditional Context
Related but distinct from #1503 (dedicated OpenAI handlers ignoring
x-headroom-base-url) — this is the Anthropic Messages equivalent of that gap, and appears unreported. Also related to #1651 (which covers the OpenAI-compatible route, already fixed by #1596).build_copilot_upstream_urlis already a generic base+path concatenation helper reused for Vertex AI Anthropic routing (proxy_routes.py), so wiring it into/v1/messagesshould be a small, low-risk change consistent with the existing OpenAI-route pattern.