# OpenCode Zen Proxy Proxy translating OpenAI and Anthropic Messages API requests to OpenCode Zen's free model API. ## Build & Run ```bash go build -o opencode-proxy . ./opencode-proxy # no auth ./opencode-proxy -api-key "secret" # with auth ``` Auth reads `OPENCODE_PROXY_KEY` env var; `-api-key` flag takes precedence. Defaults: `127.0.0.1:6446`. Use `-host 0.0.0.0` for external access. ## Architecture ``` Client → internal/server/ (HTTP, auth, routing) → internal/proxy/ (model resolve, session mgmt, forwarding, Anthropic↔OpenAI conversion) → opencode.ai/zen/v1 ``` - **Pure stdlib Go 1.22** — no router, no deps beyond stdlib - **Entrypoint:** `main.go` wires `proxy.NewProxy(key)` into `server.NewServer(p)` - **Auth middleware** in `server.go:88` checks `Authorization: Bearer` or `x-api-key` header - **Model aliases** in `internal/proxy/models.go:59` — short names, company names, and Claude model names → full IDs - **Session rotation:** session IDs rotate every 30 min per user key (`proxy.SessionID()` at `models.go:154`) - **Pass-through:** unknown model names are forwarded as-is, not rejected ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/health` | Health check (returns hardcoded `"1.0.0"`) | | `GET` | `/v1/models` | List available free models (auth required if key set) | | `GET` | `/v1/models/{id}` | Single model detail | | `POST` | `/v1/chat/completions` | OpenAI Chat Completions (streaming + non-streaming) | | `POST` | `/v1/messages` | Anthropic Messages API | | `POST` | `/v1/v1/messages` | Same as above — workaround for Claude Code double-path bug | ## Anthropic Messages API Fully implemented in `internal/proxy/anthropic.go`. Converts Anthropic → OpenAI format upstream, then converts responses back to Anthropic SSE (streaming) or JSON (non-streaming). **Claude Code quirk:** Claude Code appends `/v1/messages` to `ANTHROPIC_BASE_URL` automatically. Set `ANTHROPIC_BASE_URL` to `http://127.0.0.1:6446` (without `/v1` suffix) to avoid double-path. The proxy also handles `/v1/v1/messages` as a fallback (`server.go:62`). **Claude model name aliases** are defined in `models.go:77-88` — Claude Code validates model names client-side, so you must set `ANTHROPIC_MODEL` to a valid Claude name like `claude-sonnet-4-6` (maps to `deepseek-v4-flash-free`). ## Available Models (from `models.go`) | Model ID | Aliases | Context | Max Output | |----------|---------|---------|------------| | `deepseek-v4-flash-free` | `deepseek`, `deepseek-v4`, `ds` | 1M | 384K | | `big-pickle` | `pickle` | 200K | 32K | | `mimo-v2.5-free` | `mimo`, `mimo-v2.5`, `xiaomi` | 1M | 32K | | `north-mini-code-free` | `north`, `north-mini`, `cohere` | 256K | 64K | | `nemotron-3-ultra-free` | `nemotron`, `nemotron-3`, `nvidia` | 1M | 16K | **Source of truth is `models.go`, not `README.md`.** The README may list models that differ from the code (e.g. `minimax-m2.5-free`, `kimi-k2.5-free`, `gpt-5-nano`, `nemotron-3-super-free`, `qwen3.6-plus-free` are NOT in the code). The code controls what works. ## Quirks & Gotchas - **No tests exist.** Any change is untested unless you add them. - **No CI/CD, no Makefile, no lint config.** All manual. - **In-memory only.** Session state dies on restart. No database. - **`deploy/start-proxy.bat`** and **`deploy/CREDENCIALES.md`** contain a hardcoded API key — do not commit them. - **`opencode-proxy.exe~`** in root is a backup artifact; ignore. - Upstream Zen API expects `x-opencode-request`, `x-opencode-session`, `x-opencode-client`, `x-opencode-project` headers (set in `proxy.go:107`). - **Server logs request bodies to stdout** (truncated to 500 chars in `server.go:265-271`). Verbose by design. - **Health endpoint** returns hardcoded version `"1.0.0"` in `server.go:126`. Update both `main.go:12` and `server.go:126` when bumping. - **Upstream timeout:** `proxy.go:167` sets 10-minute HTTP client timeout — adjust if Zen models are slow on first call. - **Unknown models are forwarded as-is** (`models.go:140`) — useful when new models appear upstream before code is updated. - **Reasoning models** (DeepSeek, Nemotron) need `max_tokens` ≥ 500 — first tokens go to reasoning, not visible content.