Run the Claude Code CLI against Kimi, GLM, Grok, MiniMax or Claude, with every request going through your own Cloudflare account — one bill, one log, no provider API keys in your shell.
Cloudflare publishes a first-party Claude Code integration, but it covers the anthropic
provider only: Claude Code speaks the Anthropic Messages API (POST /v1/messages) and
every other model in the Cloudflare catalogue is Chat Completions. This Worker is the
missing translation layer, in one file, deployed on your account.
Measured on claude-cli 2.1.165, 2026-07-25. Full write-up with sources:
https://miscsubjects.com/a/claude-code-on-cloudflare-ai-gateway
claude ──▶ POST /v1/messages ──▶ this Worker ──▶ your AI Gateway ──▶ @cf/moonshotai/kimi-k2.7-code
├▶ @cf/zai-org/glm-5.2
├▶ moonshotai/kimi-k3 (1M ctx)
├▶ xai/grok-4.5
├▶ minimax/m3
└▶ anthropic/claude-opus-5
1. Create an API token at https://dash.cloudflare.com/profile/api-tokens with
Workers AI: Read + Workers AI: Run and AI Gateway: Run on your account.
2. Make sure your gateway has authentication ON. Unified Billing is refused on an
unauthenticated gateway — catalogue models return HTTP 402 with
Gateway authentication is required to use unified billing while @cf/… models keep
working, which reads like a model problem and is a gateway setting.
3. Deploy.
git clone https://github.com/massoumicyrus/claude-code-cloudflare-gateway
cd claude-code-cloudflare-gateway
npx wrangler deploy # creates the Worker; secrets need it to exist
npx wrangler secret put CF_ACCOUNT_ID # your account id
npx wrangler secret put CF_API_TOKEN # the token from step 1
npx wrangler secret put SHIM_TOKEN # any random string, e.g. openssl rand -base64 24
npx wrangler secret put AIG_RUN_TOKEN # optional: gateway Run token for cf-aig-authorizationDeploy first. wrangler secret put against a Worker that does not exist yet prompts to
create one interactively and fails outright in a non-interactive shell.
Two more optional secrets the Worker reads:
| Secret | What it does | Default |
|---|---|---|
AIG_GATEWAY_ID |
Which AI Gateway the requests are logged and billed through. Must be an authenticated gateway for Unified Billing models to work. | default |
DEFAULT_MODEL |
The model any unresolved slot falls back to — an empty model, or a Claude-shaped name that matches no alias and no keyword. |
@cf/moonshotai/kimi-k2.7-code |
4. Point the CLI at it.
export ANTHROPIC_BASE_URL="https://claude-code-cloudflare-gateway.<subdomain>.workers.dev/<SHIM_TOKEN>"
export ANTHROPIC_AUTH_TOKEN="<SHIM_TOKEN>"
export ANTHROPIC_API_KEY=""
export ANTHROPIC_MODEL="kimi"
export ANTHROPIC_DEFAULT_OPUS_MODEL="kimi"
export ANTHROPIC_DEFAULT_SONNET_MODEL="kimi"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="glm-flash" # the background/title slot
export CLAUDE_CODE_SUBAGENT_MODEL="kimi"
export ENABLE_TOOL_SEARCH=true # see "The two settings that matter"
export CLAUDE_CODE_ATTRIBUTION_HEADER=0
claudeThe SHIM_TOKEN is both the path segment and the accepted bearer, so it works whether or
not your client sends ANTHROPIC_AUTH_TOKEN (an existing /login session can override
that variable).
Persist it instead in ~/.claude/settings.json:
{
"env": {
"ANTHROPIC_BASE_URL": "https://<worker-host>/<SHIM_TOKEN>",
"ANTHROPIC_AUTH_TOKEN": "<SHIM_TOKEN>",
"ANTHROPIC_MODEL": "claude-kimi-k2.7-code",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-kimi-k2.7-code",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-kimi-k2.7-code",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-glm-flash",
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-kimi-k2.7-code",
"ENABLE_TOOL_SEARCH": "true",
"CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
"DISABLE_TELEMETRY": "1",
"API_TIMEOUT_MS": "1200000"
}
}Why claude-kimi-k2.7-code and not kimi? The desktop client validates model names
against an Anthropic-shaped allowlist and rejects kimi-k2.7-code before sending anything.
Any name containing kimi, glm, grok or gpt resolves to that model here, so a
Claude-shaped name routes wherever you want.
| Alias | Resolves to | Billing |
|---|---|---|
kimi |
@cf/moonshotai/kimi-k2.7-code (262k ctx, tool calling) |
Workers AI |
kimi-k2.6 |
@cf/moonshotai/kimi-k2.6 |
Workers AI |
kimi-k3 |
moonshotai/kimi-k3 (1,048,576 ctx) |
Unified Billing |
glm |
@cf/zai-org/glm-5.2 |
Workers AI |
glm-flash |
@cf/zai-org/glm-4.7-flash (cheapest tool-caller) |
Workers AI |
grok |
xai/grok-4.5 |
Unified Billing |
gpt |
openai/gpt-5.5 |
Unified Billing |
minimax |
minimax/m3 |
Unified Billing |
opus5 / sonnet5 |
anthropic/claude-opus-5 / -sonnet-5 |
Unified Billing |
Any @cf/... id or author/model id passes through unchanged. anthropic/* takes the
native lane (/ai/v1/messages, body untouched, no translation loss); everything else is
translated over /ai/v1/chat/completions.
Both measured through one gateway with a 856-tool MCP server attached:
| Configuration | Input tokens / turn | Cached input | Cost / turn |
|---|---|---|---|
| MCP attached, no tool search | 149,187 | 64 | $0.02852109 |
MCP attached, ENABLE_TOOL_SEARCH=true |
14,109 | 12,480 | $0.00443075 |
| MCP server disabled mid-session | 21,928 | 13,312 | $0.01089448 |
| Protocol only, no MCP server | 14,071 | — | $0.00456265 |
ENABLE_TOOL_SEARCH=truedrops the request from 856 tool definitions to 9 plus aToolSearchtool the model calls on demand. Verified end to end: Kimi searched for an MCP tool it had never been shown and invoked it correctly. Tool schemas, not the conversation, are what a big MCP setup actually costs.CLAUDE_CODE_ATTRIBUTION_HEADER=0stops the client prepending a per-request nonce (x-anthropic-billing-header: … cch=…) to the system prompt.api.anthropic.comstrips that line positionally; everyone else caches nothing behind it. This Worker also strips it server-side, which is why cached input goes from 64 tokens to ~13,000.
Seven things, each of which is a real bug in some published shim:
- Streams. A gateway that buffers whole responses stalls the client.
- Emits tool arguments incrementally (
content_block_start→input_json_delta), not one blob at the end. - Orders tool results correctly: Anthropic puts
tool_resultin a user turn, OpenAI wantsrole:"tool"messages straight after the assistant call. - Maps stop reasons (
stop→end_turn,length→max_tokens,tool_calls→tool_use). - Never returns an empty turn: Kimi and GLM spend the output budget on
reasoning_contentfirst, and an empty assistant turn makes the CLI abort with "no visible output". The reasoning text is used as a fallback. - Answers
/v1/messages/count_tokenswith an estimate instead of a 404. - Drops the attribution block without reordering the
systemarray — the strip is positional, and a merged block starting with that header swallows the rest of your system prompt.
Requires Node 18 or newer.
node tools/contract-test.mjs https://<worker-host>/<SHIM_TOKEN> kimi42 checks over the parts the CLI actually depends on: the Anthropic envelope, usage and
stop-reason mapping, the full SSE event sequence in order with every content block closed
before message_delta and usage.output_tokens on it, a streamed tool_use block whose
arguments arrive as input_json_delta and parse to valid JSON, the same tool call
non-streamed, a tool_result second turn the model reads, count_tokens, the model list
with every published id actually resolving to the model its display_name names, that a
wrong token and an unauthenticated GET /v1/models are both refused while x-api-key and
Authorization: Bearer both work, and the error statuses (400 on a non-JSON body, 404 on an
unknown path, 405 on PUT/DELETE). Every check passes on the reference deployment as of
2026-07-25.
GET /v1/models— the alias list in the shape gateway model discovery accepts. That discovery is off by default (CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1, CLI ≥ 2.1.129), times out at three seconds, treats any redirect as failure, and drops every id that does not start withclaudeoranthropic— which is why the ids here areclaude-kimi-k2.7-code,claude-glm-5.2and so on, with the real model indisplay_name.- Images.
imageblocks are forwarded as OpenAIimage_urlparts, base64 or URL. Verified: an 8×8 blue PNG pasted through the CLI came back "Blue" from@cf/moonshotai/kimi-k2.7-code. - Per-session cost attribution.
cf-aig-metadatacarries the CLI'sx-claude-code-session-id,x-claude-code-agent-idand parent agent id, plus the model you asked for and the tool count, so the gateway's cost view is filterable per session and per subagent instead of one blur. - Gateway-side retries.
cf-aig-max-attempts: 3with exponential backoff, because the client's own retry logic matches on Anthropic's error wording and will not fire for an upstream that phrases failures differently.
tools/capture-gateway.mjs is a local Anthropic-compatible server that logs exactly what
the client sends and answers with a valid response, so you can re-derive all of this on a
newer CLI version instead of trusting this README:
node tools/capture-gateway.mjs # listens on :8787, appends ./capture.jsonl (override with CAPTURE_LOG)
ANTHROPIC_BASE_URL=http://localhost:8787 ANTHROPIC_AUTH_TOKEN=x claude -p "say ok"tools/benchmark.mjs is the scorer and artifact writer used by the V2 comparison. It refuses a
run if the per-row MCP, deferred Tool Search and protocol-only trials differ on model, gateway,
catalogue snapshot or environment hash. A trial passes only when its final value is correct and
every task-required lifecycle event occurred; a plausible final answer without the required
resolve/invoke/receipt path fails.
Adapters receive one bounded JSON request on stdin and return one JSON result. The harness writes
raw trials.jsonl, machine-readable summary.json and human-readable summary.md, including
full-task success, calls, latency, input/output tokens and cost supplied by the adapter. Its cost
wording treats deferred Tool Search and protocol-only as effectively equal when their mean cost is
within 5%; it does not turn a rounding difference into a winner.
node --test tools/benchmark.test.mjsThe fixed tasks, real-model adapter and release results live with the portable catalogue runtime so they can exercise create, resolve, invoke, receipt, replay and repair on a clean local install.
- Anthropic "doesn't endorse, maintain, or audit third-party gateway products, and doesn't support routing Claude Code to non-Claude models through any gateway." Nothing forbids it; you are the one who keeps up when the client changes.
thinkingis dropped rather than translated. That is deliberate — the client sendsthinking: {"type":"adaptive"}and many backends 400, empty out or hang on it.- Prompt caching is whatever the upstream does.
cache_controlbreakpoints are not honoured by Workers AI models; the cached-input rates are prefix-based, which is why stripping the nonce matters. - The model picker will not list these models: gateway model discovery is off by default
and ignores any id that does not start with
claudeoranthropic. - AI Gateway token permissions cannot be scoped to one gateway. A Run token reaches every gateway on the account, including any holding stored provider keys.
MIT.