@rayadesu/dsh-billing 0.3.15 → 0.3.17

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/AGENTS.md CHANGED
@@ -18,13 +18,17 @@ cordis.patch.yml DSH profile bundle 补丁层:挂载 llm-billing + ui-
18
18
  ## DSH 集成方式
19
19
 
20
20
  - **bundle(推荐)**:根 `package.json` 声明 `dsh.bundle.patch`,`cordis.patch.yml`
21
- 挂载两个插件行。三个包已发布到 npm(`@rayadesu` scope),pnpm 不会把 bundle 的
22
- 本地依赖装进 profile,所以一条命令同时安装 bundle 与两个包(让行名能从 profile 的
23
- node_modules 解析):
21
+ 挂载两个插件行。三个包已发布到 npm(`@rayadesu` scope)。bundle 把两个插件包声明为
22
+ 普通 `dependencies`(官方组合包同款;profile 初始化为 `nodeLinker: hoisted` +
23
+ `autoInstallPeers: false`——peer 不会进 profile,组件包必须是 dependencies 才会随
24
+ bundle 装入并 hoist 到 profile 根、让行名从 node_modules 解析),所以单个包名即可装全:
24
25
 
25
26
  ```sh
26
- dsh plugin --profile web add @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
27
+ dsh plugin --profile web add @rayadesu/dsh-billing
27
28
  ```
29
+
30
+ Web 官方安装方式用同一个包名:侧栏 插件 → 添加插件 → 输入 `@rayadesu/dsh-billing`
31
+ (对话框也接受 GitHub 仓库地址或本地目录绝对路径;安装源可选默认源或中国大陆镜像源)。
28
32
  - **手动**:把 `cordis.patch.yml` 的 insert 合并进 `$DSH_HOME/profiles/<name>/cordis.patch.yml`,
29
33
  并用 `dsh plugin --profile <name> add @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing`
30
34
  安装两个包(行名解析同上)。
@@ -63,10 +67,15 @@ cordis.patch.yml DSH profile bundle 补丁层:挂载 llm-billing + ui-
63
67
  - **workspace 关掉 pnpm 发布龄门槛**:`pnpm-workspace.yaml` 显式 `minimumReleaseAge: 0`。
64
68
  pnpm ≥11 默认 1 天门槛会把刚发布的 alpha 包挡在 lockfile 校验外,而校验阶段不认
65
69
  `minimumReleaseAgeExclude`(那是解析期自动追加的),本仓库又要紧跟 DSH alpha 基线。
70
+ - **DSH 依赖线升级 checklist**:`@deepseek-ai/dsh-*` 全线对齐同一基线(`llm-billing` 的 peer+dev、`ui-billing`
71
+ 的 peer+dev、根的 devDependencies 三处一起改)。升级按序做:① 改三处版本号 → ② `pnpm install` 刷
72
+ lockfile(本仓库已关发布龄门槛)→ ③ `pnpm run build`(顺带确认 typert generator 仍产出 `create` 工厂,
73
+ 没产出时 `scripts/typert-compat.mjs` 会非零退出)→ ④ `pnpm run test`;⑤ 若测试报 `Cannot find package`,
74
+ 按上一条「补漏声明依赖」补进 `ui-billing` 的 devDependencies。
66
75
  - **密钥不进仓库**:`DEEPSEEK_API_KEY` 等一律由用户环境或凭据 seam 提供,仓库不含真实值。
67
76
  - **README 双语**:每个 README 遵循 DSH 结构 `README.md`(EN) + `README.zh.md`(ZH) +
68
77
  `README.i18n.yaml`(记录两文件 git blob hash,改动后需更新)。
69
- - **版本对齐**:根 bundle 与两个包统一版本号(当前 0.3.15),`pnpm-lock.yaml` 随依赖变更更新。
78
+ - **版本对齐**:根 bundle 与两个包统一版本号(当前 0.3.17),`pnpm-lock.yaml` 随依赖变更更新。
70
79
  - **提交与发布流程**:见 `.agents/skills/dsh-release/SKILL.md` —— 阶段 A(改代码 → 按档位校验/打包 →
71
80
  本地 pack 安装 → 交用户验证)**不提交**,改动留在工作区;用户说「发布」进入阶段 B 才 bump 版本、
72
81
  **按类型分别提交**、推送、发 npm 与 GitHub Release。
@@ -83,7 +92,7 @@ pnpm install # 安装本仓库依赖(dsh-* 从 registry 解析)
83
92
  pnpm run build # host + client 两个编译面(tsc + tsdown + typert 产物)
84
93
  pnpm run test # vitest
85
94
  pnpm run verify # 发布前校验
86
- dsh plugin --profile web add @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing # 安装进 DSH
95
+ dsh plugin --profile web add @rayadesu/dsh-billing # 安装进 DSH(bundle 依赖带齐两个插件包)
87
96
  ```
88
97
 
89
98
  **从零构建顺序是硬约束**:`ui-billing` 的浏览器半面(`tsconfig.client.json`)导入
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # last confirmed-consistent state. Both languages carry equal authority; after
3
3
  # editing either side, bring the other along and re-record both hashes with:
4
4
  # git hash-object README.md README.zh.md
5
- README.md: 2ccd0964f45caa63e76356b579ecfb9a53cf8865
6
- README.zh.md: 77b895e2c2f089959e27641c58a51594f63a2d5a
5
+ README.md: e210787fb9d9052bc77365a32141afb463f1d421
6
+ README.zh.md: 98f5964507235528c20f6d9169870bf1194f714c
package/README.md CHANGED
@@ -15,8 +15,9 @@ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin tha
15
15
 
16
16
  ## Data update mechanics
17
17
 
18
- - **Session spend follows the conversation** — the host prices every committed event into a per-session projection (`billingTodaySpend`) and pushes it to the browser, so **this session's own spend** updates live with no Remote call; the Remote read remains the fallback when the projection registry is absent, and `billing/getDelegatedSpend` adds the subagent sessions this session delegated (fetched on mount, on refresh, and when a turn settles) so the amount shown is the whole conversation's. **Today's spend** is recomputed on turn settle (one shared scan serves the aggregate, the ranking, and the delegated subtotal), and the parenthesized today share on that same session line reads that same ranking fetch (no extra request, at the cost of moving with it); each turn's cost comes from **one batch fetch per session** instead of one call per rendered message.
19
- - **Balance is cached and polled** — the host reuses one `/user/balance` snapshot for 15 seconds (manual refresh forces a fresh one) and caps each request at 5 seconds; the browser keeps the last settled value so a session switch renders the amount immediately and revalidates in the background, and polls every 5 minutes while the page is visible (paused while the document is hidden, refreshed once on return) — the balance-series consumption below is measured from the first balance each local day samples, so that cadence is its resolution.
18
+ - **Session spend follows the conversation** — the host prices every committed event into a per-session projection (`billingTodaySpend`) and pushes it to the browser, so **this session's own spend** updates live with no Remote call; the Remote read remains the fallback when the projection registry is absent, and `billing/getDelegatedSpend` adds the subagent sessions this session delegated (fetched on mount, on a session switch, on refresh, and when a turn settles) so the amount shown is the whole conversation's. **Today's spend** is recomputed on turn settle (one shared scan serves the aggregate, the ranking, and the delegated subtotal), and the parenthesized today share on that same session line reads that same ranking fetch (no extra request, at the cost of moving with it); each turn's cost comes from **one batch fetch per session** instead of one call per rendered message.
19
+ - **A session switch re-reads only the session's own lines** — the balance and today's spend do not vary by session, so they are not re-fetched when you switch; only `billing/getSessionSpend` and `billing/getDelegatedSpend` are. A **settled turn and the manual refresh ask for a fresh day figure** (`force`), so the day row never reports a turn's cost a turn late; **opening the detail panel** re-reads the day row too, so browsing sessions while idle cannot leave it behind. A plain read of the day cache is answered from the last value at once, with the scan running behind it — so nothing the user did not just cause ever waits on the day's all-session scan.
20
+ - **Balance is cached and polled** — the host reuses one `/user/balance` snapshot for 15 seconds (manual refresh forces a fresh one) and caps each request at 5 seconds; the browser keeps the last settled value so a session switch renders the amount immediately, and polls every 5 minutes while the page is visible (paused while the document is hidden, refreshed once on return) — the balance-series consumption below is measured from the first balance each local day samples, so that cadence is its resolution.
20
21
  - **Old values survive refreshes** — a failed refresh keeps the last good value instead of blanking it.
21
22
 
22
23
  ## Preview
@@ -41,6 +42,15 @@ Close-up of the turn-cost amount — the static `¥` amount at the end of the ac
41
42
  | [`packages/llm-billing`](packages/llm-billing) — `@rayadesu/dsh-llm-billing` | Host | Owns the `/user/balance` transport and the peak/off-peak pricing table. Exposes the `billing` Remote (`getBalance(force?)`, `getSessionSpend`, `getTodaySpend`, `getTodaySessionsSpend`, `getTurnSpend`, `getSessionTurnSpends`) and registers the client-visible `billingTodaySpend` projection unit. |
42
43
  | [`packages/ui-billing`](packages/ui-billing) — `@rayadesu/dsh-client-ui-billing` | Browser | Mounts the `billing` Remote itself and contributes the session-header badge and detail panel, plus the static turn-cost amount at the end of the message actions strip. |
43
44
 
45
+ ### Plugin manager display metadata
46
+
47
+ The sidebar's **Plugins** page and the **Settings → Plugins** list show each entry's title, description, and icon. Both come from files inside the package itself — the host reads no plugin code:
48
+
49
+ - `locale/en.json` and `locale/zh.json` — `{"meta": {"title": …, "description": …}}`, resolved against the active interface language and falling back to English.
50
+ - `icon.svg` — a self-contained SVG (no external font or image references; it renders through an `<img>` data URL) declared as the package's top-level `icon` field.
51
+
52
+ Both are reached through the package's `exports` map, so a package that declares `exports` must also export `./locale/*.json`: without it the locale files are silently dropped and the entry falls back to its bare package name. Each of the three packages carries its own pair of locale files and its own icon.
53
+
44
54
  ## Prerequisites
45
55
 
46
56
  - **DeepSeek Harness** (`dsh`) — the plugin runs inside a dsh profile.
@@ -48,19 +58,29 @@ Close-up of the turn-cost amount — the static `¥` amount at the end of the ac
48
58
 
49
59
  ## Installation
50
60
 
51
- ### Install (published to npm)
61
+ ### Install from the Web plugin page (official)
52
62
 
53
- The three packages are published to npm under the `@rayadesu` scope. Install the
54
- bundle plus the two plugin packages in one command — the bundle declares the two
55
- plugin packages as peer dependencies, which pnpm does not auto-install into the
56
- profile, so they must be named explicitly.
63
+ The three packages are published to npm under the `@rayadesu` scope. The bundle
64
+ declares the two plugin packages as its regular `dependencies`, so **one package
65
+ name installs everything**: pnpm pulls the bundle's dependency closure into the
66
+ profile, where the hoisted `node_modules` makes the two row names resolvable.
67
+
68
+ 1. In the sidebar open **Plugins** → **Add plugin**.
69
+ 2. Enter `@rayadesu/dsh-billing`. The dialog also accepts the GitHub repository
70
+ address (`https://github.com/rayadesune/DeepSeek-Harness-chat-billing`) or an
71
+ absolute local directory path — the two plugin packages themselves always
72
+ come from npm.
73
+ 3. Pick an install source (the default npm registry or the **Mainland China
74
+ mirror**), press **Install**, then **Enable now**.
75
+
76
+ ### Install with the CLI
57
77
 
58
78
  The `dsh` command you use depends on how dsh is installed:
59
79
 
60
80
  - **Global install** — use the global `dsh` from anywhere:
61
81
 
62
82
  ```bash
63
- dsh plugin --profile web add @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
83
+ dsh plugin --profile web add @rayadesu/dsh-billing
64
84
  ```
65
85
 
66
86
  - **Source-built dsh** (a deepseek-harness checkout) — the CLI only resolves from
@@ -69,7 +89,7 @@ The `dsh` command you use depends on how dsh is installed:
69
89
 
70
90
  ```bash
71
91
  cd deepseek-harness
72
- pnpm dsh plugin --profile web add @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
92
+ pnpm dsh plugin --profile web add @rayadesu/dsh-billing
73
93
  ```
74
94
 
75
95
  ### pnpm 11 release-age gate
@@ -86,9 +106,11 @@ latest version right after a publish:
86
106
  minimumReleaseAge: 0
87
107
  ```
88
108
 
89
- - Or, within the 24-hour window, install with an explicitly pinned version (an
90
- explicit pin bypasses the age gate; replace `0.3.0` with the version you want;
91
- from a source checkout, use `pnpm dsh …` as above):
109
+ - Or, within the 24-hour window, install with explicitly pinned versions (an
110
+ explicit pin bypasses the age gate; pin all three names — the bundle's two
111
+ plugin packages install transitively and need their own pin to pass the gate;
112
+ replace `0.3.0` with the version you want; from a source checkout, use
113
+ `pnpm dsh …` as above):
92
114
 
93
115
  ```bash
94
116
  dsh plugin --profile web add @rayadesu/dsh-billing@0.3.0 @rayadesu/dsh-llm-billing@0.3.0 @rayadesu/dsh-client-ui-billing@0.3.0
@@ -112,12 +134,17 @@ deepseek-harness checkout instead — the subcommands are identical.
112
134
 
113
135
  ```sh
114
136
  dsh plugin --profile web list # list the web profile's installed plugins
115
- dsh plugin --profile web add @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
116
- dsh plugin --profile web remove @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
137
+ dsh plugin --profile web add @rayadesu/dsh-billing
138
+ dsh plugin --profile web remove @rayadesu/dsh-billing
117
139
  dsh plugin --profile web update # update plugins to the latest allowed versions
118
140
  dsh plugin --profile web update --latest # ignore declared ranges; upgrade every plugin to its newest published version
119
141
  ```
120
142
 
143
+ Both commands take the bundle alone — its two plugin packages travel with it as
144
+ dependencies. A profile installed by the older three-package command lists all
145
+ three in its `package.json`, and pnpm only removes names listed there: on such
146
+ a profile, give `remove` all three names to clear the leftovers.
147
+
121
148
  `update` respects the version ranges in the profile's `package.json`, so it
122
149
  stays within the semver range each plugin declares. Adding `--latest` (a pnpm
123
150
  `update` flag) instead ignores those ranges and upgrades every plugin to its
@@ -255,7 +282,7 @@ Both packages ship sane defaults; everything below is optional.
255
282
  | --- | --- | --- |
256
283
  | `apiKeyEnv` | `DEEPSEEK_API_KEY` | Credential-reference (environment-variable) name resolved per call. |
257
284
  | `baseURL` | `$DEEPSEEK_BASE_URL` then `https://api.deepseek.com` | Endpoint base; `/user/balance` is appended. |
258
- | `models` | V4.1 Flash (`deepseek-flash`) + V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5 series | Advisory display rows, in presentation order; they mirror DSH's `llm-deepseek` catalog. |
285
+ | `models` | V4.1 Flash (`deepseek-flash`) + V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5/V2.6 series | Advisory display rows, in presentation order; they mirror DSH's `llm-deepseek` catalog. |
259
286
  | `billing.peakHours` | 09:00–12:00, 14:00–18:00 (Beijing, weekdays) | Peak-hour windows, applied weekdays (Mon–Fri) only; weekends and all other hours are off-peak. |
260
287
  | `billing.models` | Published V4 + MiMo rates | Per-model price rows (`cacheHitInput`, `cacheMissInput`, `output`, in CNY per 1M tokens) with an optional inclusive `effectiveFrom`; several rows sharing a model are its rate revisions. |
261
288
 
@@ -266,7 +293,7 @@ Both packages ship sane defaults; everything below is optional.
266
293
  - **Today's spend** aggregates every session's events on the current Beijing-time calendar day with the same pricing rules; **today's tokens** is the sum of those same priced rows' three billing buckets (cache-hit input / cache-miss input / output), rendered in DSH's compact notation with its ` tok` unit (`517 tok`, `12.2K tok`, `1.2M tok`); event dates are also assigned in Beijing time.
267
294
  - **Turn cost** prices the events inside the turn's `turn/start`..`turn/end` range with the same rules (located by the closing message's session id + message id), folded in one pass for the whole session and served as a `messageId → cost` map.
268
295
  - **Today session ranking** aggregates today's spend per session with the same rules (a cross-day session counts only today's part), sorted descending; names come from the log's latest `session/title` event (the auto-generated Chinese title or a user rename). It ranks **conversations**: a subagent session (DSH stamps its header with `origin: 'subagent'` and a `delegationDepth`; a user fork carries neither) is merged into the row of the top-level session that delegated it, so one row is one conversation. Each row reports `total` (the conversation's whole day, subagents included) plus `ownTotal` (that session's own spend) — the panel's trailing share reads `ownTotal`, and the day's aggregate total is unchanged by the regrouping.
269
- - Models without a rate row are not priced (the built-in table covers the DSH `llm-deepseek` catalog — V4.1 Flash `deepseek-flash`, V4 Flash, V4 Pro, V4 Flash Vision Exp — plus the retired `deepseek-v4.1-flash-expires-on-0910` preview id and the MiMo-V2.5 series; every flash-series route shares the same pair). Each sample takes the rate revision in effect at its own timestamp: the base schedule is the DeepSeek pricing effective **August 17**; the **flash series** (V4.1 Flash, V4 Flash, V4 Flash Vision Exp, and the retired id) was re-priced effective **September 10, 12:00 Beijing time** to off-peak 0.02 / 1.0 / 4.0 CNY per 1M tokens with peak at twice those prices — samples from before that instant, the V4.1 Flash route's own earlier usage included, keep the superseded rates; the **V4 Pro** route is announced to switch to V4.1 Flash and its rates on **September 14, 12:00 Beijing time**; the MiMo-V2.5 series is untouched. The weekend-off-peak rule (weekends billed at off-peak prices all day) follows the adjustment effective **August 23**.
296
+ - An unlisted model is NEVER priced by inference: no rate row means no price. The usage is recorded as `unpriced` instead and warned about once per model, so a new upstream model shows ¥0 WITH the model named as the reason rather than as an ordinary empty day — which is exactly how MiMo-V2.6 looked before its rows existed. Model ids are chosen by whoever ships the model, so a similar-looking name is no basis for a price; add one row under `billing.models` instead. The built-in table covers the DSH `llm-deepseek` catalog — V4.1 Flash `deepseek-flash`, V4 Flash, V4 Pro, V4 Flash Vision Exp — plus the retired `deepseek-v4.1-flash-expires-on-0910` preview id and the MiMo-V2.5/V2.6 series; every flash-series route shares the same pair). Each sample takes the rate revision in effect at its own timestamp: the base schedule is the DeepSeek pricing effective **August 17**; the **flash series** (V4.1 Flash, V4 Flash, V4 Flash Vision Exp, and the retired id) was re-priced effective **September 10, 12:00 Beijing time** to off-peak 0.02 / 1.0 / 4.0 CNY per 1M tokens with peak at twice those prices — samples from before that instant, the V4.1 Flash route's own earlier usage included, keep the superseded rates; the **V4 Pro** route is announced to switch to V4.1 Flash and its rates on **September 14, 12:00 Beijing time**; the MiMo series is untouched (MiMo-V2.6 — launched September 22, 2026 — kept V2.5's published rates and ships the same rows). The weekend-off-peak rule (weekends billed at off-peak prices all day) follows the adjustment effective **August 23**.
270
297
 
271
298
  - **The balance-series "today's consumption"** (the figure right after `API 剩余金额`) is pure account arithmetic and never enters the token pricing above: the first balance queried on the local calendar day − the current balance + the day's detected top-ups (an increase rounds **up to the next ¥10 step**, because the provider only tops up in round tens). It is the caliber of the `balanceinfo` program this plugin mirrors, and it is deliberately a separate figure from the priced 今日花费 — spend from another client, or from before this browser was opened, shows up only here; the two figures disagreeing is normal. The day record lives in this browser's `localStorage` (key `dsh.billing.balance-day.v1`), survives reloads and `dsh` restarts, rolls over on the **local** calendar day (the browser's day, not the host's Beijing day key), is sampled on mount, on session switch, on the manual refresh, and every 5 minutes while the page is visible, renders only once the day holds a sample, and is not shared between clients. Details in [`packages/ui-billing/README.md`](packages/ui-billing/README.md).
272
299
 
@@ -275,7 +302,7 @@ Both packages ship sane defaults; everything below is optional.
275
302
  - **Priced rows only** — the session, turn, and today spends only price models that have a `billing.models` row (today's token count reads those same rows, so it covers priced models only).
276
303
  - **On-demand aggregation** — today's spend and the session ranking are computed on the host behind a 60-second cache and share ONE scan; a miss resolves live sessions from their eager projection cells and cold sessions from the zero-I/O projection-cache row when that row's own day is not the queried one, reading a log only for sessions whose persisted revision changed (or whose cached row covers the queried day). A failed resolution is remembered by revision instead of being retried every scan. The ranking's subagent roll-up regroups those rows in the same pass, with no extra read, and the aggregate still sums every session — so merging never moves money. The ranking is also fetched only on demand — see the next bullet — and the trailing today share on the 本会话花费 row rides that same fetch, so it costs no extra request ((—) until the first one settles, and up to 60 seconds behind afterwards).
277
304
  - **Ranking is fetched on demand** — the panel loads the ranking when it is opened (and on refresh), so a badge that stays closed never pays for the all-session scan. The trailing today share on the 本会话花费 row rides that same fetch (reading the row's `ownTotal`, so a merged subagent total never poses as this session's own), so it costs no extra request (nothing shows until the ranking settles, and it can be up to 60 seconds behind afterwards).
278
- - **The delegated subtotal moves with the turn, not per event** — the session line's own part is live (pushed projection), while the subagent part comes from `billing/getDelegatedSpend`, refreshed on mount, on manual refresh, and when a turn settles, and served from the same 60-second host cache as today's spend. A subagent that burns money mid-turn therefore lands on the parent's amount at the next read (turn settle, refresh, or a panel open), not per event.
305
+ - **The delegated subtotal moves with the turn, not per event** — the session line's own part is live (pushed projection), while the subagent part comes from `billing/getDelegatedSpend`, refreshed on mount, on a session switch, on manual refresh, and when a turn settles, and served from the same 60-second host cache as today's spend. A subagent that burns money mid-turn therefore lands on the parent's amount at the next read (turn settle, refresh, or a panel open), not per event.
279
306
  - **A merged row can be untitled** — a subagent whose parent session is outside the scan (a deleted or archived parent log, for instance) is still attributed to the parent id its own header names, but that log is never read, so the merged row shows the untitled fallback until the parent is scanned.
280
307
  - **Ranking capped at 10** — the panel shows at most the top 10 conversations (the host's subagent roll-up runs first, so one row is one conversation), with a "…N more sessions" hint.
281
308
  - **Turn cost needs a finalized closing message** — interrupted turns have no actions row, so no turn cost; cold sessions served straight from the projection cache may rank with an "Untitled" name until their log is read again.
package/README.zh.md CHANGED
@@ -15,8 +15,9 @@
15
15
 
16
16
  ## 数据更新机制
17
17
 
18
- - **会话花费自动跟随** —— 主机端把每条已提交事件计价进每会话投影(`billingTodaySpend`)并推送给浏览器,**本会话自身的花费**因此零 Remote 调用、实时更新;投影注册表不存在时回退到 Remote 读取,而 `billing/getDelegatedSpend` 再把本会话委派的子代理会话加上去(挂载时、手动刷新时与回合结束时各拉一次),所以显示的是整次对话的金额。**今日花费**(与同一行的**今日 Token**)在回合结束时重算(聚合、排行与子代理小计共用同一次扫描),本会话花费行紧跟的今日份金额也来自这次排行读取(不额外发请求,代价是与排行同进同退);每条消息的行尾金额来自**每会话一次批量拉取**,不再逐条消息各发一次请求。
19
- - **额度有缓存、可见时每 5 分钟轮询** —— 主机端 15 秒内复用同一份 `/user/balance` 快照(手动刷新强制取新),单次请求 5 秒超时;浏览器保留最后一次结果,切会话时立即渲染旧值并在后台校验。此外**页面可见时每 5 分钟轮询一次**(页面隐藏则暂停,回到前台立刻补一次)——下面那条「余额口径的今日消费」以当天第一次采样到的余额为基准,这个节奏就是它的分辨率。
18
+ - **会话花费自动跟随** —— 主机端把每条已提交事件计价进每会话投影(`billingTodaySpend`)并推送给浏览器,**本会话自身的花费**因此零 Remote 调用、实时更新;投影注册表不存在时回退到 Remote 读取,而 `billing/getDelegatedSpend` 再把本会话委派的子代理会话加上去(挂载时、切换会话时、手动刷新时与回合结束时各拉一次),所以显示的是整次对话的金额。**今日花费**(与同一行的**今日 Token**)在回合结束时重算(聚合、排行与子代理小计共用同一次扫描),本会话花费行紧跟的今日份金额也来自这次排行读取(不额外发请求,代价是与排行同进同退);每条消息的行尾金额来自**每会话一次批量拉取**,不再逐条消息各发一次请求。
19
+ - **切会话只重取会话自己的两条线** —— 余额与今日花费都不随「切到哪个会话」变化,所以切换时不再重取,只有 `billing/getSessionSpend` 与 `billing/getDelegatedSpend` 会重发。**回合结束与手动刷新会带 `force` 要一个新值**,所以今日行不会把上一轮的花费拖到下一轮才显示;**打开详情面板**也会补读今日行,因此闲置时来回切会话不会把它落下。不带 `force` 的读取则先拿到手上的值、重扫在背后跑——凡不是用户刚造成的读取,都不会等当天那次全会话扫描。
20
+ - **额度有缓存、可见时每 5 分钟轮询** —— 主机端 15 秒内复用同一份 `/user/balance` 快照(手动刷新强制取新),单次请求 5 秒超时;浏览器保留最后一次结果,切会话时立即渲染旧值。此外**页面可见时每 5 分钟轮询一次**(页面隐藏则暂停,回到前台立刻补一次)——下面那条「余额口径的今日消费」以当天第一次采样到的余额为基准,这个节奏就是它的分辨率。
20
21
  - **刷新期间旧值保留** —— 刷新失败保留上一次有效值,不会清空。
21
22
 
22
23
  ## 显示样式
@@ -41,6 +42,15 @@
41
42
  | [`packages/llm-billing`](packages/llm-billing) —— `@rayadesu/dsh-llm-billing` | 主机端 | 负责 `/user/balance` 传输与峰/谷计价表。对外暴露 `billing` Remote(`getBalance(force?)`、`getSessionSpend`、`getTodaySpend`、`getTodaySessionsSpend`、`getTurnSpend`、`getSessionTurnSpends`),并注册客户端可见的 `billingTodaySpend` 投影单元。 |
42
43
  | [`packages/ui-billing`](packages/ui-billing) —— `@rayadesu/dsh-client-ui-billing` | 浏览器端 | 自己挂载 `billing` Remote,并贡献会话头部徽标与详情面板、消息操作行行尾的静态本轮花费金额。 |
43
44
 
45
+ ### 插件管理页的展示元数据
46
+
47
+ 侧栏「插件」页与 设置 → 插件 清单里,每一项的标题、描述与图标都取自**包自身**的文件——宿主不读插件代码:
48
+
49
+ - `locale/en.json` 与 `locale/zh.json` —— 形状 `{"meta": {"title": …, "description": …}}`,按当前界面语言解析、回落英文。
50
+ - `icon.svg` —— 自包含 SVG(不引用任何外部字体或图片;它经 `<img>` 以 data URL 渲染),由 `package.json` 顶层的 `icon` 字段声明。
51
+
52
+ 两者都经包的 `exports` 对外暴露,所以声明了 `exports` 的包必须同时导出 `./locale/*.json`:否则 locale 文件会被静默丢弃,条目回落到裸包名。三个包各自带一套 locale 与自己的图标。
53
+
44
54
  ## 前置条件
45
55
 
46
56
  - **DeepSeek Harness**(`dsh`)—— 插件运行在 dsh profile 内。
@@ -48,17 +58,26 @@
48
58
 
49
59
  ## 安装
50
60
 
51
- ### 安装(已发布到 npm,一条命令)
61
+ ### 从 Web 插件页安装(官方)
52
62
 
53
- 三个包已发布到 npm 的 `@rayadesu` scope。一条命令同时安装 bundle 与两个插件包
54
- (bundle 把两个插件包声明为 peer 依赖,而 profile 默认不自动安装 peer,所以要显式列出)。
63
+ 三个包已发布到 npm 的 `@rayadesu` scope。bundle 把两个插件包声明为普通
64
+ `dependencies`,所以**只需一个包名即可装全**:pnpm 会把 bundle 的依赖闭包一并装进
65
+ profile,hoisted 的 `node_modules` 让两个组件行名可解析。
66
+
67
+ 1. 侧栏进入 **插件** → **添加插件**。
68
+ 2. 输入 `@rayadesu/dsh-billing`。对话框也接受 GitHub 仓库地址
69
+ (`https://github.com/rayadesune/DeepSeek-Harness-chat-billing`)或本地目录绝对路径
70
+ ——两个插件包本身始终从 npm 解析。
71
+ 3. 选安装源(默认 npm 源或**中国大陆镜像源**),点**安装**,完成后**立即启用**。
72
+
73
+ ### 用命令行安装
55
74
 
56
75
  用哪个 `dsh` 命令取决于你的 dsh 安装方式:
57
76
 
58
77
  - **全局安装** —— 任意目录直接用全局 `dsh`:
59
78
 
60
79
  ```bash
61
- dsh plugin --profile web add @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
80
+ dsh plugin --profile web add @rayadesu/dsh-billing
62
81
  ```
63
82
 
64
83
  - **源码构建的 dsh**(deepseek-harness 源码目录)—— CLI 只在源码目录里能解析,
@@ -66,7 +85,7 @@
66
85
 
67
86
  ```bash
68
87
  cd deepseek-harness
69
- pnpm dsh plugin --profile web add @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
88
+ pnpm dsh plugin --profile web add @rayadesu/dsh-billing
70
89
  ```
71
90
 
72
91
  ### pnpm 11 发布龄门槛
@@ -81,7 +100,8 @@ dsh profile 通过 pnpm 安装插件,而 pnpm 11 的供应链发布龄门槛
81
100
  minimumReleaseAge: 0
82
101
  ```
83
102
 
84
- - 或者在 24 小时窗口内用**显式钉版本**安装(显式钉版本可绕开门槛,把 `0.3.0` 换成你要的版本;
103
+ - 或者在 24 小时窗口内用**显式钉版本**安装(显式钉版本可绕开门槛;三个包名都要钉——两个
104
+ 插件包随 bundle 传递安装,也要各自钉住才能过门槛;把 `0.3.0` 换成你要的版本;
85
105
  源码构建的 dsh 用 `pnpm dsh …`,同上):
86
106
 
87
107
  ```bash
@@ -106,12 +126,16 @@ dsh profile 通过 pnpm 安装插件,而 pnpm 11 的供应链发布龄门槛
106
126
 
107
127
  ```sh
108
128
  dsh plugin --profile web list # 列出 web profile 已安装的插件
109
- dsh plugin --profile web add @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
110
- dsh plugin --profile web remove @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
129
+ dsh plugin --profile web add @rayadesu/dsh-billing
130
+ dsh plugin --profile web remove @rayadesu/dsh-billing
111
131
  dsh plugin --profile web update # 把插件更新到当前允许的最新版本
112
132
  dsh plugin --profile web update --latest # 忽略声明的版本区间,把所有插件升到最新发布版本
113
133
  ```
114
134
 
135
+ `add`/`remove` 只需 bundle 一个包名——两个插件包作为它的依赖随行安装。按旧版三包方式
136
+ 安装过的 profile 其 `package.json` 里列了全部三个包,而 pnpm 只能 remove 其中列出的
137
+ 依赖:那种 profile 在 `remove` 后把三个包名一并列出即可清掉残留。
138
+
115
139
  `update` 遵循 profile `package.json` 里的版本区间,只在该插件声明的 semver 范围内升级。
116
140
  加上 `--latest`(pnpm `update` 的选项)则忽略这些区间,把所有插件直接升到最新发布的版本——
117
141
  用于在版本可解析后立刻拿到新发布。源码构建的 dsh 要在 deepseek-harness 目录里用
@@ -225,7 +249,7 @@ npm publish # @rayadesu/dsh-billing bundle(仓库根)
225
249
  | --- | --- | --- |
226
250
  | `apiKeyEnv` | `DEEPSEEK_API_KEY` | 每次调用时解析的凭据引用(环境变量)名。 |
227
251
  | `baseURL` | `$DEEPSEEK_BASE_URL`,其次 `https://api.deepseek.com` | 端点基础地址;会追加 `/user/balance`。 |
228
- | `models` | V4.1 Flash(`deepseek-flash`)+ V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5 系列 | 展示用的模型行,按展示顺序;与 DSH `llm-deepseek` 目录对齐。 |
252
+ | `models` | V4.1 Flash(`deepseek-flash`)+ V4 Flash + V4 Pro + V4 Flash Vision Exp + MiMo-V2.5/V2.6 系列 | 展示用的模型行,按展示顺序;与 DSH `llm-deepseek` 目录对齐。 |
229
253
  | `billing.peakHours` | 09:00–12:00、14:00–18:00(北京,仅工作日) | 高峰时段窗口,仅周一至周五适用;周末与其余时段均为低谷。 |
230
254
  | `billing.models` | 官方 V4 + MiMo 费率 | 每个模型的单价行(`cacheHitInput`、`cacheMissInput`、`output`,单位:元/百万 token),可带生效时刻 `effectiveFrom`(含该时刻);同一模型的多行即其费率版本。 |
231
255
 
@@ -236,7 +260,7 @@ npm publish # @rayadesu/dsh-billing bundle(仓库根)
236
260
  - **今日花费**按同一个计价规则汇总当天(北京时间自然日)所有会话的事件,**今日 Token** 是同一批计价行的三个计费桶(缓存读取 / 未缓存输入 / 输出)之和,按 DSH 的紧凑记数法加 ` tok` 单位渲染(`517 tok`、`12.2K tok`、`1.2M tok`);事件归属的日期同样按北京时间计算。
237
261
  - **本轮花费**按同一规则计价该回合 `turn/start`..`turn/end` 区间内的事件(定位到收尾消息的会话 id + 消息 id),整会话一趟折出 `messageId → 金额` 映射后下发。
238
262
  - **今日会话花费排行**按同一规则按会话汇总今日花费(跨天会话只统计今天的部分),从高到低排序;会话名取日志中最后一条 `session/title` 事件(自动生成的中文标题或用户重命名的新标题)。排行排的是**对话**:子代理会话(DSH 在其 header 上盖 `origin: 'subagent'` 与 `delegationDepth`;用户手动分叉两者都没有)会并入委派它的顶层会话那一行,所以一行就是一个对话。每行给出 `total`(这次对话的整日花费,含子代理)与 `ownTotal`(该会话自己的花费);面板里紧跟的今日份金额读的是 `ownTotal`,而归组不改变整日合计。
239
- - 没有费率行的模型不计入(内置价目表覆盖 DSH `llm-deepseek` 目录——V4.1 Flash `deepseek-flash`、V4 Flash、V4 Pro、V4 Flash Vision Exp——外加已退役的 `deepseek-v4.1-flash-expires-on-0910` 预览 id 与 MiMo-V2.5 系列;flash 系列各路由同价)。每份样本取**自身时刻生效的费率版本**:基础价目为 DeepSeek **8 月 17 日实行**的费率;**flash 系列**(V4.1 Flash、V4 Flash、V4 Flash Vision Exp 及退役 id)自 **9 月 10 日 12:00(北京时间)** 起降为谷时 0.02 / 1.0 / 4.0 元每百万 token、峰时为其两倍——该时刻之前的样本(含 V4.1 Flash 路由自身的早先用量的)沿用被取代的旧价;**V4 Pro** 路由公告于 **9 月 14 日 12:00(北京时间)** 改由 V4.1 Flash 服务并按其实施费率计费;MiMo-V2.5 系列不受影响。**周末按低谷价计费**的规则按 **8 月 23 日**生效的调整执行。
263
+ - 未列出的模型**不猜费率**:没有费率行就不计价,但会把用量记为 `unpriced` 并按模型告警一次,于是新模型显示 ¥0 时**带着原因**(列出模型名),而不只是一个普通的空日——MiMo-V2.6 在其费率行补齐前正是这个样子。模型 id 由厂家自己定,名字相近不代表价格相近,所以不做任何前缀推断;要计价就在 `billing.models` 加一行。内置价目表覆盖 DSH `llm-deepseek` 目录——V4.1 Flash `deepseek-flash`、V4 Flash、V4 Pro、V4 Flash Vision Exp——外加已退役的 `deepseek-v4.1-flash-expires-on-0910` 预览 id 与 MiMo-V2.5/V2.6 系列;flash 系列各路由同价)。每份样本取**自身时刻生效的费率版本**:基础价目为 DeepSeek **8 月 17 日实行**的费率;**flash 系列**(V4.1 Flash、V4 Flash、V4 Flash Vision Exp 及退役 id)自 **9 月 10 日 12:00(北京时间)** 起降为谷时 0.02 / 1.0 / 4.0 元每百万 token、峰时为其两倍——该时刻之前的样本(含 V4.1 Flash 路由自身的早先用量的)沿用被取代的旧价;**V4 Pro** 路由公告于 **9 月 14 日 12:00(北京时间)** 改由 V4.1 Flash 服务并按其实施费率计费;MiMo 系列不受影响(MiMo-V2.6 于 2026 年 9 月 22 日发布,沿用 V2.5 公布的费率,共用同一组行)。**周末按低谷价计费**的规则按 **8 月 23 日**生效的调整执行。
240
264
 
241
265
  - **余额口径的「今日消费」**(`API 剩余金额` 后紧跟的那个数字)是纯账户加减,不参与上面的 token 计价:当天(本地自然日)第一次查询到的余额 − 当前余额 + 当天识别出的充值(余额上涨按 **10 元步进向上取整**,因为厂商只按整十充值)。这条口径与本插件对标的 `balanceinfo` 程序一致,并且**刻意与计价的「今日花费」分成两个数**——别的客户端花的钱、或本浏览器打开前花的钱,只体现在这里;两个数字不一致是正常的。当日记录写在本浏览器的 `localStorage`(键 `dsh.billing.balance-day.v1`),跨页面刷新与 `dsh` 重启保留,按**本地自然日**翻篇(浏览器所在的日,不是宿主的北京日键);采样发生在挂载、切换会话、手动刷新与页面可见时的 5 分钟轮询,只有当天已经有过一次采样才显示,且不在客户端之间共享。细则见 [`packages/ui-billing/README.zh.md`](packages/ui-billing/README.zh.md)。
242
266
 
@@ -246,7 +270,7 @@ npm publish # @rayadesu/dsh-billing bundle(仓库根)
246
270
  - **按需聚合** —— **今日花费/今日 Token** 与今日会话排行在主机端 60 秒缓存之后计算,且共用同一次扫描;未命中时,活跃会话直接读投影单元,冷会话若缓存行自身的日期不是查询日则零 I/O 直接作答,只有持久化修订变化(或缓存行覆盖查询日)的会话才读日志。读取失败的会话按修订号记住,不再每轮重试。排行的子代理归组在同一趟扫描里完成,不额外读日志;合计仍然对每个会话求和,所以合并只在总额之间归组、不搬钱。
247
271
  - **排行按需拉取** —— 详情面板打开(或手动刷新)时才拉取排行,徽标一直关着就不会为全量会话扫描买单;「本会话花费」行紧跟的今日份金额也来自这次读取(读该行的 `ownTotal`,合并进来的子代理金额不会被当成这个会话自己的),所以它不额外发请求(代价是排行未落定时不显示这个数字,且最多滞后 60 秒)。
248
272
  - **排行只显示前 10** —— 详情面板最多展示前 10 个会话,其余以「…还有 N 个会话」提示;上限在子代理合并之后生效,所以一行就是一个对话。
249
- - **子代理小计随回合更新,不逐事件跟进** —— 会话行里本会话自身那部分是实时的(推送投影),子代理那部分来自 `billing/getDelegatedSpend`:挂载、手动刷新与回合结束时各拉一次,并与今日花费共用同一个 60 秒宿主缓存。因此子代理在回合中途烧掉的钱会在下一次读取(回合结束、手动刷新或打开面板)时落到父会话金额上,而不是逐事件实时跳动。
273
+ - **子代理小计随回合更新,不逐事件跟进** —— 会话行里本会话自身那部分是实时的(推送投影),子代理那部分来自 `billing/getDelegatedSpend`:挂载、切换会话、手动刷新与回合结束时各拉一次,并与今日花费共用同一个 60 秒宿主缓存。因此子代理在回合中途烧掉的钱会在下一次读取(回合结束、手动刷新或打开面板)时落到父会话金额上,而不是逐事件实时跳动。
250
274
  - **合并行可能显示「未命名」** —— 子代理的父会话不在本次扫描范围内时(例如父日志已被删除或归档),该行仍按 header 里写的父会话 id 归属,但那份日志从未被读取,标题要等父会话被扫描到才显示。
251
275
  - **本轮花费只出现在已定稿的收尾消息** —— 中断的回合没有操作行,不显示本轮花费;冷会话(投影缓存直接命中)排行标题可能显示「未命名」,待其日志被重新读取后恢复。
252
276
  - **额度在两次轮询之间最多旧 15 秒** —— 主机端最多复用 15 秒内的同一份快照,单次请求 5 秒超时;账户在其他客户端产生消耗时,界面值会在下一次轮询(页面可见时 5 分钟一次,隐藏时没有轮询)、手动刷新或刷新浏览器时变化。
package/cordis.patch.yml CHANGED
@@ -3,16 +3,20 @@
3
3
  # Mounts the host-side billing Remote provider (llm-billing) and the
4
4
  # session-header balance/spend badge (ui-billing). The deepseek-harness
5
5
  # official repo does not ship these packages, so the row names must resolve
6
- # from the profile's own node_modules: install the three published packages
7
- # (bundle + the two plugin packages) in one command (see below).
6
+ # from the profile's own node_modules: the bundle declares both plugin
7
+ # packages as regular `dependencies` (profiles init with `nodeLinker: hoisted`
8
+ # and `autoInstallPeers: false`, so rows must be dependencies, never peers),
9
+ # and one install of the bundle pulls them into the profile's flat
10
+ # node_modules (see below).
8
11
  #
9
12
  # No machine-specific literals: this patch carries only plugin ids and names;
10
13
  # no credentials or paths are needed — the plugins read the DeepSeek API key
11
14
  # from the credential seam / environment at runtime.
12
15
  #
13
- # Install with one command (the bundle plus the two plugin packages, since
14
- # pnpm does not install the bundle's local dependencies into the profile):
15
- # dsh plugin --profile web add @rayadesu/dsh-billing @rayadesu/dsh-llm-billing @rayadesu/dsh-client-ui-billing
16
+ # Install the bundle package alone — the Web plugin page's "Add plugin" dialog
17
+ # takes the same package name (it also accepts a GitHub URL or a local
18
+ # directory path), or use the CLI:
19
+ # dsh plugin --profile web add @rayadesu/dsh-billing
16
20
  - insert:
17
21
  - id: llm-billing
18
22
  name: '@rayadesu/dsh-llm-billing'
package/icon.svg ADDED
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48" role="img" aria-label="Billing">
2
+ <rect x="1" y="1" width="46" height="46" rx="12" fill="#2f6fed"/>
3
+ <path d="M13.5 12.5 L24 24.5 L34.5 12.5" fill="none" stroke="#ffffff" stroke-width="4.2" stroke-linecap="round" stroke-linejoin="round"/>
4
+ <path d="M24 24.5 L24 37" fill="none" stroke="#ffffff" stroke-width="4.2" stroke-linecap="round"/>
5
+ <path d="M14.5 29 L33.5 29" fill="none" stroke="#ffffff" stroke-width="4.2" stroke-linecap="round"/>
6
+ <path d="M14.5 34.5 L33.5 34.5" fill="none" stroke="#ffffff" stroke-width="4.2" stroke-linecap="round"/>
7
+ </svg>
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Billing",
4
+ "description": "DeepSeek account balance and session spend badges for the harness."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "计费",
4
+ "description": "DeepSeek 账户余额与会话花费徽标(余额、本轮、今日)。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@rayadesu/dsh-billing",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "description": "DeepSeek Harness billing plugin: account balance and this session's billed spend with a session-header badge.",
5
+ "icon": "./icon.svg",
5
6
  "license": "MIT",
6
7
  "repository": {
7
8
  "type": "git",
@@ -25,8 +26,15 @@
25
26
  "test": "vitest run",
26
27
  "verify": "node scripts/verify-packages.mjs"
27
28
  },
29
+ "exports": {
30
+ "./package.json": "./package.json",
31
+ "./locale/*.json": "./locale/*.json",
32
+ "./cordis.patch.yml": "./cordis.patch.yml"
33
+ },
28
34
  "files": [
29
35
  "cordis.patch.yml",
36
+ "locale/*.json",
37
+ "icon.svg",
30
38
  "README.md",
31
39
  "README.zh.md",
32
40
  "README.i18n.yaml",
@@ -39,9 +47,9 @@
39
47
  "publishConfig": {
40
48
  "access": "public"
41
49
  },
42
- "peerDependencies": {
43
- "@rayadesu/dsh-client-ui-billing": "^0.3.15",
44
- "@rayadesu/dsh-llm-billing": "^0.3.15"
50
+ "dependencies": {
51
+ "@rayadesu/dsh-client-ui-billing": "^0.3.17",
52
+ "@rayadesu/dsh-llm-billing": "^0.3.17"
45
53
  },
46
54
  "devDependencies": {
47
55
  "@deepseek-ai/cordis": "^4.0.4",