mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 00:48:00 +00:00
chore(compliance): drop opencode zen free-pool from default config and docs
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)
This commit is contained in:
12
README.md
12
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}` 即可。
|
||||
|
||||
11
README_EN.md
11
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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user