Skip to content

Releases: yuan240324/workbuddy-openai-proxy

v1.4.0 — apiKey 支持多个密钥 · README 口径修正

Choose a tag to compare

@yuan240324 yuan240324 released this 21 Sep 16:26

新增

apiKey 支持多个密钥

以前 apiKey 只能配一个。想给第二个客户端发密钥,或者临时给录屏 / 演示环境一个密钥,
只能改配置 + 重启 —— 而且会把原来那个客户端踢下线。

现在两种写法都支持:

{ "apiKey": "sk-wb-xxx" }                    // 单个字符串(和以前一样)
{ "apiKey": ["sk-wb-xxx", "sk-demo-yyy"] }   // 数组,每个都有效

实现上把「取密钥」和「比密钥」收敛成两个函数,避免各处各写一遍:

函数 用途
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

Choose a tag to compare

@yuan240324 yuan240324 released this 21 Sep 15:45

修复

日志和控制台的时间戳是 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 兼容性修复

Choose a tag to compare

@yuan240324 yuan240324 released this 21 Sep 11:35

新增

滑动窗口限流

服务只绑 127.0.0.1,所以要挡的不是远程攻击,而是两类真实问题:

  1. 客户端 bug 造成的失控重试循环 —— 死循环重试会把上游打爆
  2. 重端点被反复触发 —— /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)

Choose a tag to compare

@yuan240324 yuan240324 released this 20 Sep 17:42

修复 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

Choose a tag to compare

@yuan240324 yuan240324 released this 20 Sep 17:22

新增

账号池(同一站点多个账号)

一个站点下可以放多个账号,某个账号额度耗尽或请求失败时自动换号。

  • 选号顺序:未耗尽 → 连续失败少 → 最久未用(负载自动摊开)
  • 触发换号:额度耗尽(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