Releases: collegeming/cpa-jethub-plugins
Release list
v0.11.0
Full Changelog: v0.10.0...v0.11.0
v0.10.0
v0.9.0 — 静态表不是目录
修复
1. cline-free/gemini-3.8-flash 调用必失败(HTTP 502)
上游已结束该模型的免费推广(实测 2026-10-02):
| 检查项 | 结果 |
|---|---|
recommended-models 的 free 数组 |
4 条,不含 gemini |
GET /api/v1/models(464 条) |
cline-free/gemini-3.8-flash 零命中 |
| 直连 chat/completions | 404 {"error":"model not found"} |
CPA /v1/models |
仍发布 Gemini-3.8-Flash ← 故障 |
插件的静态回退表(元数据快照)被当成了目录使用。现在静态条目只有在仍被某个在线来源背书时才发布:
- 免费家族以
recommended-models为唯一权威(/api/v1/models永远不含cline-free/*); - 该端点必须真的返回内容才算权威;任一来源不可用时整表照旧发布(列表为空比列表略旧更糟);
- 被撤下的条目不再发布并记 warn。
上游恢复推广时会自动恢复发布,静态表的 65536 输出上限一并生效,无需改代码。
2. 日志字段白名单导致告警失效
CPA 控制台格式化器只打印 logFieldOrder 白名单内的字段(v8.0.4),自定义 key 不报错、直接消失。{"models": ...} 落盘后只剩 provider=cline,模型名丢失;v0.8.0 引入的 codearts 剥图告警里 images 计数同样从未显示过。两者改为拼进 message:
[warn] cline 静态目录表条目已下线,不再发布:cline-free/gemini-3.8-flash provider=cline
[warn] CodeArts 剥离了请求中的图片(模型为纯文本),共 4 处 model=deepseek-v4.1-flash
3. 发布流水线:五平台产物首次全部构建成功
此前每一次 tag 推送都失败,release 里始终只有 linux/amd64。三个独立原因:
| 现象 | 根因 |
|---|---|
| darwin 任务首行退出 | macOS 用 bash 3.2,declare -A 报 invalid option 直接终止 |
| darwin/amd64 排队 24h 未跑 | macos-13/macos-14 已退役,改用 macos-15-intel/macos-15 |
| windows 15/15 链接失败 | Go < 1.27 把输出名不加引号写进 .def,GNU ld 把 0.1.0 当浮点数解析 |
最后一项的触发条件是点分版本号,不是横杠(LIBRARY a-b-c.dll 正常,x0.1.dll 失败)。改法:Windows 先链接到不含点的 <id>.dll,再改名为 <id>-v<version>.dll;CPA 按文件名推导插件 id,与 .def 内的 LIBRARY 名无关。
本次 release 首次包含全部 5 平台 × 15 插件 = 75 个 zip(此前仅 15 个 linux/amd64)。
验证
- 发布产物部署后
/v1/models不再含Gemini-3.8-Flash,调用返回400 model_not_found(而非 502) MiMo-V2.6-Flash等仍在线的免费模型正常 200- 带图请求全部 200,
not multimodal计数保持 0,剥图告警正确显示数量 - cline 新增 3 个回归测试并做反向验证(禁用背书判断后立刻转红)
- 本地用 CI 同款 go1.26.0 构建 windows/amd64 15/15 成功;linux 回归 15/15
- CI 运行 37026777370 五个构建任务 + publish 全绿
部署提示
config.yaml 里指向该模型的 oauth-model-alias 条目需一并删除(文档 §3.4 已更新)。若上游恢复推广,再加回即可。
v0.8.0 — codearts 图片降级,不再整单 406
修复:codearts 不再因为图片整单失败
CodeArts 的模型都是纯文本,收到任何带图片的请求会整单拒绝:
InferHub.001001020.406: The request model is not multimodal
拒绝的粒度是整条请求,不是最后一条消息。 实测(每形态 12 次):
| 请求形态 | 200 | codearts 406 |
|---|---|---|
| 纯文本 | 12 | 0 |
| 图片在最新一条 | 12 | 6 |
| 图片只在历史里 | 12 | 5 |
第三行是这类报错最难排查的地方:最新一条是纯文本,表面上没有任何东西提示"有图片"。
改成什么
图片 part 在发往上游之前被换成文本占位符,并记一条 warn 日志:
[image omitted: this CodeArts model accepts text only]
为什么是降级而不是拒绝:DeepSeek-V4.1-Flash 是同名池化模型,四个上游发布同一个名字,只有部分能读图(实测同一张纯蓝图片,sensenova-max 答 Blue,codearts 报 406)。拒绝会让整个名字被一张图打挂;降级则让该上游诚实地说"看不到图",同时其它渠道照常给出完整答案。
用 400 拒绝是错的:CPA 会把 400 判为请求方错误并中断跨渠道故障转移,用户什么都拿不到。
实测效果
修复前后各跑一轮 A/B/C(每个形态 12 次):
| 406 次数 | |
|---|---|
| 修复前 | 5–6 / 12 |
| 修复后 | 0 / 12 |
同一次带图请求命中 codearts 时答 Unknown(诚实),命中其它渠道时答 Blue(正确)。
顺带查清了两个 CPA 行为(v8.0.4 源码 + 实测)
- 冷却是按「凭据+模型」,不是按渠道整体。 同一账号下模型 A 被限流不影响模型 B(实测 cline 上 DeepSeek 429 时,同凭据的 Nemotron 仍 200)。
- 冷却时长是固定阶梯
1s → 2s → 4s → … 30min,上游回复里的Try again in 14h 53m到不了 CPA:核心路径不解析 body 文案,且插件 ABI 的错误对象只有code/message/retryable/http_status四个字段,没有时长字段,auth.save写回的next_retry_after也会被宿主覆盖。因此不存在"针对某渠道模型设置冷却时长"的配置项——粒度已经是按渠道模型,缺的是时长而时长不可配。唯一可配的是按凭据整体关闭冷却(disable_cooling: true)。
详见 docs/DEPLOYMENT.md §3.7(同名池化与模态能力)与 §3.8(上游限流与冷却)。
测试
新增 plugins/codearts/vision_test.go,7 个用例覆盖:最新消息带图、仅历史带图、三种图片 part 拼写、工具结果内嵌图片、无图请求逐字不变、占位符是 text part。做了反向验证:停用剥离调用后 5 个用例立刻转红。
go build / go vet / gofmt / 全部包测试均通过。
v0.7.0 — ZCode 不再提供签到(与 cline 一致)
ZCode 的领取端点强制要求阿里云验证码,插件无法满足,所以它此前提供了一个每天必然失败一次的动作——长期占用 HUB 的 failed 计数,而那个计数是留给真正意外的。人工验证通道也做过了,失败在最后一步:验证码参数是公开的(服务端在 client/configs 里公布 sceneId/prefix/region),请求头契约在官方客户端 bundle 里,页面确实加载了阿里云 SDK 并成功初始化——但组件渲染不出验证(embed/popup、传选择器/传元素都试过,控制台无报错)。最可能的原因是该验证码场景未授权本页面来源。
于是采取 cline 的立场:完全不提供签到。
删除而不是留成不可达:/checkin 资源路由、签到页、checkinJSON 及其状态词helper、整条领取链路(claimDaily/claimPlan/claimFailureText/活跃上报/它依赖的 preview)、以及验证码机制(captcha.go、组件、脚本上下文转义)——测试一并移除。
保留(仍然可用且有用):推理、token 余额与额度桶、凭据有效期、模型目录、登录流程。
两个契约点,都做了反向验证:
- 资源路由测试现在断言不存在签到路由(与 cline 插件同一个不变量),路由无法悄悄回来;
- HUB 清单标记
supportNone并写明原因。
实测:/checkin → 404;一键签到把它报成「不支持」(与 cline 并列),failed 计数为 0;GLM-5.3-Flash 推理仍返回 OK。
开发清单里也补了这条原则:注定失败的动作不要提供入口,相关代码要删掉而不是留着看起来只是没接好。
v0.6.1 — ZCode 人工验证通道(已实现,当前不可用)+ 模型数补齐
ZCode 验证码:人工验证通道做出来了,但渲染不出来
领取端点的验证码是 ZCode 每日额度唯一的拦路石。要试它的三块拼图其实都是公开的:
- 参数由服务端公布,不在闭源客户端里:
GET /api/v1/client/configs?platform=unknown→{"enabled":true,"prefix":"no8xfe","region":"cn","sceneId":"11xygtvd"} - 请求头契约在你本机已装的客户端里(
~/.zcode/server/zcode-server.cjs):X-Aliyun-Captcha-Verify-Param+ 可选X-Aliyun-Captcha-Verify-Region - 阿里云前端 SDK 是公开脚本,正是吃这三个值
所以插件现在会:读取配置;遇到 3007 且没有令牌时渲染官方验证码组件;通过查询串收回令牌(资源路由只派发 GET)并带着两个头重试——请求头按字面名做了单元测试。
但它不工作,页面也如实这么说。 实测(面板来源):SDK 加载成功、initAliyunCaptcha 执行无异常、getInstance 回调触发、控制台干净——而元素在 embed/popup、传选择器/传元素等各种组合下始终为空。最可能的原因是验证码场景未授权本页面来源(阿里云侧配置,属 ZCode 所有)。转不完的圈比原来那句话更糟,所以组件 6 秒后主动放弃、点名回退方案并隐藏提交按钮。
写这条降级断言时查出一个真实注入:场景参数来自服务端文档,而 strconv.Quote 不足以让一个值安全地嵌进 <script>——HTML 解析器在字符串中间遇到 </script 照样结束元素。jsStringLiteral 现在转义 <、>、& 与 JS 行分隔符,并有测试拿恶意 scene id 打它。
模型数补齐
codearts / lobsterai / trae 三个渠道此前不发布 model_count,HUB 那行因此没有「模型 N」。现在:codearts 8、lobsterai 30、trae 28(cline 本来就有)。
注意这些数字是插件自己的目录,在宿主 oauth-excluded-models 过滤之前——lobsterai 报 30 而 /v1/models 只列 7,是排除清单在起作用,不是 bug。
每一项断言都做了反向验证:删掉验证码头、永不渲染组件、发送空的 region 头(缺失与空值是不同的请求)、去掉脚本上下文转义。
v0.6.0 — 统一模型命名 + 新增渠道开发清单
模型命名统一
atomcode 与 codearts 此前发布的是上游自己的 id(glm5.3-flash、qwen3.8-27b、deepseek-flash、deepseek-v4.1-flash),与其它渠道已有的名称并存,同一个模型出现两个条目。现在统一发布 GLM-5.3-Flash、Qwen3.8-27B、DeepSeek-V4.1-Flash,并做双向映射,网关收到的仍是它自己的 id。
改名放在插件里而不是 oauth-model-alias,这是实测结论:同一个别名目标被多个 provider 映射时,胜出方在多次重载之间会变——atomcode 的 qwen3.8-27b 曾被留在 cline 的 Qwen3.8-27B 旁边,同一模型两个条目;而映射到一个全新名字每次都生效。插件自己发布的名字会与其它渠道的同名模型合并(这正是 zcode 一直依赖的行为)。别名表保留它真正的用途:引入一个全新名字。
反向映射是承重的,也很容易忘:对外发布 Qwen3.8-27B 却把它发给上游,网关会用「参数错误」哨兵回应而不是报错。两个方向都有测试,且都通过删除反向查表做了反向验证。
新增渠道开发清单
新增 docs/ADDING-A-CHANNEL.md——本次会话缺的就是它。覆盖插件之外那些看不见的步骤:三个登录入口、统一命名、hub 清单条目及其必需的 action=claim、hub 真正会渲染的状态 JSON 字段、签到响应必须携带的顶层 message、写闸门、排除/别名的作用顺序、发布接线,以及每条断言的反向验证。
最后附「模型不见了」的排查顺序——从宿主的排除清单查起,而不是从上游反推。这个错误在本次会话里犯过两次。
v0.5.4 — 一键签到:能领的领掉,领不了的说明原因
一张截图里的三个问题。
ZCode 为什么失败
claim 是官方客户端唯一还挂阿里云验证码的端点,而且服务端是强制的:不带令牌一律 400 {"code":3007,"msg":"captcha verify failed"},响应里没有可解的挑战。本插件不产出验证码,所以 ZCode 的一键签到不可能成功,重试没有意义。推理不受影响(实测无需验证码)。
顺带纠正:我此前写的「领取端点目前也不需要验证码」是从 skip_model_request: true 过度外推的——那个字段说的是模型请求。
而且失败原因根本没送到你眼前:HUB 的签到行只读顶层 message,而 ZCode 的响应没有这个字段,于是那句可执行的话躺在 outcomes[].message 里没人看,页面上只剩一个 failed。现在 ZCode 会发布摘要,措辞也直接说明「重试不会有不同结果」以及该去哪里领。
Raccoon 为什么不能一键
它真的没有每日签到(每日 300 由服务端自动发放,没有端点)。但一次性桌面端登录奖励是可以领的,而且你账上正躺着 3000 分没领——之前那句「请在插件页单独领取」等于让你绕路。
现在一键动作会领它,两道闸保证放在每天按的按钮后面是安全的:
- 先读奖励状态(由账单历史推导),已领过就报「已领取」,一次写请求都不发;
- 服务端
granted标志兜底:重复调用返回granted:false,映射为「已领取」而非第二次「领取成功」。
真实账号实测:276 → 3276(reward 桶 0 → 3000);再次运行报「已领取」,余额不变。
其它
- 该渠道的路由契约测试原本断言「不允许存在 /checkin」。现在断言的是相反的不变量:路由存在,且描述必须写明本渠道没有每日签到,以免下一个读者误解。
- 传了不存在的
auth_index时,原本回「没有可用账号」,会让拿着过期索引的人去找一个明明就在的凭据。现在会指出是哪个选择器没匹配上。
每一项新断言都通过破坏它所保护的行为做了反向验证:页面加载就写入、忽略已领取状态、丢掉顶层 message、让成功压过失败。
v0.5.3 — AtomCode 支持补充服务端目录已撤下的模型
新增配置项 extra_models:把网关能调用、但服务端 models-v2 目录已不下发的模型 id 加到实时目录之上。
为什么需要它
网关和权益目录确实不一致。2026-10-01 在同一个 CodingPlan Lite 账号上实测(用本插件自己的身份):
| 模型 id | 结果 | 在 models-v2 里 |
|---|---|---|
deepseek-flash |
200,正常内容 | 否 |
Qwen/Qwen3-32B |
200,正常内容 | 否 |
Qwen/Qwen2-VL-72B |
200,正常内容 | 否 |
Qwen/Qwen3-4B-Instruct-2507 |
200,但内容是「三方请求失败: 502 …」 | 否 |
no-such-model-xyz |
200,但内容是「参数错误」 | 否 |
所以:「能调用」不等于「已下发」,更不等于「可用」——上表里就有一个返回 200 却在网关内部失败的。这也是它做成配置项而不是内置列表的原因:只有持有账号的人能判断某个未下发模型值不值得暴露;插件不该替用户猜。插件只对抓到过服务端原始元数据的 id 附带窗口/思考级别,其余一律不带任何声明(猜出来的上下文窗口会被客户端当真)。
用法
atomcode:
extra_models:
- deepseek-flash三种写法都接受:单个字符串、逗号分隔、YAML 列表。补充模型加在实时目录之上(不是替代),去重;在模型列表和状态页的「可用模型」卡片里都标注为「补充模型」,与套餐权益区分开。
顺带修复
多识别一种静默失败:上游故障被当成模型回答返回(HTTP 200 + 正文「三方请求失败: 502 …」)。此前它会被原样当成模型的回答——在 agent 循环里更会成为模型的下一轮输入。现在会转成可重试的上游错误。
新增测试:合并接线、三种配置写法、未知 id 不得凭空声明元数据、新的失败形态;每一项都做了反向验证。
v0.5.2 — 修复到期时间格式导致的续期静默失效
修复一个静默故障:CPA 会把 auth 记录里的 expires_at 规范化成 RFC3339 存回(插件写毫秒数字串,落盘变成 2026-09-30T17:48:18Z)。MiniMax 的到期解析只接受纯数字,读不懂就按「没有到期时间」处理,而该渠道把「没有到期时间」定义为「未过期」——于是凭据看起来永远健康,续期一次也没跑,直到上游返回 401。它的令牌只有 3600 秒,所以一小时内就会暴露。
修复范围:minimax、loomy、raccoon 现在同时接受数字(毫秒/秒)与 RFC3339。loomy 原本连 JWT 回退都没有,会以完全相同的方式静默失败;raccoon 的回退只覆盖令牌恰好是 JWT 的情况。codearts / trae / codebuddy / lobsterai 原本就接受多种写法。
续期现在是被证明的,而不是假设的,对外公布的断言也同步更新:把凭据改到距到期 120 秒(落在 300 秒续期窗口内)后发一次推理,请求返回 200 且落盘的 access_token 变成了新的——续期在该请求之前跑完了;随后在窗口之外再发一次,令牌不再轮换,说明续期是按需触发而非每请求一次。由于该断言此前是有意置为 false 的,守着它的测试现在要求证据必须写在代码里,不能再无理由翻回去。
三个插件都补了用真实落盘值做输入的测试,并各自做了反向验证(打断 RFC3339 分支即变红)。