@kenz1117/dsh-ui-usage-billing 1.0.43 → 1.0.44
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/lib/client.js +5 -5
- package/lib/index.js +92 -36
- package/lib/types/aggregate.d.ts +1 -1
- package/lib/types/client/UsageBilling.d.ts +9 -0
- package/lib/types/client/pricing.d.ts +0 -15
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -247,19 +247,24 @@ const WEEKEND_OFFPEAK_START_MS = Date.parse("2026-08-22T16:00:00Z");
|
|
|
247
247
|
const FLASH_REPRICE_MS = Date.parse("2026-09-10T04:00:00Z");
|
|
248
248
|
/**
|
|
249
249
|
* flash 系调价前的官方谷档价(CNY / 1M tokens):{@link FLASH_REPRICE_MS}
|
|
250
|
-
* 之前的 flash / flash-vision-exp 事件按此回算(峰档 = 谷档 × 2)。
|
|
250
|
+
* 之前的 flash / flash-vision-exp 事件按此回算(峰档 = 谷档 × 2)。pro 的
|
|
251
|
+
* 同表条目是 V4 Pro 峰谷时代刊例(谷 4.5/0.15/13.5):官方公告自
|
|
252
|
+
* {@link FLASH_REPRICE_MS} 起 V4 Pro 请求全部路由至 V4.1 Flash 并按其单价
|
|
253
|
+
* 计费(V4.1 Pro 上线前),故分界前回算 V4 Pro 刊例、分界后走目录的 Flash 价。
|
|
251
254
|
* 用户价 = 实付价,与 legacy 口径相同地跳过本表。
|
|
252
255
|
*/
|
|
256
|
+
const FLASH_PRE_REPRICE_BAND = {
|
|
257
|
+
input: 1.5,
|
|
258
|
+
cacheHit: .05,
|
|
259
|
+
output: 4.5
|
|
260
|
+
};
|
|
253
261
|
const FLASH_REPRICED_OFFPEAK = {
|
|
254
|
-
flash:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
input: 1.5,
|
|
261
|
-
cacheHit: .05,
|
|
262
|
-
output: 4.5
|
|
262
|
+
flash: FLASH_PRE_REPRICE_BAND,
|
|
263
|
+
"flash-vision-exp": FLASH_PRE_REPRICE_BAND,
|
|
264
|
+
pro: {
|
|
265
|
+
input: 4.5,
|
|
266
|
+
cacheHit: .15,
|
|
267
|
+
output: 13.5
|
|
263
268
|
}
|
|
264
269
|
};
|
|
265
270
|
/**
|
|
@@ -325,6 +330,8 @@ function isBeijingWeekend(timeMs) {
|
|
|
325
330
|
* splits peak (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate
|
|
326
331
|
* from 2026-08-17, and Gemini's Flex tier discounts spare-capacity traffic.
|
|
327
332
|
*/
|
|
333
|
+
/** DeepSeek 官方高峰时段说明(峰谷分时计费目录条目共用)。 */
|
|
334
|
+
const DEEPSEEK_PEAK_HOURS = "09:00-12:00 / 14:00-18:00";
|
|
328
335
|
const MODEL_CATALOG = [
|
|
329
336
|
{
|
|
330
337
|
key: "flash",
|
|
@@ -342,7 +349,7 @@ const MODEL_CATALOG = [
|
|
|
342
349
|
output: 4
|
|
343
350
|
}
|
|
344
351
|
},
|
|
345
|
-
peakHours:
|
|
352
|
+
peakHours: DEEPSEEK_PEAK_HOURS
|
|
346
353
|
},
|
|
347
354
|
{
|
|
348
355
|
key: "flash-vision-exp",
|
|
@@ -360,7 +367,7 @@ const MODEL_CATALOG = [
|
|
|
360
367
|
output: 4
|
|
361
368
|
}
|
|
362
369
|
},
|
|
363
|
-
peakHours:
|
|
370
|
+
peakHours: DEEPSEEK_PEAK_HOURS
|
|
364
371
|
},
|
|
365
372
|
{
|
|
366
373
|
key: "pro",
|
|
@@ -369,16 +376,16 @@ const MODEL_CATALOG = [
|
|
|
369
376
|
colorVar: "dsw-static-deepseek-500",
|
|
370
377
|
price: {
|
|
371
378
|
currency: "CNY",
|
|
372
|
-
input:
|
|
373
|
-
cacheHit: .
|
|
374
|
-
output:
|
|
379
|
+
input: 2,
|
|
380
|
+
cacheHit: .04,
|
|
381
|
+
output: 8,
|
|
375
382
|
offPeak: {
|
|
376
|
-
input:
|
|
377
|
-
cacheHit: .
|
|
378
|
-
output:
|
|
383
|
+
input: 1,
|
|
384
|
+
cacheHit: .02,
|
|
385
|
+
output: 4
|
|
379
386
|
}
|
|
380
387
|
},
|
|
381
|
-
peakHours:
|
|
388
|
+
peakHours: DEEPSEEK_PEAK_HOURS
|
|
382
389
|
},
|
|
383
390
|
{
|
|
384
391
|
key: "glm",
|
|
@@ -1413,6 +1420,7 @@ const MODEL_CATALOG = [
|
|
|
1413
1420
|
const MODEL_KEY_ALIASES = {
|
|
1414
1421
|
"deepseek-v4-flash": "flash",
|
|
1415
1422
|
"deepseek-v4-flash-vision-exp": "flash-vision-exp",
|
|
1423
|
+
"deepseek-v4.1-flash": "flash",
|
|
1416
1424
|
"deepseek-v4-pro": "pro",
|
|
1417
1425
|
"glm-5.2": "glm",
|
|
1418
1426
|
"glm-4.5-air": "glm-4.5-air",
|
|
@@ -2490,7 +2498,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2490
2498
|
id,
|
|
2491
2499
|
...cwd === void 0 ? {} : { cwd },
|
|
2492
2500
|
...stamp === null ? {} : { stamp },
|
|
2493
|
-
foldVersion:
|
|
2501
|
+
foldVersion: 12,
|
|
2494
2502
|
fingerprint: foldFingerprint,
|
|
2495
2503
|
fold: serializeFold(fold)
|
|
2496
2504
|
};
|
|
@@ -2587,7 +2595,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2587
2595
|
try {
|
|
2588
2596
|
const cwd = meta.cwd;
|
|
2589
2597
|
const ledgerRow = ledger.get(id);
|
|
2590
|
-
if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) ===
|
|
2598
|
+
if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 12 && ledgerRow.fingerprint === foldFingerprint) {
|
|
2591
2599
|
const fold = deserializeFold(ledgerRow.fold);
|
|
2592
2600
|
folds.push({
|
|
2593
2601
|
id,
|
|
@@ -2669,7 +2677,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2669
2677
|
for (const entry of ledger.values()) {
|
|
2670
2678
|
if (included.has(entry.id)) continue;
|
|
2671
2679
|
try {
|
|
2672
|
-
const stale = (entry.foldVersion ?? 1) <
|
|
2680
|
+
const stale = (entry.foldVersion ?? 1) < 12 || entry.fingerprint !== foldFingerprint;
|
|
2673
2681
|
folds.push({
|
|
2674
2682
|
id: entry.id,
|
|
2675
2683
|
...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
|
|
@@ -5745,13 +5753,7 @@ function apply(ctx, config = {}) {
|
|
|
5745
5753
|
const now = Date.now();
|
|
5746
5754
|
if (now - lastSnapshotAt < SNAPSHOT_INTERVAL_MS) return;
|
|
5747
5755
|
lastSnapshotAt = now;
|
|
5748
|
-
const payload = JSON.stringify(
|
|
5749
|
-
...doc,
|
|
5750
|
-
_writer: {
|
|
5751
|
-
pid: process.pid,
|
|
5752
|
-
at: now
|
|
5753
|
-
}
|
|
5754
|
-
});
|
|
5756
|
+
const payload = JSON.stringify(doc);
|
|
5755
5757
|
(async () => {
|
|
5756
5758
|
try {
|
|
5757
5759
|
const existing = await readFile(snapshotPath, "utf8");
|
|
@@ -5775,13 +5777,67 @@ function apply(ctx, config = {}) {
|
|
|
5775
5777
|
} catch {}
|
|
5776
5778
|
return null;
|
|
5777
5779
|
};
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5780
|
+
const notifyClaimsPath = `${snapshotPath}.notify-claims.json`;
|
|
5781
|
+
const notifyClaims = {
|
|
5782
|
+
loaded: false,
|
|
5783
|
+
keys: []
|
|
5784
|
+
};
|
|
5785
|
+
ctx.effect(() => ctx.webServer.register({
|
|
5786
|
+
kind: "exact",
|
|
5787
|
+
path: "/api/billing/notify-claim",
|
|
5788
|
+
handler: async (req, res) => {
|
|
5789
|
+
if (!guardLoopback(req, res)) return;
|
|
5790
|
+
if (req.method !== "POST") {
|
|
5791
|
+
res.writeHead(405, { "content-type": "application/json; charset=utf-8" });
|
|
5792
|
+
res.end(JSON.stringify({ error: "method not allowed" }));
|
|
5793
|
+
return;
|
|
5794
|
+
}
|
|
5795
|
+
if (!isLoopbackOrigin(req.headers.origin) || !(req.headers["content-type"] ?? "").toLowerCase().includes("application/json")) {
|
|
5796
|
+
res.writeHead(403, { "content-type": "application/json; charset=utf-8" });
|
|
5797
|
+
res.end(JSON.stringify({ error: "forbidden: loopback only" }));
|
|
5798
|
+
return;
|
|
5799
|
+
}
|
|
5800
|
+
try {
|
|
5801
|
+
let body = "";
|
|
5802
|
+
for await (const chunk of req) {
|
|
5803
|
+
body += String(chunk);
|
|
5804
|
+
if (body.length > 2048) {
|
|
5805
|
+
res.end(JSON.stringify({ error: "body too large" }));
|
|
5806
|
+
return;
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
const parsed = JSON.parse(body === "" ? "{}" : body);
|
|
5810
|
+
if (typeof parsed.key !== "string" || parsed.key === "" || parsed.key.length > 200) {
|
|
5811
|
+
res.writeHead(400, { "content-type": "application/json; charset=utf-8" });
|
|
5812
|
+
res.end(JSON.stringify({ error: "invalid key" }));
|
|
5813
|
+
return;
|
|
5814
|
+
}
|
|
5815
|
+
if (!notifyClaims.loaded) {
|
|
5816
|
+
notifyClaims.loaded = true;
|
|
5817
|
+
try {
|
|
5818
|
+
const stored = JSON.parse(await readFile(notifyClaimsPath, "utf8"));
|
|
5819
|
+
if (Array.isArray(stored.keys)) notifyClaims.keys = stored.keys.filter((k) => typeof k === "string");
|
|
5820
|
+
} catch {}
|
|
5821
|
+
}
|
|
5822
|
+
if (notifyClaims.keys.includes(parsed.key)) {
|
|
5823
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
5824
|
+
res.end(JSON.stringify({ claimed: false }));
|
|
5825
|
+
return;
|
|
5826
|
+
}
|
|
5827
|
+
notifyClaims.keys.push(parsed.key);
|
|
5828
|
+
if (notifyClaims.keys.length > 400) notifyClaims.keys = notifyClaims.keys.slice(-200);
|
|
5829
|
+
writeFileAtomic(notifyClaimsPath, JSON.stringify({ keys: notifyClaims.keys }), {
|
|
5830
|
+
mode: 384,
|
|
5831
|
+
dirMode: 448
|
|
5832
|
+
}).catch(() => {});
|
|
5833
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
5834
|
+
res.end(JSON.stringify({ claimed: true }));
|
|
5835
|
+
} catch {
|
|
5836
|
+
res.writeHead(400, { "content-type": "application/json; charset=utf-8" });
|
|
5837
|
+
res.end(JSON.stringify({ error: "invalid" }));
|
|
5838
|
+
}
|
|
5839
|
+
}
|
|
5840
|
+
}), "usage-billing: notify-claim route");
|
|
5785
5841
|
ctx.inject(["settings"], (sctx) => {
|
|
5786
5842
|
const scope = sctx.settings.register(usageBillingSettingsNs, UsageBillingSettingsSchema, { base: { enableUsageStatsTool: config.enableUsageStatsTool ?? false } });
|
|
5787
5843
|
usageSettingsScope = scope;
|
package/lib/types/aggregate.d.ts
CHANGED
|
@@ -478,7 +478,7 @@ export interface UsageLedgerDocument {
|
|
|
478
478
|
* 会话费用只剩最近一段,issue #29)。
|
|
479
479
|
* 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
|
|
480
480
|
*/
|
|
481
|
-
export declare const FOLD_VERSION =
|
|
481
|
+
export declare const FOLD_VERSION = 12;
|
|
482
482
|
/**
|
|
483
483
|
* 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
|
|
484
484
|
* 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
|
|
@@ -84,6 +84,15 @@ export declare function projectMonthCost(byDay: Record<string, {
|
|
|
84
84
|
* @returns 重估后的文档;无用户价或缺 byDayModels 时原样返回。
|
|
85
85
|
*/
|
|
86
86
|
export declare function recostWithUserPrices(stats: UsageStats): UsageStats;
|
|
87
|
+
/**
|
|
88
|
+
* 跨实例提醒认领后发系统通知(issue #44):多实例(CLI + 桌面等)合法共存,
|
|
89
|
+
* 各自轮询会重复弹通知。两级去重——进程内 Set 挡同进程的并发/重渲染(trigger
|
|
90
|
+
* 与仪表盘是两个组件实例,effect 同周期并发时本地 store 标记读到的还是旧值);
|
|
91
|
+
* 跨进程走宿主认领端点(先到先得),另一实例刚发过则跳过。端点不可达(旧版本/
|
|
92
|
+
* 网络故障)时放行,退化为原有 per-客户端去重。通知构造失败(平台限制)静默。
|
|
93
|
+
* Set 导出供测试重置(模块级状态会跨用例泄漏)。
|
|
94
|
+
*/
|
|
95
|
+
export declare const notifiedKeys: Set<string>;
|
|
87
96
|
/** 近 7 天费用序列(含今天,缺日补 0):触发卡 hover 速览的迷你柱数据源。
|
|
88
97
|
* 导出供测试:纯函数(日期取本地时区)。 */
|
|
89
98
|
export declare function activeDaysOf(byDay: Record<string, {
|
|
@@ -307,21 +307,6 @@ export interface ModelEntry {
|
|
|
307
307
|
/** 该条目当前按用户自定义单价计价(设置面板可维护);费率表标注「自定义」。 */
|
|
308
308
|
userPriced?: boolean;
|
|
309
309
|
}
|
|
310
|
-
/**
|
|
311
|
-
* Built-in catalog of current mainstream models as of 2026-08-16, priced from
|
|
312
|
-
* each provider's official price page. Domestic providers are OpenAI-API
|
|
313
|
-
* compatible and publish RMB prices directly; overseas providers publish USD
|
|
314
|
-
* and convert through the exchange rate at estimate time. Retired models
|
|
315
|
-
* (GPT-4o family, Gemini 2.x, GLM-4.x-lite, older Qwen) are deliberately
|
|
316
|
-
* absent, as are Anthropic Claude models (their native API is not
|
|
317
|
-
* OpenAI-compatible, so the harness cannot drive them directly). DeepSeek
|
|
318
|
-
* keys match the harness stats file so real usage prices from the catalog;
|
|
319
|
-
* unknown keys fall back to `other`.
|
|
320
|
-
*
|
|
321
|
-
* Time-of-day billing (peak/off-peak) is now real: DeepSeek V4 officially
|
|
322
|
-
* splits peak (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate
|
|
323
|
-
* from 2026-08-17, and Gemini's Flex tier discounts spare-capacity traffic.
|
|
324
|
-
*/
|
|
325
310
|
export declare const MODEL_CATALOG: readonly ModelEntry[];
|
|
326
311
|
/**
|
|
327
312
|
* 真实 provider model id → 计费目录键(`MODEL_CATALOG[].key`)的映射。未知 id
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenz1117/dsh-ui-usage-billing",
|
|
3
3
|
"description": "Usage billing dashboard for DeepSeek Harness: sidebar cost metrics plus a full dashboard modal, priced from a current multi-provider catalog with real usage aggregated from session logs.",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.44",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"deepseek",
|