dsh-connect-modelscope-token-plan 0.2.0 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,118 @@
1
1
  # Changelog
2
2
 
3
+ > 本文件只记公开行为变化(新增能力、破坏性改动、重要修复)。实现细节、重构与测试加固请直接看 git log。
4
+ >
5
+ > **判断标准**:写下一条之前问一句——**用户能观察到它吗?** 能(面板变了字、流程多了步、
6
+ > 数据不再丢)才进本文件;不能(改了哪个文件、加了几条测试、抽了哪个函数)进 commit message
7
+ > 或 `docs/archive/`。**本文件是给用户看的,不是给维护者看的。**
8
+ >
9
+ > **写法**
10
+ >
11
+ > - **主题化要点**,不是 `Features/Fixes` 分组。一个主题一个粗体小节,下面 1–4 条要点。
12
+ > **一条 ≤ 120 字,讲清「变了什么 + 用户看到什么」**;病灶取证、判据推导、阴性对照一律不写。
13
+ > - **大小是信号**:一节 20–30 行是正常体量,超过就说明**混进了实施过程**——把它搬走。
14
+ > 既有的长节(如 `[0.2.0]`)是历史债,**别再照着写**。
15
+ > - **摘要必须自含主语与后果**:`snapshot-aggregate.ts` 把空清单当不过滤 ≠
16
+ > 「面板说 allowed:"all" 却按旧清单注册」。写**后者**——前者是病因,读者要的是症状。
17
+ > - **不许沉默**:修好一个从未生效的分支、或改掉一个会误导人的读数,也算行为变化,
18
+ > 照实上报(例:`[0.3.0]` 的「读不到」不再被画成 0)。**不许拿「内部修复」当不写进
19
+ > 本文件的理由**。
20
+ > - **测试/门禁事项不单独成节**:`npm test` 多几个套件、CI 换了工具链版本,用户看不见。
21
+ > 确有影响就并在「工程」一行里,细节留在 commit 或归档。
22
+ >
23
+ > **过程材料去哪**:`docs/archive/YYYY-MM-DD-X.Y.Z-process.md`。发版时新开一节,条目里
24
+ > 带一句外链(见各节末)。**不要为了少写一节而把过程删掉**——那是丢证据,不是精简。
25
+ >
26
+ > **发版时**:顶部开 `## [X.Y.Z] — YYYY-MM-DD`,完整流程见
27
+ > [RELEASING.md](RELEASING.md) §3(本文开头这段是**写作者规范**,那是**发布操作流程**)。
28
+
29
+ ## [0.4.0] — 2026-10-17
30
+
31
+ **面板保存不再被瞬时网络故障打断**
32
+
33
+ - 在面板里切换 provider 开关、保存模型清单或上下文窗口时,若恰逢魔搭目录拉取失败,此前的实现会把失败降级成「空目录」并照样发布——已注册的模型会从选择器里全部消失,直到下一轮轮询才恢复。现在这种故障只保留当前已服务的模型并记录原因,下一次收敛自然重试。
34
+ - 保存动作与后台轮询此前的两套互不知情的发布逻辑,现已合并为单一的收敛流程:保存后立即生效,不再出现「保存了却要等下一轮轮询才兑现」、或生效值被旧轮询覆盖回去一个周期的现象。
35
+
36
+ **出图工具不再让整轮对话干等**
37
+
38
+ - 出图工具此前会一直 `await` 到出图完成才返回(最长 15 分钟),期间这轮对话什么都做不了。现在一次调用只等约 90 秒(新增 `drawWaitMs` 可调):图好了直接返回图片,没好就返回「仍在生成中」加一个 `jobId`,稍后带上它再调用即可继续查询——只查询,不会重复出图。
39
+ - 15 分钟总预算仍然是「这张图值不值得继续等」的上限,但改为**从提交时刻起累计**(跨多次续查),超过才放弃;放弃时依旧会说明上游任务可能仍在生成,而不是报「出图失败」。
40
+
41
+ **出图设置的保存不再丢,失败消息里的密钥不再漏**
42
+
43
+ - 出图开关与出图模型两个设置此前各存各的、互不排队:快速改两项时,后写的那次会盖掉前一次——界面报成功,其中一项却没落盘。现在两次写入串行,两个都能存住。
44
+ - 出图设置文件**存在却读不懂**时(更新的构建写的、或文件损坏),此前会被当成「没设过」并照写照存,把盘上的设置覆盖掉。现在与 provider/用量两处同纪律:读不懂就拒写并说明原因;读侧把「保存的值被忽略」明说成一条警告,面板不再把它呈现成「从未保存过」;`npm run doctor` 也终于会盘点这个文件。
45
+ - 出图失败消息的脱敏此前用的是本模块私有的一份正则副本,比插件其余部分少两组规则(`sk-` 密钥、`password=…` 形式的键值对)——上游把请求体原样回显时,凭据可能一路进面板与日志。现在全插件共用同一份。
46
+
47
+ **provider 状态读不到时,面板不再拿编出来的值顶上**
48
+
49
+ - 此前「接入为 DSH 模型」那一块读不到时,插件会塞一份填满默认值的块(开关关、0 个模型、允许清单「不过滤」)。那个形状看起来就是一个答案,面板因此不再去单独查询 provider 状态,只能按它渲染——而真相是「读不到」。现在这一块会被整个省略,面板随即改用独立的状态查询取真值,两次都取不到才显示「未接入」。
50
+ - 省掉的块不再把原因一起带走:这类失败会作为一条警告列出,操作者能区分「没接入」与「插件读不到自己的状态」。
51
+
52
+ **「全部隐藏」的口径统一**
53
+
54
+ - 允许清单里同时含「全部隐藏」哨兵和真实模型 id 时(手工编辑 `provider.json` 会出现),现在与面板显示一致地按「什么都不提供」处理;此前个别路径会把其中的真实 id 放出来,面板计数与实际提供的模型不符。
55
+
56
+ > **实施过程(病灶、取证、判据)在
57
+ > [docs/archive/2026-10-17-0.4.0-process.md](docs/archive/2026-10-17-0.4.0-process.md)**——那几轮里
58
+ > 不少是踩过的坑,照着做能绕开。
59
+
60
+ ## [0.3.0] — 2026-10-16
61
+
62
+ **新增「上下文窗口」全局档位**
63
+
64
+ - 魔搭目录 `/v1/models` 不返回任何窗口字段,此前每个模型一律按 128K 兜底声明。新增面板「上下文窗口」选择器:默认 / 128K / 256K / 512K / 1M,落盘 `provider.json`。窗口取值按三级真值链——目录真声明过就用目录值,其次是用户档位,再次是内置策展表(models.dev 交叉核对,当前目录 19 个 id 有数据),都没有才落 128K。
65
+ - roster 每行标出当前生效窗口(128000 诚实显示 125K,不美化);策展表来的值带 `*` 号与悬停说明(平台未亲口声明),与目录声明、用户覆盖区分开。
66
+ - **修复**:保存窗口覆盖后,每 `pollSeconds` 轮询都会用未打标条目重新 publish,覆盖被静默回退、适配器每轮重建。现在轮询读落盘覆盖传入;fetch 期间覆盖被改则跳过本轮。
67
+
68
+ **新增对话框用量胶囊**
69
+
70
+ - 会话当前选中的 provider 是 `modelscope-token-plan` 时,输入框工具栏出现胶囊,正文两个读数、各标各的来源:`魔搭 · 143 魔粒 · 今日本地 7 次`。前半是官方魔粒余额,后半是本地日桶——「本地」二字是承重的,官方接口没有「今日」这一项(口径见 README「诚实声明」)。
71
+ - **刻意没有百分比,也没有黄色档**:魔粒余额是存量,官方未发布任何上限或低额阈值;画进度条或定阈值都是发明一个没人公布过的分母。三档只剩绿(读数是正数)、红(官方读数确实是 0)、灰(读不到)。
72
+ - **读不到渲染「—」,绝不渲染 0**;读失败时保留上一次数值并标「上次数据」(陈旧阈值 = `pollSeconds` × 3,默认 90s)。
73
+ - 非归属会话一个请求都不发;切模型不会踩 hook 顺序。
74
+
75
+ **六处写守卫与注册状态机缺陷**(每处都有用户可见后果)
76
+
77
+ - **面板与 Host 对「正在提供什么」各说一套**:操作者按提示删掉 `provider.json`(或清单被保存为空)后,面板说 `allowed:"all"` 且数上全部模型,轮询却继续按内存里的旧清单注册。
78
+ - **版本守卫 fail-open**:**存在却读不出的** `provider.json` / `usage.json` 被判为可写,违反 ADR-006「绝不覆盖读不懂的文件」;现在只有文件不存在才返回 `null`,其余 fs 错误一律拒写并说明理由。
79
+ - **回滚路径漏 release**:注册回滚时适配器已注册成功,「面板说不注册、Host 里却还在路由」。
80
+ - **回滚成功后 `error` 不清**:每个轮询周期把同一对摘下再挂上(期间在途请求可能被打断),面板还挂着一个已不成立的错误。
81
+ - **成功的验令牌被报成失败**:凭据服务读状态一抛错就落进 catch,回报 `ok:false` 并往事件流写一条假 `error`。
82
+ - **「忘掉令牌」整条丢掉 `body.ok`**:凭据文件只读时删除失败,界面上却表现为「什么都没发生」——而该失败是刻意传播的(谎报成功比失败更糟)。
83
+
84
+ **修复发布状态机的两处自伤**
85
+
86
+ - 工厂加载的 rejection 被 memo 死:一次失败的 `import()` 把错误钉在槽里直到进程结束,此后 provider 再也注册不上,**照面板给的补救提示修好依然无效、必须重启 Host**。现在只 memo 成功,失败清槽、下一次 publish 真重试。
87
+ - `!llmAvailable` 分支漏清 `state.built`:残留 `built` 成为下一次 publish 的回滚目标,会把一只已 release 的适配器重新挂上 Host。
88
+
89
+ **「读不到」不再被画成 0**
90
+
91
+ - `usage.json` 不可读时,面板「额度」头条从「今日 0 次」变成「—」,并在面板上方给出原因。此前「读不到 ≠ 0」的语义在三个文件里写了三遍,一次都没兑现:真实的读失败被吞成空载荷再折成 0,而「读不到画成 0」会让操作者以为今天没调用过。
92
+ - 判据改为「文件在不在」:文件**不存在** = 真实的 0;**在却读不出来**(损坏 / 形状不对 / 未知版本 / 不可读)= `null`。
93
+
94
+ **令牌状态补上真实结论**
95
+
96
+ - `valid` / `checkedAt` 此前在 Host 侧硬编码为 `null`,从无生产者,面板只能一直显示「未校验」。现在由零额度探针产出:200/400 → 有效,401/403 → 无效,**429 / 额度耗尽 / 上游 5xx 一律不写**(它们回答的是「上游忙不忙」,不是「令牌好不好」)。
97
+ - 换令牌时清掉旧结论;结论只活在本进程,重启回到「未校验」。
98
+ - 新增「无效」态:`valid: false` 此前会渲染成「未校验」——把已知的坏说成不知道。探针出结论后面板立即刷新,不再等下一个 30s 轮询。
99
+
100
+ **可用性与无障碍**
101
+
102
+ - tab 键盘焦点不可见:内联 `outline: "none"` 压掉了外壳的焦点环,改为 `outlineOffset: -2`。
103
+ - 令牌输入框补 `aria-label`、ToggleSwitch 补 `busyLabel`(屏幕阅读器此前读不出「正在切换」)。
104
+ - 模型 id 含 `#` / `?` 时详情页外链 URL 语义被破坏,改为分段 `encodeURIComponent`。
105
+ - 面板会显示「NaN 个模型」,补 `Number.isFinite`。
106
+
107
+ **工程**
108
+
109
+ - 依赖安装不再被 npm 10 的自动 peer 解析卡死;构建工具链精确钉住版本(build-gate 逐字节比对产物)。
110
+ - CI 门禁补真 DOM 与真 React 两档渲染套件;测试环境隔离宿主令牌,测试缝接线双向扫描。细节与取证见下方指针。
111
+
112
+ > **实施过程(病灶、取证、判据、阴性对照)全部在
113
+ > [docs/archive/2026-10-16-0.3.0-process.md](docs/archive/2026-10-16-0.3.0-process.md)**——那几十轮里
114
+ > 不少是踩过的坑,照着做能绕开。
115
+
3
116
  ## [0.2.0] — 2026-10-05
4
117
 
5
118
  **凭据与红线**
package/README.md CHANGED
@@ -18,6 +18,40 @@
18
18
 
19
19
  ![「模型」tab:接入为DSH 模型与模型清单](assets/tab-model-list.png)
20
20
 
21
+ 同一 tab 下方另有**「出图工具」**卡(v0.2 起):开启后 Host 给 agent 注册
22
+ `modelscope_draw_image`,支持文生图、图生图与多图编辑。**它与「接入为 DSH 模型」
23
+ 是两条独立开关**,且开关值立即保存、而工具的**挂载发生在下次 Host 启动**(peer 的
24
+ tools registry 没有 unregister)。实测协议(异步两段式、必需头、提交态不可信、
25
+ 计费与耗时口径)见 [docs/SPIKE-image-gen.md](docs/SPIKE-image-gen.md)。
26
+
27
+ 工具本身也是**两段式**的(v0.4 起):一次调用只等 `drawWaitMs`(默认 90 秒)——
28
+ 图好了直接返回,没好就返回 `status: pending` 与一个 `jobId`,**带上它再调用即可
29
+ 继续查询**(只查询、不重复出图)。上游排队实测 1–15 分钟,此前一次调用会一直
30
+ await 到出图为止,整轮对话都得陪着等。
31
+
32
+ 同一 tab 下方另有**「上下文窗口」**选择器(默认/128K/256K/512K/1M):魔搭目录
33
+ `/v1/models` 不返回窗口字段,Host 侧按三级真值链给每个模型定窗口——目录真声明了
34
+ 就用目录值(不受任何下层影响);用户在这里选一个档位即对**目录没声明窗口的模型**
35
+ 全局生效,保存即重注册;**用户没设覆盖时**,插件查内置策展表(`context-catalog.ts`,
36
+ 来自 models.dev 各厂 host 交叉比对的同 id 主流窗口,2026-10-16 核对,含 DeepSeek-V4
37
+ 全系 1M、Qwen3.5/Qwen3.8-27B/Step 3.x 256K、GLM-5.2 1M 等 19 个当前目录 id);
38
+ 表里也没有的模型才落 128K 兜底。档位落盘在 `provider.json` 的 `contextWindow` 键
39
+ (v1 形状的新增可选键,旧构建宽容读),roster 每行会标出该模型当前生效的窗口——
40
+ 策展表来的值带 `*` 号与悬停说明(平台未亲口声明),与目录声明/用户覆盖区分。
41
+
42
+ ## 对话框胶囊(v0.2 起)
43
+
44
+ 注册 provider 之后,输入框工具栏会多一枚胶囊(用户不需要打开 Plugins 页就能看见额度):
45
+
46
+ ```
47
+ 魔搭 · 143 魔粒 · 今日本地 7 次
48
+ ```
49
+
50
+ - **前半**是官方魔粒余额(`balance.available`,真实值,与面板头条同一个数);**后半**是今日本地调用次数(Host 侧本地日桶,只统计经本插件的调用)。两个数字来源不同,所以后段必须带「本地」——官方接口没有「今日」这一项,不写清就会被读成官方计数。
51
+ - **只在会话当前选中的 provider 是 `modelscope-token-plan` 时出现**(读宿主 session 作用域的 `modelSelection` 投影),切到别的 provider 即整块消失,且一个请求都不发。
52
+ - **没有百分比、没有黄色档**。魔粒余额是**存量**、官方没有发布任何上限或低额阈值:画进度条或定一个「低于 N 魔粒算黄」的阈值,都是给一个没人公布过的分母配数。三档只有绿(读数是正数)/ 红(官方读数确实是 0)/ 灰(读不到)。
53
+ - **读不到 ≠ 0**(与面板同一条纪律):读不到时该段渲染「—」,0 只在官方真的给了 0 时出现并走告警色;读失败保留上一次的数值,并标「上次数据」(阈值 = Host 声明的 `pollSeconds` × 3)。tooltip 里带余额时间、失败原文与本地计数缺失的原因。
54
+
21
55
  ## 三条事实(写代码前先认清)
22
56
 
23
57
  1. **官方有「魔粒」余额端点**:`GET {siteBase}/openapi/v1/magicubes/balance`(Bearer 访问令牌,匿名 401),返回 `{success, data:{total_balance, available_balance, frozen_amount}}`(2026-10-04 实测,见 [docs/SPIKE.md](docs/SPIKE.md))。推理响应本身仍不带任何额度头。
@@ -41,8 +75,10 @@
41
75
  | 何时 | 查 |
42
76
  |---|---|
43
77
  | 「上游到底有没有额度信号」 | [docs/SPIKE.md](docs/SPIKE.md)(实测记录 + 结论) |
78
+ | 「能不能接生图、协议是什么」 | [docs/SPIKE-image-gen.md](docs/SPIKE-image-gen.md)(2026-10-10 实测;**v0.2 起已实现**,承上表体例) |
44
79
  | provider 怎么接入(写代码前) | [docs/PROVIDER-M4.md](docs/PROVIDER-M4.md)(实现契约) |
45
- | **这版是怎么做出来的、踩过什么坑** | [docs/IMPLEMENTATION.md](docs/IMPLEMENTATION.md)(实施过程档案) |
80
+ | **这版是怎么做出来的、踩过什么坑** | [docs/IMPLEMENTATION.md](docs/IMPLEMENTATION.md)(0.1.0 / M4 实施过程档案) |
81
+ | v0.2.0 的十二轮修复细节(已冻结,非入门路径) | [docs/archive/2026-10-05-closeout.md](docs/archive/2026-10-05-closeout.md) |
46
82
  | 上游端点全景 / 第三方方案对比 | [docs/REFERENCES.md](docs/REFERENCES.md)、[docs/REFERENCE-modelsdev.md](docs/REFERENCE-modelsdev.md) |
47
83
  | v1 要做什么、做到哪了 | [docs/ROADMAP.md](docs/ROADMAP.md) |
48
84
  | Host/Client 两半怎么分 | [docs/PROVIDER-M4.md](docs/PROVIDER-M4.md)(§14 红线)+ [docs/IMPLEMENTATION.md](docs/IMPLEMENTATION.md);本仓库暂无独立的 `ARCHITECTURE.md` |
@@ -50,7 +86,7 @@
50
86
 
51
87
  ## 诚实声明
52
88
 
53
- - 面板头条是**官方魔粒余额**(真实值);本地计数只统计**经本插件的调用**——它回答的是分布/趋势/429 事件流,不是余额,直连魔搭的其它客户端不计入。注册 provider 之后,DSH 的全部魔搭调用都经本插件,所以本地计数**覆盖全部 DSH 魔搭调用**。次数口径的「推算剩余」已从面板移除(官方改魔粒计费后两单位并排是误导),相应地`quota.daily` 只剩 `usedLocal` 一个数——没有 `limit`,也没有推算出的「剩余」。
89
+ - 面板头条是**官方魔粒余额**(真实值);本地计数只统计**经本插件的调用**——它回答的是分布/趋势/429 事件流,不是余额,直连魔搭的其它客户端不计入。注册 provider 之后,DSH 的全部魔搭调用都经本插件,所以本地计数**覆盖全部 DSH 魔搭调用**。次数口径的「推算剩余」已从面板移除(官方改魔粒计费后两单位并排是误导),相应地`quota.daily` 只剩 `usedLocal` 一个数——没有 `limit`,也没有推算出的「剩余」。这个数还有第三种答案:**本地计数文件读不到时它是 `null`,面板渲染「—」**——「今日 0 次」是可信的日常值,把「我不知道」画成它会让人以为今天没调用过(`usage.json` 损坏、形状不对、或被更新的构建写过都是这种情形,原因会在面板上方的漂移提示里说明)。
54
90
  - **本地计数的口径与 harness 全局账本不同,且刻意不合并**。DSH 自己有 `@linxin666/dsh-usage`,按 `days→provider→model` 记成功调用的 token;它只订阅 session 的 `assistant/message`,**失败调用一次都不记**。所以本插件只做两件全局账本答不了的事:①「经本插件的调用分布与趋势」;②**429/错误事件流**——这是本插件的独家数据(官方只给总额度余额,不给「哪个模型刚才在限频」)。成功的 token 总量不在此重复记账(详见 `src/host/usage-observer.ts` 文件头)。
55
91
  - 429/错误事件的分类读的是 **`llm-error-fix` 重分类之后**的 code:被误判成 QUOTA 的 rpm 限频在事件流里记作 `rate_limit`,与退避策略说同一件事。观察层套在重分类层**之外**,顺序不可反。
56
92
  - **计数口径**:每次流结束记一次 call,**含失败、含用户中断**(魔搭按次数计费,限频掉的请求同样是一次调用);token 数上游没给时是 `null` 而非 0。偏差方向是**按流计次**:重试不在流内部发生,而是在流消费完后由 `agent/request-error` 瀑布决定重发,因而**每次重试各记一次**——按「魔搭按次数计费」的口径这反而是对的(每次重发都真打了一次上游)。