dsh-usage-state 0.4.3 → 0.4.5

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
@@ -2,200 +2,111 @@
2
2
 
3
3
  **中文** | [English](README.en.md)
4
4
 
5
- 在 [DSH(DeepSeek Harness)](https://github.com/deepseek-ai) 里一眼看到你的**账户余额**或**套餐额度**——就在输入框上方、与输入卡片同宽的一行(原生统计行与其弹窗原样保留、不做替换)。
5
+ [![CI](https://github.com/takboo/dsh-usage-state/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/takboo/dsh-usage-state/actions/workflows/ci.yml) [![npm](https://img.shields.io/npm/v/dsh-usage-state)](https://www.npmjs.com/package/dsh-usage-state)
6
6
 
7
- > A minimal DSH plugin that shows your account **balance** (API mode) or **coding-plan quota** (5h / 7d / 30d) for the model you are using, right under the composer.
7
+ > **版本说明**:**0.4.5** 包含此前的余额解析、凭据覆盖、缓存隔离和失败展示修复,详情见 [更新日志](CHANGELOG.md#045---2026-10-10)。使用0.4.4及更早版本的用户请升级至0.4.5或更新的兼容版本。
8
8
 
9
- ```
10
- 输入框上方独立一行: z.ai / GLM · 5h 12% (4h0m) ▓▓▓░░░░░ · 7d 59% (3d17h) ▓▓▓▓▓░░░
11
- DeepSeek · ¥58.13
12
- 悬停任意一段: Source DeepSeek · Mode API balance · Granted 0 · Topped up 58.13
9
+ 在 [DSH(DeepSeek Harness)](https://github.com/deepseek-ai/deepseek-harness) 输入框上方显示当前模型供应商的**账户余额**或**套餐额度**。状态行与输入卡片同宽,保留原生统计行和上下文计量器。
10
+
11
+ ```text
12
+ z.ai / GLM · ◔ 5h 12% (4h0m) · ◔ 7d 59% (3d17h)
13
+ DeepSeek · ¥58.13
13
14
  ```
14
15
 
15
- ## 特性
16
+ ## 功能
16
17
 
17
- - **零配置可用**:按 provider 自动识别该用哪个数据源与模式,DSH 里配过的密钥会被自动复用,不填任何东西就能看到读数。
18
- - **账户级配置**:余额与额度是账户级的,所以每个供应商只配一次(`自动` / `API` / `Coding Plan` / `隐藏`),模型清单只作展示。
19
- - **始终可见一行**:独占一行、位于输入框上方(DSH 0.2 把它挂在 `conversation.input.dock`,与输入卡片同宽居中;原生统计行与上下文计量器原样保留、不做替换),不需要悬停或点击。
20
- - **失败不撒谎**:请求失败时保留上次成功值并标明「多久之前 + ⚠」,**绝不显示 0 或伪造数据**;密钥无效、接口报错、网络不可达会分别给出可读原因。
21
- - **悬浮提示**:每段文字悬停显示一行放不下的信息——数据源与模式、窗口的绝对重置时刻、余额的赠送/充值构成、失败原因与原始消息。
22
- - **中英双语**:跟随 DSH 的语言设置(`locale.preference`),设置页与状态行都不含硬编码文案。
23
- - **只做显示**:只有余额与额度,没有成本统计、价格目录、历史账单、预算、峰谷计价。
18
+ - 已识别的供应商默认使用“自动”,复用DSH已有凭据;每个provider配置一次,模型列表用于参考。
19
+ - 显示余额、已用百分比、重置倒计时和SVG进度环;默认≥80%琥珀、≥95%红,可调整阈值和隐藏进度环。
20
+ - 同一有效账户身份的源请求失败时保留上次成功读数并标记陈旧、年龄和原因;RPC传输失败也会明确标出旧值。
21
+ - 悬停查看数据源、模式、重置时刻及余额构成;中英界面跟随DSH语言设置。
22
+ - 只显示账户当前读数,不做成本归因、价格目录、预算或历史账单。
24
23
 
25
24
  ## 安装
26
25
 
27
- **前置条件**:DSH `0.2.0-rc.2` 或更新的 0.2 线、Node ≥ 20,装进 `web` profile。包内**自带预构建的 `lib/`**,安装时没有构建步骤。
28
- **用 0.1 线请装 `0.3.2`**:DSH 0.2 换掉了整套设置 API(`settingsScope` / `settings.register` → `configForms` / 插件自带 `Config`),两套 API 没有重叠,所以 `0.4.0` 起只支持 0.2 线(原因见 [`docs/design-consensus.md`](docs/design-consensus.md) 修订 17)。
26
+ 要求:DSH版本满足 `>=0.2.0-rc.2 <0.3.0-0`,运行预构建插件声明Node≥20,安装到正在使用的 `web` profile。
27
+
28
+ 从npm安装:
29
29
 
30
30
  ```bash
31
- # 1) 安装(npm 包)
32
31
  dsh plugin --profile web add dsh-usage-state
33
-
34
- # 2) 重启 DSH —— 插件的 bundle patch 只在启动时读取
35
-
36
- # 3) 卸载
37
- dsh plugin --profile web remove dsh-usage-state
32
+ # 安装后重启DSH;bundle patch在启动时读取
38
33
  ```
39
34
 
40
- 直接从 GitHub 安装(内容与 npm 上的一致):
35
+ 也可从GitHub安装:
41
36
 
42
37
  ```bash
43
38
  dsh plugin --profile web add github:takboo/dsh-usage-state
44
39
  ```
45
40
 
46
- 本地开发时可直接装目录(宿主半边改完同样要重启 DSH):
41
+ 本仓库提交预构建产物,供DSH的GitHub安装路径直接使用。浮动GitHub分支可能领先于npm;复现问题时记录安装来源和版本/提交。不要把这一行为推广到所有npm Git安装。
47
42
 
48
- ```bash
49
- dsh plugin --profile web add /path/to/dsh-usage-state
50
- ```
43
+ 也可通过 [dsh-market](https://github.com/dsh-market/dsh-market) 搜索 `usage state` 或 `takboo` 安装;安装后核对实际来源与版本。本插件已进入 [精选列表](https://awesome-dsh-plugin.com);目录更新的可见时间取决于所用区域与成功构建。
51
44
 
52
- 也可以在 [dsh-market](https://github.com/dsh-market/dsh-market) 里搜索 `usage state`(或 `takboo`)一键安装——本插件已收录在精选列表 [awesome-dsh-plugin](https://awesome-dsh-plugin.com) 的「用量与计费」分类。
53
-
54
- 装好后界面没有出现?见 [`docs/adapters.md`](docs/adapters.md) 末尾的排查表。
45
+ DSH0.1使用历史版本0.3.2;该版本不适配0.2。0.4.0有RPC挂载缺陷,应升级至**最新兼容版**。完整版本记录见 [更新日志](CHANGELOG.md)。
55
46
 
56
47
  ## 快速开始
57
48
 
58
- 1. 打开 **设置 → 用量状态**:DSH 里配置的每个供应商一行。
59
- 2. 保持默认的 **自动** 即可(它会识别数据源与主模式);需要时改成 `API` / `Coding Plan` / `隐藏`,或用 ↑↓ 调整顺序。
60
- 3. 该供应商的账户读数会出现在输入框上方、与输入卡片同宽的一行。
61
-
62
- 若某个数据源需要端点或密钥(例如自建的 Sub2API、或尚未配置的 z.ai),展开该行的 **高级**:可覆盖数据源、填接口地址、指定凭据名、粘贴密钥(写入 DSH 凭据库)。
49
+ 1. 打开 **设置→用量状态**,查看供应商、模型和检测到的数据源。
50
+ 2. 对已识别供应商保留“自动”,或选择其支持的API、Coding Plan、隐藏。
51
+ 3. 自建Sub2API在高级区填写实例地址;已配置模型的当前读数显示在输入框上方。
52
+ 4. “立即刷新”用于主动查询;出错先查看设置页原因及 [排查指南](https://github.com/takboo/dsh-usage-state/blob/main/docs/troubleshooting.md)。
63
53
 
64
- 供应商名过长时,卡片头部只截断灰色的 provider id(悬停显示全文),右侧的 `↑ ↓ 自动 / Coding Plan / 隐藏` 不会换行。
54
+ 高级区提供数据源、端点、凭据名和密钥写入入口。显式provider覆盖优先于旧source默认;凭据描述与读取共用候选顺序,保存优先写显式ref。固定端点不试镜像,配置或凭据身份变化后清除旧身份读数。只读配置与凭据写入权限分别处理,保存/清除过程会显示失败并禁用重复操作。
65
55
 
66
- ## 截图
67
-
68
- 输入框上方独占一行的账户读数(图示为 OpenCode Zen Go 的三个窗口;卡片下方那排是 DSH 原生统计,两者互不干扰。悬停任意一段可看数据源、模式与绝对重置时刻):
69
-
70
- ![输入框上方独占一行的账户读数](https://raw.githubusercontent.com/takboo/dsh-usage-state/main/assets/screenshots/status-line.webp)
71
-
72
- 设置页:每个 provider 一行,默认「自动」会识别数据源与主模式:
73
-
74
- ![设置页的供应商列表](https://raw.githubusercontent.com/takboo/dsh-usage-state/main/assets/screenshots/settings-providers.webp)
56
+ ## 数据源
75
57
 
76
- 展开「高级」可覆盖数据源与端点、指定凭据名,或写入密钥(写入 DSH 凭据库):
77
-
78
- ![高级区](https://raw.githubusercontent.com/takboo/dsh-usage-state/main/assets/screenshots/settings-advanced.webp)
79
-
80
- ## 支持的数据源
81
-
82
- | 数据源 | API 模式 | Coding Plan 模式 | 凭据 |
58
+ | 来源 | API读数 | Coding Plan读数 | 主要凭据 |
83
59
  |---|---|---|---|
84
- | DeepSeek 官方 | 余额(CNY / USD) | —(官方无 coding plan) | `DEEPSEEK_API_KEY` |
85
- | z.ai / 智谱 GLM | — | 5h / 7d 已用 % | `ZAI_API_KEY` 等 |
86
- | Kimi 国内版 | Moonshot 按量余额 | Kimi Code 订阅窗口 | `MOONSHOT_API_KEY` / `KIMI_CODING_API_KEY` |
87
- | OpenCode Zen Go | — | 5h / 7d / 30d 已用 % | `OPENCODE_GO_API_KEY` / `OPENCODE_API_KEY` |
88
- | Sub2API(自建网关) | 余额 / key 配额 | `rate_limits[]` 的 5h / 7d | `SUB2API_API_KEY` + 实例地址 |
89
-
90
- - **z.ai 分区域**:coding plan 的 key 只在自己区域的站点有效(国内 `open.bigmodel.cn` / 国际 `api.z.ai`)。默认国内站,失败时自动镜像重试;也可在设置里钉死端点。
91
- - **OpenCode Zen Go**:读 `opencode.ai/zen/go/v1/usage` 的 `rolling` / `weekly` / `monthly`;通往同一账户的两条 DSH 路由(内置 `opencode-go` 与自定义 `opencode-go-deepseek`)只产生一个读数、只发一次请求。无订阅或密钥无效时报鉴权失败,而非 0%。
92
- - **其他厂商**(Claude Pro/Max、MiniMax、OpenRouter、Codex、Antigravity、Volcengine Ark…)未实现,但适配器契约与候选清单已备好,见 [`docs/adapters.md`](docs/adapters.md)。
60
+ | DeepSeek官方 | CNY/USD余额 | — | DEEPSEEK_API_KEY |
61
+ | z.ai / 智谱GLM | — | 5h / 7d已用% | ZAI_API_KEY等 |
62
+ | Kimi / Moonshot | 人民币余额 | Kimi Code订阅窗口 | MOONSHOT_API_KEY / KIMI_CODING_API_KEY |
63
+ | OpenCode Zen Go | — | 5h / 7d / 30d已用% | OPENCODE_GO_API_KEY / OPENCODE_API_KEY |
64
+ | Sub2API | 余额或key配额 | 按实例响应,如5h / 1d / 7d / 30d | SUB2API_API_KEY及实例地址 |
93
65
 
94
- ## 显示与交互
66
+ 百分比表示**已用**。z.ai的key分国内/国际区域;默认国内地址,失败可尝试镜像。当前缓存按数据源与模式合并,不能将不同密钥/实例视为已支持独立多账户。厂商端点、字段和扩展步骤见 [适配器指南](https://github.com/takboo/dsh-usage-state/blob/main/docs/adapters.md)。
95
67
 
96
- **位置**:输入框统计行的正下方(与原生统计行的几何对齐,跟随 DSH 的会话内容宽度)。读数**始终可见**,不依赖悬停,也不需要点击。
97
-
98
- **元素**:供应商标签 · 余额金额 + 币种 · 各窗口(5h / 7d / 30d)已用百分比 · 重置倒计时 · 迷你进度环 · 阈值变色(默认 ≥80% 黄、≥95% 红,可在设置里改)。
99
-
100
- **口径**:百分比一律是**已用**(与 z.ai / Claude 官方一致);API 模式显示余额,Coding Plan 模式显示该数据源实际提供的窗口(z.ai 与 Sub2API 是 5h / 7d,OpenCode Zen Go 多一个 30d)。
68
+ ## 截图
101
69
 
102
- **降级**:
70
+ OpenCode Zen Go三个窗口,独立状态行位于输入框上方:
103
71
 
104
- | 情况 | 显示 |
105
- |---|---|
106
- | 未配置 | 灰色「未配置」 |
107
- | 自建源缺端点 | 「需要先填写接口地址」 |
108
- | 请求失败(有旧值) | 旧值 + `12m ago` + `⚠`,悬停给出原因 |
109
- | 首次失败(无旧值) | 只显示本地化原因(不显示 0) |
72
+ ![输入框上方的账户读数](https://raw.githubusercontent.com/takboo/dsh-usage-state/main/assets/screenshots/status-line.webp)
110
73
 
111
- ## 刷新与网络
74
+ 每个provider一行的设置页,以及高级设置:
112
75
 
113
- - 回合结束后 **2 秒**刷新(等 provider 结算),空闲时每 **5 分钟**兜底。
114
- - 同一数据源 **60 秒**内不重复发真实请求;并发调用共享同一次在途请求;失败的请求不节流(可立即重试)。
115
- - 刷新间隔可在设置里调整。
76
+ ![供应商设置](https://raw.githubusercontent.com/takboo/dsh-usage-state/main/assets/screenshots/settings-providers.webp)
116
77
 
117
- ## 凭据与隐私
78
+ ![高级设置](https://raw.githubusercontent.com/takboo/dsh-usage-state/main/assets/screenshots/settings-advanced.webp)
118
79
 
119
- - 凭据探测顺序:设置页覆盖 → 供应商声明的 `apiKeyEnv`(`llm-deepseek` / `llm-pi-ai`)→ 数据源内置的 ref 名 → DSH 凭据库。
120
- - 密钥在设置页写入 **DSH 凭据库**(`~/.dsh/.credentials.yaml`);**插件自身不保存明文**。客户端只拿到「是否已配置 / 来源」,永远拿不到密钥值。
121
- - 环境变量提供的密钥是只读的:界面会禁用输入框并说明原因。
122
- - 插件读取的内容只有余额/额度数字与会话当前使用的模型,**不写会话日志**,也不上报任何数据。
80
+ ## 刷新与凭据
123
81
 
124
- ## 兼容性
82
+ 宿主默认在回合结束2秒后调度刷新,并有5分钟兜底定时器;浏览器每30秒拉取RPC镜像。普通轮询仅在读数缺失或有效身份变化时初始化查询,不把idle查询改成每分钟一次。成功请求的默认最小间隔为60秒,显式刷新可绕过,失败的主动重试不受该成功间隔限制;修改空闲间隔会重调定时器。
125
83
 
126
- - **只支持 DSH 0.2 线**:`engines.dsh` = `>=0.2.0-rc.2 <0.3.0-0`(市场读它做徽标与"可安装"判定)。0.2 之前的宿主请装 `0.3.2`。
127
- - `peerDependencies` 里有 `@deepseek-ai/dsh-settings`(`^0.2.0-rc.2`)与 `@deepseek-ai/schemastery`(`^3.18.2`):前者是**故意的**——平台的运行时安装闸门只读 `@deepseek-ai/dsh*` 的 peer,声明它可以让 0.1.x 宿主在**安装时**就拒绝,而不是装上之后把启动搞崩;两者都由平台提供,pnpm 可能为此打一行 `missing peer` 警告,属预期。
128
- - 版本 `0.4.2`:状态行迁到 `conversation.input.dock`,成为**输入框上方的独立一行**(0.1 时代它在输入框之下,而 0.2 把那个位置改成了与平台统计、上下文计量器共排的一排 pill——见修订 19/20)。
129
- - 版本 `0.4.1`:修 `0.4.0` 的读数缺失——客户端 RPC contribution 的参数 codec 少了 0.2 要求的 `create()`,`remote.usageState` 因此从未挂载,界面表现为**没有任何读数**并误报 `Mode not supported`。同一轮还让挂载失败不再被静默吞掉、catalog 未到时改说「读取中」,并把**真实 0.2 registry 契约测试**写进单测(修订 18)。**请用 `0.4.1`,不要用 `0.4.0`。**
130
- - 版本 `0.4.0`:迁到 0.2 原生设置模型(`Config` + `configForms`,宿主侧 `configEditor` 读跨条目配置)。验证方式与结论见 [`docs/implementation.md`](docs/implementation.md) §10、决策见 [`docs/design-consensus.md`](docs/design-consensus.md) 修订 17。
131
- - 版本 `0.3.2`(0.1 线末版):仅放宽 `engines.dsh`;**它声称的 0.2 兼容是错的**(0.3.2 在 0.2.0-rc.2 上会因 `settingsScope` 不存在而让整个 web 启动失败)。
132
- - 同名发布在 npm([`dsh-usage-state`](https://www.npmjs.com/package/dsh-usage-state)),也可从 GitHub 直接安装。
84
+ 插件查询余额/额度并读取当前模型及供应商目录,不写会话日志,也不向第三方上报这些读数。宿主不把已保存的密钥值返回浏览器;用户主动粘贴的草稿经DSH凭据服务写入所用home的凭据文件。环境变量值优先,通常不能在设置页修改。
133
85
 
134
86
  ## 已知限制
135
87
 
136
- - **Kimi、Sub2API 未经真机验证**(本机无凭据),代码与单测已就绪;Sub2API 的 `/v1/usage` 属未文档化接口,已按易错接口做容错。
137
- - **点击状态行不会打开设置**(客户端没有公开的"打开设置面板"服务);细节通过悬浮提示呈现。
138
- - **只在输入框上方展示,不覆盖历史**:状态行给出的是账户**当前**读数;插件不按回合、也不按时间保存历史读数,因此翻看旧回合时看不到"当时的余额"。若将来要做,会是以时间轴(而不是回合)为口径的单独决定。
139
- - 完整清单见 [`docs/implementation.md`](docs/implementation.md) §6。
88
+ 完整整改状态及验证范围见 [待办清单](https://github.com/takboo/dsh-usage-state/blob/main/docs/backlog.md)。
140
89
 
141
- ## 开发
90
+ - **旧版npm0.4.4及更早版本**:仍有provider凭据覆盖、pin/缓存身份、legacy隐藏、默认排序和失败展示缺陷;Moonshot≥100元仍会被除以100,DeepSeek非法金额仍可能变0。这些已在0.4.5修复;使用旧版请升级。旧版金额应与 [官方接口](https://platform.kimi.com/docs/api/balance.md) 核对。
91
+ - **账户范围**:仍按source+mode选择一个目标;配置身份隔离不等于新增独立多账户支持。
92
+ - **未验证范围**:Kimi Code/Sub2API真实账户、高阈值视觉、真实凭据写入和OpenCode非零路径仍缺活体验证;本地交互测试不代替这些验收。
93
+ - **范围**:无模型选择或隐藏时可以不显示;点击状态行不打开设置;不保存历史读数。
142
94
 
143
- ```bash
144
- npm install # 若 ~/.npm 不可写:npm install --cache /tmp/npm-cache
145
- npm test # node:test 直接跑 .ts / .tsx(需要 Node >= 22.6)
146
- npm run typecheck # tsc --noEmit
147
- npm run build # tsdown → lib/(宿主 index.js + typert.js,浏览器 client.js)
148
- npm run watch # 只重建 client.js;客户端会被 HMR 热替换,无需刷新页面
149
- ```
95
+ ## 开发与文档
150
96
 
151
- **本地回路(不需要发版)**:`scripts/dev-local.sh` 会在 `/tmp/dsh-dev` 建一个**一次性 profile**,把本仓库以 `link:` 装进去并起一个独立端口的宿主;你的真实 profile 完全不受影响。
97
+ 开发兼容下界为Node22.18+的22线或Node24.11+;规范构建固定Node **24.21.0**([.node-version](https://github.com/takboo/dsh-usage-state/blob/main/.node-version))和npm **11.19.1**(packageManager),高于运行产物声明。基本检查:
152
98
 
153
99
  ```bash
154
- npm run build # 先有 lib/
155
- scripts/dev-local.sh # 建/复用 dev profile + 起宿主,打印带 token 的 URL
156
- # 另一个终端:
157
- npm run watch # 保存即重建 client.js
100
+ npm ci
101
+ npm run typecheck
102
+ npm run build
103
+ npm test
158
104
  ```
159
105
 
160
- 客户端改动由 `dsh-client-hmr` 热替换(宿主每 500ms 轮询 bundle,经 `/plugins/events` 通知浏览器重载模块)——**不用重启、不用发版**;宿主改动(`src/host/**`、`src/index.ts`、`cordis.patch.yml`)需要重启该脚本。
161
-
162
- ⚠️ **这个 profile 没有凭据**:`$DSH_HOME/.credentials.yaml` 是 home 级的,全新 home 里没有 key,也没有你其它插件(**字体插件会改变排版所依赖的字形度量**)。所以它只适合**结构/宿主侧**核对。要看**实际观感**,把同一份构建装进已有 key 与插件的 profile,同样不用发版:
163
-
164
- ```bash
165
- npm pack --pack-destination /tmp --cache /tmp/npm-cache
166
- dsh plugin --profile web add /tmp/dsh-usage-state-<version>.tgz # file: 安装
167
- dsh plugin --profile web add "$PWD" # 想热迭代就用 link:
168
- dsh plugin --profile web add dsh-usage-state@<已发布版本> # 回到线上版本
169
- ```
170
-
171
- 只有当你**亲眼确认**改动可用之后,才值得动版本号与 npm 发布。
172
-
173
- 宿主机改动需要重启 DSH;客户端改动 `npm run watch` 即可。`lib/` 产物**必须提交进仓库**——`dsh plugin add github:...` 直接装仓库、没有构建步骤(`npm test` 里的构建守卫会检查信封、require 白名单与 `exports` 指向)。
174
-
175
- ```
176
- src/host/ 宿主:数据源适配器、缓存调度、凭据、设置、RPC
177
- src/client/ 浏览器:词典、状态行、设置页、状态镜像
178
- src/shared/ 两端共用:类型、配置、provider 解析、显示逻辑
179
- lib/ 构建产物(提交,供 github 安装)
180
- tests/ 与 src 对应;tests/build 校验的是产物本身
181
- ```
182
-
183
- ## 文档
184
-
185
- | 文档 | 内容 |
186
- |---|---|
187
- | [`docs/implementation.md`](docs/implementation.md) | 实现与验证总览:代码地图、决策→实现→测试→验证追溯、未验证清单、平台注意事项 |
188
- | [`docs/adapters.md`](docs/adapters.md) | 添加数据源:契约、四步流程、约定与坑、候选厂商、排查表 |
189
- | [`docs/design-consensus.md`](docs/design-consensus.md) | 设计共识与修订记录(每条决策的来龙去脉) |
190
- | [`docs/research/README.md`](docs/research/README.md) | 只读侦察报告索引(各厂商接口、被替代插件剖析、DSH RPC 契约) |
191
-
192
- ## 致谢与参考
106
+ 本地profile、watch和HMR前提见 [开发指南](https://github.com/takboo/dsh-usage-state/blob/main/docs/development.md)。按任务查找架构、设计、发布、审计和研究材料见 [文档导航](https://github.com/takboo/dsh-usage-state/blob/main/docs/index.md)。
193
107
 
194
- - **[`dsh-cost-meter`](https://github.com/Han-1413141/dsh-cost-meter)**(作者 Han-1413141,MIT 许可):本插件是它的**简化替代品**——只保留「看余额 / 看 Coding Plan 额度」这个展示需求,砍掉计费、价格目录、历史账单、预算与峰谷提醒等全部逻辑(见上面的「只做显示」)。
195
- 数据源端点、响应字段语义与若干兼容陷阱(OpenCode Zen Go 必须带浏览器 UA、z.ai 用 HTTP 200 + `{success:false}` 表达鉴权失败、旧 `coding_plan/usage` 兜底路径、sub2api 的 `rate_limits[]` 形态等)来自对 `dsh-cost-meter@1.7.28` 的**只读分析**,记录见 [`docs/research/dsh-cost-meter-analysis.md`](docs/research/dsh-cost-meter-analysis.md)。本仓库的实现是独立编写的 TypeScript,不是对其源码的照搬;但那些行为语义确实源自上述分析,应归功于上游。
196
- 若上游作者认为某处需要更明确的署名或授权,请提 issue,我会立刻调整。
197
- - **[DSH(DeepSeek Harness)](https://github.com/deepseek-ai)**:宿主平台。插件使用它的设置命名空间、凭据库、Typert RPC、插槽系统与 UI 原语(`@deepseek-ai/dsh-client-ui-primitives` 等)。
108
+ ## 致谢与许可
198
109
 
199
- ## 许可
110
+ 感谢 [dsh-cost-meter](https://github.com/Han-1413141/dsh-cost-meter)(Han-1413141,MIT)提供的数据源行为参考。本实现独立编写;端点、字段语义与兼容陷阱的溯源见 [上游分析](https://github.com/takboo/dsh-usage-state/blob/main/docs/research/dsh-cost-meter-analysis.md)。宿主平台为DSH,使用其设置、凭据、RPC、插槽和UI原语。
200
111
 
201
- [MIT](LICENSE)
112
+ [MIT](LICENSE)。问题和署名建议通过 [Issues](https://github.com/takboo/dsh-usage-state/issues) 提交。
package/docs/adapters.md CHANGED
@@ -1,100 +1,69 @@
1
- # 添加一个数据源(Adapter)
1
+ # 适配器指南
2
2
 
3
- `dsh-usage-state` 只实现五家数据源(DeepSeek、z.ai / GLM、Kimi、OpenCode Zen Go、Sub2API)。其余厂商靠 adapter 扩展——加一个适配器只需要两个文件加一行注册,插件其余部分(设置页列出的数据源、轮询目标、状态行渲染、双语文案)会自动跟上。
3
+ 适用于扩展或调整余额/额度来源。当前代码支持DeepSeek、z.ai、Kimi、OpenCode和Sub2API。用户安装排查请读 [排查指南](https://github.com/takboo/dsh-usage-state/blob/main/docs/troubleshooting.md),候选和整改状态见 [Backlog](https://github.com/takboo/dsh-usage-state/blob/main/docs/backlog.md)。本文件随npm包发布,代码和其他文档链接指向GitHub。下文描述当前Unreleased工作树,npm已发布0.4.4不包含本轮修复。
4
4
 
5
- ## 契约
5
+ ## Interface
6
+
7
+ 完整定义以 [sources/types](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/types.ts) 为准。下列是主要字段:
6
8
 
7
9
  ```ts
8
- export interface UsageSource {
9
- id: string // 稳定 id,写进设置与快照,如 'deepseek'
10
- displayName: string // 设置页显示名
11
- modes: readonly UsageMode[] // 能服务哪些模式:'api' | 'coding-plan'
12
- credentialRefs(mode): string[] // 按顺序探测的凭据名(POSIX 环境变量风格)
13
- defaultBaseUrl(mode): string | undefined // 无覆盖时使用的端点
14
- requiresBaseUrl?: boolean // true = 必须由用户填端点(自建服务)
15
- request(input: RequestInput): UsageRequest // 纯构造:url + headers
16
- fallbackRequests?(input): UsageRequest[] // 可选:区域镜像,按序重试
17
- parse(payload: unknown, mode): UsageReading // 纯解析:payload → 归一化读数
10
+ interface UsageSource {
11
+ id: string
12
+ displayName: string
13
+ modes: readonly UsageMode[]
14
+ credentialRefs(mode: UsageMode): readonly string[]
15
+ defaultBaseUrl(mode: UsageMode): string | undefined
16
+ requiresBaseUrl?: boolean
17
+ requiresApiKey?: boolean
18
+ request(input: RequestInput): UsageRequest
19
+ fallbackRequests?(input: RequestInput): readonly UsageRequest[]
20
+ parse(payload: unknown, mode: UsageMode): UsageReading
18
21
  }
19
22
  ```
20
23
 
21
- `request` 与 `parse` **刻意分离**:`parse` 不碰网络、不读时钟,所以每个厂商的返回结构都能用 fixture 无网单测。
24
+ request纯构造url和headers;parse只处理payload,不访问网络、凭据或当前时钟。RequestInput提供mode/apiKey及可选baseUrl/pinnedBaseUrl;读取Module负责执行、15s单次超时、HTTP错误分类和按序镜像。模板见 [新源骨架](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/_template.ts)。
22
25
 
23
- **端点优先级**(`resolveProvider` → `UsageTarget.baseUrl` → `read.ts`):
26
+ requiresApiKey在类型中存在,但当前宿主装配仍要求解析到凭据。不能只设置false就宣称已支持无key数据源;若要支持需补完整链路测试。
24
27
 
25
- 1. 插件设置里显式填的端点(`pinnedBaseUrl: true`,**不**再试镜像);
26
- 2. 供应商在 DSH 里声明的 `baseURL` 的 **origin**(例如 `https://api.z.ai/api/paas/v4` → `https://api.z.ai`;各适配器自己拼路径,所以只取 origin),仍会试镜像;
27
- 3. 适配器的 `defaultBaseUrl(mode)`;
28
- 4. 若前两者都没有且 `fallbackRequests` 存在,则按序尝试镜像;全部失败时抛出**最有信息量**的失败("有响应"的优先于"连不上"的)。
28
+ ## 增加数据源
29
29
 
30
- 区域类厂商(z.ai 国内/国际)就该用 `fallbackRequests`:key 只在自家站点有效,而错误站点返回的是**鉴权失败**而不是重定向。
30
+ 1. 用官方文档或固定commit接口定义查清端点、鉴权、单位、百分比口径、重置时刻及错误信封;没有官方资料时明确标社区观察/未验证。fixture去除key和账户信息。
31
+ 2. 新建Adapter和源测试,覆盖正常余额/窗口、真实0、非法/缺失字段、数字串、不同时间形态,以及HTTP200错误信封。无有效读数应抛SourceError,不填伪造0。
32
+ 3. 在 [注册表](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/index.ts) 注册;用目录测试验证元数据可JSON传输。基础设置选项可从该注册表派生。
33
+ 4. 若需要自动发现provider,在 [共享hints](https://github.com/takboo/dsh-usage-state/blob/main/src/shared/config.ts) 维护路由名/host规则并加冲突测试。注册Adapter不会自动识别新provider;宿主会将可用端点的origin提示经RPC传到两UI,扩展时校验不含userinfo/query/key。
34
+ 5. 若新增窗口id或用户文案,同步 [中英词典](https://github.com/takboo/dsh-usage-state/blob/main/src/client/locales.ts)。窗口顺序由Adapter决定,展示层按返回顺序显示。
35
+ 6. 补provider配置到fake fetch和RPC/显示的装配用例,再执行typecheck→build→test→pack。更新两份README、此文字段语义、Backlog验证状态和Unreleased。
31
36
 
32
- ## 加一个适配器的步骤
37
+ 开发命令和toolchain见 [开发指南](https://github.com/takboo/dsh-usage-state/blob/main/docs/development.md)。新Adapter不应引入成本计算或历史记录。
33
38
 
34
- 1. 复制 `src/host/sources/_template.ts` 为 `<id>.ts`,填四处:`id` / `displayName` / `modes` / `credentialRefs`,以及 `request` 与 `parse`(有区域镜像时再加 `fallbackRequests`)。
35
- 2. 先写测试:`tests/sources/<id>.test.ts`,用真实返回体做 fixture,覆盖
36
- - 正常形态(余额 / 窗口)
37
- - 字段缺失、`null`、字符串数字
38
- - 百分比口径(0..1 还是 0..100)
39
- - 重置时间的三种形态(unix 秒 / 毫秒 / ISO)
40
- - 不可解析时报 `SourceError('parse')`;**若厂商用 HTTP 200 返回错误信封,先识别它**(见下)
41
- 3. 在 `src/host/sources/index.ts` 的 `ALL_SOURCES` 里加一行。
42
- 4. `npm test` 与 `npx tsc --noEmit` 应该全绿;`docs/research/` 里已有的接口侦察可以补充你的注释。
39
+ ## 归一化和错误约定
43
40
 
44
- ## 约定与坑
41
+ - 金额保留明确币种/单位,使用来源契约换算,不按数值大小猜“元或分”。数值必须有限;缺失与真实0分别处理。
42
+ - percentage已是0..100时用clampPercent。normalizePercent把0..1视为比例,因此1的解释有歧义;只在来源确需这种兼容时使用并注明。不得拿它解析明确的1%。
43
+ - used/limit只在limit有效且>0时换算;remaining需按来源说明反转。unlimited哨兵不能展示成负余额/负额度。
44
+ - normalizeResetAt支持unix秒、毫秒及ISO。不存在的重置时间保持undefined,不猜一个日期。
45
+ - normalizeBaseUrl去尾斜杠和/vN;provider声明端点目前先取origin。若来源路径有额外前缀,必须测试请求路径,不能假定都从根目录挂载。
46
+ - SourceError区分config/auth/http/network/parse;detail不得主动包含密钥。401/403由读取层归auth,JSON语法错归parse、body中断归network;HTTP200错误信封由Adapter识别。宿主快照边界精确替换本次已解析key的原文回显,不可依赖猜测key形状的正则。
47
+ - source+mode仍只选择一个目标,宿主用有效签名、私有凭据摘要和generation隔离身份/在途;不表示新增多账户,也不在Adapter另建缓存。
48
+ - 共享平台实例按DSH指南声明peer+dev,独立第三方依赖放dependencies;不要将旧宿主link解析经验写成所有平台包都不能import的规则。
45
49
 
46
- - **归一化工具**(`src/host/sources/normalize.ts`):
47
- - `toFiniteNumber` — 数字与数字串 → 有限数;其余 `undefined`(空串也算缺失)。
48
- - `clampPercent` — 已知 0..100 时用它(如 z.ai 的 `percentage`)。
49
- - `normalizePercent` — 口径不明时用它:`<= 1` 视为小数(`1` → 100%),否则视为百分数。**注意这个歧义**,口径明确时不要用它。
50
- - `normalizeResetAt` — unix 秒 / 毫秒 / 数字串 / ISO → epoch 毫秒;非法 → `undefined`(绝不给假日期)。
51
- - `normalizeBaseUrl` — 去尾斜杠与尾部 `/vN`(用户常把 `.../v1` 贴进端点配置)。
52
- - **只抛 `SourceError`**:`kind` 决定界面文案(`config` / `auth` / `http` / `network` / `parse`)。HTTP 状态与网络异常由 `src/host/read.ts` 统一归类,适配器只需负责 `parse`。
53
- - **HTTP 200 里的错误信封**:不少国内厂商这么做(z.ai 是 `{success:false,code:1000,msg:'身份验证失败。'}`,另有 `{error:{code,message}}` 变体)。**不识别它就会把"密钥无效"报成"返回内容无法解析"**——真机上正是这么踩过一次。`code` 1000 视为鉴权失败 → `auth`,其他 → `http`,并把厂商原文放进 `message`(界面会连同本地化文案一起显示)。
54
- - **窗口 id 用规范键**:`5h` / `1d` / `7d` / `30d`。状态行按 5h → 1d → 7d 排序显示,未知 id 原样显示(文案在客户端词典,`window.<id>`,缺键时回退显示 id)。
55
- - **百分比一律是"已用"**(0..100,保留一位小数),与 z.ai / Claude 官方口径一致。
56
- - **不去重、不缓存**:`resolveTargets` 已按 `source + mode` 去重,`UsageStateStore` 已做最小间隔与在途去重。
57
- - **不要 import 平台包**:宿主半边在 `link:` 装载时解析不到 `@deepseek-ai/*`,请用 `ctx` 的结构化类型(见 `src/index.ts` 的 `PluginContextLike`)。
50
+ 归一化实现见 [normalize](https://github.com/takboo/dsh-usage-state/blob/main/src/host/sources/normalize.ts),网络实现见 [read](https://github.com/takboo/dsh-usage-state/blob/main/src/host/read.ts)。
58
51
 
59
- ## 已实现的数据源与坑
52
+ ## 当前端点和字段语义
60
53
 
61
- > 下表各厂商的端点、字段语义与陷阱,主体来自社区侦察与被替代插件 `dsh-cost-meter@1.7.28` 的只读分析([`research/dsh-cost-meter-analysis.md`](research/dsh-cost-meter-analysis.md));实现是独立编写的,署名与出处见 README 的「致谢与参考」。
54
+ | 来源 | 请求及鉴权 | 读数语义与证据 |
55
+ |---|---|---|
56
+ | DeepSeek API | GET {base}/user/balance,Bearer | balance_infos多币种;优先有余额/CNY并保留granted/topped_up。有限真实0合法,非法total_balance跳过;无有效行抛parse |
57
+ | z.ai Coding Plan | GET {base}/api/monitor/usage/quota/limit,Bearer;默认open.bigmodel.cn,镜像api.z.ai | unit3→5h、6→7d;TOKENS_LIMIT/CREDIT_LIMIT读percentage,不将TIME_LIMIT混为编码额度;兼容plans/flat形态。HTTP200 success:false/code1000归auth;属社区接口观察,已有历史真机证据 |
58
+ | Moonshot API | GET api.moonshot.cn/v1/users/me/balance,Bearer | 官方data.available_balance以人民币元计,voucher/cash也为元,cash可为负;当前余额不按大小换单位,保留已有两位小数舍入及兼容字段,不新增负available_balance支持 |
59
+ | Kimi Code | GET api.kimi.com/coding/v1/usages,Bearer及KimiCLI/1.6 UA | 当前parse从usage读周窗口、limits[].detail/window读滚动窗口;UA/形态来自社区观察,缺真实账户验证 |
60
+ | OpenCode Zen Go | GET opencode.ai/zen/go/v1/usage,Bearer及浏览器UA | usage或data.usage;rolling/weekly/monthly→5h/7d/30d;percent为0..100;resetsAt归一。历史真机观察UA缺失触发Cloudflare;非零/status非ok未验证 |
61
+ | Sub2API | GET {instance}/v1/usage,Bearer;必须实例地址 | quota.remaining或wallet balance、rate_limits窗口;subscription.daily/weekly/monthly→1d/7d/30d。单位缺失默认USD;parse目前未按mode过滤,可能同时返回余额和窗口;接口字段随实例版本变化 |
62
62
 
63
- | 数据源 | 模式 | 端点 | 关键坑 |
64
- |---|---|---|---|
65
- | DeepSeek 官方 | API | `GET {base}/user/balance` | 多币种返回顺序**不稳定**,固定取首条会让余额在真值与 0 之间跳;按"优先有余额 → 优先 CNY"挑选。官方无 coding plan、无窗口 |
66
- | z.ai / 智谱 GLM | Coding Plan | `GET {base}/api/monitor/usage/quota/limit`,默认 `https://open.bigmodel.cn`,镜像 `https://api.z.ai` | 主形态按 `unit` 映射:`3` → 5h、`6` → 7d;`TIME_LIMIT` 是月度 MCP 额度**不能**当编码窗口;`percentage` 已是 0..100;`unit` 缺失时按 `nextResetTime` 升序补位(0% 滚动窗口不带重置时间)。**鉴权失败是 HTTP 200 + `{success:false,code:1000}`**,且国内/国际的 key 互不通用(实测确认)→ 用 `fallbackRequests` 试镜像。端点非官方文档,社区逆向所得,已保留旧 `plans[]` 与扁平窗口两种兜底形态 |
67
- | Kimi / Moonshot | API + Coding Plan | `GET {base}/v1/users/me/balance`(余额)/ `GET {base}/coding/v1/usages`(订阅) | 一家两种读法:按模式分派端点与密钥。编程套餐端点**必须**带 `user-agent: KimiCLI/1.6`,否则拒绝。Moonshot 余额的"分/元"单位无字段可辨,当前沿用 `>= 100 视为分` 的启发式——**用真实 key 复核过再信任绝对值** |
68
- | OpenCode Zen Go | Coding Plan | `GET https://opencode.ai/zen/go/v1/usage` | **必须带浏览器 UA**(否则 Cloudflare error 1010 → 403,实测)。根对象是 `usage.{rolling,weekly,monthly}`(文档写作 `data.usage`,两种都接受),字段 `percent` 已是 0..100 已用百分比(用 `clampPercent`,**不要**用 `normalizePercent`,否则 `1` 会变成 100%),`resetsAt` 是 ISO 串。窗口名 `rolling/weekly/monthly` → 规范键 `5h/7d/30d`。401/403 = 无订阅或密钥无效(**不是** 0%)。`resolveProvider` 会把 provider 声明的 `baseURL` 归一成 origin、`normalizeBaseUrl` 会剥掉尾部 `/vN`,所以请求前还要再剥掉 `/zen/go` 尾段,否则出现 `/zen/go/zen/go/v1/usage` |
69
- | Sub2API(自建) | API + Coding Plan | `GET {base}/v1/usage` | 未文档化的内部接口,字段曾出现前后端漂移;所有字段可选、未知结构降级。一个接口覆盖两种模式:`quota.remaining` 或钱包 `balance` 视为 USD 余额,`rate_limits[]` 的 `window` 直接是 `5h`/`1d`/`7d`(美元计价,百分比自算 `used/limit`)。`requiresBaseUrl: true` |
63
+ [Moonshot官方余额定义](https://platform.kimi.com/docs/api/balance.md)优先于早期社区“分/元不明”的研究。[DeepSeek官方余额接口](https://api-docs.deepseek.com/api/get-user-balance)提供字段契约。其余社区/历史证据见 [研究索引](https://github.com/takboo/dsh-usage-state/blob/main/docs/research/README.md) 和 [上游分析](https://github.com/takboo/dsh-usage-state/blob/main/docs/research/dsh-cost-meter-analysis.md),不能把逆向观察称为厂商保证。
70
64
 
71
- ## 候选数据源(未实现)
65
+ ## 端点策略和测试缺口
72
66
 
73
- 以下接口已侦察确认存在(详见 `docs/research/provider-balance-quota-apis.md`),但都**不适合 v1 的凭据模型**(本插件只支持环境变量风格字符串密钥,经 DSH 凭据库存取):
67
+ 当前共享解析以显式provider覆盖→legacy source默认→provider声明origin/Adapter默认确定有效目标。ref和pin贯通读取/凭据描述;显式pin使fallbackRequests返回空列表。相关回归见 [A01](https://github.com/takboo/dsh-usage-state/blob/main/docs/backlog.md#a01)、[A03](https://github.com/takboo/dsh-usage-state/blob/main/docs/backlog.md#a03)。
74
68
 
75
- | 厂商 | 端点 | 为什么还没做 |
76
- |---|---|---|
77
- | Anthropic Claude Pro/Max | `GET https://api.anthropic.com/api/oauth/usage` | 必须 OAuth 访问令牌(`user:profile`),普通 API key 无法调用;需要新增 OAuth 凭据的存取通道。返回 `five_hour` / `seven_day` 的 `utilization` + `resets_at`,是最标准的 5h/7d 形态 |
78
- | Codex / ChatGPT 订阅 | `GET https://chatgpt.com/backend-api/wham/usage` | 需要 ChatGPT OAuth;`rate_limit.primary_window`(18000s)与 `secondary_window`(604800s) |
79
- | MiniMax Token Plan | `GET https://www.minimaxi.com/v1/token_plan/remains` | 纯 API key,**可以**按本模板实现:`current_interval_remaining_percent` / `current_weekly_remaining_percent`(注意是"剩余",要反转) |
80
- | Kimi Code 国际版 | `GET https://api.kimi.com/coding/v1/usages` | 与已实现的国内版同端点,无需新增 |
81
- | OpenRouter | `GET {base}/api/v1/credits`、`/api/v1/key` | 余额接口按官方 OpenAPI 需要 management key;`/key` 的 `limit_remaining/limit` 可换算已用 %,但无 5h/7d 概念 |
82
- | SiliconFlow | `GET https://api.siliconflow.cn/v1/user/info` | 纯 CNY 余额,可照模板实现 |
83
- | CommandCode | `GET https://api.commandcode.ai/alpha/billing/credits` | 返回 `windowLimits.{fiveHour,weekly}.{used,cap,resetAt}`,形态与我们的窗口模型几乎一致 |
84
- | Volcengine Ark Coding Plan | `open.volcengineapi.com` 控制面 | 需要 AK/SK HMAC 签名,凭据形态超出 v1 |
85
- | Gemini Code Assist / Antigravity | 私有端点 / 本地语言服务 | OAuth 或本地进程通信,且 Google 已关闭个人版 CLI OAuth |
86
-
87
- > OpenCode Zen 已实现(见上表),不再是候选;它只差一个 `user-agent` 就能直连,不需要 OAuth。
88
-
89
- **优先级建议**:MiniMax → CommandCode → SiliconFlow(都是纯 API key、返回结构简单),再考虑为 Anthropic / Codex 增加 OAuth 凭据通道。
90
-
91
- ## 排查
92
-
93
- | 现象 | 原因 |
94
- |---|---|
95
- | 状态行显示"缺少密钥或接口地址" | 该 source 的所有候选凭据都未配置,或 `requiresBaseUrl` 的源没填端点。设置页每行会显示候选凭据名与配置状态 |
96
- | 显示"密钥无效" | 请求返回 401/403,或厂商在 HTTP 200 里返回鉴权失败信封(z.ai 的 `code:1000`)。**先确认 key 与站点区域匹配**(国内 key 打国际站必然失败) |
97
- | 显示旧值 + ⚠ | 最近一次刷新失败,展示的是上次成功值(鼠标悬停可看原因) |
98
- | 记录里有数据但界面为空 | 该模型在设置页是 `隐藏`,或模型不在 `models` 配置里(未配置 → 状态行不显示) |
99
- | `dsh plugin add github:...` 装完没有界面 | `lib/` 未提交或 `package.json` 的 `dsh` 字段缺失;`npm test` 里的构建守卫会检查这些 |
100
- | 界面显示"未配置"但环境里明明有 key | 宿主行可能拿不到平台的 `credentials` 服务(作用域问题)。插件会退化为直读 env → `~/.dsh/.credentials.yaml`,并把来源标注为 `env (direct)` / `file (direct)`;看到该标注即说明走的兜底 |
69
+ 无pin时首站失败仍按序尝试fallback,不仅在未配置端点时发生。扩展必须同时测Adapter、读取层和入口,覆盖真实HTTP、凭据选择、配置切换及错误边界,不能只证明一个模块接受参数。以上是Unreleased行为,npm0.4.4仍旧实现。