@ychris12138/dsh-usage-stats 0.3.0 → 0.3.2
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 +21 -23
- package/SECURITY.md +1 -1
- package/docs/release-checklist.md +23 -11
- package/docs/release-notes-v0.3.1.md +40 -0
- package/docs/release-notes-v0.3.2.md +36 -0
- package/lib/accounts.js +3 -1
- package/lib/balance.js +115 -12
- package/lib/client.js +159 -317
- package/lib/index.js +267 -47
- package/lib/orcarouter.js +79 -0
- package/lib/provider-identity.js +3 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh-usage-stats
|
|
2
2
|
|
|
3
|
-
<!-- stable-version: 0.3.
|
|
3
|
+
<!-- stable-version: 0.3.2 -->
|
|
4
4
|
|
|
5
5
|
[](https://github.com/Ychris12138/dsh-usage-stats/releases/latest)
|
|
6
6
|
[](https://github.com/Ychris12138/dsh-usage-stats/actions/workflows/ci.yml)
|
|
@@ -14,17 +14,21 @@ Provider balances, subscription quotas, and token-usage analytics for the DeepSe
|
|
|
14
14
|
|
|
15
15
|
> 展示图使用脱敏演示数据;插件不会把 API Key、Cookie、管理 PAT 或上游原始响应发送到浏览器。
|
|
16
16
|
|
|
17
|
+
[](https://www.orcarouter.ai/ref/ref_13c34663d1527ac16963)
|
|
18
|
+
|
|
19
|
+
> 🐋 OrcaRouter sponsors this project and is available as an optional OpenAI-compatible provider. [Learn more](https://www.orcarouter.ai/ref/ref_13c34663d1527ac16963) · Referral link.
|
|
20
|
+
|
|
17
21
|
## 一眼看懂 / At a glance
|
|
18
22
|
|
|
19
23
|
| | 能力 | 说明 |
|
|
20
24
|
| --- | --- | --- |
|
|
21
25
|
| 💳 | 统一账户卡片 | API 供应商显示余额,Token Plan 显示分窗口额度;面板一次只呈现当前供应商 |
|
|
22
26
|
| 📊 | Token 用量分析 | 今日、本月、累计、缓存命中率、月历热图,以及按日期/供应商/模型下钻 |
|
|
23
|
-
| 💰 | 估算费用与预算 |
|
|
27
|
+
| 💰 | 估算费用与预算 | 按事件时间匹配历史价格,提供日/月费用、session 级聚合及可选预算预警 |
|
|
24
28
|
| 🔄 | 后台监测 | 账户按 active/detail/background 自适应刷新;间隔可配置或完全关闭,本地 Token 聚合保持独立运行 |
|
|
25
29
|
| 🧩 | 可扩展适配器 | 支持 New API、Sub2API、通用余额模板,以及声明式 JSON Pointer 自定义查询 |
|
|
26
30
|
| 📦 | 安全导出 | 提供 daily/session CSV 与版本化 JSON;Unicode、CSV 公式前缀和不完整费用均安全处理 |
|
|
27
|
-
| 🔒 | 本机安全边界 |
|
|
31
|
+
| 🔒 | 本机安全边界 | 数据端点仅接受回环 GET;OrcaRouter preset 仅由带防跨站请求头的显式回环 POST 写入;凭据只在服务端解析 |
|
|
28
32
|
|
|
29
33
|
界面支持中文和英文。浏览器只请求当前选择的 provider;账户自动刷新由服务端统一调度。手动刷新会更新用量、供应商列表,并强制刷新当前账户,不会批量强制请求其他供应商。
|
|
30
34
|
|
|
@@ -35,7 +39,7 @@ Provider balances, subscription quotas, and token-usage analytics for the DeepSe
|
|
|
35
39
|
稳定版优先安装 npm 上的精确版本;这也是 DSH Desktop Market 使用的同一个包:
|
|
36
40
|
|
|
37
41
|
```bash
|
|
38
|
-
dsh plugin --profile web add "@ychris12138/dsh-usage-stats@0.3.
|
|
42
|
+
dsh plugin --profile web add "@ychris12138/dsh-usage-stats@0.3.2"
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
只有测试尚未发布的 source/RC 时才使用 `dsh plugin --profile web add "github:Ychris12138/dsh-usage-stats"`。GitHub `main` 可能领先 npm stable,不应把 source 安装当作市场安装验收。
|
|
@@ -49,7 +53,7 @@ dsh plugin --profile web add "@ychris12138/dsh-usage-stats@0.3.0"
|
|
|
49
53
|
- `catalog/catalog-source.json` — 来源 manifest(`catalog-source.schema.json` v1.0.0)
|
|
50
54
|
- `catalog/v1/plugins.json` — 标准 provider page(`catalog-provider-page.schema.json` v1.0.0)
|
|
51
55
|
|
|
52
|
-
**使用前提(重要)**:市场托管安装只接受 npm registry 的精确稳定版本,git 条目仅可浏览。`dsh-usage-stats` 这个 npm 名已被其他项目占用,因此目录条目身份使用 `@ychris12138/dsh-usage-stats`。当前 stable/catalog 版本是 `0.3.
|
|
56
|
+
**使用前提(重要)**:市场托管安装只接受 npm registry 的精确稳定版本,git 条目仅可浏览。`dsh-usage-stats` 这个 npm 名已被其他项目占用,因此目录条目身份使用 `@ychris12138/dsh-usage-stats`。当前 stable/catalog 版本是 `0.3.2`;每个新版本都按以下顺序发布:
|
|
53
57
|
|
|
54
58
|
1. 运行 `npm run release:sync -- <version>` 同步 `package.json` / `package-lock.json` / `catalog/v1/plugins.json`,再由 `npm run check:release` 阻止身份或版本漂移。
|
|
55
59
|
2. 发布 scoped 公共包:`npm publish --access public`。
|
|
@@ -101,6 +105,7 @@ npx --yes github:Ychris12138/dsh-usage-stats --no-enable
|
|
|
101
105
|
| --- | --- | --- | --- |
|
|
102
106
|
| DeepSeek | 余额 | provider `apiKeyEnv` | `/user/balance` |
|
|
103
107
|
| OpenRouter | 余额 | `OPENROUTER_MANAGEMENT_KEY` | `/api/v1/credits` |
|
|
108
|
+
| OrcaRouter | 余额 | `ORCAROUTER_API_KEY` | `/v1/balance`(旧部署回退到账单摘要接口) |
|
|
104
109
|
| Moonshot / Kimi API | 余额 | provider `apiKeyEnv` | `/v1/users/me/balance` |
|
|
105
110
|
| OpenCode Go | 订阅 | `OPENCODE_GO_API_KEY` 或本地 `auth.json` | `/zen/go/v1/usage` |
|
|
106
111
|
| Z.ai / 智谱 | 订阅 | `ZAI_API_KEY` | Coding Plan quota/subscription |
|
|
@@ -157,19 +162,6 @@ npx --yes github:Ychris12138/dsh-usage-stats --no-enable
|
|
|
157
162
|
|
|
158
163
|
预算使用本机日历日/月边界:低于 80% 为正常,达到 80% 为 warning,达到 100% 为 critical。`daily` / `monthly` 必须是正数或 `null`;当前版本不做 FX 换算,因此预算货币与可靠价格货币不兼容时状态保持 unknown。
|
|
159
164
|
|
|
160
|
-
### 界面设置 / Display settings
|
|
161
|
-
|
|
162
|
-
Current Session Pill 默认开启。只隐藏 composer 附近的 Pill、保留侧边栏账户面板时:
|
|
163
|
-
|
|
164
|
-
```yaml
|
|
165
|
-
- insert:
|
|
166
|
-
- id: usage-stats
|
|
167
|
-
name: "@ychris12138/dsh-usage-stats"
|
|
168
|
-
config:
|
|
169
|
-
display:
|
|
170
|
-
currentSessionPill: false
|
|
171
|
-
```
|
|
172
|
-
|
|
173
165
|
面板的当前 provider 会保存在浏览器的命名空间 localStorage 中;刷新页面或重启 DSH 后恢复。若该 provider 已被删除,插件会清除旧值并使用原有的 DeepSeek/已配置 provider fallback。该选择不会写入 DSH 设置、服务端缓存或新 API。
|
|
174
166
|
|
|
175
167
|
### 余额型供应商
|
|
@@ -190,6 +182,8 @@ OPENROUTER_MANAGEMENT_KEY: sk-or-v1-your-management-key
|
|
|
190
182
|
|
|
191
183
|
插件按 `total_credits - total_usage` 显示 OpenRouter 余额,并同时展示累计已用和总 credits。普通 Key 的 `/api/v1/key` 只描述单个 Key 的 spending limit,不会被当作账户余额。自定义引用可在 `monitors.openrouter` 中设置 `adapter: openrouter-balance` 与 `credentialRef`。
|
|
192
184
|
|
|
185
|
+
OrcaRouter 优先读取其余额接口 `/v1/balance`,将 paid、free 和 promo credits 汇总为当前可用余额;旧部署没有该接口时,回退到官方文档提供的 OpenAI-compatible 账单摘要接口(订阅端点总额度 + usage 端点累计用量,按美分换算)。任一可用路径返回无法识别的数据时会显示明确的错误状态,不会把未知结果当作 0;无限额度哨兵值会显示为 `∞`,OrcaRouter 路由仍不参与本插件的模型价格估算。
|
|
186
|
+
|
|
193
187
|
### Token Plan 供应商
|
|
194
188
|
|
|
195
189
|
```yaml
|
|
@@ -355,7 +349,7 @@ Constraints:
|
|
|
355
349
|
|
|
356
350
|
Procedure:
|
|
357
351
|
1. Confirm node, npx, and dsh are available.
|
|
358
|
-
2. Prefer the exact npm stable used by Desktop Market: `dsh plugin --profile web add "@ychris12138/dsh-usage-stats@0.3.
|
|
352
|
+
2. Prefer the exact npm stable used by Desktop Market: `dsh plugin --profile web add "@ychris12138/dsh-usage-stats@0.3.2"` (or update the existing scoped package).
|
|
359
353
|
3. Use `github:Ychris12138/dsh-usage-stats` only when I explicitly ask to test unreleased source/RC code.
|
|
360
354
|
4. If dsh plugin is unavailable, use the compatible source installer only with my approval: `npx --yes github:Ychris12138/dsh-usage-stats`.
|
|
361
355
|
5. Do not combine bundle installation with an existing manual dsh-usage-stats Cordis entry.
|
|
@@ -395,7 +389,7 @@ npx --yes github:Ychris12138/dsh-usage-stats --check
|
|
|
395
389
|
- 自定义 monitor 默认要求 HTTPS、同源相对路径、手动 redirect 和 JSON 响应,body 上限为 1 MiB。
|
|
396
390
|
- 发凭据前会筛选域名的 IPv4/IPv6 解析结果并固定一个允许的连接地址,优先使用公网地址;HTTPS 域名解析到 `198.18.0.0/15` 时可作为 Clash/Mihomo 等代理的 synthetic fake-IP 使用。字面量 `198.18/15`、其他私网/特殊地址仍默认拒绝,防止 DNS rebinding 绕过私网限制。
|
|
397
391
|
- `usageBaseURL` 禁止内嵌 username/password;`Authorization`、`X-API-Key`、`API-Key` 等 header 必须由 credential ref 注入。
|
|
398
|
-
-
|
|
392
|
+
- 九个数据端点仅接受 GET;OrcaRouter 集成路由的 GET 只返回布尔状态,POST 仅在用户点击后执行局部 settings mutation,并要求非简单自定义 action header。所有路由同时校验 peer socket 与 Host,支持 IPv4、IPv4-mapped IPv6 和 `[::1]:port`。
|
|
399
393
|
- 用量缓存 `~/.dsh/storages/usage-stats-cache.json` 只保存聚合 Token、会话 id、不透明 revision 与折叠游标,不保存提示词、回复或文件路径。
|
|
400
394
|
|
|
401
395
|
本机反向代理会让插件看到代理自身的回环地址。请勿把端点经反向代理暴露到局域网或公网;确需代理时必须在代理层增加可靠认证与访问控制。安全问题请按 [SECURITY.md](SECURITY.md) 私下报告。
|
|
@@ -404,7 +398,7 @@ npx --yes github:Ychris12138/dsh-usage-stats --check
|
|
|
404
398
|
|
|
405
399
|
Token 统计值来自 `assistant/chunk` 或 `assistant/message` 中 provider-reported `usage`,不是本地估算。相同 turn/step 的后续样本会替换旧样本,并按 `provider/model` 归集。
|
|
406
400
|
|
|
407
|
-
费用是明确标注的估算派生值:每个 usage 样本使用自己的事件时间、原始 provider/model 与四类 token bucket 匹配 `lib/pricing.js`;替换样本会先减去旧费用,再加入新费用。绝不会用“当前价格 × 历史累计 Token
|
|
401
|
+
费用是明确标注的估算派生值:每个 usage 样本使用自己的事件时间、原始 provider/model 与四类 token bucket 匹配 `lib/pricing.js`;替换样本会先减去旧费用,再加入新费用。绝不会用“当前价格 × 历史累计 Token”。每个 session 的派生费用继续进入 `usage.sessions`、session CSV、JSON export 与整体 billing aggregation;插件不会向 DSH composer 注入 session UI。
|
|
408
402
|
|
|
409
403
|
- 活跃会话只处理新追加事件。
|
|
410
404
|
- 持久化会话使用不透明 revision;未变化时不重复读取日志。
|
|
@@ -425,8 +419,10 @@ Token 统计值来自 `assistant/chunk` 或 `assistant/message` 中 provider-rep
|
|
|
425
419
|
| `GET` | `/api/usage-stats/export/daily.csv` | secret-free daily provider/model CSV |
|
|
426
420
|
| `GET` | `/api/usage-stats/export/sessions.csv` | secret-free session CSV |
|
|
427
421
|
| `GET` | `/api/usage-stats/export.json` | versioned usage、budget、pricing provenance 与 account-safe JSON |
|
|
422
|
+
| `GET` | `/api/usage-stats/integrations/orcarouter` | 仅返回 OrcaRouter preset 是否可写/已存在的 secret-free 布尔状态 |
|
|
423
|
+
| `POST` | `/api/usage-stats/integrations/orcarouter` | 用户明确请求后,以 revision-guarded path mutation 幂等加入 preset;要求 `application/json` 与 `X-DSH-Usage-Stats-Action: add-orcarouter` |
|
|
428
424
|
|
|
429
|
-
|
|
425
|
+
除上述 OrcaRouter POST 外,非 GET 返回 `405`;非回环请求返回 `403`。API JSON 使用 `Cache-Control: no-cache`;下载响应使用 `Cache-Control: no-store` 与固定文件名。
|
|
430
426
|
|
|
431
427
|
## 开发与验证 / Development
|
|
432
428
|
|
|
@@ -448,7 +444,9 @@ node scripts/check-balance.mjs
|
|
|
448
444
|
|
|
449
445
|
## 兼容性与致谢 / Compatibility & credits
|
|
450
446
|
|
|
451
|
-
当前 npm stable 为 `0.3.
|
|
447
|
+
当前 npm stable 为 `0.3.2`;`v0.3.2` 的完整发布门禁见 [`docs/release-checklist.md`](docs/release-checklist.md),变更摘要见 [`docs/release-notes-v0.3.2.md`](docs/release-notes-v0.3.2.md)。插件依赖 Harness 客户端模块加载器、Cordis 服务与 session persistence;Harness 预发布接口变化时可能需要同步适配。
|
|
448
|
+
|
|
449
|
+
`display.currentSessionPill` 作为 v0.3.0 legacy boolean 配置键继续被接受,避免旧配置导致启动失败;当前客户端不再注册任何 composer UI,因此该键不再产生可见效果。`session-context` 服务端 API 暂时保留原有响应语义,供 v0.3.0 API compatibility 与后续集成使用。
|
|
452
450
|
|
|
453
451
|
- [Javis603/token-monitor](https://github.com/Javis603/token-monitor):参考多 provider 配额归一化与 Z.ai 限额解析。
|
|
454
452
|
- [xiaoqi20/dsh-opencode-go-usage](https://github.com/xiaoqi20/dsh-opencode-go-usage):参考 DSH 凭据接入、OpenCode `auth.json` 回退与 Bearer usage endpoint。
|
package/SECURITY.md
CHANGED
|
@@ -12,7 +12,7 @@ Never include DeepSeek API keys, credentials files, session contents, raw logs,
|
|
|
12
12
|
|
|
13
13
|
## Scope
|
|
14
14
|
|
|
15
|
-
Security fixes target the latest version on the default branch. The nine
|
|
15
|
+
Security fixes target the latest version on the default branch. The nine read-only data endpoints and the optional OrcaRouter integration route are designed for direct loopback use only; exposing them through a reverse proxy is outside the supported security model unless the proxy adds authentication and access control. The integration route's `GET` response contains only availability booleans. Its `POST` performs the user's explicit, revision-guarded `llm-pi-ai.providers.orcarouter` path mutation and additionally requires JSON plus the non-simple `X-DSH-Usage-Stats-Action: add-orcarouter` header; plugin startup never performs this write.
|
|
16
16
|
|
|
17
17
|
CSV and JSON exports are fixed, versioned projections of normalized usage and account-safe metadata. They do not serialize plugin/provider configuration, credential references or values, raw upstream responses, prompts, replies, or file paths. CSV text fields are quoted and guarded against spreadsheet-formula execution; incomplete monetary derivations remain blank/null.
|
|
18
18
|
|
|
@@ -30,8 +30,11 @@ npm pack --json
|
|
|
30
30
|
- [ ] Confirm a valid old cache is migrated/refolded and retains exact token totals.
|
|
31
31
|
- [ ] Replace the cache with malformed JSON; confirm the plugin rebuilds from authoritative session events without blocking DSH startup.
|
|
32
32
|
- [ ] Confirm existing account monitors remain compatible and no new provider/monitor is inserted into user configuration.
|
|
33
|
-
- [ ] Confirm `display.currentSessionPill`
|
|
33
|
+
- [ ] Confirm legacy `display.currentSessionPill: true` and `false` configurations both start successfully and neither registers composer UI.
|
|
34
34
|
- [ ] Confirm a persisted provider selection survives browser refresh and DSH restart; removing that provider clears the saved id and uses the existing fallback.
|
|
35
|
+
- [ ] Confirm OrcaRouter is added only through the explicit fresh-user synthetic Add action; startup performs no settings mutation and existing provider configuration is not overwritten.
|
|
36
|
+
- [ ] Confirm OrcaRouter balance succeeds through `/v1/balance` or the compatible billing fallback, pricing stays unknown/null, and no credential value reaches the browser or export payloads.
|
|
37
|
+
- [ ] Confirm unchanged persisted fallback logs read only from the folded cursor, appended events remain incremental, and truncation/rewrite still refolds from sequence 0 (#58/#57).
|
|
35
38
|
|
|
36
39
|
## 4. Export and security
|
|
37
40
|
|
|
@@ -43,10 +46,19 @@ npm pack --json
|
|
|
43
46
|
|
|
44
47
|
## 5. Real DSH release-candidate regression
|
|
45
48
|
|
|
49
|
+
### v0.3.2-specific gates
|
|
50
|
+
|
|
51
|
+
- [ ] Current DSH sessions use the public `seq` / `snapshotEvents()` API; no current-session path accesses the removed `session.events` property.
|
|
52
|
+
- [ ] Legacy sessions exposing `seq` plus an `events` array, but no `snapshotEvents()`, remain supported.
|
|
53
|
+
- [ ] UI steady state performs zero persisted `listSnapshots`, zero persisted `readFrom`, zero unchanged aggregate rebuilds, and zero unchanged cache writes.
|
|
54
|
+
- [ ] Background refresh skips `readFrom` and cache writes for unchanged revisions, uses incremental reads for changed revisions, and full-refolds truncation or rewrite cases.
|
|
55
|
+
|
|
46
56
|
- [ ] Test the latest supported `@deepseek-ai/dsh` Desktop/Web release candidate with an isolated profile.
|
|
47
57
|
- [ ] Confirm DSH starts without `Failed to load plugins` or loader identity errors.
|
|
48
|
-
- [ ] Confirm client bundle load
|
|
49
|
-
- [ ]
|
|
58
|
+
- [ ] Confirm client bundle load and that `sidebar.footer.action` is the only extension point registered by dsh-usage-stats; verify panel open/close, provider switching, cost/budget states, and manual Retry.
|
|
59
|
+
- [ ] At 700 / 500 / 400 / 320 px, confirm dsh-usage-stats registers no `conversation.input.*` component or composer DOM and leaves native Permission / Model / Context / Submit layout untouched. Record any remaining sub-400 px overlap as DSH host behavior rather than changing host controls from this plugin.
|
|
60
|
+
- [ ] Test the sidebar action and panel in light and dark themes.
|
|
61
|
+
- [ ] Confirm Last 14 days token values retain an 84 px minimum width, right alignment, and tabular numerals while the date column shrinks/ellipsizes without adding narrow-panel overflow (#75).
|
|
50
62
|
- [ ] With `refresh.enabled: false`, confirm one first account fetch, no expiry-driven upstream requests, manual Retry, and a fresh fetch after provider configuration changes.
|
|
51
63
|
- [ ] Recheck #53 only in an available enterprise proxy environment; record evidence, but do not infer a fix without reproduction.
|
|
52
64
|
- [ ] Recheck #14 with a real MiniMax Coding Plan account: both current and weekly windows plus reset information. Record the sanitized response shape if it fails.
|
|
@@ -56,8 +68,8 @@ npm pack --json
|
|
|
56
68
|
Do not run this section until every release-candidate gate above passes and the maintainer approves preparing the release commit.
|
|
57
69
|
|
|
58
70
|
- [ ] Create a release branch from the reviewed `main` at `RC_BASE_SHA`.
|
|
59
|
-
- [ ] Run `npm run release:sync -- 0.3.
|
|
60
|
-
- [ ]
|
|
71
|
+
- [ ] Run `npm run release:sync -- 0.3.2` once to update `package.json`, `package-lock.json`, the Community Market catalog, and documented stable-version references together.
|
|
72
|
+
- [ ] Confirm the v0.3.2 release notes use final stable wording, preserve the historical v0.3.0/v0.3.1 notes, and do not claim #84 fixed or include #88 behavior.
|
|
61
73
|
- [ ] Run the release gates again against the synchronized version:
|
|
62
74
|
|
|
63
75
|
```bash
|
|
@@ -66,20 +78,20 @@ npm test
|
|
|
66
78
|
npm pack --json
|
|
67
79
|
```
|
|
68
80
|
|
|
69
|
-
- [ ] Inspect the final pack manifest, then commit all version/release metadata changes with `chore: prepare v0.3.
|
|
81
|
+
- [ ] Inspect the final pack manifest, then commit all version/release metadata changes with `chore: prepare v0.3.2 release`.
|
|
70
82
|
- [ ] Record that commit as `RELEASE_SHA`; this replaces `RC_BASE_SHA` as the only publish/tag identity.
|
|
71
83
|
- [ ] Confirm the working tree is clean and `HEAD` equals `RELEASE_SHA`.
|
|
72
84
|
- [ ] Confirm the committed package version, not merely the working-tree version:
|
|
73
85
|
|
|
74
86
|
```bash
|
|
75
|
-
test "$(git show "$RELEASE_SHA:package.json" | jq -r .version)" = "0.3.
|
|
87
|
+
test "$(git show "$RELEASE_SHA:package.json" | jq -r .version)" = "0.3.2"
|
|
76
88
|
```
|
|
77
89
|
|
|
78
90
|
The release invariant is:
|
|
79
91
|
|
|
80
92
|
```text
|
|
81
93
|
npm published source commit
|
|
82
|
-
== v0.3.
|
|
94
|
+
== v0.3.2 tag commit
|
|
83
95
|
== GitHub Release commit
|
|
84
96
|
== package/catalog version commit
|
|
85
97
|
== RELEASE_SHA
|
|
@@ -93,14 +105,14 @@ Do not run this section until the immutable release commit exists and the mainta
|
|
|
93
105
|
- [ ] From a clean checkout/worktree at exactly `RELEASE_SHA`, run `npm publish --access public --registry=https://registry.npmjs.org/`.
|
|
94
106
|
- [ ] `npm view "@ychris12138/dsh-usage-stats" version --registry=https://registry.npmjs.org/` equals the target version.
|
|
95
107
|
- [ ] Merge or push the release commit to `main` according to the chosen branch workflow; verify `main` contains the exact `RELEASE_SHA` without recreating the release changes.
|
|
96
|
-
- [ ] Only after npm and `main` verification: create the signed/annotated `v0.3.
|
|
108
|
+
- [ ] Only after npm and `main` verification: create the signed/annotated `v0.3.2` tag pointing explicitly to `RELEASE_SHA`.
|
|
97
109
|
- [ ] Verify the tag resolves to the published source commit:
|
|
98
110
|
|
|
99
111
|
```bash
|
|
100
|
-
test "$(git rev-parse v0.3.
|
|
112
|
+
test "$(git rev-parse v0.3.2^{commit})" = "$RELEASE_SHA"
|
|
101
113
|
```
|
|
102
114
|
|
|
103
|
-
- [ ] Create the GitHub Release from `v0.3.
|
|
115
|
+
- [ ] Create the GitHub Release from `v0.3.2`; verify it resolves to `RELEASE_SHA`.
|
|
104
116
|
- [ ] Verify the public Pages `catalog-source.json` and `/v1/plugins` response content type, package name, and exact version.
|
|
105
117
|
- [ ] Install the exact npm version through DSH Desktop Community Market and restart the host.
|
|
106
118
|
- [ ] Close the npm/market release issue only after the Desktop Market installation succeeds.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# v0.3.1 release notes
|
|
2
|
+
|
|
3
|
+
`v0.3.1` is a small maintenance release focused on an optional OrcaRouter integration, lower UI intrusion, and two focused correctness and usability fixes.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
### OrcaRouter optional integration
|
|
8
|
+
|
|
9
|
+
- Adds an optional, explicit OrcaRouter provider preset with `orcarouter/auto`.
|
|
10
|
+
- Never mutates settings at startup and never embeds or stores an API key value.
|
|
11
|
+
- Fresh installations expose a user-triggered Add flow; existing OrcaRouter provider settings remain untouched.
|
|
12
|
+
- Reads current wallet data from `/v1/balance`, with the compatible billing-summary fallback retained for older deployments.
|
|
13
|
+
- Includes paid, free, and promotional credit only when the upstream response provides valid values.
|
|
14
|
+
- Keeps OrcaRouter pricing unknown (`null`): an OrcaRouter route never inherits DeepSeek, OpenAI, or another upstream provider's prices.
|
|
15
|
+
- Keeps the sponsored-integration disclosure compact in the README and provider selector.
|
|
16
|
+
|
|
17
|
+
### Sidebar-only UI
|
|
18
|
+
|
|
19
|
+
- Removes all dsh-usage-stats components from `conversation.input.*` and the composer toolbar. Permission, Model, Context, and Submit remain entirely host-owned.
|
|
20
|
+
- Keeps Usage Stats available through `sidebar.footer.action` and the existing sidebar panel.
|
|
21
|
+
- Continues to accept legacy `display.currentSessionPill: true` and `false` configuration for compatibility; the setting is now a no-op and renders no composer UI.
|
|
22
|
+
|
|
23
|
+
### Persisted-session fallback performance
|
|
24
|
+
|
|
25
|
+
- Fixes #57 through #58: unchanged persisted fallback logs no longer refold from sequence 0.
|
|
26
|
+
- Keeps appended events incremental.
|
|
27
|
+
- Still detects truncation or rewrite and performs the required full refold.
|
|
28
|
+
|
|
29
|
+
### Recent usage list polish
|
|
30
|
+
|
|
31
|
+
- Fixes #75 by right-aligning Last 14 days token values.
|
|
32
|
+
- Lets the date column shrink and ellipsize in narrow panels instead of forcing a fixed width.
|
|
33
|
+
|
|
34
|
+
## Compatibility and non-features
|
|
35
|
+
|
|
36
|
+
- No OrcaRouter automatic pricing and no second pricing engine.
|
|
37
|
+
- No new client polling loop and no composer UI.
|
|
38
|
+
- No #84 quota-precision behavior change is claimed in this release; #84 remains open for clarification and retesting.
|
|
39
|
+
- No client price table, fallback unknown-model pricing, Beijing-tariff recomputation, independent account polling, or blended-price sidebar logic from #88 is included.
|
|
40
|
+
- No changes to historical pricing semantics and no credential migration.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# v0.3.2 release notes
|
|
2
|
+
|
|
3
|
+
`v0.3.2` is a focused maintenance release for current DSH session compatibility and lower steady-state usage aggregation overhead.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
### Current DSH Session API compatibility
|
|
8
|
+
|
|
9
|
+
- Fixes #95 for DSH builds where the public `Session.events` array has been replaced by `seq` and `snapshotEvents()`.
|
|
10
|
+
- Current DSH sessions now use the formal `seq` / `snapshotEvents(fromSeq)` API and continue folding only the live event tail.
|
|
11
|
+
- Older DSH session objects without `snapshotEvents()` retain the legacy `events` fallback.
|
|
12
|
+
- Compatibility is capability-based rather than tied to a DSH version string.
|
|
13
|
+
- Preserves live-log shrink recovery and conservative live/persisted refolding semantics.
|
|
14
|
+
|
|
15
|
+
### Lower usage polling overhead
|
|
16
|
+
|
|
17
|
+
- Fixes #94 by separating high-frequency UI usage reads from persisted archive refreshes.
|
|
18
|
+
- Ordinary `/usage` requests fold live-session tails and reuse cached persisted aggregates instead of enumerating every archived session.
|
|
19
|
+
- Persisted archive scanning remains on the existing background refresh path and explicit export paths.
|
|
20
|
+
- Unchanged collections no longer rebuild global aggregates or rewrite `usage-stats-cache.json`.
|
|
21
|
+
- Persisted sessions with unchanged revision tokens perform no event reads.
|
|
22
|
+
- A changed persisted revision still uses the existing incremental cursor logic, with full refolding retained for truncation or rewrite recovery.
|
|
23
|
+
- Scan-mode single-flight guarantees that a required full persisted refresh cannot be satisfied accidentally by an in-flight live-only request.
|
|
24
|
+
|
|
25
|
+
## Compatibility and non-features
|
|
26
|
+
|
|
27
|
+
- No frontend polling interval change and no additional timer or polling loop.
|
|
28
|
+
- No pricing-rule or billing-semantics changes.
|
|
29
|
+
- Pricing/provider fingerprint changes still invalidate and persist derived billing state safely.
|
|
30
|
+
- No private JSONL/zstd reader or plugin-owned persistence index.
|
|
31
|
+
- No attempt to optimize live-to-persisted transitions by trusting the previous live fold.
|
|
32
|
+
- No DSH `sessionProjectionCache` migration or modification.
|
|
33
|
+
- No cold-session eviction or bounded-cache redesign.
|
|
34
|
+
- No provider or composer UI changes.
|
|
35
|
+
- No #84 quota-precision change and no #93 feature work is included in this release.
|
|
36
|
+
- JSONL suffix reads remain an upstream DSH concern; a changed large archive may still require the host persistence backend to decode more data than the logical suffix.
|
package/lib/accounts.js
CHANGED
|
@@ -42,6 +42,7 @@ const OFFICIAL_ADAPTERS = new Set([
|
|
|
42
42
|
"openrouter-balance",
|
|
43
43
|
"moonshot-balance",
|
|
44
44
|
"zai-balance",
|
|
45
|
+
"orcarouter-balance",
|
|
45
46
|
"opencode-go",
|
|
46
47
|
"zai-token-plan",
|
|
47
48
|
"kimi-token-plan",
|
|
@@ -92,6 +93,7 @@ const ADAPTERS = new Set([
|
|
|
92
93
|
"openrouter-balance",
|
|
93
94
|
"moonshot-balance",
|
|
94
95
|
"zai-balance",
|
|
96
|
+
"orcarouter-balance",
|
|
95
97
|
"general",
|
|
96
98
|
"new-api",
|
|
97
99
|
"sub2api",
|
|
@@ -733,7 +735,7 @@ async function queryBuiltInBalance(spec, credential, deps, now) {
|
|
|
733
735
|
...(used === null ? {} : { used }),
|
|
734
736
|
...(total === null ? {} : { total }),
|
|
735
737
|
currency: nonEmptyString(raw.currency) ?? "USD",
|
|
736
|
-
unlimited:
|
|
738
|
+
unlimited: raw.unlimited === true,
|
|
737
739
|
expiresAt: null,
|
|
738
740
|
available: raw.isAvailable !== false,
|
|
739
741
|
breakdown: {
|
package/lib/balance.js
CHANGED
|
@@ -47,6 +47,28 @@ const SCHEMES = {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
|
+
/** OrcaRouter: wallet balance with an OpenAI-compatible billing fallback. */
|
|
51
|
+
orcarouter: {
|
|
52
|
+
// OrcaRouter exposes these endpoints under the public /v1 prefix. Keep the
|
|
53
|
+
// configured origin/path so the normal pinned-network policy still applies
|
|
54
|
+
// and a provider profile never causes a cross-origin request.
|
|
55
|
+
balanceURL: (baseURL) => orcaBillingURL(baseURL, "/balance"),
|
|
56
|
+
subscriptionURL: (baseURL) => orcaBillingURL(baseURL, "/dashboard/billing/subscription"),
|
|
57
|
+
usageURL: (baseURL) => orcaBillingURL(baseURL, "/dashboard/billing/usage"),
|
|
58
|
+
query: async (baseURL, apiKey, timeoutMs, fetchImpl) => {
|
|
59
|
+
// Current deployments expose the wallet's paid/free/promo balance. Older
|
|
60
|
+
// deployments may not have it, so retain the documented OpenAI-shaped
|
|
61
|
+
// subscription + usage fallback for compatibility.
|
|
62
|
+
try {
|
|
63
|
+
return parseOrcaRouterWallet(await requestJSON(orcaBillingURL(baseURL, "/balance"), apiKey, timeoutMs, fetchImpl));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error?.providerStatus !== "unsupported") throw error;
|
|
66
|
+
}
|
|
67
|
+
const subscription = await requestJSON(orcaBillingURL(baseURL, "/dashboard/billing/subscription"), apiKey, timeoutMs, fetchImpl);
|
|
68
|
+
const usage = await requestJSON(orcaBillingURL(baseURL, "/dashboard/billing/usage"), apiKey, timeoutMs, fetchImpl);
|
|
69
|
+
return parseOrcaRouter(subscription, usage);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
50
72
|
/** Moonshot / Kimi: GET {origin}/v1/users/me/balance — available/cash/voucher. */
|
|
51
73
|
moonshot: {
|
|
52
74
|
url: (baseURL) => new URL("/v1/users/me/balance", baseURL).href,
|
|
@@ -82,6 +104,74 @@ const SCHEMES = {
|
|
|
82
104
|
}
|
|
83
105
|
};
|
|
84
106
|
|
|
107
|
+
function orcaBillingURL(baseURL, path) {
|
|
108
|
+
const base = new URL(baseURL);
|
|
109
|
+
const pathname = base.pathname.replace(/\/+$/, "");
|
|
110
|
+
const prefix = pathname === "" ? "/v1" : pathname.endsWith("/v1") ? pathname : `${pathname}/v1`;
|
|
111
|
+
return new URL(`${prefix}${path}`, base.origin).href;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseOrcaRouter(subscription, usage) {
|
|
115
|
+
const total = numberOrNull(subscription?.hard_limit_usd ?? subscription?.soft_limit_usd);
|
|
116
|
+
// OpenAI-compatible dashboard usage is reported in cents. Keep the unit
|
|
117
|
+
// conversion in this adapter so `remaining`, `used`, and `limit` share one
|
|
118
|
+
// consistent currency basis.
|
|
119
|
+
const usageCents = numberOrNull(usage?.total_usage);
|
|
120
|
+
if (total === null || usageCents === null || total < 0 || usageCents < 0) {
|
|
121
|
+
throw providerError("invalid-response", "OrcaRouter billing response is missing numeric quota data");
|
|
122
|
+
}
|
|
123
|
+
const used = usageCents / 100;
|
|
124
|
+
const unlimited = total === 100000000
|
|
125
|
+
&& numberOrNull(subscription?.soft_limit_usd) === total
|
|
126
|
+
&& numberOrNull(subscription?.system_hard_limit_usd) === total;
|
|
127
|
+
return {
|
|
128
|
+
isAvailable: unlimited || total - used > 0,
|
|
129
|
+
currency: "USD",
|
|
130
|
+
total: unlimited ? total : total - used,
|
|
131
|
+
used,
|
|
132
|
+
limit: unlimited ? void 0 : total,
|
|
133
|
+
unlimited,
|
|
134
|
+
granted: void 0,
|
|
135
|
+
toppedUp: void 0
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function creditArrayTotal(value, currency, label) {
|
|
140
|
+
if (value === void 0 || value === null) return 0;
|
|
141
|
+
if (!Array.isArray(value)) throw providerError("invalid-response", `OrcaRouter ${label} credits are invalid`);
|
|
142
|
+
let total = 0;
|
|
143
|
+
for (const entry of value) {
|
|
144
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) throw providerError("invalid-response", `OrcaRouter ${label} credits are invalid`);
|
|
145
|
+
const entryCurrency = typeof entry.unit === "string" && entry.unit.trim() !== "" ? entry.unit.trim().toUpperCase() : currency;
|
|
146
|
+
if (entryCurrency !== currency) throw providerError("invalid-response", `OrcaRouter ${label} credits use a different currency`);
|
|
147
|
+
const amount = numberOrNull(entry.balance_usd ?? entry.balance);
|
|
148
|
+
if (amount === null || amount < 0) throw providerError("invalid-response", `OrcaRouter ${label} credits are missing a numeric balance`);
|
|
149
|
+
total += amount;
|
|
150
|
+
}
|
|
151
|
+
return total;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function parseOrcaRouterWallet(body) {
|
|
155
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) throw providerError("invalid-response", "OrcaRouter wallet response is invalid");
|
|
156
|
+
const currency = typeof body.unit === "string" && body.unit.trim() !== "" ? body.unit.trim().toUpperCase() : null;
|
|
157
|
+
if (currency === null) throw providerError("invalid-response", "OrcaRouter wallet response is missing currency");
|
|
158
|
+
const paid = numberOrNull(body.paid_balance);
|
|
159
|
+
if (paid === null || paid < 0) throw providerError("invalid-response", "OrcaRouter wallet response is missing paid balance");
|
|
160
|
+
const remaining = paid
|
|
161
|
+
+ creditArrayTotal(body.free_credit, currency, "free")
|
|
162
|
+
+ creditArrayTotal(body.promo_credits, currency, "promo");
|
|
163
|
+
return {
|
|
164
|
+
isAvailable: remaining > 0,
|
|
165
|
+
currency,
|
|
166
|
+
total: remaining,
|
|
167
|
+
used: void 0,
|
|
168
|
+
limit: void 0,
|
|
169
|
+
unlimited: false,
|
|
170
|
+
granted: void 0,
|
|
171
|
+
toppedUp: void 0
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
85
175
|
function providerError(status, message, httpStatus) {
|
|
86
176
|
const error = new Error(message);
|
|
87
177
|
error.providerStatus = status;
|
|
@@ -92,30 +182,43 @@ function providerError(status, message, httpStatus) {
|
|
|
92
182
|
function responseStatus(status) {
|
|
93
183
|
if (status === 401 || status === 403) return "unauthorized";
|
|
94
184
|
if (status === 429) return "rate-limited";
|
|
185
|
+
if (status === 404 || status === 405) return "unsupported";
|
|
95
186
|
return status >= 500 ? "unavailable" : "invalid-response";
|
|
96
187
|
}
|
|
97
188
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
189
|
+
function numberOrNull(value) {
|
|
190
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
191
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
192
|
+
const parsed = Number(value);
|
|
193
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
101
196
|
}
|
|
102
197
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (spec === void 0) throw new Error(`no balance scheme "${scheme}"`);
|
|
107
|
-
const response = await fetchImpl(spec.url(baseURL), {
|
|
108
|
-
headers: { authorization: `Bearer ${apiKey}` },
|
|
198
|
+
async function requestJSON(url, apiKey, timeoutMs, fetchImpl) {
|
|
199
|
+
const response = await fetchImpl(url, {
|
|
200
|
+
headers: { authorization: `Bearer ${apiKey}`, accept: "application/json" },
|
|
109
201
|
signal: AbortSignal.timeout(timeoutMs)
|
|
110
202
|
});
|
|
111
203
|
if (!response.ok) throw providerError(responseStatus(response.status), `balance API returned HTTP ${response.status}`, response.status);
|
|
112
|
-
let body;
|
|
113
204
|
try {
|
|
114
|
-
|
|
205
|
+
return await response.json();
|
|
115
206
|
} catch {
|
|
116
207
|
throw providerError("invalid-response", "balance API returned invalid JSON");
|
|
117
208
|
}
|
|
118
|
-
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Map a provider id (dsh adapter id or pi-ai route) to a balance scheme id. */
|
|
212
|
+
export function balanceSchemeOf(providerId) {
|
|
213
|
+
return balanceSchemeForProviderId(providerId);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Query one provider's balance. Throws on transport/HTTP errors. */
|
|
217
|
+
export async function queryBalance(scheme, baseURL, apiKey, timeoutMs = 15000, fetchImpl = fetch) {
|
|
218
|
+
const spec = SCHEMES[scheme];
|
|
219
|
+
if (spec === void 0) throw new Error(`no balance scheme "${scheme}"`);
|
|
220
|
+
if (typeof spec.query === "function") return spec.query(baseURL, apiKey, timeoutMs, fetchImpl);
|
|
221
|
+
return spec.parse(await requestJSON(spec.url(baseURL), apiKey, timeoutMs, fetchImpl));
|
|
119
222
|
}
|
|
120
223
|
|
|
121
224
|
/** Scheme ids with built-in support (for docs/tests). */
|