Providers are a name → config map. Anthropic-compatible and Responses-compatible upstreams can be added with configuration; provider-specific transports use native adapters:
kind = "anthropic"— the upstream speaks the Anthropic Messages API. shunt passes the request through, optionally injecting a different API key.kind = "responses"— the upstream speaks the OpenAI Responses API. shunt translates Anthropic Messages ⇄ Responses, including streaming.kind = "cursor"— the native Cursor ConnectRPC/protobuf adapter.kind = "gemini"— the native Google Code AssistgenerateContentadapter.kind = "antigravity"— the Google Antigravity backend over HTTP.kind = "antigravity_cli"— deprecated; invokes the local AntigravityagyCLI.
Built-in providers
| Name | Kind | Auth | Backend |
|---|---|---|---|
anthropic |
anthropic |
passthrough |
api.anthropic.com — forwards the caller’s own credential |
openai |
responses |
api_key (OPENAI_API_KEY) |
api.openai.com/v1 |
codex |
responses |
chatgpt_oauth |
chatgpt.com/backend-api — reuses ~/.codex/auth.json |
xai |
responses |
api_key (XAI_API_KEY) |
api.x.ai/v1 — the developer API, billed per token |
grok |
responses |
xai_oauth |
cli-chat-proxy.grok.com/v1 — the Grok CLI proxy; reuses ~/.shunt/xai-auth.json |
cursor |
cursor |
cursor_oauth |
api2.cursor.sh — reuses ~/.shunt/cursor-auth.json (shunt login cursor) |
gemini |
gemini |
google_oauth |
cloudcode-pa.googleapis.com — reuses ~/.gemini/oauth_creds.json |
antigravity |
antigravity |
antigravity_oauth |
daily-cloudcode-pa.googleapis.com — reuses ~/.shunt/antigravity-auth.json (shunt login antigravity) |
antigravity-cli |
antigravity_cli |
none |
Deprecated. Local agy CLI — same backend via subprocess |
Each linked name opens its dedicated setup page. shunt add upstream <name> prints a step-by-step blueprint for built-in providers; see the CLI reference.
Gemini providers
The built-in gemini provider translates Anthropic Messages requests to Google Code Assist and reuses the Gemini CLI OAuth file at ~/.gemini/oauth_creds.json. Authenticate with the Gemini CLI first, then add explicit discovery and routing entries. shunt uses an unexpired access token directly; if shunt itself must refresh it, set SHUNT_GOOGLE_CLIENT_ID and SHUNT_GOOGLE_CLIENT_SECRET, or rerun gemini login so the CLI refreshes the shared file.
[[models]]
id = "claude-gemini-2.5-flash-via-gemini"
display_name = "[GEM ] Gemini-2.5-Flash"
[[routes]]
model = "claude-gemini-2.5-flash-via-gemini"
provider = "gemini"
upstream_model = "gemini-2.5-flash"Tool declarations go through the same schema adaptation as the antigravity provider — both
transports share one Gemini adapter — so the tuple folding and typeless-element fallbacks described
under What the adapter carries on the Antigravity provider page apply
to gemini routes as well.
Code Assist also recognizes preview slugs such as gemini-3.1-pro-preview and gemini-3-flash-preview, subject to account entitlement and provider capacity. It does not accept the Antigravity-only slugs such as gemini-3.6-flash-medium.
The separate antigravity provider reaches the same backend over HTTP. Authenticate it with shunt login antigravity: a Google
authorization-code flow using Antigravity’s own OAuth client, which asks for two scopes (cclog, experimentsandconfigs) a Gemini CLI
login never carries — so the gemini credential cannot be reused here, and vice versa. The login also resolves the Code Assist project,
provisioning one through onboardUser for a first-time account, so no discovery happens in front of a request.
Map each local alias to the exact Antigravity model slug:
[[models]]
id = "claude-gemini-3.6-flash-via-antigravity"
display_name = "[AGY ] Gemini-3.6-Flash"
[[routes]]
model = "claude-gemini-3.6-flash-via-antigravity"
provider = "antigravity"
upstream_model = "gemini-3.6-flash-medium"Antigravity publishes its Gemini models with the effort in the id and does not serve a bare slug, in
one of two shapes — a -low / -medium / -high suffix, or a single -tiered id that takes the
effort as a request field. Which shape your account gets changes over time, so shunt reads the
account’s own catalog and picks the id it actually publishes: a bare gemini-* id is resolved for
you — from effort, the request, or medium — and a suffix your account no longer publishes is
re-resolved rather than forwarded. Every spelling works.
For the full setup — login and scopes, project discovery, model slugs, thinking, and what the adapter carries — see the dedicated Antigravity provider page.
The deprecated antigravity-cli transport
The antigravity-cli path runs one CLI subprocess per request in agentic mode. agy is a
full agent with its own tools, so it resolves its own tool calls and returns finished
work — file edits included. Four consequences follow:
- Tools are
agy’s, not the caller’s, and a request asking for one is refused. This adapter can never emit atool_useblock, so a non-emptytoolsarray, or atool_choiceofanyortool, is rejected with a400 invalid_request_errornaming the limitation. Such requests used to be dropped silently, returning a text-only200withstop_reason: "end_turn"— a response an agentic caller cannot act on, and a direct violation oftool_choice: {"type": "any"}while still reporting success. Two shapes stay exempt, because neither obliges a tool call:tool_choice: {"type": "none"}even alongsidetools, andtool_choice: {"type": "auto"}with no tools, which is the Anthropic SDK default that many clients serialize on every request. An emptytools: []is likewise not a tool request. Clients that drive the tool loop themselves should use theantigravityorgeminiprovider, both of which forward tools. - It runs with
--dangerously-skip-permissionsand, by default,--sandbox, because a non-interactive run has no channel to approve a permission prompt. See Workspace and sandboxing — this provider executes code. - Progress is streamed as it happens. Assistant text is forwarded incrementally and
tool steps become SSE
pingframes, so a long agent turn keeps producing bytes instead of going silent until it finishes. Token usage is Google’s reported count, not an estimate. - An undelivered
send_messagehandoff does not fail a finished turn. Print mode advertisesagy’s full interactive tool surface, includingsend_message, but registers no inbox. A subagent-style prompt (“report your findings back to the main agent”) is enough for the model to hand its reply tomain,team-leadoruser; the call fails andagymarks the runERRORwithrecipient "<name>" not found— after the answer has already streamed. shunt ends that turn normally (end_turn, HTTP200) when assistant text was streamed, and keeps failing it when nothing was, since the reply then only existed inside the undelivered message. Every other late failure still ends the turn with an SSEerror. This is a compatibility shim keyed on an upstream error string; seedocs/notes/agy-print-mode-tool-surface.mdin the repository. - Gateway shutdown ends the turn on Unix. Each run has an isolated process group, so shunt terminates the agent and its tool subprocesses when shutdown starts instead of letting an unattended turn hold the graceful drain open.
shunt discovers the valid model/effort pairs from agy models on the first Antigravity
turn. What happens next depends on where the value came from:
- An explicitly configured
effortthe model does not offer is rejected with a400naming the valid levels.gemini-3.1-proaccepts onlylowandhigh, so a route pinningmediumis an error rather than a silent substitution — quietly runninghighwould change cost, latency and quota while the config claimed otherwise. - A default shunt chose itself is clamped to the nearest supported level, preferring the stronger. No operator intent is being overridden.
- For a model shunt does not recognise, the configured value is passed through and
agyvalidates it; its rejection enumerates the valid levels.
Workspace and sandboxing
agy runs unattended with permission prompts disabled, so treat this provider as
arbitrary code execution as the user running shunt. Two independent settings bound it,
and they do different jobs:
sandbox (default true) passes --sandbox, which keeps the agent’s reads and writes
inside its workspace. This is the setting that actually contains it. Turning it off
leaves an agent with unrestricted shell access.
workspace_roots (default empty) decides where the agent may start. Resolution order
is SHUNT_AGY_WORKSPACE, then a Working directory: line in the request’s system
prompt, then the gateway’s own directory. The system prompt is client-controlled text
and routinely quotes fetched documents and tool output, so a path found there is honored
only when it canonicalizes inside one of these roots — canonicalizing first so .. and
symlinks cannot escape. With no roots configured, prompt-derived paths are ignored.
[providers.antigravity-cli]
kind = "antigravity_cli"
base_url = "http://localhost"
auth = "none"
# Repos an agent may be pointed at by request content.
workspace_roots = ["/Users/you/repos"]
# sandbox = false # only where the agent needs unrestricted terminal accessBe clear about what workspace_roots is not: it chooses the starting directory, not a
jail. With sandbox = false, an agent that starts in an allowed root can still act on a
path named elsewhere in the prompt. Keep the sandbox on unless you have a specific
reason, and do not expose this provider on a non-loopback bind to untrusted clients.
shunt refuses to start when sandbox = false is combined with a non-loopback bind:
that pairing hands local shell access to anyone who can post a Messages request, and
authentication alone does not make it safe. It also enforces the rule per request against
the listener bound at boot. Because a hot reload cannot move that listener, reloading a
loopback bind does not permit you to disable the sandbox while a public listener remains
active. Either keep the sandbox on or restart shunt with a loopback bind.
The model/effort matrix is discovered in the background at startup, so no request pays
for it. Until it lands, or if agy models fails, configured values are passed through
and agy validates them itself — a failed discovery is retried rather than cached, after
a short backoff so a persistently failing agy is not re-forked once per turn while the
matrix is still empty.
A discovered matrix is not treated as a closed catalogue. agy gains models while the
gateway runs, so a request naming a model the matrix does not list schedules a fresh
discovery in the background, at most once a minute. That request, and any that arrive
before the discovery finishes, still pass through to agy unvalidated; once a discovery
that lists the model lands, later requests resolve normally. Without this, a model released
after startup would stay unknown until shunt restarted.
The configured upstream slug proves which model shunt asks agy to run; it does not
independently attest Google’s internal serving identity.
The codex provider (ChatGPT subscription)
Log in once with the Codex CLI; shunt reads and auto-refreshes ~/.codex/auth.json:
codex loginIf the file is missing or expired, shunt returns an authentication_error telling you to run codex login.
For the full setup — auth-file handling, model selection, effort, and context sizing — see the dedicated ChatGPT / Codex guide.
The grok provider (SuperGrok / X Premium+ subscription)
Log in once with the built-in device-code flow; shunt writes and auto-refreshes ~/.shunt/xai-auth.json:
shunt login xaixAI may gate OAuth access by subscription tier — if grok returns 403, use the xai API-key provider instead (export XAI_API_KEY=…).
The cursor provider (Cursor subscription)
The built-in cursor provider reaches your Cursor subscription through Cursor’s own
ConnectRPC/protobuf AgentService — the kind = "cursor" native adapter translates it to and from
Anthropic Messages, streaming, reasoning, native tool calls, and inline images included. Log in
once with shunt login cursor, then route a cursor:* model id (cursor:default,
cursor-plan:default, cursor-ask:default, …) — the provider is seeded by default, so no config
table is required.
For the full setup — login, agent modes, wire model ids, adapter capabilities, and overrides — see the dedicated Cursor provider page.
The xai / grok providers (Grok)
Two built-in providers reach xAI’s Grok models, split by credential: grok spends your
SuperGrok / X Premium+ subscription over OAuth (shunt login xai, no per-token billing), while
xai uses an XAI_API_KEY against the metered developer API. A subscription bearer and an API
key are not interchangeable — each works only against its own provider.
For the full setup — login, both provider blocks, model slugs, the opt-in effort dial, and the entitlement gotchas — see the dedicated xAI / Grok guide.
Adding an Anthropic-compatible backend
Most third-party “use Claude Code with X” gateways are Anthropic-Messages-compatible: kind = "anthropic" with auth = "api_key", differing only in base_url and the key env var. Ready-to-use bases (each provider links to a dedicated page with the full setup):
| Provider | base_url |
Example model IDs |
|---|---|---|
| Kimi (Moonshot) | https://api.moonshot.ai/anthropic |
kimi-k3[1m], kimi-k2.7-code |
| Kimi Code (subscription, OAuth) | https://api.kimi.com/coding |
use the ids your subscription exposes |
| DeepSeek | https://api.deepseek.com/anthropic |
deepseek-v4-pro, deepseek-v4-flash |
| Z.ai (GLM) | https://api.z.ai/api/anthropic |
glm-5.2, glm-4.7 |
| Zhipu (GLM China) | https://open.bigmodel.cn/api/anthropic |
glm-5.3, glm-5.3-flash |
| MiniMax | https://api.minimax.io/anthropic |
see MiniMax docs |
| MiniMax China | https://api.minimax.cn/anthropic |
MiniMax-M3 |
| Mimo (Xiaomi) | https://api.xiaomimimo.com/anthropic |
mimo-v2.5-pro — see Mimo docs |
| OpenRouter | https://openrouter.ai/api |
anthropic/claude-opus-4.8 |
| Vercel AI Gateway | https://ai-gateway.vercel.sh |
anthropic/claude-opus-4.8 (accepts x_api_key) |
For a provider not in this table, shunt add upstream <docs-url> prints a research blueprint
that walks a coding agent through wiring it up from the provider’s own documentation.
Every row above but one takes auth = "api_key". Kimi Code is the exception: a separate,
subscription-billed Kimi service from the metered Moonshot API in the row above it — different
host, and OAuth instead of an API key. It has a dedicated built-in kimi-code preset (kind = "anthropic", base_url = "https://api.kimi.com/coding", auth = "kimi_oauth"), so it needs no
manual [providers.*] table beyond provider = "kimi-code" under the ordered [[upstreams]]
form, plus a logged-in account:
shunt login kimi --name <account-name> # RFC 8628 device flow -> ~/.shunt/accounts/kimi/<account-name>.json# shunt.toml — route to your Kimi Code subscription
[[upstreams]]
name = "kimi-code"
provider = "kimi-code"
auth = { mode = "kimi_oauth", account = "<account-name>" }
# Declaring [[upstreams]] replaces the built-in provider set, so keep a trailing
# anthropic passthrough — without it `shunt check` rejects the default
# server.default_provider. This is the same entry `shunt init` appends.
[[upstreams]]
name = "anthropic"
provider = "anthropic"
[[routes]]
model = "<model-id-your-subscription-exposes>"
provider = "kimi-code"kimi_oauth is pool-capable like claude_oauth/chatgpt_oauth — use accounts = [...] in place
of account to spread load across several stored Kimi accounts. See
Kimi → Kimi Code (OAuth subscription) for the
full walkthrough.
For example, to route Kimi’s (Moonshot) model through shunt:
[providers.kimi]
kind = "anthropic"
base_url = "https://api.moonshot.ai/anthropic"
auth = "api_key"
api_key_env = "MOONSHOT_API_KEY"
[[routes]]
model = "kimi-k3[1m]"
provider = "kimi"
[[routes]]
model = "kimi-k2.7-code"
provider = "kimi"Then export MOONSHOT_API_KEY=…, point Claude Code at shunt, and select kimi-k3[1m] (via ANTHROPIC_CUSTOM_MODEL_OPTION or ANTHROPIC_MODEL). Run shunt check to validate — it reports an unknown provider in a route, a missing api_key_env, or a bad base_url.
Every provider key (kind, auth, api_key_header, count_tokens, …) is documented in the Configuration Reference.
Subagent plugins
The pleaseai/shunt marketplace ships ready-made Claude Code subagents pinned to each provider’s models — one agent per model. Install a plugin, then @-mention a model or set CLAUDE_CODE_SUBAGENT_MODEL. Each agent’s model: frontmatter diverts only that subagent; the main session stays on Claude.
| Plugin | Models (one agent each) | Provider |
|---|---|---|
shunt-codex |
gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna |
codex (ChatGPT subscription) |
shunt-xai |
grok-4.6, grok-4.5, grok-build-0.1 |
xai (API key) or grok (subscription) |
shunt-kimi |
kimi-k3[1m], kimi-k2.7-code |
kimi |
shunt-deepseek |
deepseek-v4-pro, deepseek-v4-flash |
deepseek |
shunt-zai |
glm-5.2, glm-4.7 |
zai |
shunt-minimax |
MiniMax-M3[1m] |
minimax |
shunt-mimo |
mimo-v2.5-pro |
mimo |
/plugin marketplace add pleaseai/shunt
/plugin install shunt-xai@shuntEach plugin still needs its provider routed in shunt.toml (see the sections above) and the matching credential exported — the plugin’s own README lists the exact route and env var. The grok models can be served by either xAI provider: xai (API key, billed per token) or grok (SuperGrok / X Premium+ subscription via shunt login xai; tier-gated — fall back to xai on 403).