Skip to content

Antigravity

Reach the Google Antigravity backend over HTTP — login, project discovery, model slugs, thinking, and what the adapter carries.

Updated View as Markdown

The built-in antigravity provider reaches Google’s Antigravity backend over HTTP at daily-cloudcode-pa.googleapis.com — the daily- control plane the Antigravity client itself addresses for both discovery and inference. It speaks the same Code Assist protocol as the gemini provider — shunt translates Anthropic Messages to generateContent / streamGenerateContent — but it authenticates with an Antigravity subscription token and identifies itself as ideType: ANTIGRAVITY during project discovery.

Antigravity has a second, older transport. kind = "antigravity_cli" runs the local agy binary as a subprocess and is deprecated — see the deprecated antigravity-cli transport. This page covers the HTTP provider, which needs none of that machinery.

Quick start

Let a coding agent wire it up for you — shunt add prints an embedded setup blueprint (offline and read-only; the agent edits the config, the command never does):

shunt add upstream antigravity --print | claude

Or follow the manual steps below.

1. Log in

shunt login antigravity

This runs a Google authorization-code flow using Antigravity’s own OAuth client and writes ~/.shunt/antigravity-auth.json, which shunt reads and auto-refreshes. The flow asks for five scopes:

https://www.googleapis.com/auth/cloud-platform
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
https://www.googleapis.com/auth/cclog
https://www.googleapis.com/auth/experimentsandconfigs

The last two are the reason a Gemini CLI login cannot be reused here, and vice versa: a ~/.gemini/oauth_creds.json token never carries cclog or experimentsandconfigs. google_oauth and antigravity_oauth are not interchangeable.

Login also resolves your Code Assist project — loadCodeAssist first, then onboardUser to provision one for a first-time account — and stores the project id in the credential file. No discovery sits in front of your first request. If discovery fails, login still saves the token and retries on the first request.

2. Configure the upstream

The provider is built in, so a [[routes]] entry naming provider = "antigravity" works with no provider table of your own. Declaring it as an ordered upstream is different: the Antigravity kinds have no [[upstreams]] preset, so provider = "antigravity" is not available there and you must set kind, base_url, and auth yourself:

[[upstreams]]
name = "anthropic"
provider = "anthropic"   # keep Anthropic as the default for unrouted models (e.g. claude-*)

[[upstreams]]
name = "antigravity"
kind = "antigravity"
base_url = "https://daily-cloudcode-pa.googleapis.com"
auth = "antigravity_oauth"

Ordered [[upstreams]] replace shunt’s built-in providers, so an explicit config must also declare the anthropic default it still falls back to (server.default_provider defaults to anthropic).

The legacy [providers.antigravity] table form remains supported — but do not mix [[upstreams]] and [providers.*] in one file.

3. Route a model

shunt keeps no model allowlist for this provider: the resolved upstream_model is sent to the backend as written, and the backend decides. Antigravity serves the Gemini-family slugs the Code Assist gemini provider does not accept — but it publishes them with the effort in the id, in one of two shapes: a -low / -medium / -high suffix (Pro has only -low and -high), or a single -tiered id that takes the effort as a request field instead. Which shape you get is per account and changes over time — one account is served gemini-3.8-flash-tiered, another gemini-3.8-flash-medium. Run agy models to list the catalog as it stands today; at the time of writing it holds gemini-3.8-flash-*, gemini-3.7-flash-*, gemini-3.6-flash-*, gemini-3.1-pro-low / -high, claude-sonnet-4-6, claude-opus-4-6-thinking, and gpt-oss-120b-medium.

A bare slug is not served: daily-cloudcode-pa.googleapis.com answers it with a 404. Against the production host, shunt’s earlier request — a bare id and the plain Code Assist envelope — came back as a misleading 429 RESOURCE_EXHAUSTED (“check quota”), which reads as a rate limit; the probes changed both inputs together, so which one produced that 429 is not established.

So that neither shape has to be guessed, shunt reads your account’s own catalog (fetchAvailableModels) on the request path — cached for ten minutes per account, bounded, and fail-open — and picks the id that account actually publishes.

Map a local alias to the exact Antigravity 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"

You do not have to write the suffix by hand. For a bare gemini-* id, shunt resolves the tier itself and appends it, taking the first signal that applies:

  1. effort on the [[routes]] entry, or on the provider — an explicit pin. xhigh and max fold onto high here too, and matching ignores case and surrounding whitespace. A level shunt does not recognise is passed through as written (trimmed and lower-cased), so you can name a tier the catalog adds later.
  2. The request’s output_config.effort (Claude Code sends low/medium/high/xhigh/max; xhigh and max fold onto high).
  3. thinking.type = "enabled"budget_tokens at or below 2048 is low, at or below 8192 is medium, above that is high. An enabled block that names no budget uses the same 1024 default the translated request carries, so it lands in low.
  4. Otherwise medium.

The catalog then decides what that tier becomes. If your account publishes {id}-{tier}, that is what goes out. If it publishes only {id}-tiered, shunt sends the tiered id and carries the tier in generationConfig.thinkingConfig.thinkingLevel instead (alongside any thinkingBudget, which the backend accepts together). If it publishes neither but does publish other tiers of the same model, the nearest published tier wins, ties resolving upward — which is how a medium on a -pro id becomes -high without any Pro-specific rule.

A suffix you pinned by hand gets the same treatment. If your account no longer publishes gemini-3.6-flash-medium, the suffix still says which tier you wanted, so shunt re-resolves it through the rules above rather than forwarding an id the catalog just proved is a 404; a catalog that says nothing about the family leaves your pin alone. A -tiered id you pinned yourself keeps its id and still carries your effort as the thinkingLevel, so pinning the exact published id costs you no effort control — and if your account later moves back to suffixed ids, that pin is re-resolved the same way. Only a catalog fetched just now can prove an id is gone: while shunt is serving the last known set after a failed refresh, every pin goes out as written.

When the catalog is unavailable (a discovery outage, a backend that does not answer fetchAvailableModels), shunt falls back to appending -{tier} with that Pro clamp — the 0.40.0 behaviour — so a failed lookup costs you the guess, never the request. A failed lookup is remembered for a minute before it is retried, so an unreachable control plane costs one bounded request per minute rather than one per message, and only Gemini ids are looked up at all. An unrecognised configured effort is appended as written, per rule 1, and is never clamped without a catalog; with one, it folds onto a tier the account publishes, because the catalog has just said that tier is not served. On the -tiered path it folds onto medium, because the backend rejects a thinkingLevel it does not know. An id your catalog publishes verbatim and any non-Gemini id (claude-sonnet-4-6, gpt-oss-120b-medium) is sent exactly as written. An unrecognised output_config.effort falls back to medium rather than reaching the model id. A request that explicitly disables thinking (thinking.type = "disabled") never gains a thinkingLevel: the translated thinkingBudget: 0 stands on its own.

Antigravity also offers Claude models, but shunt does not yet implement the request rewrites they need (#368). Nothing rejects such a slug locally — it reaches the backend as written — so route only the Gemini-family slugs for now.

What the adapter carries

The adapter streams assistant text and thinking as Anthropic SSE, and bridges your client’s tools natively: tool_use becomes a Gemini functionCall, your tool_result becomes a functionResponse, and tool declarations and tool choice are translated with it. System prompts become systemInstruction. Token usage is Google’s reported count — promptTokenCount and candidatesTokenCount map to input_tokens and output_tokens.

Thinking follows the request: thinking.type = "enabled" sets thinkingConfig.thinkingBudget from budget_tokens (default 1024), and "disabled" sets it to 0. An enabled thinking block is also one of the signals that picks the model’s effort tier, as described under Route a model above.

Every request additionally carries the agent identity the Antigravity client sends: userAgent: "antigravity", requestType: "agent", a per-request requestId, and a sessionId derived from the earliest user text in the conversation so follow-up turns land on the same session. The gemini provider’s Code Assist requests carry none of these.

Tool schemas are adapted to Gemini’s Schema dialect — on this provider and on gemini, which shares the adapter. $schema, $id, $comment, propertyNames, patternProperties, exclusiveMinimum, exclusiveMaximum, and const are dropped; every other keyword is forwarded as written. A type list keeps its first non-null entry, a null in the list sets nullable: true, and a null-only list becomes a nullable string. Any schema that is not an array — a union that settled on another member, or a plain scalar — drops prefixItems and any items that is not a schema object, keeping an object-valued items as written; a typeless schema counts as an array when it carries prefixItems or an array- or object-valued items, and otherwise loses a lone boolean items. Every array is sent with a single items schema: a tuple — prefixItems, or draft-07’s array-valued items — is folded into one, where identical positions keep their schema, positions that agree only on a non-array type keep that type, and otherwise the first position’s schema stands. A typed items schema already sitting beside prefixItems is kept as written and the positions are dropped. An element whose type lives in anyOf/oneOf/allOf arms is folded the same way. An array that says nothing about its elements, or whose element schema declares no type, is sent with items: {"type": "string"} unless the element implies its type through enum or properties. These fallbacks narrow the element type rather than rejecting the request, so a tool whose tuple arrives typed as string is this adaptation at work, not a routing error.

Two limits are worth knowing before you route production traffic:

  • Images must be inline. A base64 image block becomes Gemini inlineData. A URL image source is rejected with a 400 (URL image sources are not supported by the Gemini adapter).
  • Tool results cannot carry rich media. A tool_result whose content includes image or document blocks is rejected.

Verify

shunt check    # -> config ok
shunt run
curl -sS http://127.0.0.1:3001/v1/messages \
  -H 'anthropic-version: 2023-06-01' \
  -H 'content-type: application/json' \
  -d '{"model":"claude-gemini-3.6-flash-via-antigravity","max_tokens":16,"messages":[{"role":"user","content":"Reply with OK."}]}'

Confirm the response’s x-gateway-upstream header names antigravity, then point Claude Code at shunt.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close