dsh-usage-state 0.3.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/LICENSE +21 -0
- package/README.en.md +126 -0
- package/README.md +158 -0
- package/cordis.patch.yml +7 -0
- package/docs/adapters.md +100 -0
- package/lib/client.js +1828 -0
- package/lib/index.js +1764 -0
- package/lib/typert.js +120 -0
- package/package.json +92 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 takboo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# dsh-usage-state
|
|
2
|
+
|
|
3
|
+
See your **account balance** or **coding-plan quota** at a glance in [DSH (DeepSeek Harness)](https://github.com/deepseek-ai) — right under the composer stats row.
|
|
4
|
+
|
|
5
|
+
> 中文说明见 [README.md](README.md)。
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
below the composer stats: z.ai / GLM · 5h 12% (4h0m) ▓▓▓░░░░░ · 7d 59% (3d17h) ▓▓▓▓▓░░░
|
|
9
|
+
DeepSeek · ¥58.13
|
|
10
|
+
hover any segment: Source DeepSeek · Mode API balance · Granted 0 · Topped up 58.13
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Works with zero configuration**: the plugin figures out which data source and mode a provider needs, and reuses the API key DSH already has.
|
|
16
|
+
- **Configured per provider, not per model** — readings are account-level, so each provider gets one setting: `Auto` / `API` / `Coding Plan` / `Hidden`. The model list is informational.
|
|
17
|
+
- **One line, always visible**: directly below the composer's stats row (the native stats row and its popover are left untouched, not replaced) — no hover, no click.
|
|
18
|
+
- **Never invents data**: a failed refresh keeps the last good value and marks it stale (`12m ago ⚠`); rejected keys, endpoint errors and network problems each get a readable reason.
|
|
19
|
+
- **Hover details**: source and mode, the window's absolute reset time, the granted/topped-up split of a balance, the failure reason with the provider's own message.
|
|
20
|
+
- **Bilingual** (zh / en), following the DSH locale setting.
|
|
21
|
+
- **Display only**: balances and quotas, nothing else — no cost accounting, pricing catalog, history or budgets.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
**Requirements**: DSH `0.1.5-rc.2` or newer, Node ≥ 20, installed into the `web` profile. The package **ships the prebuilt `lib/`**, so installation has no build step.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# 1) install (npm package)
|
|
29
|
+
dsh plugin --profile web add dsh-usage-state
|
|
30
|
+
|
|
31
|
+
# 2) restart DSH — the plugin's bundle patch is read at startup
|
|
32
|
+
|
|
33
|
+
# 3) remove
|
|
34
|
+
dsh plugin --profile web remove dsh-usage-state
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Installing straight from GitHub works too (same content as the npm package):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
dsh plugin --profile web add github:takboo/dsh-usage-state
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For development, a local path works too (host-side changes still need a DSH restart):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
dsh plugin --profile web add /path/to/dsh-usage-state
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Nothing showing up after installing? See the troubleshooting table at the end of [`docs/adapters.md`](docs/adapters.md).
|
|
50
|
+
|
|
51
|
+
## Quick start
|
|
52
|
+
|
|
53
|
+
1. Open **Settings → Usage state**: one row per provider configured in DSH.
|
|
54
|
+
2. Leave it on **Auto** (it detects the data source and its primary mode), or pick `API` / `Coding Plan` / `Hidden`; use ↑↓ to reorder.
|
|
55
|
+
3. The reading appears directly below the composer's stats row.
|
|
56
|
+
|
|
57
|
+
If a source needs an endpoint or a key (a self-hosted Sub2API, or a provider without a credential yet), expand that row's **Advanced** block to override the source, set the endpoint, name the credential, or paste a key (written to the DSH credential store).
|
|
58
|
+
|
|
59
|
+
A very long provider name never pushes the controls around: the card header stays on one line and only the grey provider id is truncated (hover it for the full text).
|
|
60
|
+
|
|
61
|
+
## Supported sources
|
|
62
|
+
|
|
63
|
+
| Source | API mode | Coding-plan mode | Credential |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| DeepSeek official | balance (CNY / USD) | — (no coding plan) | `DEEPSEEK_API_KEY` |
|
|
66
|
+
| z.ai / Zhipu GLM | — | 5h / 7d used % | `ZAI_API_KEY` and friends |
|
|
67
|
+
| Kimi (China) | Moonshot pay-as-you-go balance | Kimi Code subscription windows | `MOONSHOT_API_KEY` / `KIMI_CODING_API_KEY` |
|
|
68
|
+
| OpenCode Zen Go | — | 5h / 7d / 30d used % | `OPENCODE_GO_API_KEY` / `OPENCODE_API_KEY` |
|
|
69
|
+
| Sub2API (self-hosted) | balance / key quota | 5h / 7d from `rate_limits[]` | `SUB2API_API_KEY` + instance URL |
|
|
70
|
+
|
|
71
|
+
- **z.ai is regional**: a coding-plan key only works on its own region (`open.bigmodel.cn` for China, `api.z.ai` globally). China is the default; the other host is tried as a mirror, and you can pin an endpoint in the settings.
|
|
72
|
+
- **OpenCode Zen Go** reads `rolling` / `weekly` / `monthly` from `opencode.ai/zen/go/v1/usage`. Both DSH routes into the same account (built-in `opencode-go` and the custom `opencode-go-deepseek`) produce one reading and one request. A missing subscription or a rejected key is reported as an auth failure, never as 0%.
|
|
73
|
+
- Other vendors (Claude Pro/Max, MiniMax, OpenRouter, Codex, Antigravity, Volcengine Ark, …) are not implemented, but the adapter contract and a candidate list are ready: see [`docs/adapters.md`](docs/adapters.md).
|
|
74
|
+
|
|
75
|
+
## Display, refresh, credentials
|
|
76
|
+
|
|
77
|
+
- **Placement**: directly below the composer's stats row, aligned with the native row's geometry. The line is always visible — it never depends on hover or a click.
|
|
78
|
+
- **Elements**: provider label · balance + currency · each window (5h / 7d / 30d) used % · reset countdown · mini progress bar · threshold colours (defaults: amber ≥80%, red ≥95%).
|
|
79
|
+
- **Semantics**: percentages are always *used*; balances only appear in API mode, and coding-plan mode shows the windows the source actually has (5h / 7d for z.ai and Sub2API, plus 30d for OpenCode Zen Go); a stale reading shows its age instead of hiding.
|
|
80
|
+
- **Refresh**: 2s after a turn ends, plus a 5-minute idle fallback; at most one real request per source per 60s, in-flight calls are shared, failures are not throttled.
|
|
81
|
+
- **Credentials**: override → the provider's declared `apiKeyEnv` → the source's built-in ref → DSH credential store. Keys are written to `~/.dsh/.credentials.yaml`; **this plugin never stores a plaintext key** and the browser never receives a key value.
|
|
82
|
+
|
|
83
|
+
## Compatibility
|
|
84
|
+
|
|
85
|
+
- Verified against DSH `0.1.5-rc.2`, Node ≥ 20; the DSH requirement is declared as `>=0.1.5-rc.1 <0.2.0-0` through `engines.dsh` (this is what dsh-market's compatibility badge reads).
|
|
86
|
+
- Published to npm as [`dsh-usage-state`](https://www.npmjs.com/package/dsh-usage-state); GitHub installs work too.
|
|
87
|
+
- Version `0.3.0`: DeepSeek, z.ai and OpenCode Zen Go are verified against live accounts; see the limitations below.
|
|
88
|
+
|
|
89
|
+
## Limitations
|
|
90
|
+
|
|
91
|
+
- **Kimi and Sub2API are not verified against live accounts yet** (no credentials on the author's machine); their `/v1/usage` style endpoints are undocumented and parsed defensively.
|
|
92
|
+
- **Clicking the line does not open settings** (the platform exposes no public "open settings panel" service); details are in the hover tooltip.
|
|
93
|
+
- **Current reading only**: the line reports the account's latest value. The plugin keeps no per-turn and no per-time history, so scrolling back through old turns shows no "balance at that moment". Account history, if ever added, would be keyed by time rather than by turn — a separate decision.
|
|
94
|
+
- Full list: [`docs/implementation.md`](docs/implementation.md) §6.
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm install # add --cache /tmp/npm-cache if ~/.npm is not writable
|
|
100
|
+
npm test # node:test runs .ts / .tsx directly (needs Node >= 22.6)
|
|
101
|
+
npm run typecheck # tsc --noEmit
|
|
102
|
+
npm run build # tsdown → lib/ (host index.js + typert.js, browser client.js)
|
|
103
|
+
npm run watch # rebuilds client.js only; client changes hot-reload, no page refresh
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Host-side changes need a DSH restart; client-side changes do not. The `lib/` output is **committed on purpose**: `dsh plugin add github:...` installs straight from the repository with no build step, and `npm test` guards the bundle envelope, the require allow-list and the `exports` targets.
|
|
107
|
+
|
|
108
|
+
## Docs
|
|
109
|
+
|
|
110
|
+
| Document | Contents |
|
|
111
|
+
|---|---|
|
|
112
|
+
| [`docs/implementation.md`](docs/implementation.md) | Implementation and verification overview: code map, decision → code → test → verification traceability, open items |
|
|
113
|
+
| [`docs/adapters.md`](docs/adapters.md) | Adding a data source: contract, workflow, pitfalls, candidate vendors, troubleshooting |
|
|
114
|
+
| [`docs/design-consensus.md`](docs/design-consensus.md) | Design consensus and its revision log (Chinese) |
|
|
115
|
+
| [`docs/research/README.md`](docs/research/README.md) | Read-only research index (vendor APIs, the replaced plugin, DSH RPC contract) |
|
|
116
|
+
|
|
117
|
+
## Acknowledgements
|
|
118
|
+
|
|
119
|
+
- **[`dsh-cost-meter`](https://github.com/Han-1413141/dsh-cost-meter)** by Han-1413141 (MIT): this plugin is a **simplified replacement** for it. It keeps the "show me my balance / coding-plan quota" need and drops everything else — cost accounting, pricing catalog, history, budgets, peak/off-peak alerts.
|
|
120
|
+
The data-source endpoints, the meaning of the response fields and several compatibility pitfalls (OpenCode Zen Go needs a browser UA, z.ai reports auth failure as HTTP 200 + `{success:false}`, the legacy `coding_plan/usage` fallback, sub2api's `rate_limits[]` shape, …) come from a **read-only analysis** of `dsh-cost-meter@1.7.28`, recorded in [`docs/research/dsh-cost-meter-analysis.md`](docs/research/dsh-cost-meter-analysis.md). The implementation here is independently written TypeScript rather than copied source, but those behaviours are upstream's work and credit belongs there.
|
|
121
|
+
If the upstream author wants a clearer attribution or a different arrangement, open an issue and it will be fixed.
|
|
122
|
+
- **[DSH (DeepSeek Harness)](https://github.com/deepseek-ai)**: the host platform. The plugin relies on its settings namespace, credential store, Typert RPC, slot system and UI primitives (`@deepseek-ai/dsh-client-ui-primitives` and friends).
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
[MIT](LICENSE)
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# dsh-usage-state
|
|
2
|
+
|
|
3
|
+
**中文** | [English](README.en.md)
|
|
4
|
+
|
|
5
|
+
在 [DSH(DeepSeek Harness)](https://github.com/deepseek-ai) 里一眼看到你的**账户余额**或**套餐额度**——就在输入框统计行的正下方。
|
|
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.
|
|
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
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 特性
|
|
16
|
+
|
|
17
|
+
- **零配置可用**:按 provider 自动识别该用哪个数据源与模式,DSH 里配过的密钥会被自动复用,不填任何东西就能看到读数。
|
|
18
|
+
- **账户级配置**:余额与额度是账户级的,所以每个供应商只配一次(`自动` / `API` / `Coding Plan` / `隐藏`),模型清单只作展示。
|
|
19
|
+
- **始终可见一行**:固定在输入框统计行的正下方(原生统计行与其弹窗原样保留、不做替换),不需要悬停或点击。
|
|
20
|
+
- **失败不撒谎**:请求失败时保留上次成功值并标明「多久之前 + ⚠」,**绝不显示 0 或伪造数据**;密钥无效、接口报错、网络不可达会分别给出可读原因。
|
|
21
|
+
- **悬浮提示**:每段文字悬停显示一行放不下的信息——数据源与模式、窗口的绝对重置时刻、余额的赠送/充值构成、失败原因与原始消息。
|
|
22
|
+
- **中英双语**:跟随 DSH 的语言设置(`locale.preference`),设置页与状态行都不含硬编码文案。
|
|
23
|
+
- **只做显示**:只有余额与额度,没有成本统计、价格目录、历史账单、预算、峰谷计价。
|
|
24
|
+
|
|
25
|
+
## 安装
|
|
26
|
+
|
|
27
|
+
**前置条件**:DSH `0.1.5-rc.2` 或更高、Node ≥ 20,装进 `web` profile。包内**自带预构建的 `lib/`**,安装时没有构建步骤。
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 1) 安装(npm 包)
|
|
31
|
+
dsh plugin --profile web add dsh-usage-state
|
|
32
|
+
|
|
33
|
+
# 2) 重启 DSH —— 插件的 bundle patch 只在启动时读取
|
|
34
|
+
|
|
35
|
+
# 3) 卸载
|
|
36
|
+
dsh plugin --profile web remove dsh-usage-state
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
直接从 GitHub 安装(内容与 npm 上的一致):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
dsh plugin --profile web add github:takboo/dsh-usage-state
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
本地开发时可直接装目录(宿主半边改完同样要重启 DSH):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
dsh plugin --profile web add /path/to/dsh-usage-state
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
装好后界面没有出现?见 [`docs/adapters.md`](docs/adapters.md) 末尾的排查表。
|
|
52
|
+
|
|
53
|
+
## 快速开始
|
|
54
|
+
|
|
55
|
+
1. 打开 **设置 → 用量状态**:DSH 里配置的每个供应商一行。
|
|
56
|
+
2. 保持默认的 **自动** 即可(它会识别数据源与主模式);需要时改成 `API` / `Coding Plan` / `隐藏`,或用 ↑↓ 调整顺序。
|
|
57
|
+
3. 该供应商的账户读数会出现在输入框统计行的正下方。
|
|
58
|
+
|
|
59
|
+
若某个数据源需要端点或密钥(例如自建的 Sub2API、或尚未配置的 z.ai),展开该行的 **高级**:可覆盖数据源、填接口地址、指定凭据名、粘贴密钥(写入 DSH 凭据库)。
|
|
60
|
+
|
|
61
|
+
供应商名过长时,卡片头部只截断灰色的 provider id(悬停显示全文),右侧的 `↑ ↓ 自动 / Coding Plan / 隐藏` 不会换行。
|
|
62
|
+
|
|
63
|
+
## 支持的数据源
|
|
64
|
+
|
|
65
|
+
| 数据源 | API 模式 | Coding Plan 模式 | 凭据 |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| DeepSeek 官方 | 余额(CNY / USD) | —(官方无 coding plan) | `DEEPSEEK_API_KEY` |
|
|
68
|
+
| z.ai / 智谱 GLM | — | 5h / 7d 已用 % | `ZAI_API_KEY` 等 |
|
|
69
|
+
| Kimi 国内版 | Moonshot 按量余额 | Kimi Code 订阅窗口 | `MOONSHOT_API_KEY` / `KIMI_CODING_API_KEY` |
|
|
70
|
+
| OpenCode Zen Go | — | 5h / 7d / 30d 已用 % | `OPENCODE_GO_API_KEY` / `OPENCODE_API_KEY` |
|
|
71
|
+
| Sub2API(自建网关) | 余额 / key 配额 | `rate_limits[]` 的 5h / 7d | `SUB2API_API_KEY` + 实例地址 |
|
|
72
|
+
|
|
73
|
+
- **z.ai 分区域**:coding plan 的 key 只在自己区域的站点有效(国内 `open.bigmodel.cn` / 国际 `api.z.ai`)。默认国内站,失败时自动镜像重试;也可在设置里钉死端点。
|
|
74
|
+
- **OpenCode Zen Go**:读 `opencode.ai/zen/go/v1/usage` 的 `rolling` / `weekly` / `monthly`;通往同一账户的两条 DSH 路由(内置 `opencode-go` 与自定义 `opencode-go-deepseek`)只产生一个读数、只发一次请求。无订阅或密钥无效时报鉴权失败,而非 0%。
|
|
75
|
+
- **其他厂商**(Claude Pro/Max、MiniMax、OpenRouter、Codex、Antigravity、Volcengine Ark…)未实现,但适配器契约与候选清单已备好,见 [`docs/adapters.md`](docs/adapters.md)。
|
|
76
|
+
|
|
77
|
+
## 显示与交互
|
|
78
|
+
|
|
79
|
+
**位置**:输入框统计行的正下方(与原生统计行的几何对齐,跟随 DSH 的会话内容宽度)。读数**始终可见**,不依赖悬停,也不需要点击。
|
|
80
|
+
|
|
81
|
+
**元素**:供应商标签 · 余额金额 + 币种 · 各窗口(5h / 7d / 30d)已用百分比 · 重置倒计时 · 迷你进度条 · 阈值变色(默认 ≥80% 黄、≥95% 红,可在设置里改)。
|
|
82
|
+
|
|
83
|
+
**口径**:百分比一律是**已用**(与 z.ai / Claude 官方一致);API 模式显示余额,Coding Plan 模式显示该数据源实际提供的窗口(z.ai 与 Sub2API 是 5h / 7d,OpenCode Zen Go 多一个 30d)。
|
|
84
|
+
|
|
85
|
+
**降级**:
|
|
86
|
+
|
|
87
|
+
| 情况 | 显示 |
|
|
88
|
+
|---|---|
|
|
89
|
+
| 未配置 | 灰色「未配置」 |
|
|
90
|
+
| 自建源缺端点 | 「需要先填写接口地址」 |
|
|
91
|
+
| 请求失败(有旧值) | 旧值 + `12m ago` + `⚠`,悬停给出原因 |
|
|
92
|
+
| 首次失败(无旧值) | 只显示本地化原因(不显示 0) |
|
|
93
|
+
|
|
94
|
+
## 刷新与网络
|
|
95
|
+
|
|
96
|
+
- 回合结束后 **2 秒**刷新(等 provider 结算),空闲时每 **5 分钟**兜底。
|
|
97
|
+
- 同一数据源 **60 秒**内不重复发真实请求;并发调用共享同一次在途请求;失败的请求不节流(可立即重试)。
|
|
98
|
+
- 刷新间隔可在设置里调整。
|
|
99
|
+
|
|
100
|
+
## 凭据与隐私
|
|
101
|
+
|
|
102
|
+
- 凭据探测顺序:设置页覆盖 → 供应商声明的 `apiKeyEnv`(`llm-deepseek` / `llm-pi-ai`)→ 数据源内置的 ref 名 → DSH 凭据库。
|
|
103
|
+
- 密钥在设置页写入 **DSH 凭据库**(`~/.dsh/.credentials.yaml`);**插件自身不保存明文**。客户端只拿到「是否已配置 / 来源」,永远拿不到密钥值。
|
|
104
|
+
- 环境变量提供的密钥是只读的:界面会禁用输入框并说明原因。
|
|
105
|
+
- 插件读取的内容只有余额/额度数字与会话当前使用的模型,**不写会话日志**,也不上报任何数据。
|
|
106
|
+
|
|
107
|
+
## 兼容性
|
|
108
|
+
|
|
109
|
+
- 实测环境:DSH `0.1.5-rc.2`,Node ≥ 20;DSH 版本要求通过 `engines.dsh` 声明为 `>=0.1.5-rc.1 <0.2.0-0`(dsh-market 的兼容徽标读的就是它)。
|
|
110
|
+
- 同名发布在 npm([`dsh-usage-state`](https://www.npmjs.com/package/dsh-usage-state)),也可从 GitHub 直接安装。
|
|
111
|
+
- 版本 `0.3.0`:DeepSeek、z.ai 与 OpenCode Zen Go 已在真机验证,其余见下。
|
|
112
|
+
|
|
113
|
+
## 已知限制
|
|
114
|
+
|
|
115
|
+
- **Kimi、Sub2API 未经真机验证**(本机无凭据),代码与单测已就绪;Sub2API 的 `/v1/usage` 属未文档化接口,已按易错接口做容错。
|
|
116
|
+
- **点击状态行不会打开设置**(客户端没有公开的"打开设置面板"服务);细节通过悬浮提示呈现。
|
|
117
|
+
- **只在输入框上方展示,不覆盖历史**:状态行给出的是账户**当前**读数;插件不按回合、也不按时间保存历史读数,因此翻看旧回合时看不到"当时的余额"。若将来要做,会是以时间轴(而不是回合)为口径的单独决定。
|
|
118
|
+
- 完整清单见 [`docs/implementation.md`](docs/implementation.md) §6。
|
|
119
|
+
|
|
120
|
+
## 开发
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
npm install # 若 ~/.npm 不可写:npm install --cache /tmp/npm-cache
|
|
124
|
+
npm test # node:test 直接跑 .ts / .tsx(需要 Node >= 22.6)
|
|
125
|
+
npm run typecheck # tsc --noEmit
|
|
126
|
+
npm run build # tsdown → lib/(宿主 index.js + typert.js,浏览器 client.js)
|
|
127
|
+
npm run watch # 只重建 client.js;客户端会被 HMR 热替换,无需刷新页面
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
宿主机改动需要重启 DSH;客户端改动 `npm run watch` 即可。`lib/` 产物**必须提交进仓库**——`dsh plugin add github:...` 直接装仓库、没有构建步骤(`npm test` 里的构建守卫会检查信封、require 白名单与 `exports` 指向)。
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
src/host/ 宿主:数据源适配器、缓存调度、凭据、设置、RPC
|
|
134
|
+
src/client/ 浏览器:词典、状态行、设置页、状态镜像
|
|
135
|
+
src/shared/ 两端共用:类型、配置、provider 解析、显示逻辑
|
|
136
|
+
lib/ 构建产物(提交,供 github 安装)
|
|
137
|
+
tests/ 与 src 对应;tests/build 校验的是产物本身
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 文档
|
|
141
|
+
|
|
142
|
+
| 文档 | 内容 |
|
|
143
|
+
|---|---|
|
|
144
|
+
| [`docs/implementation.md`](docs/implementation.md) | 实现与验证总览:代码地图、决策→实现→测试→验证追溯、未验证清单、平台注意事项 |
|
|
145
|
+
| [`docs/adapters.md`](docs/adapters.md) | 添加数据源:契约、四步流程、约定与坑、候选厂商、排查表 |
|
|
146
|
+
| [`docs/design-consensus.md`](docs/design-consensus.md) | 设计共识与修订记录(每条决策的来龙去脉) |
|
|
147
|
+
| [`docs/research/README.md`](docs/research/README.md) | 只读侦察报告索引(各厂商接口、被替代插件剖析、DSH RPC 契约) |
|
|
148
|
+
|
|
149
|
+
## 致谢与参考
|
|
150
|
+
|
|
151
|
+
- **[`dsh-cost-meter`](https://github.com/Han-1413141/dsh-cost-meter)**(作者 Han-1413141,MIT 许可):本插件是它的**简化替代品**——只保留「看余额 / 看 Coding Plan 额度」这个展示需求,砍掉计费、价格目录、历史账单、预算与峰谷提醒等全部逻辑(见上面的「只做显示」)。
|
|
152
|
+
数据源端点、响应字段语义与若干兼容陷阱(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,不是对其源码的照搬;但那些行为语义确实源自上述分析,应归功于上游。
|
|
153
|
+
若上游作者认为某处需要更明确的署名或授权,请提 issue,我会立刻调整。
|
|
154
|
+
- **[DSH(DeepSeek Harness)](https://github.com/deepseek-ai)**:宿主平台。插件使用它的设置命名空间、凭据库、Typert RPC、插槽系统与 UI 原语(`@deepseek-ai/dsh-client-ui-primitives` 等)。
|
|
155
|
+
|
|
156
|
+
## 许可
|
|
157
|
+
|
|
158
|
+
[MIT](LICENSE)
|
package/cordis.patch.yml
ADDED
package/docs/adapters.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# 添加一个数据源(Adapter)
|
|
2
|
+
|
|
3
|
+
`dsh-usage-state` 只实现五家数据源(DeepSeek、z.ai / GLM、Kimi、OpenCode Zen Go、Sub2API)。其余厂商靠 adapter 扩展——加一个适配器只需要两个文件加一行注册,插件其余部分(设置页列出的数据源、轮询目标、状态行渲染、双语文案)会自动跟上。
|
|
4
|
+
|
|
5
|
+
## 契约
|
|
6
|
+
|
|
7
|
+
```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 → 归一化读数
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`request` 与 `parse` **刻意分离**:`parse` 不碰网络、不读时钟,所以每个厂商的返回结构都能用 fixture 无网单测。
|
|
22
|
+
|
|
23
|
+
**端点优先级**(`resolveProvider` → `UsageTarget.baseUrl` → `read.ts`):
|
|
24
|
+
|
|
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` 存在,则按序尝试镜像;全部失败时抛出**最有信息量**的失败("有响应"的优先于"连不上"的)。
|
|
29
|
+
|
|
30
|
+
区域类厂商(z.ai 国内/国际)就该用 `fallbackRequests`:key 只在自家站点有效,而错误站点返回的是**鉴权失败**而不是重定向。
|
|
31
|
+
|
|
32
|
+
## 加一个适配器的步骤
|
|
33
|
+
|
|
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/` 里已有的接口侦察可以补充你的注释。
|
|
43
|
+
|
|
44
|
+
## 约定与坑
|
|
45
|
+
|
|
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`)。
|
|
58
|
+
|
|
59
|
+
## 已实现的数据源与坑
|
|
60
|
+
|
|
61
|
+
> 下表各厂商的端点、字段语义与陷阱,主体来自社区侦察与被替代插件 `dsh-cost-meter@1.7.28` 的只读分析([`research/dsh-cost-meter-analysis.md`](research/dsh-cost-meter-analysis.md));实现是独立编写的,署名与出处见 README 的「致谢与参考」。
|
|
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` |
|
|
70
|
+
|
|
71
|
+
## 候选数据源(未实现)
|
|
72
|
+
|
|
73
|
+
以下接口已侦察确认存在(详见 `docs/research/provider-balance-quota-apis.md`),但都**不适合 v1 的凭据模型**(本插件只支持环境变量风格字符串密钥,经 DSH 凭据库存取):
|
|
74
|
+
|
|
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)`;看到该标注即说明走的兜底 |
|