@eddyskywalker/dsh-chatgpt-subscription 0.10.12 → 0.10.13

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 (52) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +176 -42
  3. package/lib/client.js +133 -26
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +710 -280
  6. package/lib/types/client/claude/ClaudeSection.d.ts.map +1 -1
  7. package/lib/types/client/claude/locales.d.ts +17 -2
  8. package/lib/types/client/claude/locales.d.ts.map +1 -1
  9. package/lib/types/client/kimi-code/KimiCodeSection.d.ts.map +1 -1
  10. package/lib/types/client/kimi-code/locales.d.ts +17 -2
  11. package/lib/types/client/kimi-code/locales.d.ts.map +1 -1
  12. package/lib/types/compat.d.ts +35 -2
  13. package/lib/types/compat.d.ts.map +1 -1
  14. package/lib/types/host/adapter.d.ts +20 -1
  15. package/lib/types/host/adapter.d.ts.map +1 -1
  16. package/lib/types/host/claude/adapter.d.ts +11 -0
  17. package/lib/types/host/claude/adapter.d.ts.map +1 -1
  18. package/lib/types/host/claude/client.d.ts +7 -2
  19. package/lib/types/host/claude/client.d.ts.map +1 -1
  20. package/lib/types/host/claude/mapper.d.ts +13 -1
  21. package/lib/types/host/claude/mapper.d.ts.map +1 -1
  22. package/lib/types/host/claude/routes.d.ts.map +1 -1
  23. package/lib/types/host/claude/token-store.d.ts +9 -0
  24. package/lib/types/host/claude/token-store.d.ts.map +1 -1
  25. package/lib/types/host/claude/types.d.ts +35 -0
  26. package/lib/types/host/claude/types.d.ts.map +1 -1
  27. package/lib/types/host/codex-catalog.d.ts +54 -0
  28. package/lib/types/host/codex-catalog.d.ts.map +1 -0
  29. package/lib/types/host/codex-images.d.ts.map +1 -1
  30. package/lib/types/host/codex-search.d.ts.map +1 -1
  31. package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
  32. package/lib/types/host/kimi-code/mapper.d.ts +13 -7
  33. package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
  34. package/lib/types/host/kimi-code/routes.d.ts.map +1 -1
  35. package/lib/types/host/kimi-code/token-store.d.ts +11 -1
  36. package/lib/types/host/kimi-code/token-store.d.ts.map +1 -1
  37. package/lib/types/host/kimi-code/types.d.ts +13 -0
  38. package/lib/types/host/kimi-code/types.d.ts.map +1 -1
  39. package/lib/types/host/model-catalog.d.ts +3 -2
  40. package/lib/types/host/model-catalog.d.ts.map +1 -1
  41. package/lib/types/host/responses-client.d.ts +24 -0
  42. package/lib/types/host/responses-client.d.ts.map +1 -1
  43. package/lib/types/host/responses-mapper.d.ts +5 -1
  44. package/lib/types/host/responses-mapper.d.ts.map +1 -1
  45. package/lib/types/host/wire-auth.d.ts +14 -1
  46. package/lib/types/host/wire-auth.d.ts.map +1 -1
  47. package/lib/types/index.d.ts.map +1 -1
  48. package/lib/types/shared/claude-contracts.d.ts +19 -0
  49. package/lib/types/shared/claude-contracts.d.ts.map +1 -1
  50. package/lib/types/shared/kimi-code-contracts.d.ts +16 -0
  51. package/lib/types/shared/kimi-code-contracts.d.ts.map +1 -1
  52. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - **Claude 与 Kimi 线路补上提示缓存时长(TTL)选择**,两者都可在设置页选择,默认行为各自对齐官方。
6
+ - **Claude(`claude-subscription`)**:本线路用订阅凭据,而 [Claude Code 官方文档](https://code.claude.com/docs/en/prompt-caching) 写明「订阅用户在套餐额度内,主对话使用 **1 小时** TTL;超出额度改按用量计费后降回 5 分钟」。此前本插件四�� `cache_control` 全部是裸的 `{ type: 'ephemeral' }`(即默认 5 分钟),**与官方客户端行为不一致**——同样的用量,官方用户享受 4 倍缓存窗口而本插件没有。现默认按订阅档位发 `ttl: '1h'`,并在**同一处**同步发出授权它的 beta `extended-cache-ttl-2025-04-11`(1 小时是**需要许可的能力**,body 里要了 `1h` 却没有该标记会被拒,与 `block_binding` 同理)。设置页可选「跟随官方 / 1 小时 / 5 分钟」;1 小时写入价格更高,短会话不划算,故保留覆盖项。
7
+ - **Kimi(`kimi-code`)**:[Kimi 官方缓存文档](https://www.kimi.com/academy/best-practices-for-context-caching) 明确了 `prompt_cache_options`(OpenAI 兼容线路)与**顶层** `cache_control`(Anthropic 兼容线路)两套写法,且命中价为未命中的 1/10。此前本插件两种都不发——注意这与本插件**自己实测**的结论并不矛盾:那份实测针对的是「缓存身份由内容前缀哈希决定、标记无法干预」,而 TTL 控制的是**写入时长**,两者是不同的机制。**未设置时不发任何缓存字段**,请求与该设置存在之前逐字节一致。
8
+ - 两条线路写法**不可互换**:`cache_control` 只在请求顶层生效(消息体内的同名标记会被忽略),因此这里发的是顶层字段,不是给 system/消息块加标记。
9
+ - **档位在首次写入时锁定**,之后无法改写、命中时按原档免费续期;已有条目完全过期后才能用新档重写。1 小时写入约为 5 分钟的两倍价,只有当同一前缀会在一小时内被反复读取才划算——设置项的提示文案写明了这一点。
10
+ - **测试**:新增 `test/cache-ttl.test.ts` 11 条——Claude 默认写 5 分钟标记、写 1 小时标记、1 小时必带授权 beta、5 分钟不带该 beta、拒绝非法档位、关闭缓存时不写任何标记;Kimi 未设置时两种协议都不发、OpenAI 线路用 `prompt_cache_options`、Anthropic 线路用顶层 `cache_control`、拒绝非法档位。
11
+ - **顺带修掉一个真实的不一致**:`buildClaudeSystemBlocks` 是导出函数且自己写标记,此前固定写裸 `{ type: 'ephemeral' }`,会绕过 TTL(请求构建器事后虽会覆盖,但直接调用该函数的调用方拿到的永远是 5 分钟)。现两处写入用同一个 `cacheControlFor`,并更新了 `claude-mapper` / `claude-adapter` / `claude-routes` / `claude-token-store` 中断言旧标记形态的用例。
12
+ - **验证**:强制类型检查(`tsc -b --force`)与 test tsconfig 均 0 错误,`npm run build`、`npm pack --dry-run`、`npm ci --dry-run` 通过,全量 **1847 passed** / 7 skipped;失败的 7 条仍是先前已确认与本插件无关的 `claude-model-catalog`(5)、`antigravity-callback-port`(1) 与一条既有用例。
13
+ - **未在真实订阅账号上端到端验证**。Kimi 订阅端是否接受 `prompt_cache_options` / 顶层 `cache_control`(开放平台文档有,订阅端未实测)与 Claude 1 小时写入的实际命中收益,都需要在真机上确认;第一检查点是发一轮带 1 小时的请求后看 `usage.cache_creation.ephemeral_1h_input_tokens` 是否非零。
14
+ - **修复 Codex 线路声明了输出上限却从不发送**。`shared/model-catalog.ts` 为 GPT-6 家族声明了 128K 输出上限,`resolveCodexModel` 也把它作为 `defaultMaxTokens` 报给 DSH,但 `responses-mapper.ts` 构建 payload 时**完全没有 `max_output_tokens`** 字段——目录里没有的模型则回落到 32K 预 GPT-6 默认。
15
+ - **为什么重要**:不带这个字段时由服务端套用自己的默认值,本线路既无法预测也无法上报——某一轮撞上上限看起来就像一次普通的短回答。这与 Claude 线把 `model_context_window_exceeded` 当成正常结束是同一类问题。Codex 走 `store:false` 且每轮全量重发,这一项尤其容易被反复触发。
16
+ - **修法**:始终发送 `options.maxTokens ?? codexModelMaxTokens(model)`,并在调用方请求更大时用 `Math.min` 封到模型自身上限(与 antigravity mapper 同一道守卫)——超出模型能力的请求会被后端直接拒绝,因此必须向下封而不是原样透传。
17
+ - **测试**:`test/codex-output-cap.test.ts` 6 条——「始终发送模型上限」(回归锁定)、「调用方要求更小时照发」、「超出上限必须封顶」、「只存在于实时目录的未知模型也带上限」。
18
+ - **横向审计结论(同一轮核对七条线路,结论是除本条外无其他缺口)**:Codex 是**唯一**支持 `service_tier: 'priority'`(快速模式)、`text.verbosity`、`reasoning.summary` 与 `include: reasoning.encrypted_content`(加密思考回放)的线路——这四项是 Responses 协议独有,其他线路走 Anthropic / chat-completions 协议,没有对应字段,**不应**在其他线补。缓存方面 Claude 与 minimax 用的 `cache_control` 断点是 Anthropic 协议要求(不主动声明即按全价计费),OpenAI 系则是自动前缀缓存、只需 `prompt_cache_key`,两条线现已各自满足。服务端特殊能力(图片生成 `gpt-image-2`、`/alpha/search` 网页搜索、配额重置额度)同样只有 Codex 线具备,是这条线的护城河。minimax 的静态目录**不是**遗漏:其 `model-catalog.ts` 注释记录了 `/v1/models` 对订阅流量返回 `503 direct_route_not_configured` 并明令禁止探测。
19
+ - **验证**:强制类型检查(`tsc -b --force`)与 test tsconfig 均 0 错误,`npm run build` 通过,全量 **1837 passed** / 7 skipped;失败的 6 条仍是先前已确认与本插件改动无关的 `claude-model-catalog`(5) 与 `antigravity-callback-port`(1)。
20
+ - **Codex 订阅线路跟上上游的第三方接入方式**(OpenAI Codex 负责人 Romain Huet:「我们希望人们能在任何地方使用 Codex 和他们的 ChatGPT 订阅」——涵盖 OpenCode、Pi、Claude Code;Codex CLI / app server 已开源)。本插件的 OAuth 参数此前就与社区逆向结论逐字一致(client_id、scope、redirect_uri、`id_token_add_organizations`、`codex_cli_simplified_flow`),所以基础无需改动;下面四项是实际缺口。
21
+ - **补上 `openai-beta: responses=experimental`**(`wire-auth.ts`)。该订阅后端是在 beta 标志下提供的,官方 CLI 一直发送这个头;逆向文档把它列为必填。缺少它的请求不是同一个面——现在能跑通只是后端当前宽容,这正是后端某次收紧时会突然 400/403 的那一行。
22
+ - **把 `originator` 收敛成一个值**(`compat.ts`)。此前散落三个:对话与 OAuth 用 `opencode`,图像(`codex-images.ts`)与搜索(`codex-search.ts`)覆盖成 `pi`——那是各端点逆向时间点的考古层,不是一次决定。后端按这个值区分客户端身份(官方 CLI 发 `codex_cli_rs`),一个账号因此有三种互不相关的失败签名。现统一为 `CODEX_ORIGINATOR`,并让 `OAUTH_ORIGINATOR` 直接引用它,登录与请求不可能再各说各话。两种取值对后端都已知可用;保留 `opencode` 是因为它是 OAuth 流程一直使用的值,改登录 originator 与改请求 originator 是两种不同的风险。
23
+ - **接入实时模型目录 `GET /backend-api/codex/models`**(新增 `codex-catalog.ts`)。这是本轮最有价值的一项:订阅线路的核心优势正是模型面比 API key 更新,而本插件此前把 `gpt-6-astra` 的 384K 起始窗口、128K 输出上限等**全部硬编码**——新模型发布就得改代码发版。目录加载器按账号缓存(15 分钟 TTL)、单飞,并用既有的 `catalog-snapshot` 持久化,重启后第一次请求直接从磁盘水化、不等网络;快照按账号 id 分域,避免 A 账号的列表回答 B 账号的选择器。**随仓库里其他线路(kimi / command / minimax / workbuddy)已有的动态目录范式实现**,也是这些线路里最后一个还在静态的。
24
+ - **失败时宁可放宽也不清空**:目录取不到就回落到随包发布的表(选择器因此变宽,而不是消失);只持久化成功取到的列表,失败不会用回落表覆盖已有快照。
25
+ - **未知模型照常服务**:列表是「这个账号能调什么」的权威,所以本表没听过的 id 也会出现,但**不会**继承臆造的能力——未声明的模态回落为纯文本,未声明的档位回落为该族的 profile。
26
+ - **档位经过共享词表过滤**:列表给出的档位若超出本线路能表达的词汇,会被剔除,且默认档位取自存活下来的那些,而不是被剔除的那个。
27
+ - **多轮续传:发送 `prompt_cache_key` 并回传 `x-codex-turn-state`**(`responses-mapper.ts`、`responses-client.ts`)。此前每一轮都全量重发整个历史。缓存键按会话稳定(会话 id 本身已是哈希,直接复用),让后端复用提示前缀;turn state 在响应头读取、按会话保存、下一轮原样回送,让后端续接该轮而非重新摄入历史。两者都只是优化:turn state 是后端不再下发时**立即停止回送**(不猜测重放过期值),缓存键缺失也只是少一次缓存。存储有 200 个会话的上限并淘汰最旧项——宿主进程长期存活而没有任何「会话结束」信号,无界增长是真实的。
28
+ - **测试**:`test/codex-wire.test.ts`(11 条,beta 头 / 单一定 originator / 登录与请求同源 / turn state 的有无 / 列表解析的各种形态与保守回落)、`test/codex-catalog-adapter.test.ts`(6 条,实时目录生效、未知模型、档位过滤、失败不破选择器、signal 透传)、`test/responses-client.test.ts` 新增 2 条(缓存键跨轮稳定且 turn state 首轮不发次轮回送;后端停发后不再回送)。
29
+ - **验证**:强制类型检查(`tsc -b --force`)与 test tsconfig 均 0 错误,`npm run build` 通过,全量 **1831 passed** / 7 skipped;失败的 6 条(`claude-model-catalog` 5、`antigravity-callback-port` 1)在**本轮改动前的干净工作树上同样失败**(`git stash` 验证),与本次无关。
5
30
  - **修复所有订阅线路共享号池的读-改-写竞争**:原来只分别串行化读和写,令牌刷新或设置更新会把整份旧快照写回,覆盖其他请求的新令牌、账号与冷却状态——共享内核 `account-pool.ts` 的注释曾声称不会发生。现在账号增删、备注、主账号、轮换策略、冷却和认证标记都在同一锁内读取最新文档并修改;指向同一文件的多个号池实例共享锁与身份版本号。
6
31
  - **刷新不占用整池锁**:模型请求、工具凭据、设置卡片与 Codex 强制续期共用按账号/凭据代际的单飞。读取当前凭据与登记刷新任务是一个短事务,网络请求在锁外发出,锁内不等待任何 flight(否则它自己的提交会死锁)。
7
32
  - **条件写与过期保护**:轮换结果和外部权威凭据都按“发起时的凭据”写回,不匹配就返回最新行而不覆盖——晚到的刷新不会盖掉重新登录的新令牌,也不会复活已删除的账号;旧刷新抛出的终态失败也不会把刚登录的账号标记失效。刷新期间账号被删除时,模型请求会切到其他可用账号。
package/README.md CHANGED
@@ -6,35 +6,66 @@
6
6
 
7
7
  ## 目录
8
8
 
9
+ - [线路总览](#线路总览)
9
10
  - [功能特性](#功能特性)
10
11
  - [模型目录](#模型目录)
11
12
  - [环境要求](#环境要求)
12
13
  - [安装](#安装)
13
14
  - [使用](#使用)
14
- - [Kimi Code 线路](#kimi-code-线路)
15
15
  - [Command Code 线路](#command-code-线路)
16
16
  - [WorkBuddy 线路](#workbuddy-线路)
17
17
  - [Claude(订阅)线路](#claude订阅线路)
18
- - [子代理模型授权](#子代理模型授权0215-起)
18
+ - [子代理模型授权](#子代理模型授权0215-起032-起强制指定模型)
19
19
  - [随包分发的 Agent Preset](#随包分发的-agent-preset)
20
- - [升级、降级与卸载](#升级降级与卸载)
21
20
  - [安全边界](#安全边界)
22
21
  - [插件路由](#插件路由)
23
22
  - [开发与验证](#开发与验证)
24
23
  - [故障排查](#故障排查)
25
24
 
25
+ ## 线路总览
26
+
27
+ 本插件同时接入 **7 条订阅线路**,每条各自独立注册 Provider、独立登录、独立额度卡片。
28
+
29
+ | Provider ID | 订阅 | 协议 | 登录方式 | 模型目录来源 |
30
+ | --- | --- | --- | --- | --- |
31
+ | `codex-chatgpt` | ChatGPT(Plus / Pro / Business…) | Responses | 浏览器 OAuth(localhost:1455 回调) | **实时** `/backend-api/codex/models` |
32
+ | `kimi-code` | Kimi 会员 | Anthropic Messages | **设备码**(RFC 8628) | 实时 `/v1/models` + 本地兜底 |
33
+ | `command-code` | Command Code | Anthropic / OpenAI 双轨 | 浏览器 OAuth 或粘贴 API Key | 实时 `/provider/v1/models` |
34
+ | `workbuddy-subscription` | 腾讯 WorkBuddy / CodeBuddy | OpenAI 兼容(仅流式) | 扫描桌面端凭据或官方浏览器授权 | 实时 `/v3/config` |
35
+ | `claude-subscription` | Claude Pro / Max | Anthropic Messages | 手动粘贴 / loopback 回调 | 实时 `/v1/models` + 本地兜底 |
36
+ | `minimax-code` | MiniMax Code 编程订阅 | Anthropic Messages | **复用桌面端登录态** 或设备码 | **硬编码**(见下) |
37
+ | `antigravity` | Google Antigravity | Gemini | Google OAuth | 内置表 |
38
+
39
+ > **只有 minimax-code 是硬编码目录,而且是有意的**:该端点的 `GET /v1/models` **未对订阅流量开放**(返回 503 `direct_route_not_configured`),任何「实时目录」都只会是一个必然失败的请求。其取值转录自本机官方客户端的 `config.yaml`。
40
+ >
41
+ > **线路之间互不影响**:某条线路的登录失败、额度耗尽或上游故障都不会波及其它线路;它们共用同一套号池内核与账号卡片,但不是同一个池。
42
+ >
43
+ > ⚠️ **合规提示**:`claude-subscription` 使用的订阅凭据转发方式与 Anthropic 现行条款存在冲突(详见该章节开头的「风险须知」),且插件未获官方授权;`antigravity` 线路的用户亦有账号被限制的公开报告。请自行评估风险。
26
44
  ## 功能特性
27
45
 
28
- **登录与会话**
46
+ ### 登录与会话
29
47
 
30
48
  - Authorization Code + PKCE(S256)登录,一次性 localhost 回调;
31
49
  - 支持 token 刷新、登录取消与账号注销;
32
50
  - Windows 使用 CurrentUser DPAPI 加密存储 token;Linux 使用当前用户独占的 `0600` 文件存储;明文不发送给 Client;
33
51
  - 设置页会明确显示当前存储类型,并在 Linux 上提示文件存储未额外加密。
34
52
 
35
- **模型接入**
53
+ ### 模型接入
36
54
 
37
55
  - 固定 Codex Responses 地址,支持流式文本、reasoning summary、图片输入与工具调用/结果;
56
+ - **模型目录来自订阅后端**(`GET /backend-api/codex/models`):
57
+ - 按账号缓存并持久化到本地快照,重启后无需等待网络即可渲染选择器;
58
+ - 随包发布的模型表是**兜底**而非上限:取不到目录时选择器变宽,而不是消失;
59
+ - 目录里本表没听过的模型照常出现,但未声明的能力按保守值回落,不会凭空多出图片或思考档位;
60
+ - 只有目录**确实列出**的模型会进入选择器——它是「这个账号能调什么」的权威;
61
+ - **请求带 `openai-beta: responses=experimental`**:
62
+ - 该订阅后端在 beta 标志下提供,官方 Codex CLI 一直发送这个头;
63
+ - 缺它时请求面不同:当前后端宽容,但这正是收紧后会变成 400/403 的那一行;
64
+ - **多轮续传**,避免每轮重发整个历史:
65
+ - 每个会话发送稳定的 `prompt_cache_key`,让后端复用提示前缀;
66
+ - 回传后端在响应头给出的 `x-codex-turn-state`,让它续接该轮;
67
+ - 后端不再下发该头时立即停止回送——不重放过期值;
68
+ - **始终发送 `max_output_tokens`**,按 `min(调用方请求, 模型上限)` 封顶(详见「模型目录」);
38
69
  - Antigravity(Gemini / Claude)线路同样接受图片输入:DSH 以 `{ type: 'image', attachment }` 下发的粘贴图片会经附件服务读出字节并按 Gemini `inlineData` 发出,读不出的图片降级为一条可见的说明文本而不是被静默丢弃。单次请求的图片 base64 负载超过 12 MiB 时,最旧的图片按上游同款占位文案替换为文本,避免整条请求被体积上限拒绝;
39
70
  - 原样转发 DSH 暴露的工具 schema;命令工具兼容 `pwsh` / `powershell`、`bash`、`sh` 与 `shell`,并按 PowerShell、Bash 或 POSIX sh 注入对应说明;
40
71
  - 429/5xx 由 DSH retry policy 接管;401 只强制刷新并重试一次,支持 `AbortSignal`;
@@ -44,95 +75,190 @@
44
75
  - 可选 composer 快捷用量徽标,按当前 `codex-chatgpt` 模型显示最紧张窗口的剩余额度。
45
76
 
46
77
 
47
- **Command Code 线路**
78
+ ### Command Code 线路
48
79
 
49
80
  - 注册 `command-code` Provider,使用 Command Code 的 Provider API 与账户 API;模型 id 决定线路:`claude-*` 走 Anthropic Messages(`/provider/v1/messages`),其余模型走 OpenAI Chat Completions(`/provider/v1/chat/completions`),因为该 API 会拒绝把模型发到格式不符的端点;
50
81
  - 浏览器登录复刻官方 CLI 的回环回调契约(`127.0.0.1:5959` 起顺延,`/callback` 接受 Studio 页面的跨域 POST),也可在设置页手动粘贴 API Key;两条路径都先用 `/alpha/whoami` 验证再加密保存;
51
82
  - 模型目录取自公开的 `/provider/v1/models`,每个模型的 `context_length` 作为默认上下文窗口,可逐模型覆盖;
52
- - **模型能力逐模型查表**(`src/host/command-code/model-catalog.ts`,转录自官方 CLI 的模型注册表):是否接受图片输入、支持哪些思考档位由该表决定,未知模型回落纯文本。图片能力不能靠厂商/模型名前缀推断——`deepseek/deepseek-v4.1-flash` 与 `deepseek/deepseek-v4-flash-vision-exp` 支持图片而 `deepseek/deepseek-v4-flash`、`deepseek/deepseek-v4-pro` 不支持,`z-ai/glm-5.3-flash` 支持而 `zai-org/GLM-5.3` 不支持;
83
+ - **模型能力逐模型查表**(`src/host/command-code/model-catalog.ts`,转录自官方 CLI 的模型注册表):是否接受图片输入、支持哪些思考档位由该表决定,未知模型回落纯文本;
84
+ - 图片能力**不能靠厂商/模型名前缀推断**——同厂商内部就会自相矛盾:`deepseek/deepseek-v4.1-flash` 与 `deepseek/deepseek-v4-flash-vision-exp` 支持图片,而 `deepseek/deepseek-v4-flash`、`deepseek/deepseek-v4-pro` 不支持;`z-ai/glm-5.3-flash` 支持而 `zai-org/GLM-5.3` 不支持;
53
85
  - 额度与用量来自账户 API 的账单/用量线路,任一条失败不影响其余;
54
- - **瞬时失败按 DSH retry policy 有界重试**:`command-code` 路由显式声明 `normal` 策略(最多 3 次,1.5s 起指数退避、15s 上限、0.2 抖动),覆盖 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`。上游模型供应商临时不可用(502/503/504/500,典型响应体是 `{"error":{"type":"server_error"}}`)被归类为 `SERVER` 并自动重试,429 会带上上游的 `Retry-After` 让退避按对方的节奏走;401/403 与 `ABORTED` 明确不重试;
86
+ - **瞬时失败按 DSH retry policy 有界重试**:`command-code` 路由显式声明 `normal` 策略(最多 3 次,1.5s 起指数退避、15s 上限、0.2 抖动),覆盖 `RATE_LIMIT`、`SERVER`、`TIMEOUT`、`TRANSPORT`;
87
+ - 上游模型供应商临时不可用(502/503/504/500,典型响应体是 `"{\"error\":{\"type\":\"server_error\"}}"`)被归类为 `SERVER` 并自动重试;
88
+ - 429 会带上上游的 `Retry-After` 让退避按对方的节奏走;401/403 与 `ABORTED` 明确不重试;
55
89
  - 模型勾选(含线路标签)、思考深度、上下文窗口覆盖与额度在「设置 → 订阅服务 → Command Code」标签页中配置,输入框右侧另有额度胶囊。
56
90
 
57
- **Kimi Code 线路**
91
+ ### Kimi Code 线路
58
92
 
59
93
  - 注册 `kimi-code` Provider,接入 Moonshot 的 **Kimi Code 订阅**(`https://www.kimi.com/code`)。它与 Moonshot 开放平台(pay-as-you-go)是两套互不通用的系统:订阅的模型接口是 `https://api.kimi.com/coding/v1`,凭据只来自订阅 OAuth;把开放平台的 key 或 base URL 用在这里会被判为 `401 Invalid Authentication`;
60
94
  - 登录用 **RFC 8628 设备码流程**(`auth.kimi.com`):设置页点「设备码登录」后直接展示用户码与一次性链接(浏览器会自动打开),装好后无需回调端口、无浏览器环境也能手工完成;`slow_down` 会按 RFC 调宽轮询间隔,设备码过期会自动重新申请而不是直接失败;
61
95
  - 访问令牌到期前按 `max(300s, expires_in×0.5)` 自动续期,同进程并发调用共用一次刷新;被拒的 refresh token 进入冷却并提示重新登录;
62
- - **瞬时失败按错误类别重试**(`src/host/kimi-code/adapter.ts`)。上游模型供应商临时不可用(典型是 502 `{"error":{"message":"Upstream model provider is temporarily unavailable. Please try again in a moment.","type":"server_error"}}`)、真正的 429 背压(`too many requests` / `engine is currently overloaded`)、连接失败与流停滞都会走有界退避(最多 3 次,1.5s 起步、15s 上限、0.2 抖动),并遵守上游的 `Retry-After`;而**配额耗尽型的 429、403 的账号额度上限、401 里的套餐权限不足、400 请求格式错误都不重试**——服务把这些含义压进同一个状态码,因此分类读响应正文而不只看状态码,重试无望时直接给出可操作的提示(换模型 / 降上下文 / 等窗口重置 / 重新登录);
96
+ - **瞬时失败按错误类别重试**(`src/host/kimi-code/adapter.ts`)。**会重试**(有界退避,最多 3 次,1.5s 起步、15s 上限、0.2 抖动,并遵守上游 `Retry-After`):
97
+ - 上游模型供应商临时不可用(典型是 502 `"{\"error\":{\"message\":\"Upstream model provider is temporarily unavailable. Please try again in a moment.\",\"type\":\"server_error\"}}"`);
98
+ - 真正的 429 背压(`too many requests` / `engine is currently overloaded`)、连接失败与流停滞;
99
+ - **不重试**,直接给出可操作提示(换模型 / 降上下文 / 等窗口重置 / 重新登录):配额耗尽型的 429、403 的账号额度上限、401 里的套餐权限不足、400 请求格式错误;
100
+ - 服务把这些含义压进**同一个状态码**,因此分类读响应正文而不只看状态码;
63
101
  - 模型目录为订阅侧的四款模型:`k3`(1M 上下文,需 Allegretto+;Moderato 上限 256K,故默认按 256K 计算,可用上下文覆盖升到 1M)、`k3-256k`、`kimi-for-coding`(K2.8 Preview)、`kimi-for-coding-highspeed`(约 6× 速度、3× 额度),运行时以 `GET /v1/models` 为准;
64
- - **K3 行为按其官方文档实现**:思考档位只发 \`low\` / \`high\` / \`max\`(其余写法收敛映射,未知档位不发送),关闭思考发 \`thinking:{type:"disabled"}\`,开启时发 \`thinking:{type,effort,keep:"all"}\`;**开启思考时每条 assistant 消息都回传 \`reasoning_content\`**(无推理则回传空串——服务要求的是空值而非省略,否则 400);不发送 \`temperature\`(采样参数按模型固定,显式值会报错);工具调用 id 截断到 64 字符;\`stop\` 按上限裁剪为最多 5 条、每条 ≤32 字节,超长整条丢弃(截断的停止串会在错误位置终止生成);
65
- - **K3 的长思考不会被截断**:输出上限跟随上下文窗口(保留 4096 余量),因为 \`reasoning_content\` 计入输出,固定 32K 会把 \`max\` 档的长推理中途截断并返回 \`length\`;调用方已知 prompt 规模时上限会被下调到放得下,未知时不做猜测;
66
- - **请求体超 2 MB 本地即拒绝**:该端点最常见的 400 是 \`total message size N exceeds limit 2097152\`,官方文案不给建议,这里直接按真实序列化体积拦截并提示压缩会话或检查大工具结果;
67
- - **缓存是自动的,且无法手动干预**:Kimi 按请求内容哈希命中前缀缓存,实测 \`prompt_cache_key\` 与 Anthropic \`cache_control\` 标记**均被忽略**(设与不设、同 key 与异 key 命中的是同一缓存),设备 id 与协议切换也不影响;TTL 实测在 300–1800 秒之间,按 256 token 对齐,\`/messages\` 与 \`/chat/completions\` **共享同一缓存**。真正决定命中率的是**内容稳定性**:同一会话内 \`system\` 或工具列表一旦变化会使整个前缀缓存失效(实测归零),因此应保持工具集合稳定、把新增内容追加在末尾。卡片会显示滚动命中率,方便验证效果;
68
- - **视频输入可用**(`k3`、`kimi-for-coding`):DSH 的模态词表只有 `text`/`image`,但它是可合并扩展的接口,本插件用 TypeScript 模块增强把它扩到 `video`(**未改动 DSH 任何代码**),因此视频走 DSH 真实的能力通道,而不是只能显示在提示里。适配器把视频映射为服务文档的 `{type:'video_url',video_url:{url:'data:…'}}`。视频与图片**各有独立预算**(图片 2 MB、视频 48 MiB base64,最旧优先省略),请求体校验只在确实带视频时才放宽到 64 MiB。`k3-256k` 只接受图片、文档白名单外的容器、以及未文档化视频内容块的 **Anthropic 线路**,都会把视频降级为明确的文字说明而不是猜字段发出去;
102
+ - **K3 行为按其官方文档实现**:
103
+ - 思考档位只发 `low` / `high` / `max`(其余写法收敛映射,未知档位不发送);关闭思考发 `thinking:{type:"disabled"}`,开启时发 `thinking:{type,effort,keep:"all"}`;
104
+ - **开启思考时每条 assistant 消息都回传 `reasoning_content`**(无推理则回传空串——服务要求的是空值而非省略,否则 400);
105
+ - 不发送 `temperature`(采样参数按模型固定,显式值会报错);
106
+ - 工具调用 id 截断到 64 字符;
107
+ - `stop` 按上限裁剪为最多 5 条、每条 ≤32 字节,超长整条丢弃(截断的停止串会在错误位置终止生成);
108
+ - **K3 的长思考不会被截断**:输出上限跟随上下文窗口(保留 4096 余量),因为 `reasoning_content` 计入输出,固定 32K 会把 `max` 档的长推理中途截断并返回 `length`;调用方已知 prompt 规模时上限会被下调到放得下,未知时不做猜测;
109
+ - **请求体超 2 MB 本地即拒绝**:该端点最常见的 400 是 `total message size N exceeds limit 2097152`,官方文案不给建议,这里直接按真实序列化体积拦截并提示压缩会话或检查大工具结果;
110
+ - **缓存是自动的,且无法手动干预**:
111
+ - Kimi 按请求内容哈希命中前缀缓存。实测 `prompt_cache_key` 与 Anthropic `cache_control` 标记**均被忽略**(设与不设、同 key 与异 key 命中的是同一缓存),设备 id 与协议切换也不影响;
112
+ - TTL 实测在 300–1800 秒之间,按 256 token 对齐;`/messages` 与 `/chat/completions` **共享同一缓存**;
113
+ - 真正决定命中率的是**内容稳定性**:同一会话内 `system` 或工具列表一旦变化会使整个前缀缓存失效(实测归零)。因此应保持工具集合稳定、把新增内容追加在末尾;
114
+ - 卡片会显示滚动命中率,方便验证效果;
115
+ - **写入时长可手动选择**(设置页):`prompt_cache_options`(OpenAI 兼容线路)或**顶层** `cache_control`(Anthropic 兼容线路)控制缓存**写入时长**,与上面的「无法干预」不矛盾——那一条说的是**缓存身份**(由内容前缀哈希决定),TTL 管的是另一回事;
116
+ - 档位在**首次写入时锁定**,之后无法改写、命中时按原档免费续期;1 小时写入约为 5 分钟的两倍价,只有同一前缀会在一小时内被反复读取才划算。不选择时不发任何缓存字段;
117
+ - **视频输入可用**(`k3`、`kimi-for-coding`):
118
+ - DSH 的模态词表只有 `text`/`image`,但它是可合并扩展的接口,本插件用 TypeScript 模块增强把它扩到 `video`(**未改动 DSH 任何代码**),因此视频走 DSH 真实的能力通道,而不是只能显示在提示里;
119
+ - 适配器把视频映射为服务文档的 `{type:'video_url',video_url:{url:'data:…'}}`。视频与图片**各有独立预算**(图片 2 MB、视频 48 MiB base64,最旧优先省略),请求体校验只在确实带视频时才放宽到 64 MiB;
120
+ - 以下情况会把视频降级为明确的文字说明而不是猜字段发出去:`k3-256k` 只接受图片、文档白名单外的容器、以及未文档化视频内容块的 **Anthropic 线路**;
69
121
  - **`dynamically_loaded_tools`(仅 K3)已实现**:K3 接受**消息级工具声明**(`messages[].tools`),可在会话中途用「无 `content` 字段的 system 消息」注入完整工具定义。官方把「保持顶层 `tools` 字节稳定」列为该特性的目的之一——中途修改/删除已发出的声明会使缓存从该点起失效,而在末尾追加不影响已缓存前缀,所以这是提升缓存命中的正道。声明按请求重发(服务端不保留),且仅在模型声明该能力时发送;
70
122
  - 额度卡片区分 **5 小时 / 7 天 / 月度(会员共享池)/ 月度(Kimi Code 池)** 四个窗口并显示重置时间,另可显示加油包余额;设置页为「设置 → 订阅服务 → Kimi Code」标签页,对话输入框右侧有该线路的额度胶囊。
71
123
 
72
- **WorkBuddy 线路**
73
-
74
- - 注册 `workbuddy-subscription` Provider,接入腾讯 **WorkBuddy / CodeBuddy 订阅**。该 ID 特意与用户常用的自定义 OpenAI 兼容线路 `workbuddy` 分开,安装插件不会覆盖或隐藏原有自定义 API。可直接扫描 CodeBuddy 桌面端已登录的 `*.info` 凭据,也可从设置页选择国区/国际区并通过官方浏览器授权添加账号;插件添加的凭据保存在系统加密存储中(Windows DPAPI / macOS Keychain / Linux Secret Service),token 只留在 Host 进程内,不进入浏览器;
75
- - **多账号号池,与另外四条线路同源**:WorkBuddy 走共享内核 `AccountPoolCore`(`src/host/common/account-pool.ts`)并复用同一张设置卡片(`src/client/common/AccountPoolSection.tsx`),因此具备**顺序耗尽 / 轮询调度 / 粘性会话**三种策略、429 冷却换号、401/403 账号级失效(保留账号、重新登录即恢复)、设为主账号、账号备注、清除冷却与重新登录。桌面端扫描到的账号与插件内添加的账号**在同一号池里参与调度**;
124
+ ### WorkBuddy 线路
125
+
126
+ - 注册 `workbuddy-subscription` Provider,接入腾讯 **WorkBuddy / CodeBuddy 订阅**。
127
+ - 该 ID 特意与用户常用的自定义 OpenAI 兼容线路 `workbuddy` 分开,安装插件不会覆盖或隐藏原有自定义 API;
128
+ - 可直接扫描 CodeBuddy 桌面端已登录的 `*.info` 凭据,也可从设置页选择国区/国际区并通过官方浏览器授权添加账号;
129
+ - 插件添加的凭据保存在系统加密存储中(Windows DPAPI / macOS Keychain / Linux Secret Service),token 只留在 Host 进程内,不进入浏览器;
130
+ - **多账号号池,与其它线路同源**:走共享内核 `AccountPoolCore` 并复用同一张设置卡片,具备顺序耗尽 / 轮询调度 / 粘性会话、429 冷却换号、账号级失效保留、设为主账号与备注;
131
+ - **账号身份是两层**:不可变的 `internalId` 是唯一路由键,`identityKeys`(uuid / email / 派生 seed)是**只增不换**的别名集,同一账号再次登录会**合并**而不是产生幽灵账号;
132
+ - **唯一例外的诚实说明**:当服务端既没返回 uuid 也没返回 email 时无法自动识别同一账号,卡片会把该账号标注出来并提供**手动合并**;
133
+ - **顺序耗尽 / 轮询调度 / 粘性会话**三种策略;
134
+ - 429 冷却换号、401/403 账号级失效(保留账号、重新登录即恢复);
135
+ - 设为主账号、账号备注、清除冷却与重新登录;
136
+ - 桌面端扫描到的账号与插件内添加的账号**在同一号池里参与调度**;
76
137
  - **桌面账号归 IDE 所有,不可删除**:卡片只对插件自己添加的账号显示「删除」,桌面账号显示「隐藏 / 恢复」——隐藏只把它移出本插件的调度,绝不改动 CodeBuddy 的凭据文件;号池层同样拒绝删除桌面账号(双保险,均有测试);
77
138
  - **续期后原子写回**原凭据文件(只改 `auth` 块),以免桌面端掉线——桌面账号的 refresh token 会轮换,若只写进插件自己的加密存储,IDE 手里就只剩一个已作废的 token。同一进程内的并发调用**共用一次刷新**,且不会二次刷新;
78
139
  - 上游是 OpenAI 兼容的 `POST {backend}/v2/chat/completions`,但有两条硬约束:**只支持流式**(`stream:false` → 400 `code 11101`),且**首条消息必须是 system**(否则国际区返回 400 `code 11128`)。因此请求构造器始终发送 `stream:true`,并在调用方没给系统提示时补一条中性的,避免手搓的一次性请求踩到这条规则;
79
- - **区域是凭据属性,不是请求属性**:`*.workbuddy.ai` / `*.codebuddy.ai` 走国际区 `https://www.<apex>`,其余走国区 `https://copilot.tencent.com`。账号卡片逐条标注每个账号的**国区 / 国际区**并允许选择;切换后,模型目录、额度和后续对话都使用该账号。历史凭据快照按账号身份自动去重。插件托管账号可真正删除;桌面扫描账号只能从本插件隐藏/恢复,永不删除 CodeBuddy 的原文件。两区模型清单不同,把模型发到不服务它的区会返回 400 `code 11102`,因此模型选择器**按当前账号区域过滤**;
140
+ - **区域是凭据属性,不是请求属性**:`*.workbuddy.ai` / `*.codebuddy.ai` 走国际区 `https://www.<apex>`,其余走国区 `https://copilot.tencent.com`;
141
+ - 账号卡片逐条标注每个账号的**国区 / 国际区**并允许选择;切换后,模型目录、额度和后续对话都使用该账号;历史凭据快照按账号身份自动去重;
142
+ - 插件托管账号可真正删除;桌面扫描账号只能从本插件隐藏/恢复,永不删除 CodeBuddy 的原文件;
143
+ - 两区模型清单不同,把模型发到不服务它的区会返回 400 `code 11102`,因此模型选择器**按当前账号区域过滤**;
80
144
  - **模型目录取自网关自己的 `/v3/config`**(官方 CLI 启动时读的就是它):每个模型的真实上下文上限、输出上限、是否接受图片、以及可用的思考档位都在这里,不做任何按模型名猜测。`/v1/models` 在这条线路上是 404,所以此前只能靠内置表——现在内置表只作为离线兜底,且是**从真实 `/v3/config` 转录**的(早期手写版本把 `glm-5.3`、`kimi-k3` 的窗口猜成 200K/256K,实际都是 1M);
81
145
  - 目录同时区分**默认服务的上下文长度**与**模型上限**(如 `deepseek-v4.1-flash` 默认 300K、最大 1M)。本线路不发送显式长度参数,所以 DSH 的压缩与溢出判断按**默认服务长度**计算,不会让请求越过后端实际接受的窗口;
82
- - 思考档位**逐模型**取目录声明的档位表,回落顺序为「调用方显式指定 → 用户配置的默认档 → 目录为该模型声明的默认档」。最后一档不能省:上游在请求不带 `reasoning_effort` 时返回**空的 `reasoning_content`**(实测同一提示:不带字段 0 字符,带字段 130–215 字符),不发送就等于静默丢弃模型的思考。目录里的单个 `effort` 字段是**默认值**,既不是完整档位表也不是「只此一档」:实测这类模型接受 `low` / `high` / `max`,其余取值会被上游收敛到最近的档位,因此它们使用这三档的标准表(`WORKBUDDY_STANDARD_EFFORTS`);显式声明了 `supportedEfforts` 的模型则原样采信。目录给出的**默认档**也可能落在档位表之外(如 `minimax-m3`、`kimi-k3`、国区 `glm-5.3` 都报 `medium` 却只有三档),这种值会被收敛到最近的档位——否则默认档会被判为不支持而丢弃,请求不带 `reasoning_effort`,上游返回**空的 `reasoning_content`**。不在该模型档位集合内的取值会被忽略而不是发出去(上游对不支持的档位返回 `code 11150`);
83
- - **瞬时失败按错误类别重试**(`src/host/workbuddy/adapter.ts`)。上游 5xx 与 `code 11134` 归为 `SERVER` 并走有界退避(最多 3 次,1.5s 起步、15s 上限、0.2 抖动);额度耗尽(429 / `code 6004` / `code 14003`,其中 6004 的正文带重置时刻)归为 `RATE_LIMIT` 并遵守 `Retry-After`;而 401/403、跨区模型、不可用图片、历史形状错误都**不重试**,并给出可操作的提示(换模型 / 换图片 / 重新登录桌面端);
146
+ - 思考档位**逐模型**取目录声明的档位表,回落顺序为「调用方显式指定 → 用户配置的默认档 → 目录为该模型声明的默认档」;
147
+ - **最后一档不能省**:上游在请求不带 `reasoning_effort` 时返回**空的 `reasoning_content`**(实测同一提示:不带字段 0 字符,带字段 130–215 字符)——不发送等于静默丢弃模型的思考;
148
+ - 目录里的单个 `effort` 字段是**默认值**,既不是完整档位表也不是「只此一档」:实测这类模型接受 `low` / `high` / `max`(其余取值被上游收敛到最近档),因此使用这三档的标准表(`WORKBUDDY_STANDARD_EFFORTS`);显式声明了 `supportedEfforts` 的模型则原样采信;
149
+ - 目录给出的**默认档也可能落在档位表之外**(`minimax-m3`、`kimi-k3`、国区 `glm-5.3` 都报 `medium` 却只有三档),这种值会收敛到最近档——否则默认档被判不支持而丢弃,退化成上一条的空 `reasoning_content`;
150
+ - 不在该模型档位集合内的取值会被**忽略而不是发出去**(上游对不支持的档位返回 `code 11150`);
151
+ - **瞬时失败按错误类别重试**(`src/host/workbuddy/adapter.ts`):
152
+ - 上游 5xx 与 `code 11134` 归为 `SERVER` 并走有界退避(最多 3 次,1.5s 起步、15s 上限、0.2 抖动);
153
+ - 额度耗尽(429 / `code 6004` / `code 14003`,其中 6004 的正文带重置时刻)归为 `RATE_LIMIT` 并遵守 `Retry-After`;
154
+ - 401/403、跨区模型、不可用图片、历史形状错误都**不重试**,并给出可操作的提示(换模型 / 换图片 / 重新登录桌面端);
84
155
  - **流中断即失败**:上游必然以 `finish_reason` 或 `data: [DONE]` 结束,两者都缺失说明连接中途断开,此时抛出错误而不是把半截文本当成完整回答;
85
156
  - 额度来自 `/billing/meter/get-user-resource`,卡片展示套餐名、本周期已用/上限、剩余额度与重置时间;设置页为「设置 → 订阅服务 → WorkBuddy」标签页,对话输入框右侧有该线路的额度胶囊。
86
157
 
87
158
 
88
- **MiniMax Code(编程订阅)线路**
159
+ ### MiniMax Code(编程订阅)线路
89
160
 
90
- - 注册 `minimax-code` Provider,接入 **MiniMax Code 编程订阅**。它与 MiniMax 开放平台(按量计费的 API Key)是两套互不通用的系统:订阅的模型接口是 Anthropic Messages 协议(`https://agent.minimax.cn/mavis/api/v1/llm/v1/messages`),且**只用 `authorization: Bearer <accessToken>` 认证**——实测 `x-api-key` 一律返回 401 `{"code":401,"message":"token is required"}`,因此本线路**没有** x-api-key 回退分支(回退只会在每次请求上白花一个往返);
91
- - **复用桌面端登录态,而不是让你再登录一次**:凭据就是 MiniMax Code 自己写的 `~/.minimax/auth/<buildEnv>/<region>/mcode-public/auth.json`。**只读优先**:仍在有效期内(且距离到期还有 5 分钟以上)的令牌原样使用;进入续期窗口才轮换,并走**原子替换**(临时文件 + `rename`),失败或中断都让原文件保持逐字节不变,**不创建、不删除、不等待** `auth.lock`(那是桌面端自己的刷新锁,第二方碰它就可能打断官方客户端的刷新);
92
- - **两种登录来源互不覆盖**:桌面端的 `auth.json` 归桌面端所有,本插件只读;若本机没有它,才在设置页用 **RFC 8628 设备码流程**(PKCE S256)登录,凭据存到本插件自己的 `$DSH_HOME/storages/minimax-code-credentials.json`。**登出只作用于本插件自己那份凭据**:桌面端的登录态会被续期写回(只读优先),但**绝不撤销、绝不删除**,登出请求对它会被拒绝并说明原因(撤销它等于把你从正在跑的 MiniMax Code 里踢下线),这也是 WorkBuddy 对桌面账号的既有做法;
161
+ - 注册 `minimax-code` Provider,接入 **MiniMax Code 编程订阅**。它与 MiniMax 开放平台(按量计费的 API Key)是两套互不通用的系统:
162
+ - 订阅的模型接口是 Anthropic Messages 协议(`https://agent.minimax.cn/mavis/api/v1/llm/v1/messages`);
163
+ - **只用 `authorization: Bearer <accessToken>` 认证**——实测 `x-api-key` 一律返回 401 `"{\"code\":401,\"message\":\"token is required\"}"`,因此本线路**没有** x-api-key 回退分支(回退只会在每次请求上白花一个往返);
164
+ - **复用桌面端登录态,而不是让你再登录一次**:凭据就是 MiniMax Code 自己写的 `~/.minimax/auth/<buildEnv>/<region>/mcode-public/auth.json`。
165
+ - **只读优先**:仍在有效期内(且距离到期还有 5 分钟以上)的令牌原样使用;
166
+ - 进入续期窗口才轮换,并走**原子替换**(临时文件 + `rename`),失败或中断都让原文件保持逐字节不变;
167
+ - **不创建、不删除、不等待** `auth.lock`(那是桌面端自己的刷新锁,第二方碰它就可能打断官方客户端的刷新);
168
+ - **两种登录来源互不覆盖**:
169
+ - 桌面端的 `auth.json` 归桌面端所有,本插件只读;若本机没有它,才在设置页用 **RFC 8628 设备码流程**(PKCE S256)登录,凭据存到本插件自己的 `$DSH_HOME/storages/minimax-code-credentials.json`;
170
+ - **登出只作用于本插件自己那份凭据**:桌面端的登录态会被续期写回(只读优先),但**绝不撤销、绝不删除**,登出请求对它会被拒绝并说明原因(撤销它等于把你从正在跑的 MiniMax Code 里踢下线),这也是 WorkBuddy 对桌面账号的既有做法;
93
171
  - **区域是凭据属性**:`cn` 用 `account.minimax.cn` / `agent.minimax.cn`,`global` 用对应 `.io` 域名。两个区域的目录都会被探测,因此国际区账号不会因为没有国区文件而「未登录」;读回凭据时**以记录里存的区域为准**,而不是拿默认区域覆盖它;
94
- - **模型目录是硬编码的**(`src/host/minimax-code/model-catalog.ts`):该端点的 `GET /v1/models` **未对订阅流量开放**(503 `{"errorCode":50115,"errorReason":"direct_route_not_configured"}`),所以任何「实时目录」都只会是一个必然失败的请求。四款模型(M2.7 / M2.7 HighSpeed / M3 / M3.1 Flash Preview)的上下文、输出上限与思考形态都取自本机 `~/.minimax/config.yaml` 的模型表,**不从不存在的接口推断**;
172
+ - **模型目录是硬编码的,而且必须硬编码**(`src/host/minimax-code/model-catalog.ts`):
173
+ - 该端点的 `GET /v1/models` **未对订阅流量开放**(503 `"{\"errorCode\":50115,\"errorReason\":\"direct_route_not_configured\"}"`),所以任何「实时目录」都只会是一个必然失败的请求;
174
+ - 四款模型(M2.7 / M2.7 HighSpeed / M3 / M3.1 Flash Preview)的上下文、输出上限与思考形态都取自本机 `~/.minimax/config.yaml` 的模型表,**不从不存在的接口推断**;
95
175
  - **思考形态按模型表三态实现**:M2.7 系列**恒定开启且没有档位**(不发送任何字段);M3 是**开关二态**(`{type:'enabled'|'disabled'}`);M3.1 Flash Preview **强制开启并可指定档位**(`{type:'enabled',effort:...}`)。未知或越档的取值一律回落到该模型目录声明的默认档,不会把模型没有声明的档位发出去;
96
176
  - **视频不在能力声明里**:模型表虽然给 M3 / M3.1 标了视频输入,但 DSH 的附件服务只存图片、本线路也没有安装视频字节读取器,映射器只能把它替换成一条说明文本。**声明一个做不到的能力比不声明更糟**(DSH 的能力闸门、模型选择器与子代理委派都会当它成立),所以这里只声明 `text` 与 `image`;要加 `video` 必须先有真正的读取器(对照 Kimi Code 线路的 `video-store.ts`);
97
177
  - **瞬时失败按错误类别重试**(`src/host/minimax-code/adapter.ts`):上游模型供应商临时不可用(5xx)归为 `SERVER`、429 归为 `RATE_LIMIT`、连接未产出响应归为 `TRANSPORT`,走 DSH retry policy 的有界退避(最多 3 次,1.5s 起步、15s 上限、0.2 抖动);**但 429 的正文若说的是余额/额度耗尽,则判为终局**——重试只会推迟用户真正需要看到的提示;
98
178
  - **401 会强制续期一次再重试**:本地时钟看着还有效、服务端却拒收的令牌(在桌面端被撤销、时钟偏移、桌面端已轮换)与「真的需要重新登录」在状态码上无法区分,所以第一次 401 会**强制刷新一次并原样重试同一个请求体**;重试再被拒才是终局;
99
- - **刷新令牌是单次使用的,因此续期提前 5 分钟、并全进程共用一次轮换**(这是「每小时自己掉线、然后要求重新登录」的根因修复):实测 access token 只有 **1 小时**,而原先的续期阈值是「到期前 60 秒」——等于每次都在令牌已经会被拒收的那一个瞬间才去轮换,并发请求(状态卡轮询、签到、用量读取、模型请求)会各自拿同一枚刷新令牌去换,除第一个之外的调用全部拿到 `invalid_grant`,而这条终局判定会被记成「该账号需要重新登录」并写进号池(重启也在)。现在:① 续期窗口是 `PRE_EXPIRY_REFRESH_MS`(5 分钟),轮换发生在服务端还没有拒收之前;② 同一份凭证的轮换**注册在一张按身份(recordKey / loginEpoch)索引的进程内表上**,后到的调用者要么加入在途轮换、要么等待并采纳结果,绝不二次花费同一枚令牌;③ 轮换会**先看 storage 再相信终局判定**——如果磁盘上的凭证已经前进了(另一个持有者刚刚成功轮换,也可能是桌面端自己刷新过),就直接采纳它,**不记 tombstone、不报「重新登录」**;④ 号池行上遗留的过期标记会在下一次读取或请求时被**自动核对并清除**(凭证已前进即视为误判);⑤ 卡片此前根本没有「重新登录」按钮(`onRelogin` 未接线),现已补上;⑥ 响应未返回新的 refresh token 时沿用旧值,不再当作失败;
100
- - **请求体超过 2 MB 本地即拒绝**(与 Kimi 线路同一个守卫与上限,因为两条线路上游都是同一族端点);额度面板**有配额条了**:Token Plan 的用量端点**不在 MiniMax 的 API 文档里**(文档只说"用量显示在控制台的用量条上",这大概就是最初判定"没有端点"的原因),它来自官方 CLI——`mmx quota show` 读 `GET {apiHost}/v1/token_plan/remains`。返回的每个 `model_remains` 行带**两个窗口**(5 小时滚动 + 每周),因此卡片两根条分别显示,还能看到每根条的重置时刻与"已用/总量"。三个必须照抄官方实现的细节:① **`*_usage_count` 的语义是模糊的**(老响应是"剩余"、新响应是"已用"),官方用显式百分比消歧——照抄否则进度条会**反过来**;② **周窗口带显示倍率**(返回的是百分比 × `weekly_boost_permille`,可超过 100%);③ `status: 3` 通常表示"不限量",但**两个总量都为 0 时**它表示"当前套餐不含该模型",渲染成不限量会凭空许诺额度。**有两套用量端点,第一轮打错了那一套**。① **平台端点** `/v1/token_plan/remains`(`api.minimax.cn` / `api.minimaxi.com` / `api.minimax.io`,实测都返回 JSON)只接受**平台 API 密钥**:`mcode-public` 登录态在四种认证写法(`Bearer` / 原始 / `x-api-key` / 两者都带)下全部被拒,且是 **HTTP 200 + `base_resp.status_code: 1004`**。官方 `mmx` CLI 能用,是因为它的 OAuth 是**平台**登录,与本线路的 mcode 登录是两套身份。② **mcode 自己的端点在官方客户端源码里**(`packages/tui/src/account/matrix-account-client.ts`):`GET https://agent.minimaxi.com/v1/api/openplatform/coding_plan/remains`(国际 `agent.minimax.io`),路径与平台端点**完全不同**——本插件现在用的就是这条(本线路自己的 `agent.minimax.cn` 优先、官方主机兜底)。⚠️ **但它带 `yy` / `x-timestamp` / `x-signature` / `User-Agent: MiniMaxCode` 四个"官方客户端标识头"**:官方注释自己写明这些是"把请求标记为来自 MiniMax 第一方客户端"的字面量。本插件 `types.ts` 已明确决定**不伪造官方客户端身份**(伪造既失信,也可能是封号理由),所以**默认不发**,宁可拿到一个拒绝让卡片如实说明;确实要数字的用户可以设 `DSH_MINIMAX_CODE_QUOTA_ATTRIBUTION=1` 显式选择该取舍。三处防护:① `base_resp` 非 0 视为凭据被拒,**只试一台就停**并记住 30 分钟;② 其它失败按 `unreachable` 区分、记住 10 分钟;③ `/status` **只读缓存、绝不在轮询里发网络请求**。读用量**永远不影响对话**:独立只读路径,失败只让卡片换一句话;
101
- - **设置面与其余各线路逐项对齐**(该 PR 最初缺失这一整块,用户明确指出):① **「启用此供应商」总开关**——关闭后适配器**不暴露任何模型**(包括已勾选的),因为「关掉」如果还能用就等于没关;② **模型勾选**——只有勾选的模型进入对话页的模型选择列表,另有「全选 / 全不选」;③ **模型上下文窗口覆盖**——每个已勾选模型可填自定义容量(接受 `1M` / `512K` / `200000`),「恢复默认」以 `null` 语义在 host 侧**删除**该键而不是写入 0;④ **默认思考深度**——全局档位只在模型确实列了该档时生效,否则回落到该模型自己的默认档(广告一个模型会拒绝的档位只会让服务端拒掉请求),且「关闭思考」只对**可关闭**的模型生效;⑤ **号池管理**(见下条)。目录仍然硬编码,但「这一安装当前怎么用它」是用户设置:两者分开存储(目录随代码发布、设置随用户数据保存),因此一份用户数据不会看起来像一条已发布的目录项;
179
+ - **刷新令牌是单次使用的,因此续期提前 5 分钟、并全进程共用一次轮换**:
180
+ - 这是「每小时自己掉线、然后要求重新登录」的根因。access token 实测只有 **1 小时**,而原先的续期阈值是「到期前 60 秒」——等于每次都在令牌已经会被拒收的那一瞬间才去轮换;
181
+ - 并发请求(状态卡轮询、签到、用量读取、模型请求)会各自拿同一枚刷新令牌去换,除第一个之外全部拿到 `invalid_grant`,而这条终局判定会被记成「该账号需要重新登录」并写进号池(重启也在);
182
+ - 现在:① 续期窗口 `PRE_EXPIRY_REFRESH_MS` 为 5 分钟,轮换发生在服务端还没有拒收之前;② 同一份凭证的轮换**按身份(`recordKey` / `loginEpoch`)单飞**,后到的调用者要么加入在途轮换、要么采纳结果,绝不二次花费同一枚令牌;
183
+ - ③ 轮换**先看 storage 再相信终局判定**——磁盘上的凭证若已前进(别人刚成功轮换,或桌面端自己刷新过),直接采纳,**不记 tombstone、不报「重新登录」**;
184
+ - ④ 号池行上遗留的过期标记会在下次读取或请求时**自动核对并清除**;⑤ 卡片补上了「重新登录」按钮(`onRelogin` 此前未接线);⑥ 响应未返回新 refresh token 时沿用旧值,不再当作失败;
185
+ - **请求体超过 2 MB 本地即拒绝**(与 Kimi 线路同一个守卫与上限,因为两条线路上游都是同一族端点);
186
+ - **额度面板有两套用量端点,第一轮打错了那一套**:
187
+ - **平台端点** `/v1/token_plan/remains`(`api.minimax.cn` / `api.minimaxi.com` / `api.minimax.io`)**只接受平台 API 密钥**:`mcode-public` 登录态在四种认证写法(`Bearer` / 原始 / `x-api-key` / 两者都带)下全部被拒,且是 **HTTP 200 + `base_resp.status_code: 1004`**——不是 401,很容易被误读为成功;
188
+ - 官方 `mmx` CLI 能用该端点,是因为它的 OAuth 是**平台**登录,与本线路的 mcode 登录是两套身份;
189
+ - **mcode 自己的端点在官方客户端源码里**(`packages/tui/src/account/matrix-account-client.ts`):`GET https://agent.minimaxi.com/v1/api/openplatform/coding_plan/remains`(国际 `agent.minimax.io`),路径与平台端点**完全不同**。本插件用的就是这条(本线路的 `agent.minimax.cn` 优先、官方主机兜底);
190
+ - ⚠️ **但它带 `yy` / `x-timestamp` / `x-signature` / `User-Agent: MiniMaxCode` 四个「官方客户端标识头」**——官方注释自己写明这些是「把请求标记为来自 MiniMax 第一方客户端」的字面量;
191
+ - 本插件 `types.ts` 已明确决定**不伪造官方客户端身份**(伪造既失信,也可能是封号理由),因此**默认不发**,宁可拿到一个拒绝让卡片如实说明;
192
+ - 确实要数字的用户可设 `DSH_MINIMAX_CODE_QUOTA_ATTRIBUTION=1` 显式选择该取舍;
193
+ - **用量端点不在 MiniMax 的 API 文档里**(文档只说「用量显示在控制台的用量条上」,这大概就是最初判定「没有端点」的原因),它来自官方 CLI——`mmx quota show`。返回的每个 `model_remains` 行带**两个窗口**(5 小时滚动 + 每周),卡片因此有两根条,各自显示重置时刻与「已用/总量」;
194
+ - **三个必须照抄官方实现的解析细节**:
195
+ - ① **`*_usage_count` 的语义是模糊的**(老响应是「剩余」、新响应是「已用」),官方用显式百分比消歧——照抄,否则进度条会**反过来**;
196
+ - ② **周窗口带显示倍率**(返回的是百分比 × `weekly_boost_permille`,可超过 100%);
197
+ - ③ `status: 3` 通常表示「不限量」,但**两个总量都为 0 时**它表示「当前套餐不含该模型」——渲染成不限量会凭空许诺额度;
198
+ - **三处降噪防护**:① `base_resp` 非 0 视为凭据被拒,**只试一台就停**并记住 30 分钟;② 其它失败按 `unreachable` 区分、记住 10 分钟;③ `/status` **只读缓存、绝不在轮询里发网络请求**;
199
+ - **读用量永远不影响对话**:独立只读路径,失败只让卡片换一句话;
200
+ - **设置面与其余各线路逐项对齐**(该 PR 最初缺失这一整块,用户明确指出):
201
+ - ① **「启用此供应商」总开关**——关闭后适配器**不暴露任何模型**(包括已勾选的),因为「关掉」如果还能用就等于没关;
202
+ - ② **模型勾选**——只有勾选的模型进入对话页的模型选择列表,另有「全选 / 全不选」;
203
+ - ③ **模型上下文窗口覆盖**——每个已勾选模型可填自定义容量(接受 `1M` / `512K` / `200000`),「恢复默认」以 `null` 语义在 host 侧**删除**该键而不是写入 0;
204
+ - ④ **默认思考深度**——全局档位只在模型确实列了该档时生效,否则回落到该模型自己的默认档(广告一个模型会拒绝的档位只会让服务端拒掉请求),且「关闭思考」只对**可关闭**的模型生效;
205
+ - ⑤ **号池管理**(见下条);
206
+ - 目录仍然硬编码,但「这一安装当前怎么用它」是用户设置:**两者分开存储**(目录随代码发布、设置随用户数据保存),因此一份用户数据不会看起来像一条已发布的目录项;
102
207
  - **号池与多账号登录**:复用与其它线路**同一个**号池内核(`src/host/common/account-pool.ts`)与**同一张**共享账号卡片,具备顺序耗尽 / 轮询 / 粘性调度、429 冷却换号、设为主账号与备注。**身份键不是令牌**:桌面端凭据用它自己的 `recordKey`(同一个记录槽每次轮换都是同一账号,因此在池里原地更新),插件自持凭据用 `loginEpoch`(每次设备码登录就是一次独立会话)——用刷新令牌做键在每次轮换后都会把同一账号看成新账号;
103
208
  - **桌面端登录态可被显式「导入」为号池账号**(`POST /accounts {action:'adopt'}`):只读取、只新增一行,不改动官方客户端的任何文件;重复导入按记录槽身份原地更新,不会产生重复账号。**在设置页新发起的设备码登录会自动加入号池**(`pollWebLogin` 的 `onSave` 钩子)——没有这个钩子,新登录只会写进镜像文件而号池看不见,这正是「多账号登录没配好」的根因;该钩子的失败**不会**让登录失败,因为凭据此时已经落盘。
104
209
 
105
- **Claude(订阅)线路**
210
+ ### Claude(订阅)线路
106
211
 
107
212
  - 注册 `claude-subscription` Provider,以 **Claude Pro / Max 订阅的 OAuth 登录态**访问 Claude 模型(Anthropic Messages 接口),**不使用 API Key、也不按量计费**;
108
213
  - ⚠️ **风险须知(插件不设确认步骤,但事实不变)**:Anthropic 现行条款明确写明**不允许第三方应用提供 Claude.ai 登录、也不允许代用户经 Free / Pro / Max 凭据转发请求**,并保留不经预告的执法权;已有开源项目被下架、有账号因此被限制。**本插件未获 Anthropic 任何授权或认可**,使用风险由使用者自行承担。此前版本在卡片上设有确认步骤与配套主机门禁,两者均已移除;移除的只是那一步交互,**不改变上述事实**;
109
- - **这不是抄一个 token 就能用**:订阅令牌要求请求**完整模仿 Claude Code 的身份**,否则会被服务端拒绝或分类判别——`Authorization: Bearer` 且 **`x-api-key` 必须缺省**、`user-agent: claude-cli/<版本>`、`x-app: cli`、`anthropic-beta` 至少含 `oauth-2025-04-20` 与 `claude-code-20250219`、`system` 的**首个块必须是 Claude Code 身份声明**。这些都在代码里显式实现并有测试锁定;
214
+ - **这不是抄一个 token 就能用**:订阅令牌要求请求**完整模仿 Claude Code 的身份**,否则会被服务端拒绝或分类判别。这些都在代码里显式实现并有测试锁定:
215
+ - `Authorization: Bearer` 且 **`x-api-key` 必须缺省**;
216
+ - `user-agent: claude-cli/<版本>`、`x-app: cli`;
217
+ - `anthropic-beta` 至少含 `oauth-2025-04-20` 与 `claude-code-20250219`;
218
+ - `system` 的**首个块必须是 Claude Code 身份声明**;
110
219
  - **OAuth 用 PKCE,且 state 与 verifier 独立生成**。这一点特意**不照抄参照实现**:本机参照实现把 PKCE verifier 直接当作 OAuth `state`(`state: verifier`),那会把本应保密的 verifier 写进授权 URL、地址栏、浏览器历史乃至剪贴板。本线路是两次独立随机抽样,并有测试断言授权 URL 中**不出现原始 verifier**;
111
220
  - **两条登录路径,模式在流程开始时确定且不可中途切换**:默认是**手动粘贴**(授权页把码显示在屏幕上,粘贴 `<code>#<state>`;也容错接受整条重定向 URL)。可选 loopback 回调,绑定 `127.0.0.1` 并顺序探测可用端口(Windows 的保留端口段会让固定端口绑定失败)。端口探测失败**降级为手动**而不是报错。切换模式等于作废当前流程并重新发起——授权码与签发它的那次请求的 `redirect_uri` 绑定,这是该流程最常见的失败;
112
221
  - **一次授权只兑换一次**:浏览器回调与手动粘贴可能同时到达,用 compare-and-set 保证只有一方发起兑换(另一方得到「正在处理中」,已结算的流程得到 410 且**不做任何兑换**)。错误的 `state` **只拒绝那一个请求**,不会终止正在进行的合法登录。回调服务器只绑 loopback、只应答 `/callback`、只接受本机来源;
113
222
  - **刷新是单飞的**:并发请求共享同一次刷新。否则到期瞬间的一批请求会各自轮换刷新令牌,除第一个之外全部作废(上游会给出终局判定)。刷新令牌轮换后**先读回校验再落盘**;
114
223
  - **模型能力逐模型查表**(`src/host/claude/model-catalog.ts`),**不从模型名推断**。该表转录自本机随 harness 安装的参照目录,**只证明抄录忠实,不证明服务端提供这些模型**——服务端自己的 `GET /v1/models` 才是权威,且它**只覆盖上下文窗口**,能力字段不被改写;
115
- - **思考形态有四类,顺序决定成败**(`thinkingMode`):`mid-convo` → `{type:'adaptive', block_binding:{prefix_mismatch_behavior:'drop_block'}}` 外加 `output_config.effort`;`adaptive` → `{type:'adaptive'}`;`budget` → `{type:'enabled', budget_tokens}`(预算算术按参照实现转录,思考预算计入 `max_tokens`,**必须为回答留出至少 1024 token**);`none` → 不发思考字段。**`mid-convo` 排在最前且无条件**——`claude-fable-5-1` 与 `claude-opus-5` 同时带 `forceAdaptiveThinking`,把它们当成普通 `adaptive` 会**静默丢掉 `block_binding` 与 `output_config`**,而参照实现自己的注释写明该缺失会导致**持续 400**。反过来,**`block_binding` 本身必须由 `anthropic-beta: thinking-binding-controls-2026-08-01` 授权**(官方文档:缺该 beta 时返回 400 `block_binding: Extra inputs are not permitted`),因此请求头从**已构建的请求体**读出是否带 `block_binding`,带则追加该 beta;
224
+ - **思考形态有四类,顺序决定成败**(`thinkingMode`):
225
+ - `mid-convo` → `{type:'adaptive', block_binding:{prefix_mismatch_behavior:'drop_block'}}` 外加 `output_config.effort`;
226
+ - `adaptive` → `{type:'adaptive'}`;
227
+ - `budget` → `{type:'enabled', budget_tokens}`(预算算术按参照实现转录;思考预算计入 `max_tokens`,**必须为回答留出至少 1024 token**);
228
+ - `none` → 不发思考字段;
229
+ - **`mid-convo` 排在最前且无条件**:`claude-fable-5-1` 与 `claude-opus-5` 同时带 `forceAdaptiveThinking`,把它们当成普通 `adaptive` 会**静默丢掉 `block_binding` 与 `output_config`**,而参照实现自己的注释写明该缺失会导致**持续 400**;
230
+ - 反过来,**`block_binding` 本身必须由 `anthropic-beta: thinking-binding-controls-2026-08-01` 授权**(官方文档:缺该 beta 时返回 400 `block_binding: Extra inputs are not permitted`)。因此请求头从**已构建的请求体**读出是否带 `block_binding`,带则追加该 beta;
116
231
  - **思考块带签名则原样回放**(这是思考模式下多轮工具调用的前提;`redacted_thinking` 同样回放),**无签名则丢弃**。工具名在出站时按 Claude Code 规范大小写归一化、入站时按大小写无关匹配回用户工具名;若两个工具归一化后碰撞,则**放弃归一化**原样发送,避免把结果投给错误的工具;
117
- - **多账号号池,与其它线路同源**:走共享内核 `AccountPoolCore` 并复用同一张设置卡片,具备顺序耗尽 / 轮询调度 / 粘性会话、429 冷却换号、账号级失效保留、设为主账号与备注。**账号身份是两层**:不可变的 `internalId` 是唯一路由键,`identityKeys`(uuid / email / 派生 seed)是**只增不换**的别名集,同一账号再次登录会**合并**而不是产生幽灵账号。**唯一例外的诚实说明**:当服务端既没返回 uuid 也没返回 email 时无法自动识别同一账号,卡片会把该账号标注出来并提供**手动合并**;
232
+ - **多账号号池,与其它线路同源**:复用与其它线路**同一个**号池内核(`src/host/common/account-pool.ts`)与**同一张**共享账号卡片,具备顺序耗尽 / 轮询 / 粘性调度、429 冷却换号、设为主账号与备注;
233
+ - **身份键不是令牌**:桌面端凭据用它自己的 `recordKey`(同一个记录槽每次轮换都是同一账号,因此在池里原地更新),插件自持凭据用 `loginEpoch`(每次设备码登录就是一次独立会话)——用刷新令牌做键在每次轮换后都会把同一账号看成新账号;
118
234
  - **可选择性收编本机已有的 Claude Code 登录(默认关闭)**:开启前只做一次「文件是否存在」的探测,**不读取内容**;只有你显式开启后才读取。收编得到的是一份**快照**,**永不由本插件刷新**——Claude Code 刷新的是同一枚轮换令牌,两个进程各自刷新会互相作废,而进程内的单飞解决不了跨进程竞态。快照过期后卡片会提示你**回 Claude Code 重新登录后再收编**,并且**绝不会写入或删除 Claude Code 的任何文件**;
119
- - **额度面有两个来源、两套单位**:主来源 `GET /api/oauth/usage` 的 `utilization` 是**已用百分比 0–100**;而 `/v1/messages` 响应头 `anthropic-ratelimit-unified-5h-utilization` 是**分数 0–1**、重置时间是 **epoch 秒**。两套单位分别换算并有交叉测试证明它们描述同一状态。**`utilization: 0` 表示「尚未使用」,是正常状态,不是额度耗尽**;两者都不会混淆。有真实流量时以响应头为准以降低查询频率,但卡片仍会按 `QUOTA_FULL_REFRESH_MS` 做一次完整读取;
235
+ - **额度面有两个来源、两套单位**,换算后有交叉测试证明它们描述同一状态:
236
+ - 主来源 `GET /api/oauth/usage` 的 `utilization` 是**已用百分比 0–100**;
237
+ - 而 `/v1/messages` 响应头 `anthropic-ratelimit-unified-5h-utilization` 是**分数 0–1**,重置时间是 **epoch 秒**;
238
+ - 有真实流量时以响应头为准以降低查询频率,但卡片仍会按 `QUOTA_FULL_REFRESH_MS` 做一次完整读取;
239
+ - **提示缓存时长可选择**(设置页):本线路用订阅凭据,而 [Claude Code 官方文档](https://code.claude.com/docs/en/prompt-caching) 写明**订阅用户在套餐额度内对主对话使用 1 小时 TTL**(超出额度改按用量计费后官方会降回 5 分钟)。此前固定使用 5 分钟默认值,与官方客户端行为不一致——同样的用量,官方用户享受 4 倍缓存窗口而本插件没有;
240
+ - 1 小时是**需要许可的能力**:body 里写 `ttl: '1h'` 必须同时带 `anthropic-beta: extended-cache-ttl-2025-04-11`,否则被拒(与 `block_binding` 同理)。插件从**已构建的请求体**读出所选档位并据此发出该 beta,两者不可能不一致;
241
+ - 写入 1 小时档价格更高,短会话不划算,因此设置项可覆盖为「跟随官方 / 1 小时 / 5 分钟」;
120
242
  - **换号的两条硬约束**:只在**凭据失败**或**账号级限流**时换号——全局限流、过载与 5xx **绝不换号**(其它账号共享同一全局限制)。**一旦已有输出产出就绝不换号**(否则会重复文本或重复工具调用),改为直接报错。换号最多 3 次;
121
243
  - 模型勾选、思考深度、上下文窗口覆盖与额度在「设置 → 订阅服务 → Claude」标签页中配置,输入框右侧另有额度胶囊(取**剩余最紧的那个窗口**)。
122
244
 
123
- **设置页**
245
+ ### 设置页
124
246
  - 展示账号(脱敏 email、套餐、账号 ID 后四位)、连接状态、额度与订阅增强功能开关;
125
- - **偏好落盘位置随 harness 生成**:有 `settings.register` 的一代(≤0.1.6)仍写进 harness 的设置文档;0.1.7 起该 API 被 `SettingsForms` 取代,偏好改由插件自己持久化到 `<dshHome>/storages/dsh-chatgpt-subscription-preferences.json`(0600、原子写;读取失败或校验不过就回落默认值;**首次运行会从旧设置文档里本插件的段一次性迁移**),五条线路的模型开关同样从各自既有的 `storages/*-models.json` 水合,因此重启后不会像被重置;
247
+ - **偏好落盘位置随 harness 生成**:
248
+ - 有 `settings.register` 的一代(≤0.1.6)仍写进 harness 的设置文档;
249
+ - 0.1.7 起该 API 被 `SettingsForms` 取代,偏好改由插件自己持久化到 `<dshHome>/storages/dsh-chatgpt-subscription-preferences.json`(0600、原子写;读取失败或校验不过就回落默认值);
250
+ - **首次运行会从旧设置文档里本插件的段一次性迁移**;五条线路的模型开关同样从各自既有的 `storages/*-models.json` 水合,因此重启后不会像被重置;
126
251
  - 子代理的模型与思考深度沿用 DSH 自身设置:**设置 → Subagent** 卡片授权 Agent 可以为子代理挑选的模型(来自 DSH 已接入的全部 Provider,包含本插件的 Codex / Antigravity),新 Agent 的默认路由由 DSH 的 `agent-default-model` 设置提供;
127
252
  - 最大嵌套深度不在本插件设置内,由 DSH 侧决定:0.1.5 及以前是 preset 中 `tool-subagent` 行的 `maxDepth`(默认 3),0.1.6 起改由 `subagent` 服务的设置项提供(默认 1);`provider-managed` 表示把预算交给进程外提供方;
128
253
  - GPT-6 系列(6 Astra / 6 Sol / 6 Luna)默认使用 384K 有效上下文,可配置最高 872K;5.6 Sol / Terra / Luna 保持 272K,最高 1M,用于 DSH 压缩与溢出判断;其他模型保持目录声明值;
129
- - 单次输出上限按模型区分:GPT-6 系列为 128K(官方对 6 Astra / 6 Sol / 6 Luna 均标 128K),更早的模型保持 32768;调用方未显式指定时生效,Responses 报文本身不发送输出长度参数。
254
+ - 单次输出上限按模型区分:GPT-6 系列为 128K(官方对 6 Astra / 6 Sol / 6 Luna 均标 128K),更早的模型保持 32768。调用方未显式指定时按模型上限发送;显式指定时按 `min(请求值, 模型上限)` 封顶——超出模型能力的请求会被上游直接拒绝,因此必须向下封而不是原样透传。**`max_output_tokens` 始终出现在请求中**:不发送时由上游套用自己的默认值,本插件既无法预测也无法上报,某一轮撞上上限会看起来像一次普通的短回答。
130
255
  - 可访问的进度条、窄窗口/200% 缩放布局、深浅主题与 reduced-motion。
131
256
 
132
257
  ## 模型目录
133
258
 
134
259
  | 显示名 | 模型 slug |
135
260
  | --- | --- |
261
+ | 6.1 Sol | `gpt-6.1-sol` |
136
262
  | 6 Astra | `gpt-6-astra` |
137
263
  | 6 Sol | `gpt-6-sol` |
138
264
  | 6 Luna | `gpt-6-luna` |
@@ -146,7 +272,7 @@
146
272
 
147
273
  > 目录只用于展示;账号实际可用的模型由 ChatGPT 套餐、workspace 策略与上游兼容状态决定。
148
274
 
149
- GPT-6 系列(6 Astra / 6 Sol / 6 Luna)支持文本、图片输入和工具调用,默认思考档位为 `medium`,可选 `low`、`medium`、`high`、`xhigh`、`max`。从旧会话带入的 `none` / `minimal` 会按 [OpenAI 官方迁移说明](https://developers.openai.com/api/docs/guides/latest-model) 转为 `low`。三个模型的默认 384K 与上限 872K 均取自 2026-09-23 的 Codex 模型目录(`gpt-6-sol` / `gpt-6-luna` 于 2026-09-22 发布,能力与 `gpt-6-astra` 一致;目录里的 `context_window` 是 272K,本插件把默认有效上下文提高到 384K,仍低于 872K 上限);[Codex Ultra](https://learn.chatgpt.com/zh-Hans/docs/models) 涉及客户端的子代理编排,本插件不将它作为 Responses 思考参数暴露。
275
+ GPT-6 系列(6.1 Sol / 6 Astra / 6 Sol / 6 Luna)支持文本、图片输入和工具调用,默认思考档位为 `medium`,可选 `low`、`medium`、`high`、`xhigh`、`max`。从旧会话带入的 `none` / `minimal` 会按 [OpenAI 官方迁移说明](https://developers.openai.com/api/docs/guides/latest-model) 转为 `low`。三个模型的默认 384K 与上限 872K 均取自 2026-09-23 的 Codex 模型目录(`gpt-6-sol` / `gpt-6-luna` 于 2026-09-22 发布,能力与 `gpt-6-astra` 一致;目录里的 `context_window` 是 272K,本插件把默认有效上下文提高到 384K,仍低于 872K 上限);[Codex Ultra](https://learn.chatgpt.com/zh-Hans/docs/models) 涉及客户端的子代理编排,本插件不将它作为 Responses 思考参数暴露。
150
276
 
151
277
  新配置默认显示 GPT-6 系列与 GPT-5.6 系列;已有配置保留原来的模型勾选,可在 **设置 → Codex 订阅 → 可用模型** 中勾选 **6 Sol** / **6 Luna**。
152
278
 
@@ -155,7 +281,10 @@ GPT-6 系列(6 Astra / 6 Sol / 6 Luna)支持文本、图片输入和工具
155
281
  - Windows 或 Linux;
156
282
  - Windows:系统需提供 Windows PowerShell,以使用 CurrentUser DPAPI;
157
283
  - Linux:Host 用户必须拥有可写的 `~/.dsh`(或 `$DSH_HOME`),凭据文件会强制使用 `0600`、目录使用 `0700`;
158
- - 已安装 DSH:peer 范围覆盖 0.1.2-alpha.5 及以后的 0.1.x(含 0.1.5-rc.2、0.1.6-alpha 与 0.1.7-alpha.1)。构建与测试以 **0.1.7-alpha.1**(npm 上 `@deepseek-ai/dsh` 的 `alpha`)为基线,旧版行为由版本兼容层保留:0.1.7 重写了会话消息模型(工具结果由 `tool-result` 内容块改为 `role: "tool"` 消息)、删除了 `settings.register`(偏好改由插件自有存储落盘)、并让 agent preset 不再从 `~/.dsh/.agent-presets` 读取,插件在请求边界、设置服务与 preset 注册三处同时适配,因此同一份代码可装在 0.1.2-alpha.5 以来的各代上。0.1.1-rc.2 不再声明支持——它既没有 preset 用到的 `present` 工具,`mode` 枚举那时也还写作 `code`;0.1.6 把 workflow 引擎改了包名,插件在 preset 同步时按当前安装自动适配(见下);
284
+ - 已安装 DSH:peer 范围覆盖 **0.1.2-alpha.5 及以后的每一代**,一直到 0.2.0-rc.2。构建与测试以 **0.2.0-rc.2** 为基线,旧版行为由版本兼容层保留,因此**同一份代码**可装在 0.1.2-alpha.5 以来的所有代上(用户分散在 npm 的 `latest` / `next` / `alpha` 三个标签上,多数人跑的是比 `alpha` 落后几个版本的 `latest`)。需要桥接的破坏性变更:
285
+ - **0.1.7** 重写了会话消息模型(工具结果由 `tool-result` 内容块改为 `role: "tool"` 消息)、删除了 `settings.register`(偏好改由插件自有存储落盘)、并让 agent preset 不再从 `~/.dsh/.agent-presets` 读取。插件在请求边界、设置服务与 preset 注册三处同时适配。
286
+ - **0.1.6** 把 workflow 引擎改了包名,插件在 preset 同步时按当前安装自动适配(见「随包分发的 Agent Preset」)。
287
+ - 0.1.1-rc.2 不再声明支持——它既没有 preset 用到的 `present` 工具,`mode` 枚举那时也还写作 `code`。
159
288
  - Node.js 与 npm。
160
289
 
161
290
  ## 安装
@@ -173,11 +302,11 @@ dsh plugin --profile web add @eddyskywalker/dsh-chatgpt-subscription
173
302
  npx @deepseek-ai/dsh plugin --profile web add @eddyskywalker/dsh-chatgpt-subscription
174
303
  ```
175
304
 
176
- **版本阶段**:`latest` 现在是 **0.8.2**,上面两条命令装到的就是它。(此前 0.8.0-alpha.0 至 0.8.2 都只发在 `alpha` 标签下,`latest` 一直停在 0.7.0,于是升级时版本会从 0.8 退回 0.7。)预发布版仍只进 `alpha`,需要显式带上标签或版本号:
305
+ **版本阶段**:上面两条命令装到的是 npm `latest` 标签指向的版本(撰写时为 **0.10.12**)。包不预置 `publishConfig.tag`,因此稳定版一发布就落在 `latest`——也就是 `npm install` 与 `dsh plugin add` 解析到的那个标签;预发布版只进 `alpha`,需要显式带上标签或版本号:
177
306
 
178
307
  ```sh
179
308
  dsh plugin --profile web add @eddyskywalker/dsh-chatgpt-subscription@alpha
180
- npm install @eddyskywalker/dsh-chatgpt-subscription@0.8.0-alpha.0
309
+ npm install @eddyskywalker/dsh-chatgpt-subscription@alpha
181
310
  ```
182
311
 
183
312
  ### 方式 2:在 DSH 界面里安装
@@ -614,6 +743,8 @@ npm run build
614
743
  npm pack --dry-run
615
744
  ```
616
745
 
746
+ > `npm run typecheck` 里的 `tsc -b` **不带** `--force` 时会重放 `lib/*.tsbuildinfo`,在一份陈旧构建上只要几秒就返回——**看起来绿了,却从未真正编译过**。改 harness 版本或新增类型相关的代码后,请以 `npx tsc -b --force` 为准,并把 `tsc -p test/tsconfig.json` 单独跑一遍(测试树不在 `-b` 的范围内)。
747
+
617
748
  `vitest.config.ts` 把 `testTimeout` 设为 60s、`maxWorkers` 限到 4 是有原因的,改回去会让套件重新变得不稳定:多条目测真的会 spawn `powershell.exe` 跑 Windows DPAPI 凭据存储,隔离测量最慢的一条要 12–14s。超时余量不够时不只是那一条失败——**超时后仍在飞的请求会落进下一个用例的 fetch mock**,把邻居也判失败(表现为 `expected to be called 4 times, but got 8 times`)。限并发不增加耗时:这些用例受子进程延迟约束,4 个 worker 约 51s,15 个约 54s。
618
749
 
619
750
  测试使用 mock OAuth、Responses SSE 和 Wham usage,不需要真实 ChatGPT 凭据。真实账号的端到端登录与生成应在独立 DSH profile 中人工验收,避免影响日常 profile。
@@ -623,6 +754,9 @@ npm pack --dry-run
623
754
  | 现象 | 处理 |
624
755
  | --- | --- |
625
756
  | 1455 端口占用 | 结束旧登录任务或占用该端口的进程后重试;插件卸载会关闭 listener |
757
+ | 模型选择器里没有新发布的模型 | 该模型必须出现在 `/backend-api/codex/models` 返回的列表里(该列表是「这个账号能调什么」的权威)。若后端已发布而选择器没有,点设置页的**强制刷新目录**;仍不出现则说明当前套餐/workspace 无权调用 |
758
+ | 回答在中途被截断 | 可能是撞到输出上限。插件始终发送 `max_output_tokens`(按模型上限或调用方请求的较小值),被截断会以 `max-tokens` 结束原因呈现;可用**增强功能**里的上下文覆盖或换模型调整 |
759
+ | 断网后首次打开设置页很慢 | 目录有本地快照兜底,重启后第一次渲染不需要网络;若仍慢说明快照不可写(home 只读),此时不影响功能 |
626
760
  | 登录后仍是 401 | 刷新 token;若刷新 token 已失效,注销并重新登录,不会循环请求 |
627
761
  | 额度显示旧数据 | 设置页会保留最后成功值;等待 15 秒节流窗口后手动刷新 |
628
762
  | 429 | 插件遵守 `Retry-After`,不会高频轮询;模型请求由 DSH retry policy 有界重试 |