feat: auto-route image turns to Anthropic Max OAuth (env-gated)#19
Closed
BenSheridanEdwards wants to merge 4 commits into
Closed
feat: auto-route image turns to Anthropic Max OAuth (env-gated)#19BenSheridanEdwards wants to merge 4 commits into
BenSheridanEdwards wants to merge 4 commits into
Conversation
DeepSeek's anthropic-compat endpoint returns 400 on requests containing image content blocks; OpenRouter and Fireworks reject them similarly or silently ignore the pixels. Pre-translating images to text loses the detail that vision is actually for (UI screenshots, diagrams, OCR). Detect image content in the outbound request body and re-route that single request to api.anthropic.com, leaving Claude Code's OAuth bearer intact so it consumes the user's Max subscription quota rather than a backend API key. Behavior: - `containsImageBlock` walks `tool_result.content[]` recursively because Claude Code's Read tool wraps image bytes in a tool_result rather than at the top of the message. - Routing is now decided after the body is read (previously decided pre-body) so image detection can flip a single request. - On reroute: drop `thinking` and `context_management` from the body — Anthropic 400s with "clear_thinking_20251015 strategy requires thinking to be enabled" otherwise. - Cost tracking uses `effectiveMode` so image turns are bucketed under `anthropic` rather than the resident backend. - Strip outbound `accept-encoding` and inbound `content-encoding` on proxy-mutated paths (UsageNormalizer / normalizeJsonBody). Without this, Anthropic gzipped responses break the client with `Decompression error: ZlibError`. Loopback compression buys nothing. Gate: - `DEEPCLAUDE_IMAGE_FALLBACK=anthropic` (default) — image turns reroute. - `DEEPCLAUDE_IMAGE_FALLBACK=off` — no reroute; image turns will 400 from the backend as before. Closes aattaran#12.
Five JSON.parse(body) passes in the request end-handler collapse to one parse + one stringify; all mutations (image-reroute thinking/context_management drop, model remap, thinking-block strip) operate on the same parsed object. Image-rerouted turns get a dedicated `anthropic_max` pricing bucket with cost=0 (Max consumes subscription quota, not per-token). total_cost no longer overstates spend on image turns; anthropic_equivalent still flows into savings via PRICING_PER_M.anthropic. DEEPCLAUDE_IMAGE_FALLBACK is read once at module load (IMAGE_FALLBACK_ENABLED) instead of per-request. trackUsage is declared near the routing decisions so the upstream-routing log reuses it instead of recomputing isModelCall || forceAnthropicForImage. The OAuth-bearer passthrough comment now flags the x-api-key 401 failure mode. No behavior change for the routing logic itself. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the 13-line block comment above the body-mutation block with two short inline comments above the phases whose `why` is non-obvious (image-reroute thinking/context_management drop, thinking-block strip rationale). Model-remap needs no comment — MODEL_REMAP is the doc. Add blank lines between the three mutation phases for visual separation. Switch `parsed != null` to `parsed` to match the file's idiom (body?.messages elsewhere). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BenSheridanEdwards
added a commit
to BenSheridanEdwards/DeepClaude
that referenced
this pull request
May 7, 2026
Plain `deepclaude` (without --remote) previously pointed Claude Code straight at api.deepseek.com, bypassing the proxy entirely — which made PR aattaran#19's image fallback unreachable from the default launch path. Wire launch_claude through the proxy: - Symlink-resolving SCRIPT_DIR so $SCRIPT_DIR/proxy/start-proxy.js works when deepclaude is invoked via ~/.local/bin/deepclaude symlink. - New `start_proxy` helper that spawns proxy/start-proxy.js, polls a per-instance log file (PROXY_LOG=/tmp/deepclaude-proxy.$$.log) for the bare-numeric port line, and sets PROXY_PID/PROXY_PORT/PROXY_LOG as script globals so the EXIT trap can clean up. Must be called without $() — comment makes that load-bearing. - launch_claude calls start_proxy, sets ANTHROPIC_BASE_URL to the local proxy port, unsets ANTHROPIC_AUTH_TOKEN so Claude Code's OAuth bearer flows through to the proxy (and onward to api.anthropic.com on image-reroute), and drops `exec` so the EXIT trap actually fires. Switch ANTHROPIC_DEFAULT_*_MODEL to canonical claude-* names. Required because Claude Code v2.1.132 rejects non-Claude model names client-side when ANTHROPIC_AUTH_TOKEN is unset (OAuth path). The proxy translates canonical names to backend-specific names on the wire via MODEL_REMAP. Pass backend long-name as third argv to start-proxy.js so the proxy starts in named mode (e.g. `deepseek`) rather than `_single` — MODEL_REMAP['_single'] is undefined, so without this the canonical names would be forwarded as-is and DeepSeek would 404. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DeepSeek's anthropic-compat endpoint returns 400 on requests containing image content blocks; OpenRouter and Fireworks reject them similarly or silently ignore the pixels. Pre-translating images to text loses the detail that vision is actually for (UI screenshots, diagrams, OCR).
Detect image content in the outbound request body and re-route that single request to api.anthropic.com, leaving Claude Code's OAuth bearer intact so it consumes the user's Max subscription quota rather than a backend API key.
Behavior:
containsImageBlockwalkstool_result.content[]recursively because Claude Code's Read tool wraps image bytes in a tool_result rather than at the top of the message.thinkingandcontext_managementfrom the body — Anthropic 400s with "clear_thinking_20251015 strategy requires thinking to be enabled" otherwise.effectiveModeso image turns are bucketed underanthropicrather than the resident backend.accept-encodingand inboundcontent-encodingon proxy-mutated paths (UsageNormalizer / normalizeJsonBody). Without this, Anthropic gzipped responses break the client withDecompression error: ZlibError. Loopback compression buys nothing.Gate:
DEEPCLAUDE_IMAGE_FALLBACK=anthropic(default) — image turns reroute.DEEPCLAUDE_IMAGE_FALLBACK=off— no reroute; image turns will 400 from the backend as before.Closes #12.