A zero-dependency protocol bridge that lets Claude Code (Anthropic Messages) and Codex (OpenAI Responses) share the OpenAI-compatible models in your OpenCode Go subscription — default model deepseek-v4-flash. Pure Python standard library, single file, no third-party deps.
中文简介:一个零依赖的协议中转站,让 Claude Code(Anthropic Messages 协议)和 Codex(OpenAI Responses 协议)共用 OpenCode Go 订阅里的 OpenAI 兼容模型。纯 Python 标准库,单文件,无第三方依赖。
OpenCode Go exposes an OpenAI-compatible endpoint (https://opencode.ai/zen/go/v1), but the models behind it (e.g. DeepSeek V4 Flash) only speak chat/completions:
- Claude Code only speaks the Anthropic Messages protocol → needs a bridge to translate Messages ⇄ chat/completions (including streaming SSE and tool calls).
- Codex can point directly at the upstream with
wire_api = "chat"(no relay needed), but routing it through the relay gives you a single auth token, a single model alias, and one place to deploy.
POST /v1/messages(Anthropic) ⇄ upstreamchat/completions- system blocks, tool use / tool results, images (URL + base64), thinking blocks
tool_choicemapping (any→required, named tool, etc.)- full SSE streaming translation:
message_start,content_block_delta,input_json_delta,message_delta,message_stop
POST /v1/responses(Responses) ⇄ upstreamchat/completionsinstructions→ system,function_call/function_call_outputitems,max_output_tokens→max_tokens- SSE translation:
response.created,output_text.delta,function_call_arguments.delta,response.completed
POST /v1/chat/completions— pass-through (auth only; the client's model is forwarded, defaulting toDEFAULT_MODELwhen omitted)GET /v1/models,GET /healthz- Token auth (
RELAY_TOKEN), 64 MB body cap,stream_optionsauto-retry fallback for picky upstreams - Python 3.9+, standard library only
# on your public server
export OPENCODE_GO_API_KEY="..." # your OpenCode Go key (stays on the server)
export RELAY_TOKEN="change-me" # clients must send this (strongly recommended)
python3 relay.py # listens on 0.0.0.0:8787Test it locally with a mocked upstream (no network, no real key needed):
python3 test_relay.py| Variable | Default | Description |
|---|---|---|
OPENCODE_GO_API_KEY |
(empty) | Optional. If set, all requests share this single key (server-key mode). If empty, every request must carry its own key (Authorization: Bearer <key> or x-api-key: <key>), which is forwarded upstream as-is. |
RELAY_TOKEN |
(empty) | Optional client auth token for your own access control. Set it (or restrict by IP) before exposing publicly — without it anyone can use your relay. In per-request key mode, leave it empty so the client's own key is the credential. |
DEFAULT_MODEL |
deepseek-v4-flash |
Fallback model used when the client omits one; also advertised in /v1/models. Clients can request any OpenCode Go model (e.g. glm-5.2) and it is forwarded as-is. |
UPSTREAM_BASE |
https://opencode.ai/zen/go/v1 |
Upstream OpenAI-compatible base URL. |
HOST / PORT |
0.0.0.0 / 8787 |
Listen address. |
STREAM_OPTIONS |
1 |
Send stream_options.include_usage upstream; set 0 if the upstream rejects it. |
REQUEST_TIMEOUT |
600 |
Upstream request timeout (seconds). |
UPSTREAM_UA |
(browser UA) | User-Agent sent upstream; some edges (e.g. Cloudflare) reject urllib's default. |
MAX_CONNECTIONS |
64 |
Max concurrent connections; excess connections wait (backpressure). |
CLIENT_TIMEOUT |
60 |
Per-connection read timeout in seconds; idle connections (slowloris) are dropped. |
| Path | Protocol | Translation |
|---|---|---|
POST /v1/messages |
Anthropic | → chat/completions |
POST /v1/responses |
Responses | → chat/completions |
POST /v1/chat/completions |
OpenAI | passthrough (model rewritten) |
GET /v1/models |
OpenAI | model list |
GET /healthz |
— | health check (no auth) |
Auth: every request except /healthz must carry a key. In per-request key mode that key is the client's own OpenCode Go key (sent as Authorization: Bearer <key> or x-api-key: <key>) and is forwarded upstream. If RELAY_TOKEN is set, it acts as an additional gate: send it in one of the two headers and the upstream key in the other.
Leave OPENCODE_GO_API_KEY unset. Claude Code already authenticates with
ANTHROPIC_AUTH_TOKEN, so you only need to change the base URL — the key you
already have is your OpenCode Go key, and the relay forwards it untouched:
export ANTHROPIC_BASE_URL="https://your-server:8558/opencode-go/anthropic"
export ANTHROPIC_AUTH_TOKEN="<your own OpenCode Go key>"
export ANTHROPIC_MODEL="deepseek-v4-flash"
claude中文:不设置
OPENCODE_GO_API_KEY时, relay 从每个请求里取 key 并原样转发上游, 服务器不保存任何 key。Claude Code 的ANTHROPIC_AUTH_TOKEN就是这个 key, 所以只需要把ANTHROPIC_BASE_URL指向 relay, 其余全部照旧。
Same for Codex: keep your OpenCode Go key as OPENCODE_GO_API_KEY in your
provider config and point base_url at the relay.
export ANTHROPIC_BASE_URL="http://your-server:8787"
export ANTHROPIC_AUTH_TOKEN="<RELAY_TOKEN>"
export ANTHROPIC_MODEL="deepseek-v4-flash" # optional aliasOr in ~/.claude/settings.json:
{
"env": {
"ANTHROPIC_BASE_URL": "http://your-server:8787",
"ANTHROPIC_AUTH_TOKEN": "<RELAY_TOKEN>",
"ANTHROPIC_MODEL": "deepseek-v4-flash"
}
}中文:Claude Code 通过环境变量指向 relay(
ANTHROPIC_BASE_URL),relay 把 Messages 协议翻译成上游 chat/completions。
Either point Codex straight at the upstream (OpenAI chat wire — no relay needed):
# ~/.codex/config.toml
[model_providers.opencode]
name = "OpenCode Go"
base_url = "https://opencode.ai/zen/go/v1"
env_key = "OPENCODE_GO_API_KEY"
wire_api = "chat"export OPENCODE_GO_API_KEY="..."
codex --provider opencode --model deepseek-v4-flashOr route it through the relay for unified auth (works with both wire_api = "chat" and wire_api = "responses"):
# ~/.codex/config.toml
[model_providers.opencode-relay]
name = "OpenCode Go (relay)"
base_url = "http://your-server:8787/v1"
env_key = "RELAY_TOKEN"
wire_api = "chat"See examples/opencode-go-relay.service for a ready-to-adapt unit file. Use EnvironmentFile= to keep keys out of the unit itself.
For an Alibaba Cloud (mainland ECS) walkthrough — user-level Python via uv,
nginx HTTPS on a custom port with a path prefix, per-request key mode, security
group and ICP-filing notes — see docs/aliyun-deploy.md.
OPENCODE_GO_API_KEYis your paid subscription key — keep it on the server only. Clients talk to the relay, never to the upstream.- Always set
RELAY_TOKEN; the relay warns loudly on startup if it's missing. - The relay is plain HTTP — terminate TLS in front of it (Caddy / nginx / Traefik) if it's exposed beyond a trusted network.
test_relay.py spins up a mock upstream and validates all six paths (auth, models, Anthropic non-stream/stream, Responses non-stream/stream, chat passthrough) with no network access:
python3 test_relay.py