A grounded comparison of shunt against the tools it sits closest to. The goal is to make shunt’s design boundaries explicit: what it deliberately does not do, and where the real, in-scope improvement opportunities are.
1. What shunt is (and isn’t)
shunt is a spec-compliant Claude Code LLM gateway: it implements Claude Code’s official ANTHROPIC_BASE_URL gateway contract (/v1/messages, /v1/models discovery, attribution/header pass-through) and does selective, per-model-id diversion — keep the main session on Claude, divert only the models you name onto another provider (ChatGPT/Codex, OpenAI, xAI). It translates Anthropic Messages ⇄ the OpenAI Responses API for mapped models, and passes everything else through to Anthropic unchanged. Routing is purely by the request’s model id — no prompt-shape fingerprinting (README.md:104-131).
That focus is the axis every comparison below turns on. shunt optimizes for translation fidelity and Claude-Code-native behavior, with Anthropic and ChatGPT/Codex OAuth account pools that combine model-aware proactive quota rotation with reactive failover rather than broad multi-tenant fleet operation.
2. The peer groups
| Group | Examples | Relationship to shunt |
|---|---|---|
| Subscription-backed CC proxy (same class) | raine/claude-code-proxy | Closest peer overall — Rust single binary, per-model routing, Codex WebSocket + previous_response_id continuation, 4 subscription-OAuth backends (Codex/Kimi/Grok/Cursor) |
| Broad Claude-Code proxy w/ Codex OAuth | CLIProxyAPI (router-for-me) | Closest broad peer — overlaps on Codex/ChatGPT OAuth, Codex WebSocket v2, tool translation |
| Narrow Claude Code → Codex swap | insightflo/chatgpt-codex-proxy | Same inference-layer swap, single backend |
| General Claude-Code routers | musistudio/claude-code-router, 1rgs/claude-code-proxy, fuergaosi233/claude-code-proxy | Usually a global model swap, not per-model diversion |
| General AI gateways | LiteLLM, Portkey, bifrost, modelgate | Adjacent infra — possible backends, not Claude-Code-native |
3. Feature matrix
Legend: ● full · ◐ partial / workaround · ○ none · — n/a by design
| Capability | shunt | raine/ccp | CLIProxyAPI | Gen. CC routers | Gen. gateways |
|---|---|---|---|---|---|
Claude Code gateway-protocol compliance (/v1/models discovery, attribution pass-through) |
● | ◐ | ◐ | ◐ | ○ |
Selective per-model-id diversion, main session stays on Claude (not a global swap) |
● | ◐³ | ◐ | ○ | ◐ |
| Anthropic Messages ⇄ OpenAI Responses translation | ● | ● | ● | ◐ (mostly chat-completions) | ◐ (chat-completions) |
| ChatGPT/Codex subscription (OAuth) backend | ● | ●⁴ | ● | rare | ○ |
| Codex WebSocket Responses transport | ● | ● | ● | ○ | ○ |
Upload trimming (previous_response_id continuation) on the translation path |
● | ● | ○ (passthrough only) | ○ | ○ |
tool-search / defer_loading / tool_reference handling |
◐ (shim: works, no ctx savings; native opt-in⁸) | ○⁵ | ◐ (upstream) / ● (fork) | ○ | ○ |
Reasoning round-trip to Claude Code thinking |
● (encrypted) | ◐ (Kimi/Grok; Codex dropped) | ◐ | ○ | ◐ |
| Multi-account load balancing / failover | ◐⁷ | ○ | ● | some | ● |
| Backend breadth | 4 providers¹ | 4 subs⁶ | 11 backends² | varies | 100–1600+ |
| Management API / dashboard | ◐ (opt-in admin surface) | ◐ (monitor TUI) | ● | some | ● |
| Usage / quota / cost tracking | ○ (Sentry metrics only) | ○ | ● | some | ● |
| Plugin / interceptor system | ○ | ○ | ● | some | ● |
| Language / footprint | Rust, 1 binary | Rust, 1 binary | Go | Node/Python | Go/Node/Python |
| Config model | TOML + env, hot-reload | env + config file | YAML + mgmt API | varies | YAML/UI |
¹ shunt: two adapter kinds (anthropic passthrough, responses translation) with 4 built-in providers (Anthropic, OpenAI, ChatGPT/Codex, xAI) — any Anthropic-Messages or OpenAI-Responses endpoint is config-only (src/config.rs:180-190,316-363).
² CLIProxyAPI: aistudio, antigravity, claude, codex, codex-ws, gemini, gemini-vertex, kimi, openai-compat, xai, xai-ws.
³ raine/ccp routes by ANTHROPIC_MODEL per-model like shunt, but has no Anthropic-passthrough adapter — an unknown model id returns 400, so you cannot keep the main session on Claude while diverting only the models you name.
⁴ raine/ccp implements its own ChatGPT OAuth (PKCE browser + device-code login); shunt reuses the Codex CLI login (~/.codex/auth.json) and its own PKCE flow is an open TODO (src/auth/mod.rs:18-19).
⁵ Confirmed by reading raine/ccp source (fe80a6b, 2026-07-11): no tool-search handling exists (zero matches for defer_loading / tool_reference / tool_search / advanced-tool-use). Tools are whitelist-rebuilt to {name, description, parameters} (src/providers/codex/translate/request.rs:476-494), so defer_loading:true is silently dropped — no 400, but no context saved; a tool_reference block in a ToolSearch result renders as [unsupported content block omitted: tool_reference] (request.rs:836-842) rather than shunt’s clean "Loaded tool: X". Hence ○ (vs shunt’s ◐): force-enabling ENABLE_TOOL_SEARCH against raine/ccp degrades the discovery-loop result to a placeholder. By default Claude Code’s own gate keeps tool search off behind a non-first-party base URL, so this stays latent.
⁶ raine/ccp subscription backends: Codex (ChatGPT Plus/Pro), Kimi (kimi.com), Grok (grok.com), Cursor Agent — all via subscription OAuth.
⁷ shunt pools explicit accounts for both Anthropic claude_oauth and ChatGPT/Codex chatgpt_oauth: session-sticky selection, per-provider round-robin, model-aware proactive rotation from per-account 5h/7d quota headers (anthropic-ratelimit-unified-* and x-codex-* respectively), cooldowns, forced refresh after 401, and reactive failover on 429s and 5xx responses. Per-account usage appears in the opt-in admin dashboard.
⁸ #82 adds an opt-in, per-provider tool_search flag (src/config.rs:250-261,1041-1049) that maps Claude Code’s tool search onto the OpenAI Responses API’s own native, client-executed tool_search protocol — ToolSearch → tool_search, its tool_use → tool_search_call, and tool_reference → a tool_search_output item carrying the loaded tools’ full schemas as structured JSON (src/model/responses_request.rs) — instead of folding schema into text. Off by default: it only applies for a stock OpenAI or ChatGPT/Codex Responses flavor routing to a gpt-5.4+ model, and is gated behind the flag until a live probe confirms a given backend accepts the shapes shunt emits. xAI/Grok routes and gpt-5.2-and-below models keep the #43 shim regardless of the flag.
“raine/ccp” = raine/claude-code-proxy.
4. Where shunt leads
-
Claude-Code-native fidelity. shunt implements the official gateway contract instead of the “hash the subagent system prompt” heuristic older CC proxies use; the session stays inside Claude Code’s harness (same tool loop, skills, script paths) — only token generation is outsourced (
README.md:97-131). Most general routers and gateways are OpenAI-chat-completions-centric and don’t honor Claude Code’s discovery/attribution surface. -
Upload trimming on the translation path. Because shunt translates Anthropic ⇄ Responses (Claude Code never sends
previous_response_id), it synthesizes continuation: it stores the transcript on the pooled connection, diffs the next request against it with type-aware normalization, and injectsprevious_response_id+ input-delta — real upload trimming on the Claude→Codex path (src/adapters/codex_continuation.rs:79-114). This is not unique: raine/claude-code-proxy does the same class of thing (opt-inCCP_CODEX_PREVIOUS_RESPONSE_ID, session-keyed, append-only). The two Rust subscription proxies share it — the real contrast is with passthrough proxies like CLIProxyAPI, whose Codex WS stores no transcript/response-id, relies on the Codex CLI client to sendprevious_response_id, and therefore re-sends full input every turn on its translation path (plus a tool-output “repair” cache to keep tool-call pairing consistent). -
Normalization depth + reasoning fidelity (vs the nearest peer). Within that shared-continuation pair, shunt goes further than raine/claude-code-proxy on two axes: (1) its continuation normalization parses
function_call.argumentsand round-trips reasoningencrypted_content/signature, so continuation keeps firing across tool turns where a shape-only comparison would drop it (src/adapters/codex_continuation.rs:11-48); and (2) it forwards Codex reasoning to Claude Code asthinking, whereas raine/claude-code-proxy drops Codex reasoning blocks entirely (its README lists this as a limitation). Any unforeseen shape still falls back to full input — never wrong context, only a missed optimization. -
Small, auditable footprint. Single Rust binary, TOML+env config with fail-closed boot validation and hot-reload; no runtime plugin surface to secure.
5. Where shunt trails — and why
Most gaps are deliberate scope boundaries, not oversights. shunt’s own README positions general gateways (LiteLLM/Portkey/bifrost) as adjacent infrastructure / possible backends, not the same product.
- Multi-account pooling is deliberately narrow. shunt has a proactive and reactive account pool for
auth = "claude_oauth":x-claude-code-session-idstickiness, per-provider round-robin, model-aware rotation before the 5h or governing weekly bucket reaches the wall, account cooldowns, credentials-file force-refresh after 401, and failover after quota-rejected 429s or 5xx responses (details). ChatGPT/Codex (auth = "chatgpt_oauth") has a mirrored pool with the same proactive-plus-reactive machinery fed by the backend’sx-codex-*windows (details), both pools can ramp concurrency on a freshly switched account (opt-in storm control), and the opt-in admin dashboard exposes per-account usage. CLIProxyAPI, LiteLLM, and Portkey still provide broader fleet-oriented balancing and visibility. - Narrow backend breadth. Only Anthropic-Messages passthrough or OpenAI-Responses translation; no native Gemini/Bedrock/Azure/Ollama unless they expose one of those two protocols.
- No full management API / usage-quota / cost tracking. The opt-in admin web surface covers browser account provisioning and read-only account-pool state for
claude_oauthandchatgpt_oauthproviders. Claude quota headers populate its windows; Codex response headers populate reported 5h/7d usage (both also feed pool selection). There is no general management API, per-request usage accounting, or cost tracking; observability is opt-in Sentry metrics only (src/metrics.rs). The full HTTP surface is listed in HTTP Endpoints. CLIProxyAPI ships a full management API + quota/usage manager and a third-party dashboard ecosystem; even the same-class peer raine/claude-code-proxy ships a built-in monitor TUI (live sessions, active / recent requests, error events) that shunt has no equivalent of. - No own ChatGPT OAuth login. shunt reuses the Codex CLI login (
~/.codex/auth.json); a first-party PKCE flow is an open TODO (src/auth/mod.rs:18-19). raine/claude-code-proxy is prior art here — it ships its owncodex auth login(PKCE) andcodex auth device(device-code), so it works without the Codex CLI installed. - No plugin / interceptor system. The adapter set is a fixed two-variant
match(src/proxy.rs:152-163); CLIProxyAPI has a full plugin host (RPC ABI, auth providers, executor routing, request/response translators). - Plain HTTP only (TLS out of scope,
docs/m4-inbound-auth.md:13).
6. Improvement opportunities (from this comparison)
Ordered by fit with shunt’s mission. In-scope items advance high-fidelity translation / Claude-Code-native behavior; scope-boundary items would move shunt toward being a fleet gateway and warrant a conscious decision first.
In-scope
-
A. tool-search context savings (already tracked: #43). shunt renders
tool_referenceas name-only"Loaded tool: X"text and forwards all deferred tool schemas upfront (src/model/responses_request.rs:393-403,475-508) — the loop works but reclaims zero context by default. Port the server-side emulation (filter deferred+unloaded tools, inject full schema ontool_reference) — reference implementation: CLIProxyAPI PR #1892 (Adamcf123/CLIProxyAPI@main). Partially addressed by #82: an opt-intool_search = trueper-provider flag now maps tool search onto the Responses API’s native, client-executedtool_searchprotocol instead of the text shim, for a stock OpenAI or ChatGPT/Codex provider routing to a gpt-5.4+ model (see footnote 8 above). It’s off by default pending a live probe of backend acceptance, so the shim (and the zero-savings gap for xAI/Grok and older models) remains the baseline until operators opt in. -
B. Codex WS: live-probe the continuation normalization (already tracked: #45). Reasoning/
function_callnormalization is schema-validated against 3 sources but not yet live-probed (docs/m7-codex-websocket.md:250-270). Any unaccounted field silently falls back to the safe full-input fallback — correctness-safe, but a latent missed optimization. A probe pass would confirm continuation fires as often as it should. -
C. Codex WS: mid-stream failure fallback (resolved: #46). Two fixes closed this. #93 removed one cause at checkout: a half-open pooled socket now fails the reuse liveness probe (a timely
Pongis required, not just a local write) and is replaced with a fresh handshake before the turn’s frame is sent, so a stale connection can no longer break mid-stream. #46 then closed the residual send→first-event window the checkout probe cannot cover (the socket dies after the frame is sent, before the first event):open_ws_turnpeeks the first event andcommit_or_fallbackre-drives the turn over HTTP on a pre-first-event transport error (src/adapters/responses/mod.rs), extending the pre-handshake safety net across that window. A failure after the first event has streamed is genuinely mid-stream — restarting would duplicate output — so it is surfaced as a clean AnthropicerrorSSE event rather than replayed; mid-turn resume viaprevious_response_idis a deliberate non-goal (partial output is already committed to the client). Covered bytests/codex_websocket_fallback.rs. -
D. Codex WS: speculative prewarm (
generate:false) (already tracked: #47). Explicitly out of scope today (docs/m7-codex-websocket.md:53-58), but it is a real Codex latency optimization — prewarming the socket/context before the first token. Worth revisiting once continuation is live-probed. -
E. Upstream retry/backoff (already tracked: #48). The M4-planned bounded retry/backoff is not implemented (
docs/implementation-plan.md:247); transient upstream 429/5xx errors surface directly. A small, idempotent retry would improve resilience without adding scope. -
F. Doc drift:
GET /protocol(already tracked: #49). README advertises a machine-readable spec atGET /protocol(README.md:110) but no such route exists insrc/server.rs. Implement it (cheap, and it’s part of the gateway-protocol story) or correct the docs.
Scope-boundary (decide before doing)
-
G. Minimal multi-account for ChatGPT/Codex. Full LB is out of scope, but heavy users hit ChatGPT/Codex rolling-window caps, where a fill-first rotation across a handful of
~/.codex/auth.json-style logins (burn one account’s window before moving to the next) is disproportionately valuable. This is the single biggest feature gap vs CLIProxyAPI and the one most worth a design discussion. -
H. Per-account quota/usage visibility. Follows G — if multiple subscription accounts are in play, surfacing each account’s 5h/7d window (as CLIProxyAPI’s ecosystem does) becomes useful. Ties to the observability gap.
-
I. Native Gemini (and other) backends. Only relevant if shunt broadens past the Anthropic-Messages / OpenAI-Responses duality. Not currently in scope.
7. One-line takeaway
shunt is the high-fidelity, Claude-Code-native end of the spectrum. Its nearest peer is raine/claude-code-proxy — same class (Rust, subscription OAuth, per-model routing, Codex WS + previous_response_id continuation) — against which shunt’s edge is deeper continuation normalization, Codex reasoning fidelity (raine drops it), an Anthropic-passthrough path (keep the main session on Claude), and xAI OAuth; raine’s edge is a built-in monitor TUI, a first-party ChatGPT OAuth login, and Kimi/Cursor breadth. Against CLIProxyAPI, shunt wins on translation-path upload trimming (CLIProxyAPI’s WS is a passthrough) and trades away most fleet features (broad multi-account LB, full management APIs, plugins, backend breadth) by design. It now provides narrow Anthropic OAuth and ChatGPT/Codex account pools, both with model-aware proactive quota scheduling plus reactive failover; deliberate fill-first ordering across accounts is the remaining pooling gap. The highest-value in-scope work is finishing the tool-search context savings (#43) — now partly addressed by an opt-in native tool_search path on Codex/OpenAI (#82). The Codex WS transport’s pre-first-event HTTP fallback gap has since been closed (#46); its continuation-normalization live-probe (#45) remains open.