@eddyskywalker/dsh-chatgpt-subscription 0.28.0 → 0.30.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 (67) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +11 -2
  3. package/lib/client.js +291 -164
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +694 -157
  6. package/lib/types/client/CodexSubscriptionSection.d.ts.map +1 -1
  7. package/lib/types/client/antigravity/locales.d.ts +11 -2
  8. package/lib/types/client/antigravity/locales.d.ts.map +1 -1
  9. package/lib/types/client/claude/locales.d.ts +11 -2
  10. package/lib/types/client/claude/locales.d.ts.map +1 -1
  11. package/lib/types/client/command-code/locales.d.ts +11 -2
  12. package/lib/types/client/command-code/locales.d.ts.map +1 -1
  13. package/lib/types/client/common/AccountPoolSection.d.ts +10 -2
  14. package/lib/types/client/common/AccountPoolSection.d.ts.map +1 -1
  15. package/lib/types/client/common/account-pool-labels.d.ts +17 -0
  16. package/lib/types/client/common/account-pool-labels.d.ts.map +1 -1
  17. package/lib/types/client/common/format.d.ts +14 -0
  18. package/lib/types/client/common/format.d.ts.map +1 -1
  19. package/lib/types/client/common/styles.d.ts.map +1 -1
  20. package/lib/types/client/glm-coding-plan/locales.d.ts +11 -2
  21. package/lib/types/client/glm-coding-plan/locales.d.ts.map +1 -1
  22. package/lib/types/client/glm-coding-plan/useGlmCodingPlanSection.d.ts.map +1 -1
  23. package/lib/types/client/kimi-code/locales.d.ts +11 -2
  24. package/lib/types/client/kimi-code/locales.d.ts.map +1 -1
  25. package/lib/types/client/locales.d.ts +7 -1
  26. package/lib/types/client/locales.d.ts.map +1 -1
  27. package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts +8 -5
  28. package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts.map +1 -1
  29. package/lib/types/client/minimax-code/locales.d.ts +17 -8
  30. package/lib/types/client/minimax-code/locales.d.ts.map +1 -1
  31. package/lib/types/client/minimax-code/useMinimaxCodeSection.d.ts.map +1 -1
  32. package/lib/types/client/ollama/locales.d.ts +18 -0
  33. package/lib/types/client/ollama/locales.d.ts.map +1 -1
  34. package/lib/types/client/progress-updates/index.d.ts.map +1 -1
  35. package/lib/types/client/step-code/locales.d.ts +11 -2
  36. package/lib/types/client/step-code/locales.d.ts.map +1 -1
  37. package/lib/types/client/step-code/useStepCodeSection.d.ts.map +1 -1
  38. package/lib/types/client/workbuddy/locales.d.ts +6 -0
  39. package/lib/types/client/workbuddy/locales.d.ts.map +1 -1
  40. package/lib/types/client/workbuddy/useWorkBuddySection.d.ts.map +1 -1
  41. package/lib/types/host/antigravity/account-pool.d.ts +17 -3
  42. package/lib/types/host/antigravity/account-pool.d.ts.map +1 -1
  43. package/lib/types/host/antigravity/adapter.d.ts +7 -2
  44. package/lib/types/host/antigravity/adapter.d.ts.map +1 -1
  45. package/lib/types/host/common/context-overflow.d.ts.map +1 -1
  46. package/lib/types/host/minimax-code/adapter.d.ts +14 -0
  47. package/lib/types/host/minimax-code/adapter.d.ts.map +1 -1
  48. package/lib/types/host/minimax-code/checkin-gateway.d.ts +36 -0
  49. package/lib/types/host/minimax-code/checkin-gateway.d.ts.map +1 -1
  50. package/lib/types/host/minimax-code/checkin.d.ts +60 -2
  51. package/lib/types/host/minimax-code/checkin.d.ts.map +1 -1
  52. package/lib/types/host/minimax-code/mapper.d.ts +51 -19
  53. package/lib/types/host/minimax-code/mapper.d.ts.map +1 -1
  54. package/lib/types/host/minimax-code/model-catalog.d.ts +40 -15
  55. package/lib/types/host/minimax-code/model-catalog.d.ts.map +1 -1
  56. package/lib/types/host/minimax-code/token-store.d.ts.map +1 -1
  57. package/lib/types/host/minimax-code/types.d.ts.map +1 -1
  58. package/lib/types/host/proxy-manager.d.ts.map +1 -1
  59. package/lib/types/host/workbuddy/account-pool.d.ts +24 -0
  60. package/lib/types/host/workbuddy/account-pool.d.ts.map +1 -1
  61. package/lib/types/index.d.ts +2 -2
  62. package/lib/types/index.d.ts.map +1 -1
  63. package/lib/types/shared/account-pool-contracts.d.ts +24 -0
  64. package/lib/types/shared/account-pool-contracts.d.ts.map +1 -1
  65. package/lib/types/shared/minimax-code-contracts.d.ts +17 -3
  66. package/lib/types/shared/minimax-code-contracts.d.ts.map +1 -1
  67. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,57 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.30.0 - 2026-10-20
6
+ - **[minimax-code] 请求的模型与实际回答的模型不一致时,这一轮立刻失败——而不是替用户按最贵档付费**
7
+ - **现象**:这条路由**不拒绝**它路由不了的模型 id。2026-10-20 实测:乱写串与空串都回 HTTP 200,实际由 `MiniMax-M3` 代答;同一个乱写 id 带 `reasoningEffort:'none'` 重发,回答的 M3 真的少了 thinking 块——是**真替换**,不是把名字写回去的壳。
8
+ - **为什么要紧**:我们的模型目录是硬编码的(`/v1/models` 对订阅流量是 503 `direct_route_not_configured`,那是路由裁决不是路径拼错)。若 MiniMax 下架了 M2.7 而用户仍选着它,请求**不会报错**,会被 M3 代跑——用户以为在用便宜模型,实际按最贵档计费(我们自己的实测:M3 的 max 档思考量约 3 倍、延迟约 4 倍)。**这是钱包问题,不是可用性问题**,而且**没有错误报文可依**。
9
+ - **改法**:[mapper.ts](../src/host/minimax-code/mapper.ts) 读流头的 `message_start.message.model`(此前只取 usage 与 stop_reason);[adapter.ts](../src/host/minimax-code/adapter.ts) 把它与**真正上线的那串 id** 比对(从即将发送的请求体读回,不是目录解析名),不一致则抛 `MODEL_SUBSTITUTED`。
10
+ - **判定点与代价**:判定在 `processMinimaxStreamLine` 之后、**任何 `yield` 之前**——`message_start` 是流的第一个事件,排在所有 `content_block_start` 之前,所以**没有任何内容块送达调用方**,无需回收、不会有重试重复输出。新码**不在** `retryableCodes` 里:重发同一 body 只会再被替换一次,并按替身计费。
11
+ - **验证**:`npm run typecheck` 0 错误;全量 **250 passed | 1 skipped(251 文件)、4050 passed | 7 skipped、0 failed**;`npm run build` 通过。新增 5 条守卫(一致 / 不一致且零内容块 / 无 model 字段 / 大小写双向 / mapper 层单测)。
12
+ - **[minimax-code] 每日签到锁定在它开始的那一个账号上:池行被重绑就重新认,attempt 中途换号就拒绝**
13
+ - **现象**:签到读面板用 A 的 `realUserID`、领取用 B 的凭据是可能的——网关把 `accessToken` 与 `realUserID` 当两个独立入参,彼此没有绑定校验,而 `realUserID` 是按池行缓存的。
14
+ - **两个真实窗口**:① 状态文件钉着 A、池里那一行已被换成 B;② 401 强制续期后 `renewCredential` 返回**另一个账号**的凭据,重试即以别人的账号领取。
15
+ - **两者性质不同,处理也不同**:① **重绑是合法的状态变化**——一行 = 插件替用户管理的一个账号,跟着走才是对的,所以**用当前凭据重新解析身份、重新钉住、照常签到**(老状态文件也会在第一次运行时自愈);② **attempt 中途的漂移与 401 续期换号必须硬拒绝**(`MinimaxCodeCheckinAccountMismatchError`),不自愈。
16
+ - **记一个容易骗人的地方**:`native:<region>:<recordKey>` 里的 `recordKey` 是**记录槽位**键,**不是账号 id**——用户在桌面端把另一个账号签进同一槽位时它不变。所以它只是「尽力而为的护栏」,这条线账号的**权威标识**是从身份接口解析出来的 `realUserID`。
17
+ - **验证**:签到相关 5 套件 **113 passed**;两次**变异验证**(关掉钉校验 → 两条用例红;关掉漂移守卫 → 漂移用例红),均已还原。
18
+ - **[minimax-code] 额度耗尽改按上游数字码判定,措辞表留作兜底;并纠正我们文档里的过度声称**
19
+ - **改法**:[adapter.ts](../src/host/minimax-code/adapter.ts) 的 `statusCodeIn` 读三处信封(`base_resp.status_code` → `errorCode` → **顶层 `code`**),命中上游的 `2056`/`2067` 即判账号级额度耗尽;403 与 429 两个入口读**同一个**谓词、汇进**同一个**判决,形状天然一致。**2013 明确排除**(通用「参数非法」码,本仓库在 reset-card 与 Files API 上另有语义)。
20
+ - **实测订正**:Messages 路由的错误信封**没有 `base_resp`**——400 是 `{type,error{type,message}}`(无任何数字字段),401 是 `{code,message}`。所以这条路由上**当前真正 operative 的仍是措辞表**,数字码臂是**跨信封的前向兼容臂**。README 里那段把它写成首要判据的话已改正。
21
+ - **验证**:新增 8 条(数字码 / 措辞 / 两者同形 / 2013 不判 / 顶层 401 不判 / 流内继承),邻近 6 套件 133 passed。
22
+ - **[minimax-code] 令牌时长从「约 24 小时」纠正为「约 1 小时」,设置卡改用分钟显示**
23
+ - **现象**:卡片上写着访问令牌约 24 小时有效,而实测是**一小时**(`types.ts`)——一句用户会读到的错话;余量又以「小时」报,一小时寿命的令牌因此看不出 Host 何时会续期。
24
+ - **改法**:`remainingHours` → `remainingMinutes`,单位改分钟(恒定一位小数,不足一分钟视为已过期),中英文案同步改为「约 1 小时」。
25
+ - **[minimax-code] 能力说明与上游差分归档:七处过期表述 + 活体探针 + 差分文档**
26
+ - **代码早于文档**:Files API 早就实现了(上传 / 12 小时 TTL / 账号隔离 / 去重缓存),视频也早已声明,而 `model-catalog.ts` 的三处注释与 README 仍在描述实现之前的世界——这会让人误判这条线路的能力边界,包括下一次做差分分析的人。
27
+ - **新产物**:`scripts/probe-minimax-capabilities.probe.ts`(16 个 case 的活体探针)与 `docs/minimax-code-probe-capabilities.md`(原始证据),以及 `docs/minimax-code-upstream-delta-v2.md`(本轮上游差分、明确否掉的项、待办)。
28
+ - **顺带量出**:服务侧 `max_tokens` 真阈值是 **524288**(128000 / 512000 都接受,999999999 回 400)——我们写的 128000 是保守值(与上游兜底表一致),**数值未改**;并确认那条 400 文案**不会**误触发上下文超限检测,不存在「压缩→重试→再压缩」的死循环。
29
+ - **[Codex 搜索] 读出活体搜索真正的答案字段,并给代理一个连接超时**
30
+ - **现象**:活体 `/alpha/search` 的回答在顶层 `output` 字段,而 `readContent` 只看 `content` / `output_text` / `summary`——于是每次真实搜索都**没有答案正文**,工具只报一串来源,读起来像「搜索能用,只是没搜到东西」。
31
+ - **代理**:undici 默认 10s 连接超时覆盖的是到**源站**的 TLS 握手而非到代理的那一跳;实测某条到 chatgpt.com 的慢路由仅握手就 1.2s–18s、请求整体 31s,默认值把本该几秒后成功的请求杀掉,而且是以一句不点名的 `fetch failed` 杀掉。代理 agent 改为 60s 连接超时,并用 undici 真正接受的 options 对象形式传入(第二个位置参数会被静默丢弃)。
32
+ ## 0.29.0 - 2026-10-19
33
+ - **[Antigravity] 429 不再被当成「配额耗尽」:裸 429 不冷却账号、不换账号,也不再把上游原因藏起来**
34
+ - **现象**:账号配额还很充足(实测 Gemini 5h 桶剩余 95.95%、周桶剩余 84.67%,Claude/GPT 100%),插件却报「账号配额已耗尽」,并把该账号的 Gemini 配额族冷却 15 分钟;此后每次重试都被账号池直接挡下,连一次网络请求都不发。
35
+ - **根因一(端点链)**:[adapter.ts](../src/host/antigravity/adapter.ts) 与 [image-tool.ts](../src/host/antigravity/image-tool.ts) 的端点候选循环只在 ok/400/404 时中断,主端点失败后会落到兜底端点。实测 `https://daily-cloudcode-pa.googleapis.com` 对 Antigravity 请求稳定 200,而 `https://cloudcode-pa.googleapis.com` 对**每一个** Antigravity 请求都回 429 `{"error":{"code":429,"message":"Resource has been exhausted (e.g. check quota).","status":"RESOURCE_EXHAUSTED"}}`——没有 details、没有 Retry-After。这个 429 于是成了「被上报的那一次失败」。
36
+ - **根因二(裸 429 被当成了配额结论)**:该 429 正文里没有任何重置时间,`retryDelayMs()` 返回 undefined,旧代码回落到写死的 15 分钟冷却并把它记成「配额已用尽」。**配额结论需要证据**:裸 429 只说明上游拒绝了这一次请求,不说明这个账号的 Gemini 或 Claude/GPT 额度用完了。
37
+ - **改法一(端点链)**:429 立即结束本次尝试的端点链——一个端点给出的配额结论不会因为换端点而变好,而兜底端点对每个 Antigravity 账号都回 429。**被上报的是第一个应答端点的结果**,除非后面的端点成功(ok)或回答 400/404(这两条中断条件原样保留);所以「主端点 5xx + 兜底端点 429」现在报的是那个 5xx,而不是兜底端点的 429。
38
+ - **改法二(裸 429 不等于配额耗尽)**:429 给出了重置时间(quotaResetTimeStamp / quotaResetDelay / RetryInfo.retryDelay / Retry-After)时行为完全不变:按上游给的时长 `markFamilyCooldown(account.id, family, statedWaitMs, '429 Rate Limit')`、有下一个账号就换账号、最终报 `LlmError(..., 'RATE_LIMIT', { status: 429 })`。**没有重置时间的裸 429 不再冷却账号、不再换账号**,改为可重试且与账号无关的 `LlmError(..., 'SERVER', { status: 429 })`——`SERVER` 在 DSH 默认重试集合内,也是本仓库「上游说不、但不是账号的错」的既有措辞(见 `src/host/kimi-code/adapter.ts` 的 upstreamOverload)。文案直说「上游返回 429 但未给出重置时间,因此按瞬时限流处理,不视为账号配额耗尽」,并附上截断后的上游原始响应。图片工具同理:`ANTIGRAVITY_IMAGE_RATE_LIMITED` 保留,但裸 429 不再调用 `markFamilyCooldown`,文案也不再宣称配额已用尽。
39
+ - **改法三(上游原话随冷却一起留存)**:[account-pool.ts](../src/host/antigravity/account-pool.ts) 的 `markFamilyCooldown()` 多一个可选的 `detail`,冷却条目把它和 `until`/`reason` 放在一起——**上游的原话跟着它自己造成的冷却一起走**:[adapter.ts](../src/host/antigravity/adapter.ts) 与 [image-tool.ts](../src/host/antigravity/image-tool.ts) 在为配额型 429 记冷却时,把截断后的上游响应体作为 `detail` 一起写进去。`getEffectiveAccountFor` 保留「哪一族在冷却、最短还要几分钟」的措辞,**「上游返回:」后面引的是最先解除的那条冷却所带的 detail**(与那个「最短预计 N 分钟」指的是同一条,两者不会各说各话);**没有任何 detail 时整句都不加**——共享内核抛出的那句「全部 N 个 Antigravity 账号均处于配额限制或冷却中」说的是**号池状态**而不是上游,它只作为 cause 链留在错误里,绝不会被贴上「上游返回」的标签。
40
+ - **验证**:`npm run typecheck` 0 错误;全量 **247 passed | 1 skipped(248 文件)、4016 passed | 7 skipped、0 failed**。新增 7 条回归守卫(adapter 4 条、image-tool 1 条、account-pool 2 条),并给既有的图片 429 用例加了两条断言(「429 只问一次」「上游原话随冷却一起写」);**变异验证**:把这 3 个源码文件回退到改前版本后 6 处变红;再把「上游返回:」改回引号池状态那句话、或让 adapter 不再把上游响应体交给冷却,account-pool 与 adapter 的 3 条当场变红,恢复后全绿。
41
+ - **[账号池] 冷却剩余时间改成秒级实时倒计时,并把「账号 × 模型」的停用窗口显示出来**
42
+ - **现象**:账号卡片上的「冷却中(剩 5 分)」是一个**静止的数字**——它在页面绘制那一刻算一次就再也不动了。5 分钟的冷却,用户会看着「剩 5 分」整整 5 分钟。同一行里的「重置冷却」按钮点掉之后,徽章还在(它跟号池状态不是一回事),而真正让这个号不可用的是「某个模型被停用」这件事,界面上**一个字都没有**。
43
+ - **改法一(实时倒计时)**:[AccountPoolSection.tsx](../src/client/common/AccountPoolSection.tsx) 里 `now` 从渲染时取的 `Date.now()` 变成 React state,由一个 1 Hz 的定时器推进。**定时器只在真有东西在冷却时才跑**(`counting` 开关),没有冷却的设置页不会为此每秒唤醒一次标签页;卸载时清理。徽章显示真实剩余时间:整分钟用原措辞,分钟+秒用新模板,不足一分钟**只显示秒**——「剩 1 分」撑最后 59 秒是假的。冷却结束,徽章**自己消失**,不用等下一次刷新。
44
+ - **改法二(模型停用行)**:WorkBuddy 的订阅按**账号 × 模型**计量 24 小时窗口(429 `code 6004`),所以被限的是**那个模型**、账号本身还能服务别的模型。号池里这份状态原来只活在内存里,接口一个字都不吐,界面因此把号显示成可用。现在它进账号摘要 DTO([account-pool-contracts.ts](../src/shared/account-pool-contracts.ts) 的 `modelCooldowns`),卡片为每个被停用的模型单开一行,**标明模型名并给它自己的倒计时**;账号级徽章与模型行是**两件不同的事**,不混为一谈。
45
+ - **改法三(重置冷却真的清干净)**:号池的 `clearCooldown` 现在**连该账号的模型停用一起清掉**。留着它等于报告了一次没有发生的重置,而且那份状态用户根本没有别的手段能清。
46
+ - **改法四(WorkBuddy 卡片补轮询)**:[useWorkBuddySection.ts](../src/client/workbuddy/useWorkBuddySection.ts) 补上兄弟标签页都有的 60 秒可见态轮询。此前它只在标签页重新可见时刷新,于是**已经解除的冷却、已经出现的模型停用会一直挂在屏幕上**,直到手动刷新页面。
47
+ - **验证**:`npm run typecheck` 0 错误;全量 **247 passed | 1 skipped(248 文件)、4009 passed | 7 skipped、0 failed**;`npm run build` 通过。新增 5 条回归守卫(逐秒倒计时并到点自行消失、模型行渲染与缺席、WorkBuddy 60 秒重读、共享标签纪律扩展到新键、号池摘要与状态路由透出模型停用)。
48
+ - **[进展更新] 默认开启,并补上「宿主服务晚到」时丢失的右侧栏入口**
49
+ - **现象一(多数用户)**:「进展更新」的开关默认是**关**的。没手动开过的用户装上插件后:`progress_update` 工具的 `execute` 一直返回 `displayed: false`,系统提示词里没有引导节,8 轮催促也不触发——功能整套静默不工作,而设置页里那个开关是唯一线索(截图里它是关的)。
50
+ - **现象二(部分用户)**:右侧栏 guide 里那一格「进展更新」**整格不出现**。页签类型是在本插件 `apply` 的那一刻同步向 `ctx.get('sidebarRightTabs')` 读的;宿主 `ui-sidebar-right` 若比本插件晚上线,那次读回来的是 `undefined`,注册被整段跳过——面板注册齐全但右侧栏空着,用户根本找不到入口(`test/progress-updates-client.test.tsx` 里那条「guide 入口缺失」的回归守卫就是为这个写的)。
51
+ - **改法一(默认开启)**:[src/index.ts](../src/index.ts) 的 `progressUpdates` 由 `default(false)` 改为 `default(true)`;[src/host/progress-updates/index.ts](../src/host/progress-updates/index.ts) 的 `resolveProgressUpdatesSettings` 判定由 `enabled === true` 改为 `enabled !== false`(**缺省即开启**,只有显式写 `false` 才关);[settings-section.tsx](../src/client/progress-updates/settings-section.tsx) 的开关展示同步按「缺省即开启」——宿主还没答上来时显示为**开**而不是关。关掉的能力没有丢:设置页写 `false` 立刻生效(字段是 `volatile()`,不重挂插件行)。
52
+ - **改法二(晚到的服务自动补注册)**:[src/client/progress-updates/index.ts](../src/client/progress-updates/index.ts) 里,服务在位时仍**就地注册**(同步,行为不变);服务缺席但上下文支持 `ctx.inject` 时,改用 `ctx.inject(['sidebarRightTabs'], ...)` **等它上线时补注册**页签类型与 guide 入口。取服务时先试 scope 的属性读(scope 的 inject 名单里有它,合法)、失败再退回 `ctx.get()`——本插件自己的静态 `inject` 没有这个服务名,直接读属性会被 Cordis 拒绝。设置表单 `configForms` 晚到时同样按需重取,设置页不再永久停在只读态。
53
+ - **没有踩的坑**:`ctx.inject` 在 Cordis 里会新建一个 fiber,服务永不上线时它就一直等着,**不影响**本插件其余部分的挂载;因此没有把 `sidebarRightTabs` 写进本插件的静态 `inject` 名单(那会让整个插件在没装 `ui-sidebar-right` 的部署上挂不上)。
54
+ - **验证**:`npm run typecheck` 0 错误(`tsc -b --force` + `tsc -p test/tsconfig.json`);全量 **247 passed | 1 skipped(248 文件)、4006 passed | 7 skipped、0 failed**;`npm run build` 通过。新增/更新 5 条回归守卫,并做**变异验证**:临时回退这 4 个源码文件后,其中 5 条全部变红(默认开启 3 条、侧栏服务晚到 1 条、设置服务晚到 1 条),恢复后全绿。
55
+
5
56
  ## 0.28.0 - 2026-10-19
6
57
  - **[MiniMax Code] 模型目录跟上正式版:`MiniMax-M3.1-Flash` 取代 `MiniMax-M3.1-Flash-Preview`,并给存量用户留了一条迁移路**
7
58
  - **上游改名了**:MiniMax 于 2026-10-19 发布正式版 `MiniMax-M3.1-Flash`(官方 Model Invocation 页的 Supported Models 表已不再带 `-Preview` 后缀,1,000,000 上下文)。我们随包的硬编码目录里那条 `MiniMax-M3.1-Flash-Preview` 已经不是上游当前的名字。
package/README.md CHANGED
@@ -191,8 +191,17 @@
191
191
  - 该端点的 `GET /v1/models` **未对订阅流量开放**(503 `"{\"errorCode\":50115,\"errorReason\":\"direct_route_not_configured\"}"`),所以任何「实时目录」都只会是一个必然失败的请求;
192
192
  - 四款模型:M2.7 / M2.7 HighSpeed / M3 取自本机 `~/.minimax/config.yaml` 的模型表;**M3.1 Flash 是 2026-10-19 换上的正式版 id**(`MiniMax-M3.1-Flash`,去掉了 `-Preview` 后缀),取自 MiniMax 自己的 Model Invocation 页 Supported Models 表并在该订阅端点上实测(正式 id、`output_config.effort` 的 low/max、内联图片)。无论哪一条,**都不从不存在的接口推断**;
193
193
  - **思考形态按模型表三态实现,且一律走 `output_config`,不发 `thinking` 对象**(该字段从未被文档确认,发出去只是一种形状的猜测):M2.7 系列**恒定开启且没有档位**(不发送任何字段);M3 是**开关二态**(关闭时 `output_config.effort: 'none'`);M3.1 Flash **强制开启并可指定档位**(`output_config.effort` 取 low/medium/high/xhigh/max,省略即 max)。未知或越档的取值一律回落到该模型目录声明的默认档,不会把模型没有声明的档位发出去;
194
- - **视频不在能力声明里**:模型表虽然给 M3 / M3.1 标了视频输入,但 DSH 的附件服务只存图片、本线路也没有安装视频字节读取器,映射器只能把它替换成一条说明文本。**声明一个做不到的能力比不声明更糟**(DSH 的能力闸门、模型选择器与子代理委派都会当它成立),所以这里只声明 `text` 与 `image`;要加 `video` 必须先有真正的读取器(对照 Kimi Code 线路的 `video-store.ts`);
195
- - **瞬时失败按错误类别重试**(`src/host/minimax-code/adapter.ts`):上游模型供应商临时不可用(5xx)归为 `SERVER`、429 归为 `RATE_LIMIT`、连接未产出响应归为 `TRANSPORT`,走 DSH retry policy 的有界退避(最多 3 次,1.5s 起步、15s 上限、0.2 抖动);**但 429 的正文若说的是余额/额度耗尽,则判为终局**——重试只会推迟用户真正需要看到的提示;
194
+ - **视频已在能力声明里,并且真的能发出去**:M3 与 M3.1 Flash 的 `inputModalities` 都是 `['text', 'image', 'video']`(`model-catalog.ts`),线上形状取自实测——`{ type: 'video', source: { type: 'base64', … } }` 被该端点接受并解码(同一段实测也否掉了 data-URL 字符串那种封装,它会在 `:` 处按裸 base64 解码失败)。**能不能读到字节是宿主注入的接缝**:`AttachmentVideoReader`(`adapter.ts:372`,类型在 `src/host/common/video-request.ts`)由宿主提供,DSH 自己的附件服务只存图片,所以视频只能来自本部署里某个真实的生产者;宿主不提供读取器、或某一条读不出来时,`resolveRequestVideos` 把它解析成 `unavailable`,映射器(`mapper.ts:416`)才退化成一段说明文本——`modalities.ts` 的 `videoOmissionText` 按 4 种 reason 给出各自不同的措辞(`unsupported-model` / `unsupported-container` / `unreadable` / `unsupported-wire`),让模型知道「有这段视频、为什么没带上」,而不是对着空白回答。**降级发生在单个片段上,不发生在声明上**:声明的依据是接缝存在且线上形状实测可用,不是「任何情况下都一定能读到」;
195
+ - **失败按 8 级判决分类,判词决定重试与号池动作**(`classifyMinimaxFailure`,`adapter.ts:388-470`;读的是**原始正文**而不是 `summarizeFailureBody` 抽出的那一个字段,因为同一个判词在两种信封里放的位置不同):
196
+ - **上下文超限**——`isHttpContextOverflow` 先判,`CONTEXT_OVERFLOW_CODE`,终局、不归账号;
197
+ - **账号级额度耗尽**——**`403` 或 `429`** 都可能落到这一类,判据是正文**点名了一个用尽的窗口**,与状态码无关:`isSpentWindow403`(`adapter.ts:355`)管 403,`429` 分支(`adapter.ts:425-431`)用同一个谓词问同一个问题,两条都返回**同一个** `quotaExhaustedFailure`(`adapter.ts:368`:`PROVIDER_ERROR` / `retryable: false` / `accountScoped: true`)。**判据本身是两个臂**(先问上游用量限额的**数字码**、再问**措辞表**兜底;这个先后只是求值顺序,不代表这条路由上真正会开火的是前者):`namesUsageLimitStatusCode`(`adapter.ts:311`)读两种信封里的上游码(平台信封 `base_resp.status_code`、客户端信封 `errorCode`,并额外往流内错误体 `{ error: … }` 里多读一层),命中 `USAGE_LIMIT_UPSTREAM_STATUS_CODES = {2056, 2067}` 即算;非 JSON 或没有该字段的正文回落到措辞表 `QUOTA_EXHAUSTED_PATTERNS`。两个臂汇进同一个判决 `namesSpentWindow = namesUsageLimitStatusCode || 措辞表`(`adapter.ts:334`)——它们不是两套规则,而是同一个问题的两种承载方式:一句中文没有码,一个裸码没有句子。**但要把定位说准**:数字码臂在这条 Messages 路由上目前**不会开火**,它是**跨信封的前向兼容臂**(哪天服务真的在 Messages 路由上带出 `2056`/`2067`/`errorCode`,它就会生效),**不是这条路由当前的主要判据**。2026-10-20 的活体探针(`scripts/probe-minimax-capabilities.probe.ts`,原始证据 `docs/minimax-code-probe-capabilities.md`)把这条路由的错误信封量了个遍:端点上只有**两种**信封,**没有一种带 `base_resp`**——① 请求被拒 `400` 是 `{"type":"error","error":{"type":"invalid_request_error","message":"…"}}`(参数类错误上多一个 `request_id`),**没有任何数字字段**;② 认证被拒 `401` 是 `{"code":401,"message":"auth failed"}`,**顶层 `code` 是这条路由唯一的数字字段**。`base_resp.status_code` 那个信封属于这条线路的**别的**路由(配额 `coding_plan/remains`、Files API、`reset-card`),不属于 Messages。连探针抓到的 `2013` 也**不是字段**:它只以文本形式嵌在 `error.message` 的括号里(`… does not support max tokens > 524288 (2013)`),而且是**通用「参数非法」码**——超限的 `max_tokens`、空 body、畸形 JSON 全都归它;本仓库在 `reset-card` 与 Files API 上另有语义,**绝不拿它当额度判决**。所以在 Messages 路由上**当前真正 operative 的那一条是措辞表**:数字码臂留在这里,是为了不让一个将来可能带上数字码的信封被当成措辞去猜,而不是因为这条路由现在会给码。它必须排在下面的凭据分支**之前**,因为 `401`/`403` 的正文在这条线上是同一句 `token is required`,光看状态码分不出「窗口用尽」与「凭据被拒」。号池据此冷却到窗口重置(未命中周窗口词冷却 5 小时,命中 `WEEKLY_QUOTA_WINDOW_PATTERN` 冷却 7 天,见 `adapter.ts:172-184`),且**刻意不采信 Retry-After**——等 N 秒不会让一个用尽的窗口重新可用;
198
+ - **凭据失效**——`401`/`403` 里不属于上面的,`INVALID_CREDENTIAL`,终局且归账号,但走 `markAuthFailed` 而不是 `markCooldown`;这条线的 401 正文在「没发凭据」和「凭据被拒」两种情况下是同一句 `token is required`,所以提示直接指向重新登录;
199
+ - **纯背压 429**——**没有**点名用尽窗口的 429(数字码与措辞两个臂都没命中),`RATE_LIMIT`,**可重试**且**不归账号**(同一套餐后面的每个账号都会被同样拒绝,冷却它只会把号池抽干);
200
+ - **5xx**——`SERVER`,可重试、不归账号;
201
+ - **400**——`PROVIDER_ERROR`,终局:请求本身不被接受,重发一次也不会被接受;
202
+ - **其他状态码**——`PROVIDER_ERROR`,终局,正文为空时提示「无响应」;
203
+ - **401 另有一次性补救**(合并下方一条):第一次 401 会**强制续期一次并原样重试同一个请求体**,重试再被拒才是终局;
204
+ - 可重试的四类(`RATE_LIMIT` / `SERVER` / `TIMEOUT` / `TRANSPORT`)走 DSH retry policy 的有界退避:最多 3 次,1.5s 起步、15s 上限、0.2 抖动;**终局类不重试**——重试只会推迟用户真正需要看到的那句提示;
196
205
  - **401 会强制续期一次再重试**:本地时钟看着还有效、服务端却拒收的令牌(在桌面端被撤销、时钟偏移、桌面端已轮换)与「真的需要重新登录」在状态码上无法区分,所以第一次 401 会**强制刷新一次并原样重试同一个请求体**;重试再被拒才是终局;
197
206
  - **刷新令牌是单次使用的,因此续期提前 5 分钟、并全进程共用一次轮换**:
198
207
  - 这是「每小时自己掉线、然后要求重新登录」的根因。access token 实测只有 **1 小时**,而原先的续期阈值是「到期前 60 秒」——等于每次都在令牌已经会被拒收的那一瞬间才去轮换;