dsh-cost-meter 1.5.42 → 1.6.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.en.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  **Session cost tracking plugin for the DeepSeek Harness web GUI (bilingual UI)**
6
6
 
7
- 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; from Aug 23, 2026 weekends are billed at off-peak prices all day, shown as “Weekend — all off-peak”) · 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) · quota strip above the input box (budget / Go / coding-plan usage in one row, toggleable)
7
+ 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; from Aug 23, 2026 weekends are billed at off-peak prices all day, shown as “Weekend — all off-peak”) · 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) 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)
8
8
 
9
- [![version](https://img.shields.io/badge/version-1.5.42-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.6.2-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
10
10
  [![npm](https://img.shields.io/npm/v/dsh-cost-meter?label=npm)](https://www.npmjs.com/package/dsh-cost-meter)
11
11
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
12
12
  [![dsh](https://img.shields.io/badge/DeepSeek%20Harness-dsh--plugin-4176E6)](https://github.com/deepseek-ai/deepseek-harness)
@@ -36,6 +36,7 @@ English | [中文](README.md)
36
36
  | 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 |
37
37
  | Summary cards | Settings page | Today / this month / cumulative cost and call counts |
38
38
  | 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 |
39
+ | 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) |
39
40
  | Today's sessions | Settings page | Per-session call count, input/cache/output tokens and cost |
40
41
  | History | Settings page | Per-day totals; retention days configurable (default 180) |
41
42
  | Pre-install history import | Automatic on first launch | After install/upgrade, the first launch automatically replays all host session logs to import conversations from before the plugin was installed (missing dates are rebuilt whole; existing dates only gain previously unknown sessions; idempotent and never double-counts live metering; costs priced at per-event historical rates); a manual re-run entry remains in Settings |
@@ -49,6 +50,7 @@ English | [中文](README.md)
49
50
  | AI price sync | [prompt](docs/AI-PRICE-SYNC-PROMPT.en.md) | DeepSeek official sync; other providers use the verified official price catalog and manual configuration |
50
51
  | Model & Plan adaptation guide | [adaptation doc](docs/model-and-plan-adaptation.en.md) | Adaptation matrix for per-model billing and the 8 Coding Plan vendors, the auto-matching mechanism and price sources ([中文](docs/model-and-plan-adaptation.md)) |
51
52
  | Peak/off-peak alert guide | [alert doc](docs/peak-alert.en.md) | Fully illustrated guide to the pre-switch popup and system notification: effect screenshots (EN/中文), settings reference and usage tips ([中文](docs/peak-alert.md)) |
53
+ | Token Plan usage stats guide | [panel doc](docs/token-plan-stats.md) | Meaning of the four columns in the per-1% & full-window panel, the end-to-end delta estimation method and precision tags, the scope boundary (dsh-made calls only) and usage curves ([中文](docs/token-plan-stats.md#中文)) |
52
54
  | Multi-provider billing | Settings / ledger | OpenAI, Anthropic, Google Gemini, Mistral and other providers with input/output, cache and reasoning-token pricing isolated by provider + model |
53
55
  | Model-name auto-matching | Settings / ledger | Unknown model ids are matched against the price table: case/spaces/hyphens/dots and bracket annotations (e.g. (go)) are ignored — a normalized-equal or containing name hits (e.g. `gpt5.6 luna(go)`); router providers (opencode/zen etc.) search across all vendors; can be restricted to exact match, and unmatched models can be pinned to a specific entry in Settings |
54
56
  | Extended price catalog | Settings → Extended price catalog | Built-in reference catalog grouped by vendor and model family (expandable; vendors collapsed by default); mount entries into billing with one click — mounted third-party models live inside the catalog and stay editable; a per-model “Show directly in Cost settings” toggle chooses which models (DeepSeek included) appear directly in the price table |
@@ -229,22 +231,22 @@ Real captures from an actual DSH sidebar of the period strip and collapsed verti
229
231
  dsh plugin --profile web add dsh-cost-meter
230
232
  ```
231
233
 
232
- **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.5.42`** — review the script before running):
234
+ **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.6.2`** — review the script before running):
233
235
 
234
236
  ```powershell
235
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.42/install.ps1 | iex
237
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.6.2/install.ps1 | iex
236
238
  ```
237
239
 
238
240
  **Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
239
241
 
240
242
  ```sh
241
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.42
243
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.6.2
242
244
  ```
243
245
 
244
246
  Without git, use the GitHub tag archive:
245
247
 
246
248
  ```sh
247
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.42.tar.gz
249
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.6.2.tar.gz
248
250
  ```
249
251
 
250
252
  After installing, **restart** `dsh web` (plugin rows, the Typert manifest and the client bundle are all scanned at startup):
@@ -281,6 +283,9 @@ dsh plugin --profile web add link:./dsh-cost-meter # symlink; edit lib/client.j
281
283
  - The session badge is **billed exactly** at the moment each call is made (host-exported per-call cost), just like daily/monthly/cumulative totals and the budget;
282
284
  - Billing sources are the `usage` block of every model call (including sub-agents, compression, title generation and other auxiliary calls), matching the billable view;
283
285
  - Budget and over-budget warnings **only warn — they never block calls**.
286
+ - **Plan/API dual-track billing** (issue #64): calls on subscription-style channels (MiniMax, manually-marked Codex, etc.) are recorded as catalog-price equivalents only; budget/today-cost/overview cards count the pay-as-you-go (API) channel exclusively.
287
+ - **Full-repo security audit fixes (v1.6.2)**: resolved 3 high-risk findings — ledger writes lost on exit (close/flush ordering), command injection in the release script, panel null-pointer crash on null Go monthly window — plus 30+ medium/low issues including missing hour-bucket entries for routed calls, dead balance-reconciliation warnings, failed custom-balance extraction silently showing $0, and leaked upstream streams on consumer abort; ledger corruption auto-backup, write-failure retry, config-patch atomicity and prototype-chain hardening included. Itemized list: [docs/security-audit-v1.6.2.md](docs/security-audit-v1.6.2.md).
288
+ - **"Include plan totals" toggle** under the overview cards switches every amount between real-money (API channels only, default) and total equivalent cost (plan subscriptions included); routed calls with a missing provider whose models match third-party catalogs are auto-classified into the matching plan, and legacy per-day residuals without model details count toward the API scope; The Token Plan stats panel in Settings → Usage provides per-1% and full-window token/equivalent-cost estimates plus daily/weekly/monthly usage curves; classification is configurable per vendor and per provider:model.
284
289
 
285
290
  ## Data storage
286
291
 
@@ -351,6 +356,8 @@ dsh --profile web --port 3099 # real startup (watch lo
351
356
  - Price sync overwrites the same-named models listed on the official page; custom model entries are unaffected;
352
357
  - Balance lookup needs network access to api.deepseek.com and a valid API key; **the API key is only ever sent to the official domain** (if baseURL points at a non-official host, balance queries refuse to run — model requests are unaffected);
353
358
  - The OpenCode Go quota endpoint is the official opencode.ai endpoint (community-documented); if its response shape changes, the Settings page shows an error and the display can be turned off in Display settings;
359
+ - The per-1% quota / full-window figures in Token Plan stats are estimates: vendors only report percentages with ones-digit quantization (a displayed 1% may really be 0.5%–1.5%), so the plugin derives them from end-to-end deltas over continuous sample segments to suppress quantization noise (segments spanning <5 percentage points are tagged "low reading precision"; samples older than 7 days fall back to live ratio; window edges align to whole hours). Server percentages cover ALL usage of the account — consumption of the same key on other machines/CLIs is not in the local ledger, so low estimates are expected; treat them as cross-plan comparisons only;
360
+ - Plan/API classification is inferred from channel and config; mixed subscription/PAYG usage of one vendor (e.g. Kimi) can be overridden per provider:model in settings.
354
361
  - A restart of `dsh web` is required after installing/updating the plugin.
355
362
 
356
363
  ## Update history
package/README.md CHANGED
@@ -4,9 +4,9 @@
4
4
 
5
5
  **DeepSeek Harness 会话费用统计插件(界面中英双语)**
6
6
 
7
- 本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段;2026-08-23 起周末全天按谷价,显示「周末时段——全谷价」) · 峰/谷切换前弹窗与系统通知提醒(位置/提前量/提醒类型可配) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet 八家) · 输入框上方额度横条(预算/Go/Coding Plan 用量一条横排显示,可开关)
7
+ 本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段;2026-08-23 起周末全天按谷价,显示「周末时段——全谷价」) · 峰/谷切换前弹窗与系统通知提醒(位置/提前量/提醒类型可配) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet / 火山方舟 九家,含 Volcano Ark AK/SK 签名) · Plan/API 双轨计费(订阅额度与按量金额分离统计,每 1% 额度与满窗的 token/等值金额估算及日/周/月曲线) · 输入框上方额度横条(预算/Go/Coding Plan 用量一条横排显示,可开关)
8
8
 
9
- [![version](https://img.shields.io/badge/version-1.5.42-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.6.2-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
10
10
  [![npm](https://img.shields.io/npm/v/dsh-cost-meter?label=npm)](https://www.npmjs.com/package/dsh-cost-meter)
11
11
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
12
12
  [![dsh](https://img.shields.io/badge/DeepSeek%20Harness-dsh--plugin-4176E6)](https://github.com/deepseek-ai/deepseek-harness)
@@ -29,13 +29,14 @@
29
29
  | 官方余额 | 侧边栏顶部 / 设置页(可配) | 总余额 / 赠送 / 充值,自动刷新 + 手动刷新;可选三段进度条(蓝/橙/灰),当日段只统计官方渠道费用(不含 Coding Plan / 自定义 Provider) |
30
30
  | 自定义 Provider 余额 | 侧边栏 / 设置页(可配) | 可配置 HTTP 查询任意 Provider 余额(LiteLLM 等);中/英名称、币种、extract 规则(点路径 / 数字常量 / add / subtract / divide,divide 适配 NewApi 等 quota 端点,见下方[示例](#自定义-provider-余额配置示例newapi-模板));与 Coding Plan 同区可折叠配置 |
31
31
  | OpenCode Go 额度 | 侧边栏 / 设置页 / 右下角(dock,可配) | 滚动 5 小时 / 本周 / 本月用量百分比与重置时间,三档可分别开关,可同时显示预算已用%;Key 自动发现(DSH 凭据库 OPENCODE_GO_API_KEY / 环境变量 / opencode 登录态)或手动填写 |
32
- | Coding Plan 额度 | 侧边栏 / 设置页(每家可配) | 多厂商 coding plan 订阅额度查询(Anthropic Claude Pro/Max、Z.ai/智谱 GLM、MiniMax Token Plan、Kimi Code 本周/5 小时配额(无订阅 Key 时回落 PAYG 余额)、OpenRouter credits、SiliconFlow 余额、CommandCode 5h/周窗口与月度 Credits 余额),各家独立启用开关、Key、显示位置与刷新间隔(侧边栏卡片与 Go 额度同款,收起窄栏显示百分比),凭据只发往官方端点;无凭据/无订阅为中性提示;SCNet 超算互联网 Token Plan 无 API 额度端点,按官方 Credits 抵扣表由本地账本估算月度用量(无需凭据) |
32
+ | Coding Plan 额度 | 侧边栏 / 设置页(每家可配) | 多厂商 coding plan 订阅额度查询(Anthropic Claude Pro/Max、Z.ai/智谱 GLM、MiniMax Token Plan、Kimi Code 本周/5 小时配额(无订阅 Key 时回落 PAYG 余额)、OpenRouter credits、SiliconFlow 余额、CommandCode 5h/周窗口与月度 Credits 余额、火山方舟 Volcano Ark 5h/周/月三档(需 AK/SK 管控面 HMAC 签名,需 ArkReadOnlyAccess + BillingCenterReadOnlyAccess)),各家独立启用开关、凭据、显示位置与刷新间隔(侧边栏卡片与 Go 额度同款,收起窄栏显示百分比),凭据只发往官方端点;无凭据/无订阅为中性提示;SCNet 超算互联网 Token Plan 无 API 额度端点,按官方 Credits 抵扣表由本地账本估算月度用量(无需凭据) |
33
33
  | 额度横条 | 输入框上方(显示设置可开关) | 一条横排 chips 实时显示预算已用% / Go 主窗口 / 各已启用 Coding Plan 用量窗口(短标签+迷你进度条,≥80% 预警、≥100% 超支,悬停见重置时刻);点击任意 chip 即刷新对应数据源(budget→状态、Go→Go 额度、厂商→该家全部窗口),同一厂商多窗口融合为一条 chip 分段显示;首次更新弹引导卡由用户自主决定开关;无可用数据自动隐藏 |
34
34
  | 点击立即刷新 | 侧边栏余额/额度图框 | 官方余额 / 自定义余额 / Coding Plan 图框(含窄栏收起态)点击即触发一次查询,刷新中呼吸闪烁,失败保持原值并在悬停提示说明;键盘 Enter/Space 可触发;更新后首次进入有引导提示 |
35
35
  | 当日费用 | 侧边栏底部(设置按钮上方) | 「今日 ¥x」,悬停见调用次数与 token 明细 |
36
36
  | 预算图框 | 侧边栏底部(余额行与设置按钮之间) | 圆角方形图框:预算、已用%、进度条、今日费用与占预算%、已用/额度,≥80% 预警、≥100% 超支 |
37
37
  | 汇总卡片 | 设置页 | 今日 / 本月 / 累计费用与调用次数 |
38
38
  | Token 用量统计 | 设置页(费用设置) | 历史累计 token 总量(输入/缓存/输出/调用)+ 类 Codex 的 26 周每日用量方格热图,横向铺满设置页宽度,悬停见当日明细 |
39
+ | Token Plan 用量统计 | 设置页(用量) | 各已启用 Coding Plan(含 Go)当前窗口的「每 1% 额度」与「满窗 100%」对应的 token 数与等值金额估算(采样差分/当前用量折算),附每日/每周/每月用量曲线;Plan 类调用金额只记等值,不动真金白银(issue #64) |
39
40
  | 今日会话明细 | 设置页 | 每个会话的调用次数、输入/缓存/输出 token 与费用 |
40
41
  | 历史记录 | 设置页 | 按天汇总,保留天数可配(默认 180 天) |
41
42
  | 历史按模型统计回填 | 设置页(按模型统计) | 按模型统计上线前的旧账本自动回放宿主会话日志重建逐模型 token/费用拆分(旧调用按当时基础价),日志已清理的部分归入「早期未分模型」残差行 |
@@ -50,6 +51,7 @@
50
51
  | AI 价格同步 | [提示词](docs/AI-PRICE-SYNC-PROMPT.md) | DeepSeek 官方同步;其他 provider 使用已核对的官方价格目录与手动配置 |
51
52
  | 模型与 Plan 适配说明 | [适配文档](docs/model-and-plan-adaptation.md) | 各厂商模型计费与 8 家 Coding Plan 的适配矩阵、自动匹配机制与价格来源([English](docs/model-and-plan-adaptation.en.md)) |
52
53
  | 峰/谷切换提醒图解 | [提醒文档](docs/peak-alert.md) | 峰谷切换前弹窗与系统通知的完整图解:效果截图(中/英)、设置项说明与使用建议([English](docs/peak-alert.en.md)) |
54
+ | Token Plan 用量统计图解 | [面板文档](docs/token-plan-stats.md) | 每 1% 与满窗估算的四列含义、首尾差分估算方法与精度标注、口径边界(只统计 dsh 内调用)与用量曲线说明([English](docs/token-plan-stats.md#english)) |
53
55
  | 多 provider 计费 | 设置页 / 账本 | 支持 OpenAI、Anthropic、Google Gemini、Mistral 等 provider 的 input/output、缓存与 reasoning token 价格,按 provider+model 隔离计费 |
54
56
  | 模型名自动匹配 | 设置页 / 账本 | 未知模型 id 自动匹配价格表:忽略大小写/空格/横杠/点号与括号附注,归一化等价或请求名包含表内模型名即命中(如 `gpt5.6 luna(go)`);路由 provider(opencode/zen 等)下跨厂商全库查找;可关闭为仅精确;未命中模型可手动指定计费条目 |
55
57
  | 拓展价格表 | 设置页 → 拓展价格表 | 内置各厂商、按模型家族分类的参考价格目录(点开展开,厂商默认折叠);一键挂载参与计费,挂载的第三方模型默认收入表内可编辑;逐模型「在费用设置直接显示」开关自选哪些模型(含 DeepSeek)在「价格表」区直接显示 |
@@ -231,22 +233,22 @@
231
233
  dsh plugin --profile web add dsh-cost-meter
232
234
  ```
233
235
 
234
- **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.42`**,建议先下载审阅再运行):
236
+ **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.6.2`**,建议先下载审阅再运行):
235
237
 
236
238
  ```powershell
237
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.42/install.ps1 | iex
239
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.6.2/install.ps1 | iex
238
240
  ```
239
241
 
240
242
  **或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
241
243
 
242
244
  ```sh
243
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.42
245
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.6.2
244
246
  ```
245
247
 
246
248
  没有 git 时可用 GitHub tag 打包直链:
247
249
 
248
250
  ```sh
249
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.42.tar.gz
251
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.6.2.tar.gz
250
252
  ```
251
253
 
252
254
  安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
@@ -283,6 +285,9 @@ dsh plugin --profile web add link:./dsh-cost-meter # 符号链接,改 lib/clien
283
285
  - 会话徽章与当日/月度/累计、预算一样,按每次调用的**实际时刻精确计费**(宿主导出的逐次成本);
284
286
  - 计费来源为每次模型调用的 usage 块(含子代理、压缩、标题等辅助调用),与账单口径一致;
285
287
  - 预算与超支提示**仅提醒,不阻止调用**。
288
+ - **Plan/API 双轨计费**(issue #64):订阅制渠道(MiniMax / Codex 手动标记等)的调用金额只记「等值」,预算/今日费用/概览卡片等金额展示仅统计按量计费(API)部分;
289
+ - **「含 Plan 总额」开关**:概览页汇总卡片下的快捷开关切换全部金额展示口径——关闭时仅计真金白银(API 渠道),开启后显示含 Plan 等值的总金额;网关路由(provider 缺失)调用的第三方目录模型自动归入对应订阅归类,无模型明细的历史残差计入 API 口径;设置 → 用量 的 Token Plan 统计面板提供每 1% 额度与满窗的 token/等值金额估算与日/周/月曲线;分类可在配置中按厂商或 provider:model 级覆盖。
290
+ - **全仓安全审计修复(v1.6.2)**:账本退出丢写(close/flush 次序)、发版脚本命令注入、面板空指针崩溃(Go 月窗空值)3 项高危,及路由调用小时桶漏记、官方余额对账告警失效、自定义余额提取失败误显 $0、计费流中断泄漏等 30 余项中低危问题全量修复;账本损坏自动备份、写失败重试、配置补丁原子性、原型链与凭据处理加固。逐项清单见 [docs/security-audit-v1.6.2.md](docs/security-audit-v1.6.2.md)。
286
291
 
287
292
  ## 数据存储
288
293
 
@@ -355,6 +360,8 @@ dsh --profile web --port 3099 # 真机启动(观察启
355
360
  - 价格同步会覆盖官方页面列出的同名模型价格,自定义模型条目不受影响;
356
361
  - 余额查询需要可访问 api.deepseek.com 的网络与有效 API Key;**API Key 只会发往官方域名**(baseURL 指向非官方域名时余额查询拒绝请求,模型请求不受影响);
357
362
  - OpenCode Go 额度接口为 opencode.ai 官方端点(社区文档);接口结构变化时设置页会显示错误,可在显示设置中关闭该显示;
363
+ - Token Plan 统计的「每 1% 额度/满窗」为估算值:各家额度接口只返回百分比且读数存在个位级量化(显示 1% 的真实值可能在 0.5%~1.5%),插件以连续可信段的首尾差分推算以压低量化误差(跨度不足 5 个百分点时标注「读数精度受限」,样本超 7 天回退当前用量折算,窗口边界按小时对齐);服务端百分比统计的是该账号全部用量——同一 Key 在其它机器/CLI 的消耗不在本地账本,估算偏低属预期,仅供跨套餐横向比较;
364
+ - Plan/API 双轨分类基于渠道与配置推断,混合订阅/按量使用同一厂商 Key 的场景(如 Kimi 订阅 + PAYG 混用)可在设置中按 provider:model 手动覆盖。
358
365
  - 安装/更新插件后需重启 `dsh web` 生效。
359
366
 
360
367
  ## 更新历史
@@ -1,17 +1,17 @@
1
1
  {
2
- "generatedAt": "2026-08-18",
2
+ "generatedAt": "2026-08-25",
3
3
  "note": "Auto-generated from lib/pricing.js DEFAULT_PROVIDER_PRICE_TABLE. USD per 1M tokens; unpriced entries have no verified official price.",
4
4
  "providers": {
5
5
  "openai": {
6
6
  "models": {
7
7
  "gpt-5.6-sol": {
8
- "input": 5,
9
- "cachedInput": 0.5,
10
- "output": 30,
8
+ "input": 2,
9
+ "cachedInput": 0.2,
10
+ "output": 10,
11
11
  "billingMode": "flat",
12
12
  "sourceUrl": "https://opencode.ai/docs/zen",
13
- "checkedAt": "2026-08-17",
14
- "notes": "≤272K 档;超过 272K 输入按 $10/$45 计;缓存写入 $6.25"
13
+ "checkedAt": "2026-08-25",
14
+ "notes": "≤272K 档;超过 272K $4/$15 计(缓存读 $0.40、写入 $5);缓存写入 $2.50;目录标注 2026-09-18 前为五折促销价(issue #58)"
15
15
  },
16
16
  "gpt-5.6-terra": {
17
17
  "input": 2,
@@ -287,7 +287,7 @@
287
287
  "billingMode": "flat",
288
288
  "sourceUrl": "https://ai.google.dev/gemini-api/docs/pricing",
289
289
  "checkedAt": "2026-08-17",
290
- "notes": "2026 年底前促销价;2027 起 $1.5/$7.5;Zen 网关按 $1.5/$7.5"
290
+ "notes": "2026 年底前促销价;2027 起 $1.5/$7.5;Go 网关按 $1.5/$7.5"
291
291
  },
292
292
  "gemini-3.6-flash": {
293
293
  "input": 1.5,
@@ -365,7 +365,7 @@
365
365
  "billingMode": "flat",
366
366
  "sourceUrl": "https://opencode.ai/docs/zen",
367
367
  "checkedAt": "2026-08-17",
368
- "notes": "官方人民币价 ¥20/¥100/缓存¥2 每百万;此处为 Zen 网关美元价"
368
+ "notes": "官方人民币价 ¥20/¥100/缓存¥2 每百万;此处为 Go 网关美元价"
369
369
  },
370
370
  "kimi-k2.7-code": {
371
371
  "input": 0.95,
@@ -605,6 +605,30 @@
605
605
  }
606
606
  }
607
607
  },
608
+ "meta": {
609
+ "models": {
610
+ "muse-spark-1.2": {
611
+ "input": 1.25,
612
+ "cachedInput": 0.15,
613
+ "output": 4.25,
614
+ "billingMode": "flat",
615
+ "sourceUrl": "https://opencode.ai/docs/zen",
616
+ "checkedAt": "2026-08-25"
617
+ }
618
+ }
619
+ },
620
+ "meituan": {
621
+ "models": {
622
+ "longcat-2.0": {
623
+ "input": 0.3,
624
+ "cachedInput": 0.006,
625
+ "output": 1.2,
626
+ "billingMode": "flat",
627
+ "sourceUrl": "https://opencode.ai/docs/go",
628
+ "checkedAt": "2026-08-25"
629
+ }
630
+ }
631
+ },
608
632
  "nvidia": {
609
633
  "models": {
610
634
  "nvidia/nemotron-3-ultra-550b-a55b": {
@@ -647,9 +671,12 @@
647
671
  "checkedAt": "2026-08-17"
648
672
  },
649
673
  "glm-5.3": {
650
- "unpriced": true,
674
+ "input": 1.4,
675
+ "cachedInput": 0.26,
676
+ "output": 4.4,
651
677
  "billingMode": "flat",
652
- "notes": "Go 目录在册;官方未公布单价"
678
+ "sourceUrl": "https://opencode.ai/docs/go",
679
+ "checkedAt": "2026-08-25"
653
680
  },
654
681
  "glm-5.2": {
655
682
  "input": 1.4,
@@ -770,8 +797,25 @@
770
797
  "billingMode": "flat",
771
798
  "sourceUrl": "https://opencode.ai/docs/go",
772
799
  "checkedAt": "2026-08-17"
800
+ },
801
+ "longcat-2.0": {
802
+ "input": 0.3,
803
+ "cachedInput": 0.006,
804
+ "output": 1.2,
805
+ "billingMode": "flat",
806
+ "sourceUrl": "https://opencode.ai/docs/go",
807
+ "checkedAt": "2026-08-25"
808
+ },
809
+ "muse-spark-1.2-contributor": {
810
+ "input": 0.1,
811
+ "cachedInput": 0.002,
812
+ "output": 0.2,
813
+ "billingMode": "flat",
814
+ "sourceUrl": "https://opencode.ai/docs/go",
815
+ "checkedAt": "2026-08-25",
816
+ "notes": "低价换取提示词/补全用于训练 Meta 模型;限地区"
773
817
  }
774
818
  }
775
819
  }
776
820
  }
777
- }
821
+ }
package/lib/backfill.js CHANGED
@@ -14,7 +14,8 @@ import { readdirSync, readFileSync, statSync } from 'node:fs'
14
14
  import { join } from 'node:path'
15
15
  import * as zlib from 'node:zlib'
16
16
  import { costOf, providerPriceEntryFor, usdFromCost } from './pricing.js'
17
- import { localDayKey, zeroDay } from './store.js'
17
+ import { localDayKey, zeroDay, splitLedgerApiCost } from './store.js'
18
+ import { billingClassOf, enabledPlanSetOf } from './plan-billing.js'
18
19
 
19
20
  const ZSTD_MAGIC = 4247762216
20
21
  /** 打包行(文本/推理/工具调用增量游程)不含 header 与 usage,回放时跳过。 */
@@ -95,7 +96,10 @@ export function readSessionRecords(path) {
95
96
  const line = lines[i]
96
97
  if (line.length === 0) continue
97
98
  try {
98
- records.push(JSON.parse(line))
99
+ const rec = JSON.parse(line)
100
+ // 打包行(text/reasoning/tool-call 增量游程)在解析入口就丢弃:它们是
101
+ // 大日志的体积主体,回放阶段反正会跳过,提前过滤才能兑现 OOM 缓解。
102
+ if (rec !== null && typeof rec === 'object' && !PACKED_ROW_TYPES.has(rec.type)) records.push(rec)
99
103
  } catch {
100
104
  // 坏行跳过:回放是尽力而为,不让单行损坏阻断整个会话。
101
105
  }
@@ -112,7 +116,9 @@ export function readSessionRecords(path) {
112
116
  }
113
117
  if (pending.length > 0) {
114
118
  try {
115
- records.push(JSON.parse(pending))
119
+ const rec = JSON.parse(pending)
120
+ // 与上方 consumeText 同规则:打包行不入记录(文件末尾无换行的残行兜底)。
121
+ if (rec !== null && typeof rec === 'object' && !PACKED_ROW_TYPES.has(rec.type)) records.push(rec)
116
122
  } catch {
117
123
  // 文件末尾无换行的残行:同上,尽力而为。
118
124
  }
@@ -185,7 +191,7 @@ export function listSessionLogs(root, onlySessionIds = null) {
185
191
  * @returns { sessionId, title, createdAt, days, seedDays }。
186
192
  */
187
193
  export function replaySessionRecords(records, config, wantDates = null) {
188
- const zeroBuckets = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0 })
194
+ const zeroBuckets = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0, apiCost: 0 })
189
195
  const num = value => {
190
196
  const n = Number(value)
191
197
  return Number.isFinite(n) && n > 0 ? n : 0
@@ -209,6 +215,7 @@ export function replaySessionRecords(records, config, wantDates = null) {
209
215
  reasoning: current.reasoning + sign * sample.buckets.reasoning,
210
216
  calls: current.calls + sign,
211
217
  cost: current.cost + sign * sample.cost,
218
+ apiCost: (current.apiCost ?? 0) + sign * (sample.apiCost ?? sample.cost),
212
219
  }
213
220
  }
214
221
  for (const event of records) {
@@ -242,14 +249,16 @@ export function replaySessionRecords(records, config, wantDates = null) {
242
249
  let usage = null
243
250
  let turn = 0
244
251
  let step = 0
245
- if (event.type === 'assistant/chunk' && event.data?.chunk?.type === 'usage' && event.data.chunk.usage !== undefined) {
252
+ // 判空与去重键缺省值与 index.js 投影完全同构(usage null 不入账;turn/step
253
+ // 缺省 0,保证实时投影与回放对同一事件产生同一 (turn,step) 去重键)。
254
+ if (event.type === 'assistant/chunk' && event.data?.chunk?.type === 'usage' && event.data.chunk.usage != null) {
246
255
  usage = event.data.chunk.usage
247
- turn = event.data.turn
248
- step = event.data.step
249
- } else if (event.type === 'assistant/message' && event.data?.usage !== undefined) {
256
+ turn = event.data.turn ?? 0
257
+ step = event.data.step ?? 0
258
+ } else if (event.type === 'assistant/message' && event.data?.usage != null) {
250
259
  usage = event.data.usage
251
- turn = event.data.turn
252
- step = event.data.step
260
+ turn = event.data.turn ?? 0
261
+ step = event.data.step ?? 0
253
262
  } else {
254
263
  continue
255
264
  }
@@ -289,11 +298,14 @@ export function replaySessionRecords(records, config, wantDates = null) {
289
298
  const cost = usdFromCost(priced,
290
299
  resolved.billingMode === 'deepseek-peak' && config?.prices?.currency === 'CNY' ? 'CNY' : 'USD',
291
300
  config?.exchangeRate)
301
+ // Plan/API 双轨分类(issue #64):与 store.account() 同一分类器,回放条目
302
+ // 的 apiCost 只含真金白银部分(plan 类金额仅记等值);传 prices 供路由判定。
303
+ const apiCost = billingClassOf(provider, model, config?.planBilling, enabledPlanSetOf(config), config?.prices) === 'api' ? cost : 0
292
304
  const providerKey = `${provider}:${model}`
293
305
  // 聚合目标按事件分段路由;被替换的旧样本从它当时所在的段扣回(与旧版
294
306
  // 单流先减后加的净效果一致,仅种子段的量落到 seedDays 而非 days)。
295
307
  if (prev !== null) shift(prev, -1, prev.seed ? seedDays : days)
296
- const sample = { key, date, providerKey, buckets, cost, seed: isSeed }
308
+ const sample = { key, date, providerKey, buckets, cost, apiCost, seed: isSeed }
297
309
  shift(sample, 1, isSeed ? seedDays : days)
298
310
  last = sample
299
311
  }
@@ -398,9 +410,12 @@ export async function backfillLegacyLedger(ledger, sessionsRoot) {
398
410
  // 回放完整覆盖当日全部调用与 token 时,按回放结果重算当日总额(issue #18):
399
411
  // 旧版本曾把订阅制模型模糊匹配到同家族付费价实时误计费,回放按事件时刻
400
412
  // 正确计价,重算可修正历史虚高;仅部分覆盖时保留原始记录,差额入 legacy 行。
413
+ // reasoning 必须一并相等:旧样本缺失 reasoningTokens 时其余四桶仍可能恰好
414
+ // 相等,漏判会以不含 reasoning 成本的回放值覆盖账本且无痕。
401
415
  if (replayed.calls === (day.calls ?? 0)
402
416
  && replayed.input === (day.input ?? 0) && replayed.output === (day.output ?? 0)
403
- && replayed.cacheRead === (day.cacheRead ?? 0) && replayed.cacheWrite === (day.cacheWrite ?? 0)) {
417
+ && replayed.cacheRead === (day.cacheRead ?? 0) && replayed.cacheWrite === (day.cacheWrite ?? 0)
418
+ && replayed.reasoning === (day.reasoning ?? 0)) {
404
419
  day.cost = replayed.cost
405
420
  result.recosted = (result.recosted ?? 0) + 1
406
421
  }
@@ -436,14 +451,16 @@ export async function backfillLegacyLedger(ledger, sessionsRoot) {
436
451
  acc.output += b.output ?? 0
437
452
  acc.cacheRead += b.cacheRead ?? 0
438
453
  acc.cacheWrite += b.cacheWrite ?? 0
454
+ acc.reasoning += b.reasoning ?? 0
439
455
  acc.calls += b.calls ?? 0
440
456
  acc.cost += b.cost ?? 0
441
457
  return acc
442
- }, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, calls: 0, cost: 0 })
458
+ }, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0 })
443
459
  if (sTotals.calls === (session.calls ?? 0)
444
460
  && dayIsEmpty
445
461
  && sTotals.input === (session.input ?? 0) && sTotals.output === (session.output ?? 0)
446
- && sTotals.cacheRead === (session.cacheRead ?? 0) && sTotals.cacheWrite === (session.cacheWrite ?? 0)) {
462
+ && sTotals.cacheRead === (session.cacheRead ?? 0) && sTotals.cacheWrite === (session.cacheWrite ?? 0)
463
+ && sTotals.reasoning === (session.reasoning ?? 0)) {
447
464
  session.cost = sTotals.cost
448
465
  }
449
466
  }
@@ -467,7 +484,12 @@ export async function backfillLegacyLedger(ledger, sessionsRoot) {
467
484
  }
468
485
  }
469
486
  }
470
- if (result.days > 0 || result.sessions > 0 || result.titles > 0) ledger.scheduleWrite()
487
+ // 双轨口径一致性(B-4):上面的金额重算只改写了容器 cost,apiCost 仍是旧值;
488
+ // 向下修正后会出现 apiCost > cost 的倒挂(净化过的旧条目默认 apiCost = cost,
489
+ // 一次性迁移标记不会自愈)。按最新桶级分类重建全部容器的 apiCost——
490
+ // store.splitLedgerApiCost 幂等且只读分类配置,store.js 不反向依赖本模块,无环。
491
+ const apiTouched = splitLedgerApiCost(ledger)
492
+ if (result.days > 0 || result.sessions > 0 || result.titles > 0 || apiTouched > 0) ledger.scheduleWrite()
471
493
  return result
472
494
  }
473
495
 
@@ -491,7 +513,7 @@ function mergeDayMaps(target, source) {
491
513
 
492
514
  function mergeBucketsInto(target, source) {
493
515
  for (const [key, b] of Object.entries(source)) {
494
- const current = target[key] ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0 }
516
+ const current = target[key] ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0, apiCost: 0 }
495
517
  target[key] = {
496
518
  input: current.input + (b.input ?? 0),
497
519
  output: current.output + (b.output ?? 0),
@@ -500,6 +522,7 @@ function mergeBucketsInto(target, source) {
500
522
  reasoning: current.reasoning + (b.reasoning ?? 0),
501
523
  calls: current.calls + (b.calls ?? 0),
502
524
  cost: current.cost + (b.cost ?? 0),
525
+ apiCost: (current.apiCost ?? 0) + (b.apiCost ?? b.cost ?? 0),
503
526
  }
504
527
  }
505
528
  }
@@ -515,12 +538,13 @@ function cloneDayMap(pm) {
515
538
  reasoning: b.reasoning ?? 0,
516
539
  calls: b.calls ?? 0,
517
540
  cost: b.cost ?? 0,
541
+ apiCost: b.apiCost ?? b.cost ?? 0,
518
542
  }
519
543
  }
520
544
  return out
521
545
  }
522
546
 
523
- /** 汇总一组 provider:model 桶的合计(input/output/…/calls/cost)。 */
547
+ /** 汇总一组 provider:model 桶的合计(input/output/…/calls/cost/apiCost)。 */
524
548
  function sumDayMap(pm) {
525
549
  return Object.values(pm).reduce((acc, b) => {
526
550
  acc.input += b.input ?? 0
@@ -530,8 +554,9 @@ function sumDayMap(pm) {
530
554
  acc.reasoning += b.reasoning ?? 0
531
555
  acc.calls += b.calls ?? 0
532
556
  acc.cost += b.cost ?? 0
557
+ acc.apiCost += b.apiCost ?? b.cost ?? 0
533
558
  return acc
534
- }, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0 })
559
+ }, { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, calls: 0, cost: 0, apiCost: 0 })
535
560
  }
536
561
 
537
562
  /** 把合计桶累加到日期/会话条目的顶层字段上。 */
@@ -543,6 +568,7 @@ function addTotalsTo(target, totals) {
543
568
  target.reasoning += totals.reasoning
544
569
  target.calls += totals.calls
545
570
  target.cost += totals.cost
571
+ target.apiCost = (target.apiCost ?? 0) + (totals.apiCost ?? 0)
546
572
  }
547
573
 
548
574
  /** 由回放结果构造账本会话条目(title/at 缺席时不写键,与 Typert schema 一致)。 */
@@ -556,6 +582,7 @@ function sessionEntryOf(sessionId, info, totals, pm) {
556
582
  reasoning: totals.reasoning,
557
583
  calls: totals.calls,
558
584
  cost: totals.cost,
585
+ apiCost: totals.apiCost ?? totals.cost,
559
586
  byProviderModel: cloneDayMap(pm),
560
587
  }
561
588
  if (typeof info.title === 'string' && info.title.length > 0) entry.title = info.title
@@ -662,6 +689,9 @@ function subtractBucketInto(target, source) {
662
689
  target.reasoning = Math.max(0, (target.reasoning ?? 0) - (source.reasoning ?? 0))
663
690
  target.calls = Math.max(0, (target.calls ?? 0) - (source.calls ?? 0))
664
691
  target.cost = Math.max(0, (target.cost ?? 0) - (source.cost ?? 0))
692
+ if (target.apiCost !== undefined || source.apiCost !== undefined) {
693
+ target.apiCost = Math.max(0, (target.apiCost ?? target.cost ?? 0) - (source.apiCost ?? source.cost ?? 0))
694
+ }
665
695
  }
666
696
 
667
697
  /**
@@ -676,10 +706,11 @@ function subtractBucketInto(target, source) {
676
706
  * @param sessionsRoot - 宿主会话根目录($DSH_HOME/sessions)。
677
707
  * @returns { sessions, days, scanned } 受影响的 fork 会话数、扣除的日期数与扫描文件数。
678
708
  */
679
- export function repairForkSeed(ledger, sessionsRoot) {
709
+ export async function repairForkSeed(ledger, sessionsRoot) {
680
710
  const result = { sessions: 0, days: 0, scanned: 0 }
681
711
  for (const path of listSessionLogs(sessionsRoot)) {
682
- result.scanned += 1
712
+ // 全量同步扫描会长时间占住事件循环:每 8 份让出一次(与回填/导入同策略)。
713
+ if ((result.scanned += 1) % 8 === 0) await new Promise(resolve => setImmediate(resolve))
683
714
  let records
684
715
  try {
685
716
  records = readSessionRecords(path)
@@ -748,7 +779,7 @@ export function repairForkSeed(ledger, sessionsRoot) {
748
779
  * @param ledger - 已打开的账本。
749
780
  * @returns { groups, removedCost } 合并的重复组数与扣除的金额合计(USD)。
750
781
  */
751
- export function repairProviderDupes(ledger) {
782
+ export async function repairProviderDupes(ledger) {
752
783
  const result = { groups: 0, removedCost: 0 }
753
784
  // 清洗单个容器(day 或 session):返回是否发生扣除。
754
785
  const repairContainer = (container) => {
@@ -781,7 +812,10 @@ export function repairProviderDupes(ledger) {
781
812
  return touched
782
813
  }
783
814
  let scheduled = false
815
+ let scannedContainers = 0
784
816
  for (const day of Object.values(ledger.days ?? {})) {
817
+ // 大账本逐容器指纹分组是纯 CPU 工作:每 8 个容器让出一次事件循环。
818
+ if ((scannedContainers += 1) % 8 === 0) await new Promise(resolve => setImmediate(resolve))
785
819
  if (day === null || typeof day !== 'object') continue
786
820
  if (repairContainer(day)) scheduled = true
787
821
  if (Array.isArray(day.sessions)) {
@@ -17,7 +17,15 @@
17
17
 
18
18
  import { AsyncLocalStorage } from 'node:async_hooks'
19
19
 
20
- /** 嵌套深度标记:值恒为 1(存在即嵌套);按异步上下文隔离,并发流互不串扰。 */
20
+ /**
21
+ * 嵌套深度标记:值恒为 1(存在即嵌套);按异步上下文隔离,并发流互不串扰。
22
+ *
23
+ * 设计边界(R-7):恒定深度标记会把「外层 pull 的异步上下文内同步分发瀑布时」
24
+ * 发起的任何 ctx.llm.stream() 一律判定为已记账的嵌套调用。单层包装链
25
+ * (issue #48 目标:适配器在自己 stream() 体内恰好发起一次上游调用)语义正确;
26
+ * 若未来出现扇出型路由——单次 pull 内派生 >1 条上游流——第二条及之后的上游
27
+ * 将被误判为嵌套而漏记。出现此类路由时需改用按流实例区分的标记身份。
28
+ */
21
29
  const llmStreamDepth = new AsyncLocalStorage()
22
30
 
23
31
  /**
@@ -37,6 +45,7 @@ export function createLlmStreamBilling({ account }) {
37
45
  return (async function* costMeterStream() {
38
46
  let usage = null
39
47
  const iterator = downstream[Symbol.asyncIterator]()
48
+ let completed = false
40
49
  try {
41
50
  for (;;) {
42
51
  // 在深度标记内拉取下游:下游适配器(modlens 等)体内再发起的
@@ -44,12 +53,21 @@ export function createLlmStreamBilling({ account }) {
44
53
  const result = await llmStreamDepth.run(1, () => iterator.next())
45
54
  if (result.done) break
46
55
  const chunk = result.value
47
- if (chunk !== null && chunk !== undefined && chunk.type === 'usage' && chunk.usage !== undefined) {
56
+ if (chunk !== null && chunk !== undefined && chunk.type === 'usage' && chunk.usage != null) {
48
57
  usage = chunk.usage
49
58
  }
50
59
  yield chunk
51
60
  }
61
+ completed = true
52
62
  } finally {
63
+ // 手写 for(;;) 不具备 for-await 的自动关闭语义:消费方提前中断(break/
64
+ // return/throw)时必须显式向下游传播 return(),否则上游 HTTP 流会继续
65
+ // 跑完(供应商照常计满 token,usage 块却无人读取),socket 悬挂泄漏。
66
+ if (!completed) {
67
+ try {
68
+ await iterator.return?.()
69
+ } catch {}
70
+ }
53
71
  if (usage !== null) {
54
72
  try {
55
73
  account(usage, options?.model, options?.sessionId, Date.now(), options?.provider)