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 var Description
ZEN_PROXY_VERSION Release tag to install (default: latest)
ZEN_PROXY_BINDIR Install directory override
ZEN_PROXY_REPO GitHub owner/repository for installation and update checks, e.g. owner/zen-proxy
ZEN_PROXY_FROM_SOURCE=1 Force 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/:

Platform Artifact
macOS Intel zen-proxy_darwin_amd64.tar.gz
macOS Apple Silicon zen-proxy_darwin_arm64.tar.gz
Linux amd64 zen-proxy_linux_amd64.tar.gz
Linux arm64 zen-proxy_linux_arm64.tar.gz
Windows amd64 zen-proxy_windows_amd64.zip
Windows arm64 zen-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:

Setting Value
Upstream https://openrouter.ai/api/v1/chat/completions
Models https://openrouter.ai/api/v1/models
Fallback model openrouter/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 = 1000
refresh_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 var Default Description
ZEN_HOST 127.0.0.1 Listen host. Use 0.0.0.0 only if you intentionally want network access
ZEN_PORT 8788 Listen port
ZEN_PROVIDER zen Provider preset: zen or openrouter
ZEN_UPSTREAM https://opencode.ai/zen/v1/chat/completions Upstream endpoint
ZEN_MODELS_URL derived from upstream Upstream models endpoint
ZEN_MODEL_METADATA_URL unset Optional LiteLLM-compatible capability catalog URL
ZEN_CORS_ORIGINS unset Comma-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_DEBUG unset Print sanitized inbound, translated upstream, upstream response, and client response payloads
ZEN_TUI unset Start the live in-memory request inspector; equivalent to --tui
ZEN_API_KEY public Upstream API key (use public for free tier)
ZEN_MODEL deepseek-v4-flash-free Fallback model when request has none
OPENROUTER_API_KEY unset API key used when ZEN_PROVIDER=openrouter and ZEN_API_KEY is unset
OPENROUTER_HTTP_REFERER unset Optional OpenRouter attribution header
OPENROUTER_APP_TITLE zen-proxy Optional OpenRouter attribution header
ZEN_HTTP_REFERER unset Alias for OPENROUTER_HTTP_REFERER
ZEN_APP_TITLE unset Alias for OPENROUTER_APP_TITLE
ZEN_PROXY_CODEX_INSTRUCTIONS_FILE unset Path 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

Method Path Protocol
POST /v1/messages Anthropic Messages API
POST /v1/messages/count_tokens Anthropic token-count estimate
POST /v1/responses OpenAI Responses API
POST /v1/chat/completions OpenAI Chat Completions
GET /v1/models Model listing snapshot fetched from the selected upstream at startup
GET /health Health 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