dsh-balance-widget 0.5.3 → 0.5.5

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
@@ -1,6 +1,6 @@
1
1
  # dsh-balance-widget
2
2
 
3
- [中文](README.md) | **English**
3
+ [中文](https://github.com/LL-cmyk-so/dsh-balance-widget/blob/main/README.md) | **English**
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/dsh-balance-widget?style=flat-square&label=npm)](https://www.npmjs.com/package/dsh-balance-widget)
6
6
  [![Stars](https://img.shields.io/github/stars/LL-cmyk-so/dsh-balance-widget?style=flat-square&label=Stars)](https://github.com/LL-cmyk-so/dsh-balance-widget)
@@ -15,7 +15,7 @@ A balance & cost widget for the [DeepSeek Harness](https://github.com/deepseek-a
15
15
 
16
16
  | Sidebar card | Five-tier cost popover |
17
17
  | --- | --- |
18
- | ![Sidebar card](docs/screenshot-corner.png) | ![Cost popover](docs/screenshot-popover.png) |
18
+ | ![Sidebar card](https://raw.githubusercontent.com/LL-cmyk-so/dsh-balance-widget/main/docs/screenshot-corner.png) | ![Cost popover](https://raw.githubusercontent.com/LL-cmyk-so/dsh-balance-widget/main/docs/screenshot-popover.png) |
19
19
 
20
20
  ## How it differs from similar plugins
21
21
 
@@ -153,6 +153,12 @@ Built-in DeepSeek official peak/off-peak pricing (CNY per 1M tokens), effective
153
153
 
154
154
  The official page currently lists only `deepseek-flash` and `deepseek-v4-pro`; older names (`deepseek-v4-flash`, `deepseek-v4-flash-vision-exp`, `deepseek-chat`) still work and are billed at the Flash rate. The plugin re-parses the page at startup and every 12h, and falls back to the table above only when parsing fails. Costs are **estimates**; the provider's bill is authoritative.
155
155
 
156
+ ### Search calls (estimated)
157
+
158
+ Every **query** of a `web_search` call is a separate auxiliary model call (`@deepseek-ai/dsh-web-search-deepseek`, `deepseek-v4-flash` by default, up to `maxUses` per request). The session log records only the pre-dispatch `web/deepseek-search-llm-request` event — **no usage field** — so the real token counts exist in no local file.
159
+
160
+ The plugin therefore counts requests × a calibrated constant: ~6k cache-miss input + ~2k output tokens per search, i.e. **¥0.014** off-peak and **¥0.028** at peak (calibrated 2026-09-22 against the balance delta: 14 searches measured **¥0.0133 each**). Searches are attributed to the session, workspace and day they belong to, but each amount is an estimate (±30% order).
161
+
156
162
  ## Security & permission boundaries
157
163
 
158
164
  This section is for the DSH Store / plugin audit: dependencies, runtime permissions, external services, and failure bounds.
@@ -183,6 +189,15 @@ This section is for the DSH Store / plugin audit: dependencies, runtime permissi
183
189
 
184
190
  ## Changelog
185
191
 
192
+ ### v0.5.5 — README assets now render on the npm package page
193
+ - 🐛 **Fixed**: the screenshots and the language switch used repository-relative paths (`docs/screenshot-corner.png`, `README.en.md`). The npm package page renders the README body only and does not resolve in-repo paths, so both screenshots and the language link were broken on npm. They are now absolute URLs: screenshots via `raw.githubusercontent.com`, the language switch via a GitHub blob link
194
+ - 📦 **Scope**: documentation only; no code change
195
+
196
+ ### v0.5.4 — search calls are now counted (previously dropped entirely)
197
+ - 🐛 **Fixed**: the auxiliary model calls behind `web_search` contributed nothing to cost. DSH writes only a pre-dispatch `web/deepseek-search-llm-request` event per search (no usage), so any log-derived pricing undercounts systematically — measured 2026-09-22: all 1073 searches of the day were missing. Searches are now priced as requests × a calibrated constant and attributed to their session / workspace / day and turn
198
+ - 📏 **Calibration**: ~6k cache-miss input + ~2k output tokens per search (¥0.014 off-peak, ¥0.028 at peak). Measured 2026-09-22 from the balance delta: 14 searches in one window, balance delta ¥0.28, minus ¥0.094 of log-derivable conversation cost → **¥0.0133 per search**
199
+ - ⚠️ **Scope**: the search portion is an estimate (±30% order); conversation text is still priced exactly from the logs
200
+
186
201
  ### v0.5.3 — weekends no longer mispriced as peak
187
202
  - 🐛 **Fixed**: the peak/off-peak check looked only at the hour and ignored the weekday, so weekends were priced as peak (2×) during 09:00–12:00 and 14:00–18:00, up to doubling "today's cost". The official rule is **Mon–Fri** 09:00–12:00 and 14:00–18:00 (everything else, weekends included, is off-peak); Saturday and Sunday are now excluded first, using the Beijing-time weekday
188
203
  - 📄 **Docs**: the pricing section and the peak/off-peak tooltips now state the Mon–Fri restriction
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-balance-widget
2
2
 
3
- **中文** | [English](README.en.md)
3
+ **中文** | [English](https://github.com/LL-cmyk-so/dsh-balance-widget/blob/main/README.en.md)
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/dsh-balance-widget?style=flat-square&label=npm)](https://www.npmjs.com/package/dsh-balance-widget)
6
6
  [![Stars](https://img.shields.io/github/stars/LL-cmyk-so/dsh-balance-widget?style=flat-square&label=Stars)](https://github.com/LL-cmyk-so/dsh-balance-widget)
@@ -15,7 +15,7 @@ DeepSeek Harness (DSH) Web GUI 的余额与成本小部件:侧边栏底部常
15
15
 
16
16
  | 侧边栏卡片(左下角常驻) | 点击弹出五层级成本 |
17
17
  | --- | --- |
18
- | ![侧边栏卡片](docs/screenshot-corner.png) | ![成本明细弹框](docs/screenshot-popover.png) |
18
+ | ![侧边栏卡片](https://raw.githubusercontent.com/LL-cmyk-so/dsh-balance-widget/main/docs/screenshot-corner.png) | ![成本明细弹框](https://raw.githubusercontent.com/LL-cmyk-so/dsh-balance-widget/main/docs/screenshot-popover.png) |
19
19
 
20
20
  ## 与同类插件的区别
21
21
 
@@ -150,6 +150,12 @@ DSH 的插件配置统一放在这个文件里:
150
150
 
151
151
  官方页当前只列 `deepseek-flash` 与 `deepseek-v4-pro` 两列;`deepseek-v4-flash`、`deepseek-v4-flash-vision-exp`、`deepseek-chat` 等旧名仍可调用,按 Flash 价计费。插件在启动时与每 12h 从官方页重新解析,只有解析失败才回退上表。成本为**估算值**,实际以官方账单为准。
152
152
 
153
+ ### 搜索调用(估算)
154
+
155
+ 每次 `web_search` 的**每个查询**都会被 DSH 当作一次独立的辅助模型调用(`@deepseek-ai/dsh-web-search-deepseek`,默认 `deepseek-v4-flash`,每个请求最多 `maxUses` 次)。而会话日志只记录派发前的 `web/deepseek-search-llm-request` 事件——**不含 usage 字段**,本地任何文件都查不到这些调用的真实 token 数。
156
+
157
+ 插件因此按「请求次数 × 标定常数」计入:每次搜索 ≈ 6k 缓存未命中输入 + 2k 输出 token,即谷时 **¥0.014**、峰时 **¥0.028**(2026-09-22 用余额差标定:14 次搜索实测 **¥0.0133/次**)。搜索会归到它所属的会话、工作区与「今日·全部」,但单次金额是估算值(±30% 量级)。
158
+
153
159
  ## 安全与权限边界
154
160
 
155
161
  本节面向 DSH Store / 插件审计,列出依赖、运行时权限、外部服务与失败边界。
@@ -180,6 +186,15 @@ DSH 的插件配置统一放在这个文件里:
180
186
 
181
187
  ## 版本历史
182
188
 
189
+ ### v0.5.5 — 修复 README 在 npm 包页面上的显示
190
+ - 🐛 **修复**:README 的截图与语言切换此前用相对路径(`docs/screenshot-corner.png`、`README.en.md`)。npm 包页面只渲染 README 正文、不解析仓库内的相对路径,所以在 npm 上两张截图和语言链接都是坏的。现改为绝对 URL:截图走 `raw.githubusercontent.com`,语言切换走 GitHub blob 链接
191
+ - 📦 **范围**:仅文档,代码无变化
192
+
193
+ ### v0.5.4 — 计入搜索调用(此前全部漏算)
194
+ - 🐛 **修复**:`web_search` 的辅助模型调用完全不计入成本。DSH 对每次搜索只写一个派发前的 `web/deepseek-search-llm-request` 事件(无 usage),所以按会话日志计价的实现系统性漏算——实测 2026-09-22 一天 1073 次搜索全部没进账。现按「请求次数 × 标定常数」计价,并归入该搜索所属的会话 / 工作区 / 当日及当轮
195
+ - 📏 **标定**:单次搜索 ≈ 6k 缓存未命中输入 + 2k 输出 token(谷时 ¥0.014、峰时 ¥0.028)。2026-09-22 用余额差实测:同一窗口 14 次搜索、余额差 ¥0.28、扣掉日志可算的对话成本 ¥0.094,得 **¥0.0133/次**
196
+ - ⚠️ **口径**:搜索部分是估算值(±30% 量级),会话正文部分仍是按日志精算
197
+
183
198
  ### v0.5.3 — 修复周末被误判为峰时
184
199
  - 🐛 **修复**:峰/谷判断只看小时、不看星期,导致周末 09:00–12:00、14:00–18:00 被误判为峰时并按 2× 计价,"今日花费"最多翻倍。官方规则为**周一至周五** 09:00–12:00、14:00–18:00(其余含周末全天为空闲时段),现按北京时间的星期先行排除周六/周日
185
200
  - 📄 **文档**:定价说明与峰谷 tooltip 补上"周一至周五"限定
package/lib/index.js CHANGED
@@ -298,6 +298,40 @@ function priceUsage(usage, modelId, timeMs) {
298
298
  return (input * table.inputMiss + hit * table.inputHit + output * table.output) / 1e6;
299
299
  }
300
300
 
301
+ /**
302
+ * Estimated token footprint of one auxiliary web-search turn.
303
+ *
304
+ * DeepSeek runs every `web_search` query as its own Messages call (see
305
+ * @deepseek-ai/dsh-web-search-deepseek) and DSH records only the
306
+ * pre-dispatch `web/deepseek-search-llm-request` event — never the response
307
+ * usage — so the real token counts are not recoverable from any local file.
308
+ * These constants were calibrated on 2026-09-22 against the account balance
309
+ * delta (14 searches over one window: ~¥0.0133 per search at off-peak Flash
310
+ * rates, i.e. ~6k cache-miss input + ~2k output per search).
311
+ */
312
+ const SEARCH_TURN_INPUT_TOKENS = 6000;
313
+ const SEARCH_TURN_OUTPUT_TOKENS = 2000;
314
+
315
+ /** Price one auxiliary web-search turn (estimated; see constants above). */
316
+ function priceSearchTurn(modelId, timeMs) {
317
+ const table = isPeak(new Date(timeMs ?? Date.now())) ? pricingTable[pricingKey(modelId)].peak : pricingTable[pricingKey(modelId)].offPeak;
318
+ return (SEARCH_TURN_INPUT_TOKENS * table.inputMiss + SEARCH_TURN_OUTPUT_TOKENS * table.output) / 1e6;
319
+ }
320
+
321
+ /** Cost of one parsed sample — a real usage sample, or an estimated search turn. */
322
+ function sampleCost(sample) {
323
+ return sample.search === true ? priceSearchTurn(sample.model, sample.time) : priceUsage(sample.usage, sample.model, sample.time);
324
+ }
325
+
326
+ /** Token totals of one parsed sample (a search turn contributes its estimate). */
327
+ function sampleTokens(sample) {
328
+ if (sample.search === true) return { input: SEARCH_TURN_INPUT_TOKENS, output: SEARCH_TURN_OUTPUT_TOKENS };
329
+ return {
330
+ input: (sample.usage.inputTokens ?? sample.usage.uncachedInputTokens ?? 0) + (sample.usage.cacheReadTokens ?? 0),
331
+ output: sample.usage.outputTokens ?? 0
332
+ };
333
+ }
334
+
301
335
  /**
302
336
  * Parse the official pricing page into our pricingTable shape.
303
337
  * The page is HTML; we look for per-model peak/off-peak "per 1M tokens" rates.
@@ -543,6 +577,7 @@ async function decompressSession(path) {
543
577
  function parseSessionUsages(text) {
544
578
  const samples = [];
545
579
  let currentModel = void 0;
580
+ let currentTurn = 0;
546
581
  let cwd = void 0;
547
582
  let title = void 0;
548
583
  for (const line of text.split("\n")) {
@@ -558,6 +593,8 @@ function parseSessionUsages(text) {
558
593
  cwd = event.cwd;
559
594
  } else if (type === "session/title") {
560
595
  title = event.data?.title ?? title;
596
+ } else if (type === "turn/start") {
597
+ currentTurn = event.data?.turn ?? currentTurn;
561
598
  } else if (type === "request/header") {
562
599
  currentModel = event.data?.header?.config?.model ?? currentModel;
563
600
  } else if (type === "assistant/message") {
@@ -570,6 +607,15 @@ function parseSessionUsages(text) {
570
607
  usage
571
608
  });
572
609
  }
610
+ } else if (type === "web/deepseek-search-llm-request") {
611
+ // One auxiliary search turn: no usage is recorded anywhere, so it is
612
+ // priced from the calibrated estimate below.
613
+ samples.push({
614
+ turn: currentTurn,
615
+ time: event.time ?? Date.now(),
616
+ model: event.data?.body?.model ?? currentModel,
617
+ search: true
618
+ });
573
619
  }
574
620
  }
575
621
  return { samples, cwd, title };
@@ -585,9 +631,10 @@ function lastTurnCost(samples) {
585
631
  let output = 0;
586
632
  for (const s of samples) {
587
633
  if (s.turn !== lastTurn) continue;
588
- cost += priceUsage(s.usage, s.model, s.time);
589
- input += (s.usage.inputTokens ?? s.usage.uncachedInputTokens ?? 0) + (s.usage.cacheReadTokens ?? 0);
590
- output += s.usage.outputTokens ?? 0;
634
+ cost += sampleCost(s);
635
+ const t = sampleTokens(s);
636
+ input += t.input;
637
+ output += t.output;
591
638
  }
592
639
  return { cost, inputTokens: input, outputTokens: output };
593
640
  }
@@ -602,9 +649,10 @@ function todaySessionCost(samples) {
602
649
  let output = 0;
603
650
  for (const s of samples) {
604
651
  if (s.time < dayStart || s.time >= dayEnd) continue;
605
- cost += priceUsage(s.usage, s.model, s.time);
606
- input += (s.usage.inputTokens ?? s.usage.uncachedInputTokens ?? 0) + (s.usage.cacheReadTokens ?? 0);
607
- output += s.usage.outputTokens ?? 0;
652
+ cost += sampleCost(s);
653
+ const t = sampleTokens(s);
654
+ input += t.input;
655
+ output += t.output;
608
656
  }
609
657
  return { cost, inputTokens: input, outputTokens: output };
610
658
  }
@@ -653,9 +701,10 @@ function todayCost(sessionList, cwdFilter) {
653
701
  if (cwdFilter !== void 0 && session.cwd !== cwdFilter) continue;
654
702
  for (const s of session.samples) {
655
703
  if (s.time < dayStart || s.time >= dayEnd) continue;
656
- cost += priceUsage(s.usage, s.model, s.time);
657
- input += (s.usage.inputTokens ?? s.usage.uncachedInputTokens ?? 0) + (s.usage.cacheReadTokens ?? 0);
658
- output += s.usage.outputTokens ?? 0;
704
+ cost += sampleCost(s);
705
+ const t = sampleTokens(s);
706
+ input += t.input;
707
+ output += t.output;
659
708
  }
660
709
  }
661
710
  return { cost, inputTokens: input, outputTokens: output };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-balance-widget",
3
3
  "description": "Balance & cost widget for the dsh web GUI: a sidebar footer card shows the DeepSeek account balance and today's costs; clicking opens a popover with a five-tier cost breakdown (last prompt with session name / today-this-session / today-this-workspace / today-all-workspaces), peak/off-peak status tags, term explanations, and a one-click top-up link. Zero external dependencies — works on Node 24.",
4
- "version": "0.5.3",
4
+ "version": "0.5.5",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "files": [