dsh-token-plugin 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-usage-plugin contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,203 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ > This fork is currently intended for local installation from a packed tarball; npm publication has not been verified.
2
+
3
+ <div align="center">
4
+
5
+ # DeepSeek Harness Usage & Cost Tracker (dsh-usage-plugin)
6
+
7
+ **English** · [简体中文](./README.zh.md)
8
+
9
+ [GitHub](https://github.com/realhenrylan/dsh-token-plugin) · MIT License
10
+
11
+ **A community plugin for DeepSeek Harness** — records token usage and cost for every model call, with peak/off-peak billing, a calendar heatmap, and CSV / JSON / PNG export.
12
+
13
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
14
+ ![Node](https://img.shields.io/badge/node-%3E%3D18-339933)
15
+ ![Platform](https://img.shields.io/badge/platform-web%20%26%20desktop-4d9fff)
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+
22
+ ## Overview
23
+
24
+ dsh-usage-plugin is a **usage & cost tracker** plugin in the DeepSeek Harness ecosystem (a DSH plugin shipped as a Host + Client two-in-one package). After installation, the **"Usage & Cost"** tab appear in the Web UI, right after "Conversation" and "Trace":
25
+
26
+ > Supports **Windows / macOS / Linux**: paths are handled per platform (`node:path`), and the folder picker / "reveal in file manager" use each OS's native mechanism (macOS: `osascript` / `open`; Linux: `zenity` / `xdg-open`). Export does not depend on Windows-only commands.
27
+
28
+ - **Usage & Cost**: records each model call's token usage and cache hits (input miss / cache hit / cache write / output / reasoning / finish reason), and computes cost using DeepSeek's peak/valley or base pricing (peak hours on weekdays are automatically priced by Beijing time 09:00–12:00 and 14:00–18:00; since 2026-08-23 weekends are billed entirely at the off-peak rate; since 2026-09-10 12:00 Beijing time the post-V4.1-Flash peak/valley prices apply — `deepseek-flash` off-peak cache hit 0.02 / input 1 / output 4 CNY, peak at twice that). Model names come from the actual request parameters, so non-DeepSeek models are shown truthfully instead of "unknown model"; models without an official price are counted as 0. The overview shows a by-model table plus a by-API-provider × model drill-down (each provider grouped with every model's calls and peak/off-peak cost split) and a grand total row. The overview also supports **date filtering** (Today / Last 7 days / Last 30 days / All, plus a custom start–end range), so the aggregate stats can be scoped to any single day or date range.
29
+ - **Usage Calendar**: a monthly daily-usage heatmap (colored by cost or call count), hover for details including the peak/off-peak cost split, click a day for its call list and peak/off-peak totals, plus a per-day statistics table with peak cost / off-peak cost / total columns and rollups. Supports **date-range filtering** (Today / 7 days / 30 days / All plus custom start–end dates): when a range is active, the daily stats table shows every day in range across months, the top cards switch to "Calls in range / Cost in range" with range totals, and the heatmap only colors days inside the range.
30
+ - **Cache Hit List**: newest-first, fully scrollable, with quick filters (Today / 7 days / 30 days / All) and custom date ranges; the summary line and footer total split peak vs off-peak consumption with a grand cost total. The list is paginated (100 rows per page), so it stays smooth even with large data volumes.
31
+ - **Interrupted calls shown truthfully**: calls that were aborted / errored / timed out (e.g. manually stopped generation, stream interruption) are shown with a red **"Interrupted"** badge, the finish reason (Interrupted / Error / Timeout) and a `—` cost. The official console still counts these as API requests and bills their actual tokens, but the harness does not report their usage to the plugin — so the plugin records them at 0 tokens, keeping the **call count aligned with the official console** while costs are unaffected. The Overview's "Calls" card adds a `· interrupted N (not billed)` hint.
32
+ - **Local stats vs official console**: a fixed notice banner at the top of the panel explains that this panel reflects calls captured locally by the plugin (official prices + peak/off-peak hours), and that the official console (platform.deepseek.com usage page) may show a higher amount because: ① interrupted/failed/timed-out calls are still billed by the console while the plugin records them as 0; ② calls from other API keys on your account (other apps/scripts) do not pass through DeepSeek Harness — the console includes them, the plugin does not; ③ for exact reconciliation, export the official monthly billing CSV and compare. When interrupted calls are detected, the banner also shows a red "Current records include N interrupted call(s) (not billed)" line.
33
+ - **Price Table**: the official DeepSeek API price table (covering `deepseek-flash` = DeepSeek-V4.1-Flash and `deepseek-v4-pro`; the legacy ids `deepseek-v4-flash` / `deepseek-v4-flash-vision-exp` stay callable and fold into the Flash tier) — base and peak/valley unit prices shown side by side (peak vs off-peak). The peak/off-peak tables are date-split: the first version applies from 2026-08-17, and the **post-V4.1-Flash version** applies from 2026-09-10 12:00 Beijing time (`deepseek-flash` off-peak lowered to cache hit 0.02 / input 1 / output 4 CNY, peak at twice that; `deepseek-v4-pro` unchanged) — past calls are always billed with the version in force at their own time. Editable in-panel and persisted to `pricing.json`, with a reset-to-default option.
34
+ - **Export**: CSV / JSON / **PNG long image** (newest-first, up to the latest 2000 records, warns if exceeded; the PNG report includes peak/off-peak cost columns), to any directory (native picker), auto-opens the folder after export.
35
+ - **Import**: merge-imports JSON / CSV files, deduplicated by time.
36
+ - **Persistence**: records are written live to `<session workspace>/dsh-usage/usage-records.json` and restored on restart (cap 100000 records).
37
+ - **UI adaptation**: panel typography scales with the app's display-size setting (em-relative fonts); wide tables scroll horizontally on desktop (`max-content` + `overflow-x`) and **fit the screen width on mobile (≤900px, no horizontal scrollbar)**; popup cards adapt to the viewport.
38
+ - **English UI (i18n)**: the panel follows the harness's own language setting (General Settings → Language) — switch it there and the plugin follows instantly, no separate toggle, no `localStorage` override. The bilingual dictionary covers the whole panel, the peak/off-peak billing-period hints, and the PNG report; host-level Conversation/Settings tab labels are re-read per render, so they follow the language switch live.
39
+ - **Per-message token popup**: when a turn finishes, the assistant message's footer action row shows a **"Turn tokens"** button; clicking opens a `Token Details` popup in two layers — **Conversation total** (the whole session's total tokens / total cost / run time / cache-hit rate) and **This turn** (this output's tokens, turn tokens, turn cost, turn duration, turn cache-hit rate, a cache-hit bar, and per-model cards showing each model's input·miss / cache hit / output (+ reasoning) plus its peak/off-peak cost split). Durations are shown as `Xm Ys`. Each usage record is tagged with the conversation (`sessionId`), so per-turn and per-conversation stats are computed independently.
40
+ - **Internal/tool calls grouped separately**: internal/placeholder calls (e.g. `dsh2shell-*` with model `fake`) are excluded from the model cost breakdown and collected in a collapsible **"Tool calls (internal)"** group below the breakdown, so only real model calls count in the cost tables.
41
+ - **Mobile adaptation**: tables fit the screen width on ≤900px (no horizontal scrollbar); wide tables collapse the middle columns; the popup and its stat cards / per-model cards adapt to the viewport.
42
+
43
+ ---
44
+
45
+ ## Screenshots
46
+
47
+ ### Usage & Consumption
48
+ ![Usage & Consumption](./docs/assets/usage-overview.png)
49
+
50
+ ## Installation from this checkout
51
+
52
+ Build a local package and install it through DSH:
53
+
54
+ ```bash
55
+ npm pack
56
+ dsh plugin --profile web add ./dsh-token-plugin-2.0.0.tgz
57
+ ```
58
+
59
+ Restart the DSH service after installation. The package has not been published to npm under this name; installation by name requires publication first. Desktop recommended listings are maintained separately.
60
+
61
+ ## What's in the package
62
+
63
+ One npm package = a **host half** (Node-side Cordis plugin: recording, billing, export — see `lib/index.js`) + a **client half** (browser-side panel — see `lib/client.js`, which talks to the host via `/usage/api`).
64
+
65
+ The package integrates with DSH through two declarations:
66
+
67
+ | Declaration | Purpose |
68
+ | --- | --- |
69
+ | `dsh.bundle.patch` (`cordis.patch.yml`) | Lets DSH recognize it as a **standard bundle plugin package**: `dsh plugin --profile <name> add <package>` installs and wires it in one command, no manual config editing |
70
+ | `dsh.client` + `exports["./client"]` | Lets the web client auto-load the browser panel at `/plugins/<package>/client.js` |
71
+
72
+ So for users, **installation is one command** — no YAML editing, no manual file copying.
73
+
74
+ ---
75
+
76
+ ## Updating and uninstalling
77
+
78
+ To update, pack the current checkout again and install the resulting tarball using the command above. To uninstall, run `dsh plugin --profile web remove dsh-token-plugin` and restart DSH.
79
+
80
+ ## Data & locations
81
+
82
+ > **Since v1.9.2**, records are stored in a **fixed, dedicated data directory** (fixes [#4](https://github.com/feiyang-dev/dsh-usage-plugin/issues/4)). The path no longer follows the session workspace / `~/.dsh` / desktop-app install dir, so your history never "disappears" (counted as 0) when the workspace changes, and the path shown in the UI equals the on-disk path.
83
+
84
+ - **Records**: `<data root>/dsh-usage/usage-records.json`
85
+ - Resolution order for the **data root** (data always lands in the **first writable** dir of this list, **never in the workspace** unless all of the below are unwritable):
86
+ 1. env var `DSH_USAGE_DATA_DIR` (if set) — overrides everything;
87
+ 2. Windows: `%LOCALAPPDATA%\dsh-usage-plugin` (falls back to `%APPDATA%` if `LOCALAPPDATA` is unset);
88
+ 3. user home dir: `~/dsh-usage-data` (Windows `%USERPROFILE%\dsh-usage-data`, macOS/Linux `~/dsh-usage-data`);
89
+ 4. **fallback (rare)**: current workspace `<workspace>/dsh-usage` — only used when all system/user dirs above are unwritable, and the panel will show a "persistence disabled" warning.
90
+ - **Writes bypass the model sandbox**: persistence is done by the host plugin process's own filesystem, not subject to the `workspace-write` sandbox, so the fixed directory is always writable and data is not lost when switching workspaces.
91
+ - Default on Windows: `%LOCALAPPDATA%\dsh-usage-plugin\dsh-usage\usage-records.json`
92
+ - **Legacy data auto-merge**: on first start, records previously scattered in `%USERPROFILE%\dsh-usage`, `~/.dsh/dsh-usage`, and each workspace's `dsh-usage` (or `.dsh-usage-records.json`) are merged into the fixed root, deduplicated by **record identity** — the `recordId` of new records, or a composite fingerprint (session + timestamp + model + purpose + four token buckets + finish reason) for legacy ones. This is no longer a millisecond-only comparison, so two different requests inside the same millisecond no longer overwrite each other. **Records written by older versions stay fully readable**: missing `recordId` / `pricingTime` / `unit` and friends are all optional.
93
+ - **Writes are atomic**: a temporary file in the same directory is written, flushed, and then atomically renamed over the target; a failure leaves the existing file untouched.
94
+ - **A corrupt file is never silently zeroed**: if the data file exists but cannot be parsed, the plugin no longer treats it as empty data (older versions did — and then overwrote the whole history with that empty array). It renames the original to `usage-records.json.corrupt-<timestamp>.json` and keeps starting, so the data can be recovered by hand.
95
+ - Price config (edited & saved in the panel): `<data root>/dsh-usage/pricing.json`
96
+ - Price-table snapshots (appended on every price edit / reset, capped at 200, append-only): `<data root>/dsh-usage/pricing-history.json`
97
+ - Used only to review "which table applied at that moment"; it **never participates in cost calculation**. You can also override the built-in legal-holiday list with `holidays: ["2027-01-01", ...]` in `pricing.json`.
98
+ - Default export dir: `<data root>/dsh-usage/{csv,json,images}/`
99
+ - Custom export dir: set in the panel's "Export target directory" or click "Choose directory…"
100
+ - Startup diagnostics (if the plugin fails to activate): `dsh-usage-boot.log` next to the data root
101
+
102
+ ---
103
+
104
+ ## Metering scope
105
+
106
+ Usage and cost here are **recorded call by call from DeepSeek Harness `llm/stream` events**, plus a `session/event` listener for search-backend requests. Read the boundaries below before relying on the numbers.
107
+
108
+ ### Precisely metered (provider-reported values)
109
+
110
+ | Source | Notes |
111
+ | --- | --- |
112
+ | Main agent conversation | Four token buckets, cache-hit counts and finish reason per call |
113
+ | Session-title generation | `purpose: session-title` |
114
+ | Context compaction | `purpose: compaction` |
115
+ | In-process subagents | They share the main agent's `ctx.llm`, so they are intercepted too |
116
+
117
+ The billing formula matches the official one: `uncached input × miss price + cache hit × hit price + output × output price`. There is **no separate cache-write price** (DeepSeek never reports that field; writing to cache is charged at the miss price), and `reasoning` is already included in `output`, so it is never billed twice.
118
+
119
+ ### Not precisely metered (flagged separately in the UI)
120
+
121
+ | Source | Why it is unreachable | What the plugin does |
122
+ | --- | --- | --- |
123
+ | **DeepSeek search backend calls from `web_search`** | `web-search-deepseek` issues a native `fetch` straight to `api.deepseek.com/anthropic/v1/messages` inside the Harness, deliberately bypassing `ctx.llm`; upstream only persists the **request** (`web/deepseek-search-llm-request`) and **discards the response usage entirely** (its response type does not even declare a usage field). No data source exposes this spend. | Records the **exact call count** and derives a **lower bound** for the input side from the request body, all flagged `estimated`. The output side is never guessed. The overview shows "estimated cost ≥ ¥X (N calls)". |
124
+ | External CLI subagents (claude-code / codex) | Separate child processes, fully outside `ctx.llm`, emitting no events | Not counted |
125
+ | Output produced before an aborted request | The adapter emits no usage chunk on abort | Recorded as a zero-token "interrupted call" so the call count still matches the console |
126
+ | Official billing counts HTTP requests; the plugin counts streams | Harness-level retries do not open a new stream | Call counts may differ from the official `request_count` |
127
+
128
+ ### Bottom line
129
+
130
+ - For ordinary chat / coding workloads the plugin's total tracks the official bill closely;
131
+ - **With heavy `web_search` use the plugin's total is materially lower than the official bill** — the gap is the search backend, and the panel says so explicitly;
132
+ - In all cases, **the DeepSeek console bill is authoritative**.
133
+
134
+ ### Past prices are never rewritten
135
+
136
+ Each record **freezes the three unit prices in effect when it was written** (its `unit` field). Therefore:
137
+
138
+ - Editing the price table afterwards (from the panel or by hand in `pricing.json`) **only affects future records**; past costs are not recomputed;
139
+ - Records written by older versions or imported by hand have no frozen snapshot, so their cost uses the **current** table and follows later edits — the overview and price pages report how many such records exist;
140
+ - To deliberately reprice all history, use **"Reprice all history with the current table"** on the price page (or call API `{"action":"repriceAll"}`). That is the only entry point that rewrites historical cost.
141
+
142
+ ---
143
+
144
+ ## FAQ
145
+
146
+ | Symptom | Cause / Fix |
147
+ | --- | --- |
148
+ | Panel reports `Unexpected end of JSON input` | The plugin row is missing the `inject` list, so the route isn't registered. Add the inject list per Method B3 and restart |
149
+ | Panel blank / no top tab | Plugin not activated. Check `dsh-usage-boot.log`; confirm the `cordis.patch.yml` row exists with the correct `name` |
150
+ | `dsh plugin` reports pnpm not found | Install pnpm: `npm install -g pnpm` |
151
+ | Install can't reach the npm registry | Set a mirror: `npm config set registry https://registry.npmmirror.com` (or `pnpm config set registry ...`) and retry |
152
+ | After uninstall, still reports `Cannot find package '@feiyang666/...'` | A package reference remains in the profile. Remove the corresponding row from `cordis.patch.yml` and the package name from `dsh.profile.bundles`, then restart |
153
+ | On Desktop 0.2.x every cost is ¥0 and the price page reports that all records lack a frozen unit price | 1.18.0 and earlier only recognised the provider ids `deepseek-official` / `deepseek`, while the desktop app's official-account provider id is `deepseek-account`. Since **1.19.0** every `deepseek-*` id is matched by prefix, new records freeze their unit prices, and historical records are priced correctly too — issue #19 |
154
+
155
+ ---
156
+
157
+ ## Related Projects
158
+
159
+ | Project | Description | Installation |
160
+ | --- | --- | --- |
161
+ | [Data Vault (dsh-vault)](https://github.com/feiyang-dev/dsh-vault) | Auto backup / wipe detection / one-click restore — protects chat history and workspace data | One-click from the desktop app, or `dsh plugin add @feiyang666/dsh-vault` |
162
+ | [DeepSeek-Harness](https://github.com/deepseek-ai/DeepSeek-Harness) | Official CLI / Web service | Quick start below |
163
+
164
+ ### Running DeepSeek Harness
165
+
166
+ **Quick start (via npm)**
167
+
168
+ Install Node.js, then run:
169
+
170
+ ```bash
171
+ npx @deepseek-ai/dsh web
172
+ ```
173
+
174
+ This command starts the Web UI at the default address http://127.0.0.1:3080. See the [Web UI Guide](https://github.com/deepseek-ai/DeepSeek-Harness) for details.
175
+
176
+ **Run from source**
177
+
178
+ To run from the repository source:
179
+
180
+ ```bash
181
+ git clone https://github.com/deepseek-ai/deepseek-harness.git
182
+ cd deepseek-harness
183
+ pnpm install
184
+ pnpm run build
185
+ pnpm dsh web
186
+ ```
187
+
188
+ ## Acknowledgements
189
+
190
+ - **[@ayleen](https://github.com/ayleen)**: implemented the full English UI layer (i18n) with reactive language switching, and moved balance presentation to semantic keys ([#7](https://github.com/feiyang-dev/dsh-usage-plugin/pull/7)).
191
+ - **[@wuhuqif176](https://github.com/wuhuqif176)**: added the Bailian (Qwen) Token Plan quota query to the balance panel ([#8](https://github.com/feiyang-dev/dsh-usage-plugin/pull/8)).
192
+ - **[@Martin-soaring-dev](https://github.com/Martin-soaring-dev)**: prepared the plugin for public contribution (packaging, plugin-contract checks, docs & CI) and submitted the contribution branch that became the basis for the open-source releases ([#6](https://github.com/feiyang-dev/dsh-usage-plugin/pull/6)).
193
+ - **[@mumuer1024](https://github.com/mumuer1024)**: reported and diagnosed the persistence-path drift across workspaces (history "disappearing" / counted as 0) and proposed storing data in a fixed, dedicated directory ([#4](https://github.com/feiyang-dev/dsh-usage-plugin/issues/4)); also reported that DeepSeek search-backend calls went unmetered and could understate cost by an order of magnitude, with a fully cross-checkable trail against the official bill ([#12](https://github.com/feiyang-dev/dsh-usage-plugin/issues/12)).
194
+ - **[@liu3734](https://github.com/liu3734)**: reported and diagnosed the Windows-only path handling / spawn issues on macOS (POSIX) and proposed the cross-platform fix ([#1](https://github.com/feiyang-dev/dsh-usage-plugin/issues/1)).
195
+ - **[@zhiqiangme](https://github.com/zhiqiangme)**: submitted a large PR adding history backfill and a sidebar balance entry ([#14](https://github.com/feiyang-dev/dsh-usage-plugin/pull/14)). Three of its findings — **the `cacheWriteTokens` fallback double-counting the four buckets, millisecond-only dedup overwriting records, and non-atomic writes silently zeroing a corrupt file** — became the basis for the v1.18.0 metering fixes (see [CHANGELOG](./CHANGELOG.md)).
196
+
197
+ ## Changelog
198
+
199
+ - [CHANGELOG.md](./CHANGELOG.md) — detailed notes for every release.
200
+
201
+ ## License
202
+
203
+ MIT © dsh-usage-plugin
package/README.zh.md ADDED
@@ -0,0 +1,205 @@
1
+ > 此分支目前通过本地安装包安装;尚未确认发布到 npm。
2
+
3
+ <div align="center">
4
+
5
+ # DeepSeek Harness 用量与消耗插件(dsh-usage-plugin)
6
+
7
+ [English](./README.md) | **简体中文**
8
+
9
+ [GitHub](https://github.com/realhenrylan/dsh-token-plugin) · MIT License
10
+
11
+
12
+ **由开发者制作的 DeepSeek Harness 插件** —— 记录每一次模型调用的 token 用量与消耗,支持峰谷计费、日历热力图与 CSV / JSON / PNG 导出。
13
+
14
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
15
+ ![Node](https://img.shields.io/badge/node-%3E%3D18-339933)
16
+ ![Platform](https://img.shields.io/badge/platform-web%20%26%20desktop-4d9fff)
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+
23
+ ## 简介
24
+
25
+ dsh-usage-plugin 是 DeepSeek Harness 生态的**用量与消耗统计插件**(DSH plugin,Host + Client 双面一体包)。装好后在 WebUI 顶部「对话」「轨迹」之后会出现 **「用量与消耗」** tab:
26
+
27
+ > 支持 **Windows / macOS / Linux**:路径按当前平台处理(`node:path`),目录选择与「打开所在目录」均调用系统原生方式(macOS 用 `osascript` / `open`,Linux 用 `zenity` / `xdg-open`),导出不依赖 Windows 专用命令。
28
+
29
+ - **用量与消耗**:记录每次模型调用的 token 用量与缓存命中(输入·未命中 / 缓存命中 / 缓存写入 / 输出 / 推理 / 结束原因),按 DeepSeek 峰谷/基础价格计算消耗(工作日高峰时段按北京时间 9:00–12:00、14:00–18:00 计价;自 2026-08-23 起周末全天按空闲价计费;自 2026-09-10 12:00(北京时间)起改用 V4.1 Flash 调价后的峰谷价——`deepseek-flash` 空闲价 缓存命中 0.02 / 输入 1 / 输出 4 元,高峰为其两倍)。模型名以请求参数为准如实显示(非 DeepSeek 模型不再显示为「未知模型」,无官方价格的模型消耗按 0 统计)。概览含「按模型」表与「按 API 服务商 × 模型」明细表(每个服务商一组,组内列出各模型的调用与高峰/空闲分列消耗),底部有总费用合计。概览还支持**日期筛选**(今天 / 近 7 天 / 近 30 天 / 全部,外加自定义起止区间),可将聚合统计限定到任意一天或日期范围。
30
+ - **用量日历**:按月查看每日用量热力图(按消耗或调用数着色),悬停查看详情(含高峰 / 空闲消耗拆分)、点击某天查看当日调用明细与高峰/空闲消耗统计,附每日统计表(高峰消耗 / 空闲消耗 / 总消耗分列)与汇总。支持**日期范围筛选**(今天 / 近7天 / 近30天 / 全部 + 自定义起止日期):筛选激活时每日统计表跨月显示范围内所有天、顶部卡片切换为「范围内调用 / 范围内消耗」并按范围汇总、热力图仅对范围内日期着色。
31
+ - **明细(缓存命中 / 按模型 / 按服务商)**:最新记录排在最前,支持 今天 / 近7天 / 近30天 / 全部 快捷筛选与自定义起止日期区间,汇总行与表尾合计区分高峰消耗 / 空闲消耗 / 总费用合计;列表分页渲染(每页 100 条),记录量大也不卡顿。
32
+ - **中断调用如实呈现**:被中止 / 出错 / 超时(如手动停止生成、流中断)的调用在面板中标红显示「中断」徽标、结束原因(已中断 / 错误 / 超时)与消耗 `—`。这类调用官方后台仍会计入「API 请求次数」并按实际 token 计费,但 harness 不向插件上报 usage——插件按 0 token 记录它们,**调用次数与官方口径一致,消耗不受影响**;概览「调用次数」卡片会提示 `中断 N(未计费)`。
33
+ - **本地统计 vs 官方后台的差异提示**:用量面板顶部固定显示说明横幅——本面板统计的是插件本地捕获的调用(官方价格 + 峰谷时段),与官方后台(platform.deepseek.com 用量页)相比金额可能更低:① 中断/出错/超时的调用官方仍按实际 token 计费而插件按 0 记录;② 账号下其他 API Key(其它应用/脚本)的调用不经过 DeepSeek Harness,官方包含而插件不包含;③ 精确对账可导出官方月度账单 CSV 对比。检测到中断调用时,横幅额外以红字显示「当前记录中有 N 次中断调用(未计费)」。
34
+ - **价格表**:**DeepSeek 官方 API 价格表**(覆盖官方模型 `deepseek-flash`(DeepSeek-V4.1-Flash)与 `deepseek-v4-pro`;旧名 `deepseek-v4-flash` / `deepseek-v4-flash-vision-exp` 仍可调用并并入 Flash 档位),展示基础价与峰谷价(高峰/空闲)单价表,高峰价与空闲价分列展示。峰谷价表按官方调价**分段**:2026-08-17 起为第一版峰谷价;**2026-09-10 12:00(北京时间)起为 V4.1 Flash 调价后的新版**(`deepseek-flash` 空闲价下调为 缓存命中 0.02 / 输入 1 / 输出 4 元,高峰为其两倍;`deepseek-v4-pro` 不变),历史调用始终按其发生时刻对应的版本计费。支持在面板内直接编辑价格并持久化(数据目录 `pricing.json`),也可一键恢复默认。
35
+ - **导出**:CSV / JSON / **PNG 长图**(按最新在前展示,最多含最近 2000 条,超出会提示;PNG 报告含高峰 / 空闲消耗分列统计),可导出到任意目录(原生目录选择器),导出后自动打开所在目录。
36
+ - **导入**:选择文件(JSON / CSV)合并导入,按时间去重。
37
+ - **持久化**:记录实时落盘到**固定专用数据目录**(如 `%LOCALAPPDATA%\dsh-usage-plugin\dsh-usage\usage-records.json`,详见下文「数据目录」),与当前工作区无关,重启自动恢复(上限 100000 条,尽量多存)。
38
+ - **界面适配**:面板字号跟随应用「显示大小」设置自动缩放(em 相对字号);桌面端宽表在容器内横向滑动(max-content + overflow-x),**移动端(≤900px)表格贴合屏宽、不再出现横向滑条**,弹窗与卡片随视口自适应。
39
+ - **英文界面(i18n)**:面板语言跟随宿主的「通用设置 → 语言」——在那里切换,插件即时跟随,不再自带切换按钮,也不再用 `localStorage` 覆盖系统语言。双语词典覆盖整个面板(含高峰 / 空闲时段说明、PNG 报告);外部「会话 / 设置」标签页名按渲染时读取,切换语言后实时更新。
40
+ - **消息底部 token 弹窗**:一轮输出完成后,助手消息底部操作行新增「本轮 token」按钮,点击弹出 `Token 明细`,分两层统计——**对话累计**(整场会话的 总 token / 总消耗 / 耗时 / 缓存命中率)与**本轮明细**(本次输出 / 本轮 token / 本轮消耗 / 本轮耗时 / 本轮缓存命中率、缓存命中长条图、按模型卡片:每个模型显示 输入·未命中 / 缓存命中 / 输出(+推理)与峰 / 谷成本分列)。时长按「X分Y秒」显示。宿主记录带会话标识(`sessionId`),本轮与整场对话统计各自独立。
41
+ - **内部/工具调用单独分组**:内部/占位调用(如 `dsh2shell-*`、模型 `fake`)不混入模型消耗明细,单独收在「消耗明细」下方可折叠的 **「工具调用(内部)」** 分组里(默认收起),成本表只统计真实模型调用。
42
+ - **移动端适配**:≤900px 时表格贴合屏宽(不再出现横向滑条)、宽表折叠中间列,弹窗统计卡与按模型卡片随视口自适应。
43
+
44
+ ---
45
+
46
+ ## 界面预览
47
+
48
+ ### 用量与消耗
49
+ ![用量与消耗](./docs/assets/usage-overview.png)
50
+
51
+ ## 从本仓库安装
52
+
53
+ 先打包,再通过 DSH 安装本地安装包:
54
+
55
+ ```bash
56
+ npm pack
57
+ dsh plugin --profile web add ./dsh-token-plugin-2.0.0.tgz
58
+ ```
59
+
60
+ 安装后重启 DSH 服务。此名称尚未发布到 npm;发布后才能直接输入包名安装。桌面端推荐列表由其维护方单独管理。
61
+
62
+ ## 这个包是什么
63
+
64
+ 一个 npm 包 = **host 半**(Node 侧 Cordis 插件,负责记录、计费、导出,见 `lib/index.js`)+ **client 半**(浏览器侧面板,见 `lib/client.js`,通过 `/usage/api` 与 host 通信)。
65
+
66
+ 包通过两处声明接入 DSH:
67
+
68
+ | 声明 | 作用 |
69
+ | --- | --- |
70
+ | `dsh.bundle.patch`(`cordis.patch.yml`) | 让 DSH 把它识别为**标准 bundle 插件包**:`dsh plugin --profile <名> add <包名>` 一条命令即可安装并自动接线,无需手改任何配置文件 |
71
+ | `dsh.client` + `exports["./client"]` | 让 web 客户端在 `/plugins/<包名>/client.js` 自动加载浏览器面板 |
72
+
73
+ 所以对使用者来说,**安装就是一条命令**,不用碰 YAML、不用手动复制文件。
74
+
75
+ ---
76
+
77
+ ## 更新和卸载
78
+
79
+ 更新时重新打包当前仓库,再使用上面的命令安装新安装包。卸载时运行 `dsh plugin --profile web remove dsh-token-plugin`,然后重启 DSH。
80
+
81
+ ## 数据与位置
82
+
83
+ > **自 v1.9.2 起**,记录存储在**固定专用数据目录**中(修复 [#4](https://github.com/feiyang-dev/dsh-usage-plugin/issues/4))。路径不再跟随会话工作区 / `~/.dsh` / 桌面端安装目录漂移,因此切换工作区时历史数据不会再"消失"(被统计为 0),且 UI 中显示的路径与真实落盘路径始终一致。
84
+
85
+ - 数据文件:`<数据根>/dsh-usage/usage-records.json`
86
+ - **数据根**的解析顺序(数据始终落在该顺序里**第一个可写**的目录,**不会放进工作区**,除非以下全部不可写):
87
+ 1. 环境变量 `DSH_USAGE_DATA_DIR`(若已设置)——优先级最高,覆盖其它;
88
+ 2. Windows:`%LOCALAPPDATA%\dsh-usage-plugin`(若 `LOCALAPPDATA` 未设置则回退到 `%APPDATA%`);
89
+ 3. 用户主目录:`~/dsh-usage-data`(Windows 为 `%USERPROFILE%\dsh-usage-data`,macOS/Linux 为 `~/dsh-usage-data`);
90
+ 4. **兜底(极少见)**:当前工作区 `<工作区>/dsh-usage` —— 仅当上述系统/用户目录都不可写时才使用,并会在面板顶部提示「持久化未启用」。
91
+ - **写入不经过模型沙箱**:持久化由插件宿主进程自身的文件系统直接完成,不受 `workspace-write` 沙箱约束,因此固定目录一定能正常写入,数据也不因工作区切换而丢失。
92
+ - Windows 下默认位置:`%LOCALAPPDATA%\dsh-usage-plugin\dsh-usage\usage-records.json`
93
+ - 旧数据自动合并:首次启动时,落在 `%USERPROFILE%\dsh-usage`、`~/.dsh/dsh-usage` 以及各工作区 `dsh-usage`(或 `.dsh-usage-records.json`)下的旧记录会按**记录身份**去重后合并进固定数据根,无需手动迁移。身份取 `recordId`(新记录),或"会话 + 时刻 + 模型 + 用途 + 四桶用量 + 结束原因"的复合指纹(旧记录)——不再只按毫秒去重,同一毫秒的两条不同请求不会互相覆盖。**旧版本写入的记录完全兼容**:没有 `recordId` / `pricingTime` / `unit` 等新字段也能正常读取。
94
+ - **写入是原子的**:先写同目录临时文件并刷盘,再原子替换目标文件;失败时原文件保持不变。
95
+ - **损坏文件不会静默清零**:若数据文件存在但无法解析,插件不会把它当成空数据(旧版本会,且随后用空数据覆盖掉整份历史),而是把原件改名保留为 `usage-records.json.corrupt-<时间戳>.json` 后继续启动,方便人工抢救。
96
+ - 价格配置(面板内编辑后保存):`<数据根>/dsh-usage/pricing.json`
97
+ - 价格表历史快照(每次改价 / 恢复默认自动追加,最多 200 条,只增不改):`<数据根>/dsh-usage/pricing-history.json`
98
+ - 只用于回看「某个时刻用的是哪套价」,**不参与任何金额计算**;也可以在 `pricing.json` 里写 `holidays: ["2027-01-01", ...]` 覆盖内置的法定节假日表。
99
+ - 导出目录(默认):`<数据根>/dsh-usage/{csv,json,images}/`
100
+ - 自定义导出目录:在面板「导出目标目录」里填写或点「选择目录…」
101
+ - 启动诊断日志(若插件激活失败):数据根旁的 `dsh-usage-boot.log`
102
+
103
+ ---
104
+
105
+ ## 统计口径声明
106
+
107
+ 本插件的用量与费用**基于 DeepSeek Harness 的 `llm/stream` 事件逐次记录**,并额外通过 `session/event` 监听搜索后端请求。请先了解它的边界,再决定如何使用这些数字。
108
+
109
+ ### 已精确统计(上游实测值)
110
+
111
+ | 来源 | 说明 |
112
+ | --- | --- |
113
+ | 主 Agent 对话 | 每次调用的四桶 token、缓存命中数与结束原因 |
114
+ | 会话标题生成 | `purpose: session-title` |
115
+ | 上下文压缩 | `purpose: compaction` |
116
+ | 进程内子 Agent | 与主 Agent 共用同一个 `ctx.llm`,同样被拦截 |
117
+
118
+ 计费公式与官方口径一致:`未命中输入 × 未命中价 + 缓存命中 × 命中价 + 输出 × 输出价`。其中「缓存写入」按未命中价计、不单列(DeepSeek 官方向来不单独上报该字段);`reasoning` 已包含在「输出」内,不另计。
119
+
120
+ ### 未纳入精确统计(面板中会单独标注)
121
+
122
+ | 来源 | 为什么统计不到 | 插件怎么处理 |
123
+ | --- | --- | --- |
124
+ | **`web_search` 的 DeepSeek 搜索后端调用** | `web-search-deepseek` 在 Harness 内部用原生 `fetch` 直连 `api.deepseek.com/anthropic/v1/messages`,刻意绕开 `ctx.llm`;而且上游只把**请求**写进会话日志(`web/deepseek-search-llm-request`),响应里的 usage **被整个丢弃**(其响应类型甚至没有声明 usage 字段)。这部分真实计费没有任何数据源可以取到。 | **精确记录调用次数**,并按请求体给出**输入侧下限**估算,全部标记为 `estimated`;输出侧不猜。概览页会显示「估算消耗 ≥ ¥X(N 次)」。 |
125
+ | 外部 CLI 子 Agent(claude-code / codex) | 起独立子进程,完全绕过 `ctx.llm`,不产生任何事件 | 不统计 |
126
+ | 请求中断时服务端已产生的输出 | 中断时 adapter 不产出 usage chunk | 记一条 0 token 的「中断调用」,使调用次数与官方口径一致 |
127
+ | 官方按 HTTP 请求计费,插件按「流」计费 | Harness 内部重试不会产生新的流 | 调用次数可能与官方 request_count 有出入 |
128
+
129
+ ### 结论
130
+
131
+ - 普通对话 / coding 场景下,插件金额与官方账单**基本一致**;
132
+ - **大量使用联网搜索(`web_search`)时,插件显示的金额会明显低于官方账单**——缺失的正是搜索后端的消耗,面板会显式提示存在估算项;
133
+ - 任何情况下,**请以 DeepSeek 官方后台账单为准**。
134
+
135
+ ### 历史价格不会被改写
136
+
137
+ 每条记录在落盘时都会**冻结当时生效的三个单价**(记录里的 `unit` 字段)。因此:
138
+
139
+ - 之后修改价格表(面板保存或直接改 `pricing.json`)**只影响之后产生的记录**,历史费用不会被重算;
140
+ - 旧版本写入或手工导入的记录没有冻结单价,其费用按**当前**价格表计算,改动价格后会跟着变化——概览页与价格页会提示这类记录的条数;
141
+ - 确实需要用新价格重算全部历史时,在「价格表」页点**「用当前价格表重算全部历史」**(或调用 API `{"action":"repriceAll"}`)。这是唯一会改写历史费用的入口。
142
+
143
+ ---
144
+
145
+ ## 常见问题
146
+
147
+ | 现象 | 原因 / 处理 |
148
+ | --- | --- |
149
+ | 面板报 `Unexpected end of JSON input` | 插件行缺少 `inject` 列表,路由未注册。按方法 B3 补上 inject 后重启 |
150
+ | 面板一直空白 / 顶部无 tab | 插件未激活。看会话工作区 `dsh-usage-boot.log`;确认 `cordis.patch.yml` 里的行存在且 `name` 正确 |
151
+ | `dsh plugin` 报 pnpm not found | 安装 pnpm:`npm install -g pnpm` |
152
+ | 安装时连不上 npm 官方源 | 配置镜像:`npm config set registry https://registry.npmmirror.com`(或对 pnpm 设 `pnpm config set registry ...`)后再执行安装命令 |
153
+ | 卸载后仍报 `Cannot find package '@feiyang666/...'` | profile 里残留了包引用。删掉 `cordis.patch.yml` 中对应行与 `dsh.profile.bundles` 里的包名,重启 |
154
+ | Desktop 0.2.x 上费用恒为 ¥0、且价格页提示"全部记录都没有冻结单价" | 1.18.0 及更早只认 `deepseek-official` / `deepseek` 两个 provider id,而桌面端官方账号的 id 是 `deepseek-account`。升级到 **1.19.0** 起按前缀识别所有 `deepseek-*`,新记录冻结单价,历史记录的费用也会一并算对(issue #19) |
155
+
156
+ ---
157
+
158
+ ## 相关项目
159
+
160
+ | 项目 | 说明 | 安装方式 |
161
+ | --- | --- | --- |
162
+ | [数据保险箱(dsh-vault)](https://github.com/feiyang-dev/dsh-vault) | 自动备份 / 清空检测 / 一键恢复,保护聊天记录与工作区数据 | 桌面端一键安装,或 `dsh plugin add @feiyang666/dsh-vault` |
163
+ | [DeepSeek-Harness](https://github.com/deepseek-ai/DeepSeek-Harness) | 官方 CLI / Web 服务 | 见下方「运行 DeepSeek Harness」 |
164
+
165
+ ### 运行 DeepSeek Harness
166
+
167
+ **快速安装(通过 npm)**
168
+
169
+ 安装 Node.js,然后运行:
170
+
171
+ ```bash
172
+ npx @deepseek-ai/dsh web
173
+ ```
174
+
175
+ 该命令会启动 Web UI,默认地址为 http://127.0.0.1:3080。详见 [Web UI 指南](https://github.com/deepseek-ai/DeepSeek-Harness)。
176
+
177
+ **从源码运行**
178
+
179
+ 如需从仓库源码运行:
180
+
181
+ ```bash
182
+ git clone https://github.com/deepseek-ai/deepseek-harness.git
183
+ cd deepseek-harness
184
+ pnpm install
185
+ pnpm run build
186
+ pnpm dsh web
187
+ ```
188
+
189
+ ## 致谢
190
+
191
+ - **[@ayleen](https://github.com/ayleen)**:为 Web 面板实现完整的英文界面(i18n 层)与响应式语言切换,余额查询改为语义化字段([#7](https://github.com/feiyang-dev/dsh-usage-plugin/pull/7))。
192
+ - **[@wuhuqif176](https://github.com/wuhuqif176)**:在「剩余余额查询」中新增百炼(Qwen)Token Plan 配额查询([#8](https://github.com/feiyang-dev/dsh-usage-plugin/pull/8))。
193
+ - **[@Martin-soaring-dev](https://github.com/Martin-soaring-dev)**:筹备了插件开源贡献(打包、插件契约校验、文档与 CI),并提交贡献分支,成为后续开源版本的基础([#6](https://github.com/feiyang-dev/dsh-usage-plugin/pull/6))。
194
+ - **[@mumuer1024](https://github.com/mumuer1024)**:报告并定位了持久化路径随会话工作区漂移导致历史数据"消失/统计为 0"的问题,提出把数据写到固定专用目录的方案([#4](https://github.com/feiyang-dev/dsh-usage-plugin/issues/4));并报告了 DeepSeek 搜索后端调用未计入统计导致费用被严重低估的问题,给出了可在官方账单上交叉验证的完整取证([#12](https://github.com/feiyang-dev/dsh-usage-plugin/issues/12))。
195
+ - **[@liu3734](https://github.com/liu3734)**:报告并定位 macOS(POSIX)下路径处理与 spawn 的 Windows 专用问题,提出跨平台修复方案([#1](https://github.com/feiyang-dev/dsh-usage-plugin/issues/1))。
196
+ - **[@zhiqiangme](https://github.com/zhiqiangme)**:提交了历史用量回填与侧边栏余额的大型 PR([#14](https://github.com/feiyang-dev/dsh-usage-plugin/pull/14))。其中**「`cacheWriteTokens` 兜底导致四桶双计」、「同毫秒记录按 `time` 去重会互相覆盖」、「用量文件非原子写入且解析失败被静默清零」三项定位**已成为 v1.18.0 计费口径修复的核心依据(详见 [CHANGELOG](./CHANGELOG.md))。
197
+ - **[@vb2250158](https://github.com/vb2250158)**:提交 PR [#18](https://github.com/feiyang-dev/dsh-usage-plugin/pull/18),定位并修复趋势图 X 轴日期在窄图下重叠的问题,给出「按 canvas 实测文本宽度排布 + 首尾日期优先」的完整方案与回归用例,已并入 v1.24.0。
198
+
199
+ ## 更新日志
200
+
201
+ - [CHANGELOG.md](./CHANGELOG.md) —— 每次发布的详细更新内容。
202
+
203
+ ## 许可
204
+
205
+ MIT © dsh-usage-plugin
@@ -0,0 +1,23 @@
1
+ # dsh-usage-plugin — bundle patch layer.
2
+ #
3
+ # This file is the package's `dsh.bundle.patch` layer. When the package is
4
+ # installed into a DSH profile (via `dsh plugin --profile <name> add
5
+ # dsh-token-plugin`, or manually), the loader applies this
6
+ # patch over the profile's empty root entry list, mounting the plugin row.
7
+ #
8
+ # The `inject` list is REQUIRED: it tells the Cordis loader to wait until the
9
+ # fs / webServer / subprocess / credentials / settings / sandboxPolicy / agents
10
+ # services are ready before activating the plugin. Without it the plugin's `apply`
11
+ # may run before those services exist, the `/usage/api` route never registers,
12
+ # and the client panel fails with `Unexpected end of JSON input`.
13
+ - insert:
14
+ - id: usage-plugin
15
+ name: 'dsh-token-plugin'
16
+ inject:
17
+ - fs
18
+ - webServer
19
+ - subprocess
20
+ - credentials
21
+ - settings
22
+ - sandboxPolicy
23
+ - agents