Skip to content

Repository files navigation

zen-proxy

Single-binary proxy that lets Claude Code, Codex, and any OpenAI-compatible tool use OpenCode Zen or OpenRouter models.

Claude Code ──→ /v1/messages (Anthropic) ──┐
Codex ──→ /v1/responses (OpenAI) ──┼──→ Chat Completions ──→ Zen or OpenRouter
Cursor etc ──→ /v1/chat/completions ──┘

Single Go binary with no runtime dependencies. Zen free models work without an API key; OpenRouter requires your OpenRouter API key.

Install

Recommended:

curl -fsSL https://raw.githubusercontent.com/azain47/zen-proxy/main/install.sh | sh

The installer downloads a prebuilt release asset when available, installs the zen-proxy binary into /usr/local/bin or ~/.local/bin, and falls back to building from source when run inside a checkout. Published SHA-256 checksums are verified when sha256sum or shasum is available.

Check the installed binary against the latest GitHub release without reinstalling it:

./install.sh --check-update

The check exits successfully when the installed release matches the latest tag.

go install github.com/azain47/zen-proxy/cmd/zen-proxy@latest

Or build from source:

git clone https://github.com/azain47/zen-proxy
cd zen-proxy
make build

From a source checkout you can also run:

./install.sh

Installer environment variables:

Env varDescription
ZEN_PROXY_VERSIONRelease tag to install (default: latest)
ZEN_PROXY_BINDIRInstall directory override
ZEN_PROXY_REPOGitHub owner/repository for installation and update checks, e.g. owner/zen-proxy
ZEN_PROXY_FROM_SOURCE=1Force local source build and install

Release Builds

Build release artifacts for macOS, Linux, and Windows on both amd64 and arm64:

make release VERSION=v0.3.0

Artifacts are written to dist/:

PlatformArtifact
macOS Intelzen-proxy_darwin_amd64.tar.gz
macOS Apple Siliconzen-proxy_darwin_arm64.tar.gz
Linux amd64zen-proxy_linux_amd64.tar.gz
Linux arm64zen-proxy_linux_arm64.tar.gz
Windows amd64zen-proxy_windows_amd64.zip
Windows arm64zen-proxy_windows_arm64.zip

Each archive includes the project and third-party licenses. The release target also writes dist/checksums.txt. The installer looks for the macOS/Linux .tar.gz names above when installing from GitHub Releases.

Releases are cut from main. Because main is protected, land changes through a reviewed pull request first (CI must pass and the owner must approve), then create and push a version tag from an up-to-date main:

git switch main && git pull
git tag -a v0.3.0 -m "zen-proxy v0.3.0"
git push origin v0.3.0

Pushing a v* tag triggers the release workflow, which reruns the test suite, builds the artifacts above, and publishes them to a GitHub Release with generated notes. Published v* tags are protected from deletion and force-updates, so bump the patch or minor version to ship a fix instead of re-pushing an existing tag.

Project Layout

cmd/zen-proxy/ CLI entrypoint and version wiring
internal/proxy/ protocol translation, upstream handling, config, tests
.github/workflows/ CI and release automation
install.sh standalone installer

Usage

./zen-proxy

Inspect the installed version or current model catalog:

zen-proxy --version
zen-proxy --check-update
zen-proxy models

For a live request inspector, start the proxy with:

zen-proxy --tui

The dashboard keeps the latest 100 requests in memory and shows each request's inbound payload, translated upstream request, upstream response, and response to the client. JSON bodies are shown as an ordered, collapsible tree with syntax-colored keys and values; word wrapping is enabled by default. Use Up/Down to move through JSON nodes, Left/Right or Enter to collapse/expand objects and arrays, and w to toggle word wrapping. With wrapping off, h/l or Left/Right pan long lines and 0 resets the JSON column. Statuses remain explicit without color ([WAIT], [OK], [WARN], [ERR]). Use j/k or n/p to select requests, Tab/Shift-Tab or [/] to change views, y to copy the selected JSON node, c to copy the current panel, or Y to copy the complete trace as Markdown through OSC 52. Number keys 1-4, Page Up/Page Down, Ctrl+U/Ctrl+D, and Home/End remain available. The mouse also works: the wheel scrolls the detail panel, clicking a request in the list selects it, clicking a tab in the detail header switches views, and clicking a JSON node folds/unfolds and selects it. Drag across any text to select a rectangular region; releasing the button auto-copies it to the clipboard via OSC 52. --no-color, NO_COLOR, and TERM=dumb disable dashboard colors. For headless debugging, zen-proxy --verbose (or --debug) prints the same sanitized traces to stderr.

By default, the proxy prints a compact startup summary instead of dumping every model ID. Use zen-proxy models when you need the current catalog. Startup output has this form:

zen-proxy v0.2.1
ready http://127.0.0.1:8788
route zen -> https://opencode.ai/zen/v1/chat/completions
model deepseek-v4-flash-free
catalog <count> models (<free> free, <other> other)
inspect zen-proxy --tui

With OpenRouter

Create an API key at OpenRouter, then run:

ZEN_PROVIDER=openrouter OPENROUTER_API_KEY=sk-or-v1-your-key zen-proxy

The OpenRouter preset uses:

SettingValue
Upstreamhttps://openrouter.ai/api/v1/chat/completions
Modelshttps://openrouter.ai/api/v1/models
Fallback modelopenrouter/free

You can use any OpenRouter model name directly, including :free models such as qwen/qwen3-coder:free, or the free-model router openrouter/free.

For OpenRouter, startup output lists only free text-output models to keep the terminal readable. The /v1/models endpoint still returns the full upstream model list.

Optional OpenRouter metadata headers:

OPENROUTER_HTTP_REFERER=https://your-app.example \
OPENROUTER_APP_TITLE="your app name" \
ZEN_PROVIDER=openrouter \
OPENROUTER_API_KEY=sk-or-v1-your-key \
zen-proxy

With Claude Code

export ANTHROPIC_BASE_URL=http://localhost:8788
export ANTHROPIC_API_KEY=anything
export ANTHROPIC_MODEL=deepseek-v4-flash-free
claude

You can switch models in-session with /model or set any model from the list above.

With Codex

Start the proxy, then install a Codex profile from another terminal:

zen-proxy
./scripts/install-codex-profile.sh

This writes ~/.codex/zen-proxy.config.toml. Codex 0.134.0 and later load that file when --profile zen-proxy is selected. Codex fetches per-model metadata (context window, reasoning levels, and agent instructions) from the proxy's /v1/models endpoint, so the generated profile does not override model-specific context limits. Set ZEN_PROXY_CODEX_CONTEXT_WINDOW or ZEN_PROXY_CODEX_COMPACT_LIMIT only when you intentionally want fixed values.

Manual equivalent:

model = "deepseek-v4-flash-free"model_provider = "zen-proxy"
[model_providers.zen-proxy]
name = "zen-proxy"base_url = "http://127.0.0.1:8788/v1"wire_api = "responses"
[model_providers.zen-proxy.auth]
command = "/usr/bin/printf"args = ["zen-proxy"]
timeout_ms = 1000refresh_interval_ms = 0

The static command-auth token is not a secret and Zen Proxy does not validate it. It is present because current Codex versions refresh a custom provider's /models endpoint only when that provider has command-backed authentication. This enables live model metadata without using ChatGPT authentication or a separate helper script.

Custom Codex agent instructions

By default the proxy serves a bundled copy of Codex's official agent prompt as the base_instructions of every advertised model. It comes from codex-rs/models-manager/prompt.md in openai/codex under Apache-2.0. To replace it with your own prompt, point at a file:

ZEN_PROXY_CODEX_INSTRUCTIONS_FILE=/path/to/my-prompt.md zen-proxy

The file contents are served as-is to every Codex model entry, fully replacing the built-in prompt. If the path is unset or unreadable, the proxy logs a warning and falls back to the bundled prompt. Restart the proxy after editing the file; it is read once at startup.

Start Codex with that profile:

codex --profile zen-proxy

To change models, keep the provider fixed and change only the model slug:

codex --profile zen-proxy -m mimo-v2.5-free
codex --profile zen-proxy -m 'qwen/qwen3-coder:free'

Inside an active Codex session, use /model and enter any model supported by your selected upstream. zen-proxy passes the selected model through unchanged; if a provider-side model is temporarily unavailable, pick another model from the startup list.

Do not use only OPENAI_BASE_URL for Codex. That leaves Codex on the built-in OpenAI provider path, where ChatGPT-account auth can reject non-OpenAI model names before the request reaches zen-proxy.

With Cursor / Continue / any OpenAI-compatible tool

  • Base URL: http://localhost:8788/v1
  • API Key: anything (not validated)
  • Model: any model from the list shown at startup

Configuration

Env varDefaultDescription
ZEN_HOST127.0.0.1Listen host. Use 0.0.0.0 only if you intentionally want network access
ZEN_PORT8788Listen port
ZEN_PROVIDERzenProvider preset: zen or openrouter
ZEN_UPSTREAMhttps://opencode.ai/zen/v1/chat/completionsUpstream endpoint
ZEN_MODELS_URLderived from upstreamUpstream models endpoint
ZEN_MODEL_METADATA_URLunsetOptional LiteLLM-compatible capability catalog URL
ZEN_CORS_ORIGINSunsetComma-separated exact browser origins allowed to call the proxy. Browser requests with an Origin header are denied by default; CLI clients do not require CORS
ZEN_VERBOSE / ZEN_DEBUGunsetPrint sanitized inbound, translated upstream, upstream response, and client response payloads
ZEN_TUIunsetStart the live in-memory request inspector; equivalent to --tui
ZEN_API_KEYpublicUpstream API key (use public for free tier)
ZEN_MODELdeepseek-v4-flash-freeFallback model when request has none
OPENROUTER_API_KEYunsetAPI key used when ZEN_PROVIDER=openrouter and ZEN_API_KEY is unset
OPENROUTER_HTTP_REFERERunsetOptional OpenRouter attribution header
OPENROUTER_APP_TITLEzen-proxyOptional OpenRouter attribution header
ZEN_HTTP_REFERERunsetAlias for OPENROUTER_HTTP_REFERER
ZEN_APP_TITLEunsetAlias for OPENROUTER_APP_TITLE
ZEN_PROXY_CODEX_INSTRUCTIONS_FILEunsetPath to a file whose contents replace Codex's built-in agent prompt in every /v1/models entry. See Custom Codex agent instructions.

The proxy passes through whatever model name the client sends. ZEN_MODEL is only used as a fallback. When the built-in fallback is missing from the fetched model list, Zen selects a current upstream model and OpenRouter selects a current free text model. An explicit ZEN_MODEL value is never replaced; if no safe replacement is available, the configured default is retained with a warning. When ZEN_MODEL_METADATA_URL is configured, provider model data is enriched from that LiteLLM-compatible catalog. Provider-reported context windows take precedence; the catalog fills missing context windows and known reasoning, parallel-tool, and vision capabilities. If the catalog is unavailable or has no matching entry, the proxy exposes conservative low, medium, and high reasoning controls but otherwise avoids guessing model capabilities. Providers may ignore a reasoning effort for models that do not implement one. For Anthropic Messages, output_config.effort is forwarded as the upstream reasoning_effort; enabled thinking budgets remain a fallback mapping. A mutable hosted catalog is intentionally not enabled by default.

For example, to opt into LiteLLM's staging catalog explicitly:

ZEN_MODEL_METADATA_URL=https://raw.githubusercontent.com/BerriAI/litellm/litellm_internal_staging/model_prices_and_context_window.json zen-proxy

Endpoints

MethodPathProtocol
POST/v1/messagesAnthropic Messages API
POST/v1/messages/count_tokensAnthropic token-count estimate
POST/v1/responsesOpenAI Responses API
POST/v1/chat/completionsOpenAI Chat Completions
GET/v1/modelsModel listing snapshot fetched from the selected upstream at startup
GET/healthHealth check

How it works

  1. On startup, fetches available models from the upstream models endpoint
  2. Accepts requests in three API formats (Anthropic, Responses, Chat Completions)
  3. Translates all requests into OpenAI Chat Completions format
  4. Forwards to the selected upstream with the configured authorization
  5. Translates responses back to the original protocol format

Streaming, tool calls, and thinking/reasoning blocks are all supported.

Security

zen-proxy is designed as a local developer tool. It listens on 127.0.0.1 by default and does not authenticate incoming requests. Do not expose it directly to the public internet; if you set ZEN_HOST=0.0.0.0, protect it with your own network controls.

Provider API keys are forwarded to the selected upstream. Normal mode does not store request bodies, responses, or keys. Debug and TUI modes retain a bounded, sanitized in-memory history for inspection. See SECURITY.md for details.

Using paid models

Set ZEN_API_KEY to your OpenCode API key to access paid models:

ZEN_API_KEY=sk-your-key zen-proxy

Then use any model from the paid list (e.g., claude-opus-4-8, gpt-5.5, deepseek-v4-pro).

License

Zen Proxy's original code is MIT licensed. The bundled OpenAI Codex agent prompt is Apache-2.0 licensed. See THIRD_PARTY_NOTICES.md and LICENSES/Apache-2.0.txt.

About

Single-binary proxy so Claude Code, Codex, and OpenAI-compatible tools can use OpenCode Zen or OpenRouter models

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages