zen-proxy
12errh/zen-proxy/llms-full.txt
Everything from the project README, in one plain-markdown file for LLMs. Source: https://github.com/12errh/zen-proxy a local OpenAI-compatible proxy that unlocks opencode's anonymous free tier — for any agent. anonymous free tier ◦ no accounts ◦ no keys ◦ any openai-compatible agent ◦ 100% local Zen Proxy is a zero-dependency, locally-run OpenAI-compatible proxy that unlocks opencode's Zen free tier for any coding agent — not just opencode. opencode gives you free models like mimo-v2.5-free, big-pickle, and nemotron — but only to requests…
llms.txt23 starsChanged 22 days ago
- Pipes a download into a shell
# zen-proxy — full documentation
> Everything from the project README, in one plain-markdown file for LLMs.
Source: https://github.com/12errh/zen-proxy
---
a local OpenAI-compatible proxy that unlocks opencode's anonymous free tier — for any agent.
anonymous free tier ◦ no accounts ◦ no keys ◦ any openai-compatible agent ◦ 100% local
> **Zen Proxy** is a zero-dependency, locally-run OpenAI-compatible proxy that unlocks opencode's Zen free tier **for any coding agent — not just opencode**.
>
> opencode gives you free models like `mimo-v2.5-free`, `big-pickle`, and `nemotron` — but only to requests that look like they come from opencode itself: the right `User-Agent` **and** an `x-opencode-session` header. Most agents force their own identity and get shut out with `400 MissingSessionID` ("OpenCode's free tier can only be used in OpenCode") or `429 FreeUsageLimitError`. Zen Proxy quietly speaks for them: it injects the correct `User-Agent`, mints stable per-client session IDs, forwards your real IP, and re-exposes everything as a standard `/v1/chat/completions` + `/v1/models` API.
>
> The result: whatever tool you love — Cline, Roo Code, Continue, Aider, mimo, or a plain `curl` — can now ride opencode's free models with **zero accounts, zero API keys**, and zero config beyond a `baseURL`.
**What it enables:**
- **Free OpenAI-compatible models outside opencode** — bring your own agent, keep the free tier
- **Anonymous access with no accounts or keys** (`Bearer public`), or bring your own Zen key (BYOK) for your own quota
- **Smart model fallback** — when one free model is saturated, it rolls to the next
- **Model aliases** — call them `gpt-4o` or `claude-3-5`, get routed to free models
- **Self-updating** — tracks new opencode releases (auto User-Agent) and a GitHub Action keeps the shipped free-model list current as models come and go
- **Per-IP fairness** — real client IPs are forwarded (local clients fall back to the machine's real IP)
- **A retro-zine management dashboard** — stats, one-click model tests, live config, and logs at `http://127.0.0.1:8787/`
- **One-file install** on Linux, macOS, and Windows with a single `curl`
---
## why it exists
opencode's free `-free` models (`mimo-v2.5-free`, `big-pickle`, `nemotron`, …) are only served to requests that mimic the real client. Since opencode started requiring an `x-opencode-session` header, anything without it gets `400 MissingSessionID: "OpenCode's free tier can only be used in OpenCode"` — even with a valid API key. Most coding agents — like the *mimo* CLI fork — force their own identity and get slammed with that error (or `429 FreeUsageLimitError`).
**zen-proxy** injects the correct `User-Agent` and a stable synthetic `x-opencode-session` per client, then re-exposes everything as a plain OpenAI API. No accounts, no API keys, no source patches.
> works with: **mimo cli**, **Cline**, **Roo Code**, **Continue**, **Aider**, opencode forks — anything that lets you set a `baseURL` + `apiKey`.
▸ feature list
- **OpenAI-compatible API** — `/v1/chat/completions` (stream + non-stream), `/v1/models`, `/v1/responses`
- **UA unlock** — injects `User-Agent: opencode/1.18.30` plus a stable per-client `x-opencode-session` upstream, the two things that open the free tier
- **BYOK** — ride anonymous `public` or bring your own Zen key (stable + no shared-pool throttling)
- **Smart fallback** — tries models in order on `429`/`5xx` and dead-model `4xx` (e.g. "Model is unavailable"), honors `retry-after`
- **Model aliases** — e.g. `gpt-4o → mimo-v2.5-free`, replies rewritten back
- **Per-IP fairness** — real client IPs forwarded; local clients fall back to the machine's real IP
- **Management dashboard** — glass… no, sticker-style UI at `/` for stats, model tests, config & logs
- **Zero dependencies** — one `zen-proxy.mjs`, runs on any Node ≥ 18
---
## install
Requires **Node.js ≥ 18**.
**Linux / macOS**
```bash
curl -fsSL https://raw.githubusercontent.com/12errh/zen-proxy/main/install.sh | bash
```
**Windows (PowerShell)**
```powershell
irm https://raw.githubusercontent.com/12errh/zen-proxy/main/install.ps1 | iex
```
Or just clone and run — it's a single file, no `npm install`:
```bash
git clone https://github.com/12errh/zen-proxy.git && cd zen-proxy
node zen-proxy.mjs
```
---
## usage
```bash
node zen-proxy.mjs # or the installer's `zen-proxy` launcher
# dashboard → http://127.0.0.1:8787/
```
| Thing | Value |
|---|---|
| Dashboard / admin UI | `http://127.0.0.1:8787/` |
| OpenAI base URL | `http://127.0.0.1:8787/v1` |
| API key | `public` (any value once you set `proxyKey`) |
| Example model | `mimo-v2.5-free` (auto default picks a healthy free model) |
| Health check | `http://127.0.0.1:8787/health` |
### point your agent at it
Cline / Roo / Continue / Aider / mimo — anywhere you configure an OpenAI-compatible provider:
```jsonc
{
"provider": {
"zen": {
"baseURL": "http://127.0.0.1:8787/v1",
"apiKey": "public",
"models": { "mimo-v2.5-free": {} }
}
}
}
```
Quick test:
```bash
curl -s http://127.0.0.1:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"mimo-v2.5-free","messages":[{"role":"user","content":"say hi"}]}'
```
---
## the dashboard
manage everything, no config files
- **Overview** — upstream health, uptime, request counters, live recent-request feed with status + latency
- **Models** — add/remove allowed models, pick the default, one-click **Test** per model
- **Settings** — edit host, port, upstream URL, User-Agent, proxy key, BYOK key, timeout, fallback list, aliases; saved to `zen-proxy.json` and applied instantly
- **Logs** — live terminal-style log tail
Set `proxyKey` and both the dashboard and the API require that key.
---
## configuration
Config lives in **`zen-proxy.json`** (auto-created on first run, hot-reloaded when edited — the dashboard writes it too). Env vars can override at startup.
| Key | Default | Description |
|---|---|---|
| `host` | `127.0.0.1` | Bind address (restart needed) |
| `port` | `8787` | Listen port (restart needed) |
| `upstream` | `https://opencode.ai/zen/v1` | Zen API base |
| `ua` | `opencode/1.18.30` | The `User-Agent` that unlocks the free tier (auto-updates to new opencode releases) |
| `autoUA` | `true` | Track opencode releases and update `ua` automatically (`opencode/<latest>`) |
| `uaRefreshMs` | `21600000` | How often to check for a new opencode version (ms) |
| `injectSession` | `true` | Mint an `x-opencode-session` header per client (upstream rejects requests without one) |
| `gated` (sync state) | — | Models answering `403 FreeTierError`: the free tier only accepts genuine agent requests, so our probe can't verify them. Shown as **agent-only**, kept in the list, never removed |
| `probeAuth` | `auto` | Credentials for auto-sync health probes: `auto` (your Zen key if set, else anonymous), `anonymous` (always the public free tier), or `key` (always your key, so probes reflect your own quota) |
| `defaultModel` | `""` | Empty = auto: pick the first *healthy* free model (no more hardcoded/vanished defaults) |
| `fallbackModels` | `["space-bunny-free","mimo-v2.6-flash-free", …]` | Tried in order on `429`/`5xx` and dead-model `4xx`; auto-sync prunes vanished models and adds new ones |
| `responsesModels` | `["gpt-5*","gpt-6*","grok-*","muse-spark-*"]` | Models served on `/v1/responses` (patterns may end in `*`); the proxy translates to/from chat completions for you |
| `rateLimitMax` | `0` (off) | Max chat requests per client per window — `0` disables the limiter |
| `rateLimitWindowMs` | `60000` | Rate-limit window |
| `modelAliases` | `{}` | e.g. `{"gpt-4o":"mimo-v2.5-free"}` — reply model rewritten back |
| `proxyKey` | `""` | If set, clients must send it as `Bearer`; locks the dashboard too |
| `defaultZenKey` | `""` | Your own Zen key (BYOK) instead of anonymous `public` |
| `trustForwarded` | `false` | Trust `x-forwarded-for`/`x-real-ip` from a reverse proxy |
| `timeoutMs` | `120000` | Upstream timeout for non-streaming |
| `cacheMs` | `30000` | `/v1/models` cache TTL |
Env vars: `HOST`, `PORT`, `ZEN_URL`, `ZEN_UA`, `INJECT_SESSION` (`0` to disable), `AUTO_UA` (`0` to disable), `UA_REFRESH_MS`, `PROBE_AUTH`, `RESPONSES_MODELS` (JSON), `RATE_LIMIT_MAX`, `RATE_LIMIT_WINDOW_MS`, `DEFAULT_MODEL`, `FALLBACK_MODELS` (JSON), `MODEL_ALIASES` (JSON), `PROXY_KEY`, `ZEN_KEY`, `TRUST_FORWARDED=1`, `TIMEOUT_MS`, `CACHE_MS`, `ZEN_PROXY_CONFIG` (custom config path).
### bring your own key
Anonymous `public` access rides opencode's shared free pool (per-IP quota, sometimes saturated). For stable, reliable use set `defaultZenKey` to your own free Zen key — or send it per request as `x-zen-key` / a non-`public` bearer token.
---
## api surface
| Method | Path | Description |
|---|---|---|
| `POST` | `/v1/chat/completions` | Chat completions (stream + non-stream) |
| `POST` | `/v1/responses` | Responses passthrough |
| `GET` | `/v1/models` | Allowed models (cached) |
| `GET` | `/health` | Health check |
| `GET` | `/` | Dashboard |
| `GET` | `/api/status` | Stats + upstream health |
| `GET/PUT` | `/api/config` | Read / update config |
| `POST` | `/api/test` | Test a model (`{"model":"…"}`) |
| `GET` | `/api/logs` | Log tail |
| `POST` | `/api/reset` | Reset request stats |
| `GET` | `/assets/*` | Static assets (logo, favicon) |
---
## systemd (linux)
Install as a service via `install.sh` (it prompts you), or manually:
```bash
sudo systemctl enable --now zen-proxy
```
---
## the free-tier gate (read this if a model says "agent-only")
opencode serves its free models **only to genuine opencode traffic**. Verified by capturing and replaying a real opencode request, the upstream accepts a request only when it carries:
- `stream: true`, **and**
- a realistic set of **tool definitions** in the request body
A bare `curl`, a health probe, or any non-agent client gets:
```
403 FreeTierError: OpenCode's free tier can only be used from within OpenCode
```
**This is not the model being broken.** A model in this state is shown as **agent-only**, is kept in your list, and is never pruned. Because the built-in probe can't verify it, the proxy learns real health from your actual client traffic — a model that serves your agent shows **`ok (live traffic)`**.
| badge | meaning |
|---|---|
| `ok (live traffic)` | a real request through the proxy succeeded — the strongest signal |
| `ok` | the health probe got a clean 200 |
| `agent-only` | free-tier gated: works for real agents, not verifiable by the probe |
| `flaky` | temporary trouble (timeout, 5xx, rate-limit) |
| `dead` | upstream says it's gone (`not supported`, 404) — the only state that removes it |
### Does my agent need anything?
If your agent **streams and sends tool definitions** (Claude Code, Cline, Roo Code, Continue, Aider, mimo, opencode itself — all of them do), it works. If it makes simple one-shot calls without tools, those specific calls get gated and the proxy transparently falls back to another free model.
You don't need to change anything. When a fallback happens the response tells you:
```jsonc
{ "model": "mimo-v2.6-flash-free", "zen_served_by": "space-bunny-free", /* … */ }
```
plus response headers `x-zen-served-by` and `x-zen-fallback: true`.
> The proxy deliberately does **not** fake opencode's tool schemas to get past this check. That gate is an access control, and the honest behaviour is to tell you which models are gated and let your real agent traffic use them.
---
## caveats
- This rides opencode's **anonymous free tier**: per-IP request/daily quotas and a shared pool that's sometimes saturated. Don't rotate/abuse IPs or run heavy workloads anonymously.
- opencode keeps **tightening the anonymous free tier** — most `-free` models now answer `403 FreeTierError: "OpenCode's free tier can only be used from within OpenCode"`, and that hits the real opencode client too. The proxy detects it and rolls to the next working model, so requests keep succeeding, but the pool is much smaller than it used to be. A Zen API key (BYOK) is the reliable path.
- The `-free` models are "as-is" free tiers — expect rate limits and occasional provider errors.
- For anything serious, **BYOK**.
---
zen-proxy · anonymous free tier · no accounts · no keys · 100% local
go make some noise.
**MIT License**
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

