Repository navigation
Conversation
ZCode 活动套餐通道用 HTTP 200 + application/json 返回
`{"code":1005,"msg":"exceed quota limit"}`。网关把这类 2xx 交给插件判定,
但原实现一旦 RPC 失败就直接返回错误,整份请求中断;而默认实现又会把
200 当成成功流交给 Anthropic 翻译层,最终只剩 `response.failed`、
`output` 为空。
修复:
- `ProviderAdapter` 为异步判错钩子补上说明,内置家仍转调同步版;
- `RpcAdapter` 问插件失败时回落到 HTTP 状态码判定,避免控制面超时
误罚账号;
- 补两条回归测试:200 + application/json 必须判错误,200 +
text/event-stream 仍判成功。
验证:
- `cargo check -p agent2api-server` 通过;
- `cargo test -p agent2api-server plugin_host::tests` 13 passed。
注:上游 beta 的 `provider_loop/tests.rs` 与 `api/pipeline.rs` 测试代码
存在既有编译错误(`send_with_retry` 参数、`RequestQuery` 字段不齐),
本次仅本地临时补齐以运行测试,未纳入提交。
修复:插件 2xx 业务错误不再变成空回答
## 问题 普通转发请求遇到 429 时未进入错误分类,上游错误被当成正常流透传, `attempts=1、error=null、stickyHit=true`,即使队列里还有可用账号也一直 重试同一个仍在冷却的账号。 ## 根因 插件 SDK 默认 `is_error_response()` 恒返回 false,网关 `RpcAdapter` 把 插件答复当作唯一门禁,因此非 2xx 也可能被判成「不是错误」, `classify / 冷却 / 换号` 链路从未执行。 ## 修改 - `RpcAdapter::is_error_response_async`:非 2xx 一律按错误处理;插件只允许 把 2xx 额外判为错误,不能把非 2xx 降级为成功。 - 空响应体 429 兜底为短期限流:优先采用有效 `Retry-After`(秒数或 RFC 2822), 缺失时落现有限流默认时长;客户端错误回带 `Retry-After`。 - 选路保留既有「冷却优先于会话粘性」语义,新增契约测试钉住该行为。 - 请求明细的 `notice` 区分未换号原因:禁止换号 / 无候选账号 / 换号预算耗尽。 - 显式关闭失败换号、指定账号测试、其他状态码处理保持既有语义。 ## 验证 - `local_failure::` 8 passed;`routing::tests::` 12 passed;`plugin_host::` 29 passed。 - 全量 `--lib` 285 passed / 1 failed,失败项为既有无关的 `codearts_daily_pool_envelope_cools_to_the_boundary_instead_of_ten_minutes` (CodeArts 已迁插件、`adapter_for` 现返回占位符),本次改动未触碰该家。 - 未做真实上游验证。
修复:429 后反复命中同一账号、不换号
AutoClaw 上游把「GLM-5.3 免费额度已用完,开通连续包月会员继续使用」 包成 HTTP 403 + code 810000。插件 classify 只按状态码走:403 不在 401 / 429 / 内容拦截那张表里,落进 Fatal 原样透传 —— 账号不冷却、 不换号,客户端每轮都撞同一面墙(近 72h 生产库 403×75 全部 attempts=1、无 retries、无冷却)。 - `RpcAdapter::classify_error_async`:插件分类结果之后,对 AutoClaw 两地(autoclaw / autoclaw-intl)的 403 + code 810000(或正文含 「免费额度已用完」)做网关侧兜底覆盖 → QuotaLimited。 - reset_at:上游不给恢复时刻,免费额度按天重置,取下一个北京时间 (UTC+8)零点 —— 与 CodeArts 福利日池、degrade 的零点口径同源。 - 其它 403(含 pay-view 810001)与非 AutoClaw 家不受影响。 - 新增 2 条单测:403+810000 → QuotaLimited 且恢复时刻落北京零点; 810001 对照组仍是 Fatal。 - `plugin_host::` 31 passed(29 既有 + 2 新增)。 - 全量 `--lib` 287 passed / 1 failed,失败项仍为既有无关的 `codearts_daily_pool_envelope_...`(插件化后 adapter_for 返回 占位符所致,与本改动无关,c3806db 验证记录里已注明)。
747f4c2(feat: add private plugin source with author metadata)加了 作者展示源码与 i18n 词条,但没重建入库产物 —— beta 的 frontend-artifacts 闸从此红着。按 CI 同款三步构建重建: ui-islands / mobile / i18n,产物与源码重新逐字节对齐。 验证:本地重建后 `git status --porcelain -- desktop-tauri/ui` 为空 (与 CI 判据一致)。
修复:AutoClaw「免费额度用完」403 按限流处理
issue aimod-cc#6:生产库实测 AutoClaw 家额度耗尽时除 403+810000 外,还会以 402 / 406 + 空响应体的形态出现(近 72h:autoclaw 402 ×45、 autoclaw-intl 406 ×75,全部 attempts=1、无冷却、粘住同一账号反复撞)。 修改: - `autoclaw_quota_fallback` 状态码集合从 {403} 扩到 {402, 403, 406}; 有 body 证据(code 810000 / 「免费额度已用完」)→ 冷却到下一个北京 零点(口径不变)。 - 新增分支:空响应体 + 402/406 + Fatal → QuotaLimited,reset_at 不猜 (None),由上层 provider_loop 优先接上游 Retry-After、缺失时落默认 短期冷却 —— 与 429 空体兜底(PR #5)同一收口。 - 带 body 的 402 / 406(真实计费墙等)不受影响,仍走原分类。 验证: - 新增 2 条单测:空体 402/406 → QuotaLimited 且 reset_at=None; 带 body 的 402/406 对照组仍是 Fatal。 - `plugin_host::` 34 passed(32 既有 + 2 新增)。
## 问题
`config_migration::copy_dir_all` 递归拷贝整个旧配置目录
(`~/.workbuddy-proxy` → `~/.agent2api`),没有按文件类型过滤。这段代码
写于「配置目录只有 config.json / accounts.json / logs.jsonl 这类小文件」
的时代(见模块头),当时整份拷贝的代价可忽略。
统一库落地后 `{config_dir}/agent2api.db` 是 GB 级单文件,还带 `-wal` /
`-shm`。只要旧目录里存在一份库(升级路径上的常见形态),一次启动就会
凭空多出「一整份库」的磁盘占用 —— 用户实测 C 盘被吃满。
## 修改
- `copy_dir_all` 跳过 SQLite 库文件:主库 + 一切 `-` 开头的伴随后缀
(`-wal` / `-shm` / `-journal` / WAL2 的 `-wal2`)。
- 判据按**文件名**而不是体积:体积阈值会随数据增长失效,文件名是
结构化事实(`db::FILE_NAME` + SQLite 固定后缀)。
- 跳过时打一行 `[Config]` 说明,用户能在日志里看到「库没被搬」。
- 用户手工改名的 `agent2api.db.backup` 不跳过:它不是活库,搬走不占
运行期资源,也保留了用户自己留的那份。
## 为什么跳过是安全的
- 旧版程序根本不写 `agent2api.db`(统一库由本程序引入),迁移的语义是
「把旧版的数据搬过来」,库文件不属于旧版数据;
- 若旧目录里真有一份库,它来自更早的本程序运行 —— 那种情况下新目录
通常已存在,迁移在第一次启动就完成了,走不到这条拷贝路径。
## 验证
- 新增 3 条测试:库三件套被排除、普通配置与手工副本不受影响、
端到端 `copy_dir_all` 拷贝后目标目录里没有库文件。
- `config_migration::` 3 passed。
## 问题 库开的是 `auto_vacuum=0`:`DELETE` 只把页标成空闲,文件**不会缩小**。 用户实测库里留着 340469 个空闲页(约 1.3GB),而真实数据只有 1.1GB —— 文件 2.5GB,设置页照实显示,用户以为程序在偷偷占盘(C 盘爆满)。 唯一回收路径是请求日志页的「清理」弹窗里手动压缩,但界面上没有任何 「这个库有多少是空洞」的读数,用户没有判断依据。 ## 修改 - `RequestStats::free_space()`:读 `PRAGMA page_size / page_count / freelist_count`(都是 O(1) 头字段,不扫表),给出 `(空闲页数, 总页数, 可回收字节)`。 - `stats()` 增加 `reclaimableBytes` 与 `freePageRatio`(0..1,三位小数)。 - `GET /api/storage` 的 `database` 一并透出这两个字段。 ## 为什么不自动 VACUUM VACUUM 需要**约等于整库大小**的临时空间,且持锁期间会阻塞全部数据库 读写。在只剩少量空间的盘上自动执行,恰好会把「空间紧张」变成「空间耗尽」。 所以这里只把判据交给界面与用户,由用户显式触发既有的「压缩」动作。 ## 验证 - `cargo check -p agent2api-server` 通过。 - 全量 `--lib` 293 passed / 1 failed(既有无关项 `codearts_daily_pool_envelope_...`,插件化后 adapter_for 返回占位符所致)。
独立审查指出的两点:
1. 810000 判据在扩到 402/406 时被顺手放开了状态码限制 —— 原判据只认
403,放开后任何状态码带 810000 都会被当成额度耗尽(例如 500 上游故障
被误冷却)。现在先收窄状态码集合到 {402, 403, 406},其它一律原样返回。
2. 「空响应体」原来只看顶层 message 是否为空、code 是否缺省,会把
`{"error":{"type":"insufficient_quota"}}` 这类**有结构化内容**的正文
误判成空体。新增 `is_blank_error_body`:逐值检查,只含
null / 空串 / 空对象 / 空数组才算空。
顺带:空体兜底分支保留分类器原本解析出的 upstream_code,不再无条件写 None。
验证:新增 3 条对照测试(结构化正文不兜底、402/406 之外的状态码不兜底、
非 AutoClaw 家不受影响);`plugin_host::tests::` 20 passed。
## 问题
上一版把「空响应体 + 402/406」一律兜底成 QuotaLimited。406 这一半是错的:
AutoClaw 的 406 空体是**上游预校验拒收的统一形态**,插件侧实测过三个独立
成因(max_tokens 取小值 / system 首句命中指纹 / 请求形态风控,见
`autoclaw/mod.rs` 的「四道上游闸」与 `prompt.rs` 的 `UPSTREAM_406_HINT`)。
把它当额度耗尽有两个用户可见的坏结果:
- 一次提示词闸门拒收 → 冷却一个**健康**账号并换号;而同一份 body 换谁的
账号都会被拒,等于空转一个冷却窗口、还少一个可用账号;
- 把插件拼好的「该改什么」提示替换成「请求额度受限」,用户失去唯一线索。
## 修改
- 空体兜底收窄到 **402**(Payment Required,语义上就是额度/计费受限)。
406 保持插件给的 Fatal 原样透传,提示文案逐字保留。
- `is_blank_error_body` 增加占位词识别(`is_placeholder_message`):
`上游错误` 与 `上游返回 <数字>: 上游错误` 都算「没有正文」。
修的是审查指出的「两条投递路径结论不一致」——状态码路径拿到的是真空串,
`StreamEvent::Done(Err(provider_http:…))` 路径拿到的是插件渲染过的占位文案,
只判空串会让后者永远不触发兜底。
- 占位词判据按**形状**识别而不是「包含」:`上游错误(余额不足)` 这类仍含
上游信息的文案不算空,避免真实计费墙被误判。
## 测试
- `autoclaw_empty_body_402_falls_back_to_quota_limited`:四种空体形状
(`{}` / `{code:null,message:""}` / `{message:"上游错误"}` /
`{message:"上游返回 402: 上游错误"}`)都兜底。
- `autoclaw_empty_body_406_stays_fatal`:空体与带提示两种文案都保持 Fatal,
且文案逐字不变。
- `autoclaw_402_message_with_content_is_not_treated_as_blank`:含信息量的
402 文案不被当空体。
- `cargo test --lib plugin_host::tests::autoclaw` 7 passed。
1. 设置页显示库内可回收空洞(reclaimableBytes / freePageRatio): auto_vacuum=0 下删除只标空闲页、文件不缩小,2.5GB 的库可能有一半是空洞; 界面现在会写「可回收 X(Y%)」,并指路请求日志页的「清理 → 压缩」。 条数与空洞在同一次持锁里读(free_space 收 guard,避免不可重入死锁)。 2. backups/ 增加保留上限:整库恢复的 pre-restore-*.db 与云同步的 cloud-sync-*.json 各只留最近 2 份,超出按 mtime + 名字删最旧。 两者都是整库级留影,此前从不清理,操作越多占盘越多。 3. 请求日志分析脚本退出时删除自己建的 %TEMP% 快照(含异常 / Ctrl-C): 以前一份快照 = 一份整库且从不清理,实测残留过 4.7GB。 SKILL.md 同步写明快照体积与 --no-copy 直连。
Windows / macOS 默认文件系统上 AGENT2API.DB 与 agent2api.db 是同一个文件, 按精确匹配判会把它整份拷过去 —— 正是本 PR 要堵的那条路径的漏网形态。
审查指出:仅凭前后缀匹配会导致用户放在 backups/ 里的同名前后缀文件
(例如 cloud-sync-notes.json、pre-restore-manual.db)被误当成程序备份计入并删除。
现改为按程序真实生成的两类文件名做完整结构校验:
- pre-restore-{YYYYmmdd}_{HHMMSS}-{随机串}.db
- cloud-sync-{YYYYmmdd}-{HHMMSS}.json
并补测试证明 9 种常见类似文件名绝不被触碰。
修复:启动迁移不再整份复制 SQLite 库;暴露库内空洞读数
修复:AutoClaw 额度耗尽的 402 / 406(空响应体)也按限流处理
Tauri 新版 utils.nsh 的 CheckIfAppIsRunning 改用 RestartManager 宏检测「应用正在运行」,但本仓库自带的 nsis-template.nsi(官方模板的固定快照)没有随之补上 !include Win/RestartManager.nsh。结果是 makensis 报 macro named RestartManager_StartSession not found 并中止整个 tauri build —— 二进制能编出来,但安装包无法生成。 补上这一行 include 后本地打包通过。
issue aimod-cc#6 里 CatPaw 的 1 MiB 入口墙回 413 时,有状态路径把分类默认成 Fatal, 于是队列里每个候选账号都会把同一份超限的 body 再撞一遍 —— 客户端白等 N 个 往返,最后拿到的仍是同一个 413(文案还是一页 openresty HTML)。体积拒绝与是 哪个账号无关,换号不会让这份字节变小。 - 新增 payload_bound_failure:只认 Fatal + 413。404 仍换号(别的账号 / 别的家 可能有这个模型),400 / 409 / 429 仍走各自的既有路径。 - 有状态路径(会话式)与无状态路径的动作 3(换号顺延)都在换号前拦下它, 原样把插件翻好的可读文案交回客户端,并在尝试明细里留一行「未换号(请求体 超过入口上限,换账号无效)」,排查时不必再猜为什么没顺延。 顺带修一条存量红用例:codearts_daily_pool_envelope_cools_to_the_boundary_* 原先走 adapter_for(ProviderKind::CodeArts),而迁移完成后它只返回「未安装插件」 的占位适配器(分类恒为 Fatal)—— 用例测的已经不是真实链路了。改为走插件错误 投影(与生产 classify_conversation_error 同源),并新增两个 #[cfg(test)] 的 只读缝。改动前这条在 beta 上就是红的(已用 stash 验证)。 测试:cargo test --manifest-path desktop-tauri/src-tauri/server/Cargo.toml 304 passed / 0 failed(新增 1 条 payload_bound 判据;原红用例转绿)。
`models/shape.rs::list_item` 是**逐键白名单重建**,不是透传。上一版只在插件侧 写了 `maxRequestBytes`,宿主没有对应的映射分支 —— 这个键只活在插件内部, `/v1/models` 上一个字节都看不到,等于白声明。 - `list_item` 新增 `maxRequestBytes` → `max_request_bytes` 的映射(缺失即不出现, 与 `max_output_tokens` 等键同一口径)。 - 插件侧那句「聚合层不认识也不会丢」的注释是错的,改成如实说明必须两边同时改。 - 新增用例 `the_entry_body_limit_reaches_the_public_model_list` 钉住这条映射, 并带一条反面对照(没声明的家不该多出这个键)。 白名单的形状决定了「加了字段却忘了映射」不会报错、只会静默丢失 —— 这条用例 就是为了让下一次漏改立刻变红。 测试:cargo test --manifest-path desktop-tauri/src-tauri/server/Cargo.toml 305 passed / 0 failed。
上一版把 413 一律当成「换号无效」,会把跨提供商回退一起掐掉:字节墙是**每家 自己的**(CatPaw 1 MiB、CodeArts 约 6 MiB、Loomy 的 APIG 约 12 MB),同一份 900 KB 的会话在 CatPaw 上 413、换一家可能就是 200。判据因此按家收窄。 - defer_to_next_account 新增 cross_provider_only:挑出候选账号之后才判, 只有「还是同一家」才收手;判据只有一份,两条调用点各自传值。 - 构造期本地失败那条路传 false(与体积无关)。 - 用例补上同家 / 跨家两条断言。 - shape.rs 的测试模块移回 normalize_model_id 之后 —— 原先插在文档注释与 函数之间,把 doc comment 挂到 mod 上了。 测试:305 passed / 0 failed。
插件侧已改成 413 带状态码过线;宿主这一侧原本没有用例证明「信封 → 状态码」 这一截真的通。补上:413 必须还原、且不能被 is_quota_limit 认成限额(那会给 账号挂冷却),没有信封的错误仍是未分类。 测试:cargo test --manifest-path desktop-tauri/src-tauri/server/Cargo.toml 306 passed / 0 failed。
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
This was referenced Nov 3, 2026
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 3, 2026
闸门报出 5 个产物文件与源码不同源:i18n/zh-Hant/common.js、islands/ui.js、 m/mobile.css、m/mobile.js、sw.js。成因是 upstream aimod-cc#415/aimod-cc#418/aimod-cc#449 摘樱桃后 源码已变、产物仍是各自分支上的构建结果。 本提交内容直接取自 rebuild-artifacts 工作流(与 frontend-artifacts 闸门同一 环境、同一条重建命令)的产物快照,因此逐字节同源。
xiarunjie
added a commit
to xiarunjie/Agent2API
that referenced
this pull request
Nov 4, 2026
本提交把 main 的内容整体切到上游 v3.4.0 的发布状态(tag v3.4.0 = 31bc7bd,其后 0add462 重建了手机端与 Service Worker 产物并把版本号跟到 3.4.0): · 此前在 beta.6 基线上同步进来的 8 条上游 PR 内容(aimod-cc#305 aimod-cc#311 aimod-cc#343 aimod-cc#415 aimod-cc#417 aimod-cc#418 aimod-cc#434 aimod-cc#449)不再出现在 main 上 —— 它们都改在 beta.6 的旧落点,其中相当 一部分与 3.4.0 自带的实现(路由工作台、会话图片压缩)在同一批文件上各走了 一条路(实测 43 个文件内容分叉)。旧状态完整保留在 archive-main-pre-340 (分支与 tag 同名),需要时可逐条取回。 · 前端产物取自上游 3.4.0 自己的构建结果,与上游源码逐字节同源。 · 基线切齐后,「哪些上游 PR 还能合」可用硬证据重新判定,而不是在分叉状态上叠。 上游对照:aimod-cc/Agent2API @ 0add462(tree 4ffbf15)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
背景
issue #6 的第 3 条诉求:413 出现时给客户端有意义的错误,而不是 openresty 默认页。原回复只解释了「1 MiB 是字节限制、不是上下文限制」,没有覆盖这一条;而实际行为比「文案不好看」更糟:
Fatal;HTTP 413: <html><head><title>413 Request Entity Too Large…体积拒绝与是哪个账号无关 —— 换号不会让这份字节变小。
改动
payload_bound_failure:只认Fatal + 413。顺带修一条存量红用例
codearts_daily_pool_envelope_cools_to_the_boundary_instead_of_ten_minutes原先调adapter_for(ProviderKind::CodeArts)。迁移完成后它只返回「未安装插件」的占位适配器(classify_conversation_error恒为Fatal),也就是说这条用例测的已经不是真实链路了。改为走插件错误投影(与生产classify_conversation_error同源),并新增两个#[cfg(test)]的只读缝。这条在改动前的 beta 上就是红的(已用
git stash在原始工作树上复现验证),本次一并转绿。测试
cargo test --manifest-path desktop-tauri/src-tauri/server/Cargo.toml→ 304 passed / 0 failed(新增 1 条a_payload_bound_failure_does_not_switch_accounts,含 400/404/429/5xx 与 QuotaLimited 的反面对照)。配套
插件侧同分支 PR:ProvidersPlugin/Agent2Providers#31
🤖 本条回复由 AI Agent 生成
Generated by an AI agent.