Skip to content

Releases: shuishuipingan/qoder2api-hub

v1.3.7

Choose a tag to compare

@shuishuipingan shuishuipingan released this 13 Oct 15:22

✨ 新增功能

① 双区自动路由的完整体验(#26 续) —— v1.3.6 把自动路由发出去了,这一版把它变成看得见的:

  • 「本次实际出口」不再靠猜:usage 行新增 realm_reason(三态),面板据此给出确定结论:

    realm_reason 含义 面板显示
    model_missing 当前区官方清单里没有这个模型 「模型不在默认出口,已自动改走 X 区」
    no_account 模型本区有、被账号可用性推走 「默认出口无可用账号,已自动改走 X 区」
    ""(显式空串) 明确没有换区 「按默认出口执行」
    键不存在 字段上线前的旧行 「未知(旧记录)」—— 不给用户一个假结论
  • 最近请求表新增「区域」列:直接来自 usage 行的 realm —— 反映的是每次对话的真实出口,不是面板自身请求;

  • 带时间:「最近一次对话 12:03 走了国内版 · 未换区」—— 持久化字段才有的信息量。

🐛 问题修复

  • 面板数据源从响应头迁到 usage 行:v1.3.6 冻结的契约(X-Qoder-Realm 只在 chat 类请求出现)让面板拿不到那个头(面板发的都是管理端点)→ 改为读 usage 行,绕开长连接状态泄漏那一整类问题,而且历史可查;
  • 反向保险:新增「dashboard.html 全文不得再读 X-Qoder-Realm」的断言(检查目标是被守护的文件,不是测试自己),并用金丝雀自证(注入一行读取 → 必红)证明它不是摆设 —— 两次独立跑过;
  • 两条「片段依赖自检」:测试用的 eval 片段必须自带 RECENT_SNAPSHOT 声明,否则当场红并说明原因(而不是崩溃成一个读起来像产品坏了的 ReferenceError);并断言该声明在 dashboard.html 里顶层唯一、引用都在函数体内 —— 把「浏览器里会不会炸」变成可执行断言。

🎨 体验优化:见 ① 的三条。

⚠️ 其他变更

  • 口径收紧两处:最近一次对话取「最近一条非 error 行」(失败行可能没有真实出账区,拿它当结论是错的);旧行显示「未知(旧记录)」而不是「没换区」;
  • 流程:报告「全绿」时附两枚指纹(被测文件 md5 + 套件副本 md5)—— 本轮一次误报(过期副本被读成产品故障)就是靠这条把范围锁死的;复核方也贴自己跑的那份,先对齐再谈结论。

验证

  • 双入口全绿:python tests/run_all.py → 28 suites / 1005 checks / 0 failed(前端六套件 105 = 9/19/19/15/12/31);python _test_qoder.py → 626 passed / 0 failed;
  • 金丝雀两次独立跑过(作者一次、复核者一次):注入读取 → 反向保险必红;真文件 → 全绿。

v1.3.6

Choose a tag to compare

@shuishuipingan shuishuipingan released this 13 Oct 15:08

✨ 新增功能

① 双区自动路由(#26) —— 不用再手动切面板:

  • 模型独占时自动试另一区:某模型只在一个区有的(7 个 —— gm51model / q37fmodel 仅国内,cmodel / efficient / performance / smodel / ultimate 仅国际)会自动改用那个区;
  • 无可用账号时回退另一区;
  • 面板三态开关:自动 / 锁定国内 / 锁定国际(「自动」由 QD_AUTO_REALM 控制、改它需重启;锁定走既有的区域切换);
  • 实际出口与原因可见:换区时响应带 X-Qoder-Realm 与原因 X-Qoder-Realm-Reason: model_missing | no_account;面板据此显示「最近一次请求的实际出口与原因码」。
    • ⚠️ 口径说明:面板那处显示反映的是面板自身请求(/usage、/v1/models、/realm)有没有被自动换区,不是「每个客户端对话走了哪个区」——后者要看 usage 行的区域维度(「最近请求表补区域列」已排入下一版);
  • /v1/models?merge_realms=1:两区并集视图(含区域标注与 realm_exclusive),opt-in —— 默认仍是单区视图。

边界与原则(重要)

  • 显式优先:?realm= / Key 绑定 / X-Realm 永远优先于自动路由,自动只在用户没指定时兜底;
  • 判得出才动:只在「模型独占」或「当前区无可用账号」时换区,判不出就维持现状;
  • MiniMax 那两个是数据驱动排除(同 key 两区型号不同即不猜),不是硬编码黑名单 —— 未来同类模型自动覆盖;
  • 跨区会断会话亲和 —— 这是接受的:那两种情形下当前区本来就没有可用的亲和目标,解绑的是一条已失效绑定;
  • 不违反限额护栏:护栏是账号级的,换区即换到另一个账号,它自己的阈值照常生效。

🐛 问题修复

  • 路由规则叠加时的优先级错误(本轮自查发现):账号回退逻辑只看账号数,会把「模型规则已经推走的区」按账号换回原区 —— 全局在 cn、请求仅 intl 有的模型、而 intl 恰好没号时,请求会被送回 cn,打到「没有这个模型」的上游,用户看到的是语义错误而不是「没号」。已修:模型归属优先于账号可用性。

🎨 体验优化

  • 看板:自动换区与原因可见(三态开关 + 「本次实际出口」),并严守一条原则:只在真的抓到响应头时才说「自动换区」;没抓到显示「未观察到换区」;有头但与默认出口一致时显示「(与默认出口一致)」—— 不给假指示。

⚠️ 其他变更

  • tests/_tools/ 进仓:dash_extract.js(锚点缺失即抛 + 按函数做纯度检查)、dep_scan.py(迁移依赖扫描)+ 两个自测 —— 后来人可直接复用这些判断;
  • tests/README.md 新增「基线数字(提交前核对用)」:28 suites / 989 checks / 前端 89 / legacy 630,附提交前三步 —— 其中一条是「用 git add -A,不要只用具体文件名」(新增套件会被静默漏掉,表现是全绿但总数变小)。

验证

  • 双入口全绿:python tests/run_all.py → 28 suites / 989 checks / 0 failed;python _test_qoder.py → 630 checks / 626 passed / 0 failed;
  • 自动路由逐条实测:7 个独占模型 7/7 正确、双区都有的 12 个 12/12 保持当前区、显式优先级六条全对、换区可见(日志 + 响应头)、并集 19 条去重正确;
  • 能红证据:改动前那 7 个模型在当前区不可用(输出已留存)。

v1.3.5

Choose a tag to compare

@shuishuipingan shuishuipingan released this 13 Oct 14:46

✨ 新增功能

  • Linux / macOS 启动脚本(社区贡献 @jk-uzi):start-qoder-proxy.sh / start-qoder-proxy-lan.sh / allow-firewall.sh,与 Windows 的 .bat 一一对应;运行时探测链相同、要求 Python 3.9+、用 exec 让终端生命周期等于进程生命周期;allow-firewall.sh 检测 ufw/firewalld/nftables/iptables、用 -C 保持幂等并打印回滚命令。

🐛 问题修复

  • Anthropic 入口的 thinking 档位被静默丢弃(@jk-uzi):/v1/messages 收下 thinking.budget_tokens 却没有把档位表传下去 → 每个请求都静默跑默认档位。这是我们在接线时漏掉的一步(桥接设计文档里写过)。已补 catalog_meta_for() 并把 meta 传进转换层。
  • 非对象上游帧会让流崩溃(@jk-uzi):信封 body 可能是数组或标量,三处直接 .get() 就抛 AttributeError → 流式中途崩、聚合整单失败。现在按类型分流(放行 / 丢弃 / 不动 inner)。
  • 双区账号池下冷却判断用错口径(#29 @ismodo):冷却函数问「全局有几个账号」,选号循环问「本区域有几个可用」——双区池里两数不等,本区域唯一的账号被当成多号池,于是上游请求的 30/60s 冷却原样生效、超出等待预算,一次本可 3 秒自愈的排队变成中断。两处现在共用同一个 helper;等待逻辑改为收集候选取最小值(不再因某个号的长频控而放弃其它号的短冷却)。
  • Windows 下网关会因系统代理开关而全挂(#27 @mengmengma0104):urllib 在 Windows 会回退读 WinINET 注册表,且默认 opener 在首次请求时固化代理配置 → 代理工具关闭后网关仍去连已无人监听的端口,直到重启。现在自建 opener、只从环境变量取代理:设了 HTTPS_PROXY / HTTP_PROXY / ALL_PROXY 就走它,没设就直连(不回退注册表),且每请求现读、无固化。
  • intl 账号账本被写全 0 / 活动文案误报 / lastCheckin 恒空(#25 @xiarunjie):
    • 账本写入此前无任何合理性守卫 —— 一个退化响应(exceeded + 两个 quota 全 0 + CN 的永不过期占位)会把健康的 399 覆盖成 0,而且不记录原始响应,事后无法回溯。现在:保守判定(真·余额归零照常覆盖)、拒绝覆盖时落盘截断的原始摘要、调度器排除本轮凭证失效的账号(不再用失效 token 打上游);
    • 看板与一键签到把已领取的奖励报成「暂无可领取」—— 因为过滤发生在归类之前,而 intl 那条活动的 actionType 不是 CLAIM_BENEFIT。现在先归类再决定是否参与领取;lastCheckin 也改成已领取时同样盖章(此前只在「我们自己去领」时盖章 → can_checkin 恒真 → 调度器每轮重走同一个号)。
  • 批量刷新路由对账号集合没有守卫:/accounts/credits(GET/POST)、/accounts/refresh 会对禁用号与坏凭证号也发请求 → 它们的账本被反复零化。现在统一走 _refreshable_accounts(),被跳过的号以 {uid, reason} 出现在响应的 skipped 里(不静默少项)。

🎨 体验优化

  • 看板区分「未知」与「0」:此前 energy || 0 让 None 与 0 塌成同一个显示 —— 这正是 #25 报告者被误导的界面症结。现在三态分明(正常数字 / 未知(warn 色 + 原因)/ 真 0);批量视图的合计会标注「已排除 N 个未知账号」并列出是哪些。

⚠️ 其他变更

  • ⚠️ 一条语义变化(#29 的连带):频控窗口 ≤10s 时,网关会等待并重试,客户端收到的是延迟后的成功而不是 429。这是自愈(比中断好),但与旧行为不同;>10s 的长频控不受影响,仍返回 429。
  • 测试:编排总数到 974 条;tests/README.md 增补「桩的保真度」纪律(元素桩照 DOM 语义做、兄弟函数显式注入空桩、锚点缺失即抛、按函数而非区间做纯度检查)。

验证

  • 双入口全绿:python tests/run_all.py → 27 suites / 974 checks / 0 failed;python _test_qoder.py → 630 checks / 626 passed / 0 failed;
  • 每条修复都带能红证据:冷却四组数字(30→3 / wait 0→3 / 长窗保持 0 / 多号坑 0→3)· 账本污染前后(ZEROED: ['u-disabled','u-no-cred'] → [],且断言 calls == 0)· 文案矩阵三档一致 · CN 对照组逐字 SAME(8/8) · 非对象帧的 8 条 FAIL 自证;
  • 一条流程留痕:VIEW_DETAILS 该不该「算已领」出现过新旧断言冲突,最终判定不该把显示与盖章分开(分开会重现 lastCheckin 恒空那条链),并把原断言拆成 E3a/E3b 各自钉住新语义与「防过度」的界。

v1.3.4

Choose a tag to compare

@shuishuipingan shuishuipingan released this 09 Oct 07:47

⚠️ 如果你在用 1.3.2 或 1.3.3 的预构建镜像,请升级到本版 —— 那两个镜像里少了一个模块文件,容器会启动即崩(ModuleNotFoundError: No module named 'qoder_anthropic')。本版已修复,并加了 CI 冒烟防止再犯。

🐛 问题修复

  • 镜像漏 COPY qoder_anthropic.py(issue #23,报告者 loki0411):qoder_proxy.py 从 1.3.2 起 import qoder_anthropic,但 Dockerfile 的 COPY 清单是逐个文件枚举的,新增模块时漏了它 → 镜像里没有这个文件 → 容器启动即 ModuleNotFoundError、无限重启。
    • 报告者的排查很完整(镜像内 find 无该文件、镜像内 qoder_proxy.py 的 sha256 与 tag 一致、对照 1.3.1/1.2.19 自洽可启动),我们照此复现并修复;
    • 为什么我们的测试没抓到:所有测试都跑在源码树上,而这是镜像内容问题;CI 的构建步骤不会因为漏 COPY 而失败(构建本身成功)。四个发布的验证里,我们一次都没真正运行过容器 —— 这是流程缺口,不是测试缺口;
    • 受影响范围:1.3.2 与 1.3.3 的预构建镜像(latest 在 v1.3.4 发布前指向 1.3.3,因此也受影响)。源码运行不受影响;
    • 临时绕过(报告者提供,仍可用):compose 里挂只读文件 - ./qoder_anthropic.py:/app/qoder_anthropic.py:ro;升级到本版后可移除。

✨ 新增功能

  • CI 冒烟(防复发):构建之后新增一步,用本地单架构镜像实际跑三件事 ——① 按源码里的 import qoder_* 反查每个模块文件是否真的在镜像里(这次就是这么漏的,将来任何新增文件忘 COPY 都会立刻红);② import qoder_proxy, qoder_anthropic 确实可导入;③ 起容器并等 /ping 回应(60 秒超时,失败打印容器日志)。

    一句话教训:构建成功不等于镜像能跑。

⚠️ 其他变更

  • README 安装段补了镜像与版本直达入口(GHCR 包页面 + Releases 链接、可用 tag 形状、双架构说明、以及「仓库侧栏未必列出容器包」的提示)。

验证

  • 双入口测试全绿:tests/run_all.py → 19 suites / 881 checks / 0 failed;_test_qoder.py → 630 checks / 0 failed;
  • 镜像层验证由新增的 CI 冒烟承担:本版 CI 里会执行上面三步(其中第 ① 步在修复前必然失败 —— 因为 qoder_anthropic.py 不在镜像里)。

v1.3.3

Choose a tag to compare

@shuishuipingan shuishuipingan released this 08 Oct 21:46

✨ 新增功能

① 前端测试破题(工作包点名的第五类空白):tests/ 新增 4 个前端断言套件(62 条),把前面几轮的前端证据从会话里搬进仓库:

  • effort_parity(9 条) —— 逐行审计列与汇总视图的四类计数一致性。这是这批里最有价值的一条:同一份数据两处实现,口径一旦漂移,运维就会看到互相矛盾的数(与我们 sum(by_key)==sum(by_account) 是同一类问题);
  • limits_auth(19 条) —— 403/401 分流(把 403 当鉴权失效会让用户白白掉登录、真原因被吞 —— 这是修过的真 bug 的回归网)+ 限额保存载荷形状(空串=继承 / 0=关闭 原样传递);
  • effort(19 条) —— 档位三态渲染 + Key 列四态;
  • log_and_badge(15 条) —— 日志增量追加的行为断言(追加而非重建)+ 护栏四态徽标。
  • 做法:自读 dashboard.html + 抽 script 块 + 内联元素桩,没有引入 jsdom(守住零依赖红线);配两个工具:tests/_tools/dash_extract.js(锚点缺失即抛 —— 防「切片拿到空串、断言照样通过」这种假绿)与 dep_scan.py(迁移依赖扫描)。

② 按 Key 的档位分布(矩阵下方):一眼看出哪个 Key 受影响最多(多少请求被挪档、多少被丢弃)。这是把 P1-3 的审计口径从「逐行可见」升级成「按 Key 汇总可见」;零额外请求(复用已拉回的最近请求行);口径行明写「基于最近请求页样本,不是全量聚合、数字不可相减」。

🐛 问题修复:Key 列 tooltip 不再出现后端桶名的字面量 —— 界面(含 tooltip)均不泄露实现细节。

🎨 体验优化:见 ② 的汇总视图。

⚠️ 其他变更

  • 测试迁移策略调整(有数据支撑,不是妥协):写了一个依赖扫描工具(提取每段定义的全局名 → grep 后续段引用),跑完 55 个段后 --leaf-only 没有任何输出 —— 不存在纯叶子段(段间共享显式全局如 time / base64,也有隐式的、供后续段继承的绑定)。
    结论:逐段迁移在这个文件结构下,边际成本已高于收益。 因此调整为:legacy 作终态(它是在用的测试文件、630 条断言、持续全绿,不是技术债)、新测试一律走新套件、迁移只做低风险部分 —— 让它自然冻结,而不是强行拆完。
  • 迁移期三条纪律(三次回滚换来的):① 迁移与修 bug 不混在同一次编辑;② 共享头部显式抽出(别让每个套件自己抄 import);③ 先做依赖扫描(只有叶子段适合独立成套件)。

验证

  • 双入口全绿:python tests/run_all.py → 19 suites / 881 checks / 0 failed;python _test_qoder.py → 630 checks / 626 passed / 0 failed / 4 skipped;
  • 4 条「改坏 → 必红」自证:parity(9 FAIL)· auth(7 FAIL)· log(10 FAIL)· effort(EXIT=1)—— 每条新套件都证明了自己真的在守,而不是「看起来绿」;
  • 一条工具经验:改坏点里含非 ASCII 时 PowerShell 的转义会坑人(试两次没命中),改用 Python 按行号替换一次成功 —— 已记进团队纪律。

v1.3.2

Choose a tag to compare

@shuishuipingan shuishuipingan released this 08 Oct 21:34

✨ 新增功能

① Anthropic 原生入口 /v1/messages —— Claude Code 这类 Anthropic 客户端可直接接入:

  • 新增独立转换模块 qoder_anthropic.py(零依赖纯标准库):入站映射(14 个字段)、出站(非流式 + 流式事件流)、usage 守恒映射、effort 断点映射(逐模型 clamp + 单调不降);
  • 接线的三条要害(都配了断言):① x-api-key 头(Anthropic 客户端不用 Bearer,漏了会全量 401);② 形态决策单点(结构化与否只判一次;两处各判会出现「桥接层以为结构化、flatten 走文本化」的错配);③ 守卫链逐字复用:sse_with_heartbeat(recover_leaked_tool_calls(iter_inner_sse(...), allowed_names=...)),且 allowed_names 必须取转换后的 chat 请求(用 Anthropic 原始 payload 会取不到工具名 → 回读守卫失效);
  • 泄漏回归(新入口):#8 / #9 / #11 三条历史样本走新入口 → 守卫仍然生效(有日志实证),#8 还能正确还原成 tool_use 块。新入口没有变成第三个绕过守卫的门;
  • 三条已知差异(已写进 README 已知限制):流式不做流内信封重开(message_start 已发出、重开等于两个 start,协议违规;改为发明确的 event: error 终态事件而非静默关流;非流式仍完整重试)· 默认不发 thinking block(Anthropic 要求 signature,我们生成不了也不伪造;QD_ANTHROPIC_THINKING=empty 为实验开关)· 入站 thinking 一律丢弃。

② 模型闸门:全局 banned_models + 每个 API Key 的 models 白名单(fnmatch 通配)。

  • 统一落点 _model_precheck、三个入口共用(chat / responses / anthropic)—— 不是三份实现,天然没有「某个入口绕过」的缺口(已用断言覆盖 /v1/messages);
  • 拒绝发生在本地:出站层与网络层两层计数均为 0;并含对照组(白名单内放行)与默认不变(空配置放行)—— 防「一律拒绝也绿」;
  • 同理,_clean_key_entry 是白名单重建,models 必须显式保留(否则每次保存被静默丢弃 —— 与早前 limits 同型的坑,已断言钉住)。

③ 会话亲和长度上限:QD_AFFINITY_MAX_MSGS(默认 0 = 关闭)。⚠️ 阈值没有照抄参考项目 —— 那张断连率表来自单一实例、333 个请求,上游也不同;等我们自己的数据再定。

🐛 问题修复:无

🎨 体验优化:Anthropic 入口的三条已知差异写进 README(读者需要知道边界在哪)。

⚠️ 其他变更

  • tool_choice="none" 形态对齐:从「连 tools 一起删」改为「保留 tools + 写回 none」。上游 body 对比确认只影响 tools 一个字段(其余 7 个非随机顶层键全同);开关 QD_TOOLCHOICE_NONE_DROPS_TOOLS=1 可一键回退。
    • ⚠️ 一条反例(如实登记):参考项目自己的注释说「其上游并不真正遵守 none」(保留 tools 后仍回 tool_calls),与我们工作包对 qoder 的实测相反。我们按 qoder 的实测默认新行为,但留了回退开关 —— 若观察到 none 下仍回 tool_calls,设它即回旧行为。
  • 出站收口(P1-2 第一步):把带凭证的直连收进统一出站函数(不变量基线 3 → 2,且中间态红过,证明断言在守);并判定「按账号出站代理」不做 —— 账号全部由用户本机导入(出口天然相同)、全局代理已零代码支持(HTTPS_PROXY,README 已补)、无任何「上游按 IP 风控」信号。若将来真撞上,最小方案约 25 行(已留档)。
  • 测试工程化:tests/ 编排现 15 个套件 / 819 断言;从旧单体文件已迁出 4 段(每批双入口对比、账目精确);迁移期两条纪律已写进 tests/README.md(路径一律基于 __file__;每套件自己的临时目录、不写仓库内共享路径)。

验证

  • 双入口全绿:python tests/run_all.py → 15 suites / 819 checks / 0 failed;python _test_qoder.py → 626 checks / 0 failed / 4 skipped;
  • 本轮新增能红证据 3 组:Anthropic 模块(改前 ModuleNotFoundError)· 接线(4 红 → 16 绿)· 模型闸门(4 红 → 16 绿,明细显示未实现时请求放行到出站口);
  • 旁路核查:确认模型闸门三入口共用同一判定,不存在「chat 被拦、messages 绕过」;
  • 一处自我更正也已登记:某次 run_all 出现过 1 failed,追查为撞上并发编辑的中间态(不是回归);重跑 0 failed。这也提醒:并行开发期的瞬时红要先排除中间态再当回归。

v1.3.1

Choose a tag to compare

@shuishuipingan shuishuipingan released this 08 Oct 21:18

✨ 新增功能

① 测试工程化 —— tests/ 并发编排上线:

  • tests/run_all.py:并发、每套件独立日志、失败打 tail 25 行、强制 UTF-8、无匹配套件返回 2(防「0 passed / 0 failed → exit 0」这种假绿);
  • 11 个套件 / 747 条断言,含工作包点名的三类空白:代理不变量(参数穿透式断言 + 基线登记)、keep-alive 早拒(裸 socket)、前端 DOM 断言(首个 .js 套件,自读 dashboard.html + 内联元素桩);
  • 兼容入口保留:python _test_qoder.py 照旧可用;套件正逐段从它迁出(本轮已迁 2 段,legacy 仍承载其余 —— 迁移期间总数只增不减);
  • ⚠️ 一处更正:工作包让照抄的 wb tests/_dom_stub.js 并不存在(递归查 0 命中);它的真实做法就是「每个套件自读 dashboard.html + 内联手写约 15 行元素桩」,我们按后者实现,没有引入 jsdom(保住零依赖)。

② 账号退避 / 熔断 / 降权三族:

  • note_soft_rate(软限流指数退避:600 → 1200 → 2400,封顶 7200)· note_failure(硬错误熔断)· note_unknown_failure(未知错误降权)· note_success(统一复位入口);
  • 接线 6 处 —— 这正是教训所在:三族在接上调用点之前是死代码(有断言证明接线前 6 处计数全 0);
  • 防误接(反例断言):直连 401/403 · 信封 429(10605 排队,接了会让一次排队冷掉全账号) · 内容审核 · 402 余额不足 · 单号池 → 三族零调用;
  • 单号池不接(total>1 条件):单号池接熔断/长退避 = 变相打死账号;
  • 运维恢复:三族状态是内存态(不落盘)→ 重启即清 / 等窗口自动过(30 分钟起、封顶 6 小时);面板的「重新启用」走 clear_error,有意不清熔断。

③ 用量字段与聚合:

  • usage 行补 reasoning_effort / reasoning_effort_requested / key_id(全脱敏);三态可辨:同值 = 原样透传 · 不同 = 被挪档 · 空串 = 请求了但未下发;
  • 新增 GET /usage/by-key(与 /usage/by-account 逐字对称;sum(by_key) == sum(by_account) 不变量已断言;缺 key_id 的行归 (no-key) 桶、不丢弃);
  • 面板:最近请求表加 Key 与 档位 两列(档位三态一眼可辨)· 指标页新增按 API Key 用量透视(Key / 请求数 / 输入 / 输出(含思考)/ 缓存 / 总 token / 占比 / 积分 / 模型前 5,按 token 降序)。

🐛 问题修复

  • 临期积分分派:管道就绪、当前不触发 —— 实测上游 /quota/usage 不返回 per-package 到期字段(只有顶层 expiresAt,且它可能是「按月重置」而非「到期作废」)。处置:防御性透传(上游一旦补字段即自动生效、无需改码)+ 刻意不把顶层 expiresAt 贴到包级 —— 硬贴会让「按月重置」的额度被优先消耗(反向浪费)。

🎨 体验优化:见 ③ 的两处 UI(档位三态 + 按 Key 透视)。

⚠️ 其他变更

  • 测试方法论(写入团队配方):parity 类断言只能用 requests / token / credit(models 是前 5 名截断的展示字段,拿它求和必假失败);error 行两侧都跳过;窗口按 row["at"](epoch)过滤、完全不看 iso,且缺 at 的行被当 0(夹具必须给 at);窗口边界是闭区间;桶数不等是正常语义(一个 Key 可打多个账号)。
  • 验证配方补充:任何「逐字节一致」类对照必须用固定夹具或同一份快照 —— 真实日志仍在被追加(analytics 统计 error 行、 by_account 跳过),否则会把「日志在长」误判成「改动引入差异」。

验证

  • 双入口全绿:python tests/run_all.py → 11 suites / 747 checks / 0 failed;python _test_qoder.py → 658 checks / 0 failed / 4 skipped;
  • 每个新套件都有「改坏 → 必红」自证(本轮 6 条:护栏等号边界 · 代理绕过点 · 前端配色 · 三族接线计数 · parity 口径 · dashboard 渲染);
  • 三族接线:接线前 6 红(计数全 0,正是 6 个待接点)→ 接线后 13 绿;
  • parity 修绿的过程本身也是个证据:期望值先按实测口径重算(无窗口 645 / day 145),而不是改实现去迁就断言。

v1.3.0

Choose a tag to compare

@shuishuipingan shuishuipingan released this 08 Oct 20:49

自 v1.2.12 以来的主要变更概览:机器身份落盘缓存 + 种子机制修正(容器重建不再换设备)、VM 抖动多数表决(不把身份固定在少数派)、工具历史结构化直传、只读积分路由 GET /credits/summary、usage credit 统计修复 —— 以及本版两项:事实性限额护栏与用量区间 / 时间序列。

✨ 新增功能

① 事实性限额护栏 —— 账号级预算闸门:用尽自动换号,而不是等上游返 402/429。

  • 四条护栏:余额地板(reserve_credits)· 当日 token(daily_token_limit)· 当日 credit(daily_credit_limit)· 单模型当日 token(model_daily_token_limit);
  • 三层作用域(global + realm 两级):留空 = 继承、显式 0 = 关闭;
  • 后台余额刷新:每 tick 最多刷一个最陈旧账号(30min tick + 12h TTL),惰性启动(阈值全 0 时线程根本不存在,零副作用);
  • 计数来自本地 usage 折叠 —— 不新增任何上游调用;
  • 402 / 额度停放:停到 UTC+8 每日 10:00(qoder 自己的重置窗口,非通用 4am),仅余额恢复可提前解封;
  • 临期积分优先分派:7 天内到期的额度优先消耗(smooth weighted round-robin),默认窗口 0(关);
  • 面板:设置页限额表格(带量纲提示)+ 账号行四态徽标(未启用 / 未知 / 已暂停 / 正常)。

② 用量区间与时间序列:/usage/analytics · /usage/by-account · /usage/perf 支持 ?range=day|week|month|all|custom&since=&until=;新增 /usage/timeseries;realm=all 现在是真正的通配符(此前 /usage/perf 采样恒为 0)。

🐛 问题修复

  • 限额设置此前不会生效(接线缺口):没有任何生产代码把 limits 推给账号池 —— 配置写进 settings.json 就停住了,阈值恒 0、守卫永不触发。现在启动时与设置保存后各推一次(free 名单同批推送,避免空集拦住全部模型);
  • 后台刷新线程此前会静默死掉:AccountPool.log 默认实现只接受一个位置参数,而刷新线程传了 level= → 一 tick 就 TypeError 退出,整条刷新链路失效;
  • 设置页改阈值现在会打印状态翻转日志(此前只有其它路径打)。

🎨 体验优化:面板两处(限额表格的量纲提示、账号护栏四态徽标)—— 见上 ① 末条。

⚠️ 其他变更

  • ⚠️ 量纲说明(重要):qoder 的 credits 是积分点数(实测账号区间 0~1100),不是 token 量级 —— 照 token 量级填阈值会永远拦不到任何账号;
  • 默认全部关闭(0) —— 不给任何非零默认值;
  • 参考材料:本仓 .team/_gap/(双向差距分析 6 份文档 + 实测证据)。

验证

  • 全量 693 checks / 0 failed;
  • 端到端(真实链路,非桩):配置 → quota_enabled=True → 后台线程启动 → 守卫命中 → pick 返回 None → 网络层调用计数 0(确实没发上游)→ 复位后账号恢复;
  • 配置层 A1-A5 · 守卫层 B1-B6 · 汇合点 C1-C3 · 折叠 D2-D4 · 后台刷新 E1-E5(含惰性启动)· 派发偏好 F1-F6(含「默认关时与改动前等价」「永不过期占位不参与权重」两条边界)· 停放时钟 G1-G2 全部通过;
  • 一条流程留痕:这条端到端判据前五轮不闭环,根因是实现侧接线缺失 + 测试侧判据位置错误各占一半 —— 判据应拦在网络层(是否真的出网),而不是「open_upstream 是否被调用」(网关正常路径本来就会进它、由它内部选号)。

v1.2.19

Choose a tag to compare

@shuishuipingan shuishuipingan released this 08 Oct 18:53

✨ 新增功能:无

🐛 问题修复

  • usage 记录(与面板)的 credit 统计恒为 0(issue #22,by @jianjiuss):_extract_usage() 读的是单数 usage.get("credit"),而上游给的是复数 credits(附 original_credits / billable)—— usage.get("credit") 恒为 None,于是所有 usage 记录都记成 0(报告者实测 426 条流式 + 16 条非流式全部为 0)。
    • 现在复数 credits 优先、保留单数兜底(防其它区域字段差异);
    • 顺带记录 original_credits(参与聚合 —— 折扣时段对账要用)与 billable(仅写进行、不进聚合 —— 布尔求和没有意义)。

🎨 体验优化:无

⚠️ 其他变更

  • 历史数据不可回填:修复只影响此后写入的行;此前记录里的那个 0 是永久性的(当时的原始 credits 从未落盘)。我们不会在日志或文档里暗示它可恢复。

验证

  • 全量 657 checks / 0 failed([39] 段 10 条);
  • 修复前的红色证据已留存(落盘前抓到的原始输出:复数 credits=97.96 → credit: 0;两者都有时旧实现取单数)—— 没有它,这个回归网就是空转;
  • 变异自证 4 条:改回只读单数 → 必红;单数优先 → 必红;抽掉 original_credits → 必红;把 billable 塞进聚合表 → 必红;
  • 面板不受影响(已 grep 确证:它读的是账号对象的 credits.remain、模型元数据的 credits、签到侧的 earned_credit,没有一处读 usage 行的 credit);
  • 未覆盖(如实):credits 为字符串或 None 的形态(or 链会把 "0" 当假值,属理论边界)。

v1.2.18

Choose a tag to compare

@shuishuipingan shuishuipingan released this 06 Oct 13:17

✨ 新增功能

  • GET /credits/summary:API-key 级鉴权的只读积分汇总(issue #21)——给下游集成(如网关池面板)展示各账号池余额用。
    • 鉴权走 _key_ok()(API Key 或面板会话均可,未设 Key 时放行),刻意不放进 _is_panel_route() —— 否则就会要求 X-Panel-Token,与用途相反;
    • 只返回本地已登记的 credits,绝不触发任何上游查询 —— 这是本 issue 的核心约束(对照 GET /accounts/credits:那个会逐号 fetch_credits() 强刷,适合作者手动点、不适合机器轮询);
    • 返回 { by_realm, totals, accounts[], note },每行含 {uid, nickname, realm, credits_remain, credits_used, credits_size, registered_at};
    • 新鲜度语义写在字段名与 note 里:返回的是「上次登记时的值」(签到写权威值 + 每次消耗回写),不是实时余额;
    • 未登记过积分的账号照常列出但 credits_* / registered_at 为 null 且不计入汇总;空池 / POOL=None 返回空结构而非报错。

🐛 问题修复:无

🎨 体验优化:无

⚠️ 其他变更

  • README 的接口一览与说明补上该路由(含「为什么不能拿 /accounts/credits 轮询」的对照)。

验证

  • 全量 647 checks / 0 failed([38] 段 12 条);
  • 命门:fetch_credits() 桩计数 = 0 —— 且这次用真实 handler 验证(ThreadingHTTPServer + 后台线程 + urllib 真请求,鉴权 / 路由匹配 / JSON 写出全走真代码路径);
  • 能红证据:把实现改成「先 fetch_credits() 再返回」→ 命门断言必红(变异下桩被调 3 次 = 3 个账号各一次);
  • 鉴权矩阵:有效 key 200 / 无效 key 401 / 无凭据 401 / 面板会话 200 / 未设 key 放行 200(全部 0 次 fetch);
  • 边界:空池 / POOL=None / 无 credits 账号 / 部分 credits 全绿。