Files
ModelRouter/docs/channel-issues-2026-10-19.md
root 9c7b5f8556 fix(channels): 商汤扫码链路重建 + 双区登录 + 凭据过期口径 + 四条老化判据
① 商汤(Raccoon)扫码「链接无效」——两个真根因
- 登录页 /login/mp 已被站点删除(Next 路由 404,实测只有 404 页);
- 旧实现本地随机造 qrcode_code,而上游早已不认自造码:旧端点
  /api/web/auth/v1/login_with_qrcode_code 对任意 code 恒回 pending
  (服务端没有该会话)⇒ 扫码永远完不成,账号一直是空凭据。
修法:改为上游现行 IAM 流程(逐行对齐其网页客户端 auth chunk)——
  POST /api/iam/v1/auth/mp/qrcode 建会话 → 链接用
  /login?code=…&appname=…&source=platform(实测 200 + 微信扫码面板)
  → 轮询 /api/iam/v1/auth/mp/qrcode/login(IAM code:"OK" 与旧 code:0 都认)。
  服务端建码失败才退回旧本地码并打 warn。UI 对扫码流程改文案为
  「用微信扫码」(原本文案会让人以为点开就自动登录)。
生产实测:链接 HTTP 200、轮询回 pending(会话真实存在)。

② 双区登录(P7)
- 凭据增加 region(intl|cn,缺省=历史行为);start_login 接受 region,
  会话按区域选端点,完成登录写进凭据;
- CodeBuddy:cn=copilot.tencent.com(platform=ide)/
  intl=www.workbuddy.ai(platform=workbuddy-ai,X-Domain、
  X-Product-Code、UA 按区域;实测 auth/state 回 200 信封);
- Qoder:intl=openapi.qoder.sh+qoder.com(client_id e883ade2-…)/
  cn=openapi.qoder.com.cn+qoder.cn(client_id 不同 732aef47-…,照参考
  QODER_CN)。CN 无公开 chat 端点 ⇒ CN 只做登录/领取;
- UI:buddy/qoder 详情页增加「+国际区」按钮。

③ 凭据过期时刻的时区口径(P6 真根因)
credentialExpiresAtMs 把 ISO 过期时刻按本地时区解析,而 codearts 给的是
UTC ISO(2026-10-19T16:42:43.06Z)⇒ 本机 +08 下过期时刻虚早 8 小时 ⇒
refresh_creds 每 30 分钟都判"已过期"并对华为云真打一次 STS(token 本还有
23 小时寿命)。改为按声明时区解析(Z / ±hh:mm / 无后缀=UTC 三种形态同一
epoch),补 TestCredentialExpiresAtMsParsesISOAsUTC。

④ 四条老化/假红判据
- TestCreditsPoolReason*:account_add 触发的选号会用构造期默认余额 (0,0)
  先查一次并缓存 60s ⇒ 之后改假上游读不到("余额 100 却报 exhausted")。
  harness 改余额后清 TTL 缓存(__ttl_reset_probe,与生产同一实现)。
- TestAccountReorderActually... / TestPoolListOrderIsDeterministic:随
  「一通道一源」失效(按 channels-buddy-<acct> 过滤恒得空)。改写为——
  重排必须切换服务账号(新增 __chosen_account_probe),顺序确定性改为断言
  选号顺序(__rank_accounts_probe)由 id 稳定决定。
- channels.lua 顶层 local 余量:区域表与 5 份适配器源码常量收进 plugin.K
  (TestChannelsLuaHasLocalHeadroom 需要 ≥10 槽位);ZCode 适配器源码里的
  CLI_PREFIX 必须留在源码内(适配器沙箱没有 plugin 全局)。

⑤ 错误文案
openai_error 增补腾讯 codebuddy 三层嵌套 {error:{data:{code,msg}}}:实测
429 code=14018「额度已用尽」此前落到"不识别",客户端只看到通用文案。

验证:core 434s 全绿、lua 62s 全绿、provider 全绿;生产已部署并实测
raccoon 链接 200 / trae 非流式 200 / zcode 适配器加载。

(cherry picked from commit 662f8f02b6)
2026-10-20 08:27:41 +08:00

9.9 KiB
Raw Blame History

渠道问题清单(2026-10-19 晚生产实测)

逐项修复。每项标注:现象 / 根因 / 状态。

P1 trae 非流式 500(unmarshal unified response: invalid character 'e')

  • 现象:stream:false 打 channels-trae:* 必现 500,body 以 event:metadata data:{...} 开头。
  • 根因:trae SOLO 上游对非流式请求也回 SSE(且帧带 event: 前缀)。 插件自装的 channels-trae 适配器 transform_response 直接 __prelude.openai_response(raw_body), 不识别 SSE;而内核只把 data: 行交给流式适配器,非流式路径把整个 body(含 event: 行) 塞给 transform_response → json.decode 失败 → 原样返回 → Go 侧 unmarshal 报 invalid character 'e'。 (internal/lua/adapters/trae.lua 有 SSE 聚合逻辑,但 channels 平台用的是插件内嵌 TRAE_SOLO_ADAPTER_SRC(channels.lua:9744),两份代码不同步。)
  • 修法:TRAE_SOLO_ADAPTER_SRC.transform_response 加 SSE 聚合(复用流式帧解析, 聚合成一个非流式响应),并把 internal/lua/adapters/trae.lua 的聚合逻辑对齐。
  • 状态:✅ 已修复(aggregate_solo_sse)并部署生产,实测非流式返回 200 正常内容。

P2 buddy(CodeBuddy)非流式 400(11101 Non-stream not supported)

  • 现象:stream:false 打 channels-buddy:* 上游 400 11101: Non-stream chat request is currently not supported。
  • 根因:腾讯 codebuddy 网关只支持流式。适配器 transform_request 没有强制 stream=true, 非流式请求原样透传被上游拒绝。buddy 的 Accept: text/event-stream 头已发,但 body.stream 仍是 false。
  • 修法:buddy.lua transform_request 强制 req.stream = true;transform_response 增加 SSE 聚合兜底(内核非流式路径收到 SSE 时聚合)。参考 opencodezen.lua 的既有做法(门禁第 4 条)。
  • 状态:✅ 已修复并部署生产,实测非流式返回 200 正常内容。

P3 buddy 偶发 provider busy(429)+ 11133 参数拒绝

  • 现象:连续请求 channels-buddy 间歇返回 provider busy(source 并发槽 max_concurrent=1 被占满)与上游 11133: the request parameters were rejected。
  • 根因:① max_concurrent=1 是刻意的(channels.lua:4332 注释:并发配额按模型计),busy 是正常自保;② 11133 与空 tool_calls 修复(未提交的 buddy.lua 改动)相关,待部署后观察。
  • 状态:观察项,非缺陷。

P4 Qoder 领取失败(缺少凭据对象)——已修复

  • 现象:channels/claim 报 {"error":"缺少凭据对象"},而 account_test 正常。
  • 根因:qoder 是 PKCE 设备码流且无 register 第二跳,落库凭据是裸 token 字符串; qoderClaim 硬要求 type(cred)=="table"。
  • 修法:qoderClaim 兼容裸 token(非空时包 {access_token=tok}); 回归测试 TestQoderClaimAcceptsBareTokenCredential。
  • 状态:✅ 已修复并部署生产(claim 现在返回真实上游业务结论)。

P5 Qoder/OpenCode 渠道页账号串台(UI 渲染竞态)

  • 现象:从 qoder 切到 opencode 详情页,qoder 的账号行闪现在 opencode 卡片下。
  • 根因:renderDetail() 切渠道时不清空 #ch-accts,renderAccounts 是异步的, 空窗期 DOM 仍挂旧渠道账号行;且慢的旧请求返回会覆盖新渠道数据。
  • 修法:① renderDetail 立即清空账号/模型区并置"加载中";② renderAccounts/renderModels 加代际校验(state.selected !== id 时丢弃响应)。
  • 状态:✅ 已修复并部署生产。

P6 CodeArts「完全不可用」——多数模型实际可用

  • 现象:用户报"codearts 完全不可用"。实测逐模型探测:
    • 可用:openpangu-2.0-flash/pro、GLM-5.2、glm-5.3、kimi-k2.6-vl、Qwen3-VL-235B、glm-5.2-sft-harmony
    • 429(限流非下架):deepseek-v4.1-flash、deepseek-v4-flash、openpangu-2.0-pro(间歇)
    • 404(真下架,需标 disabled):GLM-5.3 报 InferHub.002002009.404: The model is not registered
  • 附带发现:GLM-5.3(大写变体)不在 deadModels 记录里——recordDeadModel 只被 __record_dead_probe 调用,request_end hook 没接 isModelGone(583976f 引入的 基础设施丢了挂接点)。需要把 on_request_end 补上 dead-model 记录。
  • 凭据:expiresAt 已过期(15:05),account/refresh 续期成功(refreshable=true)。 建议把「凭据到期自动续期」纳入定时任务检查。
  • 修法(2026-10-20 完成):
    • on_request_end 补上 isModelGone + recordDeadModel 挂接(P9 一并解决);
    • 凭据自动续期失效的真根因:credentialExpiresAtMs 把 ISO 过期时刻按本地时区 解析,而 codearts 给的是 UTC ISO(2026-10-19T16:42:43.06Z)⇒ 本机 +08 下过期时刻 虚早 8 小时 ⇒ refresh_creds 每 30 分钟都判"已过期"并对华为云真打一次 STS (token 本来还有 23 小时寿命)。已改为按声明时区解析(Z/±hh:mm/无后缀=UTC 三种形态 同一 epoch),回归测试 TestCredentialExpiresAtMsParsesISOAsUTC。
  • 状态:✅ 已修复并部署生产。

P7 CodeBuddy 海外账号 / Qoder 国内账号

  • 现象:CodeBuddy 登录只走国内(copilot.tencent.com),Qoder 登录只走国际 (openapi.qoder.sh / qoder.com)。用户要求两边都能选区。
  • 根因:平台表单区硬编码。buddy 的 state-poll 端点与 Qoder 的 PKCE 端点都锁死一个区域。
  • 修法(2026-10-20 已实施,账号级 region):
    • 凭据 JSON 增加 region 字段(intl|cn,缺省 = 历史行为);
    • start_login 接受 region,会话带着它选端点;登录完成写进凭据;
    • CodeBuddy:cn = copilot.tencent.com(platform=ide),intl = www.workbuddy.ai (platform=workbuddy-ai,X-Domain/X-Product-Code/UA 按区域切换;实测 POST /v2/plugin/auth/state?platform=workbuddy-ai 回 200 信封);
    • Qoder:intl = openapi.qoder.sh + qoder.com(client_id e883ade2-…), cn = openapi.qoder.com.cn + qoder.cn(client_id 不同:732aef47-…, 照参考 QODER_CN;用国际版 id 授权页报「参数无效」)。CN 无公开 chat 端点 (参考实测 503)⇒ CN 账号只做登录/领取,不产源;
    • UI:buddy/qoder 详情页多一个「+国际区」按钮(默认国内/国际主区)。
  • 状态:✅ 已实施并部署生产。

P8 商汤(Raccoon)扫码登录「链接无效」——已修复

  • 现象:用户点登录看到的链接打不开(404)。
  • 根因(两个,2026-10-20 实测定位): ① 登录页 /login/mp 已被站点删除(Next 路由 404,只有 404 页面返回); ② 旧实现本地随机造 qrcode_code,而上游早已不认自造码:旧端点 /api/web/auth/v1/login_with_qrcode_code 对任意 code 恒回 pending (服务端根本没有该会话)⇒ 扫码永远完不成,账号一直空凭据。
  • 修法:改为上游现行 IAM 流程(逐行对齐其网页客户端 auth chunk): ① POST /api/iam/v1/auth/mp/qrcode 由服务端建会话(返回 qrcode_code / scan_path / expires_in / poll_interval); ② 链接/二维码内容 = https://xiaohuanxiong.com/login?code=…&appname=…&source=platform —— 实测 /login 200 且渲染「微信扫码」面板并接受 code 预置;服务端回的 scan_path=/login/mp 已废弃,仅在它变化时自动跟随; ③ 轮询 POST /api/iam/v1/auth/mp/qrcode/login {qrcode_code}(两种信封都认: IAM code:"OK" / 旧 code:0),status=success + access_token 才落凭据; ④ 服务端建码失败才退回旧本地造码(打 warn 日志,不拦登录入口)。
  • 状态:✅ 已修复并部署生产。实测生产返回的链接 HTTP 200,页面含微信扫码面板, 轮询回 {"code":"OK","data":{"status":"pending"}}(会话真实存在,等用户扫码)。

P9 死模型记录基础设施未挂接(P6 的根因之一)

  • isModelGone / recordDeadModel 只有测试 probe 在调;plugin.hooks = { request_end } 的 on_request_end 只处理 term_error(账号级),不记模型级死亡。
  • 修法:on_request_end 失败分支里,取 payload.model + error message, isModelGone(msg) 为真时 recordDeadModel(provider, model, msg) 并重同步源。
  • 状态:✅ 已修复并部署生产(真实 404 现在会进 deadModels)。

2026-10-20 追加修复(测试与工程健康)

  • TestCreditsPoolReason* 两条判据假红:account_add 触发的选号会用构造期默认余额 (0,0) 先查一次并缓存 60s,之后 serveBalance 改假上游也读不到 ⇒ 表现为"余额 100 却报 exhausted"。修法:harness 在改余额后清 TTL 缓存(__ttl_reset_probe,与生产同一实现)。
  • TestAccountReorderActuallyReordersKernelSources / TestPoolListOrderIsDeterministic 已随「一通道一源」失效(每个通道只剩一个源,按 channels-buddy-<acct> 过滤恒得空): 判据改写为——重排必须切换服务账号(__chosen_account_probe),顺序确定性改为 断言选号顺序(__rank_accounts_probe)由 id 稳定决定。
  • channels.lua 顶层 local 余量回归绿:区域表/适配器源码常量收进 plugin.K (TestChannelsLuaHasLocalHeadroom 需要 ≥10 槽位)。

部署注意事项

  • 工作树有未提交改动:buddy.lua / trae.lua(tool_calls 空 {} 过滤)、provider.go(stop→tool_calls 归一)、 channels.lua(qoder 裸 token + UI 竞态)、provider_test.go、channels_qoder_claim_test.go。
  • 生产 channels.lua 哈希与仓库不同步是常态(deploy.sh 会 sync_plugins)。
  • 商汤账号:用户需在二维码有效期内扫码确认;当前账号 5a2ea34df521 无凭据属正常(未完成扫码)。