zen-free-proxy
OpenCode Zen の無料モデルだけを中継する、OpenAI 互換のリバースプロキシ。
- クライアントの API キーは何でもいい(
sk-Whateverでも空でも通る)。上流用の実キーはプロキシ側だけが保持します。 - モデルの一覧はハードコードしていません。 無料かどうか・どの上流ルートで配信されるか・窓サイズ是多少かを、起動時に取得します。Zen が出した新しい無料モデルは、コードを書き換えずに現れます。
/v1/modelsに出るのは無料モデルのみ。有料モデルを叩くと 404 + 理由付き。/v1/modelsには 実際の context window / 出力上限 も入ります(Zen 自身は id しか返さないため)。pi-web-ui の「補参数 / Enrich params」のように 200K を仮定するクライアントが、1M モデルで 12% しか使っていないのに圧縮してしまう事故を防げます。- Chat Completions / Responses / System One の 3 ルートを吸収し、素の OpenAI SDK・LiteLLM・Ollama・Continue などにそのまま差し込めます。
- ストリーミング(SSE)はバイト単位で中継。バッファしません。
インストール
PyPI から:
# インストールして起動
uv tool install zen-free-proxy
zen-free-proxy
# uv がなければ pip でも同じ
py -m pip install zen-free-proxy
zen-free-proxy
インストールせずに動かす:
uvx zen-free-proxy
リポジトリから開発するときは uv sync → uv run zen-free-proxy です。
設定ファイル(任意)
環境変数でも .env でも指定します(prefix は ZEN_PROXY_、ZEN_API_KEY のみ例外)。
.env は起動時のカレントディレクトリから読み込まれます:
# .env
ZEN_API_KEY=sk-...
ZEN_PROXY_PORT=8787
ZEN_PROXY_ALLOWED_CLIENT_KEYS=team-a-secret,team-b-secret
クイックスタート
zen-free-proxy
既定で http://0.0.0.0:8787 で待ち受けます。
curl http://localhost:8787/v1/chat/completions `
-H "Content-Type: application/json" `
-H "Authorization: Bearer なんでもいい" `
-d '{"model":"space-bunny-free","messages":[{"role":"user","content":"hello"}]}'
OpenAI SDK から:
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8787/v1", api_key="whatever")
print([m.id for m in client.models.list().data])
reply = client.chat.completions.create(
model="space-bunny-free",
messages=[{"role": "user", "content": "hello"}],
)
print(reply.choices[0].message.content)
IMPORTANT: 無料モデルの実際利用可能範囲
2026-10 時点の実測: 無料モデルは全部、キー必須です。 匿名(Bearer public)では
space-bunny-free を含めすべて 403 FreeTierError になります。
| モデル | 実キー無し(匿名) | 実キーあり |
|---|---|---|
space-bunny-free |
❌ 403 FreeTierError |
✅ 動作 |
jev-1.13-free(/v1/systemone) |
❌ 403 FreeTierError |
✅ 動作 |
big-pickle, mimo-*, ling-*, nemotron-*, longcat-*, fledge-*, muse-spark-*-contributor-free |
❌ 403 FreeTierError |
✅ 動作 |
GET /v1/models だけは匿名でも通るので、モデル一覧の自動更新は匿名でも動きます。
# .env に書く(.env.example をコピー)
ZEN_API_KEY=sk-...
キーは https://opencode.ai/auth で取得できます。設定すると上流には
Authorization: Bearer <あなたのキー> が送られ、クライアントのキーは一切
上流へ漏れません。
実キー無しで対象モデルを叩くと、プロキシは素の 403 ではなく
free_tier_restricted というタイプで「ZEN_API_KEY を設定せよ」と明示した
エラーを返します。
「OpenCode 外で使うな」エラーとヘッダー
Error from provider (Console): OpenCode's free tier can only be used from within OpenCode
という 403 が返る件について。opencode.exe(bun コンパイル)を解析したところ、
Zen 宛てに送られるアプリ固有ヘッダーは次の 4 つだけです(x-opencode-* はこれが全部):
x-opencode-client: cli
x-opencode-session: ses_…
x-opencode-request: req_…
User-Agent: opencode/<バージョン>
プロキシは既定でこれらをそのまま再生します(ZEN_PROXY_ZEN_CLIENT_HEADERS=0 で無効化)。
ただし実測した範囲では、足しても 403 は消えません。UA のみ / +client /
+session +request / +project、cli desktop web の 6 通りを試しましたが全て同じ 403。
一方、偽キーを付けると 401 Invalid API key、キー無しだと有料モデルで
401 Missing API key になります。つまり判定しているのはヘッダーではなく API キーで、
解決は ZEN_API_KEY の設定です。ヘッダーは Zen がさらに検査を厳しくした時のための保険です。
エンドポイント
| メソッド | パス | 説明 |
|---|---|---|
GET |
/ |
サービス情報と公開モデル一覧 |
GET |
/healthz |
起動確認 + 上流到達性(503 を返せば劣化) |
GET |
/v1/models |
無料モデルのみ(OpenAI の形式 + 窓サイズ。下節) |
GET |
/v1/models/{id} |
個別取得。有料/未知名は 404 + 理由 |
POST |
/v1/chat/completions |
メイン。ストリーミング可、tool calling 可 |
POST |
/v1/responses |
Responses API ネイティブの無料モデル用 |
POST |
/v1/systemone |
jev-* 用(OpenAI 形式ではない) |
モデル名には opencode/ などのプロバイダ接頭辞が付けてもそのまま動きます
(opencode/space-bunny-free → space-bunny-free)。
カタログの入手元(ハードコードなし)
このリポジトリにはモデル id が 1 つも書かれていません。Zen が新しい無料モデルを
公開したあとにコードを書き換えなくて済むよう、起動時と ZEN_PROXY_CATALOG_TTL
秒ごとに次のソースを読みます。
-
GET https://opencode.ai/zen/v1/models— Zen が現在扱っているモデルの一覧。 Zen が外したモデルはそのまま/v1/modelsから消え、Zen が出したモデルは自動で 追加されます。「このモデルを使ってよいか」の根拠はこの一覧です。 -
models.dev の
opencodeエントリ(https://models.dev/api.json) — 本家 OpenCode クライアントが使っているカタログで、「無料かどうか」「どのルートで 配信されるか」「窓はいく大きいか」をこの 1 ファイルで答えられます。知りたいこと 見る場所 opencode のソースでの根拠 無料かどうか cost.input == 0packages/core/src/provider/provider.tsのcustom.opencodeが「未認証には有料モデルを隠す」判定に使う値どのルート provider.npmが@ai-sdk/openaiなら Responses API、省がなければ(プロバイダ既定の@ai-sdk/openai-compatible)chat同じファイルが api.npmから SDK を選ぶ部分窓サイズ limit.context / input / output、modalities、reasoning同じファイルが models.dev を読む部分 無料かどうかは名前ではなく値段で判定します。無料なのに有料モデルらしい名前の
big-pickleも、-freeが付いていて数百ドルかかるモデルも、名前の表に頼らず 正しい答えになります。 -
opencode のリポジトリにコミットされているスナップショット(ミラー) —
.github/workflows/models-snapshot.ymlが毎日更新して push するpackages/core/src/models-dev/snapshot.txt。上の models.dev が取れなかった ときのフォールバックです(ZEN_PROXY_MODELS_DEV_MIRROR_URL)。中身はまったく 同じファイルで、実測ではこれ 1 つだけで 14 件の無料モデルと窓サイズまで揃います。
どちらのソースも取れなかったときは、直前のカタログをそのまま使い続けます
(15 分あけて再試行するので、推論リクエストのたびに取得に走ることはありません)。
どのソースも把握していないモデルだけは、名前ベースの判定(-free など)に
フォールバックします。
ルート自動振り分け
Zen は無料モデルごとに上流エンドポイントが違います。上のカタログが教える 「どのルートで配信されるか」に従って自動で振り分けます。
- Chat Completions ネイティブ → そのまま中継
- Responses ネイティブ →
chat/completionsに叩かれても要求を Responses 形式へ 変換して送り、応答(JSON / SSE とも)を Chat Completions 形式へ戻します。system→instructions、tool_calls→function_call、 SSE のresponse.output_text.delta→chat.completion.chunkの delta に対応。 - System One ネイティブ(
jev-*)→POST /v1/systemone向け。models.dev に載って いないので、名前でしか判別できません。
ルートの当てが外れても止まりません。Zen が
Model X is not supported for format openai と返した場合、もう一方の OpenAI 互換
ルートで 1 回だけ試して、成功したルートをカタログに記録します。Zen が 500 で
落ちる場合にそれを試すのは、名前からの推測で得たルートだけです。ソースが
教えてくれたルートの 500 は本物の失敗なので、そのまま返します。
つまり「モデルがルートを移った」「ソースの更新が遅れた」「名前からの推定が外れた」 場合でも、最初の 1 リクエストが 2 本になるだけで、コードを直す必要はありません。
/v1/models のメタデータ
context window も上の models.dev と同じ取得から来ます。ハードコード表は持たないので、 Zen が無料モデルを新しく出した場合も窓サイズが自動で入ります。
クライアントごとに綴りが違うので、まとめて出します:
{
"id": "space-bunny-free",
"object": "model",
"owned_by": "opencode",
"name": "Space Bunny Free",
"context_window": 1048576, // pi-web-ui / vLLM 系
"context_length": 1048576, // OpenRouter 系
"max_context_length": 1048576,
"max_input_tokens": 524288,
"max_tokens": 524288, // OpenAI 系
"max_output_tokens": 524288,
"modalities": ["text", "image", "video"],
"reasoning": true,
"limit": { "context": 1048576, "input": 524288, "output": 524288 } // models.dev 系
}
取得に失敗した場合や models.dev に無いモデル(jev-* など)は、窓サイズ無しで
そのまま配信されます。ZEN_PROXY_MODELS_DEV_URL と
ZEN_PROXY_MODELS_DEV_MIRROR_URL の両方を空にすると、その取得自体を無効化できます
(その場合、Zen の一覧と名前の判定だけでカタログを作ります)。
pi-web-ui の「补参数(Enrich params)」は外部カタログ(OpenRouter → models.dev)を
引く仕組みで、Zen の無料モデルは どちらのカタログにも一致しません。実際に効くのは
/v1/models で、pi-web-ui 側の server/model-admin.ts の parseOpenAiModel が
そのレスポンスのフィールドを読みます。ただし pi-web-ui は 手動で入れた値を優先し、
Enrich は空欄しか埋めないため、200K を入れてある行は一度消してから取り直してください。
設定
すべて環境変数、または .env で指定します(_prefix は ZEN_PROXY_、ただし
ZEN_API_KEY のみ例外)。
| 変数 | 既定 | 説明 |
|---|---|---|
ZEN_API_KEY |
(空) | 上流に送る実キー。空なら匿名(Bearer public) |
ZEN_PROXY_HOST |
0.0.0.0 |
バインド先 |
ZEN_PROXY_PORT |
8787 |
ポート |
ZEN_PROXY_ZEN_BASE_URL |
https://opencode.ai/zen/v1 |
上流 |
ZEN_PROXY_REQUEST_TIMEOUT |
600 |
上流タイムアウト(秒) |
ZEN_PROXY_CONNECT_TIMEOUT |
15 |
接続タイムアウト(秒) |
ZEN_PROXY_MAX_RETRIES |
2 |
429/5xx の再試行回数(Retry-After 考慮) |
ZEN_PROXY_CATALOG_TTL |
900 |
カタログ(モデル一覧・ルート・窓サイズ)の再取得間隔。0 で「起動時に 1 回だけ取得」 |
ZEN_PROXY_MODELS_DEV_URL |
https://models.dev/api.json |
カタログ(判定・ルート・窓サイズ)の取得元。空で無効化 |
ZEN_PROXY_MODELS_DEV_MIRROR_URL |
opencode の models-dev/snapshot.txt |
上の取得元が使えないときのミラー。空で無効化 |
ZEN_PROXY_ZEN_CLIENT_HEADERS |
1 |
本番クライアントの x-opencode-* と User-Agent を上流に再生する |
ZEN_PROXY_ALLOWED_CLIENT_KEYS |
(空) | 空 = 誰でも通過。 カンマ区切りで指定すると、そのキーだけ受理 |
ZEN_PROXY_LOG_LEVEL |
INFO |
ログレベル |
認証について
既定は完全なオープンです。クライアントの Authorization ヘッダは
読みも検証もせず、上流へ転送しません(「API キー何でも通る」要件)。
共有先将誰かのキーを渡す必要もありません。
後から認証を強めたいときは:
$env:ZEN_PROXY_ALLOWED_CLIENT_KEYS = "team-a-secret,team-b-secret"
とすると、指定したキーのどれかを持つリクエストだけが通ります。
⚠️ バインドは既定で
0.0.0.0かつ無認証です。LAN や公開先将想定する場合はZEN_PROXY_HOST=127.0.0.1にするか、ZEN_PROXY_ALLOWED_CLIENT_KEYSを 設定してください。
開発
uv run pytest -q # 115 tests
uv run ruff check .
uv run ruff format .
テストは全て httpx.MockTransport で上游をモックしています。実ネットワークに
アクセスしません。
リリース
PyPI へは Trusted Publishing で公開しています(API トークンを持っていません)。
v* タグを push すると、CI を通って自動でアップロードされます:
uv version patch # 0.1.0 -> 0.1.1(pyproject を更新)
git commit -am "release 0.1.1" && git push
git tag v0.1.1 && git push --tags
初回の公開時だけ、PyPI 側に一度だけ発行者の登録が必要です:
https://pypi.org/manage/project/zen-free-proxy/settings/publishing で
「GitHub のリポジトリ nennneko5787/opencode-zen-server、ワークフロー
publish.yml、 Environments は空欄」を登録します。
構成:
src/zen_free_proxy/
config.py 環境変数 → Settings
catalog.py 無料モデルのカタログと窓の型(ModelMeta / CatalogSource)。id は持たない
models_dev.py models.dev から無料判定・ルート・窓サイズを取得・解析
upstream.py Zen への HTTP クライアント(再試行・カタログ更新・クライアントヘッダー)
sse.py SSE のフレーミング
bridge.py Chat Completions ⇄ Responses API の相互変換
app.py FastAPI のルート定義
免責
- Zen の無料モデルは「期間限定」的ライセンスで、勝手に入れ替わったり消えたりします。 あれこれリストを直す必要はありません。カタログは起動時に取得します (下記「カタログの入手元」)。新しいモデルが出ても、古いモデルが消えても、 そのまま追従します。
- 無料ティールの利用制限(レートリミット等)は Zen 側にあり、このプロキシでは
回避しません。429 は
Retry-Afterを付けてそのままクライアントに返します。 無料モデルの公開範囲も Zen 側の判断で、匿名利用は 403 になります(上記のとおり)。 muse-spark-*-contributor-freeと一部無料モデルは、プライバシー上の理由 (学習利用への同意など)でプロンプトが保存されます。機密情報を扱う場合はspace-bunny-free/longcat-2.5-preview-free(ゼロレテンション)を選んでください。
Metadata
Release files for zen-free-proxy 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zen_free_proxy-0.1.0.tar.gz | 28.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zen_free_proxy-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 61.8 kB
Release files / zen_free_proxy-0.1.0.tar.gz
| Download URL | zen_free_proxy-0.1.0.tar.gz |
|---|---|
| Size | 28.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f1e1bb9b9ed5b076af7b1a3e6a0b819192c6d9044e5895da2c4a678f8d9f0593
|
|
BLAKE2b-256 checksum How to use checksums |
f13b871bf07db2d0aa84e37f84644417056c41014ed82d43ed0a4b4ebc48740d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / zen_free_proxy-0.1.0-py3-none-any.whl
| Download URL | zen_free_proxy-0.1.0-py3-none-any.whl |
|---|---|
| Size | 33.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fab9db5f1170c89672e9929aa25e8526d77f7a042644d7f31c4970d15a97c61a
|
|
BLAKE2b-256 checksum How to use checksums |
78dc808acbaa4aa1fa4e163a5aca7c4590d64b7b6de70646ddd161e6480a8c26
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|