# WorkBuddy2API-Hub — 国际版、国内版多账号网关中枢
> [!IMPORTANT]
> **项目进入低频维护(2026-10-23) / Low-frequency maintenance**
>
> 大幅降低更新频率与 Issue / PR 处理速度。请先阅读风险说明,再决定是否继续使用。
>
> Updates and responses to issues / pull requests will be significantly reduced. Read the risk notice below before continuing.
> [!WARNING]
> **WorkBuddy 上游正在收紧对非官方客户端的识别与拦截。**
>
> - **封禁**:2026-10-23 下午起,非官方客户端的识别与拦截明显收紧。
> - **维护者**:近期工作压力增大,无法保证及时更新与响应。
> - **可用性**:上游可能随时进一步收紧,导致本项目不可用。
> - **账号风险**:使用非官方客户端造成的风控、限流或封禁,由使用者自行承担。
>
> **Upstream enforcement** tightened detection and blocking of unofficial clients from the afternoon of 2026-10-23. The maintainer is under increased workload and cannot guarantee timely updates or replies. The upstream may tighten further at any time; rate limits, account restrictions or bans caused by using an unofficial client are the user's responsibility.
本项目仅用于协议学习、研究与个人自用。请优先使用官方客户端,不要使用主账号,不要共享账号,也不要用于商业转售。
This project is for protocol research and personal use only. Prefer the official client, do not use your primary account, and do not resell or share access.
把腾讯 **[www.workbuddy.ai](https://www.workbuddy.ai)**(国际版)与 **[codebuddy.cn](https://www.codebuddy.cn)**(国内版)的原生服务封装成标准 OpenAI 兼容接口(Chat Completions / Responses),并提供默认关闭的实验性 Anthropic Messages 适配;同时补齐多账号调度与运维能力:
- **开箱即用**:绿色包自带精简 Python(Windows),双击脚本即启;
- **双区域独立路由**:国际版 / 国内版各自配置与调度,看板一键切换,状态落盘;
- **协议与风险**:默认支持 Chat Completions、Responses API(Codex);Anthropic Messages(Claude Code / Anthropic SDK)经协议转换且缓存兼容性尚未验证,默认禁用,仅可显式选择启用;
- **模型目录对齐官方桌面端**:剔除补全通道与专线变体,能力与规格按桌面端宣告,上游新模型无需发版即进 `/v1/models`;
- **设备指纹隔离 (`derive_id`)**:以账号 UID 稳定派生机器码与会话标识,同一账号不漂移、不同账号互不关联;
- **账号接入**:看板点链接走浏览器 OAuth;Windows 上还能直接从运行中的桌面客户端导入(加密 token 就地解密),macOS / Linux 可手动粘贴解码密钥后导入;
- **每日自动化**:国内版签到 / 成长与积分任务 / 猫猫旅行;国际版每日活跃打卡(真跑完网页会话);后台调度器按 09:00 & 21:00、22:00、01:00 排程;
- **限额护栏**:保留积分 · 每日 Token · 每日积分 · 按模型 Token 四条账号级护栏,单账号独立计数,本地 0 点解封(默认全部关闭);
- **OpenRouter 价估算**:把 token 折算成等价花费(人民币 / 美元可切),逐条请求可悬停查看定价来源;
- **多 Key 与出口绑定**:每个 Key 可固定走国际版或国内版、可限定模型,用量按 Key 归属;
- **Web 看板**:指标卡片、模型性能与用量大表、请求流水、账号与任务管理一屏可查。
> ⚡ **Vibe Coding 产物**:本项目为 100% Vibe Coding 协同产物——人类开发者提出架构与业务意图,AI 助手端到端完成逆向分析、链路调度、WAF 指纹脱敏与界面编写。
> 📌 **提交代码贡献前**:请先查看并**严格遵循**贡献手册 [`CONTRIBUTING.md`](CONTRIBUTING.md)——提交格式、测试门禁与端到端冒烟要求都在那里(AI / 智能体请特别注意其中的硬性规则节)。
---
## 一、快速启动
### 1. 本机运行
**Windows** 双击 **`start-wb-proxy.bat`**:默认静默启动(`pythonw` 后台运行 + 独立日志窗口,关掉日志窗口不会停服务,用面板电源按钮或 `restart-wb-proxy.bat` 停止);想用「前台控制台 + 关窗即停」,双击 **`start-wb-proxy-visible.bat`**。**macOS** 双击 **`start-wb-proxy.command`**(首次被 Gatekeeper 拦截时右键 →「打开」确认一次),或在终端执行 `./start-wb-proxy.sh [端口]`(默认 8788)。
启动后:**API 地址** `http://127.0.0.1:8788/v1`,**Web 看板** `http://127.0.0.1:8788/`。首次启动还没有账号,打开看板点「+ 添加账号 (OAuth)」完成授权即自动入库。
> 绿色包自带的精简 Python 运行时只覆盖 Windows;macOS / Linux 直接使用系统 Python,需要 **3.9+**(启动脚本找不到内置运行时会明确打印所用的解释器与版本)。Docker 部署自带基础镜像,不受影响。
> zip 解压后若提示权限不足,先执行一次:`chmod +x start-wb-proxy.sh start-wb-proxy.command start-wb-proxy-lan.sh start-wb-proxy-lan.command allow-firewall.command`
### 2. 面板访问密码
看板需要**面板访问密码**(默认 `admin`),它与 API Key 相互独立:密码只用于打开看板,可在「设置」页修改(或启动时 `--panel-password` 指定),以 PBKDF2-SHA256 摘要存于 `accounts/settings.json`(不存明文),登录态只留在浏览器会话里。
本机 / 受信局域网自用可带 `?pwd=面板密码` 免手输直接进面板(如 `http://127.0.0.1:8788/?pwd=admin`);公网暴露时不要用——密码会留在浏览器历史与反向代理日志里。注意它和局域网共享的 `?key=` 不同:`?key=` 只把 API Key 存给 `/v1` 用,不会自动登录面板。**首次登录后请立即改掉默认密码。**
### 3. 局域网共享
- **Windows** 双击 `start-wb-proxy-lan.bat`(静默)或 `start-wb-proxy-lan-visible.bat`(前台窗口,关窗即停);**macOS** 双击 `start-wb-proxy-lan.command`,或 `./start-wb-proxy-lan.sh [端口] [Key]`;
- **Base URL** `http://<本机局域网IP>:8788/v1`;带密钥直达面板 `http://:8788/?key=生成的Key`;
- **API Key**:首次启动生成高强度随机 Key,写入 `accounts/settings.json` 并打印在终端,重启复用;也可用第二个参数指定自己的 Key;
- **macOS 防火墙**:首次监听端口时选「允许」;macOS 15+ 还要在「系统设置 → 隐私与安全性 → 本地网络」允许终端,`./allow-firewall.command` 可查看状态并加白。
### 4. 多 API Key 与出口绑定
「设置」页可管理多个 Key,每个 Key 独立出口——不同客户端各用各的 Key,国内外流量互不干扰:
- **生成 / 绑定**:填名称点「生成随机 Key」;可固定走 🌐 国际版或 🇨🇳 国内版,不绑定则跟随看板顶部的全局出口开关;
- **模型限制**:可填允许的模型(如 `deepseek*`、`gpt-6-astra`,支持 `*`,逗号分隔),留空不限;不在列表里的请求本机直接返回 400,不送达上游、不消耗额度;
- **区域自检**:Key 绑定的出口与模型不匹配时(如国际版 Key 调国内独占的 `deepseek-v4-pro`)直接返回可读 400,而不是上游晦涩的 WAF 报错;
- **启停 / 删除**:可单独启停;删除后密钥立即失效,条目以只读形式留在「设置」页的「已删除」折叠区,历史用量仍显示它的名字;
- **用量归因**:「数据看板」页按 Key 列出请求数、Token、缓存命中、积分与模型分布,口径与账号表一致;
- **防冲突**:面板保存过 Key 后,启动参数里的 `--api-key` 自动失效。
### 5. Docker 部署
预编译双架构镜像(`linux/amd64`、`linux/arm64`)发布在 GHCR 与 Docker Hub,**无需克隆代码、无需本地编译**:
**方式一:一键脚本(推荐)**
```bash
# 官方源(可直连 GitHub 环境):
curl -fsSL https://raw.githubusercontent.com/ardeyouxipianyi/workbuddy2api-hub/main/quick-deploy.sh | bash
# 国内网络 / NAS 加速(Connection reset 时用):
curl -fsSL https://gh-proxy.com/https://raw.githubusercontent.com/ardeyouxipianyi/workbuddy2api-hub/main/quick-deploy.sh | sudo bash
```
NAS(如飞牛 fnOS)普通用户没有 Docker 权限时,把结尾的 `bash` 换成 `sudo bash`。再次运行同一条命令即平滑升级,账号与用量数据不受影响。
**部署模式**:默认 `lan`(`8788:8788`,局域网内可访问)。部署到公网服务器、或只想本机能连时,把环境变量放在管道后的 `bash` 前面:
```bash
curl -fsSL https://raw.githubusercontent.com/ardeyouxipianyi/workbuddy2api-hub/main/quick-deploy.sh | WB_DEPLOY_MODE=public bash
```
- `local` / `public` 只把宿主端口绑到 `127.0.0.1`,容器内仍是 `HOST=0.0.0.0`(Docker 转发需要);
- `public` 请再套 Nginx / Caddy / Cloudflare Tunnel 并加 TLS,不要把 8788 直接开到公网;
- 已经存在的 `docker-compose.yml` 不会被自动改写端口映射,脚本只提示手动调整。
**方式二:NAS / 面板单文件 Compose**
在 NAS 或面板的 Compose 界面新建项目并粘贴以下内容保存启动,无需拉取源码:
```yaml
services:
wb-proxy:
image: ghcr.io/ardeyouxipianyi/workbuddy2api-hub:latest # 或 ardeyouxipianyi/workbuddy2api-hub:latest
container_name: wb-proxy
restart: unless-stopped
ports:
- "8788:8788" # 左侧宿主端口可自选;右侧必须与下面的 PORT 一致
environment:
- HOST=0.0.0.0
- PORT=8788
# - API_KEY=your_secret_key # 留空则自动生成并打印在启动日志
- TZ=Asia/Shanghai
volumes:
- ./accounts:/app/accounts # 账号凭证与配置(更新/重建容器不丢)
- ./usage:/app/usage # 用量流水日志(更新/重建容器不丢)
```
更新:`docker compose pull && docker compose up -d`(重建容器)。面板的「安装更新」按钮在镜像部署里默认不可用——镜像里既没有 systemd unit,也没有让 `wb_update_portable.available()` 成立的应用内 `release-manifest.json`;需要注册外部执行者(见下一节),而它只替换应用层文件,不会重建镜像。
**无 systemd 部署的面板一键更新**:容器 / LXC / 便携版没有 systemd 时,面板默认不能安装更新。部署方可以注册一个外部执行者:
1. 让网关能写请求:挂载一个目录(例如 `/run/workbuddy2api-hub-updater`),设置 `WB_UPDATE_INSTALL_ENABLED=1`、`WB_UPDATE_REQUEST_DIR=<目录>`,并设置 `WB_UPDATE_EXECUTOR=external`(或在该目录放一个 `executor.json`)。
2. 执行者监听 `<目录>/request.json`,读取 `action` / `tag` / `version` / `pid` / `port` / `root`,执行下载 → 校验 `SHA256SUMS` → 备份 → 停进程 → 替换 → 等 supervisor 拉起 → 探针 `/health`;失败回滚。`action` 缺省为 `install`,`rollback` 表示用本地备份恢复旧版本(不下载)。参考实现:`wb_update_external.py --request-dir <目录>`(需要 supervisor 在网关退出后自动拉起)。
3. 执行者写 `<目录>/status.json`,`state` 使用 `queued` / `downloading` / `verifying` / `backing_up` / `stopping` / `applying` / `restoring` / `restarting` / `succeeded` / `restored` / `rolled_back` / `failed`,面板沿用现有状态机。
保留历史版本备份:设置页可选 0–3 份(默认 1),更新成功后把旧版本的程序文件留在 `updates/backup-<这次更新去的版本>/`(内容其实是更新前的版本),面板「版本回退」据此列出可回退的版本。`0` = 不留备份,回退不可用。
约束:`accounts/`、`usage/` 不参与替换;执行者必须有应用目录写权限;网关只写请求,不碰自己的代码。
**方式三:Watchtower 自动静默更新**
```bash
docker run -d --name wb-proxy-watchtower --restart unless-stopped \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower:latest --interval 86400 --cleanup wb-proxy
```
**补充与排错**
- **持久化**:`./accounts` 与 `./usage` 由宿主机挂载,更新或重建容器都不丢数据;
- **docker run 快捷启动**:
```bash
docker run -d --name wb-proxy --restart unless-stopped -p 8788:8788 \
-v $(pwd)/accounts:/app/accounts -v $(pwd)/usage:/app/usage \
ghcr.io/ardeyouxipianyi/workbuddy2api-hub:latest
```
- **鉴权**:容器以 `--lan` 启动,无显式 `API_KEY` 时自动生成 Key 写入 `./accounts/settings.json` 并打印在日志里:`docker compose logs wb-proxy | grep -i "api key"`;
- **镜像名要带 registry**:写成 `ardeyouxipianyi/workbuddy2api-hub` 会去 Docker Hub 找并报 `pull access denied`,正确写法是 `ghcr.io/ardeyouxipianyi/workbuddy2api-hub:latest`(GHCR 公开包,拉取无需登录);
- **目录权限**:默认以 root(`0:0`)运行;想以宿主用户跑就设 `PUID` / `PGID`(或 `--user $(id -u):$(id -g)`),并确保两个挂载目录对该 uid 可写;
- **健康检查**:镜像自带 `HEALTHCHECK`(每 30s 探一次 `/health`),`docker ps` 的 STATUS 列会显示 healthy / unhealthy;
- **源码构建**:`docker compose -f docker-compose.build.yml up -d --build`。
### 6. 测试
```bash
python tests/run_all.py # 全部套件
python tests/run_all.py realm # 只跑名字里含 realm 的
```
- 234 个套件:175 个 Python + 59 个 JS;JS 需要 PATH 上有 `node`,缺失时会跳过并提示。
- `tests/_mobile_check.py` 是独立的 Playwright 手机/桌面布局检查器(需自行安装 Playwright),按需手动运行,不在上面的套件集里。
- CI(`.github/workflows/tests.yml`)跑同一条命令:Ubuntu 上 python 3.9 与 3.12(3.9 是本项目声称的最低版本),Windows 上 python 3.12;推送 `v*` tag 时额外断言 **tag == 源码版本**(`-ci` 演练 tag 豁免)。
---
## 二、核心特性
### 1. 模型列表对齐官方桌面端
对官方本地配置清单(50+ 底层模型)做清洗:剔除行内补全专用模型(`codewise-*`、`completion-gf`、`hunyuan-3b/7b` 等)与多云专线变体(`*-volc`、`*-lkeap` 等),严格对齐官方 Windows 桌面端,并宣告每个模型的上下文窗口、单次最大输出、视觉支持、工具调用与推理档位。
- **🌐 国际版(17 个)**:`hy4-preview-f`、`hy3`、`deepseek-v4.1-flash`、`gpt-6-astra`、`gpt-5.6-sol`、`gpt-5.6-terra`、`gpt-5.6-luna`、`gpt-5.5`、`gpt-5.4`、`grok-4.7`、`gemini-3.5-flash`、`glm-5.3-flash`、`glm-5.3`、`glm-5.2`、`kimi-k3`、`kimi-k2.6`、`kimi-k2.8-preview`
- **🇨🇳 国内版(14 个)**:`hy4-preview-f`、`hy3`、`deepseek-v4.1-flash`、`deepseek-v4-pro`、`glm-5.3`、`glm-5.3-flash`、`glm-5.2`、`glm-5.1`、`glm-5v-turbo`、`minimax-m3`、`kimi-k3-1`、`kimi-k2.8-preview`、`kimi-k2.7`、`kimi-k2.6`
清单与上游 `GET /v3/config` 的 `agents[cli].models` 保持同步(接口不可用时依次回落到桌面端缓存文件、内置快照);过滤规则:去掉 5 个档位别名与 `auto`、去掉 `-sg` / `-x` 变体、同名的只留 0.00 倍率那一档。上游新上架的模型无需发版即出现在 `/v1/models`。
> 同名模型(如 `deepseek-v4.1-flash`)**暂未实现跨国内 / 国际账号的混合轮询**:两个区域作为独立出口分别配置与调度,请求只走当前所选网关。这是出站指纹对齐与账号防风控的取舍,待长期实测确认稳定后再补。
### 2. 设备指纹隔离(`derive_id`)
国际版与国内版共用同一套算法内核:以账号 UID 结合固定业务盐值单向哈希派生机器码与会话标识——同一账号每次出站都来自同一台虚拟设备(不随机漂移),不同账号之间彼此独立(阻断跨账号关联风控)。
### 3. 国内版自动化
- **每日签到**:一键完成国内版打卡领积分;
- **自动连续打卡与连登管家**:每日定时为国内版账号上报轻量会话事件(复刻客户端 `chat_request_send`),点亮官方成长中心连登天数与热力墙;自动闭环检查昨日漏签(有补签卡则自动补签保连登)、领取新手礼包/补偿、兑换 7d/14d/28d 连登档位奖励,并自动抽完抽奖次数;
- **成长任务与积分任务**:自动批量接取未接任务,构造规范行为事件上报点亮(画布创建、灵感案例、模板使用、模型体验、多轮对话等 14 项),并自动领奖入账;
- **猫猫日常**:自动检查旅行状态,在家自动派出、归来自动领奖。
### 4. 后台常驻调度器
常驻后台,按固定整点执行自动化运维排程:
- **每日 09:00 & 21:00**:国内版账号自动签到、对话活跃上报(点亮连登)与猫猫旅行闭环;国际版账号执行每日活跃打卡(领官方每日 30/50 积分福利);
- **每日 22:00**:集中扫描全库账号,Token 剩余寿命不足 2 小时自动调用 Refresh Token 保活;
- **每日 01:00**:深夜时段执行夜猫子任务;
- 看板顶部另有「立即巡检保活」与「每日活跃打卡 (国际版)」可随时手动触发。
### 5. 限额护栏(设置 → 账号限额)
四条账号级护栏放在**同一张表**里,每行一条、每列一个作用域:
| 护栏 | 作用 |
| --- | --- |
| 保留积分 | 账号余额低于该值时不再接单,避免余额被用尽后触发上游的提醒短信 |
| 每日 Token 限额 | 账号当日消耗的 token 达到该值时暂停接单,请求自动切到其他账号 |
| 每日积分限额 | 账号当日消费的积分达到该值后只服务免费模型,需要花积分的模型自动切号 |
| 按模型每日 Token 限额 | 账号在单个模型上当日消耗的 token 达到该值时只禁该模型,同账号其他模型照常 |
- **全局默认 + 可选分版本**:每条护栏的「全局默认」同时作用于国际版与国内版;勾上「分别设置国际版 / 国内版」后可单独设值,某版本**留空即继承全局**(占位符会写出继承到的数字),取消勾选再保存等于清回继承。
- 每条填 `0` 表示关闭(默认值);四条都按**本地时间 0 点**解封,只在请求路径生效(定时任务不受影响),且都是**单账号独立计数**。
- 免费 / 付费以**各出口自己的模型目录**为准,未知模型按付费处理;账号行会显示对应徽章与 `模型 · N tok 达限` 标记。
- 某个出口的全部账号都达额时,该出口的请求返回 `429`(文案说明本地 0 点恢复,`Retry-After` 指向 0 点)。
- 存储:`accounts/settings.json` 里的一组 `limits` 映射(`{"global": …, "intl": …, "cn": …}`,`null` 表示继承全局);旧版写在外层的四个扁平键会在第一次读取时自动折入。
### 6. OpenRouter 价估算(等价 token 花费)
把每条请求的 token 消耗按 **OpenRouter 公布的模型价**折算成等价金额,回答「这些 token 放在 OpenRouter 上值多少钱」——与账号实际扣除的积分是两个口径,看板里并列显示:
- **总开关**(「设置 → 模型价格估算」,默认开启):关掉后价格列、「API 等价花费」卡片、取价控件与未定价清单一起隐藏,后端也不再取价与折算;重新打开会立刻抓一次价并**补算关闭期间的请求**(历史日志一个字不改,金额始终是读时算的)。
- **计价口径**:输入按缓存命中 / 未命中两档单价拆分,输出单独单价,乘 token 数再按汇率折算;输出的 token 数取上游 `completion_tokens`(已包含推理 token,不另计)。按条件定价的模型(按输入长度或按 UTC 时段)按每条请求取最紧的那一档。
- **定价来源**:OpenRouter 模型目录的**模型级公布价**。OpenRouter 把它定义为该模型 top provider 的价——对多 provider 模型通常就是最低档(实测 `deepseek-v4.1-flash` 的模型级价只比 34 家 provider 的最低价高 5%)。这组数字只用于「这些 token 放在 OpenRouter 上值多少钱」的参考估算,**不等于 WorkBuddy 官方价,也不能用于结算**。每份价格按内容存成一条「定价策略」,每条请求只记引用了哪条策略,所以之后调价不会改写历史数字;`wb_pricing.py` 另内嵌一份快照作出厂价。
- **刷新**:「设置 → 定价刷新」默认每 5 分钟一次(填 0 关闭);只有价格真的变了才写策略与时间轴,价格不动时每轮只是一次 HTTP GET。面板可看当前生效策略、上次 / 下次取价时间与失败原因,也能点「立即取价」。
- **新增模型自动取价**:候选清单 = 内置目录 ∪ 网关实时目录,上游新上架的名字 5 分钟内进入取价;两次取价之间就被调用也会按需补价(请求路径不发网络请求)。匹配顺序:面板手填覆盖 → 人工覆盖表 → 同名唯一命中 → 变体后缀继承(剥 `-lkeap` / `-taiji` / `-volc` / `-sg` 等,仍要求唯一命中),继承可在设置里整体关掉。
- **按模型校准**:在 `usage/pricing-markup.json` 写 `{"deepseek-v4.1-flash": 5.36}` 之类的系数,估算金额会乘以它;文件缺失或值非法时按 1.0。它只影响读时估算,不改写历史策略。
- **未定价可见**:`/pricing` 列出未定价模型及分类(别名 / OpenRouter 无对应 / 剥后缀后无唯一基准)与相似候选(仅建议,不自动采用),可在「设置 → 定价刷新」为某条手填 OpenRouter id 并登记(写 `pricing-overrides.json`,不改源码,登记后立即取价)。
- **展示位置**:数据看板「API 等价花费」卡片、各账号用量透视与模型性能表**最后一列**、网关页「估算价格」卡片、最近请求**最后一列**(悬停可看完整定价链:三档原始单价、汇率、匹配证据、策略 id 与补算标记 `*`)。
- **人民币 / 美元**一键切换(记在浏览器本地);快照覆盖不到的模型显示 `—`,不猜数字。
### 7. 本地网络工具(可选,默认关闭)
Codex App 这类客户端会在 Responses 请求里声明 `web_search` / `web_fetch`,而上游没有对应的执行器——声明送上去只会拿到一句 unsupported call。打开「设置 → 本地网络工具」后,网关把那份声明换成自己的同名 function,拦下模型的调用并在本地执行(搜索在 DuckDuckGo / Bing / Wikipedia / Stack Overflow / Google News 之间自动轮换,抓页面抓模型给出的 URL),再把结果喂回模型,最多代跑 3 轮(`WB_MAX_WEB_ROUNDS` 可调,上限 8);搜索过程会作为 `web_search_call` 卡片事件与 `url_citation` 引用回到客户端。
- 关闭时(默认)声明原样透传,客户端自己声明的搜索工具照常拿到调用;
- 打开后网关会主动出网抓取模型给出的 URL(字面私网地址与解析进内网的域名都会被挡;
`WB_WEB_TOOLS_PROXY` 可以给这组工具单独指定出口代理),每轮代跑都会多跑一次上游、多消耗该账号额度;
- 某个搜寻引擎被限流/拦截时会自动轮换到下一家,只有全部不可用才把明确错误交给模型;常规引擎(DuckDuckGo / Bing)接手时对模型无感,Wikipedia / Stack Overflow / Google News 这类非常规来源会在结果里标明来源;
- 只影响声明了这两个工具的客户端,普通 `/v1/chat/completions` 客户端不经过这条路径。
### 8. 智能体一键配置
看板「智能体配置」页可检测本机已装的 AI 客户端,一键把它们的配置指向本网关,改前自动备份、随时字节级还原:
| 客户端 | 写入位置 | 协议 |
| --- | --- | --- |
| Claude Code | `~/.claude/settings.json` | 原生 Anthropic Messages(自动剥 `/v1` 后缀) |
| Codex CLI | `~/.codex/config.toml`、`~/.codex/auth.json` | Responses API |
| OpenCode | `~/.config/opencode/opencode.json` | OpenAI 兼容(`@ai-sdk/openai-compatible`) |
| DSH (DeepSeek Harness) | `~/.dsh/profiles//cordis.patch.yml`(无 profile 时回退 `~/.dsh/settings.yaml`)、`~/.dsh/.credentials.yaml` | OpenAI 兼容 |
| Crush (Charm Crush) | `~/.config/crush/crush.json` | OpenAI 兼容 |
| ZCode | `~/.zcode/v2/provider_config.json` | OpenAI 兼容(`openai-chat-completions`;**配置前先退出 ZCode**,运行中它会重写该文件) |
用法:打开看板 →「智能体配置」→ 选 API Key(支持全局默认或绑定特定出口的多 Key)、默认模型与网关地址 → 在检测到的客户端卡片上点「一键配置」;随时可点「一键还原」。
- **模型清单可控**:「网关与账号」页的「网关模型清单」每行模型 ID 前有一个复选框,**默认全部勾选**(从没动过勾选时等价于全量),表头另有「全选 / 全不选 / 反选」三个批量动作;只有勾选的模型才会被一键配置写进 DSH / OpenCode / Crush / ZCode 这类把模型清单抄进自身配置的客户端(Claude Code / Codex 不读这份清单,不受影响)。一个都没勾时,这几家的「一键配置」会直接拒绝并提示先勾选。勾选结果存在 `accounts/settings.json` 的 `agent_models` 里(对所有版本视图生效;字段缺失 = 从未保存 = 全量,空数组 = 明确全不选,两者语义不同),改完需要重新一键配置并重启对应客户端才生效;
- **非侵入**:纯标准库实现的文本级 YAML / TOML / JSON / .env 编辑器,只增量插入或更新 `wb-proxy` 那段,绝不重排或抹掉用户的注释、缩进与其他 provider 配置;
- **可回滚**:写入前自动备份到 `accounts/agent-backups/<客户端ID>/`(每文件保留最新 10 份),首次接入的原件永久保留;多文件客户端(DSH、Codex)两阶段写入,任一文件失败立即回退;
- **还原**:byte-exact 回到首次接入前,网关新建的文件会被清理;若之后手动改过配置,看板会提示「检测到外部修改」,还原仍会覆盖回原件;
- **密钥安全**:API Key 只写进客户端自己的本地配置目录(权限仅限当前系统用户),单文件超过 8MB 拒绝编辑。
**DSH:思考档位、上下文窗口、识图与限流重试写在哪**(`~/.dsh/profiles//cordis.patch.yml`)
当前 DSH 只在首次启动时导入一次 `~/.dsh/settings.yaml`,随后改名 `settings.yaml.imported`,之后不再读它;桌面版 / Web 版真正生效的是各自 profile 的 `cordis.patch.yml`。一键配置因此直接写这一层(`~/.dsh/settings.yaml` 只在它本来就有、或机器上还没有 profile 时写入)。给 DSH 写的每个模型条目都带上网关宣告的规格,DSH 会据此生成它自己的选择器:
```yaml
wb-proxy:
api: openai-completions
baseURL: http://127.0.0.1:8788/v1
compat: {thinkingFormat: openai, supportsReasoningEffort: true}
retryPolicy: # 限流等待:见下「限流重试」
mode: normal
maxRetries: 8
backoff: {initialDelayMs: 2000, maxDelayMs: 30000, jitterRatio: 0.1}
models:
- id: deepseek-v4.1-flash
contextWindow: 1000000 # 客户端认为的上下文容量(压缩 / 截断按它算)
maxTokens: 128000 # 单次输出上限
input: [text, image] # 输入模态:不写 image 就贴不了图
reasoningEfforts: # 会话里可选的思考档位:键=档位,值=发给网关的拼写
low: low
high: high
max: max
```
- **识图**:DSH 只认模型条目里的 `input`(`inputModalities`)声明,不读 `supportsVision`,也不读 `/v1/models` 里的能力字段。所以网关宣告 `capabilities.vision` 的模型都会写上 `input: [text, image]`,纯文本模型(如 `glm-5.3-flash`)不写。缺了这一行,贴图会被 DSH 直接拒掉:`Model "" does not support image input.`
- **思考程度在 DSH 里怎么选**:不在「设置 → 模型」页(DSH 有意不把 per-model 的档位放进 provider 级表单),而在**输入框旁的模型菜单**:菜单里有两行 `模型` 与 `推理等级`,点「推理等级」选档位;`/model` 弹窗只列模型、不列档位。选项完全来自上面的 `reasoningEfforts` —— 不声明就没有这一行(`/v1/models` 里的 `reasoning_efforts` DSH 不读,它只读 `id` / `name` / `contextWindow` / `maxTokens` / `input`)。
- **上下文长度怎么改**:`contextWindow` 就是 DSH 用的容量。改客户端侧就编辑这一行(或在 DSH 的模型编辑界面里填),改网关侧就改 `/v1/models` 宣告的 `context_length`;两条链路现在共用 `wb_modelsdev.lookup` 的同一套取值(上游值 > 内建知识表 > 本地缓存 > 1M 默认),不会再出现「网关宣告 1M、一键配置写 300k」这种对不上的情况。
- **上游钉死档位的模型**(目录里只有 `reasoning.effort`、没有 `supportedEfforts`)不写 `reasoningEfforts`:给了选择器也不会改变上游实际跑的档位。
- **限流重试(「对话到一半链接中断」的根因)**:DSH 对失败请求的重试由每个 provider 自己的 `retryPolicy` 决定,默认 normal 模式、最多 5 次、退避 500ms 起按 2 倍翻、封顶 10s——五次加起来不到 16 秒。WorkBuddy 的模型冷却(429)通常要一分钟,DSH 在冷却结束前就把重试次数用光,回合以 `RATE_LIMIT` 失败,客户端看到的就是对话中途断掉。一键配置因此给 `wb-proxy` 单独放宽成最多 8 次、退避 2s 起、封顶 30s(合计约 2 分半),足够熬过一次 60 秒冷却。字段名与取值范围按 `@deepseek-ai/dsh-llm` 的 `RetryPolicySchema`;不写 `retryableCodes`,沿用 DSH 默认(`EMPTY_RESPONSE` / `RATE_LIMIT` / `SERVER` / `TIMEOUT` / `TRANSPORT`,429 落在 `RATE_LIMIT`)。只影响 `wb-proxy`,其它 provider 仍是默认策略;已经接入过的机器重新点一次「一键配置」补上这段(幂等),重启 DSH 生效。
- **注意 profile 覆盖层**:`$DSH_HOME/profiles//cordis.patch.yml` 里的 `- id: llm-pi-ai` 条目会**整体替换**该插件的配置(不是逐键合并)。一键配置现在直接更新 patch 层里的这一条:只替换 `wb-proxy` 那段,其他 provider(如 `qwen-token-plan-cn`)、其他条目与注释原样保留,所以不会再出现「写进 `settings.yaml` 被 patch 盖住、不生效」的情况。
**在另一台机器上接入(远程 / 非本机)**:看板的一键配置运行在网关进程里,改的是**网关所在机器**的配置目录——所以它只在网关本机、且用 `127.0.0.1` 打开看板时可用(远程访问、容器 / OpenWrt 部署下按设计隐藏入口)。客户端装在别的电脑(网关跑在服务器 / NAS / 路由器上)时,改用自带的配置器 CLI:它在**客户端所在的机器**上运行,写本机配置、只通过 HTTP 从网关取模型表:
```bash
# 1. 取配置器(网关直接提供该文件;也可从仓库 / 绿色包复制 wb_agents.py)
curl -fsSL http://<网关地址>:8788/agents/cli -o wb_agents.py
# 2. 一条命令接入(需要 Python 3.9+,Key 在「设置」页生成)
python wb_agents.py list # 本机装了哪些客户端
python wb_agents.py apply --client codex --base-url http://<网关地址>:8788/v1 --key sk-xxxx
python wb_agents.py restore --client codex # 随时字节级还原
```
- 支持 `claude-code` / `codex` / `opencode` / `dsh` / `crush` / `zcode` 六个客户端,与看板共用同一套写入、备份与还原逻辑(`plan()` 同时驱动 `--dry-run` 与真实写入);
- 默认带 Key 请求一次 `GET /v1/models`:既为 OpenCode / DSH / Crush / ZCode 填入模型表,也顺带验证 Key(401 = Key 被拒);网关不可达或想完全离线时加 `--no-models`;
- 状态与备份存在该机器自己的 `~/.wb-agent-config/`(可用 `--state-dir` 覆盖),每台机器一份,`restore` 只对执行过 `apply` 的机器有效;
- `--dry-run` 只打印将要写入的内容、不落盘;Key 也可用环境变量 `WB_PROXY_API_KEY` 传入。
模块内部设计与新增客户端的方法见 **[docs/CONTRIBUTING-智能体配置.md](docs/CONTRIBUTING-智能体配置.md)**。
### 9. 剩余用量估算(数据看板 → 剩余用量估算)
上游按 **24 小时窗口**给「账号 × 模型」配额(用满时 429 / code 6004 会带上重置墙钟),但从不告诉你这个窗口的预算是多少。看板新增的「剩余用量估算」区块把它反推出来:
- **预算 = 撞线账号窗口用量的平均**:账号撞线(收到带重置时刻的 429)的那一刻,它在该模型上「自窗口起点以来的成功用量」就是预算的一个样本;按区域聚合、先按账号平均再跨账号平均。样本在 429 处理的那一刻就记进 `usage/limit-events.jsonl`(重试循环里只有最后一个账号会留下带归属的 429 用量行,光靠日志会漏掉大部分撞线),历史日志里带账号的 429 行作为补充,两者按「账号 + 模型 + 重置时刻」去重。
- **已用从每个账号自己的重置时刻起算**:窗口起点取该账号该模型最近一次重置时刻;重置时刻在未来的组合(正在冷却)剩余记 0 并显示恢复时间。
- **没有撞线记录的组合标「按 24h 估算」**:窗口起点未知时按最近 24 小时用量统计——窗口起点必然落在最近 24h 内,所以这是剩余量的**下界**(偏保守,不会高估)。
- 接口 `GET /usage/remaining`(需面板会话)返回 `budgets`(每个区域 × 模型的预算均值 / 区间 / 样本数)与 `rows`(每个账号 × 模型:已用、预算、剩余、窗口起点、是否冷却、是否估算),区块只读不写,不参与请求路径(唯一的请求路径接触点是撞线时记一行事件)。
- **查询侧不做重复计算**:每个「账号 × 模型」的用量缓冲是**时间戳 + 累计和**两个列表(窗口求和 = 两次二分 + 一次相减,O(log n),内存约 16 字节/行),载荷再挂一个短 TTL(`WB_REMAINING_TTL`,默认 15 秒)并支持 `ETag` / `If-None-Match`:面板 5 秒一轮的轮询在 TTL 内直接拿上次算好的载荷(连日志尾部都不再扫),带条件请求时回 304。实测(2 万行缓冲):单次重建 ~0.7ms(旧版逐行重算 ~1.8ms)、轮询一分钟 2.9ms(旧版 21.5ms)、缓冲内存 321KB(旧版 2.3MB)。
### 10. 积分获取历史(数据看板 → 积分获取历史)
「积分扣减历史」看的是花掉的部分,这一块补上进账的部分:上游对每个账号返回一份**积分包清单**(每日活跃奖励的 Bonus Pack、免费套餐、活动包…),每个包带面额、发放时间与到期时间。区块把全部账号的包摊平成一张表、新的在前:
- 列:时间(发放)/ 账号 / 区域 / 名称 / **来源** / 积分 / 剩余 / 到期 / 状态;
- 状态按「过期 > 用完 > 在扣减 > 可用」归类:**生效中**(上游标了 `in_usage`,当前正从它扣减)、**可用**、**已用完**、**已过期**;`no_expiry` 的包显示「不过期」;
- **来源**:上游自带的发放原因原样显示(如「Buddy 加油站签到」「成长计划奖励」「官方活动发放」);国际版的 Bonus Pack 30/50 没有原因字段,按官方规则推断为「每日活跃奖励」(悬停里注明这是推断)。我们自己的**签到 / 每日活跃**记录也会按时间关联上去(取发放前 2 小时内最近的一次尝试,带成功 / 失败),悬停即可对上「本机动作:每日签到 10-10 00:07 成功」——一次发放到底由哪次动作带来,一眼可见;
- 摘要行给出笔数、合计面额、合计剩余与**快照时刻**。接口 `GET /accounts/credits/grants` 只读内存里的积分快照、**不发上游请求**——数字的新鲜度取决于最近一次「一键刷新积分」(签到与每日活跃打卡也会顺带刷新它),所以要看最新的包先点账号页那个按钮。
### 11. 单请求体体积限制(`WB_MAX_PAYLOAD_BYTES`)
针对携带超长历史、大内联附件或高分辨率图片的请求,网关默认对单次请求体做上限保护(默认 16MB = `16777216` 字节),以防止异常巨大的请求挤占内存。若请求体超过上限,网关在解析前即直接返回 HTTP 413(`payload too large` 并附带 `gateway_hint` 说明)。
如需调整上限(例如提升至 32MB):
- 设置环境变量 `WB_MAX_PAYLOAD_BYTES=33554432`(单位为字节);
- 此环境变量仅在进程启动时读取一次,修改后重启 `wb-proxy` 即可生效。
---
### 12. 后台模型重定向(`WB_BACKGROUND_MODEL_NAMES` / `WB_BACKGROUND_MODEL_TARGET`)
桌面客户端(Codex 等)的模型选择器**只管主对话线程**。客户端内置的「模型路由配置」会把后台工作——记忆生成、环境建议、临时内容生成、自动化任务——送到自己硬编码的模型上,UI 里选什么免费模型都改不掉。这些请求带着同一把 Key 打到网关,于是在共享 Key 的部署里悄悄烧付费积分。
网关在唯一的 POST 分发点(选账号之前)把这类模型名改写成免费目标:
- `WB_BACKGROUND_MODEL_NAMES`:要拦截的模型名清单(逗号分隔,大小写不敏感),默认 `gpt-5.6-terra,gpt-5.6-luna,gpt-5.3-codex`;
- `WB_BACKGROUND_MODEL_TARGET`:改写目标,默认 `deepseek-v4.1-flash`。
改写只动 `model` 字段,请求体其余部分原样透传;命中与改写都会记一条 `alias: background model X -> Y` 日志,方便核对。OpenAI 形状的两个路由(`/v1/chat/completions`、`/v1/responses`)与 Anthropic 形状的 `/v1/messages` 都覆盖。不需要时把名单设为空即可关闭。
---
### 13. 自签证书 / TLS 解密代理(WB_CA_BUNDLE)
如果网关跑在公司代理、Clash/Surge 等 HTTPS 解密后面,或 macOS 上 Python 没有读到系统钥匙串里的根证书,上游请求会报 CERTIFICATE_VERIFY_FAILED: self-signed certificate in certificate chain,面板上的 OAuth 登录和账号测试会显示 502。把签发该证书的根证书导出为 PEM,然后设置 WB_CA_BUNDLE=/path/to/ca.pem(优先)或 SSL_CERT_FILE=/path/to/ca.pem,重启 wb-proxy。网关会在系统根证书之外追加这份 PEM,不关闭证书校验;证书错误也不再重试,日志和面板会直接给出这段提示。
---
### 14. 国内版上游地址(`WB_CN_CHAT_UPSTREAM`)
国内版对话侧默认发往 `https://copilot.tencent.com`。网关跑在海外出口后面(代理软件的全局模式、海外服务器)时,到它的 TLS 握手会被复位,日志里是 `SSL: UNEXPECTED_EOF_WHILE_READING` 或 `WinError 10054`,国内版账号的对话、刷新 token、模型目录、签到与成长任务全部失败。`www.workbuddy.cn` 提供同一套接口且海外可达(官方桌面端当前即以它为 endpoint,VSCode 身分本来也走它)。
- 设置 `WB_CN_CHAT_UPSTREAM=https://www.workbuddy.cn`,重启 `wb-proxy`;启动日志的 `realm` 行会显示新的出口,并多一行 `cn upstream: ... (WB_CN_CHAT_UPSTREAM)`;
- 只改「往哪发」:`X-Domain` 仍用账号登录时记下的 domain,计费侧(`www.codebuddy.cn`)不变;
- 不是 `http(s)://` 开头的值会被忽略并回落默认地址,启动日志给出提示;不设则行为与此前完全一致。
---
## 三、账号添加与管理
打开看板 `http://127.0.0.1:8788/`,在「账号」区域操作。若上游对某账号的单个模型返回 429,账号行会显示受限模型与预计恢复时间(浏览器本地时间),该账号仍可用于其他模型;模型冷却只在当前进程内保留,重启清空。
窗口探针管的是「已经撞到 429 之后」;想提前知道某条配额线有没有被打满,可在「设置 → 模型额度定时主动查询」勾选模型:网关每 6 小时为每个「账号 + 模型」发一个最小请求做体检,查到 429 就立刻记该模型的冷却并把重置时刻交给窗口探针,请求通了就说明还没到限额。一旦某个「账号 + 模型」已经有了登记的 429 记录,就停止定时查询(改由窗口探针负责),记录消失后自动恢复轮询;每次查询都会消耗该账号极少量额度,默认不勾选任何模型。
### 方式一:浏览器 OAuth 授权(推荐,免客户端)
1. 点「+ 添加账号 (OAuth)」;
2. 选择要登录的区域(国际版 / 国内版),点弹出的官方授权链接并在浏览器完成登录;
3. 程序自动检测回调,账号自动加入账号池,无需手动复制凭证。
### 方式二:从本机桌面客户端导入
1. 让 **WorkBuddy 桌面客户端保持运行并已登录**(Windows 上网关要从它的进程内存里取解码密钥,这一步不能省);
2. 看板点「扫描桌面客户端账号」,弹窗列出本机已登录的国际版 / 国内版账号;
3. 若提示凭据已加密,先把解码密钥交给网关,再点账号行的「导入」:
- **Windows**:点弹窗里的「回收密钥」(只读,实测 1 秒内完成);
- **macOS / Linux**:进程内存回收只在 Windows 上实现,弹窗里会直接给出输入框,粘贴密钥即可(见下)。
关于加密凭据:桌面客户端从 2026-09-24 起把 `accessToken` / `refreshToken` 存成 `$wbEncrypted` 信封,网关按客户端 `packages/at-rest-crypto` 的同一套方案(AES-256-GCM + `WB-AAD` 帧头)就地解密,导入的仍是可直接使用的 token;解码密钥编译在客户端原生模块里、磁盘上没有明文。Windows 上可以从**正在运行的**桌面端进程内存里找回(只读 `OpenProcess(PROCESS_VM_READ)` + `ReadProcessMemory`,不向客户端写任何东西);macOS / Linux 上没有这条路,看板会显示输入框让用户**手动粘贴**一次——两种方式拿到的密钥都只留在网关进程内存、不落盘不写日志,且都要先过 keyblob 自检(protectorKeyId 对得上、能解出 32 字节主密钥)才生效。
手动粘贴接受两种写法(都不用自己换算):64 位 hex 的保护密钥,或 44 字符 base64 的 `atRestSecretKey`。拿不到密钥时,macOS / Linux 上请直接用方式一走浏览器登录。提示权限不足时,以管理员身份启动网关再试一次。
---
## 四、客户端配置与接入
- **API 接口地址 (Base URL)**:`http://127.0.0.1:8788/v1`(局域网为 `http://<局域网IP>:8788/v1`)
- **API Key**:本机单机模式(未配置 Key 且未开 LAN)可留空或填任意字符;已在看板配置 Key 或 LAN 模式,请用看板「设置」页里绑好出口的 Key;
- **模型名称**:填 `/v1/models` 里列出的任意官方对齐模型 ID(如 `deepseek-v4.1-flash`、`gpt-6-astra`、`glm-5.3`)。
**Codex CLI / Claude Code(Responses API)**
```bash
export OPENAI_BASE_URL="http://127.0.0.1:8788/v1"
export OPENAI_API_KEY="你在看板设置中添加并绑定的API_Key"
```
**Claude Code / Anthropic SDK(实验性 Anthropic Messages 适配,默认禁用)**
> ⚠️ **积分风险:默认拒绝调用。** 网关对外接受 Anthropic Messages,但对内将请求转换为 WorkBuddy 的 `/v2/chat/completions`;`cache_control` 被忽略。已有 [#367](https://github.com/ardeyouxipianyi/workbuddy2api-hub/issues/367) 报告长会话缓存命中率仅约 11%,单次消耗超过 100 积分。该问题的因果机制仍待定位,故暂时默认禁用整个 Anthropic Messages 入口,而不假装完全兼容。禁用时 `/v1/messages`、`/messages` 及对应 `count_tokens` 入口返回 Anthropic 格式的 HTTP 400(`invalid_request_error`),请求不会转发至上游。
只有确认理解风险后,才在**网关服务端**显式设置 `WB_ENABLE_ANTHROPIC_MESSAGES=1` 并重启。Docker Compose 可在 `wb-proxy.environment` 中加入 `- WB_ENABLE_ANTHROPIC_MESSAGES=1`;其他环境则在启动网关前设置此环境变量。客户端配置示例:
```bash
export ANTHROPIC_BASE_URL="http://127.0.0.1:8788"
export ANTHROPIC_API_KEY="你在看板设置中添加并绑定的API_Key"
```
实验性适配包含 `system`、`text`、`image`、`document`、`tool_use`、`tool_result`、`tools` 与 `tool_choice` 等字段转换,并将响应包装为 Anthropic 的流式事件序列或非流式消息。这不是向上游的原生 Anthropic 透传。
已知边界:服务端工具(`web_search` 等)上游不支持,会被丢弃并在 system 里注明;`thinking` / `redacted_thinking` 历史块不回放;`top_k`、`cache_control`、`context_management` 与 `betas` 被忽略;`/v1/messages/count_tokens` 在显式启用后只提供网关的 CJK 感知估算值,**不是**官方分词器的精确值。
---
## 五、看板与接口一览
访问 `http://127.0.0.1:8788/` 使用集成看板:顶部页签默认是「网关与账号 / 数据看板 / 智能体配置 / 设置 / 运行日志」,每个主页面左侧有一条按页面区块现场生成的导航(点击直达、滚动高亮、地址栏带锚点,可收起成窄轨)。
账号多起来之后(国际版十几个),账号列表与「当前禁用」总览会把网关页挤下去。设置页的「账号独立成一个页签」默认关闭,账号区就留在「网关与账号」页(位置与拆分前一致);打开后它们和签到记录一起成为独立的「账号」页:添加 / 导入 / 导出、启用停用、凭证与积分刷新、代理出口绑定都在那里,顶部页签变成「网关 / 账号 / 数据看板 / 智能体配置 / 设置 / 运行日志」。页内的「签到与活跃记录」读 `GET /activity/history`,可按**区间 / 任务 / 结果 / 账号**筛选(筛选在服务端做),一行一次真实尝试,列出 时间 · 账号 · 区域 · 任务 · 来源 · 结果 · 说明,最新在前;超过一页时用「加载更多」往下翻,上限 1000 行——「这个账号昨天到底签成了没有」不用再去调度器日志里翻。
「数据看板」页顶部可切换统计口径:**今日 / 本周 / 本月 / 全部历史 / 自定义**(本周自周一零点起算、本月自 1 号零点起算,任一侧留空表示不限),切换后 KPI 卡片、账号用量透视表与模型性能表一起切到同一窗口。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | / | Web 用量与任务监控看板 |
| POST | /v1/chat/completions | 标准 Chat Completions 接口 |
| POST | /v1/responses | Responses API 协议接口 |
| POST | /v1/messages | 实验性 Anthropic Messages 适配,默认禁用(HTTP 400);需显式启用并承担缓存风险 |
| POST | /v1/messages/count_tokens | 实验性 Anthropic 计数估算接口,随 Messages 入口默认禁用 |
| GET | /v1/models | 官方对齐模型列表(含能力与规格宣告) |
| GET | /u/\/v1/models | 定址账号的模型清单:\ 支持完整 uid / uid 前 8 位 / 昵称,过滤该账号禁用模型 |
| POST | /u/\/v1/chat/completions | 定址调用:锁定该账号,不参与会话亲和 / 轮换 / 自动换号;账号不可用直接报错 |
| POST | /u/\/v1/responses | 定址 Responses API,语义同上 |
| GET | /v1/balance-summary | 当前 API Key 所属区域的启用账号积分汇总;只读内存快照、不发上游请求,不返回账号身份 |
| GET | /pricing | 定价状态:当前生效策略、上次/下次取价时间、未定价清单(分类 + 候选) |
| POST | /pricing/refresh | 立即取一次价(需面板会话) |
| POST | /pricing/mapping | 手填 / 清除「模型 → OpenRouter id」运行期映射,随后自动取价(需面板会话) |
| GET | /agents | 客户端探测概览、支持的模型清单及网关连接地址 |
| GET | /agents/cli | 智能体配置器源码(供远程机器直接 `curl` 取用,免面板鉴权;与仓库内 `wb_agents.py` 逐字节一致) |
| POST | /agents/apply | 一键写入客户端配置并备份原件(需面板会话) |
| POST | /agents/restore | 一键还原客户端至首次配置前的状态(需面板会话) |
| GET | /tasks | 国内版成长任务、连续打卡与猫猫日常状态 |
| POST | /tasks/run | 触发国内成长任务全自动点亮与领奖 |
| POST | /tasks/travel | 触发猫猫日常旅行(派出 / 领奖) |
| GET | /scheduler | 定时调度器运行状态与排程日志 |
| POST | /scheduler/trigger | 手动立即执行后台巡检保活 |
| GET | /activity/history | 账号每日活动历史:签到与每日活跃的每一次真实尝试(`range` / `uid` / `task` / `result` / `limit`,最新在前) |
| GET | /accounts/credits/grants | 积分获取历史:各账号积分包(发放时间 / 面额 / 剩余 / 到期 / 状态),只读内存快照、不发上游请求 |
| GET | /usage/remaining | 剩余用量估算:每个账号 × 模型在当前 24h 窗口的已用 / 预算 / 剩余(预算取撞线账号的平均用量;需面板会话) |
---
## 六、版本更新记录 (Changelog)
- **面板密码加密异常明确报错**(#658):解释器缺少 `hashlib.pbkdf2_hmac` 支持时(如 Windows SAC 拦截便携运行时 DLL),不再将计算异常吞成「密码错误」返回 401,而是明确返回 500 与环境排查指引;启动自检与 `/health`、`/panel/status` 暴露 `crypto_ok` 状态并在面板提示。新增 `tests/_test_panel_crypto_unavailable.py`。
- **v1.9.1 发布**:集成 #692 与 #693,详见 [更新记录](docs/CHANGELOG.md#v191)。
- **流式终止**:设置 `WB_STOP_AT_SSE_DONE=1` 后,收到上游 SSE `[DONE]` 立即结束读取;默认关闭,保持原有行为。 / **Stream termination**: set `WB_STOP_AT_SSE_DONE=1` to stop reading at the upstream SSE `[DONE]` marker; disabled by default to retain existing behavior.
- **HTTP 错误正文**:设置 `WB_CACHE_HTTP_ERROR_BODY=1` 后缓存首次读取的错误正文,供后续读取使用;默认关闭。 / **HTTP error body**: set `WB_CACHE_HTTP_ERROR_BODY=1` to retain the first error-body read for later readers; disabled by default.
- **v1.9.0 发布**:汇总 v1.8.0 之后的 10 个社区 PR(#649、#650、#652、#653、#669、#670、#671、#672、#684、#686)。完整变更、验证范围与已知限制见 **[docs/CHANGELOG.md](docs/CHANGELOG.md#v190)**;下一版的改动继续写在本段。
- **v1.8.0 发布**:汇总 v1.7.33 之后的 #640、#642、#643、#645/#647,并补齐 #578、#618、#625 的发布记录。完整变更、验证范围与已知限制见 **[docs/CHANGELOG.md](docs/CHANGELOG.md#v180)**;下一版的改动继续写在本段。
- **修正 CN 专属模型判定,并给 OpenCode 写入真实上下文上限**(#618 / #625):`minimax-` 曾被当成整个模型家族的 CN 专属前缀,于是国际版也提供的 `minimax-m3.1-flash` 被本地 400 拦下。现在只保留确定的 CN 独占项(`deepseek-v4-pro*` 与显式 `minimax-m3`),同族其他模型按出口实际目录判断。OpenCode 一键配置此前只写空的模型条目,客户端只能回落到自己的 200k 默认值;现在把网关目录中的 `context_window` / `max_output` 写进 `limit.context` / `limit.output`,没有规格的条目仍保持空对象。新增回归覆盖。 / Narrow the China-only model gate and give OpenCode the real context limit (#618 / #625): `minimax-` used to mark the whole family as CN-exclusive, so the globally served `minimax-m3.1-flash` was refused locally with a 400. Only the confirmed China-only entries remain (`deepseek-v4-pro*` and the explicit `minimax-m3`); other members of the family follow the actual catalogue of the selected exit. OpenCode's one-click config used to write empty model entries, so it fell back to its own 200k default; the gateway's catalogue `context_window` / `max_output` now go into `limit.context` / `limit.output`, while entries without specs stay empty objects. Regression coverage added.
- **流式回复在真实静默期发送 SSE 心跳**(#578):上游思考期间可能长时间一个字节都不发,而客户端会把静默误判为断流;只在每次阻塞读之前检查心跳,恰好无法覆盖它。Chat、Responses 与 Anthropic Messages 三条流式路径现在共用后台 `SSEHeartbeat`:每 15 秒无写入就发送合法的 `: keepalive` 注释帧,与业务帧共用锁,保证不会截断 JSON;响应正常时不会多插帧,流结束后线程立即停止。新增 `tests/_test_stream_keepalive.py`(9 项,含读线程阻塞期间仍收到心跳与并发写入完整性),并复跑流式 id、usage 帧、断流错误帧与重试套件。 / Keep SSE streams alive through real upstream silence (#578): while the model is thinking the upstream may send nothing for longer than the client's idle window, and checking a heartbeat only before the next blocking read cannot cover that gap. Chat, Responses and Anthropic Messages now share a background `SSEHeartbeat`: after 15 idle seconds it writes a valid `: keepalive` comment frame under the same lock as application writes, so JSON frames cannot be split; responsive streams gain no extra frames and the thread stops as soon as the stream ends. Added `tests/_test_stream_keepalive.py` (9 checks, including heartbeat delivery while the reader is blocked and write integrity under contention), and re-ran the stream id, usage-frame, abort-frame and retry suites.
- **修复调度器日志与区域风控闸门读错模块副本**:网关以 `python[w] wb_proxy.py` 启动时主模块是 `__main__`,`wb_scheduler` 里裸写 `import wb_proxy` 会再加载一份副本——副本的 `LOG_STORE` 是 None、`restriction_hold_remaining()` 也是另一份状态。结果是调度器写的每条日志都掉进黑洞(实测 6.4 万行日志库里 `[调度器]` 0 条,同期启动横幅 8 次),区域级连锁暂停的闸门恒为 0、从未生效。现在统一走 `_proxy_module()`,优先取正在运行的那一份。新增 2 项断言。 / Fix the scheduler reading a module copy: launched as `python[w] wb_proxy.py` the main module is `__main__`, so a plain `import wb_proxy` inside `wb_scheduler` loads a second copy whose `LOG_STORE` is None and whose `restriction_hold_remaining()` is separate state — every scheduler log line was dropped (64k log rows, zero `[调度器]` entries, against 8 startup banners) and the realm-level pause gate was always 0, so it never took effect. Both now go through `_proxy_module()`, which prefers the running instance. Two assertions added.
- **修复签到把业务失败记成成功**:`ok: (code == 0 or code == 10001)` 无条件成功,而 10001 在本仓库不是「签到成功」专属码(活动补偿的「每人限领一次,您已领取过」、content rejected 都用它),上游还会在 `code=0` 下回「签到失败,已签到天数不足 7 天」——「请求处理中,请勿重复操作」这类明确没签成的回执因此被记成成功(实测一轮 27 个账号里 4 条,当天真签成 8 个)。现在按文案归 `checked` / `already` / `pending` / `failed` 四类,并且**只有 checked / already 才写 `lastCheckin`**:旧实现拿到 HTTP 200 就先写盘,于是当天 `can_checkin()` 恒为 False,9 点与 21 点两轮都不会重试,这次失败被永久放弃,面板还显示「今日已领」。新增 `tests/_test_checkin_state.py`(7 项)。 / Fix check-in recording business failures as success: `ok: (code == 0 or code == 10001)` treated everything as success, but 10001 is not the check-in success code here — the compensation path's "already claimed" and content rejection use it too, and upstream also returns code 0 with 「签到失败,已签到天数不足 7 天」. Receipts like 「请求处理中,请勿重复操作」 (still processing, do not repeat) were therefore recorded as successes (4 of 27 accounts in one measured round). Receipts are now classified as `checked` / `already` / `pending` / `failed`, and only `checked` / `already` write `lastCheckin`: the old code wrote it as soon as it saw HTTP 200, so `can_checkin()` stayed False for the rest of the day, the 09:00 and 21:00 rounds never retried, and the panel showed "claimed today". Covered by `tests/_test_checkin_state.py` (7 tests).
- **修复「一键签到」不按任务开关选号**:成长任务、猫猫旅行、连登管家与网页活跃都按 `task_active()` 选号(#521),只有签到路由漏了——不传 `uid` 时 `targets` 只按 `realm == "cn"` 过滤,`tasksEnabled=False` 的账号照样被打一遍上游,签到记录也写进只做 API 派号的账号。显式点名单个 `uid` 时仍允许执行,与其它手动入口一致。`tests/_test_task_participation.py` 新增 2 项断言(名单里排除、显式 uid 放行)。 / Fix "check in all" ignoring the task switch: growth tasks, cat travel, streak bonus and the intl daily activity all select accounts by `task_active()` (#521), but the check-in route was missed — without a `uid` its target list only filtered on `realm == "cn"`, so accounts with `tasksEnabled=False` were still hit upstream and got check-in records written into accounts that only do API dispatch. Naming a single `uid` still runs, matching the other manual entry points. Two assertions added to `tests/_test_task_participation.py`.
- **看板下拉框改为自绘(原生 select 的展开层无法美化)**:原生 `