@loommii/dsh-provider-usage 0.6.1 → 0.8.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/README.md CHANGED
@@ -1,9 +1,17 @@
1
1
  # dsh-provider-usage — DSH「用量中心」
2
2
 
3
- > 当前版本 **v0.6.1** · npm `@loommii/dsh-provider-usage` · [更新历史](docs/CHANGELOG.md)
3
+ > 当前版本 **v0.8.0** · npm `@loommii/dsh-provider-usage` · [更新历史](docs/CHANGELOG.md)
4
4
 
5
5
  DSH Web GUI 插件 **「用量中心」**:一个常驻右下角的用量卡片,实时显示你的 AI 服务用量 / 余额,不用再打开网页查;附带本地 Token 统计页,不联网也能看自己今天用了多少。
6
6
 
7
+ ## v0.8.0 更新摘要(适配 dsh 0.1.2-alpha)
8
+
9
+ - **适配 dsh `0.1.2-alpha` 破坏性重构**:修复升级后「设置页无用量中心 / 卡片消失」与
10
+ `duplicate loader entry id` 启动崩溃(根因:v0.6.0 包名迁移时 `cordis.patch.yml` 与
11
+ client 注册 id 仍用旧名,叠加 alpha 新的客户端发现机制导致静默失联)
12
+ - host 侧 API 契约、会话文件解析、平台注入(react / slots)经逐项核对在 alpha.4 全部兼容
13
+ - 完整变更与用户升级指引见 [CHANGELOG](docs/CHANGELOG.md)
14
+
7
15
  ## v0.6.0 更新摘要
8
16
 
9
17
  - **新增提供方 Command Code(订阅+余额混合卡)**:5 小时 / 周 / 月窗口使用量 + 月度剩余额度(USD),主数字为月已用百分比
@@ -19,14 +27,14 @@ DSH Web GUI 插件 **「用量中心」**:一个常驻右下角的用量卡片
19
27
  |---|---|
20
28
  | OpenCode Go(订阅) | 5 小时 / 7 天 / 月度三个窗口的已用百分比 + 进度条 + 重置倒计时 |
21
29
  | DeepSeek(余额) | 账户余额(CNY) |
22
- | Command Code(订阅+余额,自定义) | 5 小时 / 周 / 月窗口使用量 + 月度剩余额度(USD),主数字为月已用百分比 |
30
+ | Command Code(订阅+余额,自定义) | 5 小时 / 周 / 月窗口使用量 + 月度剩余额度(USD),主数字为本月剩余百分比(3 位小数向下取整) |
23
31
 
24
32
  ## 功能
25
33
 
26
34
  - **常驻用量卡片**:右下角悬浮,一眼看到剩余量;可随意拖动,位置自动记住
27
35
  - **自动刷新**:每 30s 自动更新;想看最新数据点卡片上的 ↻ 立即重新查询
28
36
  - **多实例**:可以同时配置多个提供方(如多个 OpenCode Go 账号),点卡片 logo / 名称切换查看
29
- - **本地 Token 统计**:自读本机 `$DSH_HOME/sessions` 会话文件,按天聚合今天的 Token 用量(总量 + 按模型汇总),**只读本地、不联网**;数据落盘 `$DSH_HOME/provider-usage/daily-stats/`,历史天封存只算今天
37
+ - **本地 Token 统计**:自读本机 `$DSH_HOME/sessions` 会话文件,按天聚合今天的 Token 用量(按提供方分类 + 按模型汇总,汇总表跟随所选提供方),**只读本地、不联网**;数据落盘 `$DSH_HOME/provider-usage/daily-stats/`,历史天封存只算今天;设置页可一键重置统计缓存(不影响会话记录)
30
38
  - **状态提示**:正常 / 未更新 / 注意 / 错误,请求中显示「查询中」动画
31
39
  - **Key 安全**:密钥只在 DSH 进程内解析使用,界面上一律打码显示;自定义 Key 可加密保存在本机(AES-256 加密),不落明文
32
40
 
@@ -72,6 +80,10 @@ dsh plugin --profile web remove @loommii/dsh-provider-usage
72
80
  ## 使用说明
73
81
 
74
82
  - 提供方实例的增删改、Key 设置都在 **设置 → 用量中心** 里完成,改动即时生效,无需重启
83
+ - 每个实例可在**编辑**里用「启用」开关管理状态:**开 = 启用(绿)/ 关 = 暂停(灰)**,保存后生效;
84
+ 新添加的提供方默认启用。暂停后该实例**不再轮询查询**(卡片 30s 自动刷新跳过它),
85
+ 卡片切换菜单里也**不可选**;暂停的若是当前卡片实例会自动切到其他启用实例,全部暂停时卡片隐藏。
86
+ 启用/暂停只影响查询与卡片选择,编辑 / 删除 / Key 配置都原样保留
75
87
  - 设置页有两个标签:「提供方」管理实例,「用量统计」查看本地 Token 统计
76
88
  - 设置保存在本机浏览器中;删掉实例不会上传或泄露任何 Key
77
89
  - 卡片拖动后的位置会自动保存,下次打开还是老位置
package/cordis.patch.yml CHANGED
@@ -1,7 +1,10 @@
1
1
  # dsh-provider-usage M1: 插入插件行(自动 reconcile 进 profile bundles)
2
+ # v0.8.0: name 使用正式包名 @loommii/dsh-provider-usage(与 package.json 一致)。
3
+ # 0.7.0 及以前误用改名前的旧名 dsh-provider-usage,在 dsh 0.1.2-alpha 上
4
+ # 只能靠 profile node_modules 里的旧名残留符号链接解析——重装后插件即失联。
2
5
  - insert:
3
6
  - id: provider-usage
4
- name: dsh-provider-usage
7
+ name: '@loommii/dsh-provider-usage'
5
8
  config:
6
9
  baseUrl: https://opencode.ai/zen/go # {{baseUrl}}/v1/usage 即官方 usage 接口
7
10
  timeoutMs: 15000
package/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,119 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.8.0(2026-09-03)
4
+
5
+ ### 修复:重启/存储丢失后提供方清单被静默重建(严重数据丢失)
6
+
7
+ **原先的现象**:在用量中心删除某提供方(如 OpenCode Go)后,浏览器存储里的实例清单一旦丢失
8
+ (`dsh web` 重启且访问 origin 变化、清站点数据、浏览器驱逐临时存储、换浏览器等),下次打开页面
9
+ **已删除的默认实例(OpenCode Go/DeepSeek)会凭空复活,而自定义提供方(如 Command Code,`customOnly`
10
+ 不在默认清单里)永久消失**;其私有库 Key 成为孤儿,只能重新手动添加。用户实测即「之前删了 OpenCode Go,
11
+ 重启后变成 Command Code 被删了」。
12
+
13
+ **根因**:提供方实例清单此前**只存浏览器 localStorage**(host 端零持久化),且
14
+ `loadProviders()` 在存储 key 读取失败时静默走 `DEFAULT_PROVIDERS` 兜底重建默认清单;
15
+ host 也没有任何路由能枚举私有库 Key 供启动对账。
16
+
17
+ **修复后的现象**:提供方实例清单以 host 端 `$DSH_HOME/provider-usage/providers.json` 为
18
+ **单一事实源**(0600,与 credentials.json 同边界),localStorage 降级为写穿透缓存;
19
+ 浏览器存储全部丢失后重启,清单从 host 完整恢复,删除仍然生效、自定义实例不再消失。
20
+
21
+ - **host 新增 `GET/POST /api/provider-usage/providers`**(仅回环;POST 整单覆盖保存,幂等)
22
+ - 条目逐个规范化校验:未知 adapter / 缺 id 的脏条目剔除而非整单拒绝;`paused` 严格归一为布尔;
23
+ `ref/name/source/type` 长度钳制;上限 64 个实例
24
+ - 原子写(tmp → rename)+ 0600;损坏文件改名 `providers.json.corrupt-<ts>` 留证并按缺失处理,
25
+ 不卡死恢复流程;目录缺失时自动创建
26
+ - **client 启动恢复**:`useProviders` 首个订阅者触发一次 `GET /providers` 去重恢复;
27
+ host 有清单 → 应用为当前清单;host 无文件(首次升级 0.8.0)→ 把本地缓存清单一次性写回 host 固化;
28
+ host 失联 → 继续用本地缓存(乐观值),后续写操作会重试落盘
29
+ - **恢复竞态守卫**:恢复响应迟到且期间用户已增删改实例时,放弃 host 过期数据,
30
+ 不把用户刚删的实例复活(按会话内写计数判定)
31
+ - **`setProviders` 支持函数式更新**(修复同族丢更新竞态):所有写方(删除/导入/手动添加/编辑/
32
+ 旧数据自愈)改为基于写入时刻的最新清单计算,快速连删两个实例不再复活第一个、
33
+ 自愈写入不再覆盖刚添加的实例;传数组仍兼容旧用法
34
+ - host 场景 40(路由契约/落盘/跨重启恢复/脏条目剔除/400/损坏隔离,13 断言)
35
+
36
+ ### 小功能
37
+ - **提供方支持启用 / 暂停**:设置 → 用量中心 → 提供方设置,每个实例的**编辑卡**里新增「启用」开关
38
+ - 语义:**开 = 启用(开关亮绿色)/ 关 = 暂停(灰色)**;编辑后点「保存」生效
39
+ - 新添加(导入 / 手动)的提供方**默认启用**
40
+ - 暂停后该实例**不再参与轮询查询**(卡片 30s 自动刷新与设置页打开时的状态查询都跳过它),
41
+ 卡片**切换菜单中不可选**;若暂停的正是卡片当前实例,卡片立即切到另一个启用实例,
42
+ 全部暂停时卡片隐藏;恢复启用后立即重新生效(即时持久化,无需重启/刷新),
43
+ Key 等配置原样保留
44
+ - 视觉:编辑卡内官方样式 switch(开=绿 / 关=灰)+ 状态文案;列表中暂停行名称与状态点
45
+ 置灰为「已暂停」,编辑 / 删除仍可用(启用/暂停只影响查询与卡片选择)
46
+ - 启用/暂停状态随实例清单持久化到 host `providers.json`,跨浏览器/存储丢失后同样保留
47
+
48
+ ### 兼容性修复:适配 dsh 0.1.2-alpha(破坏性变更)
49
+
50
+ 本次 dsh `0.1.1-rc.2 → 0.1.2-alpha.4` 重构后,插件出现两类症状:升级当天 `dsh web` 启动即崩
51
+ (`duplicate loader entry id: provider-usage`),重新安装后变为 **host 半区正常(7 条 API 路由全部注册)
52
+ 但 Web UI 静默失联**(设置页无「用量中心」、无常驻卡片)。两个问题的根源都在插件自身的
53
+ 「包名迁移残留」与 alpha 新的客户端发现机制不兼容,本版全部修复:
54
+
55
+ - **`cordis.patch.yml` 的 entry `name` 改为正式包名 `@loommii/dsh-provider-usage`**
56
+ - v0.6.0 包名迁移(`dsh-provider-usage` → `@loommii/dsh-provider-usage`)时漏改了此文件,
57
+ 此后一直靠 profile `node_modules` 里改名前遗留的旧名符号链接 `dsh-provider-usage → 工作区`
58
+ 才能解析到包(rc.2 环境下侥幸可用)
59
+ - alpha.4 的客户端扫描(`dsh-client-modules` node 半区 `nearestPackage`)从 loader entry
60
+ 的模块位置向上查找 package.json,且要求 `name === entry 名`;旧名在重装后解析失败 →
61
+ 包被判定为「非客户端包」→ 永不进 `window.__DSH_BOOT__` 组合图 → UI 静默消失(host 不受影响)
62
+ - 升级首日的 `duplicate loader entry id` 崩溃亦与残留链接相关(旧名/新名两条路径同时进树,
63
+ loader `EntryGroup.update` 对同 id 双行 fail-loud);清理残留链接 + 本修复后消除
64
+ - **删除 `dsh.client.inject: ["@deepseek-ai/dsh-client-runtime"]` 死引用**
65
+ - alpha 重构将 `dsh-client-runtime` 并入 `dsh-client-modules`,该包已不存在;
66
+ 插件 client 半区从未 require 过 runtime 的任何导出,声明本就多余
67
+ - 两侧加载器对 graph 外的 inject 静默跳过,故这是卫生项而非故障源
68
+ - **`lib/client.js` 的 `__ModuleLoader__.load` 注册 id 同步改为 `@loommii/dsh-provider-usage`**
69
+ - alpha 的 boot graph 行 id = 包名,`serveBundle` 按 id 应答 combo bundle;注册 id 与
70
+ graph 行不一致时模块系统找不到 factory(对官方包此场景 fail-loud,对插件即静默失联)
71
+ - **peerDependencies 放宽**:`@deepseek-ai/dsh-host-webserver` `^0.1.0-rc.6` → `>=0.1.0-rc.6`
72
+ (npm prerelease semver 规则下 `^` 不匹配 `0.1.2-alpha.*`,消除安装时 peer 警告)
73
+
74
+ ### 兼容性核对(对 dsh-v0.1.2-alpha.4 源码逐项验证)
75
+
76
+ - `ctx.webServer.register({kind:'exact'|'prefix', path, handler(req,res)})` 契约不变(`dsh-host-webserver` 健在)
77
+ - `window.__ModuleLoader__.load({id, factory})` 仍是注册协议;`exports.inject`(`['slots']`)仍被
78
+ vendored cordis Loader 消费(`Cr.resolve(n.inject)`)
79
+ - 平台 seed 词 `react` / `react-dom/client` / `@deepseek-ai/cordis` / `dsh-client-ui-slots` 全保留
80
+ (React 仍为 18.3.1)
81
+ - `settings.section` 插槽契约保留(`dsh-client-ui-settings` contract/slots.ts)
82
+ - 会话文件格式:JSONL 后端仍默认(`.jsonl.zstd`、`SESSION_FORMAT_VERSION = 0`、目录布局、
83
+ `assistant/message` → `data.usage{inputTokens,outputTokens,cacheReadTokens}` 事件行全部不变),
84
+ 本地 Token 统计不受影响;alpha.3 已移除可选 SQLite 后端,此前的前瞻风险解除
85
+ - alpha.4 新增的「web 请求一次性 fetch 审批(SSRF 防护)」为 web 客户端侧,不影响 host 侧出站查询
86
+
87
+ ### 用户升级指引(从 ≤0.7.0 升级到 0.8.0)
88
+
89
+ 若升级 dsh 后曾出现 `duplicate loader entry id: provider-usage`,profile 的 `node_modules`
90
+ 里可能残留改名前的旧名符号链接。重装插件即可清理:
91
+
92
+ ```sh
93
+ dsh plugin --profile web remove @loommii/dsh-provider-usage
94
+ dsh plugin --profile web add github:loommii/dsh-provider-usage # 或 npm: @loommii/dsh-provider-usage
95
+ # 若 node_modules 下仍有 dsh-provider-usage/(旧名,非 @loommii scope)残留目录/链接,手动删除
96
+ ```
97
+
98
+ ## v0.7.0(2026-09-01)
99
+
100
+ ### 小功能
101
+ - **Command Code 卡片主数字改为「本月剩余百分比」**:原大数字为月已用百分比(整数、四舍五入),现改为月剩余百分比,保留 3 位小数向下取整(如 62.9432148907/70 = 89.91887…% → 显示 89.918%);标签「已用(每月)」→「剩余(每月)」
102
+ - host:`monthly` 新增 `remainingPct` 字段(剩余/总额 ×100,钳 [0,100] 后 `floor` 到 3 位小数);`usedPct` 保留(月窗口行仍显示已用口径)
103
+ - client:大数字读 `remainingPct`;连旧版服务端(无该字段)时按 `100 − usedPct` 同公式兜底;降级(无计划/未知计划)仍显示 `--`
104
+ - 回归:场景 20b/20c/20d 补断言,新增场景 20g(向下取整边界:1/70 → 1.428 非 1.429、69.999/70 → 99.998、满额 100、用完 0、超额钳 0)
105
+ - **用量统计页新增「重置统计」按钮**:「刷新」旁两段式确认(4s 未确认自动解除)后删除按天物化的派生缓存(天文件/游标/哨兵)并清空 30s 查询缓存,下次查询自动从会话原文全量重算;**会话原文只读不动**;天文件损坏 / 迁移异常 / 想强制重建时的自助兜底;新端点 `POST /api/provider-usage/reset-local-stats`(仅回环,POST-only)
106
+
107
+ ### 修复
108
+ - **用量统计页「按模型汇总」不随提供方选择切换(恒显示全部提供方的合并数据)**:chips/totals 按 `provider` 过滤,但 `byModel` 恒为全量聚合,且扁平 `{ 模型: 计数 }` 结构无提供方维度、服务端无法过滤
109
+ - host:`byModel` 升级为按提供方嵌套 `{ 提供方: { 模型: 计数 } }`(新会话折叠与天文件落盘同步新形态);响应按选中提供方过滤模型汇总,与 chips/totals 语义对齐;选中提供方恒返回嵌套形态(无数据 = 空对象)
110
+ - 存储迁移(验收回归修复):天文件格式升为 v2(`DAILY_VERSION = 2`);v0.6.1 旧扁平天文件按 deps 重折恢复真实提供方归属(会话文件仍是事实源,重写为嵌套形态);deps 缺失无法重折时才兜底迁入 `unknown` 桶;`.backfilled` 哨兵按版本判定,v1 哨兵触发一次性全量重折
111
+ - client:按选中提供方渲染模型表;「全部」时同名模型跨提供方合并求和展示;按响应形态自动判别、兼容 v0.6.1 旧服务端(无提供方维度时行为同旧版)
112
+ - 回归:host 场景 35(跨提供方同名模型、切换提供方模型表跟随)、场景 36a/36b(旧扁平天文件重折恢复归属 / 无 deps 兜底 unknown)、场景 37(v1 哨兵触发一次性全量重折)、场景 38(重置统计:清缓存重建 + 会话原文完好)
113
+ - 重置统计未清空天文件内存缓存(`dayCache`):重置后立即重查仍以旧天数据做种子,导致「重置对损坏天文件无效」且旧数字残留;现删盘上文件时一并清空(host 场景 39:投毒天文件在重置后从会话原文重建)
114
+ - 重置交互:确认重置后旧数据仍展示到新查询完成(沿用静默刷新策略,与「清空 → 重建」的用户预期相悖);现确认后立即清空展示进入「查询中」重建态。重置完成提示语此前无清除路径会常驻页面;现查询完成后自动消失
115
+ - 测试:user-agent 断言由硬编码 0.6.0 改为动态读取 `package.json` 版本(v0.6.1 升版时漏改,main 上存量失败)
116
+
3
117
  ## v0.6.1(2026-08-31)
4
118
 
5
119
  ### 修复