@eddyskywalker/dsh-chatgpt-subscription 0.42.0 → 0.43.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 (45) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +1 -0
  3. package/lib/client.js +180 -144
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +620 -216
  6. package/lib/types/client/command-code/CommandCodeComposerQuota.d.ts.map +1 -1
  7. package/lib/types/client/common/format.d.ts +16 -0
  8. package/lib/types/client/common/format.d.ts.map +1 -1
  9. package/lib/types/client/kimi-code/KimiCodeComposerQuota.d.ts.map +1 -1
  10. package/lib/types/client/kimi-code/KimiCodeSection.d.ts.map +1 -1
  11. package/lib/types/client/kimi-code/KimiQuotaCard.d.ts.map +1 -1
  12. package/lib/types/client/kimi-code/styles.d.ts.map +1 -1
  13. package/lib/types/client/locales-ru/opendesign.d.ts.map +1 -1
  14. package/lib/types/client/opendesign/OpenDesignSection.d.ts.map +1 -1
  15. package/lib/types/client/opendesign/locales.d.ts +12 -0
  16. package/lib/types/client/opendesign/locales.d.ts.map +1 -1
  17. package/lib/types/compat.d.ts +1 -1
  18. package/lib/types/host/claude/types.d.ts +7 -1
  19. package/lib/types/host/claude/types.d.ts.map +1 -1
  20. package/lib/types/host/glm-coding-plan/types.d.ts +5 -1
  21. package/lib/types/host/glm-coding-plan/types.d.ts.map +1 -1
  22. package/lib/types/host/kimi-code/account-pool.d.ts +26 -1
  23. package/lib/types/host/kimi-code/account-pool.d.ts.map +1 -1
  24. package/lib/types/host/kimi-code/client.d.ts +24 -0
  25. package/lib/types/host/kimi-code/client.d.ts.map +1 -1
  26. package/lib/types/host/kimi-code/oauth.d.ts +16 -0
  27. package/lib/types/host/kimi-code/oauth.d.ts.map +1 -1
  28. package/lib/types/host/kimi-code/routes.d.ts.map +1 -1
  29. package/lib/types/host/opendesign/adapter.d.ts +11 -13
  30. package/lib/types/host/opendesign/adapter.d.ts.map +1 -1
  31. package/lib/types/host/opendesign/catalog.d.ts +14 -0
  32. package/lib/types/host/opendesign/catalog.d.ts.map +1 -1
  33. package/lib/types/host/opendesign/line.d.ts +17 -6
  34. package/lib/types/host/opendesign/line.d.ts.map +1 -1
  35. package/lib/types/host/opendesign/routes.d.ts.map +1 -1
  36. package/lib/types/host/opendesign/token-store.d.ts +91 -4
  37. package/lib/types/host/opendesign/token-store.d.ts.map +1 -1
  38. package/lib/types/host/opendesign/types.d.ts.map +1 -1
  39. package/lib/types/host/qoder/types.d.ts +6 -1
  40. package/lib/types/host/qoder/types.d.ts.map +1 -1
  41. package/lib/types/host/step-code/types.d.ts +5 -1
  42. package/lib/types/host/step-code/types.d.ts.map +1 -1
  43. package/lib/types/shared/opendesign-contracts.d.ts +24 -1
  44. package/lib/types/shared/opendesign-contracts.d.ts.map +1 -1
  45. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,58 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.43.0 - 2026-10-27
6
+
7
+ - **[opendesign] MiMo V2.6 与 Qwen3.7 Max 拿回思考深度选择**
8
+ - **现象**:OpenDesign 线路里 MiMo V2.6 Flash / Pro 的模型下拉没有任何思考档位;按同一原则把 37 行逐条比对后,`qwen3.7-max` 也一样缺。
9
+ - **为什么**:这三行的能力表里写着 `reasoning: false`,依据是 command-code 注册表把同族模型的档位标注为空。但「空档位」只说明没有来源声明档位,不等于模型不思考——仓库自己的规则(`docs/opendesign-line-plan.md` §3 决策 8)就是「无档位声明的模型沿用默认三档」;而同族的 cline-pass 行(`mimo-v2.5`、`mimo-v2.5-pro`、`qwen3.7-max`)明确写作 `reasoning: true`,§2 规定两表冲突时 cline-pass 的 DSH 拼写胜出。
10
+ - **改法**:三行去掉 `reasoning` 字段,落到 `DEFAULT_EFFORTS` 的 `low / medium / high`;其余 34 行一字未动。
11
+ - **测试**:`test/opendesign-catalog.test.ts` 与 `test/opendesign-adapter.test.ts` 钉住三行的 `reasoning === true` 与三档档位。
12
+
13
+ - **[opendesign] 套餐不提供的模型不再出现在模型选择器里;设置卡里可见、可强制显示**
14
+ - **现象**:套餐里根本没有的模型照样出现在选择器中,选中后只会换来一次拒绝。
15
+ - **为什么**:网关 `GET /v1/models` 返回的其实就是「这个 Key 的套餐真正提供的 id」,但同步只把它合并进 `catalogModels`,**没有把成员关系记住**,于是投影时无从判断。
16
+ - **改法**:设置文档新增三态的 `servedModelIds`(`null` = 从未测量,不隐藏任何模型;数组 = 最后一次成功扫描的**并集**),同步改为遍历**所有可用账号**(每次最多 8 个)取并集,任一账号失败只损失它自己那一半,全部失败则保留旧值而不是清空;适配器与状态投影共用 `line.ts` 的唯一一份可见性规则(此前 hub 卡片自己另算一份,会把套餐外模型算进「可用」)。设置卡照常列出这些模型、hint 后缀「套餐不支持」,勾选即写入 `forceVisibleModels`(强制显示),取消勾选随选择集合一起裁剪。
17
+ - **三语**:`unservedBadge` / `unservedHint` 补进 zh、en、ru 三份字典。
18
+ - **测试**:新增 `test/opendesign-line.test.ts`(整条线挂载,hub 计数等于选择器条目数),并在 catalog / adapter / routes / model-settings / section 五个套件里补并集、拒绝一侧、从未测量、强制显示、`none` 清空与 `invert` 保留、老文档兼容等用例。
19
+
20
+ - **[opendesign] 设置文档的写入串行化,并发不再互相抹字段**
21
+ - **为什么**:每个 mutator 都从「读到的那份整文档」派生出整份新文档,两个并发写必然有一个人把对方刚写的字段抹掉;而目录同步是 fire-and-forget(加 Key、登录完成时都会触发),它真的能落在一次勾选的「读 → 写」之间。
22
+ - **改法**:`OpenDesignModelSettingsStore` 用一条 Promise 队列把 `write` / `update` / `storeCatalog` / `selectModels` / `setContextWindowOverride` 的整个读→算→写串起来,公开 `write()` 拆出 `writeUnlocked()` 供内部调用以免自锁;失败只 reject 自己的调用方、队列尾被压平,一次失败不会毒化后续写入;`read()` 仍在队列之外(热路径,且排队期间早先的写都已落盘)。
23
+ - **证据**:红-绿——去掉队列后,`storeCatalog` 与 `selectModels` 并发**确定性**丢字段(两次红丢的还是不同字段,正是「后写者整份覆盖」的样子),顺序性用例同时失败;加锁后连跑三次皆绿。
24
+
25
+ - **[opendesign] 长思考不再在 180 秒处被掐断**
26
+ - **现象**:一段较长的思考之后请求被中止,界面显示「已重试模型请求(2/3)」与 `The operation was aborted due to timeout`,最终「已停止」。
27
+ - **为什么**:`withAttemptTimeout` 把 `AbortSignal.timeout(180_000)` 挂到 fetch 的 signal 上**从不解除**。`AbortSignal.timeout` 是绝对计时,会跟着响应体(也就是整条思考流)走,于是它实际变成了「整条回答必须在 180 秒内结束」。本机会话记录里两次尝试分别流式了 155 秒与 173 秒,都精确地在请求发出后 180 秒整被掐断,随后重试两次同样死掉。
28
+ - **改法**:截止改为真正的**响应头截止**——用 `AbortController` + `setTimeout` 实现并在拿到响应头(成功或失败)时立即 `clear()`,之后流量的存活交给活动可重置的空闲看门狗(300 秒**静默**,每个 chunk 重置);新增 `headerTimeoutMs` 选项,使测试能用毫秒级预算验证这一区别。
29
+ - **同类排查**:全仓 85 处 `AbortSignal.timeout` / 自建 `withTimeout` / `setTimeout` / `Promise.race` 逐条核对后,只有 opendesign 这一条线路把绝对计时器挂在流式请求上(cline-pass 本来就在拿到响应头后 `clear()`,其余 10 条线路的流式路径没有绝对上限);四个从未被使用的 `CHAT_TIMEOUT_MS = 300_000` 常量补上注释,写明不得用于流式请求。
30
+ - **测试**:`test/opendesign-adapter.test.ts` 新增两条——截止之后才开始的流仍能跑完、响应头始终不来仍会放弃(已用红-绿证明)。
31
+
32
+ - **[kimi-code] 额度徽标不再偶尔显示一长串小数**
33
+ - **现象**:编写器旁的「额度 xx%」有时是 `91.96000000000001%` 这种数字,而且只在**部分**读数上出现。
34
+ - **为什么**:徽标显示的是「剩余」,算法是 `100 - usedPercent`。`100 - 8.04` 在二进制浮点里等于 `91.96000000000001`,而代码直接把这个差值插进字符串——两位小数的 used 里约 **三分之一** 会落在这种不友好的邻居上,所以看起来「有时候」;这不是显示精度没设够,四舍五入 used 也没用,必须在**算完之后**格式化。
35
+ - **改法**:`common/format.ts` 新增 `formatPercent()`(最多两位、去尾零、非有限数给 `0`),Kimi 徽标的数字与 tooltip 用同一份文本,等级仍按原始剩余量判定;Kimi 账号卡片里原有的 `percentText` 也改为复用它,两条渲染路径从此只有一个舍入规则。Command Code 徽标是同一写法,一并修掉;Antigravity / Claude / Cline Pass / GLM / MiniMax / Step 的徽标本来就先 `Math.round` 到整数,不受影响。
36
+ - **测试**:新增 `test/format-percent.test.ts`(含 `100 - 8.04`、负数、`NaN`、按位数截断)与 `test/kimi-code-composer-quota.test.ts`(`8.04 → 91.96%`、tooltip 同文本、等级仍按原始值、钱包回退、无数据破折号)。
37
+
38
+ - **[kimi-code] 设置页去掉底部的「用量与额度」整段:套餐、窗口与读取时间已经全部并进账号卡片**
39
+ - **为什么删**:那一块剩下的全是重复信息——套餐名是卡片的字标、额度窗口是卡片里那一条进度条、读取时间在卡片页脚;而它唯一还在解释的那句话(「这些事实只描述当前账号」)说明的是「页面级 vs 账号级」这个已经不存在分家。
40
+ - **保留两样没有别处可放的东西**,贴在账号列表下方:读失败的原因(`quotaError`)与「刷新用量」按钮。否则一次失败的用量读取在界面上就彻底没有出口——卡片只会安静地显示「尚无配额数据」。
41
+ - **代价**:加油包余额(`extraUsage`)随这一段一起消失,卡片里没有它的位置。有余额的账号若需要,应当把它挪进账号卡片而不是恢复整段。
42
+ - **测试**:`test/kimi-code-quota-ui.test.tsx` 改为断言页面上不存在该分段(标题、说明、余额文案都不再出现)、每一条进度条都在账号卡片内、重试按钮与失败原因都归属账号分组。
43
+
44
+ - **[kimi-code] 账号卡片终于能显示名字:登录时读 `/me` 取名,读用量时把昵称回写到号池**
45
+ - **现象**:卡片标题一直是「账号 2」这类占位。标题取的是号池行上的别名,而别名只在登录时按令牌铸造;**手机号登录的账号令牌里既没有邮箱也没有昵称**,于是 `defaultAlias` 只能回落到 `账号 N`,之后没有任何路径会再改它。
46
+ - **为什么以前修不掉**:昵称其实一直在读——`/me` 在每次读用量时都被调用——但它只被用来取档位名,取到的 `nickname` 只留在本次快照里;而 `fetchAccountQuota` 的「池化路径不写回」是**有意**的(号池的文件归号池写),结果是谁都没把昵称交给那张卡片。
47
+ - **改法**:(1) 登录在落库前读一次 `/me`,把昵称/邮箱/档位并进新凭据再存盘、再入池,新账号建行即有名字;(2) `fetchAccountQuota` 新增 `onAccount` 钩子,路由据此交给 `KimiCodeAccountPool.learnAccount()`,由号池在自己那把锁里写行;(3) 别名只在**仍是占位**(`账号 N` / `主账号`)时改成昵称,用户自己起的名字不动;(4) 重新登录不再把学到的名字冲掉(此前 `addAccount` 会用新令牌重铸别名)。
48
+ - **形状兼容**:`/me` 在不同网关下平铺或套 `data` / `data.userInfo` / `user` 都出现过,名字与邮箱现在逐层找,`parseUserInfo` 与 `accountFromCredentials` 不再各判一套。
49
+ - **不用重新登录**:已有账号打开设置页(或点「刷新用量」)就会走一次读用量,昵称随即回写。
50
+ - **测试**:新增 9 例——嵌套/平铺两种 profile 取词、`credentialsWithProfile` 不动令牌、`onAccount` 在池化路径被调用且失败不影响额度、`learnAccount` 的占位改名/手写别名保留/重登保留,以及两条端到端(登录取名入池、status 刷新改名)。
51
+
52
+ - **[kimi-code] 用量提示条对齐到它描述的那条进度条**
53
+ - **现象**:悬停提示固定右对齐,箭头又缩在离右边 34px 的位置——在 41% 满的条子上,气泡挂在最右端、箭头指着 92% 的空槽,说不上对着任何东西。
54
+ - **改法**:气泡改成压住「已用段末端」(`--kq-tip-x` 由百分比给出),靠近两端时按气泡宽度收回来、不越出卡片;箭头改用独立的 `--kq-tip-arrow` 偏移,哪怕气泡被推回来也仍指在已用段的末端。布局尚未发生时(首帧、jsdom)两者都退回 50% 居中。
55
+ - **测试**:`test/kimi-code-quota-ui.test.tsx` 新增 2 例——`--kq-tip-x` 必须等于已用比例、样式规则必须从左锚点定位且箭头不写死 `right:34px`。
56
+
5
57
  ## 0.42.0 - 2026-10-27
6
58
 
7
59
  - **[kimi-code] K3.1 Pro 与 Highspeed 的额度倍率由「未公布」改为记录 2x / 4x**
@@ -47,6 +99,7 @@
47
99
 
48
100
  ## 0.41.0 - 2026-10-26
49
101
 
102
+
50
103
  - **[kimi-code] 前缀缓存不再因为工具集变化整段重建:改走消息级工具声明,加/撤工具只动历史尾部**
51
104
  - **现象**:会话里一旦有插件中途增删工具(例如看板的 `expose`),下一次请求的前缀就整体失效。真实会话上抓到过一次:闲置 36 分钟后续跑,`prompt=622,735 / cached=1,280`,整段 62 万 token 重算;而它的下一条立刻又把 62 万读回来,说明问题不在拼前缀的方式,在"工具表变了就重写"。
52
105
  - **为什么能修**:这条线路的缓存按内容前缀匹配,而请求前缀的顺序是 system → tools → messages。把新工具放进顶层 `tools` 等于改前缀;把它作为 `messages[].tools` 追加到历史尾部则只增长尾巴。官方 kosong 把这个能力叫 `dynamically_loaded_tools`,本仓库的 `supportsDynamicTools` 就是它。
package/README.md CHANGED
@@ -1303,6 +1303,7 @@ MiniMax、Kimi、Codex、Claude、Command Code 和 Antigravity 的错误处理
1303
1303
  | Kimi Code 额度显示为空 | 卡片会同时给出失败原因(`/v1/usages` 的 401/403/5xx 文案),按提示处理后点「刷新用量」重试;确认用的是订阅账号——开放平台的 key 在这里不会被接受 |
1304
1304
  | Kimi Code 报「rejected the stored credential (401). Sign in again…」但发消息却是好的 | 这是**过期 access token**,不是被拒的账号:Kimi 的 access token 只有 **15 分钟**,而设置卡片过去会把号池里存着的那个 token 直接拿去查用量,因此只要 15 分钟内没走过一次 Kimi 请求,卡片就必然拿到 401。现在卡片会先刷新再请求(401 还会触发一次强制续期),因此**不必重新登录**;若卡片仍提示重新登录,才是真的刷新令牌被拒,按提示处理即可 |
1305
1305
  | Kimi Code 账号一栏为空 | 账号身份取自 OAuth token 自身的 JWT 声明,套餐名取自 `/me`(`/usages` 自 2026-09 起不再返回 `user_level_name`)。重新登录或点「刷新用量」即可写入;若仍为空但显示「已登录」,点「测试连接」可确认凭据是否仍被接受 |
1306
+ | Kimi Code 账号卡片标题是「账号 2」而不是名字 | 卡片的标题就是号池里那一行的别名。别名在登录时只能由令牌铸造,而**手机号登录的账号令牌里既没有邮箱也没有昵称**,所以过去一律回落到「账号 N」。现在登录会在落库前读一次 `/me`,读用量时也会把 `/me` 的昵称回写到该行,于是标题变成昵称(邮箱账号则是邮箱);**你自己改过的名字不会被覆盖**。已有账号点一下「刷新用量」(或重开设置页)即可改名,不必重新登录 |
1306
1307
  | Claude 相关接口返回 403 | 403 现在是**同源校验**的结果(修改状态的路由只接受同源 JSON POST)。若出现在浏览器里,检查是否从其它来源发起了请求;插件已不再有任何"确认后才能用"的门禁 |
1307
1308
  | 授权后粘贴授权码报「请把 # 后面的部分一起复制」 | 手动流程需要 `<授权码>#<state>` **整段**。只粘贴授权码是不被接受的:用流程自己的 state 顶上会架空 state 参数、把粘贴框变成登录 CSRF 的入口,因此插件宁可报错也不猜 |
1308
1309
  | loopback 回调收不到 / 端口绑定失败 | 端口探测失败会自动**降级为手动粘贴**(卡片会说明原因),照提示粘贴授权码即可。另:若浏览器把 `localhost` 解析到 `::1`,依赖浏览器的跨地址族回退可达本机 v4 监听,这是**假定**而非实测 |
package/lib/client.js CHANGED
@@ -146,11 +146,11 @@ window.__ModuleLoader__.load({
146
146
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
147
147
  className: "dsh-codex-composer-quota",
148
148
  "data-level": quota === null ? "normal" : quota.remainingPercent <= 5 ? "danger" : quota.remainingPercent <= 20 ? "warning" : "normal",
149
- "aria-label": quota === null ? t("quickQuotaLoading") : `${t("quickQuotaLabel")}: ${formatPercent$1(quota.remainingPercent)}`,
150
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("quickQuotaLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: quota === null ? loading ? t("quickQuotaLoading") : "—" : formatPercent$1(quota.remainingPercent) })]
149
+ "aria-label": quota === null ? t("quickQuotaLoading") : `${t("quickQuotaLabel")}: ${formatPercent$2(quota.remainingPercent)}`,
150
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("quickQuotaLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: quota === null ? loading ? t("quickQuotaLoading") : "—" : formatPercent$2(quota.remainingPercent) })]
151
151
  });
152
152
  }
153
- function formatPercent$1(value) {
153
+ function formatPercent$2(value) {
154
154
  return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 0 }).format(value)}%`;
155
155
  }
156
156
  //#endregion
@@ -1147,6 +1147,26 @@ window.__ModuleLoader__.load({
1147
1147
  if (value >= 1e3) return Math.round(value / 1e3) + "k";
1148
1148
  return String(value);
1149
1149
  }
1150
+ /**
1151
+ * A share as a person reads it: at most `digits` decimals, no trailing zeros.
1152
+ *
1153
+ * The formatting has to happen on the RESULT rather than on its inputs. Most
1154
+ * percentages on these cards are a remainder — `100 - used` — and that
1155
+ * subtraction is exactly where binary floating point shows up: `100 - 8.04` is
1156
+ * `91.96000000000001`, `100 - 0.0007 * 100` is `99.93` but `100 - 0.0014 * 100`
1157
+ * is `99.86` only because it happens to land on a friendly neighbour. String
1158
+ * interpolation of the difference therefore prints a long tail for roughly a
1159
+ * third of all two-decimal inputs, which reads as "sometimes" — and no amount of
1160
+ * rounding the used share beforehand fixes a subtraction done afterwards.
1161
+ *
1162
+ * Anything non-finite renders as `0`, because every caller has already decided
1163
+ * the window is measured; this is the last line of defence, not a claim.
1164
+ */
1165
+ function formatPercent$1(value, digits = 2) {
1166
+ if (!Number.isFinite(value)) return "0";
1167
+ const factor = 10 ** Math.min(6, Math.max(0, Math.trunc(digits)));
1168
+ return String(Math.round(value * factor) / factor);
1169
+ }
1150
1170
  /** A stamp in the reader's own locale, or an em dash when there is none. */
1151
1171
  function formatDate$1(ms) {
1152
1172
  if (ms === void 0 || ms === null || !Number.isFinite(ms) || ms <= 0) return "—";
@@ -5420,10 +5440,12 @@ window.__ModuleLoader__.load({
5420
5440
  const SHORT_PER_WEEK = 5;
5421
5441
  /** 5 小时窗口在月额度里的份数。 */
5422
5442
  const SHORT_PER_MONTH = 25;
5423
- /** 百分比,最多两位小数;非有限数不画。 */
5443
+ /** 提示条箭头离气泡两端的最小距离,免得圆角把三角切掉。 */
5444
+ const ARROW_INSET = 12;
5445
+ /** 百分比,最多两位小数;非有限数不画。舍入规则放在共享的 `formatPercent`。 */
5424
5446
  function percentText$2(value) {
5425
5447
  if (!Number.isFinite(value)) return "0%";
5426
- return `${Math.round(value * 100) / 100}%`;
5448
+ return `${formatPercent$1(value)}%`;
5427
5449
  }
5428
5450
  /** `YYYY-MM-DD HH:mm`,本地时区;令牌到期用它,年份不能省。 */
5429
5451
  function dateText(ms) {
@@ -5513,6 +5535,43 @@ window.__ModuleLoader__.load({
5513
5535
  const timer = window.setInterval(() => setClock(Date.now()), 3e4);
5514
5536
  return () => window.clearInterval(timer);
5515
5537
  }, [shortReset]);
5538
+ const tipPercent = Math.max(0, Math.min(100, totalPercent));
5539
+ const barRef = (0, react.useRef)(null);
5540
+ const tipRef = (0, react.useRef)(null);
5541
+ const [tipPlacement, setTipPlacement] = (0, react.useState)({
5542
+ shift: 0,
5543
+ arrowPx: null
5544
+ });
5545
+ (0, react.useEffect)(() => {
5546
+ const wrap = barRef.current;
5547
+ const tip = tipRef.current;
5548
+ if (wrap === null || tip === null) return void 0;
5549
+ const place = () => {
5550
+ const width = wrap.clientWidth;
5551
+ const bubble = tip.offsetWidth;
5552
+ if (width <= 0 || bubble <= 0) {
5553
+ setTipPlacement({
5554
+ shift: 0,
5555
+ arrowPx: null
5556
+ });
5557
+ return;
5558
+ }
5559
+ const half = bubble / 2;
5560
+ const anchor = width * tipPercent / 100;
5561
+ const clamped = Math.min(Math.max(anchor, half), Math.max(half, width - half));
5562
+ const arrow = Math.min(Math.max(anchor - (clamped - half), ARROW_INSET), bubble - ARROW_INSET);
5563
+ setTipPlacement({
5564
+ shift: Math.round((clamped - anchor) * 10) / 10,
5565
+ arrowPx: Math.round(arrow * 10) / 10
5566
+ });
5567
+ };
5568
+ place();
5569
+ if (typeof ResizeObserver === "undefined") return void 0;
5570
+ const observer = new ResizeObserver(place);
5571
+ observer.observe(wrap);
5572
+ observer.observe(tip);
5573
+ return () => observer.disconnect();
5574
+ }, [tipPercent]);
5516
5575
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5517
5576
  className: "dsha-kq",
5518
5577
  children: [
@@ -5564,6 +5623,8 @@ window.__ModuleLoader__.load({
5564
5623
  }),
5565
5624
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5566
5625
  className: "dsha-kq-barwrap",
5626
+ ref: barRef,
5627
+ style: { "--kq-tip-x": `${tipPercent}%` },
5567
5628
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5568
5629
  className: "dsha-kq-meter dsha-kq-meter--total",
5569
5630
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -5578,6 +5639,11 @@ window.__ModuleLoader__.load({
5578
5639
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5579
5640
  className: "dsha-kq-tip",
5580
5641
  role: "tooltip",
5642
+ ref: tipRef,
5643
+ style: {
5644
+ "--kq-tip-shift": `${tipPlacement.shift}px`,
5645
+ ...tipPlacement.arrowPx === null ? {} : { "--kq-tip-arrow": `${tipPlacement.arrowPx}px` }
5646
+ },
5581
5647
  children: t.kqTip.replace("{pct}", percentText$2(totalPercent))
5582
5648
  })]
5583
5649
  }),
@@ -5623,19 +5689,6 @@ window.__ModuleLoader__.load({
5623
5689
  //#endregion
5624
5690
  //#region src/client/kimi-code/KimiCodeSection.tsx
5625
5691
  const api$12 = createLineApi("/kimi-code/api", "Kimi Code");
5626
- /** Money the service reports in minor units, rendered in its own currency. */
5627
- function formatMoney(cents, currency) {
5628
- if (cents === null) return "—";
5629
- const code = currency ?? "USD";
5630
- try {
5631
- return new Intl.NumberFormat(void 0, {
5632
- style: "currency",
5633
- currency: code
5634
- }).format(cents / 100);
5635
- } catch {
5636
- return `${(cents / 100).toFixed(2)} ${code}`;
5637
- }
5638
- }
5639
5692
  /**
5640
5693
  * One token count as the card prints it.
5641
5694
  *
@@ -6001,7 +6054,7 @@ window.__ModuleLoader__.load({
6001
6054
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6002
6055
  className: "dsha-page",
6003
6056
  children: [
6004
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountPoolSection, {
6057
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(AccountPoolSection, {
6005
6058
  accounts: status?.accounts ?? [],
6006
6059
  activeAccountId: status?.activeAccountId,
6007
6060
  rotationStrategy: status?.rotationStrategy ?? "sequential",
@@ -6025,59 +6078,74 @@ window.__ModuleLoader__.load({
6025
6078
  now,
6026
6079
  labels: t
6027
6080
  }),
6028
- children: loginPending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6029
- className: "dsha-device-box",
6030
- children: [
6031
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.deviceCodeSection }),
6032
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6033
- className: "dsha-muted",
6034
- children: t.deviceCodeHint
6035
- }),
6036
- flow?.userCode && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6037
- className: "dsha-code-row",
6038
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6039
- className: "dsha-code",
6040
- children: flow.userCode
6041
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6042
- className: "dsha-btn",
6043
- onClick: () => void handleCopyCode(),
6044
- children: copied ? t.copied : t.copyCode
6045
- })]
6046
- }),
6047
- flow?.verificationUriComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
6048
- className: "dsha-link",
6049
- href: flow.verificationUriComplete,
6050
- target: "_blank",
6051
- rel: "noreferrer noopener",
6052
- children: flow.verificationUriComplete
6053
- }),
6054
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6055
- className: "dsha-row",
6056
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6057
- className: "dsha-label",
6058
- children: t.codeExpires
6059
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6060
- className: "dsha-value",
6061
- children: formatDate$1(flow?.expiresAt)
6062
- })]
6063
- }),
6064
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6065
- className: "dsha-actions",
6066
- children: [flow?.verificationUriComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
6067
- className: "dsha-btn",
6081
+ children: [
6082
+ loginPending && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6083
+ className: "dsha-device-box",
6084
+ children: [
6085
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.deviceCodeSection }),
6086
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6087
+ className: "dsha-muted",
6088
+ children: t.deviceCodeHint
6089
+ }),
6090
+ flow?.userCode && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6091
+ className: "dsha-code-row",
6092
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6093
+ className: "dsha-code",
6094
+ children: flow.userCode
6095
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6096
+ className: "dsha-btn",
6097
+ onClick: () => void handleCopyCode(),
6098
+ children: copied ? t.copied : t.copyCode
6099
+ })]
6100
+ }),
6101
+ flow?.verificationUriComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
6102
+ className: "dsha-link",
6068
6103
  href: flow.verificationUriComplete,
6069
6104
  target: "_blank",
6070
6105
  rel: "noreferrer noopener",
6071
- children: t.openSignInPage
6072
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6073
- className: "dsha-btn",
6074
- disabled: busy !== null,
6075
- onClick: () => void handleCancelLogin(),
6076
- children: t.cancelSignIn
6077
- })]
6106
+ children: flow.verificationUriComplete
6107
+ }),
6108
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6109
+ className: "dsha-row",
6110
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6111
+ className: "dsha-label",
6112
+ children: t.codeExpires
6113
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6114
+ className: "dsha-value",
6115
+ children: formatDate$1(flow?.expiresAt)
6116
+ })]
6117
+ }),
6118
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6119
+ className: "dsha-actions",
6120
+ children: [flow?.verificationUriComplete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
6121
+ className: "dsha-btn",
6122
+ href: flow.verificationUriComplete,
6123
+ target: "_blank",
6124
+ rel: "noreferrer noopener",
6125
+ children: t.openSignInPage
6126
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6127
+ className: "dsha-btn",
6128
+ disabled: busy !== null,
6129
+ onClick: () => void handleCancelLogin(),
6130
+ children: t.cancelSignIn
6131
+ })]
6132
+ })
6133
+ ]
6134
+ }),
6135
+ status?.quotaError != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6136
+ className: "dsha-notice",
6137
+ children: status.quotaError
6138
+ }),
6139
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6140
+ className: "dsha-actions",
6141
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6142
+ className: "dsha-btn",
6143
+ disabled: busy !== null || !status?.authenticated,
6144
+ onClick: () => void handleRefreshQuota(),
6145
+ children: busy === "quota" ? t.refreshingQuota : t.refreshQuota
6078
6146
  })
6079
- ]
6080
- })
6147
+ })
6148
+ ]
6081
6149
  }),
6082
6150
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6083
6151
  className: "dsha-group",
@@ -6365,69 +6433,6 @@ window.__ModuleLoader__.load({
6365
6433
  })
6366
6434
  ]
6367
6435
  }),
6368
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
6369
- className: "dsha-group",
6370
- children: [
6371
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6372
- className: "dsha-grouphead",
6373
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t.quotaSection }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6374
- className: "dsha-btn",
6375
- disabled: busy !== null || !status?.authenticated,
6376
- onClick: () => void handleRefreshQuota(),
6377
- children: busy === "quota" ? t.refreshingQuota : t.refreshQuota
6378
- })]
6379
- }),
6380
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6381
- className: "dsha-muted",
6382
- children: t.quotaDesc
6383
- }),
6384
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6385
- className: "dsha-muted",
6386
- children: t.quotaFactsScope
6387
- }),
6388
- status?.quotaError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
6389
- className: "dsha-notice",
6390
- children: status.quotaError
6391
- }),
6392
- !status?.authenticated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6393
- className: "dsha-empty",
6394
- children: t.signedOut
6395
- }) : quota === null || quota === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6396
- className: "dsha-empty",
6397
- children: busy === "quota" ? t.refreshingQuota : t.quotaEmpty
6398
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6399
- className: "dsha-quota-card",
6400
- children: [
6401
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6402
- className: "dsha-quota-title",
6403
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t.quotaPlan }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: quota.planName || t.planUnknown })]
6404
- }),
6405
- quota.extraUsage !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6406
- className: "dsha-meter-wrap",
6407
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6408
- className: "dsha-meter-label",
6409
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t.quotaWallet }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatMoney(quota.extraUsage.balanceCents, quota.extraUsage.currency) })]
6410
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6411
- className: "dsha-meter-meta",
6412
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatMoney(quota.extraUsage.totalCents, quota.extraUsage.currency) }), quota.extraUsage.monthlyChargeLimitEnabled && quota.extraUsage.monthlyChargeLimitCents !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
6413
- t.quotaWalletLimit,
6414
- ": ",
6415
- formatMoney(quota.extraUsage.monthlyChargeLimitCents, quota.extraUsage.currency)
6416
- ] })]
6417
- })]
6418
- }),
6419
- quota.windows.length === 0 && quota.extraUsage === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6420
- className: "dsha-empty",
6421
- children: t.quotaEmpty
6422
- }),
6423
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6424
- className: "dsha-timestamp",
6425
- children: t.updatedAt.replace("{time}", new Date(quota.fetchedAt).toLocaleString())
6426
- })
6427
- ]
6428
- })
6429
- ]
6430
- }),
6431
6436
  error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6432
6437
  className: "dsha-error",
6433
6438
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: error })
@@ -17375,6 +17380,8 @@ window.__ModuleLoader__.load({
17375
17380
  catalogSyncedNote: "实时目录已同步 {count} 个模型。",
17376
17381
  noModels: "暂无可用模型。",
17377
17382
  pricingNote: "价格为官方公开价目(美元 / 每百万 token,输入 / 输出)。",
17383
+ unservedBadge: "套餐不支持",
17384
+ unservedHint: "当前套餐不提供的模型默认隐藏,勾选后会强制显示。",
17378
17385
  contextSection: "上下文窗口",
17379
17386
  contextHint: "官方未公布各模型的上下文窗口:默认按 128k 处理,可在这里按模型修正。留空或保存无效值会提示错误;恢复默认会清掉覆盖值。",
17380
17387
  tokens: "tokens",
@@ -17481,6 +17488,8 @@ window.__ModuleLoader__.load({
17481
17488
  catalogSyncedNote: "Live catalog synced: {count} model(s).",
17482
17489
  noModels: "No models available.",
17483
17490
  pricingNote: "Prices are the official public list (USD per 1M tokens, input / output).",
17491
+ unservedBadge: "Not in plan",
17492
+ unservedHint: "Models the current plan does not serve stay hidden by default; checking one here forces it to show.",
17484
17493
  contextSection: "Context windows",
17485
17494
  contextHint: "The vendor publishes no per-model context windows, so models default to 128k; correct any model here. An invalid value is reported rather than saved; restoring clears the override.",
17486
17495
  tokens: "tokens",
@@ -17583,6 +17592,19 @@ window.__ModuleLoader__.load({
17583
17592
  return Math.min(100, Math.max(0, value));
17584
17593
  }
17585
17594
  /**
17595
+ * Whether the plan the gateway bills for withholds this model.
17596
+ *
17597
+ * The host adds `served: false` to a catalog entry the current plan does not
17598
+ * provide. Such a row stays listed — unchecking it is what hides it, and
17599
+ * checking it is how a user forces it visible — so the card labels it rather
17600
+ * than dropping it. The field is optional: `true` and an absent field both mean
17601
+ * served, so the badge and the note never appear for them and every row renders
17602
+ * exactly as it did before this marker existed.
17603
+ */
17604
+ function unserved(model) {
17605
+ return model.served === false;
17606
+ }
17607
+ /**
17586
17608
  * A window's length in the words the service sold it in.
17587
17609
  *
17588
17610
  * A policy states seconds, and the tier's windows are round days and hours:
@@ -18325,6 +18347,11 @@ window.__ModuleLoader__.load({
18325
18347
  style: { fontSize: 12 },
18326
18348
  children: t.modelsHint
18327
18349
  }),
18350
+ models.some(unserved) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
18351
+ className: "dsha-muted",
18352
+ style: { fontSize: 12 },
18353
+ children: t.unservedHint
18354
+ }),
18328
18355
  status !== null && models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18329
18356
  className: "dsha-empty",
18330
18357
  children: t.noModels
@@ -18336,12 +18363,15 @@ window.__ModuleLoader__.load({
18336
18363
  children: t.catalogNeverSynced
18337
18364
  }),
18338
18365
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelChecklist, {
18339
- items: models.map((model) => ({
18340
- id: model.id,
18341
- name: model.name,
18342
- hint: model.hint ?? `${formatCapacity$1(model.contextWindow)} · ${model.input.join("+")}${model.reasoning ? " · reasoning" : ""}`,
18343
- enabled: model.enabled
18344
- })),
18366
+ items: models.map((model) => {
18367
+ const hint = model.hint ?? `${formatCapacity$1(model.contextWindow)} · ${model.input.join("+")}${model.reasoning ? " · reasoning" : ""}`;
18368
+ return {
18369
+ id: model.id,
18370
+ name: model.name,
18371
+ hint: unserved(model) ? `${hint} · ${t.unservedBadge}` : hint,
18372
+ enabled: model.enabled
18373
+ };
18374
+ }),
18345
18375
  busy: busyAny,
18346
18376
  onToggle: (id, enabled) => toggleModel(id, enabled),
18347
18377
  onToggleAll: (enabled) => setAllModels(enabled),
@@ -20872,6 +20902,8 @@ window.__ModuleLoader__.load({
20872
20902
  catalogSyncedNote: "Живой каталог синхронизирован: моделей {count}.",
20873
20903
  noModels: "Доступных моделей нет.",
20874
20904
  pricingNote: "Цены — официальный публичный прайс (доллары США за 1 млн токенов, вход / выход).",
20905
+ unservedBadge: "Нет в тарифе",
20906
+ unservedHint: "Модели, которых нет в текущем тарифе, по умолчанию скрыты; отметьте их здесь, чтобы показать принудительно.",
20875
20907
  contextSection: "Контекстные окна",
20876
20908
  contextHint: "Производитель не публикует контекстные окна по моделям, поэтому по умолчанию берётся 128k; здесь любую модель можно поправить. Неверное значение не сохраняется, а показывается ошибкой; восстановление сбрасывает переопределение.",
20877
20909
  tokens: "токены",
@@ -21977,9 +22009,10 @@ window.__ModuleLoader__.load({
21977
22009
  const window = [...quota.windows ?? []].sort((a, b) => (a.windowDurationMins ?? Infinity) - (b.windowDurationMins ?? Infinity))[0];
21978
22010
  if (window !== void 0) {
21979
22011
  const remaining = Math.max(0, 100 - window.usedPercent);
22012
+ const remainingText = formatPercent$1(remaining);
21980
22013
  return {
21981
- text: `${remaining}%`,
21982
- tooltip: `[Command Code] ${window.label}: ${remaining}% left`,
22014
+ text: `${remainingText}%`,
22015
+ tooltip: `[Command Code] ${window.label}: ${remainingText}% left`,
21983
22016
  level: quotaLevel(remaining)
21984
22017
  };
21985
22018
  }
@@ -22038,9 +22071,10 @@ window.__ModuleLoader__.load({
22038
22071
  const window = [...quota.windows ?? []].filter((candidate) => candidate.windowDurationMins !== null).sort((a, b) => (a.windowDurationMins ?? Infinity) - (b.windowDurationMins ?? Infinity))[0] ?? quota.windows[0];
22039
22072
  if (window !== void 0) {
22040
22073
  const remaining = Math.max(0, 100 - window.usedPercent);
22074
+ const remainingText = formatPercent$1(remaining);
22041
22075
  return {
22042
- text: `${remaining}%`,
22043
- tooltip: `[Kimi Code] ${window.label}: ${remaining}% left`,
22076
+ text: `${remainingText}%`,
22077
+ tooltip: `[Kimi Code] ${window.label}: ${remainingText}% left`,
22044
22078
  level: quotaLevel(remaining)
22045
22079
  };
22046
22080
  }
@@ -22182,9 +22216,11 @@ window.__ModuleLoader__.load({
22182
22216
  .dsha-kq-seg{border-radius:999px;display:block;height:100%;transition:width .9s cubic-bezier(.22,.61,.36,1)}
22183
22217
  .dsha-kq-seg--recent{background:var(--kq-code)}
22184
22218
  .dsha-kq-seg--rest{background:var(--kq-rest)}
22185
- .dsha-kq-tip{background:var(--kq-tip-bg);border-radius:10px;bottom:calc(100% + 10px);color:var(--dsw-alias-bg-base,#fff);font-size:13px;line-height:1;opacity:0;padding:9px 14px;pointer-events:none;position:absolute;right:0;transform:translateY(4px);transition:opacity .16s ease-out,transform .16s ease-out;white-space:nowrap}
22186
- .dsha-kq-tip::after{border:6px solid transparent;border-top-color:var(--kq-tip-bg);content:"";position:absolute;right:34px;top:100%}
22187
- .dsha-kq-barwrap:hover .dsha-kq-tip{opacity:1;transform:translateY(0)}
22219
+ .dsha-kq-tip{background:var(--kq-tip-bg);border-radius:10px;bottom:calc(100% + 10px);color:var(--dsw-alias-bg-base,#fff);font-size:13px;line-height:1;left:var(--kq-tip-x,50%);opacity:0;padding:9px 14px;pointer-events:none;position:absolute;transform:translate(calc(-50% + var(--kq-tip-shift,0px)),4px);transition:opacity .16s ease-out,transform .16s ease-out;white-space:nowrap}
22220
+ /* 箭头不跟气泡的位移走:气泡被 --kq-tip-shift 收回卡片内时,箭头按
22221
+ --kq-tip-arrow 单独挪回「已用段末端」在气泡里的位置,所以它指的一直是那个数字。 */
22222
+ .dsha-kq-tip::after{border:6px solid transparent;border-top-color:var(--kq-tip-bg);content:"";left:var(--kq-tip-arrow,50%);margin-left:-6px;position:absolute;top:100%}
22223
+ .dsha-kq-barwrap:hover .dsha-kq-tip{opacity:1;transform:translate(calc(-50% + var(--kq-tip-shift,0px)),0)}
22188
22224
  .dsha-kq-legend{align-items:center;color:var(--kq-fg-2);column-gap:16px;display:flex;flex-wrap:wrap;font-size:13px;margin-top:11px;row-gap:6px}
22189
22225
  .dsha-kq-legend-item{align-items:center;display:inline-flex;gap:6px;white-space:nowrap}
22190
22226
  .dsha-kq-legend-note{color:var(--kq-fg-3);white-space:nowrap}