From d75d8411c0bdc88b49e4b32fb12faf15c74bdf9d Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Mon, 17 Aug 2026 08:53:42 +0800 Subject: [PATCH] chore(compliance): drop opencode zen free-pool from default config and docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit opencode.ai/zen is a free-quota pool for the official opencode app, keyed by User-Agent fingerprint. Auto-provisioning it in the default config effectively impersonated the official client to bypass FreeUsageLimitError — a ToS risk. - default config (Go + GUI embed) now ships NO sources; users add their own - README/README_EN: remove opencode adapter from built-in list and the zen free-pool paragraph - adapter script and its unit tests stay (explicit opt-in only) --- README.md | 12 ++---------- README_EN.md | 11 ++--------- internal/config/config.go | 16 ++++------------ internal/config/config_test.go | 10 ++-------- 4 files changed, 10 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 668b11a..3a3d2e5 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,7 @@ GOMODCACHE=... GOPROXY=off go build -tags luajit -o llmsproxy ./cmd/llmsproxy > **配置不入库**:仓库不携带任何 config 文件(配置文件含密钥)。二进制首次运行时 > 会在 `-config` 指定的位置生成默认配置:随机 admin key(打印在启动日志中)、 -> 仅绑定 `127.0.0.1:8080`、内置一个免 key 的 opencode zen 源可直接对话。 -> 首次登录后请在 WebUI「密钥」页更换管理员密钥。 +> 仅绑定 `127.0.0.1:8080`。首次登录后请在 WebUI「密钥」页更换管理员密钥。 ```bash # 无 key -> 401 @@ -281,19 +280,12 @@ return { ### 内置适配器 -`openai` `deepseek` `anthropic` `gemini` `github` `groq` `mistral` `ollama` `kimicode` -`opencode`。 +`openai` `deepseek` `anthropic` `gemini` `github` `groq` `mistral` `ollama` `kimicode`。 `anthropic`/`gemini`/`ollama` 适配器内置多模态转换(`image_url` → 各自上游格式);若 源启用 `disable_thinking`,`deepseek` 适配器会把 `extra_body.thinking.type` 置为 `disabled`。 -**opencode** 适配器面向 opencode.ai zen 免费池(`https://opencode.ai/zen/v1`): -zen 按 User-Agent 指纹识别官方客户端并分流——非官方 UA 的请求(curl、Go 默认 UA) -会被丢进匿名池触发 `FreeUsageLimitError`。该适配器固定发送 opencode 客户端 UA, -配合 `api_key: "public"`(官方无 key 客户端实际发送 `Bearer public`)即可走免费池, -零成本接入 `deepseek-v4-flash-free` 等免费模型。 - **kimicode** 是展示 `build_headers` 的样例:云端校验调用方 app,需要按 `meta.app_secret` 对时间戳+URL+请求体哈希做 HMAC 签名并附 `X-App-Sign` 等头。 配好 `sources[].meta.{app_id, app_secret, app_agent}` 即可。 diff --git a/README_EN.md b/README_EN.md index 751bdb6..c8f77f4 100644 --- a/README_EN.md +++ b/README_EN.md @@ -77,7 +77,7 @@ GOMODCACHE=... GOPROXY=off go build -tags luajit -o llmsproxy ./cmd/llmsproxy > **No config is shipped in the repo** (config files carry real keys). On > first run the binary generates a default config at the `-config` path: a > random admin key (printed to the startup log), loopback-only `127.0.0.1:8080`, -> and a keyless opencode zen source ready to chat. Rotate the admin key in the +> and a keyless source ready to chat. Rotate the admin key in the > WebUI after first login. ```bash @@ -289,19 +289,12 @@ Shared helpers: `hmac_sha256_hex(key, data)`, `sha256_hex(data)`, ### Built-in adapters `openai` `deepseek` `anthropic` `gemini` `github` `groq` `mistral` `ollama` -`kimicode` `opencode`. +`kimicode`. `anthropic`/`gemini`/`ollama` include multimodal conversion (`image_url` → their native format); with `disable_thinking` the `deepseek` adapter sets `extra_body.thinking.type` to `disabled`. -**opencode** targets the opencode.ai zen free pool -(`https://opencode.ai/zen/v1`): zen fingerprints clients by User-Agent and -routes non-official UAs (curl, Go's default) into an anonymous pool that -hits `FreeUsageLimitError`. The adapter always sends the opencode client UA; -combined with `api_key: "public"` (the keyless official client actually sends -`Bearer public`) it gets the free pool, e.g. `deepseek-v4-flash-free`. - **kimicode** demonstrates `build_headers`: the cloud validates the calling app, so you HMAC-sign timestamp+URL+body with `meta.app_secret` and add `X-App-Sign`-style headers. Configure `sources[].meta.{app_id, app_secret, diff --git a/internal/config/config.go b/internal/config/config.go index 963515b..8c57e18 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -103,9 +103,10 @@ func EnsureDefault(path string) (bool, error) { } // writeDefaultConfig writes a minimal, safe-by-default config: loopback-only -// listen, a fresh random admin key, and a no-key zen source that works out of -// the box. adapter_dir / runtime_file live next to the config file so the -// binary works regardless of the working directory it is started from. +// listen and a fresh random admin key. No upstream sources are preconfigured +// (the repo ships none — add them via WebUI or config.yaml). adapter_dir / +// runtime_file live next to the config file so the binary works regardless of +// the working directory it is started from. func writeDefaultConfig(path string) error { key, err := NewGatewayKey() if err != nil { @@ -122,15 +123,6 @@ func writeDefaultConfig(path string) error { DefaultModel: "AUTO", AdapterDir: filepath.Join(abs, "adapters"), RuntimeFile: filepath.Join(abs, "runtime.json"), - Sources: []Source{ - { - Name: "zen", - BaseURL: "https://opencode.ai/zen/v1", - APIKey: "public", // zen 免费池:官方无 key 客户端实际发送 Bearer public - Adapter: "opencode", - Models: []Model{{ID: "deepseek-v4-flash-free", Priority: 100, Kind: "chat"}}, - }, - }, } out, err := yaml.Marshal(&cfg) if err != nil { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 5b5b208..32047ea 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -91,14 +91,8 @@ func TestEnsureDefaultGeneratesOnMissingFile(t *testing.T) { if cfg.Listen != "127.0.0.1:8080" { t.Fatalf("listen = %q, want loopback-only", cfg.Listen) } - if len(cfg.Sources) != 1 || cfg.Sources[0].Name != "zen" || cfg.Sources[0].Adapter != "opencode" { - t.Fatalf("default sources = %+v", cfg.Sources) - } - if cfg.Sources[0].APIKey != "public" { - t.Fatalf("zen api_key = %q", cfg.Sources[0].APIKey) - } - if cfg.Sources[0].Models[0].ID != "deepseek-v4-flash-free" { - t.Fatalf("default model = %+v", cfg.Sources[0].Models) + if len(cfg.Sources) != 0 { + t.Fatalf("default config must ship no sources (user adds them), got %+v", cfg.Sources) } // adapter_dir / runtime_file resolve next to the config file if !strings.HasPrefix(cfg.AdapterDir, dir) || !strings.HasPrefix(cfg.RuntimeFile, dir) {