Repository navigation
Releases: yuan240324/workbuddy-openai-proxy
Release list
v1.4.0 — apiKey 支持多个密钥 · README 口径修正
新增
apiKey 支持多个密钥
以前 apiKey 只能配一个。想给第二个客户端发密钥,或者临时给录屏 / 演示环境一个密钥,
只能改配置 + 重启 —— 而且会把原来那个客户端踢下线。
现在两种写法都支持:
实现上把「取密钥」和「比密钥」收敛成两个函数,避免各处各写一遍:
| 函数 | 用途 |
|---|---|
authKeys(cfg) |
归一化出所有有效密钥(trim、去重、丢空串与非字符串);空数组 = 未配置 |
primaryKey(cfg) |
需要单个密钥的场景:脚本自己发请求时的 Authorization 头、控制台掩码展示、启动横幅。多密钥时取第一个 |
authorized() 改成「命中数组里任意一个即放行」。启动横幅在多密钥时会提示「另有 N 个密钥同样有效」。
顺带修掉一个如果不改就会坏的地方:
ask.mjs/status.mjs/stop.mjs
原来直接把cfg.apiKey拼进 Bearer 头。配置成数组后会拼出Bearer sk-a,sk-b
这种坏头 —— 这三个脚本已改用primaryKey()。
文档修正
「只在 Windows 上实测过」的说法不准确
CI 徽章就挂在 README 顶上,点进去是 Linux(ubuntu-latest)× Node 18/20/22/24 全绿,
而正文却写着「目前只在 Windows + Node 24 上实测过」—— 读者会觉得前后矛盾。
这其实是两件事,现在拆开说:
| 验证范围 | |
|---|---|
| 测试套件(392 用例) | CI:Linux × Node 18/20/22/24 |
| 端到端实跑(真实上游账号) | Windows + Node 24 |
两处过期内容
- 英文版测试命令里还留着 v1.3.0 已移除的
--experimental-test-isolation=none。
那个参数需要 Node 22.8+,会让 Node 18/20 直接报bad option。 - 中文版解释该参数的 FAQ 已失效,改成说明当前的默认隔离行为(
node --test本来就是文件级进程隔离)。
顺手修掉:两个测试其实没测到鉴权
security.test.mjs 里「正确 key 放行」和「x-api-key 头同样被接受」用的是 /v1/models ——
而那个端点是故意免鉴权的(客户端的配置向导会裸探模型目录,见 server.mjs 里的注释)。
所以这两条不带密钥也会 200,等于什么都没测。
改用 /definitely/not/an/endpoint:过了鉴权才走到 404,没过是 401。现在它们真的在测鉴权了。
升级
git pull
node stop.mjs && node server.mjs完全向后兼容 —— 单字符串的 apiKey 写法和以前行为一致,不改配置也能用。
测试
392 个用例全部通过(较 v1.3.1 新增 7 个,覆盖数组归一化、去重、坏输入,
以及一条真实 HTTP 的多密钥鉴权用例)。
Full changelog: v1.3.1...v1.4.0
v1.3.1 — 时间戳改用本地时间 · 补演示 GIF
修复
日志和控制台的时间戳是 UTC,比本地时间慢 8 小时
src/log.mjs 的时间戳取自 new Date().toISOString() —— 那是 UTC。
在东八区下,控制台「实时日志」里每一行的时间都比系统时间慢 8 小时:
系统本地时间 : 2026-09-21 22:49:31
日志里的时间 : 2026-09-21 14:49:14 ← 差 8 小时
这不只是个观感问题 —— 排查请求时对不上时间,日志基本就废了一半。
同一个根因还带出两处:
| 位置 | 问题 |
|---|---|
src/usage.mjs todayKey() |
用 UTC 日期当「今天」的键,日切边界落在早上 8 点。凌晨 0~8 点的用量会被记进前一天,「用量统计」的分日数据错位。 |
src/auth.mjs token 刷新日志 |
到期时间是 ISO/UTC 字符串,和同一行前缀的时间格式也对不上。 |
改法:在 log.mjs 里导出一个 stamp()(本地时间 YYYY-MM-DD HH:mm:ss),
ts() 复用它,auth.mjs 也改用它;usage.mjs 改用本地日期。
有一处
toISOString()特意没动:src/upstream.mjs里那个是发给上游的
查询参数(PackageEndTimeRangeBegin/End),不是给人看的显示值。
不知道上游期望哪个时区,贸然改可能把额度查询弄坏。
文档
补上演示 GIF
README 里一直留着一句「演示 GIF 待补」的占位注释,这次补上:
| 文件 | 大小 | 内容 |
|---|---|---|
docs/demo-chat.gif |
150 KB | 在 TraeWork 里当自定义模型对话 |
docs/demo-config.gif |
364 KB | 添加自定义模型的完整过程 |
docs/demo-console.gif |
825 KB | 控制台四个分页走一遍 |
三张合计 1.31 MB,单张都低于 2 MB。docs/screenshot-console.png 已被
demo-console.gif 取代,删除。
顺带记一个反直觉的发现:GIF 编码不要套 palettegen/paletteuse。
那套配方流传很广,但在屏幕录制这个场景下反而让体积翻倍:
| 配方 | 大小 |
|---|---|
fps=12,scale=880(ffmpeg 默认编码器) |
3.43 MB |
同上 + palettegen/paletteuse |
6.19 MB |
ffmpeg 默认的 GIF 编码器带 +offsetting 逐帧调色板优化,对大块平色 + 少量文字的
UI 内容比全局调色板更划算,而且画质没有肉眼差别。
另外一个容易踩的坑:分辨率是最有效的杠杆,不是帧率。1920 → 880 宽能砍掉七成,
30 → 12 fps 只能砍掉一半。
升级
git pull
node stop.mjs && node server.mjs无需迁移配置或凭证。
测试
385 个用例全部通过。
Full changelog: v1.3.0...v1.3.1
v1.3.0 — 限流 · GitHub Actions CI · Node 18 兼容性修复
新增
滑动窗口限流
服务只绑 127.0.0.1,所以要挡的不是远程攻击,而是两类真实问题:
- 客户端 bug 造成的失控重试循环 —— 死循环重试会把上游打爆
- 重端点被反复触发 ——
/console/api/probe一次最多 60 次上游调用 + 至少 15 秒
限流放在鉴权与路由之前,未授权的洪泛也能被廉价拒掉,而不是先做完鉴权才发现对方在刷。
超限返回 429 并带 Retry-After 头。
{
"rateLimit": {
"enabled": true,
"windowMs": 10000, // 滑动窗口长度
"max": 600, // 每窗口每来源 IP 的总请求上限(≈60 次/秒)
"probeMax": 3 // 其中 /probe 更严
}
}默认值刻意放宽:正常单用户使用(含编码 agent 的工具调用突发)远达不到;
控制台 20 秒轮询一次 /state 与 /logs,占用可忽略。
设 enabled: false 可完全关闭。
这个改动的起因是 PR #3(自动化安全扫描工具)。
它指出的/probe开销是真问题,但实现有三处偏差,因此按其思路重做:
位置(原补丁放在鉴权之后,未授权洪泛到不了那里)、
内存(原补丁用只写不删的 Map,等于用无界结构去修「无界资源」)、
以及可配置与测试。
GitHub Actions CI
- test:Node 18 / 20 / 22 / 24 矩阵,每个版本先语法检查再跑全量测试。
fail-fast关掉,
某个版本挂了也能看到其它版本结果。 - zero-deps:守住「零依赖」这个核心卖点 —— 检查
package.json没声明任何依赖、
仓库里没有node_modules与 lockfile、且在没有node_modules时能导入全部src/模块。
CI 刻意不跑 npm install —— 这本身就是对「零依赖」的持续验证。
README 上的测试徽章也从写死的数字换成了实时 CI 徽章。
修复
- 「Node ≥ 18」此前是假的。
src/config.mjs用了import.meta.dirname(需要 Node ≥ 20.11),
Node 18 上它是undefined,path.resolve直接抛TypeError,服务根本起不来。
已改用等价的path.dirname(fileURLToPath(import.meta.url))。 - 测试命令有两处版本依赖:
--experimental-test-isolation需要 Node 22.8+(旧版本报bad option);
带引号的 glob 在 Node 20 及更早无法展开。已移除前者、改用可被 shell 展开的形式。
现在 Node 18/20/22/24 四档都实测通过。 .gitignore放宽配置备份规则:从config.json.bak-*改成config.json.bak*。
手工复制出的config.json.bak(无时间戳后缀)同样含apiKey,之前没被忽略,
等于给未来的git add -A留了个坑。
升级
git pull
node stop.mjs && node server.mjs无需迁移配置或凭证。新增的 rateLimit 段有内置默认值,不写也能用。
测试
385 个用例全部通过(较 v1.2.1 新增 14 个,其中一组专门断言限流的内存有界性)。
Full changelog: v1.2.1...v1.3.0
v1.2.1 — 修复 400 Illegal API invocation(Claude Code / Claude desktop)
修复 issue #1 与 issue #2(同一个根因)。
症状
400 [cn-cli] 上游 HTTP 400:Illegal API invocation from an unapproved channel
国内版与国际版都会出现,与账号无关。
根因
上游对 system 消息做「客户端指纹」精确匹配,命中黑名单就拒。
实测(cn-cli 与 intl-cli、/v1/chat/completions 与 /v1/messages 四条路径结果一致):
| system 内容 | 结果 |
|---|---|
You are Claude Code, Anthropic's official CLI for Claude. |
❌ 400 |
只留 You are Claude Code |
✅ 通过 |
只留 Anthropic's official CLI for Claude |
✅ 通过 |
| 同样内容放 user 位置 | ✅ 通过 |
拆开任一部分都放行,说明上游维护的是一份精确黑名单,不是关键词过滤。
影响面:Claude Code 的开场白就是这句话;Claude desktop 的 /code 面板同源。
这也解释了 issue #1 里为什么 /code 报 400 而 /cowork 正常 —— 两个面板的提示词不同。
修复
在出站前剥离命中的那一句:
- system 提示词的其余内容原样保留,不改动你的实质指令
- 整条 system 就是指纹时,用
defaultSystemPrompt兜底(不留空串) - 只处理 system 角色,user 内容不动
- 默认开启(否则这些客户端完全不可用)
只放实测确认过的模式 —— 多一条可疑模式就多一份误伤风险。
配置
{
"stripClientFingerprint": true // 默认 true;设为 false 可关闭
}升级
git pull
node stop.mjs && node server.mjs无需迁移任何配置或凭证。
测试
371 个用例全部通过(+8),新增回归用例覆盖:「只剥离 system」「不误伤拆开的部分与其他客户端提示词」「开关可关」「多模态数组内容不崩」。
Full changelog: v1.2.0...v1.2.1
v1.2.0 — 账号池 · 上下文压缩 · Responses API
新增
账号池(同一站点多个账号)
一个站点下可以放多个账号,某个账号额度耗尽或请求失败时自动换号。
- 选号顺序:未耗尽 → 连续失败少 → 最久未用(负载自动摊开)
- 触发换号:额度耗尽(429 / insufficient credits)标记 6 小时;401 先原地刷 token 再换号;5xx 走退避冷却
- 无需迁移:池文件不存在时,旧的
auth.<site>.json即唯一账号,行为与之前完全一致 - 登录:
node login.mjs --site X --label 名字,--list查看
上下文压缩
上游对输入长度有硬限制,超了回 400 prompt is too long。以前是原样转发,长会话必撞。现在会按模型上限裁剪,仍被拒则压缩后重试。
裁剪规则:system 提示永不丢、从最老的丢、至少保最近 4 条、tool_calls 与其 tool 结果同生共死。
/v1/responses
OpenAI Responses API 兼容层,供 Codex CLI / 桌面端接入。
网页控制台
状态总览、模型与一键切换、用量统计、实时日志、账号管理,深/浅双主题。单文件零依赖。
安全加固
控制台来源校验:Origin 必须为空或回环、校验 Host 防 DNS rebinding、返回 X-Frame-Options: DENY、控制台响应不带 Access-Control-Allow-Origin: *。防止你浏览器里的任意网页窃取控制台会话令牌。
改进
- 配置校验:
config.json字段类型写错会自动回退到安全默认值并在启动日志逐条列出,不再崩溃或静默忽略 - 上游重试与站点降级:连接级失败自动重试(国际版实测约 17% 连接抖动);网关 5xx 或模型不存在时自动换站点
- 模型白/黑名单:支持
*通配符,全局 + 站点级 - 中文感知 token 估算:实测中文 0.528 tokens/字符(原按英文口径 0.333 估,低 1.6 倍)
- 路径后缀容错:
/v1/messages/chat/completions这类拼接错误不再 404
文档
- 中英双语 README(
README.md/README.en.md) - 三平台说明:Windows / macOS / Linux 的启动、后台常驻、开机自启(launchd / systemd)差异
- 无头机器(SSH)登录方案
安装
只需要 Node.js ≥ 18,没有 npm install,没有 node_modules,没有 Docker:
git clone https://github.com/yuan240324/workbuddy-openai-proxy.git
cd workbuddy-openai-proxy
node server.mjs然后打开 http://127.0.0.1:8788/console 点登录授权即可。
从 1.1.0 升级
直接 git pull 后重启服务。凭证与配置无需迁移 —— 账号池会自动兼容旧的单账号凭证文件。
git pull
node stop.mjs && node server.mjs测试
363 个用例全部通过(npm test,零依赖)。
说明
本版本的 tag 指向包含 README 渲染修复的提交(截图改用标准 Markdown 语法,
原先的 HTML 表格写法在 GitHub 上会被包进 markdown-accessiblity-table 导致布局异常)。
Full changelog: v1.1.0...v1.2.0
{ "apiKey": "sk-wb-xxx" } // 单个字符串(和以前一样) { "apiKey": ["sk-wb-xxx", "sk-demo-yyy"] } // 数组,每个都有效