dsh-composer-ux 0.24.0 → 0.26.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
@@ -2,6 +2,115 @@
2
2
 
3
3
  本项目遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [0.26.0] — 2026-10-27(订阅通道的**额度**口径 + 设置页整体换肤)
6
+
7
+ 用户 2026-10-27:「**我总感觉子代理计费还是不准**,你参考一下这个是用什么方式计费的
8
+ (`kenz1117/dsh-ui-usage-billing`),并参考它的 UI 修改我整个插件设置的 UI」。
9
+
10
+ **先说结论:子代理的取数没错,错的是"价格口径"。**
11
+
12
+ - 本机最近 25 个会话里 **`opencode-go` 24 个、`go` 18 个** —— 用户走的是 **OpenCode Go 套餐**
13
+ ($10/月 + 分档额度)。这类通道**单次调用不扣钱、扣额度**;而 0.25.0 及以前只有一套算法:
14
+ **DeepSeek 官方刊例价 × token**(还分峰谷)。于是同一个会话算出"花了 ¥91",而他实际付的是
15
+ $10/月 —— 这个数字既不是钱包里的钱、也不是额度消耗,子代理越多偏得越多。
16
+ - 参考仓库的账本是**双口径**:`code` 计划(订阅)与 `token` 计划(按量)分开记,订阅通道
17
+ **调用成本记 0**、另列订阅费与额度窗口;它的子代理归并(header 的 `origin: 'subagent'` +
18
+ 跳过 `seq < inheritedEventCount` 的 fork 种子)与我们在 0.24.0 做的是同一套 ——
19
+ 所以差异确实在价格,不在取数。
20
+
21
+ **用户拍板的三件事(一问一答定下来的)**
22
+
23
+ 1. **C:两套口径并存** —— 额度(实付)与刊例价折算都要,标签写明各是什么;
24
+ 2. **A:胶囊主数字 = 本会话"额度消耗金额"**(美元,含子代理),旁边加 `订阅内` 小标;
25
+ 点开看 5 小时 / 本周 / 本月额度、按模型占月额度比例、以及按刊例价折算的 ¥;
26
+ 3. **B:12 张设置卡整体换视觉语言**(参考仓库那套卡片 / 标题 + `?` 说明 / 分段控件 / 行语言),
27
+ **不加**它的左侧锚点导航(会和 DSH 自己的 section 导航叠成双层)。
28
+
29
+ **额度是怎么算的**
30
+
31
+ - 官方 Go 价目表给出每模型每百万 token 的单价与**月度上限**(V4.1 Flash:谷
32
+ $0.15 / 命中 $0.003 / 输出 $0.60,峰翻倍,月上限 $80;V4 Pro 月上限 $15)。
33
+ 额度消耗 = **Σ token × 该档单价**,峰谷判定沿用插件自己那套(工作日 09–12 / 14–18 北京时间)。
34
+ - 额度窗口走官方接口 `GET https://opencode.ai/zen/go/v1/usage`(Bearer 该通道的 key),
35
+ 返回 `rolling` / `weekly` / `monthly` 三段**已用百分比**与重置时刻 —— 与 OpenCode 控制台
36
+ 上那三个进度条同源。本机 2026-10-27 实测:**5h 0% · 本周 3% · 本月 54%**。
37
+
38
+ **改动**
39
+
40
+ - 新增 `src/subscription-plans.ts`(零依赖):套餐表、模型名归一(`deepseek-chat` 这类旧名按
41
+ V4.1 Flash 计)、`quotaCostOf`(额度消耗 + 占月上限百分比)、额度响应解析(缺段不补 0、
42
+ 百分比钳到 0–100)、`degradeQuota`(失败**保留上一次窗口**,不许把「周 3%」擦成空白)。
43
+ - 宿主半(`src/host.ts`):`priceFold` 每条 route 多算一份 `quota`(不动原有 `cost`);
44
+ 合计时**按模型合并**(同一模型跨峰谷两条 route 要先合并再除上限,否则得出"每档各 x%"的假数);
45
+ 子代理的额度也并进来;`/composer-ux/usage` 多回 `quota` 与 `plan` 两个字段。
46
+ - 新增路由 `GET /composer-ux/quota`(同一道官方信任关卡):给设置页那张卡用。
47
+ **额度取数不阻塞**:路由只读缓存(没有就回 `pending`),后台刷新、60 秒 TTL、同套餐单飞
48
+ —— 胶囊 600ms 一轮,绝不能被一次网络往返拖住(`test/subscription-billing.mjs` 第 4 节用
49
+ "永不 resolve 的 fetch"把这条钉住)。
50
+ - 金额胶囊(`src/client/CostChipEntry.tsx`):主数字换成 `$额度` + `订阅内` 小标;浮层里
51
+ 额度消耗(含子代理)/ 按模型 + 占月额度% / 三段窗口 / **按刊例价折算 ¥X** 同屏并列,
52
+ 并写明"两套口径各是什么"。按量付费的会话与老宿主逐字节保持旧行为。
53
+ - 设置页(新建 `src/client/ui-kit.tsx` + `settings-style.ts` + `SettingsSection.tsx`):
54
+ 12 张卡换成统一外壳(12px 圆角 / 1px 描边 / hover / 未启用虚线)与行语言(标题 + 说明 +
55
+ 右侧控件、`?` 悬停说明、分段控件、卡内小标题)。旧类名一个没删(`client-registration`
56
+ 逐条仍绿)。金额卡正文新增「订阅额度」块:三段进度条 + 重置时间 + 未配 key 时点名该配哪个引用。
57
+ - 顺带修三处既有问题:`AutostartCard` / `DoctorCard` 正文里的字面 `**`(渲染出来是星号)、
58
+ 「OpenCode 请求头」关掉时概览渲染成「未启用 · 未启用」、共享行样式与卡片视觉不一致。
59
+
60
+ **验证**:34 套件 **2854 条** 0 失败;变异 **226 条全部咬住**(新增 `SJ`–`SO` 六条:
61
+ 峰谷、别名、月上限、跨档合并、缓存刷新、失败保留窗口)。新增套件
62
+ `test/subscription-billing.mjs`(43 条,含真路由往返与"额度接口挂住也不卡"这条性能契约);
63
+ `test/cost-panel-render.mjs` 新增 1.3 节(10 条)。真机:`GET /composer-ux/quota` 用用户凭据
64
+ 取到真实窗口(见上)。
65
+
66
+ **未做 / 说明**:只给 OpenCode Go 建了套餐表(用户实际在用这一条);其它订阅通道仍按刊例价
67
+ 折算,等真用到再加一行。官方没公布月度上限的模型不给百分比(不拿 0 冒充)。桌面端要重启并
68
+ 装 0.26.0 才能看到新版(运行中的应用会一直占着 profile 锁,CLI 装不进去)。
69
+
70
+
71
+ ## [0.25.0] — 2026-10-18(「OpenCode 请求头」降级为旧宿主兼容层:宿主已原生支持就不注入)
72
+
73
+ 用户 2026-10-18 在设置页看到这一栏,问:**「是否还需要这个功能」**。查完上下游后答案是
74
+ **不需要了,而且开着比不开更差**,用户拍板:关掉 + 改成"启动时探测宿主,有原生支持就不注入"。
75
+
76
+ **三条证据(都在本机核过)**
77
+
78
+ 1. OpenCode Go 官方文档「可以在哪里使用?」第 3 条仍然要求客户端发
79
+ `x-opencode-session`(用于优化路由与 prompt cache),所以这个头本身还是要的;
80
+ 2. 但**上游已经自己发了**:`@earendil-works/pi-ai` 从 **1.0.2** 起自带
81
+ `dist/providers/opencode-headers.js`,`opencode` 与 `opencode-go` 两个 provider 都被
82
+ `withOpenCodeSessionHeader()` 包过;它用的是 `options.sessionId`(**每段对话的真实会话 id**),
83
+ 而且 **`hasHeader()` 命中就不覆盖**;
84
+ 3. DSH 的 `llm-pi-ai` 调用时是 `headers: requestHeaders(profile.headers)` 与
85
+ `sessionId` **一起**交给 pi-ai 的 ⇒ 本插件注入的那个**固定 UUID**(设置页原话"所有对话共用
86
+ 这一个值")会**顶掉**真实 session id。也就是说:开着它,所有对话在 OpenCode 那边都是同一个
87
+ 会话 —— 正是官方要用来做路由与缓存优化的那一维被抹平了。
88
+
89
+ **改动**
90
+
91
+ - 新增 `src/opencode-header.ts`:启动时探测宿主能力。判据是**宿主 pi-ai 安装里有没有
92
+ `dist/providers/opencode-headers.js`**(能力就是那个模块,版本号只用来写证据),探测面覆盖
93
+ 桌面端(`resources/app.asar` 里的运行时)与网页版(从入口往上找 `node_modules` / `.pnpm`);
94
+ 探不到时**按旧宿主处理**(继续注入,失败方向是多做一次旧行为),并可用环境变量
95
+ `DSH_COMPOSER_UX_HEADER_NATIVE=1|0` 强制。
96
+ - 宿主半(`src/host.ts`):探测到原生支持时 —— 不注入、不生成 UUID、不记"已注入"的账;
97
+ 状态行写「宿主已原生支持(pi-ai 1.1.0 自带 opencode-headers.js):按每段对话的真实
98
+ session id 自动发送,本栏不再注入」。
99
+ - 设置页(`src/client/SettingsSection.tsx`):卡片顶部多一条说明("本宿主已原生支持…这一栏
100
+ 什么都不做",与自检卡、状态行说同一件事);概览写「宿主已原生支持(不需要)」而不是像故障的
101
+ "未启用";「值」那一行的说明改成"旧宿主专用"。
102
+ - 自检卡(`src/doctor.ts`):`header` 那一行在原生支持时如实说明"这一栏不需要、也不会注入"。
103
+ - 桌面 / 网页两个 profile 的 `headerEnabled` 均置 `false`(用户要求"关掉";不过即便打开,
104
+ 探测到原生支持也不会注入)。
105
+
106
+ **验证**:33 套件 **2800 条** 0 失败;变异 **220 条全部咬住**(新增 `SG`/`SH`/`SI`)。`test/host-header-mirror.mjs` 新增第 19 / 20 节(原生支持不注入 + 不生成 UUID +
107
+ 状态行;探测判据用假目录逐条钉:模块在⇒native、模块缺⇒legacy、目录缺⇒legacy、环境变量强制、
108
+ 版本比较按数字不按字符串);变异新增 `SG`/`SH`/`SI` 三条。
109
+
110
+ **未验证(如实说明)**:探测只在本机两种布局(Electron asar / pnpm 检出)上验过;发行渠道若把
111
+ pi-ai 装到别处,会落到"按旧宿主处理",此时用环境变量强制即可。桌面端要重启后才加载这一版。
112
+
113
+
5
114
  ## [0.24.0] — 2026-10-17(金额胶囊把**子代理**的费用算进去)
6
115
 
7
116
  用户 2026-10-17:「**我发现金额胶囊不计算子代理的费用**」。
@@ -50,8 +159,8 @@
50
159
  变异 **217 条全部咬住**(新增 `SA`–`SF` 六条);`tsc` 0 错误;`cost-panel-render` 新增第 1.2 节
51
160
  17 条渲染断言(旧夹具与旧断言一条没动)。
52
161
 
53
- **未验证(如实说明)**:没有真机截图;桌面端要等用户重启后才会加载这一版(那个进程还是
54
- 2026-10-16 19:21 起的 0.2.0-rc.2);子代理的钱只有总量、没有按峰谷拆开的金额。
162
+ **未验证(如实说明)**:没有真机截图;桌面端要等用户重启后才会加载这一版(那个进程是
163
+ 2026-10-17 23:30 起的 **0.2.1-rc.2**,装的还是 0.23.4);子代理的钱只有总量、没有按峰谷拆开的金额。
55
164
 
56
165
 
57
166
  ## [0.23.4] — 2026-10-16(适配 dsh 0.2.1-rc.1:无功能变化,只做验证与版本对齐)
package/README.md CHANGED
@@ -493,6 +493,20 @@ Windows 上 DSH 给模型的终端工具是 **PowerShell**(工具名 `pwsh`)
493
493
  - **写完价目要立刻生效,靠的是"写的人通知",不是设置变更事件**:0.1.7 的 `settings/document-updated` 只在 `describe()` 里比对 raw 变化后发出,而我们自己 `mutate` 写设置时并不调 `describe()` —— 设置页开着时会被界面刷新顺带触发,**设置页没开着**(例如后台的自动同步跑完)就不会发;而折叠缓存按 `seq` 去重、改规则不会重折旧事件,金额会**静静地停在旧价格档**上、界面上看不出。所以宿主半加了模块级 `moneyInvalidators` 注册表:用量路由登记"重读规则 + 作废价目缓存 + 作废折叠缓存",两条同步路径写完设置直接调 `invalidateMoney()`,事件只作补充。
494
494
  - **内置第三方价目快照(0.10.0)**:`src/provider-prices.ts`(**生成**的,别手改:`node scripts/gen-provider-prices.mjs <models.dev-api.json>`)收了 **11 个 provider / 346 个模型**,快照日期写在文件头。**没点过同步时**就是非 DeepSeek 模型的兜底价;点过一次「同步第三方价目」后最新数据会盖住同名条目,界面分别标明"内置快照价"还是已同步。查价优先级:**用户覆盖价 > 已同步价目 > 内置快照 > 未定价**。
495
495
  - **非 DeepSeek 模型定价(0.10.0)**:按 `(provider, model)` 查价 —— 精确 provider → `PROVIDER_ALIASES` 别名(`deepseek-official`→`deepseek`、`kimi-coding`→`moonshotai`…)→ **按模型 id 全局唯一匹配**(`glm-5` 在两家都有的价时**拒绝猜**)。**认不出价就说"未定价"**(单价全 0,明细页写明"去同步第三方价目或给这行填个价"),**不再把 DeepSeek 的 flash 价静默套到第三方模型头上** —— 编一个看着合理的假数字,比承认不知道更糟。
496
+ - **「OpenCode 请求头」这一栏(0.25.0 起只给旧宿主)**:OpenCode Go 要求客户端按**每段对话**发 `x-opencode-session`。上游 `@earendil-works/pi-ai` 从 **1.0.2** 起自带 `dist/providers/opencode-headers.js`(`opencode` / `opencode-go` 两个 provider 都被包过),用`options.sessionId` 发**真实会话 id**,且「已有同名头就不覆盖」。本插件注入的是**固定 UUID**,开着反而会顶掉真实 id ⇒ 0.25.0 起宿主半**先探测**(判据是宿主 pi-ai 里有没有那个模块;桌面 asar与 pnpm 检出两种布局都覆盖),探到原生支持就**不注入、不生成 UUID、不记账**,状态行写「宿主已原生支持…本栏不再注入」;探不到按旧宿主处理(继续注入),也可用`DSH_COMPOSER_UX_HEADER_NATIVE=1|0` 强制。`test/host-header-mirror.mjs` 第 19 / 20 节把两条分支与探测判据逐条钉住。
497
+ - **订阅通道的额度口径(0.26.0)**:OpenCode Go 这类「月费 + 额度」的套餐**单次调用不扣钱、扣额度**,
498
+ 所以"按刊例价折算"出来的 ¥ 既不是账单、也不是额度消耗(用户 2026-10-27 的「感觉还是不准」就是这个)。
499
+ 现在宿主半对订阅通道**另算一份额度**:`额度(USD) = Σ token × 该模型该档单价`(单价与月度上限来自
500
+ Go 官方表:V4.1 Flash 谷 $0.15 / 命中 $0.003 / 输出 $0.60、峰翻倍、月上限 $80;V4 Pro 月上限 $15),
501
+ 同一模型跨峰谷的两条 route **先合并再除上限**;额度窗口走官方 `GET /zen/go/v1/usage`
502
+ (5 小时 / 本周 / 本月三段**已用百分比**,与控制台同源),60 秒缓存、同套餐单飞、**不阻塞**路由
503
+ (拿不到先回 `pending`,失败保留上一次的窗口)。金额胶囊默认显示**本会话额度消耗**并标 `订阅内`,
504
+ 展开同时给出「按刊例价折算 ¥X」——两套口径并列、各自写明是什么;设置页「金额」卡多了订阅额度进度条。
505
+ `test/subscription-billing.mjs`(43 项)钉住峰谷、别名、月上限、跨档合并、缓存刷新、失败降级,以及
506
+ "额度接口挂住也不卡"这条性能契约。
507
+ - **设置页视觉(0.26.0)**:12 张卡统一成一套行语言(标题 + 说明 + 右侧控件、`?` 悬停说明、分段控件、
508
+ 卡内小标题、12px 圆角描边外壳),原语抽在 [src/client/ui-kit.tsx](src/client/ui-kit.tsx);**没有**引入
509
+ 社区插件那种左侧锚点导航(会和 DSH 自己的 section 导航叠成双层)。
496
510
  - **为什么金额必须自己算**:DSH 送到浏览器的 `tokenUsage` 投影**只有 token 桶**,全库没有一处把"钱"送到客户端;`llm-pi-ai` 里那个 `cost` 只活在 provider 内部,而且用户自定义的路由(profile 里手写的 provider)拿到的是 `NO_COST`(全 0)。所以费用只能由本插件按刊例价算。
497
511
  - **数据全部来自官方、不新增采集**:token 桶读 `tokenUsage` 投影(与官方统计行**同一份**),模型读 `modelSelection` 投影(来自 `request/header` 事件)。会话中途换过模型时按**最后一次请求**的模型计价(近似,浮层里写明)。
498
512
  - **按 route 分列与"逐笔准时"的取数**(点开才去问宿主半;折叠态那颗胶囊也走同一条路由):面板里列出每条 route(provider + model + 高峰/空闲)的 token 与花费,**金额由宿主半按档算好**(用户覆盖价一并读自设置),所以"各行加起来等于总额""三项相加等于合计"都精确成立。逐请求归因只能宿主半做,走只读路由 `GET /composer-ux/usage?sessionId=…`(`src/host.ts` 注册):首次(或发现 `seq` 落后)用官方 `sessionQuery.readSession(sessionId)` 完整读一次,之后靠 `session/event` 订阅**增量**喂折叠缓存(`createUsageCache`,纯逻辑在 [src/usage-fold.ts](src/usage-fold.ts))—— 所以胶囊取价是 O(1),不必反复重读整份日志。**播种窗口**(读日志期间追加的事件)会先缓冲、读完按 `seq` 补上:不这么做的话水位会跳过快照里那些中间事件,金额**永久少算**(`feed()` 的 seq 幂等,补也补不进去)。**事件形状按本版官方 `SessionEventMap` 的真实声明**(`assistant/attempt` 的 `stream` 里取最后一条 usage 块,或 `assistant/message` 的 `data.usage`;**本版没有 `assistant/chunk` 事件**),口径对齐官方 `token-meter` 的 `usage-projection`(同一步替换、`llm/retry-started` 重开替换槽)。`test/usage-fold.mjs`(89 项)逐条钉住差分、归属、重试、脏数据、逐笔分档与增量缓存;分列出不来时面板会显示"读了 N 条事件、M 条 usage、来源哪条路",一眼就能定位。
@@ -721,9 +735,9 @@ node build.mjs # 产出 lib/index.js + lib/client.js
721
735
  # ⚠️ 宿主半会**内联** schemastery / cosmokit:
722
736
  # 优先用 DSH 检出里的 vendor 副本,检出不在时退到 node_modules
723
737
  # 里同版本的 npm 包(两者逐字节相同)——CI 上走的就是退路
724
- npm test # 33 个套件;当前 2780 passed, 0 failed(2026-10-17 实测;CI 三平台同样全绿)
738
+ npm test # 34 个套件;当前 2854 passed, 0 failed(2026-10-27 实测;CI 三平台同样全绿)
725
739
  # 走 scripts/run-tests.mjs:顺带把"多少套件/多少条"记进 test/.last-run.json
726
- npm run test:mutations # 手动跑:变异测试,证明那套护栏真的在咬人(217 条,须单独跑)
740
+ npm run test:mutations # 手动跑:变异测试,证明那套护栏真的在咬人(226 条,须单独跑)
727
741
  # 同样记录结果,供下面的文档门禁核对
728
742
  npm run gates # 发版门禁三条一起跑:tag 指向 / 包内容 / 文档数字
729
743
  npm run check:tag # ① tag 名里的版本 == 该 tag 所指提交里的 package.json 版本
package/dsh-plugin.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifestVersion": "0.15",
4
4
  "id": "dsh-composer-ux.input-experience",
5
5
  "name": "dsh-composer-ux",
6
- "version": "0.24.0",
6
+ "version": "0.26.0",
7
7
  "license": "MIT",
8
8
  "facets": {
9
9
  "host": {