dsh-model-usage 0.8.2 → 0.8.4

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,295 +1,312 @@
1
- # dsh-model-usage
2
-
3
- Provider-account usage for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
4
- sessions. The plugin adds a **Usage** (`用量`) entry to the session header and reports the quota
5
- windows, balance and request counters of the provider account behind the model the conversation is
6
- using.
7
-
8
- Queries run in the host process with the provider's own credential reference. The browser receives
9
- normalized values and the credential reference name; credential values never leave the host.
10
-
11
- ![Usage panel](docs/screenshots/usage-panel.png)
12
-
13
- *Usage panel for a Command Code subscription (Chinese UI): five-hour and weekly quota windows,
14
- credit balance, spend, request count, and the model and credential reference used for the query.*
15
-
16
- *Command Code 订阅的用量面板:5 小时 / 7 天窗口、信用余额、已花费、请求数,以及本次查询使用的
17
- 模型与凭据引用。*
18
-
19
- Published on npm as [`dsh-model-usage`](https://www.npmjs.com/package/dsh-model-usage).
20
-
21
- ## Features
22
-
23
- - **Session model.** The query target comes from the session's `modelSelection` projection, so the
24
- panel follows model changes in the conversation.
25
- - **Manual target.** Any routable provider model can be queried from the panel without changing the
26
- model the conversation runs on.
27
- - **Background refresh.** The mounted component refreshes on an interval, including while the panel
28
- is closed.
29
- - **Provider directory.** The deployment's provider routes are listed with their usage adapter,
30
- credential reference name and configuration state. Values are never read out.
31
-
32
- ## Requirements
33
-
34
- - DeepSeek Harness `>=0.2.1-rc.2` with the Web surface
35
- - Node.js `>=18`
36
- - No runtime dependencies. The browser half requires only the platform `react` module.
37
-
38
- ## Installation
39
-
40
- ```sh
41
- dsh plugin --profile <profile> add dsh-model-usage
42
- ```
43
-
44
- From an agent session, the bundle can also be installed by name with `install_bundle` (target
45
- `dsh-model-usage`, or the repository path for a local checkout). Both paths install the package into
46
- the profile, mount the bundle patch row `model-usage` and enable the bundle.
47
-
48
- With profile HMR enabled, host-side changes apply immediately; otherwise restart the host
49
- (`dsh web` or the desktop application). The browser half is served by `dsh-client-modules` and is
50
- picked up by reloading the page.
51
-
52
- ## Usage sources
53
-
54
- | Provider / base URL | Source | Reported values |
55
- | --- | --- | --- |
56
- | `commandcode`, `*.commandcode.ai` | `GET /alpha/whoami?limits=1`, `/alpha/billing/credits`, `/alpha/billing/subscriptions`, `/alpha/usage/summary?since=` | Plan, five-hour and weekly windows, credit balance, spend, request count |
57
- | `deepseek-official`, `deepseek`, `*.deepseek.com` | `GET https://api.deepseek.com/user/balance` | Balance per currency |
58
- | `deepseek-account` | `ctx.deepseekAccount.getBalance()` | Wallet balances of the signed-in account, without an API key |
59
- | `opencode-go`, `opencode.ai/zen/go` | `GET {origin}/zen/go/v1/usage` | Five-hour, weekly and monthly allowances per model |
60
- | `openrouter`, `*.openrouter.ai` | `GET /api/v1/key`; `GET /api/v1/credits` when the key has no limit of its own | Used, limit, remaining and the account credit pool |
61
- | `moonshotai`, `moonshotai-cn`, `kimi-coding`, `*.moonshot.cn`, `*.moonshot.ai` | `GET /v1/users/me/balance` | Available, cash and voucher balance |
62
- | `siliconflow`, `*.siliconflow.cn`, `*.siliconflow.com` | `GET /v1/user/info` | Total, charged and granted balance |
63
- | Other OpenAI-compatible routes with a `baseURL` | cc-switch New API convention: `GET {origin}/api/user/self` | Quota converted to USD; best effort |
64
-
65
- The OpenCode Go endpoint is not publicly documented. Fields are normalized by their common names;
66
- payloads that cannot be recognized remain available under **Raw response**. OpenCode Zen
67
- (pay-as-you-go) has no usage endpoint (`/zen/v1/usage`, `/me` and `/credits` return 404), so
68
- `opencode` routes report that usage queries are not supported. Routes without a `baseURL` and
69
- without a known provider return an explicit error instead of an inferred endpoint.
70
-
71
- Adding an adapter requires selecting the route in `kindOf()` and returning normalized data from a
72
- `queryXxx()` function: `{ rows }` for label/value lines or `{ windows, models }` for meters. The
73
- client renders both shapes with the generic card.
74
-
75
- ## Query target and credentials
76
-
77
- 1. The browser reads `next ?? lastUsed` from the session's `modelSelection` projection and sends
78
- `{ provider, model, reasoningEffort, sessionId }` to `POST /model-usage/balance`.
79
- 2. If the request carries no target, the host falls back to the session projection of the process
80
- and then to the deployment default (`agentDefaultModel.currentSelection()`).
81
- 3. The provider's settings location is resolved through `llm.listConfigurableProviders()` and read
82
- with `settings.describe()`, which supplies `apiKeyEnv`, `baseURL`, `api` and custom headers.
83
- 4. `ctx.credentials.resolve(apiKeyEnv)` returns the key in use. An unconfigured reference falls back
84
- to the environment variable of the same name; Command Code also falls back to
85
- `~/.commandcode/auth.json`. Keys are resolved per request, so a key added in Settings → Models is
86
- used by the next query without a restart.
87
- 5. The usage adapter is selected from the provider or base URL.
88
-
89
- Providers without `apiKeyEnv` use the convention `${PROVIDER}_API_KEY`; `deepseek-official` uses
90
- `DEEPSEEK_API_KEY`.
91
-
92
- The panel header shows the active target. `Auto · <provider> / <model>` follows the conversation.
93
- Selecting it opens the model picker with `Auto (follow the conversation)` and the routable provider
94
- models from `sessionController.modelCatalog()`, annotated with their adapter or with
95
- `usage query not supported`. A manual target changes only the panel's query, not the conversation
96
- model, and the credential reference follows the selected provider. The choice is stored in
97
- `dsh-model-usage:model` and can be reset to auto from the panel.
98
-
99
- Usage is account-level: models behind the same key share the same quota, so selecting a target
100
- selects the account to query. The panel lists the model and credential reference used.
101
-
102
- ## Refresh and status
103
-
104
- The component queries when it mounts and when the session model changes. With auto-refresh enabled
105
- it continues on an interval whether or not the panel is open.
106
-
107
- - The status line reports freshness: `Updated just now`, `Updated 12 min ago`,
108
- `Refreshing… · showing data from 3 min ago`, or `Not refreshed yet`. A hint is appended when the
109
- data is older than the interval.
110
- - Auto refresh is toggled from the status line or the refresh settings dialog.
111
- - The interval accepts 1, 5, 15, 30 or 60 minutes, or any value from 1 to 1440. Defaults are enabled
112
- and five minutes. The policy is stored in `dsh-model-usage:refresh`.
113
- - Opening the panel with data inside the interval does not send a request; **Refresh** forces one.
114
- - A failed query keeps the previous data and appends `Last refresh failed: …`. The full error state
115
- with **Retry** appears only when no query has succeeded.
116
- - The host applies a 30-second timeout per usage call; the browser applies a 45-second outer timeout.
117
-
118
- ## Security
119
-
120
- - Credential values are read in the host process and are never returned to the browser or written to
121
- logs. Responses contain normalized values plus the reference name and source.
122
- - All three routes check the composition's browser trust decision (Host/Origin) through the
123
- `connection` service and answer 401/403 when it is denied.
124
- - The plugin uploads nothing. Usage requests go from the host to the provider API.
125
-
126
- ## Compatibility
127
-
128
- - The bundle declares `dsh.bundle.patch`, `dsh.client` (`platform: web`), `manifestVersion: 1`,
129
- `engines.dsh` and `engines.node`.
130
- - `locale/zh.json` and `locale/en.json` provide the plugin title and description, and `icon.svg` is
131
- the icon; both are exported and included in the package.
132
- - The browser half registers `conversation.session.header.utilities`, requires only `react`, and
133
- addresses the plugin routes with document-relative URLs, so a GUI mounted under a subpath works.
134
- - The host half uses Node built-ins only. `webServer` is the single hard dependency; `connection`,
135
- `credentials`, `llm`, `settings`, `sessions`, `sessionProjections`, `sessionController`,
136
- `agentDefaultModel` and `deepseekAccount` are resolved through `ctx.get()` when used, so the
137
- plugin mounts where those services are optional or activate later.
138
- - Without a locale service, both dictionaries fall back to the Chinese source text.
139
-
140
- ## Development
141
-
142
- ```sh
143
- npm run verify # check + smoke, also runs from prepublishOnly
144
- npm test # alias for the same suite
145
- ```
146
-
147
- `scripts/check.mjs` validates the manifest and npm metadata, shipped files, README images, loader
148
- id, baseline module requests, dictionaries, host imports and route registration.
149
- `scripts/smoke.mjs` mounts the host half on a stub context and exercises the three routes, the error
150
- envelopes and the trust fence.
151
-
152
- Release:
153
-
154
- 1. Set `version` according to Semantic Versioning (visible behavior: minor; fixes: patch).
155
- 2. Update `CHANGELOG.md`.
156
- 3. Run `npm run verify`, then `npm publish`.
157
-
158
- Accounts with 2FA publish with `npm publish --otp=<code>`, or authenticate with a Granular Access
159
- Token that allows bypassing 2FA. Keep tokens in the user-level `~/.npmrc`, outside the repository.
160
-
161
- ## License
162
-
163
- MIT. DeepSeek Harness is also MIT-licensed. This is an independent community plugin, not affiliated
164
- with or endorsed by DeepSeek.
165
-
166
- ## 中文
167
-
168
- 本插件为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 会话提供
169
- 当前模型对应供应商账户的用量。它在会话头部增加「用量」入口,显示套餐窗口、余额与请求数;
170
- 查询在 host 进程内使用该供应商自己的凭据引用完成,浏览器只收到归一化数值与凭据引用名。
171
-
172
- ### 功能
173
-
174
- - **跟随会话模型**:查询对象取自会话的 `modelSelection` 投影,对话中切换模型时面板随之切换。
175
- - **手动查询对象**:可查询任意可路由的供应商模型,不改变当前对话使用的模型。
176
- - **后台刷新**:已挂载的组件按间隔刷新,弹窗关闭时同样生效。
177
- - **供应商目录**:列出本部署声明的供应商路由、用量适配器、凭据引用名与配置状态,不读取凭据值。
178
-
179
- ### 环境要求
180
-
181
- - DeepSeek Harness `>=0.2.1-rc.2`,并启用 Web 界面
182
- - Node.js `>=18`
183
- - 无运行时依赖;浏览器半只使用平台提供的 `react` 模块
184
-
185
- ### 安装
186
-
187
- ```sh
188
- dsh plugin --profile <profile> add dsh-model-usage
189
- ```
190
-
191
- 也可在 agent 会话中通过 `install_bundle` 安装(target 为 `dsh-model-usage`,本地开发时传仓库路径)。
192
- 两种方式都会把包装入 profile、挂载 bundle patch 的 `model-usage` 行并启用。
193
-
194
- 启用 profile HMR 时 host 侧改动立即生效;否则重启宿主(`dsh web` 或桌面版)。浏览器半由
195
- `dsh-client-modules` 提供,刷新页面后生效。
196
-
197
- ### 用量来源
198
-
199
- | provider / baseURL | 来源 | 返回值 |
200
- | --- | --- | --- |
201
- | `commandcode`、`*.commandcode.ai` | `GET /alpha/whoami?limits=1`、`/alpha/billing/credits`、`/alpha/billing/subscriptions`、`/alpha/usage/summary?since=` | 套餐、5 小时 / 7 天窗口、信用余额、已花费、请求数 |
202
- | `deepseek-official`、`deepseek`、`*.deepseek.com` | `GET https://api.deepseek.com/user/balance` | 按币种的余额 |
203
- | `deepseek-account` | `ctx.deepseekAccount.getBalance()` | 已登录账户的钱包余额(不涉及 API Key) |
204
- | `opencode-go`、`opencode.ai/zen/go` | `GET {origin}/zen/go/v1/usage` | 按模型的 5 小时 / 周 / 月额度 |
205
- | `openrouter`、`*.openrouter.ai` | `GET /api/v1/key`;key 无独立额度时再查 `GET /api/v1/credits` | 已用、额度、剩余与账户额度池 |
206
- | `moonshotai`、`moonshotai-cn`、`kimi-coding`、`*.moonshot.cn`、`*.moonshot.ai` | `GET /v1/users/me/balance` | 可用余额、现金余额、代金券 |
207
- | `siliconflow`、`*.siliconflow.cn`、`*.siliconflow.com` | `GET /v1/user/info` | 总余额、充值余额、赠送余额 |
208
- | 其它带 `baseURL` 的 OpenAI 兼容路由 | cc-switch New API 约定:`GET {origin}/api/user/self` | 按约定换算为 USD,尽力而为 |
209
-
210
- OpenCode Go 的端点未公开文档化,字段按常见命名归一化;无法识别的返回保留在「原始返回」中。
211
- OpenCode Zen(按量付费)没有用量接口(`/zen/v1/usage`、`/me`、`/credits` 均为 404),
212
- `opencode` 路由会明确提示不支持用量查询。既无 `baseURL`、也非已知供应商的路由返回明确错误,
213
- 不会推断接口。
214
-
215
- 新增适配器只需在 `kindOf()` 中认领路由,并由 `queryXxx()` 返回归一化数据:`{ rows }` 渲染为
216
- 标签值行,`{ windows, models }` 渲染为用量条,客户端统一用通用卡片展示。
217
-
218
- ### 查询对象与凭据
219
-
220
- 1. 浏览器读取会话 `modelSelection` 投影的 `next ?? lastUsed`,向 `POST /model-usage/balance`
221
- 发送 `{ provider, model, reasoningEffort, sessionId }`。
222
- 2. 请求未携带查询对象时,host 依次回退到本进程的会话投影与部署默认模型
223
- (`agentDefaultModel.currentSelection()`)。
224
- 3. host 通过 `llm.listConfigurableProviders()` 取得该 provider 的配置位置,并用
225
- `settings.describe()` 读取实时配置,得到 `apiKeyEnv`、`baseURL`、`api` 与自定义 headers。
226
- 4. `ctx.credentials.resolve(apiKeyEnv)` 取得当前使用的 key。引用未配置时回退到同名环境变量;
227
- Command Code 还会回退到 `~/.commandcode/auth.json`。解析在每次查询时进行,因此
228
- 「设置 → 模型」中新填的 key 无需重启即可用于下一次查询。
229
- 5. 按 provider 或 baseURL 选择用量适配器。
230
-
231
- 未声明 `apiKeyEnv` 的 provider 按 `${PROVIDER}_API_KEY` 约定推导引用名,`deepseek-official`
232
- 使用 `DEEPSEEK_API_KEY`。
233
-
234
- 面板顶部显示当前查询对象。`自动 · <provider> / <model>` 跟随对话;点击后可在模型选择器中选择
235
- `自动(跟随当前对话)`,或选择 `sessionController.modelCatalog()` 中任意可路由模型(标注其适配器
236
- 或「不支持用量查询」)。手动选择只改变面板的查询对象,不改变对话模型,凭据引用随所选供应商切换;
237
- 选择保存在 `dsh-model-usage:model`,可在面板中改回自动。
238
-
239
- 用量是账户级数据:同一把 key 下的模型共享同一份额度,因此切换查询对象即切换要查询的账户。
240
- 面板底部列出本次查询使用的模型与凭据引用。
241
-
242
- ### 刷新与状态
243
-
244
- 组件在挂载时与会话模型变化时查询。开启自动刷新后,无论弹窗是否打开,都会按间隔继续查询。
245
-
246
- - 状态行显示数据新鲜度:`刷新时间:刚刚`、`刷新时间:12 分钟前`、
247
- `刷新中… · 当前显示 3 分钟前数据`、`尚未刷新`;数据超过刷新间隔时会追加提示。
248
- - 自动刷新可在状态行或刷新设置中开关。
249
- - 刷新间隔支持 1 / 5 / 15 / 30 / 60 分钟,也可填写 1–1440 之间的任意分钟数;默认开启、间隔
250
- 5 分钟。策略保存在 `dsh-model-usage:refresh`。
251
- - 数据仍在间隔内时打开面板不会发起请求;「刷新」按钮可强制查询。
252
- - 查询失败时保留上次数据并追加 `上次刷新失败:…`;仅在从未成功时显示整屏错误与「重试」。
253
- - host 侧单次用量请求超时 30 秒,浏览器侧另有 45 秒外层超时。
254
-
255
- ### 安全
256
-
257
- - 凭据值只在 host 进程内读取,不返回浏览器,也不写入日志;响应仅包含归一化数值、引用名与来源。
258
- - 三条路由在应答前通过 `connection` 服务校验浏览器信任(Host/Origin),未通过时返回 401/403。
259
- - 插件不上传任何数据,用量请求由 host 直接发往供应商接口。
260
-
261
- ### 兼容性
262
-
263
- - bundle 声明 `dsh.bundle.patch`、`dsh.client`(`platform: web`)、`manifestVersion: 1`、
264
- `engines.dsh` 与 `engines.node`。
265
- - `locale/zh.json`、`locale/en.json` 提供插件标题与描述,`icon.svg` 为图标;两者都已导出并包含在包内。
266
- - 浏览器半注册 `conversation.session.header.utilities`,只依赖 `react`,并使用文档相对路径访问
267
- 插件路由,因此 GUI 挂在子目录下同样可用。
268
- - host 半只使用 Node 内置模块。`webServer` 是唯一硬依赖;`connection`、`credentials`、`llm`、
269
- `settings`、`sessions`、`sessionProjections`、`sessionController`、`agentDefaultModel`、
270
- `deepseekAccount` 均按需通过 `ctx.get()` 获取,服务缺失或稍后激活时插件仍可挂载。
271
- - 没有 locale 服务时,词典回退为中文原文。
272
-
273
- ### 开发
274
-
275
- ```sh
276
- npm run verify # check + smoke,prepublishOnly 同样执行
277
- npm test # 同一套检查的别名
278
- ```
279
-
280
- `scripts/check.mjs` 校验清单与 npm 元数据、随包文件、README 图片、loader id、基线模块、
281
- 词典、host 导入与路由注册;`scripts/smoke.mjs` 在桩 ctx 上挂载 host 半,覆盖三条路由、
282
- 错误响应与信任栅栏。
283
-
284
- 发布流程:
285
-
286
- 1. 按语义化版本更新 `version`(可见行为变化为 minor,修复为 patch)。
287
- 2. 更新 `CHANGELOG.md`。
288
- 3. 运行 `npm run verify`,然后执行 `npm publish`。
289
-
290
- 启用 2FA 的账号可用 `npm publish --otp=<验证码>` 发布,或使用允许绕过 2FA 的 Granular Access
291
- Token 认证。令牌只保存在用户级 `~/.npmrc`,不要提交到仓库。
292
-
293
- ### 许可
294
-
295
- MIT。DeepSeek Harness 同样以 MIT 许可发布。本项目为独立社区插件,与深度求索公司无隶属或背书关系。
1
+ # dsh-model-usage
2
+
3
+ Provider-account usage for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
4
+ sessions. The plugin adds a **Usage** (`用量`) entry to the session header and reports the quota
5
+ windows, balance and request counters of the provider account behind the model the conversation is
6
+ using.
7
+
8
+ Queries run in the host process with the provider's own credential reference. The browser receives
9
+ normalized values and the credential reference name; credential values never leave the host.
10
+
11
+ ![Usage panel](docs/screenshots/u1.png)
12
+
13
+ ![Usage panel](docs/screenshots/u2.png)
14
+
15
+ ![Usage panel](docs/screenshots/u3.png)
16
+
17
+ ![Usage panel](docs/screenshots/u4.png)
18
+
19
+ ![Usage panel](docs/screenshots/u5.png)
20
+
21
+ *Usage panel for a Command Code subscription (Chinese UI): five-hour and weekly quota windows,
22
+ credit balance, spend, request count, and the model and credential reference used for the query.*
23
+
24
+ *用量面板:兼容深浅色模式*
25
+
26
+ Published on npm as [`dsh-model-usage`](https://www.npmjs.com/package/dsh-model-usage).
27
+
28
+ ## Features
29
+
30
+ - **Session model.** The query target comes from the session's `modelSelection` projection, so the
31
+ panel follows model changes in the conversation.
32
+ - **Manual target.** Any routable provider model can be queried from the panel without changing the
33
+ model the conversation runs on.
34
+ - **Background refresh.** The mounted component refreshes on an interval, including while the panel
35
+ is closed.
36
+ - **Command Code balance history.** The plugin queries on the system clock's ten-minute marks
37
+ (`:00`, `:10`, `:20`, …), independently of the display refresh setting. Samples are kept locally
38
+ per account for the current calendar month and the two preceding calendar months. The chart has
39
+ uniform time-axis ticks, actual sample positions for short ranges, daily totals for week/month
40
+ views, custom start/end dates, and month deletion/reset controls.
41
+ - **Provider directory.** The deployment's provider routes are listed with their usage adapter,
42
+ credential reference name and configuration state. Values are never read out.
43
+
44
+ ## Requirements
45
+
46
+ - DeepSeek Harness `>=0.2.1-rc.2` with the Web surface
47
+ - Node.js `>=18`
48
+ - No runtime dependencies. The browser half requires only the platform `react` module.
49
+
50
+ ## Installation
51
+
52
+ ```sh
53
+ dsh plugin --profile <profile> add dsh-model-usage
54
+ ```
55
+
56
+ From an agent session, the bundle can also be installed by name with `install_bundle` (target
57
+ `dsh-model-usage`, or the repository path for a local checkout). Both paths install the package into
58
+ the profile, mount the bundle patch row `model-usage` and enable the bundle.
59
+
60
+ With profile HMR enabled, host-side changes apply immediately; otherwise restart the host
61
+ (`dsh web` or the desktop application). The browser half is served by `dsh-client-modules` and is
62
+ picked up by reloading the page.
63
+
64
+ ## Usage sources
65
+
66
+ | Provider / base URL | Source | Reported values |
67
+ | --- | --- | --- |
68
+ | `commandcode`, `*.commandcode.ai` | `GET /alpha/whoami?limits=1`, `/alpha/billing/credits`, `/alpha/billing/subscriptions`, `/alpha/usage/summary?since=` | Plan, five-hour and weekly windows, credit balance, spend, request count |
69
+ | `deepseek-official`, `deepseek`, `*.deepseek.com` | `GET https://api.deepseek.com/user/balance` | Balance per currency |
70
+ | `deepseek-account` | `ctx.deepseekAccount.getBalance()` | Wallet balances of the signed-in account, without an API key |
71
+ | `opencode-go`, `opencode.ai/zen/go` | `GET {origin}/zen/go/v1/usage` | Five-hour, weekly and monthly allowances per model |
72
+ | `openrouter`, `*.openrouter.ai` | `GET /api/v1/key`; `GET /api/v1/credits` when the key has no limit of its own | Used, limit, remaining and the account credit pool |
73
+ | `moonshotai`, `moonshotai-cn`, `kimi-coding`, `*.moonshot.cn`, `*.moonshot.ai` | `GET /v1/users/me/balance` | Available, cash and voucher balance |
74
+ | `siliconflow`, `*.siliconflow.cn`, `*.siliconflow.com` | `GET /v1/user/info` | Total, charged and granted balance |
75
+ | Other OpenAI-compatible routes with a `baseURL` | cc-switch New API convention: `GET {origin}/api/user/self` | Quota converted to USD; best effort |
76
+
77
+ The OpenCode Go endpoint is not publicly documented. Fields are normalized by their common names;
78
+ payloads that cannot be recognized remain available under **Raw response**. OpenCode Zen
79
+ (pay-as-you-go) has no usage endpoint (`/zen/v1/usage`, `/me` and `/credits` return 404), so
80
+ `opencode` routes report that usage queries are not supported. Routes without a `baseURL` and
81
+ without a known provider return an explicit error instead of an inferred endpoint.
82
+
83
+ Adding an adapter requires selecting the route in `kindOf()` and returning normalized data from a
84
+ `queryXxx()` function: `{ rows }` for label/value lines or `{ windows, models }` for meters. The
85
+ client renders both shapes with the generic card.
86
+
87
+ ## Query target and credentials
88
+
89
+ 1. The browser reads `next ?? lastUsed` from the session's `modelSelection` projection and sends
90
+ `{ provider, model, reasoningEffort, sessionId }` to `POST /model-usage/balance`.
91
+ 2. If the request carries no target, the host falls back to the session projection of the process
92
+ and then to the deployment default (`agentDefaultModel.currentSelection()`).
93
+ 3. The provider's settings location is resolved through `llm.listConfigurableProviders()` and read
94
+ with `settings.describe()`, which supplies `apiKeyEnv`, `baseURL`, `api` and custom headers.
95
+ 4. `ctx.credentials.resolve(apiKeyEnv)` returns the key in use. An unconfigured reference falls back
96
+ to the environment variable of the same name; Command Code also falls back to
97
+ `~/.commandcode/auth.json`. Keys are resolved per request, so a key added in Settings → Models is
98
+ used by the next query without a restart.
99
+ 5. The usage adapter is selected from the provider or base URL.
100
+
101
+ Providers without `apiKeyEnv` use the convention `${PROVIDER}_API_KEY`; `deepseek-official` uses
102
+ `DEEPSEEK_API_KEY`.
103
+
104
+ The panel header shows the active target. `Auto · <provider> / <model>` follows the conversation.
105
+ Selecting it opens the model picker with `Auto (follow the conversation)` and the routable provider
106
+ models from `sessionController.modelCatalog()`, annotated with their adapter or with
107
+ `usage query not supported`. A manual target changes only the panel's query, not the conversation
108
+ model, and the credential reference follows the selected provider. The choice is stored in
109
+ `dsh-model-usage:model` and can be reset to auto from the panel.
110
+
111
+ Usage is account-level: models behind the same key share the same quota, so selecting a target
112
+ selects the account to query. The panel lists the model and credential reference used.
113
+
114
+ ## Refresh and status
115
+
116
+ The component queries when it mounts and when the session model changes. With auto-refresh enabled
117
+ it continues on an interval whether or not the panel is open.
118
+
119
+ - The status line reports freshness: `Updated just now`, `Updated 12 min ago`,
120
+ `Refreshing… · showing data from 3 min ago`, or `Not refreshed yet`. A hint is appended when the
121
+ data is older than the interval.
122
+ - Auto refresh is toggled from the status line or the refresh settings dialog.
123
+ - The interval accepts 1, 5, 15, 30 or 60 minutes, or any value from 1 to 1440. Defaults are enabled
124
+ and five minutes. The policy is stored in `dsh-model-usage:refresh`.
125
+ - Opening the panel with data inside the interval does not send a request; **Refresh** forces one.
126
+ - A failed query keeps the previous data and appends `Last refresh failed: …`. The full error state
127
+ with **Retry** appears only when no query has succeeded.
128
+ - The host applies a 30-second timeout per usage call; the browser applies a 45-second outer timeout.
129
+
130
+ ## Security
131
+
132
+ - Credential values are read in the host process and are never returned to the browser or written to
133
+ logs. Responses contain normalized values plus the reference name and source.
134
+ - All three routes check the composition's browser trust decision (Host/Origin) through the
135
+ `connection` service and answer 401/403 when it is denied.
136
+ - The plugin uploads nothing. Usage requests go from the host to the provider API.
137
+
138
+ ## Compatibility
139
+
140
+ - The bundle declares `dsh.bundle.patch`, `dsh.client` (`platform: web`), `manifestVersion: 1`,
141
+ `engines.dsh` and `engines.node`.
142
+ - `locale/zh.json` and `locale/en.json` provide the plugin title and description, and `icon.svg` is
143
+ the icon; both are exported and included in the package.
144
+ - The browser half registers `conversation.session.header.utilities`, requires only `react`, and
145
+ addresses the plugin routes with document-relative URLs, so a GUI mounted under a subpath works.
146
+ - The host half uses Node built-ins only. `webServer` is the single hard dependency; `connection`,
147
+ `credentials`, `llm`, `settings`, `sessions`, `sessionProjections`, `sessionController`,
148
+ `agentDefaultModel` and `deepseekAccount` are resolved through `ctx.get()` when used, so the
149
+ plugin mounts where those services are optional or activate later.
150
+ - Without a locale service, both dictionaries fall back to the Chinese source text.
151
+
152
+ ## Development
153
+
154
+ ```sh
155
+ npm run verify # check + smoke, also runs from prepublishOnly
156
+ npm test # alias for the same suite
157
+ ```
158
+
159
+ `scripts/check.mjs` validates the manifest and npm metadata, shipped files, README images, loader
160
+ id, baseline module requests, dictionaries, host imports and route registration.
161
+ `scripts/smoke.mjs` mounts the host half on a stub context and exercises the three routes, the error
162
+ envelopes and the trust fence.
163
+
164
+ Release:
165
+
166
+ 1. Set `version` according to Semantic Versioning (visible behavior: minor; fixes: patch).
167
+ 2. Update `CHANGELOG.md`.
168
+ 3. Run `npm run verify`, then `npm publish`.
169
+
170
+ Accounts with 2FA publish with `npm publish --otp=<code>`, or authenticate with a Granular Access
171
+ Token that allows bypassing 2FA. Keep tokens in the user-level `~/.npmrc`, outside the repository.
172
+
173
+ ## License
174
+
175
+ MIT. DeepSeek Harness is also MIT-licensed. This is an independent community plugin, not affiliated
176
+ with or endorsed by DeepSeek.
177
+
178
+ ## 中文
179
+
180
+ 本插件为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 会话提供
181
+ 当前模型对应供应商账户的用量。它在会话头部增加「用量」入口,显示套餐窗口、余额与请求数;
182
+ 查询在 host 进程内使用该供应商自己的凭据引用完成,浏览器只收到归一化数值与凭据引用名。
183
+
184
+ ### 功能
185
+
186
+ - **跟随会话模型**:查询对象取自会话的 `modelSelection` 投影,对话中切换模型时面板随之切换。
187
+ - **手动查询对象**:可查询任意可路由的供应商模型,不改变当前对话使用的模型。
188
+ - **后台刷新**:已挂载的组件按间隔刷新,弹窗关闭时同样生效。
189
+ - **供应商目录**:列出本部署声明的供应商路由、用量适配器、凭据引用名与配置状态,不读取凭据值。
190
+
191
+ ### 环境要求
192
+
193
+ - DeepSeek Harness `>=0.2.1-rc.2`,并启用 Web 界面
194
+ - Node.js `>=18`
195
+ - 无运行时依赖;浏览器半只使用平台提供的 `react` 模块
196
+
197
+ ### 安装
198
+
199
+ ```sh
200
+ dsh plugin --profile <profile> add dsh-model-usage
201
+ ```
202
+
203
+ 也可在 agent 会话中通过 `install_bundle` 安装(target 为 `dsh-model-usage`,本地开发时传仓库路径)。
204
+ 两种方式都会把包装入 profile、挂载 bundle patch 的 `model-usage` 行并启用。
205
+
206
+ 启用 profile HMR 时 host 侧改动立即生效;否则重启宿主(`dsh web` 或桌面版)。浏览器半由
207
+ `dsh-client-modules` 提供,刷新页面后生效。
208
+
209
+ ### 用量来源
210
+
211
+ | provider / baseURL | 来源 | 返回值 |
212
+ | --- | --- | --- |
213
+ | `commandcode`、`*.commandcode.ai` | `GET /alpha/whoami?limits=1`、`/alpha/billing/credits`、`/alpha/billing/subscriptions`、`/alpha/usage/summary?since=` | 套餐、5 小时 / 7 天窗口、信用余额、已花费、请求数 |
214
+ | `deepseek-official`、`deepseek`、`*.deepseek.com` | `GET https://api.deepseek.com/user/balance` | 按币种的余额 |
215
+ | `deepseek-account` | `ctx.deepseekAccount.getBalance()` | 已登录账户的钱包余额(不涉及 API Key) |
216
+ | `opencode-go`、`opencode.ai/zen/go` | `GET {origin}/zen/go/v1/usage` | 按模型的 5 小时 / 周 / 月额度 |
217
+ | `openrouter`、`*.openrouter.ai` | `GET /api/v1/key`;key 无独立额度时再查 `GET /api/v1/credits` | 已用、额度、剩余与账户额度池 |
218
+ | `moonshotai`、`moonshotai-cn`、`kimi-coding`、`*.moonshot.cn`、`*.moonshot.ai` | `GET /v1/users/me/balance` | 可用余额、现金余额、代金券 |
219
+ | `siliconflow`、`*.siliconflow.cn`、`*.siliconflow.com` | `GET /v1/user/info` | 总余额、充值余额、赠送余额 |
220
+ | 其它带 `baseURL` 的 OpenAI 兼容路由 | cc-switch New API 约定:`GET {origin}/api/user/self` | 按约定换算为 USD,尽力而为 |
221
+
222
+ OpenCode Go 的端点未公开文档化,字段按常见命名归一化;无法识别的返回保留在「原始返回」中。
223
+ OpenCode Zen(按量付费)没有用量接口(`/zen/v1/usage`、`/me`、`/credits` 均为 404),
224
+ `opencode` 路由会明确提示不支持用量查询。既无 `baseURL`、也非已知供应商的路由返回明确错误,
225
+ 不会推断接口。
226
+
227
+ 新增适配器只需在 `kindOf()` 中认领路由,并由 `queryXxx()` 返回归一化数据:`{ rows }` 渲染为
228
+ 标签值行,`{ windows, models }` 渲染为用量条,客户端统一用通用卡片展示。
229
+
230
+ ### 查询对象与凭据
231
+
232
+ 1. 浏览器读取会话 `modelSelection` 投影的 `next ?? lastUsed`,向 `POST /model-usage/balance`
233
+ 发送 `{ provider, model, reasoningEffort, sessionId }`。
234
+ 2. 请求未携带查询对象时,host 依次回退到本进程的会话投影与部署默认模型
235
+ (`agentDefaultModel.currentSelection()`)。
236
+ 3. host 通过 `llm.listConfigurableProviders()` 取得该 provider 的配置位置,并用
237
+ `settings.describe()` 读取实时配置,得到 `apiKeyEnv`、`baseURL`、`api` 与自定义 headers。
238
+ 4. `ctx.credentials.resolve(apiKeyEnv)` 取得当前使用的 key。引用未配置时回退到同名环境变量;
239
+ Command Code 还会回退到 `~/.commandcode/auth.json`。解析在每次查询时进行,因此
240
+ 「设置 → 模型」中新填的 key 无需重启即可用于下一次查询。
241
+ 5. 按 provider 或 baseURL 选择用量适配器。
242
+
243
+ 未声明 `apiKeyEnv` 的 provider 按 `${PROVIDER}_API_KEY` 约定推导引用名,`deepseek-official`
244
+ 使用 `DEEPSEEK_API_KEY`。
245
+
246
+ 面板顶部显示当前查询对象。`自动 · <provider> / <model>` 跟随对话;点击后可在模型选择器中选择
247
+ `自动(跟随当前对话)`,或选择 `sessionController.modelCatalog()` 中任意可路由模型(标注其适配器
248
+ 或「不支持用量查询」)。手动选择只改变面板的查询对象,不改变对话模型,凭据引用随所选供应商切换;
249
+ 选择保存在 `dsh-model-usage:model`,可在面板中改回自动。
250
+
251
+ 用量是账户级数据:同一把 key 下的模型共享同一份额度,因此切换查询对象即切换要查询的账户。
252
+ 面板底部列出本次查询使用的模型与凭据引用。
253
+
254
+ ### 刷新与状态
255
+
256
+ 组件在挂载时与会话模型变化时查询。开启自动刷新后,无论弹窗是否打开,都会按间隔继续查询。
257
+
258
+ - 状态行显示数据新鲜度:`刷新时间:刚刚`、`刷新时间:12 分钟前`、
259
+ `刷新中… · 当前显示 3 分钟前数据`、`尚未刷新`;数据超过刷新间隔时会追加提示。
260
+ - 自动刷新可在状态行或刷新设置中开关。
261
+ - 刷新间隔支持 1 / 5 / 15 / 30 / 60 分钟,也可填写 1–1440 之间的任意分钟数;默认开启、间隔
262
+ 5 分钟。策略保存在 `dsh-model-usage:refresh`。
263
+ - 数据仍在间隔内时打开面板不会发起请求;「刷新」按钮可强制查询。
264
+ - 查询失败时保留上次数据并追加 `上次刷新失败:…`;仅在从未成功时显示整屏错误与「重试」。
265
+ - Command Code 余额历史独立于面板刷新开关,在系统时间的每小时 `00 / 10 / 20 / 30 / 40 / 50`
266
+ 分自动查询。数据按账户保存在浏览器本地,保留当前自然月及前两个月;例如十月会保留十月、
267
+ 九月、八月,七月数据自动清除。用量卡片提供「今天 / 昨天 / 近一周 / 近一月 / 自定义」筛选,
268
+ 周和月按每天合计,短时间段按真实查询时间定位折线点,并可清除指定月份或重置当前账户记录。
269
+ 首次记录作为余额基线,后续每段消耗按前后余额差计算,余额增加时该段显示为零。
270
+ - host 侧单次用量请求超时 30 秒,浏览器侧另有 45 秒外层超时。
271
+
272
+ ### 安全
273
+
274
+ - 凭据值只在 host 进程内读取,不返回浏览器,也不写入日志;响应仅包含归一化数值、引用名与来源。
275
+ - 三条路由在应答前通过 `connection` 服务校验浏览器信任(Host/Origin),未通过时返回 401/403。
276
+ - 插件不上传任何数据,用量请求由 host 直接发往供应商接口。
277
+
278
+ ### 兼容性
279
+
280
+ - bundle 声明 `dsh.bundle.patch`、`dsh.client`(`platform: web`)、`manifestVersion: 1`、
281
+ `engines.dsh` 与 `engines.node`。
282
+ - `locale/zh.json`、`locale/en.json` 提供插件标题与描述,`icon.svg` 为图标;两者都已导出并包含在包内。
283
+ - 浏览器半注册 `conversation.session.header.utilities`,只依赖 `react`,并使用文档相对路径访问
284
+ 插件路由,因此 GUI 挂在子目录下同样可用。
285
+ - host 半只使用 Node 内置模块。`webServer` 是唯一硬依赖;`connection`、`credentials`、`llm`、
286
+ `settings`、`sessions`、`sessionProjections`、`sessionController`、`agentDefaultModel`、
287
+ `deepseekAccount` 均按需通过 `ctx.get()` 获取,服务缺失或稍后激活时插件仍可挂载。
288
+ - 没有 locale 服务时,词典回退为中文原文。
289
+
290
+ ### 开发
291
+
292
+ ```sh
293
+ npm run verify # check + smoke,prepublishOnly 同样执行
294
+ npm test # 同一套检查的别名
295
+ ```
296
+
297
+ `scripts/check.mjs` 校验清单与 npm 元数据、随包文件、README 图片、loader id、基线模块、
298
+ 词典、host 导入与路由注册;`scripts/smoke.mjs` 在桩 ctx 上挂载 host 半,覆盖三条路由、
299
+ 错误响应与信任栅栏。
300
+
301
+ 发布流程:
302
+
303
+ 1. 按语义化版本更新 `version`(可见行为变化为 minor,修复为 patch)。
304
+ 2. 更新 `CHANGELOG.md`。
305
+ 3. 运行 `npm run verify`,然后执行 `npm publish`。
306
+
307
+ 启用 2FA 的账号可用 `npm publish --otp=<验证码>` 发布,或使用允许绕过 2FA 的 Granular Access
308
+ Token 认证。令牌只保存在用户级 `~/.npmrc`,不要提交到仓库。
309
+
310
+ ### 许可
311
+
312
+ MIT。DeepSeek Harness 同样以 MIT 许可发布。本项目为独立社区插件,与深度求索公司无隶属或背书关系。