把 CodeBuddy 账号变成 OpenAI 兼容 API 的多账号网关 · 自带 Web 管理面板
账号池轮转 · 冷却与熔断 · 会话粘性 · 定时签到 / 活跃 / 旅行 / 保活 · 成长任务自动化 · 联网搜索
快速开始 · 配置 · 面板 · API · FAQ · 下载
本仓库是 linguo2625469/workbuddy2api-panel 的独立增强分支(前身为该项目的 fork,现已迁至独立仓库),面向个人自用的多账号运维。 二进制、Docker 镜像与 Release 均以本仓库为准:
ghcr.io/jin66god/workbuddy2api。 本仓库自 2026-10-23 起为独立仓库(前身为jin66god/w2api-panel,已归档),后续版本与 Issue 均在此维护。
⚠️ 仅限管理本人授权的账号,禁止批量注册、转售额度、加壳或卡密分发。详见使用声明。
本项目是免费、开源、按「原样」提供的个人自用工具,用于管理自己的 CodeBuddy 账号(签到、保活、给本地工具提供 OpenAI 兼容入口)。
- 禁止批量注册 / 收购账号后对外提供付费 API、共享池、代充;
- 禁止二次加壳、捆绑卡密售卖,或以「公益服」「低价中转」名义收费分发;
- 任何商用、售卖行为与本项目及作者无关,后果由行为人自行承担;
- 本项目永远免费开源。你买到的任何「收费版」都是他人篡改的产物,且可能窃取
auths/中的明文凭证; - 滥用行为持续时,作者保留随时停止维护或关闭仓库的权利。
WorkBuddy2API 是一个自托管的 OpenAI 兼容反向代理网关:通过 OAuth 设备授权或静态 API Key 接入 CodeBuddy 账号,在网关侧完成 token 自动刷新、账号池调度与流量治理,对客户端只暴露标准接口,现有 SDK / 前端 / 工具零改造接入。
- 多账号共享,单号故障自动换号;
- 冷却、熔断、在途租约防止雪崩;
- 会话粘性保证多轮上下文不跳号;
- 面板可视化管理账号、任务、配置与日志。
⚠️ 非官方网关,仅限本人授权账号、本机 / 私有环境测试。合规边界见安全与合规。
| 能力 | 说明 |
|---|---|
| 🔑 OAuth 一键登录 | 支持国内站 / 国际站(codebuddy.ai)/ 旧桌面端(workbuddy.ai)三个登录站点,设备授权流程自动落盘凭证并热加载进池 |
| 🔄 多账号池 | 快过期积分优先(硬优先)+ 成本分层 + 加权随机 / 轮询 / 顺序耗尽,Top-5 候选 + 防惊群 |
| 🛡️ 熔断与冷却 | 429 软冷却 600s 起指数退避(封顶 soft_rate_max)、404 固定 60s 短冷却、402 硬冷却至次日 04:00、连续失败熔断、在途租约限流 |
| 🧲 会话粘性 | 同一会话(conversation_id)尽量绑定同一账号,TTL 滚动续期,失败自动解绑,可镜像 Redis 防重启丢失 |
| ⏰ 定时任务 | 签到(09/21 点,末尾自动跑连登管家:兑换已解锁档位 + 抽完抽奖次数)+ 活跃上报(10 点,点亮连登 / 解锁领养 + streak 自检)+ 猫猫旅行(09/21 点,独立排程)+ token 保活(22 点),四类独立开关 |
| ⚡ 流式 + 非流式 | 出站强制 stream:true;SSE 帧按规范白名单重建;非流式由本地聚合为单响应 |
| 🧠 推理模型兼容 | DeepSeek 思维链注入(thinking.type=enabled + 默认档)、reasoning_content 多轮回填、effort 档位自动降级 |
| 🔍 联网搜索 | POST /v1/search/messages 把上游 /agenttool/v1/search 包成 Anthropic Messages 兼容形态(DSH 等客户端的搜索 provider 零改动接入);复用账号池选号 / 在途租约 / token 刷新 / 错误冷却,出站请求头对齐官方客户端 |
| 💬 系统提示词体系 | passthrough(默认)/ append / custom 三模式;可选仅替换 Harness 身份首句;遇内容拦截可降级重试 |
| 🗑️ 指纹脱敏 | 出站请求体黑名单指纹字段清洗(可关闭):Claude Code / Codex 模板句、裸 11-128、DeepSeek Harness 品牌词。品牌词只改写 system / developer(实测 user 消息同一短语不触发拦截),用户输入 / assistant 回复 / 工具结果原样保留 |
| 📊 可观测 | 每请求一行表格日志(TTFB / token 速率 / uid);/healthz 带 service 身份标识可接负载均衡 / 宿主探活 |
| 💾 状态持久化 | 池状态本地原子落盘 + Upstash Redis 异步镜像(可选),重启择新恢复 |
| 🖥️ Web 管理面板 | 内嵌单页面板(明暗主题,七个视图):账号运维 / 用量与积分分析 / 模型档位条件查询 / 在线改配置(热生效)/ 运行日志(含调用来源 IP·UA 与时间区间,落盘持久化,重启/升级后保留,支持按今日/3 天/7 天/全部导出 ZIP)/ 积分任务,见 Web 管理面板 |
官方「成长计划」的 18 个成长任务中,17 个可在面板上一键纯 API 完成——无需安装官方客户端、无需人工交互,点一下「一键完成」即自动推进进度、等待异步计分落定并自动领奖。剩余任务展示操作指引。
| 任务 | 奖励 | 一键完成方式 |
|---|---|---|
first_buddy |
+300c +8e | 解锁上报 → 同意协议 → 领养第一只 Buddy |
create_canvas |
+300c +5e | 设计画布创建事件组(Ardot 遥测) |
chat_5 |
+100c | 对话活跃上报 ×5(自动补足差额) |
Model_chat_GLM5.2 |
+100c +5e | glm-5.2 真实对话一次(发一条短消息) |
RichMeow_Chat |
+100c +5e +UR Buddy | 桌面端对话事件链(6 事件,含成功回执) |
Buddy_App |
+100c +5e | Buddy 应用「发现→进入→授权」事件链 |
Buddy_App_QQ |
+50c +5e | 企鹅教师助手进入事件链(与上一条共用) |
automation_1 |
+100c +5e | 定时任务创建成功事件 |
Library_read |
+100c +5e | 资料库阅读点击(web 域上报) |
template_5 |
+100c +5e | 模板使用事件组 ×5 |
playbook_prompt |
+100c +5e | 灵感案例「做同款」发送事件 |
expert_5 |
+100c +5e | 真实专家召唤+使用链 ×5(专家市场拉真实专家 → 真实对话 → 使用事件) |
Expert_team_use_3 |
+100c +5e | 专家团召唤+使用链 ×3 |
Hp_Appearance |
+100c +5e | 主题设置 + 皮肤生效事件 |
Expert_lighthouse |
+100c +5e | 轻量云专家召唤+使用链(真实对话 requestId,可免费领一个月轻量服务器) |
WorkBuddy_Valorant |
+100c +5e | 「无畏契约拼豆专家」召唤+使用链(专家 ex_xQHHD1u5Jkua,截止 11-15) |
skill_1 |
+100c +5e | 真实对话 + 技能加载事件(skill_info) |
全新账号一键全做完 ≈ +1950 credits +78 能量,其中仅数个任务涉及真实对话(Model_chat_GLM5.2 一条、expert_5/Expert_team_use_3/skill_1 各数条 fast-model 短对话),其余全部为行为事件上报,零对话消耗。
| 任务 | 原因 |
|---|---|
Expert_Philanthropy |
需真实捐款(服务端领奖时校验捐赠回执,已实测无法绕过) |
任务计分走 /v2/report 行为上报,但不同任务认不同客户端指纹:CLI 指纹(www.codebuddy.cn)、桌面指纹(copilot.tencent.com + WorkBuddy/5.5.6 UA + workbuddy-desktop 事件族)、web 指纹(www.workbuddy.cn + x-client-platform: web)。网关为每类任务构造对应指纹的判据事件链(internal/upstream/desktop.go);专家类任务额外要求真实专家 id 与真实对话回执(internal/upstream/streak.go 之外的 expert 序列)。上报 200 ≠ 计分——面板在执行后轮询任务进度,达标即自动调用 Web 域领奖接口。
⚠️ 行为事件按天幂等:重复点「一键完成」不会重复扣资源,已达标的任务自动跳过。
「任务中心」视图把散落的任务能力收拢成一处:
- 全账号任务扫描:一键拉取每个账号的成长任务(未完成且可自动化的 19 项,含小程序口径的「校园日」与「小程序首对话」)+ 开学季待办,列表一目了然
- 执行队列:把待办按账号排队执行——账号内串行(与单任务/一键完成共用互斥锁),账号间可选并发(1-3);执行进度实时更新到每个条目
- 开学季独立状态卡:每账号 5 任务(分享/桌面/对话×3/专家/学生认证)的状态矩阵 + 剩余抽奖次数,一键触发全账号闭环
- 日志分频道:运行日志按「任务 / 对话 / 系统」三个频道筛选——对话流量再大,任务结果也不会被冲掉;日志条目带频道徽标与时间
- 历史日志保留:三频道同时写入
<state>/logs/panel-YYYY-MM-DD.jsonl,启动回填最近 500 行——更新面板或重启后历史日志不再消失(默认保留 7 天 / 50 MiB,可配置) - 日志导出:标题栏「导出」+ 范围选择(今日 / 近三天 / 近 7 天 / 全部),下载 ZIP(
panel-logs.ndjson+meta.json);导出前自动脱敏 token / api_key / Bearer / 手机号,单次上限 200 MiB
🎒 开学季活动(5/5 全自动)——活动期至 2026-09-24,已结束;逆向成果存档,点击展开
官方「AI 好 Buddy,开学有好礼」小程序活动的 5 个任务全部纯 API 自动完成(挂签到排程末尾,幂等):
| 任务 | 奖励(每日) | 判据(已逆向) |
|---|---|---|
| 分享活动 | +100c +1抽奖 | share-complete 直调即点亮 |
| 桌面端体验(单次) | +100c +1抽奖 | viewed 激活 + 真实 chat + 桌面六事件链 |
| 和 AI 对话 3 次 | +50c +1抽奖 | viewed 后 3 条 chat_request_send 埋点(无需真实会话) |
| 召唤开学季专家 | +50c +1抽奖 | viewed 后 mp 事件链(召唤×3 + 对话) |
| 学生认证 | +100c | 需微信学生真实认证,不做 |
抽奖次数自动全部抽完。期间逆向成果(cf-connect 加密通道、mp 云对话全链路)记录在 data/desktop-task-protocol.md §8。
同一活动在成长任务中心还有两条小程序口径任务(X-Client-Platform: miniprogram 专属下发,默认列表不可见,各 +100c+5e):
| 任务 | 判据(已逆向) |
|---|---|
school_season 校园日 |
mini chat_request_send + activityId=school_open_day_2026(无 activityId 不点亮;accept/claim 均要求 mp 头) |
Sequential_Tasks_1 小程序首对话 |
mini chat_request_send(无 activityId,服务端按 source=mini_program 指纹关联) |
任务中心扫描自动合并 mp 口径待办;accept 带登记回读验证(上游存在 200+OK 但未落账的形态,未生效自动重试一次)。
成长中心连登档位(连续登录 7/14/28 天)兑换后发放积分 / 能量 / 补签卡 / 抽奖次数,抽奖次数只能从兑换获得。网关把它挂在每日签到排程末尾自动跑闭环(见定时任务):档位解锁当天自动兑换、有抽奖次数自动抽完,全程无需人工盯。
本分支在上游 panel 的基础上持续吸收社区 PR 并自研修复,以下为当前已落地能力(均经 go build / go vet / go test ./... 与真实多账号环境验证)。
| 能力 | 说明 |
|---|---|
| Web 管理面板 | go:embed 单文件,明暗主题、七视图:账号池 / 用量与积分 / 模型档位 / 在线配置 / 运行日志 / 任务中心 / 补偿 |
| 在线改配置 | 保存即热生效;装配期字段提示需重启。深合并 + 原子替换,保留未知键 |
| 手机端适配 | ≤760px 抽屉侧栏 + 宽表卡片化 + 窄屏时序图 |
| 账号备注 | 账号池 / 用量明细 / 积分扣除三表共享映射(本机 localStorage) |
| 积分历史 | 余额观察者逐次比对留痕,原子落盘、上限 2000 条 |
| 到期提醒 | 首页快照与「检查」同口径:按自然日聚合批次,DeductionEndTime 优先,标注后台刷新节奏 |
| 积分到期折叠 | 折叠状态内存兜底(localStorage 抛 SecurityError 时仍可用)+ 静态资源 no-cache + ?v=<版本> 破 CDN 缓存 |
| 能力 | 说明 |
|---|---|
| 静态 API Key 账号 | ck_ / pt_ 直接粘贴,无需浏览器 OAuth;额度走企业口径,成长任务自动隐藏 |
| 联网搜索 | POST /v1/search/messages,Anthropic Messages 兼容,DSH 等客户端零改动接入 |
| 原生 tool_calls 还原 | 上游标记文本在客户端未声明 tools 时还原为结构化 tool_calls(流式 / 非流式) |
| 推理模型兼容 | DeepSeek 思维链注入、reasoning_content 回填、effort 档位降级、reasoning_text 剥离 |
| 渠道校验形态 | 首条非 system 自动补 system(developer 就地归一)+ 会话头三件套缺失兜底,从源头避免 11-128 first message is not system prompt / conversation id missing |
| 出站指纹 | UA 按 realm 分档(CN WorkBuddy/5.8.3、global workbuddy-ai/5.8.4,CLI 2.161.0),global chat 补 X-Private-Data,对齐官方客户端抓包 |
| 缓存命中修复 | 补齐 body 级 conversation_id,修前缀缓存整段未命中 |
| 能力 | 说明 |
|---|---|
| 地区独立调度 | pool.strategy.domestic/international:加权随机 / 轮询 / 顺序耗尽,可配账号顺序,热生效 |
| 模型黑名单 | models.hidden:列表过滤 + 点名调用 400,热生效 |
| 11158 / 11102 区分 | 账号类型无权限与模型不存在分别避让;模型锁按 realm 隔离;全池无权限返回 400 而非 503 |
| 6021 queue.full | 渠道级容量满载:不罚号、不轮转,末端 503 upstream_busy |
| 14003 model busy | 模型后端间歇繁忙:只停「账号 × 该模型」10s(不写账号级冷却),全池被挡且剩余 ≤10s 时请求内等一次再重试;超时 503 model_busy + Retry-After |
| 11-128 / 11140 分流 | 11-128 通道校验失败是请求级错误:不罚号、不冷却、不轮转,原样透传 400;11140 账号限制连续 3 次才禁用并提示在 WorkBuddy 申诉 |
| refresh 单飞 | 同账号并发 refresh 复用首个结果,避免 RT 反重放撞 12153 掉登录 |
| 出站网络 | dns_servers 指定解析器;HTTP(S)_PROXY 环境变量自动生效;cn.chat_base 支持海外出口改用 www.workbuddy.cn |
| 错误处置 | 12153 连续计数才禁用、404 短冷却、402 硬冷却至次日 04:00、连败降权、积分保底 |
| 能力 | 说明 |
|---|---|
| 一键完成 18/19 | 纯 API 推进任务进度、等待异步计分并自动领奖 |
| 任务补领清扫 | 每 15 分钟幂等 accept / claim「已达标未领」,不跑行为链路,可关 |
| 一键完成补领 | 遇已达标未领先补领再跳过 |
| 补偿可观测 | 先查后领、截止倒计时、逐号 / 全量领取 |
| 连登兑换 | 签到排程末尾自动兑换已解锁档位并抽完奖 |
flowchart LR
Client["客户端 / SDK\nOpenAI 兼容请求"] --> H
subgraph GWI["WorkBuddy2API 网关 :7863"]
H["HTTP Handler\n鉴权 · 请求体上限 · 提示词改写 · 轮转"] --> P
H --> S
P["账号池\n快过期加权 · 成本分层 · 熔断 · 冷却 · 租约"] --> U
S["会话粘性路由"] -.绑定镜像.-> REDIS
T["定时调度\n签到 09/21 · 旅行 09/21 · 活跃 10 · 保活 22"] --> P
U["上游 Client\nChatHTTP 流式 · 短 RPC"]
end
P -. "读凭证 (0600)" .-> AUTH[("auths/*.json")]
P -. "状态镜像" .-> REDIS[("Upstash Redis\n可选")]
U -->|"chat/completions (SSE)"| CB["CodeBuddy\ncopilot.tencent.com"]
U -->|"billing / auth / growth"| CB
上游请求在出站前经历统一的改写管线(internal/upstream/payload.go):强制 stream:true、developer 角色归一、tool_choice 归一、image_url 字符串兼容为 OpenAI 对象形态、DeepSeek 思维链注入、reasoning_effort 档位降级、reasoning_content 回填、指纹脱敏。
- Docker + Docker Compose(服务端部署方式,镜像内已含低权限用户与全部工具脚本)——或
- Windows / macOS / Linux 直接跑单文件二进制(无需 Docker,见下方「Windows 单文件运行」)
- 一个或多个已注册的 CodeBuddy 账号,用于 OAuth 登录
- 宿主机 Go ≥ 1.27(仅从源码构建时需要)
CI 会自动构建多架构镜像(amd64 / arm64)并发布到 GHCR,git clone 之外的部署路径:
# 1. 准备配置与数据目录
mkdir -p auths data && cp config.example.json config.json
# 建议编辑 config.json 设置 api_key(或留空由程序自动生成随机密钥)
# 2. 拉取并运行
docker run -d --name workbuddy2api \
-p 7863:7863 -e TZ=Asia/Shanghai \
-v ./auths:/app/auths -v ./data:/app/data -v ./config.json:/app/config.json \
ghcr.io/jin66god/workbuddy2api:latest
# 3. 健康检查(无可用账号时返回 503)
curl -s http://localhost:7863/healthz首次发布后须将包设为公开:GitHub 仓库页 → Packages →
workbuddy2api→ Package settings → Change visibility → Public,否则拉取需要docker login ghcr.io。镜像 tag 规则:
main分支推送latest/main/sha-xxxxxx;打v*tag 额外发布X.Y.Z/X.Y/X(major 别名)语义化版本,三者指向同一 multi-arch manifest (linux/amd64 + linux/arm64),由 CI 自动生成。PR 仅构建验证、不推送。
docker-compose.yml 默认直接拉取本仓库 CI 发布的镜像,服务器上不需要 Go 环境、也不需要本地构建:
# 1. 克隆(只需要 compose 文件与配置模板)
git clone https://github.com/jin66god/workbuddy2api-panel.git
cd workbuddy2api-panel
# 2. 准备配置(compose 挂载此文件,缺失会导致容器启动失败)
cp config.example.json config.json
# 建议编辑 config.json 设置 api_key(或留空由程序自动生成随机密钥)
# 3. 拉取镜像并启动(compose 会按宿主架构拉 amd64 / arm64 对应 manifest)
docker compose pull
docker compose up -d
# 4. 健康检查(无可用账号时返回 503)
curl -s http://localhost:7863/healthz
# {"healthy":0,"total":0,"service":"workbuddy2api"}生产环境建议固定版本。镜像只在打 tag 时发布,latest 不会随 main 漂移:
# 跟随最新发布
WB2API_TAG=latest docker compose up -d
# 冻结到某一版:把 <版本号> 换成 Releases 里的 tag
WB2API_TAG=<版本号> docker compose up -d
# 也可写入 .env 文件,避免每次带环境变量从源码构建(改了代码或要用未发布版本时):
docker compose build --no-cache
docker compose up -d镜像可见性为 Private 时须先登录:
echo $GITHUB_TOKEN | docker login ghcr.io -u <GitHub用户名> --password-stdin(token 需read:packages权限);仓库页 → Packages → Package settings 可改为 Public 免登录拉取。
启动后打开 http://localhost:7863/panel/,用面板「添加账号」完成登录(见下节)。
常用运维命令:
docker compose logs -f # 跟踪日志
docker compose restart # 重启
docker compose down # 停止并移除容器(数据在 ./auths 与 ./data,不受影响)# 1) 下载 Release 中的 wb2api.exe,或从源码构建
go build -trimpath -ldflags="-s -w" -o wb2api.exe ./cmd/server
# 2) 直接运行:首次启动自动生成 config.json(含随机 api_key,日志打印一次)
.\wb2api.exe -config config.json
# 3) 浏览器打开面板添加账号
# http://127.0.0.1:7863/panel/exe 为单文件自包含(前端资源已 embed 进二进制),拷到任意 Windows 机器即可运行,只需保证 auths/(凭证)与 data/(状态)目录可写。
go build ./...
go vet ./...
go test ./... # 完整测试套件
go run ./cmd/server -config config.json构建全部二进制:
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o wb2api ./cmd/server
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o signin_bin ./cmd/signin
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o login ./cmd/login
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o credit ./cmd/credit方式 A:Web 面板(推荐,各平台通用,免命令行)
打开 http://127.0.0.1:7863/panel/,点右上角「添加账号」:先选登录站点,面板展示授权链接 → 浏览器完成登录 → 自动检测并落盘凭证 → 热加载进池(无需重启),顺带完成首次签到(仅国内站)。
三个站点的区别:
| 站点 | 上游主机 | 说明 |
|---|---|---|
cn 国内版 |
copilot.tencent.com / codebuddy.cn |
默认;签到、成长任务、连登体系均在此站点 |
intl 国际站 |
www.codebuddy.ai |
官方 CLI 国际站;登录后自动注册地区、激活、领试用额度,不发起 CN 签到 |
workbuddy 国际旧版 |
www.workbuddy.ai |
旧桌面端国际版;与 intl 同后端但 token 只在签发域可用 |
网关按凭据里的
domain自动选择国际域主机(*.codebuddy.ai→ codebuddy.ai,*.workbuddy.ai→ workbuddy.ai),不需要手工配global.chat_base。已有账号的凭据不受影响。
方式 B:命令行脚本(仅 Linux / macOS,依赖 bash + python3)
./login.sh # 默认国内站
./login.sh --site=intl # 官方国际站(www.codebuddy.ai)
./login.sh --site=workbuddy # 旧桌面端国际版(www.workbuddy.ai)
./login.sh --realm=global # 兼容旧写法,等价于 --site=workbuddy
# 按提示在浏览器打开授权链接 → 回到终端确认 → 凭证落盘 auths/workbuddy-<uid>.jsonlogin.sh 内置授权 URL 获取 + 浏览器登录 + token 轮询 + 首次签到 + 凭证落盘 + 容器重启,全程无 PKCE(state 由服务端签发)。国际站/旧版自动跳过 CN 签到;state 文件记站点,url 与 poll 必须是同一站点(防换站)。账号池在容器启动时用 auths/ 目录自动对齐,新增凭证文件即自动发现。
Windows 用户请用方式 A(或 WSL);
login.sh需要 python3。
# 模型列表
curl -s http://localhost:7863/v1/models -H "Authorization: Bearer your-api-key"
# 账号状态(汇总 + 每账号详情,disabled 账号透出 disabled_reason)
curl -s http://localhost:7863/status -H "Authorization: Bearer your-api-key"
# 流式聊天
curl -sN http://localhost:7863/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"stream":true}'
# 非流式聊天(本地聚合)
curl -s http://localhost:7863/v1/chat/completions \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"stream":false}'
# 联网搜索(Anthropic Messages 兼容形态,走上游 /agenttool/v1/search)
curl -s http://localhost:7863/v1/search/messages \
-H "Authorization: Bearer your-api-key" \
-H "Content-Type: application/json" \
-d '{"model":"deepseek-v4-flash","tools":[{"type":"web_search_20250305","name":"web_search"}],
"messages":[{"role":"user","content":"Perform a web search for the query: OpenAI 最新模型"}]}'config.example.json 是配置项最完整的参考:每个字段、默认值与结构都能在其中找到,示例值一律是 test_key 之类占位符,不含任何真实密钥。下表为字段含义速查。
| 字段 | 默认 | 说明 |
|---|---|---|
listen |
:7863 |
HTTP 监听地址 |
api_key |
空 | 网关鉴权密钥;空 = 不鉴权直接放行(公网必须设置) |
auth_dir |
./auths |
账号凭证目录 |
state_file |
./data/state.json |
账号池状态持久化文件 |
dns_servers |
空 | 出站解析用的 DNS 服务器(数组,按顺序试,都用不上才报错);空 = 系统解析器。/etc/resolv.conf 不存在或不可用的环境(Android、精简容器)必须显式配,否则 Go 解析器会退回 127.0.0.1:53,查任何域名都变成"连本机 53 端口"(dial tcp: lookup ... on [::1]:53: connection refused)。只换 Transport.DialContext,不动超时与连接池 |
server.read_timeout |
300s |
入站请求读取(含 body 上传)总时长上限;大上下文/文件块经反代转发超时会 400 read body: i/o timeout;0 = 不限制;改动需重启(#100) |
panel.package_detail_limit |
5 |
积分构成页单账号默认展示的最早到期包数;其余未用完包与已用完包聚合折叠 |
logging.request_archive_enabled |
true |
请求元数据 JSONL 归档开关;不记录提示词、响应正文或 Authorization |
logging.request_retention_days |
7 |
请求归档保留天数;超期文件在启动和周期清理时删除 |
logging.request_archive_max_mb |
100 |
请求归档总容量上限(MiB);超限优先删除最旧文件 |
logging.panel_log_persist_enabled |
true |
运行日志落盘开关;关闭 = 仅保留内存 500 行,重启清空 |
logging.panel_log_retention_days |
7 |
运行日志保留天数;超期文件在启动时清理 |
logging.panel_log_max_mb |
50 |
运行日志落盘总量上限(MiB);超限优先删除最旧文件 |
logging.log_export_max_mb |
200 |
运行日志单次导出上限(MiB);超限返回 413 |
cooldown.soft_rate |
600s |
软限流(429 / 限流文案)冷却基数;同一账号连续触发按 2 倍指数退避 |
cooldown.soft_rate_max |
2h |
软冷却指数退避封顶 |
schedule.checkin_hours |
[9, 21] |
每日本地时区整点签到 + 余额查询解冻。空数组 / null = 未配置回落默认(不是禁用) |
schedule.account_spread_minutes |
0 |
账号级随机摊开窗口(分钟):>0 时每个账号在「时点 + [0, 窗口)」内按 hash(uid|任务|日期) 的确定性偏移执行,降低多号同刻的时间相关性;0 = 关闭,整点按顺序执行。窗口应小于相邻时点的间隔(如 9 点签到窗口 60 分钟会与 10 点活跃上报重叠,重叠部分由任务重入保护跳过) |
schedule.account_spread_min_gap_seconds |
0 |
摊开后的最小账号间隔(秒)。只做冲突兜底:先随机、落在该间隔内的后者才往后推,不会变成等距;0 = 不设下限 |
schedule.travel_hours |
[9, 21] |
每日本地时区整点推进猫猫旅行状态机(领养 / 派出 / 领奖) |
schedule.activity_hours |
[10] |
每日本地时区整点对话活跃上报(点亮连登 + 解锁 first_buddy) |
schedule.keepalive_hours |
[22] |
每日本地时区整点触发周期 token 保活;仅刷新临期窗口内的凭证 |
schedule.keepalive_refresh_window |
168h |
周期保活的临期窗口:剩余有效期不超过该时长才刷新;已过期/无 expiry 仍刷新;面板「全部保活」手动入口仍全量 |
schedule.blackcat_hours |
[23] |
每日本地时区整点夜猫子补足(23:00–08:00 计数窗口) |
schedule.checkin_enabled |
true |
签到总开关;false 真正关闭 |
schedule.travel_enabled |
true |
猫猫旅行总开关(独立于签到) |
schedule.activity_enabled |
true |
活跃上报总开关 |
schedule.keepalive_enabled |
true |
token 保活总开关 |
schedule.blackcat_enabled |
true |
夜猫子总开关 |
schedule.include_disabled_in_tasks |
false |
让保号类四任务(签到 / 活跃上报 / token 保活 / 余额刷新)对已禁用账号也执行——「禁用」只关选号,不停保号。false(默认)保持「禁用的跳过」 |
upstream.timeout_seconds |
120 |
短 RPC(刷新 / 签到 / 余额 / 模型列表)总时长上限 |
upstream.header_timeout_seconds |
回落 timeout_seconds |
聊天首字节前(响应头)上限 |
upstream.idle_timeout_seconds |
300 |
聊天流中空闲上限(活跃续命,静默断流) |
upstream.user_agent |
空 | 出站 User-Agent 覆盖(空 = 按账号身份自动:CN WorkBuddy/5.8.3 · global workbuddy-ai/5.8.4,CLI 段 2.161.0)。官网「使用端」列按出站 UA 服务端归因 |
features.sanitize_blacklist_fingerprints |
true |
出站请求体黑名单指纹脱敏 |
models.hidden |
[] |
模型黑名单:不对外暴露的模型。裸名=两域都藏;"cn:xxx"/"global:xxx"=只藏该域。GET /v1/models 列表过滤 + POST /v1/chat/completions 点名拒绝(400 model_unavailable)。面板保存后热生效 |
prompt.mode |
passthrough |
系统提示词模式:custom = 网关用自有提示词替换客户端 system;append = 开头连续 system/developer 块后插网关提示词(既有消息逐字不动);passthrough = 透传客户端原始 system(降级重试仍切中性提示词) |
prompt.file |
空 | 提示词文件路径;空 = 内置默认(约 2KB);路径非空但不可读 → 启动报错 |
prompt.identity_override_enabled |
false |
勾选后仅在 passthrough / append 模式覆盖客户端 system/developer 开头的 Harness 身份首句 |
prompt.identity_override_text |
You are CodeBuddy Code. |
新的身份首句,可在面板输入;空值恢复默认。身份替换配置修改后需重启 |
upstash.url / upstash.token |
空 | 空 = 纯内存模式(Noop 降级,功能照常) |
pool.max_in_flight |
3 |
单账号最大在途请求数(0 = 不限) |
pool.max_in_flight_global |
2 |
global 域单账号在途上限(国际版 WAF 风控更紧,压低并发) |
pool.degrade_threshold |
5 |
连败降权阈值:未知错误(ErrClient/传输层)连败 N 次临时出池 |
pool.degrade_cooldown / pool.degrade_cooldown_max |
10m / 2h |
连败降权时长与上限钳制 |
pool.cost_explore_interval |
30m |
costTier 条件探索窗口:免费层垄断且存在未知号时,每窗口把一个真实请求搭车改道给未知号(零新增上游请求;成功即毕业,失败走既有错误策略)。0 = 关停 |
pool.credit_floor |
100 |
积分保底:账号余额低于该值时,对实测收费模型(tier 2,账本 6h 内有效观测)不再参与选号——防止收费模型把余额打穿、连免费模型都 402 冷却到次日签到(最坏约 11.5 小时不可用)。tier 0(实测免费)/ tier 1(无观测)不受限:保底保的是「留余额给免费模型用」,且 tier 1 若拦会让账本过期 / 重启清零的触底号死锁在「学不回来」。含会话粘性路径(粘性号触底则解绑换号)。全池触底且全 tier 2 时选号返回空(网关回 503),不放行。签到回血越过 floor 即刻自动恢复。0 = 关闭 |
pool.breaker_threshold |
3 |
连续失败触发熔断阈值 |
pool.breaker_cooldown |
30m |
熔断基础退避时长 |
pool.breaker_cooldown_max |
6h |
熔断指数退避封顶 |
pool.idle_weight_per_hour |
0.5 |
闲置补偿:每小时未使用 +0.5 权重 |
pool.idle_weight_max |
5.0 |
闲置补偿权重封顶 |
pool.strategy.domestic |
weighted_random |
国内版账号池调度:weighted_random / round_robin / sequential_exhaust,支持热更新 |
pool.strategy.international |
weighted_random |
国际版账号池独立调度策略,同上;不改变既有地区路由 |
pool.account_order.domestic / international |
[] |
对应地区的 UID 优先顺序;未列出账号按 UID 排序追加;只用于轮询和顺序耗尽 |
pool.prefer_expiring |
true |
快过期积分优先(硬优先,先于成本分层):只要池内还有「expiring_soon 窗口内有正余额批次」的可用账号,本轮调度就只在它们中间选——即使它们是收费层、池里有免费号,也先清快过期的(省下的积分到期同样归零);三种策略均生效。快过期账号清空后自动回到全量。健康 / 积分保底 / 在途过滤仍在最前:触底号不接收费模型 |
pool.expiring_soon |
168h |
快过期加权窗口:仅窗口内仍有有效批次的账号命中上述 ×3;窗口开大 → 命中账号变多、偏好被稀释;留空或 0 关闭 |
session_sticky.enabled |
true |
会话粘性路由开关 |
session_sticky.ttl |
30m |
会话绑定 TTL(滚动续期) |
session_sticky.gc_interval |
5m |
过期绑定 GC 周期 |
国内版(cn)与国际版(global)可独立选择策略,面板「账号池与流量治理」保存后热生效。
国内版(cn)与国际版(global)可独立选择策略,例如:
{
"pool": {
"strategy": {
"domestic": "weighted_random",
"international": "sequential_exhaust"
},
"account_order": {
"international": ["uid1", "uid2", "uid3"]
}
}
}- 加权随机:在快过期优先生效后的候选内做成本分层 + Top5 加权抽签(余额比例 + 闲置补偿)。旧配置缺少策略时,两域均使用此默认值。
- 轮询:在当前最优成本层的全部可用账号之间按顺序轮换,不截断 Top5。跳过禁用、暂停、对应模型冷却、积分保底拦截及在途占满的账号。
- 顺序耗尽:持续使用当前账号,直到对应模型冷却、账号不可用、积分保底拦截或成本层优先级变化,再向后切换。恢复的早序账号不会抢占仍可用的当前账号,后续轮转可再次使用。请求级重试和在途占满可暂用其他账号,但不推进主账号;普通失败仍按现有熔断规则处理。
两种有序策略仍遵守会话粘性和成本探索,轮询指针 / 顺序耗尽主账号按 (realm, model) 独立维护,通过既有本地状态与 Redis 快照机制恢复。全部候选冷却时返回无可用账号,等待既有恢复机制,不提前试探冷却账号;加权随机继续保留原有全冷却兜底。选号和指针更新持池锁,在途租约仍由既有 Acquire/Release 保证。
Web 面板「账号池与流量治理」可分别配置策略和账号顺序,保存后热生效。完整优先级:会话粘性 → 健康 / 积分保底 / 在途 → 快过期积分(勾选时,先于成本分层) → 模型成本分层 → 策略内部次序。快过期账号清空后自动回到全量;闲置补偿仅影响加权随机。无地区上下文的旧 Pool 入口保留原行为;策略不会改变路由或自动跨地区借号。
本功能不假定国际版免费模型已采用五小时限额;限流及恢复时间继续依据现有上游识别逻辑处理,不硬编码五小时。
| 字段 | 作用对象 | 默认 | 行为 |
|---|---|---|---|
timeout_seconds |
短 RPC(token 刷新 / 签到 / 余额 / 模型列表) | 120 |
总时长硬上限,到期报错走换号 / 熔断 |
header_timeout_seconds |
聊天 SSE 首字节前 | 120 |
由 Transport.ResponseHeaderTimeout 约束;超时 = 换号重发 |
idle_timeout_seconds |
聊天 SSE 流中空闲 | 300 |
活跃吐数据续命不掐;静默超时才断流释放租约 |
聊天流(stream true / false 均同)没有总时长上限:聊天使用 Timeout=0 的专用 client,长思考 / 长输出不会被掐断。
加载顺序:JSON 文件 → WB2A_* 环境变量(变量非空才覆盖):
WB2A_LISTEN · WB2A_API_KEY · WB2A_AUTH_DIR · WB2A_STATE_FILE · WB2A_DNS(逗号分隔多个) · WB2A_MAX_BODY_MB · WB2A_SOFT_RATE(duration) · WB2A_SOFT_RATE_MAX(duration) · WB2A_TIMEOUT_SECONDS · WB2A_HEADER_TIMEOUT_SECONDS · WB2A_IDLE_TIMEOUT_SECONDS · WB2A_USER_AGENT · WB2A_SANITIZE_FINGERPRINTS(bool) · WB2A_PROMPT_MODE · WB2A_PROMPT_FILE
客户端(Claude Code / Codex 等 CLI)会在 system prompt 注入固定模板句,上游内容审核按逐字精确匹配误杀合法流量(HTTP 400 + 审核文案)。网关提供两层防护,互不替代:
- 提示词体系(解决 system / developer 来源的误报):由
prompt.mode控制 - 指纹脱敏(出站前统一清洗):由
features.sanitize_blacklist_fingerprints控制(默认开启)。内置规则覆盖 Claude Code / Codex 模板句、裸11-128错误码,以及 DeepSeek Harness:身份首句You are CodeBuddy Code改写成新版 CodeBuddy 首句(保留原标点与后续内容);裸品牌词DeepSeek Harness只对system/developer改写成DeepSeekRuntime,user/assistant/tool里的同名文本原样保留(上游渠道校验只看系统提示词,改写用户数据没有必要)
| 模式 | 语义 |
|---|---|
custom |
出站前用网关自有提示词替换客户端 system / developer 消息(删除全部 system / developer,头部插入单条 system);user / assistant / tool 消息逐字不动 |
append |
开头连续 system / developer 块之后插入一条网关自有 system,既有消息(含客户端项目规范/工具约定)逐字不动——两者并用;降级期退化为 replace(带指纹原文重试只会确定性再撞 400) |
passthrough(默认) |
透传客户端原始 system(若勾选身份首句替换则只改首句) |
可选勾选面板中的「替换 Harness 身份首句」(prompt.identity_override_enabled=true),将客户端开头的 system/developer 文本中以 You are 起始的身份首句(例如 You are an AI agent powered by DeepSeek Harness.)替换为可编辑的 prompt.identity_override_text,默认 You are CodeBuddy Code.。只影响 passthrough 和 append,首句之后的项目规范、工具规则和其他消息均保留;custom 不使用此选项,降级期仍使用原有的 Degraded 整体替换。配置保存后需重启。
内置默认提示词约 2KB(internal/prompt/defaultprompt.md,嵌入二进制)。prompt.file 指向自定义提示词文件(自定义人格 / 人设)即整体替换内置默认;留空 = 内置默认,路径非空但不可读 → 启动报错(fail fast,不会静默回落到内置默认)。
passthrough 模式请求被上游内容策略拦截(HTTP 400 + blocked by security policy / unapproved channel / illegal api invocation 文案)时,判定为 system 指纹误报:同请求内换 Degraded 中性提示词重试一次;第二次仍被拦(用户内容本身触发审核)→ 走既有错误路径返回客户端,并如实报给调用方。
- 触发降级后持续到次日 00:00 CST(Asia/Shanghai)重置;降级期内
passthrough请求直达中性提示词,不再先撞 400 - 降级状态是进程内存态,重启清零
- 内容问题非账号问题:
ErrContentBlocked不罚账号(无冷却 / 熔断 / 计错),由网关降级重试消化
上游(DeepSeek 系)的工具调用原生语法不是 OpenAI 的 JSON tool_calls,而是一段带全角竖线(U+FF5C)分隔符的标记文本:
<PIPE>DSML<PIPE> calls>
<PIPE>DSML<PIPE> invoke name="exec_command">
<PIPE>DSML<PIPE> parameter name="cmd" string="true">ls -la<PIPE>DSML<PIPE> parameter>
</PIPE>DSML<PIPE> invoke>
</PIPE>DSML<PIPE> calls>
(上面用 <PIPE> 占位,真实分隔符是两个全角竖线。)
上游只在请求声明了 tools 时才把这段标记解析成结构化 tool_calls。一旦某次请求没带上 tools——实测这在链路上是常态(协议转换丢字段、一次性 exec 探测、auto-review 复核回合等)——模型仍然想调工具,于是标记以纯文本落进 content。下游没有任何一层认识它:Codex 只执行 Responses API 的结构化 function_call 条目,于是把它当普通助手正文写进会话历史,回合直接 task_complete、工具一次都没跑。
网关在出站响应里做最后一道还原(internal/upstream/dsml.go),判定分两级:
| 工具名单状态 | 判定 |
|---|---|
| 非空 | 严格:块内每个工具名都必须在名单里 |
为空(tools 丢失,实测常态) |
弱:块必须完美闭合,且工具名形如合法标识符(^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,63}$) |
名单有两个来源,二者会分别缺失,所以都要:本请求声明的工具(tools[].function.name / tools[].name / functions[].name)与会话历史里出现过的工具名(messages[].tool_calls[].function.name)。
还原还受三条硬性前提约束:本回合上游没有给出任何结构化 tool_calls(不覆盖真实调用)、块内除 invoke/parameter 与空白外不夹带正文、非流式额外要求 finish_reason 为 stop。任何一条不满足即原文逐字节透出——未闭合、参数畸形、超长(>256KB)一律回吐,修复层绝不吞字节。
命中与否各记一行日志,便于排障时区分「没识别到」与「识别到但判定不通过」:
INFO: [server] stream ...: repaired N native tool call(s) from assistant text (tool names known=K)
WARN: [server] stream ...: saw M native tool call block(s) but repaired none (tool names known=K)
上游错误由 Classify 统一分类(判定优先级:余额耗尽 → session 失效 → 限流文案 → 状态码兜底),账号处置如下:
| 分类 | 触发条件 | 账号处置 | 恢复 |
|---|---|---|---|
| 余额不足 | HTTP 402 / body 含余额关键词 | 硬冷却到次日 04:00(本地时区) | 签到(09/21 点)余额恢复自动解冻 |
| 频控 | HTTP 429 / 限流文案(不限状态码) | 软冷却 soft_rate(600s 起,连续触发指数退避,封顶 soft_rate_max)。code 6004(模型级)带「将在 … 重置」时冷却到上游重置墙钟并豁免切模型(见常见问题) |
到期自动恢复 / 成功清零退避 |
| Session 失效 | body 含 Offline user session not found / 12153 |
连续 3 次才永久禁用(一次 12153 多为临时抖动:网络 / 闪断 / refresh 竞态);刷新成功 / 任意成功 / 手工复活清计数 | 人工重新登录(login.sh)或 ReviveDisabled 复活 |
| Token 被网关拒绝 | HTTP 401 且无业务信封(APISIX 401 Authorization Required HTML) |
先强制 refresh 一次并用新 token 重试同一账号;刷新失败按连续 3 次会话失效计数,单次 401 不禁用。搜索端点只换号不计数 | refresh 成功即自愈;连续失败需人工重新登录 |
| 上游 404 | HTTP 404 | 软冷却固定 60s(不随 soft_rate、不单独退避) |
到期自动恢复 |
| 服务端错误 | HTTP ≥500 | 喂连续失败计数,达阈值熔断 | 熔断到期 / 成功清零 |
| 请求体解析失败 | HTTP 400 + Unmarshal chat params failed / code 11101 |
不罚账号,但仍轮转(客户端畸形 JSON,换号照样 400) | 即时 |
| 内容拦截 | HTTP 400 + 审核文案 | 不罚账号,passthrough 模式走降级重试 |
即时 |
| 客户端错误 | 其余 4xx / 业务 code≠0 |
不处罚,换号重试 | 即时 |
请求体解析失败(11101)与内容拦截一样不罚账号:问题在请求内容而非账号健康。网关不做请求体截断与预拦截,11101 均为客户端发来的畸形 JSON。
熔断器:所有冷却入口与 5xx 共用唯一连续失败计数器 fails;累计达 breaker_threshold(默认 3)触发熔断,退避 breaker_cooldown × 2^retryCount,封顶 6h;成功清零。
软冷却指数退避(与熔断器并存的第二条升级线):软限流的冷却时长本身也按连续次数退避——同一账号连续触发软冷却时 soft_rate × 2^(连续次数-1),封顶 soft_rate_max。计数 soft_streak 独立于熔断器的 fails,只在成功或签到解冻时清零,随 state.json 持久化。
-
过滤:禁用 / 冷却 / 熔断 / 在途占满账号不参与
-
按模型实测成本分层,只保留当前最优层
-
默认开启快过期积分优先(硬优先,三种策略通用):
- 在模型成本最优层内,
expiring_soon窗口内仍有有效快过期批次的账号,选号权重 ×3(expiringVirtualSlots虚拟实例展开,会话粘性路径同口径)。 - 不按到期时间排序、与批次金额无关:14 天后到期与 3 天后到期、1 分与几千分,只要在窗口内待遇相同。
- 该偏好弱于会话粘性(绑定号健康则直接使用)与模型成本分层(按实测成本硬过滤);已过期、零余额、无有效到期时间的账号不进入加权集。
- 在模型成本最优层内,
-
同一候选池内加权随机(快过期命中账号在上面的基础上 ×3):
weight = credits 比例 ×10 + idleWeightcredits 比例= 该号积分 / 候选集最大积分idleWeight=min(闲置小时 × idle_weight_per_hour, idle_weight_max),从未使用给满分
-
防惊群:跳过 100ms 内刚被选中的账号;全冷却时从非禁用、非余额耗尽的软冷却 / 熔断账号中选最早到期者顶班
同一会话尽量复用同一账号,多轮对话不跳号:
- 会话键提取顺序:
metadata.conversation_id→metadata.conversationId→metadata.user_id→ 顶层conversation_id→ 顶层conversationId(snake_case 优先于 camelCase) - TTL 滚动续期(默认 30m),GC 周期 5m;绑定可镜像到 Redis(7 天 TTL)防重启丢失
- 请求失败自动解绑;成功后绑定跟随最终成功账号
五类任务各自独立排程、各有开关,互不影响。容器时区由 TZ 控制(compose 默认 Asia/Shanghai)。
| 任务 | 开关(默认 true) | 时刻(默认) | 行为 |
|---|---|---|---|
| 签到 | schedule.checkin_enabled |
checkin_hours [9, 21] 整点 |
签到 + 余额查询;余额恢复则解冻冷却账号。末尾追加连登管家(见下) |
| 活跃上报 | schedule.activity_enabled |
activity_hours [10] 整点 |
对话活跃上报(chat_request_send 事件,必须含 userId);点亮连登 + 解锁 first_buddy;每号每天 1 次 |
| 猫猫旅行 | schedule.travel_enabled |
travel_hours [9, 21] 整点 |
独立排程:无猫领养 / idle 派出 / arrived 领奖 |
| 保活 | schedule.keepalive_enabled |
keepalive_hours [22] 整点 |
刷新临期窗口(默认 7 天)内的 token;手动「全部保活」全量;session 失效连续 3 次才自动禁用 |
| 夜猫子 | schedule.blackcat_enabled |
blackcat_hours [23] 整点 |
先查任务进度再决定:black_cat 未达标才在 23:00–08:00 计数窗口内补足 glm-5.2 短对话(每天 1 次累计 3 天,漏跑次日窗口自动补) |
缺省 false:禁用账号被上述签到 / 活跃上报 / 保活 / 余额刷新四任务跳过,与选号过滤一致。
面板「禁用」的语义是「不再参与选号」,但这四类任务此前会一并跳过禁用号——被禁用的账号因此拿不到签到积分、不续 token、余额也不再刷新;而 ReenableIfCredits 明确不复活 disabled 账号,等于签到这条唯一的自动回血路径也断了,只能人工点「解冻」。
如果采用「一次只放开一个账号、用禁用做流量开关」的轮换方式(同 IP 多号怕触发风控),闲置待命的号恰恰是最需要签到的——把它设为 true,禁用号仍会签到 / 保活 / 刷新余额,但依旧不参与选号(pool 选号侧的 disabled 过滤不受本开关影响)。
该开关只覆盖调度器的这四类任务。猫猫旅行、夜猫子、连登管家(挂在签到末尾的
RunStreakBonusNow)与成长任务队列仍按原样跳过禁用账号——若也需要,请另行提出。
比上面的全局开关更常用的账号级入口:面板账号行的「暂停选号」按钮。暂停的账号:
- 退出选号(含全冷却兜底),状态显示「已暂停选号」,点「恢复选号」即刻回池——不清冷却域、无需解冻或重登;
- 无条件照常保号:签到 / 活跃上报 / 保活 / 余额刷新四任务照跑(不依赖
include_disabled_in_tasks); - 其余任务体系照常:猫猫旅行(纯 RPC:状态/派出/领奖)、连登管家、成长任务队列与任务中心扫描/执行都照常参与——这些走的是上报/领奖接口,不发模型对话(个别任务动作自带一次真实短对话,如
Model_chat_GLM5.2,影响可控);失败照常记错误行,不特殊处理; - 唯一跳过的是夜猫子:
RunNightChats逐条发真实 glm-5.2 对话,是任务体系中唯一「整任务都是模型对话」的,与「让位防风控」正面冲突。
这正是「一次只放开一个号、其余让位」轮换用法想要的粒度:让位的号不再承接选号流量,也避开夜间的对话补足;其余养号动作照常。与「禁用」的区别:禁用是终态(session/授权判死,需人工解冻,保号默认也停),暂停是运维临时态(账号健康,随时恢复)。状态持久化(state.json paused 字段),跨重启不丢;disable/revive 会一并清掉 paused。
auth 文件带非空 enterpriseId 的账号被识别为企业版(面板账号名旁显示「企业版」标签)。企业版没有个人成长体系——上游对这些端点一律拒绝(实测 2026-10-07,同一时刻与个人号 A/B 对照):
| 端点 | 企业版响应 |
|---|---|
POST /v2/billing/meter/daily-checkin(签到) |
400 code 10001「企业账号不支持该操作」 |
POST /billing/meter/claim-gift / claim-compensation(夜猫子领奖) |
400 code 10001 同上 |
POST /billing/meter/compensation-status(补偿状态查询) |
网关在门控处一并跳过(与 claim-compensation 同属无补偿体系的调用) |
GET /activity/growth/*(连登 / 旅行 / 热力图 / 抽奖)与 GET /v2/activity/growth/tasks(成长任务) |
403「growth system is only available for personal users」 |
因此网关对企业号不发起这五类调用(签到 / 活跃上报 / 猫猫旅行 / 夜猫子 / 连登管家),面板也不渲染「签到」「任务」按钮(只留「额度」)。判定(auth.Auth.IsEnterprise())与既有 IsGlobal()(D4 门控)在同一批引用处并列书写,upstream 侧零改动。
企业版照常的能力:选号派发、保活(token 刷新仍必需——不刷新同样会过期失效)、额度查询。
企业额度不在个人「资源包」体系内(get-user-resource* 对 enterpriseId 账号恒返回空 Accounts,面板因此长期显示 0),故额度改走 POST /v2/billing/meter/get-enterprise-user-usage:上游 credit 是本周期已用(与个人口径「剩余」语义相反),limitNum 是分配给该账号的额度,网关据此换算 剩余 = limitNum - credit;limitNum = -1 为不限量(面板显示「不限」,且不参与周期分桶)。企业配额按周期重置(cycleResetTime),未用完即作废,故周期末会参与 prefer_expiring 优先消耗。
成员账号无权查询企业池总额度(实测:池端点在成员 token 下返回
403 not_authorized,/console/accounts显示isAdmin=false)。面板展示的只是该账号被分配的额度;企业池余额只有企业管理员在管理后台可见。
CodeBuddy 平台签发的静态 API Key(ck_ / pt_ 前缀,控制台 API Key 页签发,官方用途是给 CodeBuddy CLI 用)可以直接当账号用:面板「添加账号 → API Key」粘贴即可,无需浏览器登录。落盘仍是 auths/workbuddy-<uid>.json(accessToken 存 Key、refreshToken 为空、expiresAt 0、enterpriseId 留空),uid 缺省取 Key 的 <id> 段拼成 apikey-<id>。
判定(auth.Auth.IsAPIKey())只看 access token 前缀——官方 CLI 的判据是 tokenType === "ApiKey" 且 token 以 ck_ / pt_ 开头,落盘形态里前缀是唯一稳定、跨进程一致的判据。它与企业版共用「无个人成长体系」门控(auth.Auth.SupportsPersonalGrowth()),但不等同企业版:没有企业身份,不注入 X-Enterprise-Id / X-Tenant-Id。
| 能力 | 静态 API Key 账号 |
|---|---|
| chat / 选号派发 | 照常;出站按官方 CLI 双头 Authorization: Bearer <key> + X-API-Key: <key> |
| token 刷新 / 保活 | 不适用(refresh 端点只认 OAuth 设备流):NeedsRefresh() 恒 false,不会因为"刷新失败"把好号冷却换号 |
| 签到 / 活跃上报 / 成长任务 / 连登管家 / 猫猫旅行 / 夜猫子 | 全部不适用(无个人账号身份,上游 403 / 400「企业账号不支持该操作」):排程一个请求都不发;面板不渲染这些按钮;单账号任务类接口(checkin / tasks / tasks/accept_all / tasks/auto / tasks/auto_all)返回 422 并附说明,而不是执行后静默无效 |
| 额度 | 走企业口径 POST /v2/billing/meter/get-enterprise-user-usage(个人 get-user-resource 对静态 Key 返回空 Accounts):由余额定时器(schedule.balance_refresh_*)与面板「额度」按钮刷新,不依赖 refresh token |
导入 JSON(cockpit 形态)同样接受 refresh_token 为空的记录:必填判据只要求 uid + access_token 非空(issue #165 的 access-token-only 场景,静态 Key 凭证本就没有 refresh token)。
成长中心的连登档位(连续登录 7/14/28 天)兑换后发放积分 / 能量 / 补签卡 / 抽奖次数,抽奖次数只能从兑换获得。管家在每日签到后自动跑一遍闭环(幂等,未解锁静默跳过):
- 查连登档位状态 → 已解锁(非 locked / 非 claimed)的档位自动兑换
- 查抽奖次数 → 有次数自动全部抽完,奖品记日志(
streak-bonus <uid>: 🎲 …)
无需配置,跟随签到排程;到天数那天自动完成「兑换 → 抽奖」,无需人工盯。
关闭定时任务:用 schedule.*_enabled: false 显式关闭(四个都设 false 则调度器不空转,直接阻塞等待退出信号)。注意两点语义:
- 空数组与
null表示「未配置 → 回落默认」,不是「禁用」;真正关闭请用*_enabled: false - 禁用不会擦除小时配置:
*_hours原样保留,改回true即恢复原时点;小时值必须是 0-23,非法值启动即报错 - 关签到会把「余额恢复即解冻」一起关掉,被硬冷却的账号只能等次日 04:00 自然到期
对池内每个可用账号在 activity_hours(默认 [10] 整点)发送一条对话活跃上报(事件 chat_request_send,body 为数组,事件必须含 userId):
- 一条上报同时点亮 growth 连登 + 解锁
first_buddy任务(领养前置) - 每号每天 1 次即可(单时点):日活跃奖励按天去重,重复上报无额外收益
conversationId由网关生成(wb2api-<ms>),无需真实会话- 限速:账号间间隔 800ms(与旅行同口径)
- streak 自检:上报成功后回读连登天数(只读 oracle),日志每号一行可 grep:
activity <uid>: streak days=N。days=0记 warn(report OK but streak.days=0 (silent drop?),对应上游「200 但静默丢弃」);回读失败记 warn 但不影响主流程(上报按天幂等,不重试,只观测) - 手动诊断 / 补跑用
python3 scripts/probe_active.py(只读探测;写操作默认 dry-run,需--yes)
对池内每个可用账号在 travel_hours(默认 [9, 21] 整点)单趟推进一次,每趟只做一个动作,不轮询不等待。默认两趟闭环:9 点领昨日到站奖励并派出,21 点领当日到站奖励(daily_limit_reached 自动挡住二次派出)。
| 探测结果 | 动作 |
|---|---|
无猫(buddy 为 null) |
先同意协议(幂等),再尝试领养;过门槛则 +300 积分并获得猫 |
state=idle 且今日未派出 |
派出 location_id=4(古镇客栈;4 个地点收益 / 时长区间相同,无最优解) |
state=arrived |
领取到站奖励(带 record_id) |
state=traveling / 今日已达上限 / 未知状态 |
跳过 |
- 领养门槛未达标时上游返回 HTTP 400,每账号每自然日只尝试一次(跨日重试,记录仅存内存);门槛可用活跃上报解除
- 限速:账号间间隔 800ms
- 每自然日 1 次派出:按 CST(Asia/Shanghai)自然日重置,与容器
TZ无关 - 失败隔离:单账号失败只跳过该账号当趟;401 不强刷(token 刷新交保活时点)
把上游 /agenttool/v1/search 包成 Anthropic Messages 兼容的搜索端点。这样任何按
${baseURL}/messages 发起搜索的客户端(如 DSH 的 web-search-deepseek)只需把搜索
baseURL 指向 <网关>/v1/search,即可零代码接入本网关的搜索能力。
与 chat 共享同一套账号池语义:选号轮转、在途租约、token 临近过期自动刷新、失败按错误 类型冷却或熔断。
为什么不用 /v1/messages:那是 Anthropic 模型对话的标准路径,留给未来的 chat 转发;
搜索挂在 /v1/search/messages,语义清晰且互不干扰。
请求(Anthropic Messages 形态):
{
"model": "deepseek-v4-flash",
"max_tokens": 4096,
"tools": [{ "type": "web_search_20250305", "name": "web_search", "max_uses": 5 }],
"messages": [
{ "role": "user", "content": [{ "type": "text", "text": "Perform a web search for the query: OpenAI 最新模型" }] }
]
}tools必须声明 web_search(type以web_search开头或name为web_search), 否则返回 400——本端点只服务搜索,不处理模型对话- 搜索词取自 user 消息文本,固定前缀
Perform a web search for the query:会被剥离 - 结果条数固定为官方默认值 5(服务端实际上限约 10)
响应为 Anthropic Messages 形态,搜索结果在 web_search_tool_result block 里:
{
"id": "msg_wb2api_...",
"type": "message",
"role": "assistant",
"model": "deepseek-v4-flash",
"content": [
{
"type": "web_search_tool_result",
"tool_use_id": "srvtoolu_...",
"content": [
{ "type": "web_search_result", "url": "https://...", "title": "..." }
]
},
{
"type": "text",
"text": "",
"citations": [
{ "type": "web_search_result_location", "url": "https://...", "cited_text": "摘要" }
]
}
],
"stop_reason": "end_turn",
"usage": { "input_tokens": 0, "output_tokens": 0 }
}摘要只走 citations:Anthropic 的
web_search_result本身不带摘要,摘要由textblock 的citations[].cited_text承载(与官方行为一致)。上游未实现的参数不要暴露给用户:实测
allowed_domains、freshness、blocked_domains(折成(-site:)拼进 query)在服务端均静默失效——传了不报错也 不生效。本端点因此不接受这三个字段,避免调用方产生「已过滤」的错误预期。
余额后台刷新(schedule.balance_refresh_enabled,缺省开启):每 balance_refresh_minutes(缺省 5)分钟并发查询全部账号余额并更新池内积分——两次签到时点之间 credits 保持新鲜,余额恢复的冷却账号也会自动解冻(语义同签到,但不做签到不刷 token)。面板「立即刷新」按钮也是全量刷余额;5 秒自动轮询只读内存,不打上游。
内嵌式管理面板(internal/panel,前端 go:embed 单文件打进二进制,无外部构建依赖),服务启动后访问:
http://127.0.0.1:7863/panel/
- 账号池按地区分组:国内版 / 国际版分段展示,段头显示账号数,多账号不再混排;
- 功能区块可折叠:每个区块标题右侧有「收起 / 展开」,状态记在本机(禁用站点数据时退回内存兜底),账号或日志较多时可只留关心的块。
鉴权与 API 同口径:api_key 非空时面板要求输入一次密钥(浏览器 localStorage 记住);为空则直接可用。
界面支持明暗主题切换(首次跟随系统偏好,点击按钮两态翻转并记住选择),左侧导航七个视图:
| 视图 | 功能 |
|---|---|
| 账号池 | 统计条(账号总数 / 可用 / 冷却中 / 已禁用 / 积分剩余·总额 / 粘性会话)+ 账号表:健康色条与状态标签(可用 / 限流冷却 / 积分冷却 / 熔断 / 连败降权 / 已禁用)、积分量条、成功·失败计数、在途、最近一次用量(次数 / token / 延迟 / 速率)、最近成功时间;单号操作 签到 / 余额 / 任务 / 解冻 / 禁用 / 移除;批量「全部签到」「旅行巡检」「活跃上报」「全部保活」;下方模型锁池表列出被上游按模型限流的模型(整池不可用 / 没号可用 / 部分限流)、可选与总数、锁定账号数、最早解锁与全池解锁倒计时、限流原因,无锁定时显示「所有模型均可选」 |
| 用量 | 指标卡(请求数 / 总 token / prompt / completion / 失败尝试 / 平均延迟,含占比与吐字速率)+ Token 时序图(渐变堆叠柱、均值参考线、峰值标注、悬停看单桶明细)+ 用量明细(按账号 / 按模型 / 按域切换,可按合计 Token / 请求数 / 失败数 / 平均延迟排序,含 prompt·completion 占比条)+ 积分扣除历史(按账号 / 按模型,折算「积分 / 1M Token」) |
| 积分构成 | 积分到期分布(按批次到期日聚合,颜色区分账号)+ 账号对比(按来源归并的面额 / 余额 / 包数卡片与混合构成条)+ 逐包明细(按到期日排序,默认展开最近到期的 panel.package_detail_limit 个,其余与已用完的包折叠) |
| 任务中心 | 成长任务队列:一键扫描全部账号待办 → 按账号排队执行(账号内串行,账号间可选并发 1-3),进度实时更新;另含开学季券码查询。详见 任务中心 |
| 模型与档位 | 实时查询上游:每模型的积分倍率(含限时免费 / 夜间折扣等生效价与划线牌价)、默认思考档、支持的档位(含「off(可关)」)、上下文长度与最大输出;存在探测数据时最大输出列显示实测上限与钳制告警(见「探测模型真实输出上限」)。支持按条件查询:关键词(ID / 名称 / 描述 / 厂商 / 标签,空格分词 AND)、域(CN / Global)、能力(工具 / 视觉 / 思考 / 默认)、思考档位、价格(折扣 / 限时免费 / 打折),以及按倍率 / 上下文 / 最大输出 / ID 排序 |
| 配置 | 在线编辑 config.json:服务(监听地址 / API 密钥 / 单账号明细条数)、定时任务(签到 / 保活 / 旅行 / 活跃 / 成长任务五类时点与开关 + 余额刷新间隔)、账号池与流量治理、上游与高级(超时 / 出站 UA / 提示词模式 / 指纹脱敏 / 粘性路由)、日志(调用来源记录开关) |
| 运行日志 | 请求记录表(时间 / 结果 / 模型 / 账号 / 来源 IP / User-Agent / 耗时 / Token / 积分 / 请求 ID),支持按 IP·UA·模型·账号·请求 ID 与结果筛选、读取条数、时间范围;下方 运行日志 按「任务 / 对话 / 系统」分频道,可开关自动滚动;新增 积分历史(每次真实查到余额与上次比对,变动即留痕) |
顶部另有「刷新」(向上游全量查询真实余额并回写;5 秒自动轮询只读内存,不打上游)与「添加账号」(浏览器内完成 OAuth 设备授权:可选国内站 / 国际站 / 旧桌面端,显示授权链接 + 自动轮询,登录后凭证落盘并热加载进池,免重启,也支持导入 cockpit tools 导出的 JSON)。
无需配置:窗口宽度 ≤760px 时自动生效,>760px 完全不参与,桌面端逐像素不变。
- 侧栏 → 抽屉:顶栏出现汉堡按钮,点遮罩 / 选中导航项 /
Esc/ 窗口拉宽都会自动关闭(拉宽不复位会留一层 fixed 遮罩把页面盖住点不动) - 宽表 → 卡片:表头隐藏,每列字段名由
mobile.js从<th>的纯文本抄进同列td[data-l],CSS 用content: attr(data-l)渲染——写死在 CSS 里会随表头改名漂移 - Token 时序图窄屏重画:上游那张图固定 viewBox
1200×200靠width:100%缩放,360px 宽下缩放比只剩 ~0.28(内部 10px 刻度实际 ~2.8px,看着是一排小点);窄屏改画440×230并抽稀刻度,tooltip 同样跟随柱子 - 顶栏 / 筛选栏 / 弹层 / 统计条 / KPI 卡按同一密度口径收紧;
color-scheme: only light|dark退出手机浏览器的「自动暗化」(否则主题按钮显示浅色、页面实际还是黑的) - 实现全在
mobile.css/mobile.js两个独立文件里(index.html只多一行<link>、一行<script>),改手机端不会与上游对index.html/app.js的改动冲突
「用量」与「运行日志 → 请求记录」共用同一个时间范围控件,预设 今天 / 近 24 小时 / 近 3 天 / 近 7 天 / 近 30 天 / 全部历史 / 自定义(自定义展开起止时间输入,精确到分钟;起止颠倒就地标红):
- 「今天」与「自定义」的区间由浏览器按本地时区算好后发给服务端——服务端时区未必与浏览器一致(容器常挂
TZ=Asia/Shanghai),让服务端算"今天"会在跨时区时切错日子; - 滚动预设走服务端的
hours口径(当前整点往回 N-1 小时),与既有统计口径逐位一致(前端自己减 N 小时会在边界多算/少算一个桶); - 用量侧由
usage.Window统一三种窗口口径,并在响应里回显实际生效区间(window_from/window_to),总览右上角因此会显示形如9-30 00:00 → 9-30 15:02 · 14 个分桶 · 数据自 …的口径说明; - 请求记录的时间过滤在归档侧完成(
reqlog.Filter.From/To,而不是筛已拉取的条目),所以区间落在更早时段时也能取到记录。
请求记录表会显示每次调用的来源:IP 取 X-Forwarded-For 首段 → X-Real-IP → 回落 TCP 对端(直连部署下这是唯一线索);User-Agent 截断 200 字节入库,stdout 流水行同时追加压缩后的客户端标签(src=203.0.113.7 ua="Chrome/120.0.0.0",浏览器 UA 不会整段铺开)。
来源信息比 token 计数敏感(IP 属个人信息),因此由 logging.request_client_info 控制,缺省开启、热生效、面板「配置 → 日志」可关;关闭后归档与面板都不再出现来源字段(老归档里没有该字段的条目显示 —)。
配置热生效:保存配置后,api_key、cooldown.soft_rate、features.sanitize_blacklist_fingerprints、
models.hidden、logging.request_client_info、pool.*(熔断/在途/权重)、schedule.*(时点/开关/余额刷新间隔/保活临期窗口)立即生效,无需重启;
涉及进程装配期依赖的字段(listen、auth_dir、state_file、upstream.*、upstash.*、session_sticky.ttl)
保存后会提示"需重启进程生效"。配置写入采用「深合并且原子替换」:只更新面板表单覆盖的键,
用户手写的未知键与其余字段原样保留。
与页面同一 Bearer 鉴权,可直接脚本化调用:
| 分组 | 端点 |
|---|---|
| 只读 | GET overview · GET usage(hours 或 from/to)· GET packages · GET models · GET model_probes · GET logs · GET request_metrics · GET request_logs(limit / outcome / account / model / client_ip / user_agent / from / to)· GET config · GET tasks/queue · GET school/vouchers |
| 账号运维 | POST accounts/{uid}/checkin · balance · revive · disable · remove;POST checkin_all · travel_all · activity_all · keepalive_all · balance_all |
| 补偿 | GET compensation(逐号查补偿状态,只读);POST accounts/{uid}/compensation(单号领);POST compensation_all(全量领,返回 summary{claimed,skipped,errored,credit}——先看 errored 再看 claimed,否则会把「全部查询失败」说成「没有待领补偿」) |
| 任务 | GET accounts/{uid}/tasks;POST accounts/{uid}/tasks/accept · accept_all · claim · auto · auto_all;POST tasks/scan_all · tasks/run_queue |
| 添加账号 | POST login/start · GET login/poll · GET login/regions · POST import/cockpit · POST accounts/add_apikey(静态 API Key,见上) |
| 写入 | POST config(校验 → 落盘 → 热应用,返回需重启字段清单)· POST usage/save(立即把内存用量落盘) |
账号运维操作均落到池既有入口(Revive/Disable/Remove 等),与 /status 观测口径一致。
安全响应头:面板页面与全部 /panel/api/* 响应统一带 Content-Security-Policy(default-src 'none',脚本仅同源,frame-ancestors 'none' 禁嵌套)、X-Content-Type-Options: nosniff、X-Frame-Options: DENY、Referrer-Policy: no-referrer 等;前端脚本独立为同源 app.js,不含内联脚本与内联事件处理器。
鉴权实现:internal/httpauth 统一 server 与 panel 的 Bearer 校验,使用 SHA-256 摘要 + subtle.ConstantTimeCompare 常量时间比较(避免逐字节比较泄露密钥信息);上游返回的 uid 经白名单校验([A-Za-z0-9_-],长度 ≤64)后才用于拼凭证文件名,防止路径穿越。
⚠️ 公网部署提示:服务自身只提供明文 HTTP,请务必置于 HTTPS 反向代理之后(Nginx/Caddy 等)并配置访问限流;仅本机或私有网络使用时可直接运行。
| 端点 | 鉴权 | 说明 |
|---|---|---|
POST /v1/chat/completions |
Bearer(api_key 非空时) |
OpenAI 兼容补全;流式/非流式;请求体上限 8 MiB |
GET /v1/models |
Bearer(api_key 非空时) |
模型列表(纯动态拉取,缓存 1h;失败返回空列表 + 5min 负缓存);每模型带 context_length/max_output_tokens(四级查找链:上游目录 → 内置知识表 → model.json 缓存 → models.dev)、reasoning_supported_efforts/reasoning_default_effort 思考档位及描述/标签/倍率等全字段(上游有返回时) |
GET /v1/capacity |
Bearer(api_key 非空时) |
按模型查询可用容量:?model=cn:xxx(或 global:xxx)返回 model_available_accounts(该模型可出票账号数)与 available_accounts(整池健康账号数)。纯本地台账不打上游;models.hidden 点名的模型恒报 0;缺 model 返回 400 |
GET /status |
Bearer(api_key 非空时) |
账号状态汇总 + 每账号详情(积分/冷却/熔断/在途/粘性)+ model_locks(按「域 + 模型」聚合的限流视图:可选 / 总数、锁定账号数、state、最早解锁与全池解锁时间、限流原因;无锁定为空) |
GET /healthz |
无 | 健康检查:有 healthy 且未占满账号返回 200,否则 503;响应带身份标识(见下) |
鉴权规则:仅当
api_key非空才校验Authorization: Bearer <api_key>;api_key为空时上述端点直接放行;/healthz恒无鉴权。
/healthz 响应示例(200 / 503 同结构,仅状态码与计数变化):
{"healthy": 2, "total": 3, "service": "workbuddy2api"}响应同时带 X-Service: workbuddy2api 头。这两个身份标识用于区分本网关与同端口上可能残留的其他服务——对方即使返回 2xx 也不会带该字段 / 头,宿主探测据此避免"假成功"。
宿主健康探测指引:强校验(推荐)用 /status + api_key——只有持有正确 api_key 的本网关返回 200,其他服务返回 401 / 404;弱校验(不适合持 key 的负载均衡器)用 /healthz + service 字段判据(/healthz 恒无鉴权,service == "workbuddy2api" 才算命中本网关)。容器自带 HEALTHCHECK 用的就是弱校验(仅进程内自检,够用)。
- 出站请求强制
stream:true;SSE 帧按 OpenAI 规范白名单重建(reasoning_content保留、工具调用按 index 合并、未知字段剥离) - 保证恰好一个
data: [DONE](上游漏发时兜底补写);空流先写一帧error再补[DONE] - 非流式请求由本地聚合完整 SSE 流为单
chat.completion响应(含reasoning_content/tool_calls) - 正文里的原生工具调用标记在透传前还原为
delta.tool_calls(见「原生工具调用标记还原」):命中时一帧会拆成「正文帧 + N 个调用帧 + 收尾帧」,上游的finish_reason: stop改写为tool_calls且排在调用帧之后;未命中时逐字节透传
上游接口均为 CodeBuddy 官方 CLI / 插件使用的非公开 / 逆向接口,未见公开 API 文档;路径及 Host 以代码内常量为准(见文末出处表)。两类 base:
copilot.tencent.com:聊天补全(SSE)、token 刷新、OAuth、模型列表、growth 域(旅行 / streak)www.codebuddy.cn:每日签到、余额查询、活跃上报
| 相对路径(绝对路径见出处表) | 方法 | 用途 |
|---|---|---|
chat/completions |
POST | 聊天补全(SSE) |
console/enterprises/personal/models |
GET | 动态模型列表 |
plugin/auth/token/refresh |
POST | token 刷新 |
billing/meter/daily-checkin |
POST | 每日签到 |
billing/meter/get-user-resource |
POST | 余额查询 |
billing/meter/compensation-status |
POST | 补偿活动状态(active / claimed / credit_num / validity_period / end_time);active=false 是「当前无活动」的正常 200,不算错误 |
report |
POST | 对话活跃上报(chat_request_send 事件数组,必须含 userId;点亮连登 / 解锁领养) |
plugin/auth/state?platform=CLI |
POST | OAuth 取授权 URL |
plugin/auth/token?state= |
GET | OAuth 轮询取 token |
plugin/login/account?state= |
GET | OAuth 取账号信息 |
activity/growth/buddy/agreement |
POST | 猫猫旅行:同意协议(幂等) |
activity/growth/buddy/first |
POST | 猫猫旅行:首次领养 |
activity/growth/buddy/info |
GET | 猫猫旅行:查询猫档案 |
activity/growth/buddy/travel/status |
GET | 猫猫旅行:旅行状态 |
activity/growth/buddy/travel/depart |
POST | 猫猫旅行:派出 |
activity/growth/buddy/travel/claim |
POST | 猫猫旅行:领奖 |
activity/growth/streak |
GET | 连登天数 + 兑换档位状态(活跃自检 / 连登管家) |
activity/growth/redeem |
POST | 连登档位兑换({tier, client_token};未解锁 403) |
activity/growth/lottery/summary |
GET | 抽奖次数查询 |
activity/growth/lottery/draw |
POST | 抽奖一次({client_token},消耗 1 次) |
activity/growth/tasks |
GET | 任务列表(含 reward_credit/reward_energy/progress) |
activity/growth/tasks/accept |
POST | 接受任务({"task_codes":[...]}) |
activity/growth/tasks/<task_code>/claim |
POST | 领取任务奖励(任务码在路径、无 body;Web 域 www.workbuddy.cn,非 CLI 域——这是领奖能成功的关键) |
出站请求按账号身份携带三段式 UA(CN WorkBuddy/5.8.3 WorkBuddy/5.8.3 CLI/2.161.0,global workbuddy-ai/5.8.4 workbuddy-ai/5.8.4 CLI/2.161.0,可被 upstream.user_agent 整体覆盖);聊天请求带账号头(X-User-Id 等),永不携带 X-Refresh-Token(该头只出现在 token 刷新请求)。领奖请求额外带 x-client-platform: web 与 workbuddy.cn 的 Origin/Referer。
每个 /v1/chat/completions 请求结束时输出一行表格日志(stdout):
| #001 | 18:31:31 | deepseek-v4 | stream | 200 | uid=0851ce35 | TTFB=801ms | tok=60 | 23.5tok/s | effort=max->high | total=2.6s |
| 字段 | 说明 |
|---|---|
#001 |
进程级请求序号 |
18:31:31 |
结束时刻 |
deepseek-v4 |
模型名(超 11 字符截断) |
stream / sync |
请求模式 |
200 |
状态码 |
uid=0851ce35 |
账号 UID 前 8 位 |
TTFB |
流式首帧耗时(非流式为 -) |
tok / tok/s / total |
输出 token 数 / 速率 / 总时长 |
effort |
思考档位。high = 客户端要的与实际生效一致;max->high = 客户端要 max、模型档位表只支持到 high(降级链路一目了然,issue #43);- = 客户端没传该字段 |
敏感度:日志不含任何 token 明文(详见安全与合规),无落盘日志文件。
CI 用多阶段 Dockerfile(golang:1.27.3-alpine 构建 → alpine:3.20 运行)在原生 amd64 / arm64 runner 上分别构建,再合并为一个 multi-arch manifest 推送到 ghcr.io/jin66god/workbuddy2api;本地 docker compose build 走同一份 Dockerfile。镜像内一次编译全部四个二进制并随镜像分发:
- wb2api(主服务)、signin_bin、login、credit + 脚本(
login.sh/signin.sh/credit.sh/scripts/probe_active.py) - 以
app用户(uid 10001)运行,app/auths与app/data预建 - 镜像内默认落
config.example.json作为空配置(不含密钥),生产用挂载卷覆盖/app/config.json - 内置
HEALTHCHECK(wget /healthz,30s 间隔)
账号 / 数据通过 docker-compose.yml 卷挂载持久化:./auths、./data、./config.json。
| 脚本 | 用途 |
|---|---|
./login.sh |
OAuth 登录 → 落盘 auth → 重启容器 |
./signin.sh [auths_dir] |
批量签到(过期先刷新) |
./credit.sh / ./credit.sh -json |
积分日报(美化 / 原始 JSON) |
python3 scripts/probe_active.py |
活跃上报手动诊断 / 补跑(probe=只读 / report=单号上报 / unlock=单号领猫 / ALL=全池;写操作默认 dry-run,需 --yes) |
python3 scripts/probe_max_tokens.py |
探测各模型真实输出上限(区分静默钳制与模型主动收尾),--panel-out 结果可直接进面板展示(见下节) |
二进制不在 git 中:脚本首次使用自动 go build 对应 cmd/*(Docker 镜像内已预编译)。
上游 /v3/config 里的 max_output_tokens 是声称值,普遍虚高:实测 16 个 CN 模型中 8 个被
静默钳制(请求 max_tokens 更大也不报错,输出到真实上限即截断),最狠的声称 1M 实际 32K。
「模型与档位」视图因此支持在最大输出列叠加实测标注:
- 🔴
32K ⚠ 钳制 12×—— 实测被截断于 32K,声称值的 1/12(finish=length判据,可信) - 🟢
48K ✓ / 64K ↑—— 实测与声称一致 / 实际比声称更大 - ⚪
≥40K/?—— 满额未触顶(下界)/ 模型主动收尾未测出
实测值不写死在代码里——它来自探测工具写入的数据文件,上游调整后重跑一次即自动刷新:
# 在网关所在机器上(探测会真实消耗积分;单模型预算默认 600s,并行 4)
python3 scripts/probe_max_tokens.py --base http://127.0.0.1:7863/v1 --key sk-xxx --panel-out data/output_probes.json
# 断点续测 / 只测指定模型 / 预览计划
... --resume
... --models cn:glm-5.2 --panel-out data/output_probes.json
... --dry-run文件落在 state 文件同目录(默认 data/output_probes.json,data/ 已被 gitignore),面板
GET /panel/api/model_probes 只读透传,写入后下次查询即生效,无需重启网关;未探测的
模型不受影响。探测判据(两种停止的区分 / 提示词量级匹配 / 并行与时间预算)的设计细节
见脚本头部注释。
- 多账号复制
auths/workbuddy-<uid>.json即可,池启动时自动对齐目录 - Session 失效账号被禁用(
disabled_reason透出在/status)后,可用./login.sh重新登录覆盖凭证;已持久化disabled=true的账号可在源码侧调用Pool.ReviveDisabled(uid)复活(state.json中清除disabled标志) - 备份 =
auths/(凭证)+data/state.json(池状态:积分 / 冷却 / 计数);配置 Upstash 后状态另镜像至 Redis(7 天 TTL)
- 位置:
./auths(auth_dir可配),文件名workbuddy-<uid>.json - 内容:明文
accessToken/refreshToken+ 账号元信息(account.uid/enterpriseId/nickname) - 权限:容器内以
app用户(uid 10001)运行;token 刷新由SaveAtomic以0600原子写回(tmp + rename);login.sh首次落盘遵循登录 umask,建议手动chmod 600 auths/*.json - 切勿提交 git:
.gitignore已排除auths/、data/、backups/、config.json、*.key、*.pem、*.env、docs/及除 README 外的全部*.md工作文档
- 默认监听
:7863,compose 暴露0.0.0.0:7863,无内置 TLS;公网部署必须设置api_key,建议前置反代 / 内网 - 请求日志字段:序号 / 模型 / 模式 / 状态码 / uid 前 8 位 / TTFB / token 数——不含
accessToken/refreshToken/api_key明文(不读取Authorization头) - 日志写 stdout / stderr(容器内进入
docker logs),代码无任何落盘日志文件
- 发布产物:每个
v*tag 由 CI 构建五平台二进制 Release(附checksums.txt)与 GHCR 双架构镜像(linux/amd64+linux/arm64指向同一 manifest) - 登录 / 签到 / 积分工具:
./login.sh/./signin.sh/./credit.sh - 产物校验:Release 资产用
sha256sum -c checksums.txt校验;镜像架构与 digest 可用docker buildx imagetools inspect ghcr.io/jin66god/workbuddy2api:<tag>核对 - 上游 CodeBuddy 属腾讯系商业产品,本项目是其非官方 OpenAI 兼容网关;使用其账号做 API 网关涉及目标平台服务条款与账号风险,作者不对账号封禁、条款违约或使用结果负责
- 仅限本人授权账号、本机 / 私有环境测试
- 不得共享、转售、违规分发,或用于违反目标平台条款的用途;严禁批量注册账号做 API 业务、二次加壳或捆绑卡密售卖——详见顶部使用声明(必读)
- 遵守 CodeBuddy 平台服务条款与所在地法律
- 妥善保管
auths/(明文凭证)与网关端口
上游 429 + code 6004 是该模型的使用量超限(msg 通常带「将在 YYYY-MM-DD HH:MM:SS UTC+8 重置」),不是账号整体被限流。网关的处理:
- 冷却到上游重置时间:msg 带「将在 … 重置」时,账号冷却
until精确等于该墙钟(按 UTC+8 解释),并封顶soft_rate_max(默认 2h) - 切模型立即可用:冷却由 6004 触发时会记录触发模型;同一账号改用其他模型请求时视为可用。同模型或未记录模型的冷却回到现状
- 退回指数退避:6004 无「将在 … 重置」文案,或非 6004 的普通软限流 → 仍是
soft_rate(600s 起,连续触发指数退避,封顶soft_rate_max) - 面板双口径展示(issue #171):上游文案给的恢复时刻晚于网关软冷却上限时,账号行的限流文案同时给出「上游称 … 恢复 · 网关 … 起重试」,倒计时跟随网关实际重试时刻(与实际掉框一致),不再让「解封时间」与实际解锁时刻差出几十分钟到一小时以上
不会。429 + {"code":6021,"msg":"queue.full.title"} 是上游推理集群准入队列满(渠道级容量信号,不是账号配额),2026-10-15 实案:集群排队时 2 秒内可让同域全部健康号「看起来」在限流。网关的处置:
- 识别为
upstream_busy:Classify在通用429兜底之前判 6021(结构化 code 主判据 +queue.full文案外卡),与 14018 等账号级权威业务码的优先级哲学一致 - 不罚号:不冷却、不熔断、不
NoteError、不喂连败——账号余额与 session 本就健康 - 不轮转:同域换号撞的是同一堵墙(同一渠道),直接 fail-fast 返回
503 + code=upstream_busy(透传上游原文含requestId),客户端 SDK 按退避自然重试 - 为什么必须这样:旧行为按账号级软限流逐号
CooldownSoftRate(600s),全池冷却后pickEarliestExpiryLocked兜底又把冷却号送上游、再撞 6021 又续订 600s——两者互相喂导致冷却永不到期,一次几秒的上游排队被放大成整段时间每个请求都失败
不会。14003 + Model busy. Please switch models or retry later 是上游模型后端的间歇繁忙——同一分钟内既有 14003 也有成功,与账号无关。旧行为把它归进通用 429 → 账号级软冷却 60s,三个号各命中一次就叠成整分钟黑障,客户端连撞几次即熔断,用户侧表现为「用几分钟断一次」。
现在的处置(比单纯缩短冷却更进一步):
- 只停「账号 × 该模型」10s:写
modelCooldowns[model],不写账号级until。账号仍可为其它模型服务,换号照常(不同号可能落到不同后端分片) - 全池被挡时请求内等一次:仅当「全池该模型都在 14003 短冷却」且「最早解封 ≤10s」才等,等待带 ±25% 抖动(防解封瞬间多请求同时醒来二次聚团),不占换号次数、不占在途名额,每请求最多一次
- 为什么要在网关里等:DSH / Codex 这类客户端实测不读
Retry-After,收到 503 后按固定间隔连撞,会把 10s 抖动放大成请求风暴并触发熔断 - 超时降级:超过 10s 或原因不是 14003(11102/11158 换号必败)→ 立即
503 + code=model_busy并带Retry-After,不拖住客户端
默认关闭(account_spread_minutes=0)。开启后五个任务(签到 / 旅行 / 活跃上报 / Token 保活 / 夜猫子)共用一组参数:
- 每个账号的偏移 =
hash(uid|任务|日期) → [0, 窗口),当天确定性:重启不重排,不会出现「重启后所有号挤到同一段」 - 最小间隔只做冲突兜底,不是等距——等距本身也是可识别的时间形状
- 窗口 > 0 时排程不再等待任务结束(否则 60 分钟窗口会把下一个时点整轮挤掉),改为每类任务独立 goroutine + 同类重入保护
- 手动入口(面板「全部签到」/单号操作)始终立即执行
- 已完成账号照常跳过:签到看当日完成标记,旅行靠上游 daily limit,保活/夜猫子本身幂等
这个机制只降低时间相关性,不解决 IP / 设备指纹(那属于出口代理的范畴),也不应当被当作「防封」使用。
网关不再设请求体上限(server.max_body_mb 已移除,对齐上游):任意大小的请求体都会完整读入并转发上游,超限类问题由上游自然返回错误——其响应信息量更大(能看到上游的真实策略),网关不再以 413 提前拦截。
- 多图/长上下文会话(历史图片每轮以 base64 重发,编码再膨胀约 37%)不会再撞网关侧 413
- 若上游真的返回 413/超限错误,网关按既有错误分类链路如实透传(不打码、不罚号——超限是请求侧问题)
- 客户端中途断流导致的半截 body 在读入阶段即报
400 invalid_request,不会把截断 JSON 喂给上游(issue #41 语义保留在读错误路径)
容器以 app 用户(uid 10001)运行,而宿主机挂载的 ./auths、./data 目录属主不是它——写凭证 tmp 文件被拒。三种解法任选(前两种均无需 root 容器):
# 方案 1(推荐,非 root):让容器以你自己的 uid 运行——挂载目录本来就是你建的
PUID=$(id -u) PGID=$(id -g) docker compose up -d --force-recreate
# 或写进 .env 文件长期生效(.env 已被 .gitignore 忽略):
# echo "PUID=1000" > .env && echo "PGID=1000" >> .env
# 方案 2:把挂载目录属主交给容器默认用户(需要 sudo)
sudo chown -R 10001:10001 ./auths ./data ./config.json
# 方案 3:compose 设 user: "0:0" 以 root 运行(NAS/群晖不便 chown 时用)报错信息里自带这条指引;compose 的 user 已参数化为 ${PUID:-10001}:${PGID:-10001}。
- 用
./login.sh重新登录覆盖凭证,重启后自动回池; - 或源码侧调用
Pool.ReviveDisabled(uid)清除disabled状态(state.json同步刷新)。
默认 prompt.mode=passthrough 透传客户端 system;如需整体替换可切 custom(用网关自有提示词替换客户端 system,从源头消除大部分误报);用户 / assistant 消息中的指纹串由 features.sanitize_blacklist_fingerprints 清洗,两层叠加。passthrough 模式下首遇拦截会自动换 Degraded 中性提示词同请求重试一次。
官网「使用端」列按出站请求 UA 服务端归因。配置 upstream.user_agent: "WorkBuddy/2.x.x"(或环境变量 WB2A_USER_AGENT)即可改写全部出站请求的 UA;默认保持 CLI/2.63.2 CodeBuddy/2.63.2 现状(指纹净化考虑,可配而非改死)。
本项目仅供学习和研究使用。使用者需遵守 CodeBuddy 服务条款,自行承担使用风险(包括账号封禁、条款违约等)。作者不对任何因使用本项目产生的直接或间接损失负责。
本项目采用 MIT License 开源协议。
- 允许任意使用、复制、修改、合并、发布、分发、再授权及销售
- 再分发(源码或二进制形式)时,请保留原仓库的 MIT 版权声明与许可声明(如在 NOTICE 或 README 中注明原始出处
https://github.com/Sliverkiss/workbuddy2api) - 本项目不授予任何上游(CodeBuddy / 腾讯)接口或服务的权利;使用者仍需自行遵守上游服务条款