feat: opencode zen adapter + first-run config generation, fix stats/stream bugs

- adapters/opencode.lua: opencode.ai zen free pool adapter — sends the
  opencode client User-Agent (zen fingerprints clients by UA; non-official
  clients hit FreeUsageLimitError); pairs with api_key: public
- config: no config file ships in the repo; first run generates a default
  config at the -config path with a random admin key, loopback listen and a
  keyless zen source (config.EnsureDefault); remove config.example.yaml
- lua: seed bundled adapters from the embedded FS instead of a hardcoded
  name list
- ui: widen model kind select (chat was clipped to 'cha')
- phase 5 bugfixes: stats ms/s bucket mixing, cleanScopes nil, ctx.Err
  guards, direct-path ModelAvailable, empty stream body failure,
  bestImageModel rewrite, transform failure recording, Core.mu, timer,
  effective model for tool-calls
This commit is contained in:
JianFeeeee
2026-08-13 12:25:07 +08:00
parent d06210204b
commit 2bc1d0e67a
22 changed files with 910 additions and 148 deletions

View File

@ -79,26 +79,30 @@ Groq、Mistral、Ollama、KimiCode…通过 **Lua 适配器** 做协议转换
## 快速开始
```bash
cp config.example.yaml config.yaml # 编辑你的源与 key
GOMODCACHE=... GOPROXY=off go build -tags luajit -o llmsproxy ./cmd/llmsproxy
./llmsproxy -config config.yaml
./llmsproxy -config config.yaml # 首次运行自动生成默认配置并打印随机 admin key
```
> 依赖 [golua](https://github.com/aarzilli/golua)LuaJIT 绑定)。**必须**带 `-tags luajit`
> 构建,否则默认走内置超集 gopher-lua 路径(行为略有差异)。
> **配置不入库**:仓库不携带任何 config 文件(配置文件含密钥)。二进制首次运行时
> 会在 `-config` 指定的位置生成默认配置:随机 admin key打印在启动日志中
> 仅绑定 `127.0.0.1:8080`、内置一个免 key 的 opencode zen 源可直接对话。
> 首次登录后请在 WebUI「密钥」页更换管理员密钥。
```bash
# 无 key -> 401
curl http://127.0.0.1:8080/v1/models
# 单次
curl -H "Authorization: Bearer sk-gw-local-0001" \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}]}' \
# 单次$KEY 换成首次启动日志打印的 admin key
curl -H "Authorization: Bearer $KEY" \
-d '{"model":"AUTO","messages":[{"role":"user","content":"hi"}]}' \
http://127.0.0.1:8080/v1/chat/completions
# 流式
curl -N -H "Authorization: Bearer sk-gw-local-0001" \
-d '{"model":"deepseek-v4-flash","stream":true,"messages":[{"role":"user","content":"hi"}]}' \
curl -N -H "Authorization: Bearer $KEY" \
-d '{"model":"AUTO","stream":true,"messages":[{"role":"user","content":"hi"}]}' \
http://127.0.0.1:8080/v1/chat/completions
```
@ -106,11 +110,11 @@ curl -N -H "Authorization: Bearer sk-gw-local-0001" \
## 配置
[`config.example.yaml`](config.example.yaml)核心字段
首次运行在 `-config` 路径自动生成默认配置默认 `./config.yaml`核心字段
```yaml
listen: 127.0.0.1:8080 # 网关监听地址(建议绑内网/回环)
gateway_keys: [sk-gw-0001] # 初始 admin 密钥种子,仅首启写入运行时存储用
gateway_keys: [sk-gw-<随机>] # 初始 admin 密钥(每次安装随机生成,仅首启写入运行时存储用
default_model: AUTO # model 无法路由时自动按优先级链选源
adapter_dir: adapters # Lua 适配目录;目录不存在时首启创建并 seed 内置,存在则只读
runtime_file: runtime.json # WebUI 编辑的源/密钥/AUTO 链持久化到此文件
@ -137,6 +141,12 @@ sources:
timeout: 120s # 请求超时,默认 120s
```
`config.yaml``runtime.json``master.key``adapters/` 均不提交版本库
`.gitignore`请随备份带走——`master.key` 丢失后运行时密文无法解密
运行文件生成在 `-config` 所在目录`adapter_dir` / `runtime_file` 自动指向
配置文件旁边的 `adapters/` `runtime.json`从任意工作目录启动都可用
运行时文件`runtime_file`中的敏感字段自动加密
- 加密算法 AES-256-GCM格式 `enc:v1:<base64>`
@ -153,7 +163,7 @@ sources:
- `gateway_keys` 配置只是**初始 admin 密钥种子**首次启动迁移为运行时 admin
key之后不再参与鉴权管理
- **重要首次启动后请在 WebUI密钥页更换管理员密钥**——初始密钥明文写在
`config.yaml` 继续使用存在被盗风险用新密钥登录后删除初始密钥
`config.yaml` 每次安装随机生成继续使用存在被盗风险用新密钥登录后删除初始密钥
- WebUI **密钥页**可创建/删除密钥每个密钥可指定 `admin`管理全部
`user`仅看自己的 key角色并配置**模型范围**模型 + + token 配额 +
重置周期)。
@ -270,12 +280,19 @@ return {
### 内置适配器
`openai` `deepseek` `anthropic` `gemini` `github` `groq` `mistral` `ollama` `kimicode`
`openai` `deepseek` `anthropic` `gemini` `github` `groq` `mistral` `ollama` `kimicode`
`opencode`
`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}` 即可。