dsh-balance-widget 0.5.2 → 0.5.4
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 +16 -1
- package/README.md +16 -1
- package/lib/client.js +4 -4
- package/lib/index.js +63 -11
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -142,7 +142,7 @@ If you mainly use DeepSeek-V4-Pro, point the pricing model at it for a more accu
|
|
|
142
142
|
|
|
143
143
|
## Pricing
|
|
144
144
|
|
|
145
|
-
Built-in DeepSeek official peak/off-peak pricing (CNY per 1M tokens), effective 2026-09-10. Peak windows are Beijing time 09:00–12:00 and 14:00–18:00; prices are double the off-peak rates:
|
|
145
|
+
Built-in DeepSeek official peak/off-peak pricing (CNY per 1M tokens), effective 2026-09-10. Peak windows are Beijing time **Mon–Fri** 09:00–12:00 and 14:00–18:00 (weekends are off-peak all day); prices are double the off-peak rates:
|
|
146
146
|
|
|
147
147
|
| Model | Window | Cache hit (input) | Cache miss (input) | Output |
|
|
148
148
|
| --- | --- | --- | --- | --- |
|
|
@@ -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.4 — search calls are now counted (previously dropped entirely)
|
|
193
|
+
- 🐛 **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
|
|
194
|
+
- 📏 **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**
|
|
195
|
+
- ⚠️ **Scope**: the search portion is an estimate (±30% order); conversation text is still priced exactly from the logs
|
|
196
|
+
|
|
197
|
+
### v0.5.3 — weekends no longer mispriced as peak
|
|
198
|
+
- 🐛 **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
|
|
199
|
+
- 📄 **Docs**: the pricing section and the peak/off-peak tooltips now state the Mon–Fri restriction
|
|
200
|
+
|
|
186
201
|
### v0.5.2 — DSH 0.1.5: new session format & new pricing page
|
|
187
202
|
- 🐛 **Fixed**: DSH 0.1.5 writes session logs under a generation-tagged name (`session.v3.jsonl.zstd`), but the plugin only matched `session.jsonl.zstd`, so every session created after the upgrade was invisible — viewing one produced red error text in the popover, and today's cost was understated (measured ~36% low). Logs are now found by taking the highest generation per session directory; a migrated session's older file is a subset of the new one, so reading only the newest avoids double-counting the session
|
|
188
203
|
- 🐛 **Fixed**: the official pricing page renamed the Flash column to `deepseek-flash` and cut its rates. The parser's model-id anchor landed on a page footnote, so the sync reported success while silently keeping the stale, higher rates — overstating costs by ~1.7x. The parser now anchors on the table's row labels and applies that column to every Flash-family name
|
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ DSH 的插件配置统一放在这个文件里:
|
|
|
139
139
|
|
|
140
140
|
## 定价说明
|
|
141
141
|
|
|
142
|
-
内置 DeepSeek 官方 2026-09-10 峰谷定价(元 / 百万 tokens
|
|
142
|
+
内置 DeepSeek 官方 2026-09-10 峰谷定价(元 / 百万 tokens),高峰时段为北京时间**周一至周五** 09:00–12:00、14:00–18:00(周末全天为空闲时段),价格为空闲时段两倍:
|
|
143
143
|
|
|
144
144
|
| 模型 | 时段 | 缓存命中(输入) | 缓存未命中(输入) | 输出 |
|
|
145
145
|
| --- | --- | --- | --- | --- |
|
|
@@ -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.4 — 计入搜索调用(此前全部漏算)
|
|
190
|
+
- 🐛 **修复**:`web_search` 的辅助模型调用完全不计入成本。DSH 对每次搜索只写一个派发前的 `web/deepseek-search-llm-request` 事件(无 usage),所以按会话日志计价的实现系统性漏算——实测 2026-09-22 一天 1073 次搜索全部没进账。现按「请求次数 × 标定常数」计价,并归入该搜索所属的会话 / 工作区 / 当日及当轮
|
|
191
|
+
- 📏 **标定**:单次搜索 ≈ 6k 缓存未命中输入 + 2k 输出 token(谷时 ¥0.014、峰时 ¥0.028)。2026-09-22 用余额差实测:同一窗口 14 次搜索、余额差 ¥0.28、扣掉日志可算的对话成本 ¥0.094,得 **¥0.0133/次**
|
|
192
|
+
- ⚠️ **口径**:搜索部分是估算值(±30% 量级),会话正文部分仍是按日志精算
|
|
193
|
+
|
|
194
|
+
### v0.5.3 — 修复周末被误判为峰时
|
|
195
|
+
- 🐛 **修复**:峰/谷判断只看小时、不看星期,导致周末 09:00–12:00、14:00–18:00 被误判为峰时并按 2× 计价,"今日花费"最多翻倍。官方规则为**周一至周五** 09:00–12:00、14:00–18:00(其余含周末全天为空闲时段),现按北京时间的星期先行排除周六/周日
|
|
196
|
+
- 📄 **文档**:定价说明与峰谷 tooltip 补上"周一至周五"限定
|
|
197
|
+
|
|
183
198
|
### v0.5.2 — 适配 DSH 0.1.5:新会话格式与新定价页
|
|
184
199
|
- 🐛 **修复**:DSH 0.1.5 将会话日志改为代数命名 `session.v3.jsonl.zstd`,插件此前只认 `session.jsonl.zstd`,导致升级后新建的会话全部不可见——查看这类会话时弹层出现红色报错,今日成本也漏算(实测少约 36%)。现按「每个会话目录取最高代」发现日志;迁移会话保留的旧文件是新代的子集,因此只读最高代,避免整个会话被重复计算
|
|
185
200
|
- 🐛 **修复**:官方定价页将 Flash 列改名为 `deepseek-flash` 并下调价格,插件按模型名定位价格块的锚点因此落到页面脚注,同步"成功"却静默沿用旧高价,成本被高估约 1.7 倍。现改为按价格表行标签锚定,并把该列价格套用到全部 Flash 系名称
|
package/lib/client.js
CHANGED
|
@@ -476,8 +476,8 @@ window.__ModuleLoader__.load({
|
|
|
476
476
|
"todayCostShort": "今日·全部",
|
|
477
477
|
"peak.active": "峰时",
|
|
478
478
|
"peak.offpeak": "谷时",
|
|
479
|
-
"peak.tooltip.active": "
|
|
480
|
-
"peak.tooltip.offpeak": "
|
|
479
|
+
"peak.tooltip.active": "当前为峰时(周一至周五 09:00-12:00、14:00-18:00,价格翻倍):输入 ¥{input}/M · 输出 ¥{output}/M",
|
|
480
|
+
"peak.tooltip.offpeak": "当前为谷时(其余时段含周末全天,半价):输入 ¥{input}/M · 输出 ¥{output}/M",
|
|
481
481
|
"sessionNote": "基于最近活跃会话:",
|
|
482
482
|
"hint": "价格为估算值,实际以官方账单为准。",
|
|
483
483
|
"topUp": "去充值",
|
|
@@ -507,8 +507,8 @@ window.__ModuleLoader__.load({
|
|
|
507
507
|
"todayCostShort": "Today · all",
|
|
508
508
|
"peak.active": "Peak",
|
|
509
509
|
"peak.offpeak": "Off-peak",
|
|
510
|
-
"peak.tooltip.active": "Peak hours now (09:00-12:00, 14:00-18:00, 2× price): input ¥{input}/M · output ¥{output}/M",
|
|
511
|
-
"peak.tooltip.offpeak": "Off-peak hours now (
|
|
510
|
+
"peak.tooltip.active": "Peak hours now (Mon-Fri 09:00-12:00, 14:00-18:00, 2× price): input ¥{input}/M · output ¥{output}/M",
|
|
511
|
+
"peak.tooltip.offpeak": "Off-peak hours now (all other times incl. weekends, half price): input ¥{input}/M · output ¥{output}/M",
|
|
512
512
|
"sessionNote": "Based on most recent session:",
|
|
513
513
|
"hint": "Prices are estimates; actual billing from the provider is authoritative.",
|
|
514
514
|
"topUp": "Top up",
|
package/lib/index.js
CHANGED
|
@@ -264,11 +264,14 @@ const PRICING_ALIASES = {
|
|
|
264
264
|
/**
|
|
265
265
|
* Whether `date` falls inside a Beijing-time (UTC+8) peak window.
|
|
266
266
|
* DeepSeek's peak pricing applies to Beijing time 09:00-12:00 and 14:00-18:00
|
|
267
|
-
*
|
|
268
|
-
* explicitly instead of the machine's local time
|
|
267
|
+
* on weekdays only (Mon-Fri); the whole weekend is off-peak. The window is
|
|
268
|
+
* computed in UTC+8 explicitly instead of the machine's local time, which also
|
|
269
|
+
* supplies the Beijing weekday.
|
|
269
270
|
*/
|
|
270
271
|
function isPeak(date) {
|
|
271
272
|
const beijing = new Date(date.getTime() + 8 * 60 * 60 * 1000);
|
|
273
|
+
const day = beijing.getUTCDay(); // 0 = Sunday, 6 = Saturday
|
|
274
|
+
if (day === 0 || day === 6) return false;
|
|
272
275
|
const hour = beijing.getUTCHours();
|
|
273
276
|
return (hour >= 9 && hour < 12) || (hour >= 14 && hour < 18);
|
|
274
277
|
}
|
|
@@ -295,6 +298,40 @@ function priceUsage(usage, modelId, timeMs) {
|
|
|
295
298
|
return (input * table.inputMiss + hit * table.inputHit + output * table.output) / 1e6;
|
|
296
299
|
}
|
|
297
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
|
+
|
|
298
335
|
/**
|
|
299
336
|
* Parse the official pricing page into our pricingTable shape.
|
|
300
337
|
* The page is HTML; we look for per-model peak/off-peak "per 1M tokens" rates.
|
|
@@ -540,6 +577,7 @@ async function decompressSession(path) {
|
|
|
540
577
|
function parseSessionUsages(text) {
|
|
541
578
|
const samples = [];
|
|
542
579
|
let currentModel = void 0;
|
|
580
|
+
let currentTurn = 0;
|
|
543
581
|
let cwd = void 0;
|
|
544
582
|
let title = void 0;
|
|
545
583
|
for (const line of text.split("\n")) {
|
|
@@ -555,6 +593,8 @@ function parseSessionUsages(text) {
|
|
|
555
593
|
cwd = event.cwd;
|
|
556
594
|
} else if (type === "session/title") {
|
|
557
595
|
title = event.data?.title ?? title;
|
|
596
|
+
} else if (type === "turn/start") {
|
|
597
|
+
currentTurn = event.data?.turn ?? currentTurn;
|
|
558
598
|
} else if (type === "request/header") {
|
|
559
599
|
currentModel = event.data?.header?.config?.model ?? currentModel;
|
|
560
600
|
} else if (type === "assistant/message") {
|
|
@@ -567,6 +607,15 @@ function parseSessionUsages(text) {
|
|
|
567
607
|
usage
|
|
568
608
|
});
|
|
569
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
|
+
});
|
|
570
619
|
}
|
|
571
620
|
}
|
|
572
621
|
return { samples, cwd, title };
|
|
@@ -582,9 +631,10 @@ function lastTurnCost(samples) {
|
|
|
582
631
|
let output = 0;
|
|
583
632
|
for (const s of samples) {
|
|
584
633
|
if (s.turn !== lastTurn) continue;
|
|
585
|
-
cost +=
|
|
586
|
-
|
|
587
|
-
|
|
634
|
+
cost += sampleCost(s);
|
|
635
|
+
const t = sampleTokens(s);
|
|
636
|
+
input += t.input;
|
|
637
|
+
output += t.output;
|
|
588
638
|
}
|
|
589
639
|
return { cost, inputTokens: input, outputTokens: output };
|
|
590
640
|
}
|
|
@@ -599,9 +649,10 @@ function todaySessionCost(samples) {
|
|
|
599
649
|
let output = 0;
|
|
600
650
|
for (const s of samples) {
|
|
601
651
|
if (s.time < dayStart || s.time >= dayEnd) continue;
|
|
602
|
-
cost +=
|
|
603
|
-
|
|
604
|
-
|
|
652
|
+
cost += sampleCost(s);
|
|
653
|
+
const t = sampleTokens(s);
|
|
654
|
+
input += t.input;
|
|
655
|
+
output += t.output;
|
|
605
656
|
}
|
|
606
657
|
return { cost, inputTokens: input, outputTokens: output };
|
|
607
658
|
}
|
|
@@ -650,9 +701,10 @@ function todayCost(sessionList, cwdFilter) {
|
|
|
650
701
|
if (cwdFilter !== void 0 && session.cwd !== cwdFilter) continue;
|
|
651
702
|
for (const s of session.samples) {
|
|
652
703
|
if (s.time < dayStart || s.time >= dayEnd) continue;
|
|
653
|
-
cost +=
|
|
654
|
-
|
|
655
|
-
|
|
704
|
+
cost += sampleCost(s);
|
|
705
|
+
const t = sampleTokens(s);
|
|
706
|
+
input += t.input;
|
|
707
|
+
output += t.output;
|
|
656
708
|
}
|
|
657
709
|
}
|
|
658
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.
|
|
4
|
+
"version": "0.5.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"files": [
|