An abstract graphic representing data compression next to a colorful Google AI star, with text reading: "OpenCode + Headroom + Vertex AI: Compressing your Google Cloud LLM traffic."

Headroom is a context-compression proxy for AI coding agents. It sits between your agent and the model, strips redundant tokens out of tool outputs, logs, RAG chunks, and conversation history, then forwards the trimmed request upstream. The project's own published numbers: 15 to 20% fewer tokens on typical coding-agent workloads, 60 to 95% fewer on JSON-heavy data. Their benchmark table shows specific cases going further still:

Workload Before After Savings
Code search (100 results) 17,765 1,408 92%
SRE incident debugging 65,694 5,118 92%
GitHub issue triage 54,174 14,761 73%
Codebase exploration 78,502 41,254 47%

On accuracy, Headroom reports no regression on standard benchmarks: GSM8K held at 0.870, TruthfulQA improved to 0.560 (from 0.530), and both SQuAD v2 and BFCL scored 97% with 19 to 32% compression. This post does not reproduce these numbers, but they justify the setup below.

None of that applies if your models come from Google Cloud instead of Anthropic's or OpenAI's own APIs. Headroom's default routing points at api.anthropic.com and api.openai.com. If you're calling Claude or Gemini through Google Cloud, using your project's Vertex AI quota and gcloud credentials, that traffic never touches the proxy. Zero compression or savings, until you wire it up by hand.

This post walks through wiring up OpenCode, starting with Anthropic's Claude on Google Cloud AI's Model Garden first, then Gemini via Google Cloud AI. Both routes have been tested end to end with a Google Cloud project and Headroom 0.32.0.

What you're building

Two separate routing paths, because Claude and Gemini get to Google Cloud AI through different mechanisms:

Model family How Headroom reaches it OpenCode provider
Claude on Google Cloud AI litellm backend, translating to Anthropic's native /v1/messages format anthropic, with its baseURL pointed at the proxy (not the vertex provider)
Gemini on Google Cloud AI Headroom's own native Vertex passthrough, no litellm involved a new custom provider you add yourself

For Claude, Headroom doesn't have a hand-written Vertex-Anthropic bridge; it delegates that translation work, along with the Google OAuth and regional-endpoint plumbing, to a library that already solves it: litellm. Gemini doesn't need any of that: Headroom ships a native handler for Vertex's publisher=google request shape, registered unconditionally regardless of which backend flag you pass the proxy.

Prerequisites

  • gcloud auth application-default login already run, with valid ADC credentials in place.
  • A Google Cloud project with the Vertex AI API enabled and Claude model access granted (Claude models on Google Cloud requires explicit, per model variation enablement, unlike Gemini).
  • OpenCode installed and able to reach google-vertex/* models without Headroom in the loop. Get that baseline working first; it's the thing you're about to route around, and if it's broken now, Headroom won't fix it.

Step 1: Install Headroom with the right extras

Headroom's PyPI package ships most functionality behind optional extras. Installing it bare omits the proxy server. There is also a Python version constraint: litellm requires Python below 3.14, so if your default python3 is newer, pin the interpreter.

uv python list   # confirm a 3.13.x interpreter is available

uv tool install "headroom-ai[proxy,mcp,memory,code]" \
  --python 3.13 \
  --with google-cloud-aiplatform \
  --force

The --with google-cloud-aiplatform flag is required for the Google Cloud path. Skip it and the proxy starts but fails on a Claude request with No module named 'vertexai'.

Verify both the CLI and the interpreter it's running on:

headroom --version
~/.local/share/uv/tools/headroom-ai/bin/python --version   # should print 3.13.x

Avoid reinstalling with a bare uv tool install headroom-ai --force. That wipes the extras you installed and drops you onto whichever Python uv picks as default, which might be 3.14. Always repeat the full extras list on reinstall.

Step 2: Point the proxy at Google Cloud

The proxy needs four pieces of configuration to route Claude requests through Google Cloud instead of straight to Anthropic: a backend flag, a region, and two litellm-specific environment variables that are easy to confuse with Google's own standard ones.

export HEADROOM_BACKEND=litellm-vertex
export HEADROOM_REGION=global
export VERTEXAI_PROJECT=<your-google-cloud-project-id>
export VERTEXAI_LOCATION=global

Note the variable names: VERTEXAI_PROJECT and VERTEXAI_LOCATION, not GOOGLE_CLOUD_PROJECT or GOOGLE_CLOUD_LOCATION. litellm reads its own set, distinct from other Google Cloud SDKs, and errors are silent. It can authenticate against your default quota instead, which only shows up on a billing dashboard. Set them rather than assuming your existing GOOGLE_CLOUD_PROJECT covers it.

Put these in your shell rc file (e.g. bashrc, zshrc, etc.) so they're set every time, not just in the terminal you happen to be testing in.

Sanity-check the proxy

Before touching OpenCode at all, confirm the proxy itself can reach Google Cloud:

headroom proxy --port 8787 --backend litellm-vertex --region global &

curl -s -X POST http://127.0.0.1:8787/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: sk-ant-dummy" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"claude-sonnet-4-6","max_tokens":32,"messages":[{"role":"user","content":"Reply with exactly the word: pong"}]}'

A {"type":"message", ...} response means Google Cloud auth and routing work. No module named 'vertexai' indicates a step 1 installation failure. An auth error points to GOOGLE_APPLICATION_CREDENTIALS or your ADC setup.

The x-api-key header is a dummy value. Headroom's litellm-vertex backend ignores it and authenticates through your Google Cloud ADC credentials. The header is required only to satisfy the Anthropic wire format.

Confirm the running proxy loaded the expected backend:

curl -s http://127.0.0.1:8787/health | python3 -c \
  "import json,sys; print(json.load(sys.stdin)['config']['backend'])"
# -> litellm-vertex

Keep this /health check in your back pocket. If OpenCode ever starts behaving like it's hitting the real Anthropic API instead of Vertex, checking config.backend here is the fastest way to confirm whether the proxy itself is misconfigured, versus a problem somewhere else in the chain.

Step 3: Wire Claude into OpenCode

OpenCode has two ways to reach a custom base URL; only one works for this setup. headroom wrap opencode injects an OpenAI-compatible provider that hits /v1/chat/completions. For Claude on Google Cloud AI, that path fails upstream because litellm's OpenAI-to-Vertex-Anthropic bridge sends a field the real endpoint rejects. Use the native Anthropic-format route by overriding the anthropic provider's baseURL.

In ~/.config/opencode/opencode.jsonc:

"provider": {
  "google-vertex": {
    "options": { "project": "", "location": "global" }
  },
  "anthropic": {
    "options": {
      "baseURL": "http://127.0.0.1:8787/v1",
      "apiKey": "sk-ant-dummy"
    }
  }
}

Leave your existing google-vertex provider block unchanged. This override targets anthropic only. Once in place, select models under anthropic/* in OpenCode, not google-vertex/*. This routes the same Google Cloud-hosted model through Headroom's compression. Your google-vertex/* selections work as before, uncompressed. Headroom only processes traffic explicitly routed to it.

Picking a model enabled on your project

OpenCode's /models picker lists every Claude model ID Anthropic has shipped, sourced from a generic public catalog, not what is enabled on your specific Google Cloud project. Selecting an unsupported model does not return a clean error; instead, Google Cloud returns a 404 access error.

Check availability before committing to a model ID in OpenCode:

curl -s -X POST http://127.0.0.1:8787/v1/messages \
  -H "content-type: application/json" -H "x-api-key: sk-ant-dummy" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"<model-id>","max_tokens":16,"messages":[{"role":"user","content":"say pong"}]}'

A not_found_error mentioning access means the model is not enabled for your project or region. This is a Model Garden configuration issue, not a Headroom problem; OpenCode's catalog does not reflect project-level model access.

Launching and confirming it's live

headroom wrap opencode

Then check:

headroom doctor

headroom doctor reports the proxy status and which backend is running. It shows "no tokens saved yet" until you send a message from inside OpenCode on an anthropic/* model; that line updates when traffic flows. If doctor warns about unset ANTHROPIC_BASE_URL or OPENAI_BASE_URL variables, ignore it: headroom wrap opencode injects routing through an inline config override, so the warning does not apply.

Now add Gemini on Vertex

With Claude working, Gemini is more straightforward. Headroom's Vertex publisher passthrough for publisher=google requests runs unconditionally, independent of the --backend flag or the VERTEXAI_PROJECT/VERTEXAI_LOCATION variables you set above. It forwards your ADC bearer token through as-is and resolves the real regional Vertex host from the location segment already present in the request path.

Verify the setup using the same pattern as Claude:

TOKEN=$(gcloud auth application-default print-access-token)
curl -s -X POST "http://127.0.0.1:8787/v1/projects/<project>/locations/global/publishers/google/models/gemini-3.5-flash:generateContent" \
  -H "content-type: application/json" \
  -H "authorization: Bearer $TOKEN" \
  -d '{"contents":[{"role":"user","parts":[{"text":"Reply with exactly the word: pong"}]}]}'

If you build that URL from a shell variable in zsh, use ${model}:generateContent with braces, not $model:generateContent. Without braces, zsh parses the colon as a parameter-expansion modifier and mangles the URL.

Adding a Gemini provider, not modifying the existing one

OpenCode's existing google-vertex provider hosts Gemini, Claude, and DeepSeek models under a single provider ID. Adding baseURL to that block to route everything at once will fail. Don't. That sends Claude traffic through Headroom's native publisher=anthropic passthrough, which has a bug that substitutes a deprecated Sonnet model and returns a 404.

Define a separate provider ID listing only Gemini models, and leave google-vertex unchanged:

"google-vertex-headroom": {
  "npm": "@ai-sdk/google-vertex",
  "name": "Gemini on Vertex (Headroom)",
  "options": {
    "project": "",
    "location": "global",
    "baseURL": "http://127.0.0.1:8787"
  },
  "models": {
    "gemini-3.5-flash": {
      "name": "Gemini 3.5 Flash (Headroom)",
      "limit": { "context": 1048576, "output": 65536 }
    },
    "gemini-3.1-pro-preview": {
      "name": "Gemini 3.1 Pro Preview (Headroom)",
      "limit": { "context": 1048576, "output": 65536 }
    }
  }
}

The baseURL here carries no /v1 suffix. @ai-sdk/google-vertex builds the full path on top of the base URL.

Select models under google-vertex-headroom/* to get the compressed path. Target the Gemini 3.x line; add the enabled model IDs to the models map and verify availability using the curl pattern above.

Adding Gemma 4 on Vertex

Gemma routes through the same Headroom path as Claude — no new provider, no separate auth setup. litellm, which backs the litellm-vertex backend, has native support for Vertex's Model Garden Gemma deployment (google/gemma-* model IDs) and handles the format translation and ADC auth automatically.

The only requirement is the model ID prefix. Headroom's litellm-vertex backend uses the model string to determine which litellm provider handler to invoke. Sending google/gemma-4-26b-a4b-it-maas on its own doesn't tell litellm which provider to use — the / in google/gemma- looks like a provider prefix but it's part of Vertex's model naming, not litellm's routing scheme. Prepend vertex_ai/ and litellm picks up the Vertex Gemma handler:

vertex_ai/google/gemma-4-26b-a4b-it-maas

Add the model to the existing anthropic provider block in ~/.config/opencode/opencode.jsonc — the same block you already configured for Claude:

"anthropic": {
  "options": {
    "baseURL": "http://127.0.0.1:8787/v1",
    "apiKey": "sk-ant-dummy"
  },
  "models": {
    "vertex_ai/google/gemma-4-26b-a4b-it-maas": {
      "name": "Gemma 4 26B IT (Vertex, via Headroom)",
      "limit": { "context": 200000, "output": 128000 }
    }
  }
}

Verify it before selecting it in OpenCode:

curl -s -X POST http://127.0.0.1:8787/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: sk-ant-dummy" \
  -H "anthropic-version: 2023-06-01" \
  -d '{"model":"vertex_ai/google/gemma-4-26b-a4b-it-maas","max_tokens":32,"messages":[{"role":"user","content":"Reply with exactly the word: pong"}]}'

A valid {"type":"message",...,"content":[{"type":"text","text":"pong"}],...} response means Headroom is routing Gemma correctly through litellm's Vertex handler. In OpenCode, select anthropic/vertex_ai/google/gemma-4-26b-a4b-it-maas. Compression and ADC auth apply exactly as they do for Claude.

What you have now

Three working routes are now configured with Headroom: Anthropic's Claude on Google Cloud through litellm, Google's Gemini on Google Cloud through Headroom's native passthrough, and Gemma 4 through the litellm backend (same as Claude). They route compressed traffic from your machine and authenticate through your existing Google Cloud credentials. Original google-vertex/* selections continue to work uncompressed.

Another path exists for the direct Gemini Developer API (API-key auth) rather than Google Cloud. The same native passthrough handles it, pointed at a different host.

The full setup, including a troubleshooting table and config snippets, lives in the Headroom repo and its docs.

Our usage through this Google Cloud AI setup has been light so far. Because we run in Headroom's default cache-preserving mode rather than its more aggressive token-compression mode, our headroom doctor output does not yet match those headline percentages. We look forward to watching that change as we use this setup more.

Happy compressing!