dsh-cost-meter 1.7.37 → 1.7.39

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
@@ -8,9 +8,9 @@
8
8
 
9
9
  Per-conversation cost · daily totals · OpenCode Go subscription quota display · budget with usage percentage · official account balance · custom provider balance · balance progress bar · history · peak/off-peak pricing hours display (peak hours UTC 01:00–04:00, 06:00–10:00; weekends and Chinese public holidays are off-peak all day, with separate labels) · pre-switch popup & system-notification alerts for peak/off-peak changes (position / lead time / alert type configurable) · one-click price sync from the official docs · Codex-style token usage heat grid · multi-vendor model pricing (built-in 90+ model price catalog with auto-matching) · mainstream Coding Plan quota queries & display (Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet / Volcano Ark / Qwen / Xiaomi MiMo) plan/API dual-track billing (subscription quota vs pay-as-you-go money separated, per-1% & full-window token/equivalent-cost estimates with daily/weekly/monthly curves) · · quota strip above the input box (budget / Go / coding-plan usage in one row, toggleable)
10
10
 
11
- [![version](https://img.shields.io/badge/version-1.7.37-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
11
+ [![version](https://img.shields.io/badge/version-1.7.39-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
12
12
 
13
- **v1.7.37** bills Chinese public holidays at off-peak DeepSeek rates all day, updates the period display, and recalculates affected history when complete session logs are available. Work-shift weekends remain off-peak under DeepSeek's published rule. See the [release notes](docs/release-notes/v1.7.37.md).
13
+ **v1.7.39** fixes quota and balance caching, duplicate billing after 1,024 sessions, malformed ledger recovery, and external usage validation. See the [release notes](docs/release-notes/v1.7.39.md).
14
14
 
15
15
  [![npm](https://img.shields.io/npm/v/dsh-cost-meter?label=npm)](https://www.npmjs.com/package/dsh-cost-meter)
16
16
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
@@ -40,6 +40,7 @@ Per-conversation cost · daily totals · OpenCode Go subscription quota display
40
40
  | Today's cost | Sidebar bottom (above the settings button) | “Today ¥x”, hover for call count and token details |
41
41
  | Budget box | Sidebar bottom (between the balance row and the settings button) | Rounded-square frame: budget, used %, progress bar, today's cost & share of budget, used/limit; ≥80% warning, ≥100% over-budget |
42
42
  | Summary cards | Settings page | Today / this month / cumulative cost and call counts |
43
+ | External usage | Settings → Cost | [Read-only snapshots](docs/external-usage.md#english) from other processes on the same account; per-source tokens/calls/cost and recent days, plus DSH + external daily/monthly/all-time totals. Official balance reconciliation stays DSH-only. |
43
44
  | Token usage stats | Settings page (Cost section) | All-time token totals (input/cache/output/calls) + a Codex-style 26-week daily usage heat grid that fills the settings width; hover a cell for that day's detail |
44
45
  | Token Plan usage stats | Settings page (Usage) | Per enabled coding plan (incl. Go): per-1% quota and full-window token / equivalent-cost estimates for the current windows (sample delta / live ratio), plus daily/weekly/monthly usage curves; plan-channel amounts are equivalent-only and never touch real money (issue #64) |
45
46
  | Today's sessions | Settings page | Per-session call count, input/cache/output tokens and cost |
@@ -316,22 +317,22 @@ On Node.js 20, use `npm install -g pnpm@10` instead. See [pnpm installation and
316
317
  dsh plugin --profile web add dsh-cost-meter
317
318
  ```
318
319
 
319
- **PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.7.37`** — review the script before running):
320
+ **PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.7.39`** — review the script before running):
320
321
 
321
322
  ```powershell
322
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.7.37/install.ps1 | iex
323
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.7.39/install.ps1 | iex
323
324
  ```
324
325
 
325
326
  **Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
326
327
 
327
328
  ```sh
328
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.7.37
329
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.7.39
329
330
  ```
330
331
 
331
332
  Without git, use the GitHub tag archive:
332
333
 
333
334
  ```sh
334
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.7.37.tar.gz
335
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.7.39.tar.gz
335
336
  ```
336
337
 
337
338
  After installing, **restart** `dsh web` (plugin rows, the Typert manifest and the client bundle are all scanned at startup):
package/README.zh-CN.md CHANGED
@@ -8,9 +8,9 @@
8
8
 
9
9
  本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段;周末与中国法定假日全天按谷价,分别标注) · 峰/谷切换前弹窗与系统通知提醒(位置/提前量/提醒类型可配) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet / 火山方舟 / 千问 / 小米 MiMo 十一家,含 Volcano Ark AK/SK 签名与 MiMo 控制台 Cookie 查询) · Plan/API 双轨计费(订阅额度与按量金额分离统计,每 1% 额度与满窗的 token/等值金额估算及日/周/月曲线) · 输入框上方额度横条(预算/Go/Coding Plan 用量一条横排显示,可开关)
10
10
 
11
- [![version](https://img.shields.io/badge/version-1.7.37-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
11
+ [![version](https://img.shields.io/badge/version-1.7.39-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
12
12
 
13
- **v1.7.37**:中国法定假日全天按 DeepSeek 谷价计费,界面同步标注假日;可按完整会话日志修正受影响的历史费用。调休上班的周末仍按官方规则全天谷价。详见[更新说明](docs/release-notes/v1.7.37.md)。
13
+ **v1.7.39**:修复额度与余额缓存、超过 1024 个会话后的重复计费、异常账本恢复和外部用量校验。详见[更新说明](docs/release-notes/v1.7.39.md)。
14
14
 
15
15
  [![npm](https://img.shields.io/npm/v/dsh-cost-meter?label=npm)](https://www.npmjs.com/package/dsh-cost-meter)
16
16
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
@@ -40,6 +40,7 @@
40
40
  | 当日费用 | 侧边栏底部(设置按钮上方) | 「今日 ¥x」,悬停见调用次数与 token 明细 |
41
41
  | 预算图框 | 侧边栏底部(余额行与设置按钮之间) | 圆角方形图框:预算、已用%、进度条、今日费用与占预算%、已用/额度,≥80% 预警、≥100% 超支 |
42
42
  | 汇总卡片 | 设置页 | 今日 / 本月 / 累计费用与调用次数 |
43
+ | 外部用量 | 设置 → 费用 | 同账户其他进程通过[只读快照](docs/external-usage.md#中文)提供用量;按来源查看 token、调用、费用和近期每日记录,并显示 DSH + 外部的日/月/累计合计。官方余额对账仍只用 DSH 账本。 |
43
44
  | Token 用量统计 | 设置页(费用设置) | 历史累计 token 总量(输入/缓存/输出/调用)+ 类 Codex 的 26 周每日用量方格热图,横向铺满设置页宽度,悬停见当日明细 |
44
45
  | Token Plan 用量统计 | 设置页(用量) | 各已启用 Coding Plan(含 Go)当前窗口的「每 1% 额度」与「满窗 100%」对应的 token 数与等值金额估算(采样差分/当前用量折算),附每日/每周/每月用量曲线;Plan 类调用金额只记等值,不动真金白银(issue #64) |
45
46
  | 今日会话明细 | 设置页 | 每个会话的调用次数、输入/缓存/输出 token 与费用 |
@@ -316,22 +317,22 @@ Node.js 20 请改用 `npm install -g pnpm@10`。版本要求见 [pnpm 官方安
316
317
  dsh plugin --profile web add dsh-cost-meter
317
318
  ```
318
319
 
319
- **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.7.37`**,建议先下载审阅再运行):
320
+ **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.7.39`**,建议先下载审阅再运行):
320
321
 
321
322
  ```powershell
322
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.7.37/install.ps1 | iex
323
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.7.39/install.ps1 | iex
323
324
  ```
324
325
 
325
326
  **或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
326
327
 
327
328
  ```sh
328
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.7.37
329
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.7.39
329
330
  ```
330
331
 
331
332
  没有 git 时可用 GitHub tag 打包直链:
332
333
 
333
334
  ```sh
334
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.7.37.tar.gz
335
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.7.39.tar.gz
335
336
  ```
336
337
 
337
338
  安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
@@ -0,0 +1,61 @@
1
+ # External usage snapshots / 外部用量快照
2
+
3
+ ## English
4
+
5
+ Another local process can write `$DSH_HOME/storages/cost-meter/external_usage.json` to add usage from the **same account** that bypasses DSH. The plugin reads the file on each state refresh. It never calls Hindsight or any other external service. Settings → Cost shows DSH and external usage separately, plus combined today/month/all-time totals. Expand a source to see its token buckets, calls, costs and up to 90 recent daily rows. DSH session totals and official balance reconciliation remain DSH-only.
6
+
7
+ Write the entire snapshot to a temporary file in the same directory, then atomically rename it to `external_usage.json`. Replace the file on every refresh; **do not append deltas**. The same snapshot can be read repeatedly without double-counting. Use ISO 8601 timestamps with `Z` or an explicit offset. `source` is a stable name (1–64 characters), not a model name. Up to eight sources are accepted. The file is capped at 512 KiB; each source can supply up to 3,660 daily summaries, or the whole file can supply up to 5,000 individual records. Snapshots older than 24 hours are marked stale; those older than 30 days, malformed, oversized, or unreadable are ignored. The producer should refresh regularly and keep historical days in each replacement snapshot. All-time totals cover only the days supplied by the producer.
8
+
9
+ **Daily summary mode**: `input` means uncached input, `cached` means cache-read input, and `cacheWrite` is a separate optional bucket. All buckets are mutually exclusive. `costUsd` is the exact USD cost calculated by the producer; it is required because daily totals cannot reconstruct peak/off-peak or long-context prices for individual calls. `calls` is the number of calls. Optional `reasoning` is reported separately. Amounts are converted to the selected display currency by the plugin.
10
+
11
+ ```json
12
+ {
13
+ "fetchedAt": "2026-09-26T09:00:00Z",
14
+ "sources": [
15
+ {
16
+ "source": "Hindsight",
17
+ "days": {
18
+ "2026-09-25": {
19
+ "input": 4888706,
20
+ "output": 592536,
21
+ "cached": 1082880,
22
+ "calls": 363,
23
+ "costUsd": 1.2345
24
+ }
25
+ }
26
+ }
27
+ ]
28
+ }
29
+ ```
30
+
31
+ **Call record mode**: send one record per completed call with a stable unique `id` within that source. The plugin calculates USD cost using its configured provider/model prices and each call's timestamp, including the configured DeepSeek peak/off-peak rules. Current price settings are reapplied when the snapshot is read, so use daily `costUsd` summaries if historical costs must remain fixed. An unpriced provider/model invalidates the snapshot instead of silently recording zero cost. `input`, `output`, `cached`, optional `cacheWrite` and `reasoning` have the same meanings as in summary mode. Each record represents one call.
32
+
33
+ ```json
34
+ {
35
+ "fetchedAt": "2026-09-26T09:00:00Z",
36
+ "source": "Hindsight",
37
+ "records": [
38
+ {
39
+ "id": "request-abc123",
40
+ "at": "2026-09-25T08:40:00Z",
41
+ "provider": "deepseek",
42
+ "model": "deepseek-chat",
43
+ "input": 1200,
44
+ "output": 250,
45
+ "cached": 100
46
+ }
47
+ ]
48
+ }
49
+ ```
50
+
51
+ A snapshot can contain a single top-level source as above, or a `sources` array. Each source uses either `days` or `records`, never both. Do not include calls already reported to DSH; source separation prevents same-name model collisions but cannot detect duplicate calls across systems. The file must contain no API keys or other credentials. The budget widget continues to use DSH's own ledger; combined totals appear in the external usage section.
52
+
53
+ ## 中文
54
+
55
+ 其他本地进程可将同一账户、但绕过 DSH 的用量写入 `$DSH_HOME/storages/cost-meter/external_usage.json`。插件每次刷新状态时读取,不直接请求 Hindsight 等服务。设置 → 费用按来源显示外部用量、DSH 与外部合计,以及近 90 天的逐日数据;DSH 会话金额和官方余额对账仍只使用 DSH 账本。
56
+
57
+ 采集器先在同目录写完整临时文件,再原子重命名为 `external_usage.json`。每次刷新替换整份快照,**不要追加增量**;重复读取不会重复计费。时间使用带 `Z` 或时区偏移的 ISO 8601。`source` 是稳定的来源名称,不是模型名。最多 8 个来源;文件上限 512 KiB;每来源最多 3660 条日汇总,或整份快照最多 5000 条调用记录。超过 24 小时未更新会标为过期;超过 30 天、损坏、过大或不可读的快照会被忽略。刷新时应保留所需的历史日期;累计费用只覆盖采集器提供的日期。
58
+
59
+ 上方第一例为**每日汇总**:`input` 是非缓存输入,`cached` 是缓存读取,可选 `cacheWrite` 是独立缓存写入;各桶互不重叠。`costUsd` 是采集器计算的准确美元费用,必须提供,因为日合计不能还原每次调用的峰谷或长上下文价格。`calls` 是调用次数,`reasoning` 可单独提供。插件按当前显示汇率展示费用。
60
+
61
+ 第二例为**逐次记录**:每次完成的调用提供来源内唯一且稳定的 `id`。插件按 `at`、provider/model 及现有价表计算美元费用,包括 DeepSeek 峰谷规则。快照每次读取会应用当前价格设置;若历史费用必须固定,请使用含 `costUsd` 的日汇总。未知价格的模型会使整份快照失效,避免误记零费用。每个来源只能在 `days` 和 `records` 中选一种。不要包含已由 DSH 记录的调用;来源分栏能区分同名模型,但无法自动识别跨系统重复调用。文件中不要存放密钥。预算图框仍按 DSH 账本计算;合计显示在外部用量区。
@@ -94,18 +94,22 @@ async function readPayload(response, locale) {
94
94
  } catch { throw failure(locale, 'payload') }
95
95
  }
96
96
 
97
- export async function queryAliyunBalance(ctx, config, { fetchImpl = fetch } = {}) {
97
+ export async function queryAliyunBalance(ctx, config, { fetchImpl = fetch, signal } = {}) {
98
+ signal?.throwIfAborted()
98
99
  const locale = config?.locale
99
100
  const [ak, sk, token] = await Promise.all(ALIYUN_BALANCE_CREDENTIAL_VARS.map(name => resolveCredential(ctx, name)))
100
101
  if (!ak || !sk) throw Object.assign(failure(locale, 'missing'), { soft: true })
101
102
  let response
102
103
  try {
103
104
  // 每次网络重试重新生成时间戳与 nonce;URL、Action、body 均固定为只读账户查询。
104
- response = await fetchWithRetry(ALIYUN_BALANCE_URL, {}, {
105
+ response = await fetchWithRetry(ALIYUN_BALANCE_URL, { signal }, {
105
106
  attempts: 2, timeoutMs: 15000,
106
107
  fetchImpl: (url, init) => fetchImpl(url, { ...signAliyunBalanceRequest(ak, sk, token), signal: init.signal }),
107
108
  })
108
- } catch { throw failure(locale, 'network') }
109
+ } catch {
110
+ signal?.throwIfAborted()
111
+ throw failure(locale, 'network')
112
+ }
109
113
  if (!response.ok) {
110
114
  await response.body?.cancel().catch(() => {})
111
115
  if (response.status >= 300 && response.status < 400) throw failure(locale, 'redirect')