@eddyskywalker/dsh-chatgpt-subscription 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +100 -111
  3. package/lib/client.js +4731 -1239
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +11514 -3162
  6. package/lib/types/client/common/format.d.ts +10 -0
  7. package/lib/types/client/common/format.d.ts.map +1 -1
  8. package/lib/types/client/hub/brand-icons.d.ts +18 -2
  9. package/lib/types/client/hub/brand-icons.d.ts.map +1 -1
  10. package/lib/types/client/hub/hub-styles.d.ts.map +1 -1
  11. package/lib/types/client/hub/providers.d.ts.map +1 -1
  12. package/lib/types/client/index.d.ts +4 -0
  13. package/lib/types/client/index.d.ts.map +1 -1
  14. package/lib/types/client/minimax-code/VideoFormDialog.d.ts +46 -50
  15. package/lib/types/client/minimax-code/VideoFormDialog.d.ts.map +1 -1
  16. package/lib/types/client/minimax-code/locales.d.ts +80 -20
  17. package/lib/types/client/minimax-code/locales.d.ts.map +1 -1
  18. package/lib/types/client/minimax-code/styles.d.ts.map +1 -1
  19. package/lib/types/client/minimax-code/video-command-host.d.ts +17 -11
  20. package/lib/types/client/minimax-code/video-command-host.d.ts.map +1 -1
  21. package/lib/types/client/minimax-code/video-command-icon.d.ts +38 -0
  22. package/lib/types/client/minimax-code/video-command-icon.d.ts.map +1 -0
  23. package/lib/types/client/minimax-code/video-command.d.ts +53 -59
  24. package/lib/types/client/minimax-code/video-command.d.ts.map +1 -1
  25. package/lib/types/client/minimax-code/video-media-dropzone.d.ts +57 -0
  26. package/lib/types/client/minimax-code/video-media-dropzone.d.ts.map +1 -0
  27. package/lib/types/client/minimax-code/video-media-input.d.ts +173 -0
  28. package/lib/types/client/minimax-code/video-media-input.d.ts.map +1 -0
  29. package/lib/types/client/minimax-code/video-presets.d.ts +67 -0
  30. package/lib/types/client/minimax-code/video-presets.d.ts.map +1 -0
  31. package/lib/types/client/minimax-code/video-workspace.d.ts +41 -0
  32. package/lib/types/client/minimax-code/video-workspace.d.ts.map +1 -0
  33. package/lib/types/client/opendesign/OpenDesignComposerQuota.d.ts +20 -0
  34. package/lib/types/client/opendesign/OpenDesignComposerQuota.d.ts.map +1 -0
  35. package/lib/types/client/opendesign/OpenDesignSection.d.ts +18 -0
  36. package/lib/types/client/opendesign/OpenDesignSection.d.ts.map +1 -0
  37. package/lib/types/client/opendesign/api.d.ts +45 -0
  38. package/lib/types/client/opendesign/api.d.ts.map +1 -0
  39. package/lib/types/client/opendesign/locales.d.ts +689 -0
  40. package/lib/types/client/opendesign/locales.d.ts.map +1 -0
  41. package/lib/types/client/opendesign/styles.d.ts +11 -0
  42. package/lib/types/client/opendesign/styles.d.ts.map +1 -0
  43. package/lib/types/client/qoder/QoderComposerQuota.d.ts +41 -0
  44. package/lib/types/client/qoder/QoderComposerQuota.d.ts.map +1 -0
  45. package/lib/types/client/qoder/QoderSection.d.ts +36 -0
  46. package/lib/types/client/qoder/QoderSection.d.ts.map +1 -0
  47. package/lib/types/client/qoder/api.d.ts +4 -0
  48. package/lib/types/client/qoder/api.d.ts.map +1 -0
  49. package/lib/types/client/qoder/locales.d.ts +316 -0
  50. package/lib/types/client/qoder/locales.d.ts.map +1 -0
  51. package/lib/types/client/qoder/styles.d.ts +16 -0
  52. package/lib/types/client/qoder/styles.d.ts.map +1 -0
  53. package/lib/types/client/qoder/useQoderSection.d.ts +97 -0
  54. package/lib/types/client/qoder/useQoderSection.d.ts.map +1 -0
  55. package/lib/types/host/common/model-request-control.d.ts +1 -1
  56. package/lib/types/host/common/model-request-control.d.ts.map +1 -1
  57. package/lib/types/host/common/request-diagnostics.d.ts +1 -1
  58. package/lib/types/host/common/request-diagnostics.d.ts.map +1 -1
  59. package/lib/types/host/kimi-code/adapter.d.ts +34 -1
  60. package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
  61. package/lib/types/host/kimi-code/mapper.d.ts +8 -0
  62. package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
  63. package/lib/types/host/kimi-code/types.d.ts +92 -0
  64. package/lib/types/host/kimi-code/types.d.ts.map +1 -1
  65. package/lib/types/host/opendesign/account-pool.d.ts +54 -0
  66. package/lib/types/host/opendesign/account-pool.d.ts.map +1 -0
  67. package/lib/types/host/opendesign/adapter.d.ts +69 -0
  68. package/lib/types/host/opendesign/adapter.d.ts.map +1 -0
  69. package/lib/types/host/opendesign/catalog.d.ts +44 -0
  70. package/lib/types/host/opendesign/catalog.d.ts.map +1 -0
  71. package/lib/types/host/opendesign/client.d.ts +148 -0
  72. package/lib/types/host/opendesign/client.d.ts.map +1 -0
  73. package/lib/types/host/opendesign/line.d.ts +49 -0
  74. package/lib/types/host/opendesign/line.d.ts.map +1 -0
  75. package/lib/types/host/opendesign/mapper.d.ts +128 -0
  76. package/lib/types/host/opendesign/mapper.d.ts.map +1 -0
  77. package/lib/types/host/opendesign/oauth.d.ts +105 -0
  78. package/lib/types/host/opendesign/oauth.d.ts.map +1 -0
  79. package/lib/types/host/opendesign/routes.d.ts +74 -0
  80. package/lib/types/host/opendesign/routes.d.ts.map +1 -0
  81. package/lib/types/host/opendesign/token-store.d.ts +154 -0
  82. package/lib/types/host/opendesign/token-store.d.ts.map +1 -0
  83. package/lib/types/host/opendesign/types.d.ts +180 -0
  84. package/lib/types/host/opendesign/types.d.ts.map +1 -0
  85. package/lib/types/host/qoder/account-pool.d.ts +137 -0
  86. package/lib/types/host/qoder/account-pool.d.ts.map +1 -0
  87. package/lib/types/host/qoder/adapter.d.ts +158 -0
  88. package/lib/types/host/qoder/adapter.d.ts.map +1 -0
  89. package/lib/types/host/qoder/adopt.d.ts +146 -0
  90. package/lib/types/host/qoder/adopt.d.ts.map +1 -0
  91. package/lib/types/host/qoder/client.d.ts +301 -0
  92. package/lib/types/host/qoder/client.d.ts.map +1 -0
  93. package/lib/types/host/qoder/cosy.d.ts +173 -0
  94. package/lib/types/host/qoder/cosy.d.ts.map +1 -0
  95. package/lib/types/host/qoder/mapper.d.ts +235 -0
  96. package/lib/types/host/qoder/mapper.d.ts.map +1 -0
  97. package/lib/types/host/qoder/model-catalog.d.ts +231 -0
  98. package/lib/types/host/qoder/model-catalog.d.ts.map +1 -0
  99. package/lib/types/host/qoder/oauth.d.ts +134 -0
  100. package/lib/types/host/qoder/oauth.d.ts.map +1 -0
  101. package/lib/types/host/qoder/routes.d.ts +119 -0
  102. package/lib/types/host/qoder/routes.d.ts.map +1 -0
  103. package/lib/types/host/qoder/token-store.d.ts +280 -0
  104. package/lib/types/host/qoder/token-store.d.ts.map +1 -0
  105. package/lib/types/host/qoder/types.d.ts +257 -0
  106. package/lib/types/host/qoder/types.d.ts.map +1 -0
  107. package/lib/types/host/workbuddy/adapter.d.ts.map +1 -1
  108. package/lib/types/host/workbuddy/mapper.d.ts +1 -0
  109. package/lib/types/host/workbuddy/mapper.d.ts.map +1 -1
  110. package/lib/types/index.d.ts +15 -0
  111. package/lib/types/index.d.ts.map +1 -1
  112. package/lib/types/shared/opendesign-contracts.d.ts +159 -0
  113. package/lib/types/shared/opendesign-contracts.d.ts.map +1 -0
  114. package/lib/types/shared/qoder-contracts.d.ts +332 -0
  115. package/lib/types/shared/qoder-contracts.d.ts.map +1 -0
  116. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,87 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.34.0 - 2026-10-21
6
+ - **[Kimi Code] 长会话撞 2 MB 请求体上限整轮失败——现在撞墙交给压缩恢复,窗口折算降为可选**
7
+ - **现象**:中文长会话在 K3 线上整轮失败,报 `Kimi Code rejected the request before sending: the serialized body is 2107355 bytes, above the 2097152-byte limit this route enforces. Composition: images 0 bytes, tool schemas 2531 bytes, conversation text and system prompt 2104824 bytes`。图片 0 字节,也就是说这**不是读图问题**,上一版的「丢最旧的图」回收路径在这里丢无可丢,只能报错并建议压缩——而用户真正要做的是让这一轮活下来。
8
+ - **根因一(错误码不对,宿主接不上自愈)**:[mapper.ts](../src/host/kimi-code/mapper.ts) 的 `assertRequestBodyFits` 抛的是 `PROVIDER_ERROR`,而 DSH 的溢出恢复**只对 `CONTEXT_WINDOW_EXCEEDED` 生效**(`compaction-basic` 的 `agent/request-error` 监听器按该码强制压缩本轮历史、再重试同一步,重试上限默认 1 次)。于是唯一还能靠压缩救回来的场景——正文太大、图已丢完——恰好是唯一不会被救的那个。本线路服务端真返回 400 溢出的路径(`classifyKimiFailure`)早就用对了这个码,只有本地守卫没接上。
9
+ - **根因二(两个上限量不同的东西)**:DSH 用 adapter 申报的 `context.contextWindow` 算压缩阈值(`resolveCompactSpec`:`thresholdRatio` 默认 0.8、`headroomTokens` 默认 65536,本线路不申报 `defaultMaxTokens` 故无输出预留,即 `min(floor(0.8W), W − 65536)`),这是 **token** 口径;网关的 2 MB 是 **字节** 口径。而 DSH 的 token 计量是固定密度(`@deepseek-ai/dsh-token-meter` 的 `CHARS_PER_TOKEN = 4`,按 `text.length / 4` 计价,**看不见中日韩字符占 3 字节**)。所以「一个计量 token 占多少字节」= 4 × 每字符字节数:纯 ASCII 约 4、中英混合约 8、密集中文约 12。`kimi-for-coding` 申报 1M、阈值 85 万计量 token,而 2 MB 中文正文只有约 17.5 万计量 token——**压缩永远不会先触发**,字节墙先到。
10
+ - **根因三(网关原文没被认出来)**:`total message size 5943865 exceeds limit 2097152` 匹配不上 `common/context-overflow.ts` 里任何一条超限措辞,所以请求即便漏过本地守卫、由服务端拒绝,同样落进 `PROVIDER_ERROR`。
11
+ - **改法(兜底,本次的主修复)**:`assertRequestBodyFits` 的错误码从 `PROVIDER_ERROR` 改为 `CONTEXT_OVERFLOW_CODE`,文案与 `BodyFitContext` 一字未动;`classifyKimiFailure` 增加 `isKimiBodySizeRefusal`(认 `total message size … exceed(s) … limit`)归入同一码,message 明说是**字节上限**而不是 token 窗口。判定函数刻意写在 Kimi 自己的分类器里——共享的 `isContextOverflow` 被其它八条线路共用,把一句话加进去会连带改写别人的失败语义。
12
+ - **改法(窗口折算,默认关)**:[types.ts](../src/host/kimi-code/types.ts) 新增 `wireBytesPerToken(env)`、`byteDerivedTokenCap(env)` 与 `effectiveContextWindow(tokenWindow, env)`;[adapter.ts](../src/host/kimi-code/adapter.ts) 的 `contextWindowFor` 仍是「申报值与内部预算的唯一入口」,但**默认原样发布目录值或用户覆盖值**:2 MB 墙已经由恢复码接管,用户显式覆盖(例如把 `k3` 覆盖成 1,048,576)不该被插件悄悄压小——这条是本轮返工的核心,最初把 clamp 设成默认是错的。设 `DSH_KIMI_CODE_BYTES_PER_TOKEN` 才启用折算,申报值 = `min(声明值, floor(2MB / d))`:`d=4` → 524288、`5` → 419430、`8` → 262144、`12` → 174762。
13
+ - **为什么是这组数**:DSH 的 token 计量是固定密度(`@deepseek-ai/dsh-token-meter` 的 `CHARS_PER_TOKEN = 4`,按 `text.length / 4` 计价,**看不见中文占 3 字节**),所以「字节 / 计量 token」= 4 × 每字符字节数:纯 ASCII 约 4、中英混合约 8、密集中文约 12;2 MB 墙换算成计量 token 分别是 524288 / 262144 / 174763。压力阈值是 `min(floor(0.8 × 窗口), 窗口 − 65536)`(`compaction-basic/src/config.ts` 的 `resolveCompactSpec`,`thresholdRatio` 默认 0.8、`headroomTokens` 默认 65536,本线路不申报 `defaultMaxTokens` 故无输出预留)。选哪个 `d` 就是选「让哪一类会话在墙前压缩」。
14
+ - **旋钮语义(与守卫不同)**:该变量是**可选的提前压缩开关**,不是安全守卫——不设、空串、非正、非法一律等于**不启用**(真正的安全网是恢复码,它永远在);`DSH_KIMI_CODE_MAX_BODY_BYTES` 仍按旧约定(typo 回落默认值而不是关掉守卫)。
15
+ - **默认行为的代价(写成事实)**:不折算时,`k3`@1M 的压力阈值 `min(838860, 983040)` = 838860 计量 token,而字节墙只有 174763(中文)/ 262144(混合)/ 524288(ASCII)——**阈值永远够不到**,所以长会话走的是「撞墙 → 自动压缩 → 重试同一步」。想让压缩发生在墙前就开折算。设置卡片仍按用户配置值显示窗口(客户端展示层,未改动)。
16
+ - **佐证**:官方 `kimi-cli` 走的是同一条「预算前置」路线——`src/kimi_cli/soul/compaction.py::should_auto_compact` 只在「达窗口 85%」或「剩余不足 50K token」时压缩,**从不特殊处理 2097152**;它的 CHANGELOG 还记过一次同类缺陷(工具结果 token 没算进压缩触发 → 压缩不触发 → 直接 400),与本次的「计量口径没算字节」是同一类错误。
17
+ - **测试**:新增 `test/kimi-code-capacity.test.ts`(29 例),分三段钉住:`window folding is opt-in`(**默认无 env 时 `k3` 的 1M 覆盖与 `kimi-for-coding` 的 1M 都原样发布**、4/5/8/12 各自的 min 语义、非法值当未设、`min` 不会把 256K 放大);`what each density setting protects, when folding is on`(把三种密度的撞墙点 524288 / 262144 / 174763、压力阈值 `floor(min(0.8W, W − 65536))` 与各档的护住范围写成断言,并**明写默认不折算时三种密度都先撞墙、这不是缺陷而是恢复码接管**);以及「2.1 MB 正文在发请求前就以 `CONTEXT_WINDOW_EXCEEDED` 被拒」的端到端用例(`fetch` 零调用);另有「2.1 MB 正文在发请求前就以 `CONTEXT_WINDOW_EXCEEDED` 被拒」的端到端用例(`fetch` 零调用)、`k3`/`k3-256k` clamp 前后相等、以及网关原文归类与「无关 400 仍是 `PROVIDER_ERROR`」的用例。`test/kimi-code-adapter.test.ts`、`test/kimi-code-k3.test.ts`、`test/kimi-code-body-fit.test.ts`、`test/dynamic-output-reservation.test.ts` 按新语义更新。
18
+ - **验证**:`npm run typecheck` 0 错误;`npx vitest run test/kimi-code test/dynamic-output-reservation` **30 文件 / 475 条全过**;全量 `npm test` **290 文件通过 / 4751 条通过 / 0 失败**(7 skipped 为仓库既有平台跳过:`token-store-linux` 等)。
19
+ - **[qoder] 模型选择里 Qoder 整条线路消失、「全不选」等于「全选」、重启后先出现一大堆用不上的模型——三件事都出在「哪些模型算可用」这一条链上**
20
+ - **现象一(最严重)**:重启宿主后,模型快捷选择栏里**根本没有 Qoder 这个供应商**;要打开一次设置页才会出现。
21
+ - **现象二**:设置卡片上的「全不选」点下去与「全选」效果完全相同;计数行还字面显示「已选 {selected} / 2」(占位符没被替换)。
22
+ - **现象三**:进设置页先看到 15 个模型,其中大部分是当前套餐根本没有的,必须点一次「刷新目录」才缩到自己账号真实的 2 个。
23
+ - **真因一(白名单词汇表错配)**:[adapter.ts](../src/host/qoder/adapter.ts) 的 `listModels` 拿用户白名单去收敛随包兜底表,而两者**不是同一套 id**——白名单是实时目录的 `display_name` 去空白(`DeepSeek-Flash`),兜底表照录 pi-provider 的静态表(`DeepSeek-V4-Flash`)。冷启动时进程里没有实时目录,收敛结果必然是空数组,`listModels` 返回 0 个模型,DSH 于是把整个 provider 分组过滤掉。实测:白名单 `['DeepSeek-Flash']` + 兜底表 → 0 个模型。
24
+ - **真因二(三态缺失)**:`enabledModelIds` 只有「缺省或空数组 = 不过滤」一种读法,而旧版 `defaultQoderModelSettings()` 的出厂值**恰好就是 `[]`**。于是「全不选」提交的 `[]` 被读成「不过滤」,同时也意味着**每一个老用户的落盘文件里都写着 `[]`**——直接改语义会让他们升级后一个模型都不剩。
25
+ - **真因三(快照读写不互逆)**:目录快照的写入侧存上游原始条目(`{ key, display_name, context_config }`),读取侧却用只认 `{ chat: [...] }` 的 `parseQoderModelListing` 去解析它,**任何快照都解析成空数组**并被当作「没有可用快照」丢掉。那份 `qoder-global-catalog.json` 一直都在、内容也是对的,却从来没有被成功读回来过一次——所谓「打开设置页就恢复」,真相是「等了一次真实的上游刷新」。
26
+ - **真因四(刷新不发事件)**:目录刷新成功后没有发 `llm/adapters-updated`,所以即使真实目录已经到手,模型选择器的快照也不会重读,停在空分组上。
27
+ - **改法(三态)**:`enabledModelIds` 改成 `string[] | null`——`null` = 不过滤(出厂)、`[]` = 用户明确全关、非空 = 白名单。存储、适配器过滤、状态下发、hub 概览、客户端五处同步;`ModelChecklist` 的「全不选」不再在自己已是 0 时禁用(全关是合法终态)。
28
+ - **改法(迁移)**:新增 `selectionVersion` 迁移标记。无标记 + `[]` → 按旧版出厂值迁移成 `null` 并落盘;有标记 + `[]` → 尊重用户真实的全关。幂等,且 register 后端的 schema 缺省刻意写成 `0`(旧 `update()` 写的是 `?? []`,那个后端同样会留下无标记的 `[]`)。
29
+ - **改法(收敛口径)**:新增 `convergeQoderSelection(stored, live, catalog)`——**收敛只在实时目录上做**,兜底表阶段不按白名单筛(它是能力占位,且与白名单不是同一套词汇);`[]` 在两种阶段下都生效。`listModels`、状态面勾选标记、hub 概览三处读同一个函数。
30
+ - **改法(预热与事件)**:新增 `persistedQoderModelRecord` / `parsePersistedQoderModelEntries` 这一对互为反函数的序列化器(顺带修掉 `is_reasoning` 按 `reasoningEfforts.length > 0` 写、会把 toggle 形态推理模型抹成不推理的问题);启动时 `void warmQoderCatalogOnStartup(...)` 非阻塞预热,先读磁盘快照回填、再让上游刷新跑在背后;`refreshCatalog` 改为返回「目录有没有真的换掉」,只有变了才 `ctx.emit('llm/adapters-updated')`。
31
+ - **改法(自动取目录)**:`GET /status` 在应答背后自动触发一次目录刷新(复用 `QuotaRefresh`,用 `force: false` 让 30 分钟 TTL 与 in-flight 去重生效);客户端首屏拿到 `catalogRefreshing` 就静默补拉一次,最多一次,不设 busy、不弹错。「刷新目录」按钮保留为强制出口。
32
+ - **验证**:`npm run typecheck` 0 错误;全量 `npx vitest run` **282 文件 / 4626 passed / 7 skipped**;`npm run build` 通过。冷启动回归用例**验证过先红后绿**(把规则改回旧实现时它报 `expected 0 to be greater than 0`);快照读写不对称用真实文件对拍过(旧解析器 0 条 / 新解析器 2 条);三态迁移用真实落盘文件跑过完整往返(迁移 → 幂等 → 全关不被翻回 → 全选回到 `null`)。
33
+
34
+ - **[qoder] 模型行只有名字:窗口、是否收图、思考档位、输出预算现在逐行写在勾选行下面**
35
+ - **现象**:Qoder 卡的模型清单每一行只有一个模型名,用户勾选时看不出这个模型能收多长的上下文、收不收图、有几档思考——与同一屏里的 OpenDesign 卡形成反差(那边一行写全)。
36
+ - **真因**:不是 host 没数据。[useQoderSection](../src/client/qoder/useQoderSection.ts) 拿到的 `QoderModelOption` 里 `contextWindow` / `supportsImages` / `reasoningEfforts` / `defaultMaxTokens` 都在(能力字段一律来自目录端点,随包兜底表也有逐模型事实),而是 [QoderSection.tsx](../src/client/qoder/QoderSection.tsx) 当时**刻意不把 hint 传给 ModelChecklist**——那次的需求是「模型行下面不需要介绍文字」,被误读成了「不要任何能力行」。
37
+ - **改法**:新增 `qoderModelRowHint()`,由 DTO 字段生成一行**数据**(`1M · text+image · 档位 low/high · 输出 131k`)并传给共享 ModelChecklist;散文式介绍仍然不进这一行。目录不给档位的模型写「无档位」而不是留空或套默认三档。共用新增的 `formatTokenCount()`(百万位保留两位、千位取整),二进制窗口不再渲染成 `1048576` 或 `131.1k`。
38
+ - **顺带修掉一个真 bug**:落盘快照的写入侧只写了 `is_reasoning`,没写 `thinking_config`,而回读侧的档位只从 `thinking_config.enabled.efforts` 取——于是**重启后所有推理模型都变成「无档位」**(模型是真推理模型,档位在磁盘上被丢了;重启前一切正常,所以只在冷启动那一眼看得出来)。现在写入侧按上游 effort 形态把梯子写回去(toggle 形态写 `enabled: {}`,不可关闭的写不带 `disabled`),读写仍是同一个 `readQoderThinkingConfig`,互为反函数。
39
+ - **验证**:`test/qoder-ui.test.tsx` 新增一例,钉住三行不同能力的摘要(单模态两档 / 多模态两档 / 无档位)与用户覆盖窗口按覆盖值显示;`test/qoder-model-catalog.test.ts` 新增两例,用生产写入侧 → 生产读取侧往返,断言梯子、可关闭性、图片与窗口都不丢(老快照缺字段时仍是原来的兜底,不炸);全量 typecheck 与 vitest 通过。
40
+
41
+ - **[opendesign] 新增第十三条订阅线路:OpenDesign(open-design.ai 订阅,浏览器设备授权 + API Key)**
42
+ - **协议面**:OpenAI Chat Completions 流式对话,网关 `https://amr-link.open-design.ai/v1`,`Authorization: Bearer` 鉴权。网关主机与 `/v1` 拼写转录自官方 Cloud 控制台的 quick-setup 包(它给 DeepSeek Harness 推荐的正是 Chat Completions),四个协议端点均经未认证探测确认(401 包络符合各自协议)。
43
+ - **登录一:浏览器设备授权(主路径)**。按官方 `vela login` 的公开 HTTP 契约重写:`POST /api/v1/cli/device-authorizations`(`{host, profile, cliVersion}`,**实测** 201 返回 deviceId / userCode / deviceSecret / activationUrl / pollIntervalSeconds),用户在浏览器确认后 `POST …/{deviceId}/token` 轮询取回 `vela_ctrl_…` 控制 Key + `sk-amr-…` 运行时 Key + 账号身份(字段名据上游 issue #8870;pending 帧的 null 形状也是实测的)。判定凭证「到位」看**字段在不在**,不看 status 字符串;取消过的流程绝不因迟到的确认而落号;宿主自身不打开浏览器(`test/login-opens-browser-once.test.ts` 钉住)。
44
+ - **登录二:粘贴 API Key(回退)**。控制台(工作区 → API Keys)签发,仅用于对话。`OPENDESIGN_API_KEY` 环境变量可投射首个账号;Key 测试读免费的目录端点,不花额度。
45
+ - **模型目录**:随包 37 个聊天模型(id、显示名、美元单价逐字转录自官方公开价目接口 `/api/v1/public/model-prices`),登录成功或添加 Key 后自动同步一次实时目录(`GET /v1/models`),同步条目持久化进设置文档,重启无需网络;目录真的变了才发 `llm/adapters-updated`。官方不公布各模型上下文窗口,默认 128k,卡片可按模型覆盖。
46
+ - **每模型能力(窗口 / 输出上限 / 是否收图 / 推理档位)**:原先一律 128k + 固定三档,现在随包表按模型带上真实取值——转录自本仓库已核对的同族模型表(command-code 官方注册表、cline-pass、Claude、Antigravity;冲突以 cline-pass 为准,注册表的 `off` 译成 DSH 的 `none`)。**浏览器授权账号还会读 AMR 控制面目录**(`GET /api/v1/models`,`metadata.contextWindowTokens`,`public_model_*` id 按字母数字归一),把上游真实窗口写回;用户覆盖值 > 实时目录 > 随包表 > 默认。`minimax-m3.1-flash`、`muse-wonder` 无同族表可核对,保留默认并在计划文档列名,不猜。
47
+ - **额度(钱包余额)**:只有浏览器授权账号带控制 Key,故只有它们能读 `GET /api/v1/wallet/balance`(`balanceUsd` 字符串);余额显示在账号行与输入框胶囊(胶囊只显示主账号,$0 才转红)。上游只给当前余额、没有可对照的总额,所以**不画百分比额度条**;钱包 401/403 按上游语义(OPEND-3553)只作「读不到」显示,绝不标记账号、绝不影响对话。粘贴 Key 的账号没有控制 Key,卡片会说明并指向控制台。
48
+ - **验证**:`npm run typecheck` 0 错误;新增 7 个 opendesign 套件(设备流 / 路由 / 适配器 / 映射 / 目录 / 契约 / UI)并给 `login-opens-browser-once` 与既有计数断言(TAB_IDS、卡片名、开关数、槽位 order 46)各追加一项;全量 `npx vitest run` 与 `npm run build` 通过。**未用真实订阅联调**:设备授权的创建/轮询响应是实测的,批准后的帧未观察到(据 #8870 推断且以字段存在为准),对话链路未与真实 Key 联调——清单见 `docs/opendesign-line-plan.md` §5。
49
+
50
+ ## 0.33.0 - 2026-10-20
51
+ - **[qoder] 看板上的 Qoder 卡片用的是字母 Q,不是官方标志——现在换成官方那张 412×412 应用图标**
52
+ - **现象**:设置页订阅列表里其余线路都是自家标志,只有 Qoder 是一个灰底字母方块。
53
+ - **真因**:[brand-icons.tsx](../src/client/hub/brand-icons.tsx) 里那条注释给出的理由是「官方 favicon 是一枚 73 KB 的复合字形,体量不适合内联」。那个 73 KB 确实存在——但 `https://qoder.com/favIcon.svg` 现在返回的是**整站 HTML**(73,379 字节,HTML 长度不是图标大小),页面真正挂的图标是 `<link rel="icon" type="image/png">` 指向的一张 alicdn PNG。于是「太大所以不做」这个判断建立在一次看错的抓取上。
54
+ - **改法**:`ProviderBrand` 新增 `image` 形态(data URI + `fit`),`BrandTile` 在它有值时画 `<img>` 并铺满底砖;Qoder 用官方 412×412 PNG(4,990 字节,sha256 `2276f0f8…26f3d`,2026-10-21 抓取 HTTP 200),base64 后 6.6 KB。旧注释连同 monogram 一起删掉,换成带 URL / 抓取时间 / 字节数 / 校验和的出处。
55
+ - **验证**:`npm run typecheck` 0 错误;`test/hub-overview-ui.test.tsx` 新增一例断言 Qoder 卡渲染 data URI 且不再出现 `dsh-hub-brand-mono`;全量 `npx vitest run` **280 文件 / 4572 passed / 7 skipped**。
56
+ - **[workbuddy] 会话一长就整轮失败、红条只有一句「必须以 system 提示词开头」——真因是上游 WAF 在按竞品客户端身份做指纹拦截**
57
+ - **现象**:截图那次是国际区账号第 2 轮起每轮必挂,报错前缀写着 `rejected the message history: the conversation must open with a system prompt`,与正文 `{"code":11128,"msg":"Illegal API invocation from an unapproved channel"}` 自相矛盾(历史首条本来就是 system)。同一账号发一句 `ping` 却可能正常,像是「聊着聊着就坏」。
58
+ - **真因**:`11128` 被上游复用给了至少两种拒绝,而 `unapproved channel` 这一种是**内容侧的 WAF**——网关对 `system`、`assistant.content` 以及 `assistant.tool_calls[].function.arguments` 里出现的第三方 Agent 身份声明做**精确短语匹配**,命中即整单拒绝,与鉴权、额度、模型、区域都无关;又因为每轮都全量重放历史,一条 assistant 消息或一次工具参数中毒,此后每轮必挂。本机对失败会话的 1,552 行系统提示词做行级二分,定位出**唯一触发行**是首行 `You are an AI agent powered by DeepSeek Harness.`(国内区在 `system`/`assistant` 中原样出现即 400,把 `AI agent` 改成 `AI assistant` 后同一份提示词 200);`Claude Code`、`Codex CLI`、`OhMyOpenCode`、`x-anthropic-billing-header` 则两区都会触发。
59
+ - **为什么还牵扯到别的区域**:号池里两个 `intl` 账号被上游瞬时 `429 (code 14003)` 打回后按既有逻辑冷却并**在同一次请求内轮换到 `cn` 账号**,而 `cn` 网关恰好拦首行那句话——于是「国际区账号」的会话撞上的是国内区的规则。
60
+ - **改法([mapper.ts](../src/host/workbuddy/mapper.ts))**:`sanitizeText()` 增加出站指纹改写(覆盖 `options.system`、`role: "system"` 消息、`assistant` 文本、以及 `tool_calls[].function.arguments` 的序列化参数),每条只改**实测确认会触发的那一句**、不动其余指令语义;同时把「剥 `\0`」拆成 `stripNulls()`,**入站 SSE 仍只做 `\0` 清理**——模型自己吐出的字不该被改写。
61
+ - **改法([adapter.ts](../src/host/workbuddy/adapter.ts))**:`code === 11128` 的判定从只认 `conversation id` 扩到 `conversation id|unapproved channel`,并把原始 detail 一并带出;此前除 `conversation id` 外的所有 11128 都被翻译成「必须以 system 提示词开头」,把读者指向一段本来正确无误的历史。
62
+ - **验证**:两区对拍——同一份含 5 类指纹的请求,脱敏前国内区与国际区都 `400 / 11128`,脱敏后都 `200`;新增 `test/workbuddy-message-shape.test.ts` 的跨 `system`/`assistant`/`tool_calls` 用例与 `test/workbuddy-adapter.test.ts` 的两种 11128 文案用例;`npm run typecheck` 0 错误,全量 `npx vitest run` **280 文件 / 4571 passed / 7 skipped**,`npm run build` 通过;用完整 1,552 行系统提示词带上下文对号池 4 个账号(2 `intl` + 2 `cn`)端到端实测,**四个账号全部 200**。
63
+ - **[qoder] 新增第十二条订阅线路:Qoder(全球区 qoder.com / 中国站 qoder.com.cn)**
64
+ - **协议面**:Qoder 自有的 COSY 签名对话协议(`POST {base}/algo/api/v2/service/pro/sse/agent_chat_generation`)——AES-128-CBC 加密的 UserInfo + RSA-1024 包裹的会话密钥 + canonical MD5 签名、自定义 Base64 alphabet + 三段交换的 body codec、外层信封包 inner JSON 的明文 SSE。全部算法按 Liki4/qodercli2api 的全合成 frozen fixtures(qodercli v1.1.34 互操作基线)**逐字节锁定**(`test/fixtures/qoder/` vendored,`test/qoder-cosy.test.ts` 8 例),并与 simonsmh/pi-provider-qoder 的 live 验证实现互相印证。
65
+ - **登录三路径**:浏览器设备流(仅全球区,challenge/verifier 轮询,不开本地回调端口,host 不自行开浏览器——`test/login-opens-browser-once.test.ts` 钉住)、手动粘贴 PAT(`jobToken/exchange` 兑换,PAT 本体留在 host 加密存储里供过期重兑换)、adopt 只读导入本机 qodercli 登录态(`~/.qoder/.auth/user`,AES 解密由 fixture 锁定;**区域不猜,是对两区 userinfo 各探一次探出来的**)。
66
+ - **账号模型**:多账号号池,凭据**会过期、可刷新**(提前 1 小时,逐字上游文档;PAT 来源重兑换、其余走 refresh_token 轮换),两区账号可同池共存(区域是凭据的属性)。
67
+ - **模型与额度**:目录实时发现(COSY 签名的 `/algo/api/v2/model/list`)+ 随包能力兜底表;COSY 版本每日跟随官方 CLI release feed(落后会拿到缩减目录);能力一律来自目录字段(`max_input_tokens` 那个陈旧地板不信)。额度卡片读 `/api/v2/quota/usage`(**非公开个人端点**,失败绝不影响模型请求),三只桶(个人/加购/组织包)分开画绝对量;订阅无公开单价,整页只有 token 与 Credits 数字,没有金额。
68
+ - **验证**:`npm run typecheck` 0 错误;新增 9 个 qoder 套件;全量测试与 build 通过。**未用真实账号联调**(mock + frozen fixtures 验收),联调清单见 `docs/qoder-line-plan.md` §9。
69
+ - **[kimi-code] K3 跑到一半突然整轮失败,红条上只有一个 `terminated` 配 `UNKNOWN`——现在它是可重试的 `TRANSPORT`**
70
+ - **现象**:`k3` 的长回答(截图那次已经执行 27 次思考、调用 30 次工具)偶发中断,界面是「本轮运行失败 terminated」,右侧徽标 `UNKNOWN`;手动重发一次立刻恢复。
71
+ - **真因**:连接在响应体中途被掐断时 undici 抛的是裸 `TypeError: terminated`——它自身不带任何错误码,真原因(ECONNRESET、对端关闭、body timeout)只挂在 `.cause` 上。[adapter.ts](../src/host/kimi-code/adapter.ts) 的读流循环只改写 mapper 已经定性的 in-band 错误,其余原样 `throw error`,于是这个裸值一路漏到 harness:`normalizeLlmFailure` 对它只能归一成 `UNKNOWN`,而 `UNKNOWN` 不在本线路的 `retryableCodes`(RATE_LIMIT / SERVER / TIMEOUT / TRANSPORT)里——一次断连就把整轮判死,连一次自动重试都没有,同时还把 `.cause` 丢掉,报告里连发生过什么都说不出来。
72
+ - **对照**:同仓库 codex 线路早为同一形状写过 `streamFailure()`([responses-client.ts](../src/host/responses-client.ts)),claude 线路有 `outputStarted ? PROVIDER_ERROR : TRANSPORT` 的分支;kimi 是唯一照旧写法留下的那条。
73
+ - **改法**:新增 `streamFailure()`([adapter.ts](../src/host/kimi-code/adapter.ts)),把逃出读循环的非 `LlmError` 值改判成 `TRANSPORT`,并把 `.cause` 链拼进消息(`Kimi Code stream failed: terminated: read ECONNRESET`);调用方取消仍是 `ABORTED`,本线路自己定性的 `LlmError` 原样通过。`assertStreamComplete` 那条同类形状——连接干净结束、但始终没等到终止事件——同步从 `PROVIDER_ERROR` 改成 `TRANSPORT`([mapper.ts](../src/host/kimi-code/mapper.ts)),两种「说到一半没了」都交给 harness 的退避重试(最多 3 次,1.5s–15s)。
74
+ - **为什么已经出字也重试**:失败的那次 attempt 由 DSH 记成 `assistant/attempt`——不进行模型历史、也不作为正文渲染(宿主 `turn-process.ts` 的 `visibleAssistantEvent` 对 attempt 恒为不可见),工具调用要等流结束才执行,所以重试既不会让模型看到重复内容,也不会重跑工具;用户看到的只是已流出的那段字被重试的那一次顶替,比整轮作废划算。
75
+ - **验证**:`npx vitest run test/kimi-code-adapter.test.ts test/kimi-code-stream-idle.test.ts test/kimi-code-mapper.test.ts` **90 passed**(新增 3 条断流用例;把 `throw streamFailure(...)` 临时改回 `throw error` 时,断流用例当场复现裸 `TypeError: terminated`,证明用例咬得住);`npm run typecheck` 0 错误;全量 `npx vitest run` **270 文件 / 4491 passed / 7 skipped**。
76
+ - **[minimax-code] `/video` 不再是「先选档位、再往输入框塞一段模板」:命令行有了图标,档位搬进面板,面板也终于不再把整页压暗**
77
+ - **现象**:斜杠菜单里 `/video` 是**唯一一行没有图标**的,混在「压缩 / 权限 / 模型 / 导出」里明显没对齐;点它先弹一个只有「常用档位 + 自定义…」的小列表,选中任何一档都会把一整段 `用 minimax_generate_video 生成视频:prompt=(在这里写画面描述)…` 灌进输入框,用户还得回到那段话里找该改哪一处;而「自定义…」打开的面板盖着一层半透明遮罩,整页对话被压暗,参数又是 9 个同权重字段竖着排下来。
78
+ - **改法([video-command.ts](../src/client/minimax-code/video-command.ts))**:`popupSelect` 换成 `action`——命令只做一件事:打开面板。图标走宿主的 `CommandContribution.icon`([`video-command-icon.tsx`](../src/client/minimax-code/video-command-icon.tsx),按 `IconProps` 的 `{ size, className }` 同形自绘,因为 `dsh-client-ui-primitives` 的 `lib` 是宿主装配产物、插件装不到)。**宿主侧限制**:`ui-commands` 的 `matchEnter` 只在裸命令时把一行交给客户端贡献(`if (!bare) return undefined`),所以「`/video` 后面直接写描述」做不到;改为**打开时把输入框里已有的文字当成画面描述带进来**(`composerDraftOf` 读 `InputState.draft`,并剥掉可能残留的前导命令 token)。
79
+ - **改法([VideoFormDialog.tsx](../src/client/minimax-code/VideoFormDialog.tsx) / [styles.ts](../src/client/minimax-code/styles.ts))**:去掉遮罩,面板变成阴影浮起的居中浮层(`margin:auto` 保住「内容太高仍可滚动」),点浮层外仍然关闭;版面按「先说画面、再说参数」重排——描述占最大位,下面一排**档位胶囊**,再一行模型/时长/分辨率,比例按联动规则锁死或可选,水印与参考媒体收进「更多参数」。
80
+ - **参考媒体**(新增 [video-media-input.ts](../src/client/minimax-code/video-media-input.ts) 与 [video-media-dropzone.tsx](../src/client/minimax-code/video-media-dropzone.tsx)):四排纯文本框换成四个媒体块,**浏览 / 拖拽 / 粘贴**三入口合一,路径显示成可移除的标签。桌面端靠 `__DSH_HOST_PATHS__.pathFor` 把 `File` 还原成真实绝对路径;拿不到路径时如实提示而不是编一条。`file://` 地址还原成本机路径;`http(s)` 链接里**只有 `cdn.hailuoai.com` 被接受**(宿主 `assertMinimaxCdnUrl` 实测只读这一个主机),其余公网地址当场拒绝——否则会变成计费之后才被服务端打回。CDN 参考视频读不到时长,所以每出现一条就长出一行「它的时长」,填了才进请求(宿主 `MINIMAX_VIDEO_REFERENCE_DURATION_REQUIRED`)。
81
+ - **两处回调**:① 档位胶囊原本会折成两行(三个全名并排塞不进 600px),改成`grid-auto-flow:column` 等宽一行,胶囊上只留 `H3 · 4秒` 这类短标签,分辨率与比例挂在 `title`/`aria-label` 上仍然查得到;② 每个媒体块补了一行**手填入口**——路径与 `@工作区文件` 都收(`stripReferencePrefix` 去掉那个前缀,因为工具参数要的是普通路径),回车即添加。
82
+ - **版面再调整**:面板从 `600px` 加宽到 `880px` 并改成两栏(左栏只放**画面描述**,右栏放档位、参数与参考媒体,≤720px 时折回一栏);拖拽区与输入行**合并为同一个框**——已收下的标签在上、输入行贴底,整块都是放置目标;输入框里的 `@` 现在会弹出**真实工作区候选**(新增 [video-workspace.ts](../src/client/minimax-code/video-workspace.ts) 包住宿主 `remote.workspaceFiles.list`:`@` 后的目录片段决定列哪一层,选中文件即入列、选中目录则继续下钻,`Tab` 取第一项、`Esc` 先收候选再关面板);宿主没有这条远端时自动退化为纯手填,不报错。
83
+ - **两栏落地后的三处修正**:① 描述框的 `width:100%` 加上左右内边距把它**顶出了栏宽**,文字一路压到右栏的「档位」标签上——补 `box-sizing:border-box`,两栏间距同时从 16px 放到 28px;② 展开「更多参数」后左栏只剩一小块输入框、下半截全空——描述框改为跟随右栏一起长(`flex:1` 一路传到 textarea,上限 520px);③ 左栏标题行的「已带入输入框里的文字」提示与右栏列头贴在一起,允许换行并在挤不下时省略。
84
+ - **验证**:`npm run typecheck` 0 错误;全量 `npx vitest run` **270 文件 / 4488 passed / 7 skipped**(新增 `test/minimax-code-video-media-input.test.ts`,扩写命令与面板两个套件);`npm run build` 通过。
85
+
5
86
  ## 0.32.0 - 2026-10-20
6
87
  - **[cline-pass] 凭据后端读不出来时不再吞掉 `CLINE_PASS_API_KEY`:环境变量与密钥环是两条独立来源**
7
88
  - **现象**:在**没有 Secret Service 的 Linux 环境**(裸 CI runner 最典型)里,`cline-pass` 的账号列表是空的,即使显式导出了 `CLINE_PASS_API_KEY`——于是「随包内置的密钥改用环境变量注入」这条文档化的用法在这些环境里**静默失效**。
package/README.md CHANGED
@@ -14,11 +14,12 @@
14
14
  - [使用](#使用)
15
15
  - [推理塌缩守卫(熔断,默认关闭)](#推理塌缩守卫熔断默认关闭)
16
16
  - [Command Code 线路](#command-code-线路)
17
- - [WorkBuddy 线路](#workbuddy-线路)
18
17
  - [Claude(订阅)线路](#claude订阅线路)
19
18
  - [GLM(ZCode 账号)线路](#glmzcode-账号线路)
20
19
  - [Step Code(Step Plan 订阅)线路](#step-codestep-plan-订阅线路)
21
20
  - [Cline Pass(OAuth 订阅)线路](#cline-passoauth-订阅线路)
21
+ - [Qoder(订阅)线路](#qoder订阅线路)
22
+ - [OpenDesign(订阅,API Key / 浏览器授权)线路](#opendesign订阅api-key--浏览器授权线路)
22
23
  - [子代理模型授权](#子代理模型授权0215-起032-起强制指定模型)
23
24
  - [随包分发的 Agent Preset](#随包分发的-agent-preset)
24
25
  - [本机登录](#本机登录)
@@ -29,20 +30,21 @@
29
30
 
30
31
  ## 线路总览
31
32
 
32
- 本插件同时接入 **10 条订阅线路**,每条各自独立注册 Provider、独立登录、独立额度卡片。
33
+ 本插件同时接入 **11 条订阅线路**,每条各自独立注册 Provider、独立登录、独立额度卡片。
33
34
 
34
35
  | Provider ID | 订阅 | 协议 | 登录方式 | 模型目录来源 |
35
36
  | --- | --- | --- | --- | --- |
36
37
  | `codex-chatgpt` | ChatGPT(Plus / Pro / Business…) | Responses | 浏览器 OAuth(localhost:1455 回调),或导入本机 Codex CLI 登录 | **实时** `/backend-api/codex/models` |
37
38
  | `kimi-code` | Kimi 会员 | Anthropic Messages | **设备码**(RFC 8628) | 实时 `/v1/models` + 本地兜底 |
38
39
  | `command-code` | Command Code | Anthropic / OpenAI 双轨 | 浏览器 OAuth 或粘贴 API Key | 实时 `/provider/v1/models` |
39
- | `workbuddy-subscription` | 腾讯 WorkBuddy / CodeBuddy | OpenAI 兼容(仅流式) | 扫描桌面端凭据或官方浏览器授权 | 实时 `/v3/config` |
40
40
  | `claude-subscription` | Claude Pro / Max | Anthropic Messages | 手动粘贴 / loopback 回调 | 实时 `/v1/models` + 本地兜底 |
41
41
  | `minimax-code` | MiniMax Code 编程订阅 | Anthropic Messages | **复用桌面端登录态** 或设备码 | **硬编码**(见下) |
42
42
  | `antigravity` | Google Antigravity | Gemini | Google OAuth | 内置表 |
43
43
  | `glm-coding-plan` | GLM Coding Plan(ZCode 账号,国区 BigModel / 国际区 Z.ai) | Anthropic Messages + OpenAI Chat Completions(按模型声明) | ZCode 账号 OAuth(浏览器中转轮询)+ 可选导入本机 ZCode 登录态 | 随包目录(源自官方 `zcode-builtin.json`) |
44
44
  | `step-code` | StepFun Step Plan 订阅(国区 api.stepfun.com / 国际区 api.stepfun.ai)或开放平台 API Key | OpenAI Chat Completions | **云端轮询**浏览器授权(不开本地回调端口)/ 手工粘贴 Platform API Key / 只读导入本机 `~/.stepcode/auth.json` | **实时运行时发现**(随包表刻意为空) |
45
45
  | `cline-pass` | Cline Pass | OpenAI Chat Completions | **浏览器设备码 OAuth**(WorkOS,账号入池、自动续期);API Key 仅作兼容回退 | 随包表兜底 + 官方 `recommended-models` / models.dev / 上游网关目录补全 |
46
+ | `qoder` | Qoder 订阅(全球区 qoder.com / 中国站 qoder.com.cn) | Qoder 自有(COSY 签名 + 信封 SSE) | **浏览器设备流**(仅全球区)/ 手工粘贴 PAT(两区)/ 只读导入本机 `~/.qoder/.auth/user` | **实时运行时发现**(COSY 签名 GET)+ 随包能力兜底表 |
47
+ | `opendesign` | OpenDesign 订阅(Go / Plus / Pro / Max) | OpenAI Chat Completions | **浏览器设备授权**(AMR 设备流,签发控制 Key + 运行时 Key);粘贴 Cloud 控制台的 API Key 作回退 | 随包表兜底(源自官方公开价目)+ 实时 `GET /v1/models` |
46
48
 
47
49
  > **只有 minimax-code 是硬编码目录,而且是有意的**:该端点的 `GET /v1/models` **未对订阅流量开放**(返回 503 `direct_route_not_configured`),任何「实时目录」都只会是一个必然失败的请求。其取值转录自本机官方客户端的 `config.yaml`。
48
50
  >
@@ -124,7 +126,7 @@
124
126
  - 工具调用 id 截断到 64 字符;
125
127
  - `stop` 按上限裁剪为最多 5 条、每条 ≤32 字节,超长整条丢弃(截断的停止串会在错误位置终止生成);
126
128
  - **K3 的长思考不会被截断**:输出上限跟随上下文窗口(保留 4096 余量),因为 `reasoning_content` 计入输出,固定 32K 会把 `max` 档的长推理中途截断并返回 `length`;调用方已知 prompt 规模时上限会被下调到放得下,未知时不做猜测;
127
- - **请求体超 2 MB 本地即拒绝**:该端点最常见的 400 是 `total message size N exceeds limit 2097152`,官方文案不给建议,这里直接按真实序列化体积拦截,并按「循环实际做了什么」给出处方(有图可丢但丢了也补不上缺口 / 图已丢完仍超限 / 本来无图,三种情况文案不同,不再声称发生了没发生的省略);确有实测上限的部署(前置代理/网关)可设 `DSH_KIMI_CODE_MAX_BODY_BYTES` 指定该档上限,取值非正整数时**回落到默认值而不是关闭守卫**;带视频的请求走 64 MiB 档,不受该变量影响;
129
+ - **请求体超 2 MB 本地即拒绝**:该端点最常见的 400 是 `total message size N exceeds limit 2097152`,官方文案不给建议,这里直接按真实序列化体积拦截,并按「循环实际做了什么」给出处方(有图可丢但丢了也补不上缺口 / 图已丢完仍超限 / 本来无图,三种情况文案不同,不再声称发生了没发生的省略);确有实测上限的部署(前置代理/网关)可设 `DSH_KIMI_CODE_MAX_BODY_BYTES` 指定该档上限,取值非正整数时**回落到默认值而不是关闭守卫**;带视频的请求走 64 MiB 档,不受该变量影响;**超限时报的是可恢复的溢出码**(`CONTEXT_WINDOW_EXCEEDED`):DSH 会对该码强制压缩本轮历史并重试同一步,所以长会话的表现是「压缩一次后继续」,而不是整轮失败——官方 `kimi-cli` 从不特殊处理 `2097152`,它靠 85% 提前压缩避开,详见下文「上下文窗口」那条;
128
130
  - **缓存是自动的,且无法手动干预**:
129
131
  - Kimi 按请求内容哈希命中前缀缓存。实测 `prompt_cache_key` 与 Anthropic `cache_control` 标记**均被忽略**(设与不设、同 key 与异 key 命中的是同一缓存),设备 id 与协议切换也不影响;
130
132
  - TTL 实测在 300–1800 秒之间,按 256 token 对齐;`/messages` 与 `/chat/completions` **共享同一缓存**;
@@ -140,42 +142,6 @@
140
142
  - **`dynamically_loaded_tools`(仅 K3)已实现**:K3 接受**消息级工具声明**(`messages[].tools`),可在会话中途用「无 `content` 字段的 system 消息」注入完整工具定义。官方把「保持顶层 `tools` 字节稳定」列为该特性的目的之一——中途修改/删除已发出的声明会使缓存从该点起失效,而在末尾追加不影响已缓存前缀,所以这是提升缓存命中的正道。声明按请求重发(服务端不保留),且仅在模型声明该能力时发送;
141
143
  - 额度卡片区分 **5 小时 / 7 天 / 月度(会员共享池)/ 月度(Kimi Code 池)** 四个窗口并显示重置时间,另可显示加油包余额;设置页为「设置 → 订阅服务 → Kimi Code」标签页,对话输入框右侧有该线路的额度胶囊。
142
144
 
143
- ### WorkBuddy 线路
144
-
145
- - 注册 `workbuddy-subscription` Provider,接入腾讯 **WorkBuddy / CodeBuddy 订阅**。
146
- - 该 ID 特意与用户常用的自定义 OpenAI 兼容线路 `workbuddy` 分开,安装插件不会覆盖或隐藏原有自定义 API;
147
- - 可直接扫描 CodeBuddy 桌面端已登录的 `*.info` 凭据,也可从设置页选择国区/国际区并通过官方浏览器授权添加账号;
148
- - 插件添加的凭据保存在系统加密存储中(Windows DPAPI / macOS Keychain / Linux Secret Service),token 只留在 Host 进程内,不进入浏览器;
149
- - **多账号号池,与其它线路同源**:走共享内核 `AccountPoolCore` 并复用同一张设置卡片,具备顺序耗尽 / 轮询调度 / 粘性会话、429 冷却换号、账号级失效保留、设为主账号与备注;
150
- - **账号身份是两层**:不可变的 `internalId` 是唯一路由键,`identityKeys`(uuid / email / 派生 seed)是**只增不换**的别名集,同一账号再次登录会**合并**而不是产生幽灵账号;
151
- - **唯一例外的诚实说明**:当服务端既没返回 uuid 也没返回 email 时无法自动识别同一账号,卡片会把该账号标注出来并提供**手动合并**;
152
- - **顺序耗尽 / 轮询调度 / 粘性会话**三种策略;
153
- - 429 冷却换号、401/403 账号级失效(保留账号、重新登录即恢复);
154
- - 设为主账号、账号备注、清除冷却与重新登录;
155
- - 桌面端扫描到的账号与插件内添加的账号**在同一号池里参与调度**;
156
- - **桌面账号归 IDE 所有,不可删除**:卡片只对插件自己添加的账号显示「删除」,桌面账号显示「隐藏 / 恢复」——隐藏只把它移出本插件的调度,绝不改动 CodeBuddy 的凭据文件;号池层同样拒绝删除桌面账号(双保险,均有测试);
157
- - **续期后原子写回**原凭据文件(只改 `auth` 块),以免桌面端掉线——桌面账号的 refresh token 会轮换,若只写进插件自己的加密存储,IDE 手里就只剩一个已作废的 token。同一进程内的并发调用**共用一次刷新**,且不会二次刷新;
158
- - 上游是 OpenAI 兼容的 `POST {backend}/v2/chat/completions`,但有两条硬约束:**只支持流式**(`stream:false` → 400 `code 11101`),且**首条消息必须是 system**(否则国际区返回 400 `code 11128`)。因此请求构造器始终发送 `stream:true`,并在调用方没给系统提示时补一条中性的,避免手搓的一次性请求踩到这条规则;
159
- - **区域是凭据属性,不是请求属性**:`*.workbuddy.ai` / `*.codebuddy.ai` 走国际区 `https://www.<apex>`,其余走国区 `https://copilot.tencent.com`;
160
- - 账号卡片逐条标注每个账号的**国区 / 国际区**并允许选择;切换后,模型目录、额度和后续对话都使用该账号;历史凭据快照按账号身份自动去重;
161
- - 插件托管账号可真正删除;桌面扫描账号只能从本插件隐藏/恢复,永不删除 CodeBuddy 的原文件;
162
- - 两区模型清单不同,把模型发到不服务它的区会返回 400 `code 11102`,因此模型选择器**按当前账号区域过滤**;
163
- - **模型目录取自网关自己的 `/v3/config`**(官方 CLI 启动时读的就是它):每个模型的真实上下文上限、输出上限、是否接受图片、以及可用的思考档位都在这里,不做任何按模型名猜测。`/v1/models` 在这条线路上是 404,所以此前只能靠内置表——现在内置表只作为离线兜底,且是**从真实 `/v3/config` 转录**的(早期手写版本把 `glm-5.3`、`kimi-k3` 的窗口猜成 200K/256K,实际都是 1M);
164
- - **目录不是服务全集,模型必须实测**:实测国际区的 `gpt-6-sol`、`gpt-6-luna`、`gemini-3.8-flash` 都能正常返回 200,却**完全不在 `/v3/config` 里**(该接口只公布 `gpt-6-astra`)。而联网时目录会**整体替换**内置表,只加进内置表等于加了个看不见的模型——所以这三个单列一份实测表(`UNPUBLISHED_MODELS`),并合并进实时目录。存在性、思考档位、图片支持是**打接口量出来的**(`gpt-6-sol` 只收 `low`/`medium`/`high`/`xhigh`/`max`,`minimal` 被拒);上下文与输出上限**继承同族已公布的兄弟模型**(`gpt-6-astra` / `gemini-3.5-flash`),因为网关对这些 id 不公布任何信息,而 `max_tokens` 也不受窗口约束(连 `gpt-6-astra` 自己都接受远超其公布上限的值),无从反推。三者仍可在设置页逐模型覆盖;判定一个 id 是否存在只有一条路——直接问:`code 11102` 是没有这个模型,`code 11133` 是模型存在但参数被拒;
165
- - 目录同时区分**默认服务的上下文长度**与**模型上限**(如 `deepseek-v4.1-flash` 默认 300K、最大 1M)。本线路不发送显式长度参数,所以 DSH 的压缩与溢出判断按**默认服务长度**计算,不会让请求越过后端实际接受的窗口;
166
- - 思考档位**逐模型**取目录声明的档位表,回落顺序为「调用方显式指定 → 用户配置的默认档 → 目录为该模型声明的默认档」;
167
- - **最后一档不能省**:上游在请求不带 `reasoning_effort` 时返回**空的 `reasoning_content`**(实测同一提示:不带字段 0 字符,带字段 130–215 字符)——不发送等于静默丢弃模型的思考;
168
- - 目录里的单个 `effort` 字段是**默认值**,既不是完整档位表也不是「只此一档」:实测这类模型接受 `low` / `high` / `max`(其余取值被上游收敛到最近档),因此使用这三档的标准表(`WORKBUDDY_STANDARD_EFFORTS`);显式声明了 `supportedEfforts` 的模型则原样采信;
169
- - 目录给出的**默认档也可能落在档位表之外**(`minimax-m3`、`kimi-k3`、国区 `glm-5.3` 都报 `medium` 却只有三档),这种值会收敛到最近档——否则默认档被判不支持而丢弃,退化成上一条的空 `reasoning_content`;
170
- - 不在该模型档位集合内的取值会被**忽略而不是发出去**(上游对不支持的档位返回 `code 11150`);
171
- - **瞬时失败按错误类别重试**(`src/host/workbuddy/adapter.ts`):
172
- - 上游 5xx 与 `code 11134` 归为 `SERVER` 并走有界退避(最多 3 次,1.5s 起步、15s 上限、0.2 抖动);
173
- - 额度耗尽(429 / `code 6004` / `code 14003`,其中 6004 的正文带重置时刻)归为 `RATE_LIMIT` 并遵守 `Retry-After`;
174
- - 401/403、跨区模型、不可用图片、历史形状错误都**不重试**,并给出可操作的提示(换模型 / 换图片 / 重新登录桌面端);
175
- - **流中断即失败**:上游必然以 `finish_reason` 或 `data: [DONE]` 结束,两者都缺失说明连接中途断开,此时抛出错误而不是把半截文本当成完整回答;
176
- - 额度来自 `/billing/meter/get-user-resource`,卡片展示套餐名、本周期已用/上限、剩余额度与重置时间;设置页为「设置 → 订阅服务 → WorkBuddy」标签页,对话输入框右侧有该线路的额度胶囊。
177
-
178
-
179
145
  ### MiniMax Code(编程订阅)线路
180
146
 
181
147
  - 注册 `minimax-code` Provider,接入 **MiniMax Code 编程订阅**。它与 MiniMax 开放平台(按量计费的 API Key)是两套互不通用的系统:
@@ -187,7 +153,7 @@
187
153
  - **不创建、不删除、不等待** `auth.lock`(那是桌面端自己的刷新锁,第二方碰它就可能打断官方客户端的刷新);
188
154
  - **两种登录来源互不覆盖**:
189
155
  - 桌面端的 `auth.json` 归桌面端所有,本插件只读;若本机没有它,才在设置页用 **RFC 8628 设备码流程**(PKCE S256)登录,凭据存到本插件自己的 `$DSH_HOME/storages/minimax-code-credentials.json`;
190
- - **登出只作用于本插件自己那份凭据**:桌面端的登录态会被续期写回(只读优先),但**绝不撤销、绝不删除**,登出请求对它会被拒绝并说明原因(撤销它等于把你从正在跑的 MiniMax Code 里踢下线),这也是 WorkBuddy 对桌面账号的既有做法;
156
+ - **登出只作用于本插件自己那份凭据**:桌面端的登录态会被续期写回(只读优先),但**绝不撤销、绝不删除**,登出请求对它会被拒绝并说明原因(撤销它等于把你从正在跑的 MiniMax Code 里踢下线);
191
157
  - **区域是凭据属性**:`cn` 用 `account.minimax.cn` / `agent.minimax.cn`,`global` 用对应 `.io` 域名。两个区域的目录都会被探测,因此国际区账号不会因为没有国区文件而「未登录」;读回凭据时**以记录里存的区域为准**,而不是拿默认区域覆盖它;
192
158
  - **模型目录是硬编码的,而且必须硬编码**(`src/host/minimax-code/model-catalog.ts`):
193
159
  - 该端点的 `GET /v1/models` **未对订阅流量开放**(503 `"{\"errorCode\":50115,\"errorReason\":\"direct_route_not_configured\"}"`),所以任何「实时目录」都只会是一个必然失败的请求;
@@ -572,7 +538,9 @@ DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允
572
538
  3. 点击 **设备码登录**,在弹出的浏览器页面确认授权(页面已预填用户码;也可手工复制用户码到 `verification_uri`);
573
539
  4. 登录完成后按需勾选模型、设置默认思考档位与上下文窗口。
574
540
 
575
- `kimi-code` 会像其他 Provider 一样出现在 DSH 模型选择器中。**上下文窗口**默认取模型目录值,可逐模型覆盖(用于 DSH 的压缩与溢出判断,支持 `1M` / `256K` / `200000` 等写法)——注意 `k3` 的 1M 上下文需要 Allegretto 及以上套餐,因此默认按 256K 计算,升级后再在此覆盖为 1M。**默认思考档位**只提供 `low` / `high` / `max` 三档与「关闭思考」(服务对这三档以外的取值直接报 400);切换模型或切换档位都会使上下文缓存失效,建议在同一会话内保持一致。
541
+ `kimi-code` 会像其他 Provider 一样出现在 DSH 模型选择器中。**上下文窗口**默认取模型目录值,可逐模型覆盖(用于 DSH 的压缩与溢出判断,支持 `1M` / `256K` / `200000` 等写法)——注意 `k3` 的 1M 上下文需要 Allegretto 及以上套餐,因此默认按 256K 计算,升级后再在此覆盖为 1M。
542
+
543
+ **申报的窗口默认就是目录值或你的覆盖值,不做折算**——`k3` 覆盖成 1M 就按 1M 申报。撞上 2 MB 请求体上限时,本地守卫抛的是 `CONTEXT_WINDOW_EXCEEDED`:DSH 会对该码**自动压缩本轮历史并重试同一步**,所以表现是「压缩一次后继续」,不是整轮失败。想要「撞墙之前就自动压缩」,可设 `DSH_KIMI_CODE_BYTES_PER_TOKEN` 启用窗口折算:申报值 = `min(声明值, floor(2MB / d))`。取值按内容密度选——DSH 的 token 计量是固定密度(4 字符/token,见 `@deepseek-ai/dsh-token-meter`),看不见中文占 3 字节,所以「一个计量 token 占多少字节」= 4 × 每字符字节数:纯 ASCII 约 4、中英混合约 8、密集中文约 12;对应的字节墙分别是 524288 / 262144 / 174763 计量 token。实测:`d=4` → 524288(代码为主的会话在 1.68 MB 处压缩)、`d=8` → 262144(混合)、`d=12` → 174762(中文,三种密度都在墙前)。压力阈值是 `min(floor(0.8 × 窗口), 窗口 − 65536)`(`compaction-basic`:`thresholdRatio` 默认 0.8、`headroomTokens` 默认 65536、本线路不申报 `defaultMaxTokens` 故无输出预留)。该变量是**可选的提前压缩开关**而不是安全守卫:不设或拼错等于不启用,真正兜底的是上面那个恢复码。**默认思考档位**只提供 `low` / `high` / `max` 三档与「关闭思考」(服务对这三档以外的取值直接报 400);切换模型或切换档位都会使上下文缓存失效,建议在同一会话内保持一致。
576
544
 
577
545
  **额度倍率**取自官方模型表并显示在设置页模型能力表的「说明」列(悬停可一并看到):`k3` = **2×**、`k3-256k` = **1×**、`kimi-for-coding` = **1×**、`kimi-for-coding-highspeed` = **3×**。倍率绑定**模型 id**,与上下文长度、缓存命中率都无关——选 `k3` 就是 2×,哪怕只用几千 token。注册表不认识的新模型 id 显示为**未知**而不是猜成 1×。
578
546
 
@@ -676,69 +644,6 @@ DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允
676
644
 
677
645
  与 `codex-chatgpt` 线路一样,所有修改状态的路由只接受同源 JSON POST,并校验 `Origin` 与 `Host`。
678
646
 
679
- ## WorkBuddy 线路
680
-
681
- 1. 重启 DSH,让 Host 加载本插件;
682
- 2. 打开 **设置 → WorkBuddy**;
683
- 3. 二选一:登录 CodeBuddy 桌面端后点「重新扫描」,或点「账号管理」右上角的「添加国区账号 / 添加国际区账号」并在官方页面完成浏览器授权;
684
- 4. 在「账号管理」里选择账号(每条都标注国区 / 国际区),再按需勾选模型、设置默认思考深度与上下文窗口。
685
-
686
- `workbuddy-subscription` 会像其他 Provider 一样出现在 DSH 模型选择器中,并可与名为 `workbuddy` 的自定义 API 同时存在。**上下文窗口**默认取网关 `/v3/config` 声明的**默认服务长度**,可逐模型覆盖(用于 DSH 的压缩与溢出判断,支持 `1M` / `300K` / `200000` 等写法);**默认思考深度**逐模型生效,选项由当前账号各模型**实际声明的档位**取并集(仅部分模型支持的档位会标注数量),档位不在该模型集合内时不会被发送。
687
-
688
- **消耗倍率**也来自同一份目录:每个模型在 `/v3/config` 里带一个 `credits` 字符串(如 `x0.79 credits`、`x6.67`),就是该模型消耗套餐额度的相对速率——官方 CodeBuddy 客户端把它渲染成 `6.67x` 显示在模型下拉里。设置页的模型行末尾会以 `倍率 0.79x` 的形式给出,**两区不一致时把两区都写出来**(实测国际区 `deepseek-v4.1-flash` 是 `0.00`、国区是 `0.11`),账号区域已知时只显示该区的值,**绝不拿另一个区的数字顶上**(跨区调用本来就会被上游以 400 `code 11102` 拒绝)。`x0.00` 是真实声明(该模型不消耗额度),照常显示;网关没给倍率的模型(如 `default-model`)则**整项不出现在提示里**,而不是编一个 0。
689
-
690
- **凭据目录**按平台解析,可用 `CODEBUDDY_AUTH_DIR` 覆盖(与官方工具链一致):
691
-
692
- | 平台 | 默认目录 |
693
- | --- | --- |
694
- | Windows | `%LOCALAPPDATA%\CodeBuddyExtension\Data\Public\auth` |
695
- | macOS | `~/Library/Application Support/CodeBuddyExtension/Data/Public/auth` |
696
- | Linux | `$XDG_DATA_HOME/CodeBuddyExtension/Data/Public/auth`(默认 `~/.local/share`) |
697
-
698
- 目录里通常同时存在当前凭据与若干带时间戳的历史快照。插件**优先取 `workbuddy-desktop.info` / `codebuddy-desktop.info` 这类规范文件名**,其余按 token 剩余有效期取最长的一个——只按文件 mtime 选会选到过期快照。卡片会列出目录里所有可用凭据,标明各自区域。
699
-
700
- **失败分类与重试**:这条线路把失败分成「换号能解决」与「换号也一样」两类,判据不是状态码本身。
701
-
702
- - **401 不等于凭据失效**:实测同一枚 bearer 在 `GET /v3/config` 上是 200、在 `POST /v2/chat/completions` 上是 401(APISIX 生成的 HTML 拒绝页)。本线路的 access token 有效期约 28 天,本地时钟**几乎永远不会**在 401 时触发续期,因此这类 401 多半是会话被顶掉或网关/路由层拒绝。收到 401 时先对**同一个账号**强制续期一次(`POST /v2/plugin/auth/token/refresh`)并用新 token **原样重放同一份请求体**:续期成功即说明凭据还活着,不写任何账号状态;**只有续期本身被上游拒绝**才把该账号记为需要重新登录,此时才换号。
703
- - **请求期不写「需要重新登录」**:401 只让该账号退出**本次请求**的轮换,不写号池状态。此前一次 lane 级的 401 会在同一个请求里把每一个账号依次写成 `expired`,而号池没有任何清除路径,于是整条线路要求全部重新登录。
704
- - **自愈**:被标记的账号在**每个请求前**与卡片列表前都会重新对照权威存储(桌面账号看 CodeBuddy 自己的凭据文件、托管账号看插件的加密存储);换了另一枚**未过期** token 就清掉标记并采用新凭据,仍持着被拒那枚的账号保留标记。
705
- - **429 冷却换号**:按 `Retry-After`(缺省 5 分钟,上限 10 分钟)冷却该账号,并用同一份 body 换下一个账号重试。
706
-
707
- **上游接口**:
708
-
709
- | 用途 | 路径(前缀为区域后端) |
710
- | --- | --- |
711
- | 模型生成 | `POST /v2/chat/completions`(仅流式) |
712
- | 模型目录 | `GET /v3/config` |
713
- | 浏览器授权 | `POST /v2/plugin/auth/state` + `GET /v2/plugin/auth/token` |
714
- | 令牌续期 | `POST /v2/plugin/auth/token/refresh` |
715
- | 额度 | `POST /billing/meter/get-user-resource` |
716
- | 签到状态 | `POST /billing/meter/checkin-activity-status`(仅国区) |
717
- | 每日签到 | `POST /billing/meter/daily-checkin`(仅国区) |
718
-
719
- 两区后端分别是 `https://copilot.tencent.com`(国区)与 `https://www.workbuddy.ai` / `https://www.codebuddy.ai`(国际区)。**请求身份统一使用 CLI UA**(`CLI/2.63.2 CodeBuddy/2.63.2`):实测 `CodeBuddyIDE` 被 `/v3/config` 以 400 `code 12403` 拒绝,国际区对话端点也直接返回 401,因此不做按端点切换。
720
-
721
- **每日自动签到**(仅国区;语义与 workbuddy2api 的 `daily_checkin.py` 对齐):DSH 启动时自动签到一轮,运行期间每 10 分钟幂等补检(当日已签的账号不再发请求);签到前先查签到状态,token 过期会自动续期并回写(桌面账号写回 CodeBuddy 自己的凭据文件)。失败当天最多自动重试 3 次;**活动尚未开启的账号不会当天放弃**——首次签到状态若报无权益,之后每小时复查一次,避免开机早于活动开放时间就错过当天。国际区账号不参与(国际后端没有签到活动)。**DSH 没开机的当天不会签到**——插件不是常驻服务。设置页「每日签到」区块可开关自动签到、查看「今日已签 x/y · 无活动 z · 失败 w」并手动「立即签到」(手动会重跑一轮并绕过开关与重试上限)。签到状态存在 `storages/workbuddy-checkin.json`,token 不出 Host。
722
-
723
- ### 插件路由(WorkBuddy)
724
- 所有路由都以 `/workbuddy/api` 为前缀:
725
-
726
- | 方法 | 路径 | 说明 |
727
- | --- | --- | --- |
728
- | GET | `/status` | 账号、额度、模型目录与路由归属(每次都重扫凭据目录) |
729
- | GET | `/accounts` | 列出桌面扫描及插件托管账号(不含 token) |
730
- | POST | `/accounts/login` | 按国区/国际区启动官方浏览器授权 |
731
- | GET | `/accounts/login/status` | 读取授权轮询状态(不含 token) |
732
- | POST | `/accounts/action` | 删除插件托管账号,或隐藏/恢复桌面账号 |
733
- | POST | `/rescan` | 清缓存并重扫凭据目录 |
734
- | GET / POST | `/quota` | 强制刷新额度(POST)或读取当前状态(GET) |
735
- | GET / POST | `/models`、`/settings` | 读取或更新勾选模型、上下文窗口与默认思考深度 |
736
- | POST | `/catalog/refresh` | 强制刷新网关模型目录 |
737
- | POST | `/connection/test` | 用已识别凭据向上游发一次最小请求测试连接 |
738
- | POST | `/checkin/now` | 立即执行一轮每日签到(忽略开关与重试上限、不受无活动复查节流限制;当日已签的账号仍跳过) |
739
-
740
- 与其它线路一样,所有修改状态的路由只接受同源 JSON POST,并校验 `Origin` 与 `Host`。
741
-
742
647
  ## Claude(订阅)线路
743
648
 
744
649
  ### 风险须知(请先读)
@@ -846,7 +751,7 @@ adaptive 分支对这类模型额外发送 `block_binding: drop_block`(**不**
846
751
  ### 插件路由(MiniMax Code)
847
752
 
848
753
  所有路由都挂在本线路自己的 `/minimax-code/api` 前缀下(与 antigravity / claude / command-code /
849
- kimi-code / workbuddy 一致),**不**占用 Codex 那条线路的 `/api/dsh-chatgpt-subscription` 前缀。
754
+ kimi-code 一致),**不**占用 Codex 那条线路的 `/api/dsh-chatgpt-subscription` 前缀。
850
755
 
851
756
  | 方法 | 路径 | 说明 |
852
757
  | --- | --- | --- |
@@ -856,7 +761,7 @@ kimi-code / workbuddy 一致),**不**占用 Codex 那条线路的 `/api/dsh-
856
761
  | POST | `/login/cancel` | 取消授权 |
857
762
  | POST | `/logout` | 注销**本插件自己那份**凭据;桌面端的登录态会拒绝并说明原因 |
858
763
  | POST | `/test` | 向上游发一次最小 Messages 请求测试连接 |
859
- | GET / POST | `/models`、`/settings` | 读取或更新启用开关、勾选模型、上下文窗口覆盖与默认思考深度;两个路径等价(kimi/claude 叫 `/models`,workbuddy/command-code 叫 `/settings`) |
764
+ | GET / POST | `/models`、`/settings` | 读取或更新启用开关、勾选模型、上下文窗口覆盖与默认思考深度;两个路径等价(kimi/claude 叫 `/models`,command-code 叫 `/settings`) |
860
765
  | GET / POST | `/accounts` | 号池动作:`set-primary` / `set-alias` / `delete` / `clear-cooldown` / `strategy` / `relogin` / `adopt`(`adopt` = 把桌面端当前登录态导入号池,只读、不改动官方客户端文件) |
861
766
  | GET / POST | `/quota` | 读取用量快照(POST 强制刷新):Token Plan 的 5 小时与每周窗口。**上游读失败不算请求失败**——`quota` 是可选字段,失败时卡片显示“暂无数据”,对话不受任何影响 |
862
767
 
@@ -1016,6 +921,55 @@ access token / refresh token / 授权码 / PKCE verifier**;诊断里提到某
1016
921
 
1017
922
  与其它线路一样,所有修改状态的路由只接受同源 JSON POST 并校验 `Origin` 与 `Host`。**响应里永远不含 api_key、`Oasis-Token` 或账号密码的原文**——下发的只有「有没有凭据、从哪来、大概什么时候过期、上次读它出了什么问题」。
1018
923
 
924
+ ## Qoder(订阅)线路
925
+
926
+ \`qoder\`(显示名 **Qoder(订阅)**)接入 **Qoder 订阅**(全球区 \`qoder.com\` / 中国站 \`qoder.com.cn\`)。两个区域是**两套互不通用**的部署:一个区铸造的凭据打另一个区的端点会被上游直接拒绝。因此**区域是凭据的属性而不是请求的属性**,号池里可以同时持有两区账号且互不串用。
927
+
928
+ > ⚠️ **这条线路的协议面是逆向工程成果,不是官方公开契约,这是本线路唯一的合规与稳定性风险点。** COSY 签名、body codec、额度端点都来自开源实现([pi-provider-qoder](https://github.com/simonsmh/pi-provider-qoder),live 验证)与 [qodercli2api](https://github.com/Liki4/qodercli2api) 对官方 Qoder CLI v1.1.34 的全合成 frozen fixtures——本插件把每一步**逐字节**锁在测试里(\`test/fixtures/qoder/\` vendored),但上游随时可能改协议或收紧风控。额度端点(\`/api/v2/quota/usage\`)更是**非公开的个人接口**:它的失败绝不影响模型请求,卡片把它画成尽力而为的读数,不是账单。COSY 版本跟随官方 CLI 的 release feed(落后会拿到**缩减的**模型目录),拉取失败回落随包版本。
929
+
930
+ **登录**(三条路,任选其一,互不替代):
931
+
932
+ 1. **浏览器设备流**(仅全球区):本线路**不开本地回调端口**,流程跑在 host 上,你在浏览器里人工确认,卡片每 2 秒跟随一次进度。授权页地址始终显示在卡片上——host 不自行开浏览器,即使自动打开失败你也能自己复制打开;
933
+ 2. **手工粘贴 PAT**(两区都支持):在账号的 Integrations 页生成一个 Personal Access Token(\`pt-...\`)粘贴进来。它先被兑换成短期 job token(\`jt-...\`)使用;**PAT 本体只存在 host 的加密存储里**,用途只有一个:过期后重新兑换;
934
+ 3. **只读导入本机 \`qodercli\` 登录态**(可选):读 \`~/.qoder/.auth/user\`(AES-128-CBC 信封,key 是 \`machine_id\` 前 16 个字符——格式逐字来自协议文档,且有 frozen fixture 锁定)。**只读**,从不创建、修改或删除 qodercli 的任何文件。文件不记录区域,所以导入时对两区的 userinfo 各探一次(只读 GET),哪区认账就是哪区;两区都不认(token 已过期/被撤销)就如实告知,**不替你猜一个必然 401 的区域**。
935
+
936
+ **凭据会过期、可刷新**(与 step-code 的静态凭据相反):号池在 \`expiresAt - 1 小时\` 前自动续期——PAT 来源拿存下的 PAT 重兑换,其余走 \`refresh_token\` 轮换(轮换的新对先落盘再使用)。刷新被上游判 401/403 时账号标记为失效并提示重新登录;网络/5xx 只算瞬时失败,不会误登出。
937
+
938
+ **能力**:Qoder 自有对话面(OpenAI 形状的消息数组包在 \`RemoteChatAsk\` 信封里,过自定义 body codec,COSY 头签名,应答是信封 SSE)。模型目录**运行时发现**(COSY 签名的 \`GET /algo/api/v2/model/list?Encode=1\`)+ 随包能力兜底表;能力(上下文窗口 / 图片输入 / 思考档位)全部来自目录字段(\`context_config\` / \`is_vl\` / \`thinking_config\`),**不从模型名推断**,目录里那个 \`max_input_tokens\` 陈旧地板值明确不信。档位在本地先收敛到**模型真实梯子**再发出去;toggle 形态的模型只发开关。输出上限取上游文档化的 128K(目录不报输出上限)。
939
+
940
+
941
+ **模型行**:勾选行下面一行写清能力——上下文窗口、是否收图、思考档位、输出预算(例:`1M · text+image · 档位 low/high · 输出 131k`),全部取自目录字段(`context_config` / `is_vl` / `thinking_config`)与随包兜底表的逐模型事实,客户端不按模型名推断。
942
+
943
+ **缓存与用量**:响应 usage 里读缓存命中(\`prompt_tokens_details.cached_tokens\`,顶层 \`cached_tokens\` 兜底),缓存写入读 \`cache_write_tokens\`(上游不报就是 0,不做「恒零」声明)。**整页不出现任何金额**:订阅没有公开的 token 单价,Credits 与金额之间没有官方汇率——只显示 token 与 Credits 数字。
944
+
945
+ **已知限制**:
946
+
947
+ - **未用真实账号联调**:全部验收来自 mock + frozen fixtures(\`docs/qoder-line-plan.md\` §9 是联调清单与逐项风险)。上线时的实际置信度就是这句话,不打包成「已验证」;
948
+ - **中国站没有设备流**:它只接受 PAT 粘贴或导入本机 qodercli 登录态(卡片在选中国站时不显示浏览器登录按钮,host 侧同样拒绝);
949
+ - **额度端点是非公开个人接口**(见开头的风险提示);三只桶分开渲染,缺桶不画,没有总额的桶不参与任何百分比;
950
+ - **品牌标记是 monogram**:官方 favicon 是一枚 73KB 的复合字形,不适合内联转录;卡片用字母 Q,底砖与字形色的取值都取自官方站点资产(出处记在 \`brand-icons.tsx\` 注释里);
951
+ - **\`chat_context\` 按官方 fixture 发最小形态** \`{}\`:若实测发现模型路由异常,对照 pi-provider 的 \`extra.modelConfig\` 补上(计划 §9 风险 10)。
952
+
953
+ ### 插件路由(Qoder)
954
+
955
+ 所有路由都以 \`/qoder/api\` 为前缀:
956
+
957
+ | 方法 | 路径 | 说明 |
958
+ | --- | --- | --- |
959
+ | GET | \`/status\` | 区域、号池、模型目录、额度快照、缓存统计与登录流程;页面唯一的读口 |
960
+ | POST | \`/settings\` | 启用开关、勾选模型、上下文窗口覆盖、默认思考档位、手选账号 |
961
+ | POST | \`/accounts\` | 号池动作:\`set-primary\` / \`clear-cooldown\` / \`delete\` / \`relogin\` / \`strategy\` |
962
+ | POST | \`/login/start\` | 开一次设备流登录(仅全球区),立即返回流程状态(不阻塞、不开本地回调端口、host 不自行开浏览器) |
963
+ | GET | \`/login/status\` | 轮询登录流程状态;host 在后台轮询上游 |
964
+ | POST | \`/login/cancel\` | 取消登录流程 |
965
+ | POST | \`/login/manual\` | 手工粘贴 PAT(**请求体必然带凭据,刻意不在冻结契约里声明**) |
966
+ | POST | \`/adopt\` | 只读导入本机 qodercli 登录态;失败**永不阻断**,每种失败都是契约里的一个 reason |
967
+ | POST | \`/connection/test\` | 对目录端点做一次 COSY 签名的探测(**裸 DTO,不走信封**);它答「这个账号能不能读目录」,答不了「这个具体模型会不会成功」 |
968
+ | POST | \`/quota\` | 强制刷新额度;一次失败的额度读不是一次失败的请求,卡片照旧渲染 |
969
+ | POST | \`/catalog/refresh\` | 强制刷新模型目录 |
970
+
971
+ 与其它线路一样,所有修改状态的路由只接受同源 JSON POST 并校验 \`Origin\` 与 \`Host\`。**响应里永远不含 job token、PAT 或刷新令牌的原文**——下发的只有「有没有凭据、从哪来、大概什么时候过期」。
972
+
1019
973
  ## Cline Pass(OAuth 订阅)线路
1020
974
 
1021
975
  `cline-pass`(显示名 **Cline Pass**)接入 [Cline Pass](https://cline.bot/cline-pass)。与其它订阅线路一样,它**用 OAuth 登录**:Cline 账号走 **WorkOS 设备码授权**(RFC 8628 形态),浏览器确认后账号自动加入号池;聊天请求携带 `Authorization: Bearer workos:<accessToken>`,access token 到期前用轮换的 refresh token 自动续期。静态 API Key 仍保留为**兼容回退**(旧 `dsh-cline-pass` 用户或某些部署),但不再是默认入口。
@@ -1077,6 +1031,43 @@ OAuth 是默认入口;「使用 API Key」按钮保留为兼容回退,部署
1077
1031
 
1078
1032
  见上表;与其它线路一样,路由表在 `src/host/cline-pass/routes.ts` 与客户端 `api.ts` 各有一份手写副本,集成结束时逐条对照。设置卡在「设置 → 订阅服务 → Cline Pass」,对话输入框右侧有该线路的额度胶囊(显示当前已读账号中最紧张的窗口)。
1079
1033
 
1034
+ ## OpenDesign(订阅,API Key / 浏览器授权)线路
1035
+
1036
+ `opendesign`(显示名 **OpenDesign**)接入 [OpenDesign](https://open-design.ai/) 订阅(开源项目 [nexu-io/open-design](https://github.com/nexu-io/open-design))。OpenDesign 的付费计划(Go / Plus / Pro / Max)含模型额度,官方明确支持第三方 Agent 使用其 API,并点名支持 Codex、Claude Code、**DSH**、OpenCode 等客户端。
1037
+
1038
+ **登录有两条路,共用同一个号池**:
1039
+
1040
+ 1. **浏览器授权(推荐)** — 点「登录 OpenDesign 账号」,宿主向 `POST /api/v1/cli/device-authorizations` 申请一次设备授权并给出激活地址与授权代码;在浏览器确认后,查询接口 `POST /api/v1/cli/device-authorizations/{deviceId}/token` 会返回一对 Key:`vela_ctrl_…` 控制 Key 与 `sk-amr-…` 运行时 Key,外加账号身份(邮箱 / 名称 / 套餐)。运行时 Key 走对话,控制 Key 读钱包余额。签发的 Key 不过期,失效只能重新登录;**宿主自身不打开浏览器**,由卡片 `window.open` 打开激活页(与本插件其它线路同一条规则)。
1041
+ 2. **粘贴 API Key(回退)** — 在 Cloud 控制台(工作区 → API Keys)创建后粘贴,仅用于对话(没有控制 Key,因此读不了余额)。
1042
+
1043
+ 号池支持多账号轮换;部署也可以用 `OPENDESIGN_API_KEY` 提供第一把 Key(号池为空时投射为首要账号,不写入凭据库)。
1044
+
1045
+ **能力**:OpenAI Chat Completions 流式对话(官方给 DeepSeek Harness 推荐的正是这个协议形态),工具调用、图片输入、推理档位透传。网关是 `https://amr-link.open-design.ai/v1`,Bearer 鉴权。
1046
+
1047
+ **模型目录**:随包内置 37 个聊天模型,每个都带 id、显示名、美元单价,以及**上下文窗口、最大输出、输入模态(是否收图)与推理档位**——价目来自官方公开价目接口,能力字段转录自本仓库已核对过的同族模型表(command-code 官方注册表、cline-pass、Claude 与 Antigravity 目录;两处冲突以 cline-pass 为准)。登录成功或添加 Key 后自动同步实时目录:**浏览器授权账号(带控制 Key)还会读 AMR 控制面目录**,那里有上游真实的 `metadata.contextWindowTokens`,会覆盖随包表;网关自己的 `/v1/models` 提供该 Key 实际可见的模型清单。同步条目持久化在设置文档里,重启无需网络即可恢复。**`minimax-m3.1-flash` 与 `muse-wonder` 没有任何同族表可核对**,按 128k / 默认三档处理并在文档里列名,不猜数字;任何模型都能在卡片的「上下文窗口」里按模型修正,覆盖值优先于以上全部来源。
1048
+
1049
+ **额度(钱包余额)**:只有浏览器授权登录的账号带控制 Key,因此只有它们能读余额(`GET /api/v1/wallet/balance`,返回 `balanceUsd` 字符串);粘贴的 API Key 没有控制 Key,卡片会说明并建议去控制台查看。余额按账号显示在账号行,对话输入框右侧的胶囊显示主账号的余额;读不到时显示原因而不是 0。**本线路不画百分比额度条**——上游只给当前余额,没有可对照的总额,编一个比例就是编数据。
1050
+
1051
+ ### 插件路由(OpenDesign)
1052
+
1053
+ 所有路由都以 `/opendesign/api` 为前缀:
1054
+
1055
+ | 方法 | 路径 | 用途 |
1056
+ | --- | --- | --- |
1057
+ | GET | `/status` | 卡片状态:开关、号池、模型、目录同步、余额读数、登录流程 |
1058
+ | POST | `/settings` | 线路开关与高级设置(网关地址、默认窗口、并发、空闲超时) |
1059
+ | GET\|POST | `/accounts` | 账号增删、主账号、别名、轮换策略、Key 测试(用免费的目录读取验证,不花额度) |
1060
+ | POST | `/login/start` | 申请设备授权,应答激活地址与授权代码(不含 deviceSecret) |
1061
+ | GET | `/login/status?id=` | 轮询一次登录流程(宿主按服务给出的间隔节流) |
1062
+ | POST | `/login/cancel` | 取消登录;迟到的确认不会落号 |
1063
+ | GET\|POST | `/models` | 模型勾选(三态:不筛选 / 显式清单 / 全不选) |
1064
+ | POST | `/models/visibility`、`/models/visibility-all` | 单个/批量隐藏 |
1065
+ | POST | `/models/context-window` | 单模型上下文窗口覆盖 |
1066
+ | POST | `/catalog/refresh` | 用号池里可用的 Key 同步实时目录;目录真的变了才通知模型选择器重建 |
1067
+ | GET\|POST | `/usage` | 读取每个 OAuth 账号的钱包余额(每账号一次请求);失败作为数据显示,绝不标记账号 |
1068
+
1069
+ 协议事实与未验证项的完整证据记录见 `docs/opendesign-line-plan.md`:设备授权的创建与轮询响应是**实测**的,批准后的字段名来自上游 issue #8870;本次接入没有真实订阅账号,对话链路未与真实 Key 联调。
1070
+
1080
1071
  ## 本机登录
1081
1072
 
1082
1073
  **每条线路只在自己的详情页里回答关于本机的问题**。ChatGPT(Codex)线路的 ChatGPT 卡片里,「登录」按钮**旁边**就是「导入本机 Codex CLI 登录」——与 Claude 页面上那个收编按钮同样的位置、同样的次要样式;点它可以省掉再走一次 ChatGPT 授权。Claude Code 与 MiniMax Code 各自的设置页里本来就各有一个导入入口,不在这里重复。
@@ -1122,8 +1113,6 @@ OAuth 是默认入口;「使用 API Key」按钮保留为兼容回退,部署
1122
1113
 
1123
1114
  Antigravity 的 access token / refresh token 使用独立的系统凭据存储:Windows 使用 CurrentUser DPAPI(`$DSH_HOME/storages/antigravity-oauth.json.dpapi`),macOS 使用登录钥匙串,Linux 使用 Secret Service。macOS / Linux 的服务名为 `dsh-antigravity`,账号键按旧凭据文件的绝对路径生成,隔离不同的 `DSH_HOME`。
1124
1115
 
1125
- **WorkBuddy token 仅在 Host 内处理,从不进入浏览器**(`/workbuddy/api` 响应不含 `accessToken` / `refreshToken`,有测试锁定)。桌面扫描账号仍使用 CodeBuddy 自己的登录态文件:续期只原子写回其 `auth` 块;从本插件删除时只隐藏/恢复,绝不删除原文件。通过浏览器授权添加的账号归本插件所有,保存在独立系统凭据存储中:Windows CurrentUser DPAPI(`$DSH_HOME/storages/workbuddy-accounts.json.dpapi`)、macOS 登录钥匙串、Linux Secret Service;这类账号可在设置页真正删除。
1126
-
1127
1116
  **MiniMax Code 的 access token / refresh token 同样只在 Host 内处理,从不进入浏览器**(`/minimax-code/api` 的响应只含非机密事实,有测试锁定)。号池的**身份键也刻意不含机密**:桌面端凭据用 `recordKey`(路径摘要)、插件凭据用 `loginEpoch`,**不是**刷新令牌的哈希——用令牌做键在每次轮换后都会把同一账号看成新账号。它的凭据**不是本插件自持的**:正常路径是**只读复用** MiniMax Code 桌面端自己的 `~/.minimax/auth/<buildEnv>/<region>/mcode-public/auth.json`,续期时通过「同目录临时文件 + `rename`」原子写回,**不创建、不删除、不等待**桌面端的 `auth.lock`,也**不留任何旁路副本**——不会在桌面端的目录里写出明文 `.dsh-bak` 之类的第二份凭据。只有本机没有桌面端凭据时,本插件才把设备码登录得到的凭据存到自己的 `$DSH_HOME/storages/minimax-code-credentials.json`;**登出只删这一份**,桌面端的登录态绝不撤销、绝不删除(撤销它等于把用户从正在运行的官方客户端踢下线)。诊断中提及令牌时只输出 SHA-256 指纹前缀,不含令牌原文。
1128
1117
 
1129
1118
  **Claude 订阅的 access token / refresh token 同样只在 Host 内处理,从不进入浏览器**(`/claude/api` 的响应只含非机密事实,有测试锁定)。它保存在独立系统凭据存储中:Windows CurrentUser DPAPI(`$DSH_HOME/storages/claude-credentials.json.dpapi`)、macOS 登录钥匙串、Linux Secret Service;号池另用一份同样加密的文件 `$DSH_HOME/storages/claude-pool.json`。凭据文档是**多账号**结构,账号身份由不可变的 `internalId` 与只增不换的别名集共同表达,**任何路由键都不是令牌或其摘要**(令牌会轮换,用它做键会产生幽灵账号)。每次写入都**先读回校验再落盘**,校验失败会抛错且不破坏既有数据。
@@ -1190,9 +1179,9 @@ npm pack --dry-run
1190
1179
 
1191
1180
  ### 压缩失败
1192
1181
 
1193
- Kimi Code、MiniMax Code、Command Code、Claude、WorkBuddy 遇到“输入加输出预留超限”时,插件可在 HTTP 400/422 的完整 token 计数证明输入仍能放下、且上游确实采用了请求输出上限的情况下,仅降低输出上限重试一次。消息、工具和历史保持不变,摘要请求也适用。降低后的输出仍可能截断,截断不会冒充完整摘要。Codex 不支持该输出参数,不走此恢复;输入本身超限、模糊错误或流内错误仍交给 Harness 处理。这不是对所有压缩故障的通用修复。
1182
+ Kimi Code、MiniMax Code、Command Code、Claude 遇到“输入加输出预留超限”时,插件可在 HTTP 400/422 的完整 token 计数证明输入仍能放下、且上游确实采用了请求输出上限的情况下,仅降低输出上限重试一次。消息、工具和历史保持不变,摘要请求也适用。降低后的输出仍可能截断,截断不会冒充完整摘要。Codex 不支持该输出参数,不走此恢复;输入本身超限、模糊错误或流内错误仍交给 Harness 处理。这不是对所有压缩故障的通用修复。
1194
1183
 
1195
- MiniMax、Kimi、Codex、Claude、Command Code、WorkBuddy 和 Antigravity 的错误处理会将明确的上下文超限错误交给 Harness 的溢出恢复机制。该机制需要宿主启用压缩后端;它不是无条件重发同一个超限请求。认证、配额、请求体字节限制和输出截断不会因此被当作上下文超限。手动压缩仍需生成完整摘要;如果摘要请求本身超限、被截断或缺少正文,修正错误分类也不能保证它成功。请保留失败会话中的 `compaction/end` 错误、provider/model、宿主及插件版本,以便定位。
1184
+ MiniMax、Kimi、Codex、Claude、Command Code 和 Antigravity 的错误处理会将明确的上下文超限错误交给 Harness 的溢出恢复机制。该机制需要宿主启用压缩后端;它不是无条件重发同一个超限请求。认证、配额和输出截断不会因此被当作上下文超限。**请求体字节限制是唯一按线路区分的**:Kimi Code 把「请求体超过 2 MB」的本地拒绝与网关原文 `total message size N exceeds limit 2097152` 都归入该码(压缩确实能缩小请求体,实测这是长会话最常见的 400),其余线路的请求体拒绝仍按普通错误呈现。手动压缩仍需生成完整摘要;如果摘要请求本身超限、被截断或缺少正文,修正错误分类也不能保证它成功。请保留失败会话中的 `compaction/end` 错误、provider/model、宿主及插件版本,以便定位。
1196
1185
 
1197
1186
  | 现象 | 处理 |
1198
1187
  | --- | --- |