@kenz1117/dsh-ui-usage-billing 1.1.12 → 1.1.13
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 +5 -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
|
@@ -231,19 +231,24 @@ const WEEKEND_OFFPEAK_START_MS = Date.parse("2026-08-22T16:00:00Z");
|
|
|
231
231
|
const FLASH_REPRICE_MS = Date.parse("2026-09-10T04:00:00Z");
|
|
232
232
|
/**
|
|
233
233
|
* flash 系调价前的官方谷档价(CNY / 1M tokens):{@link FLASH_REPRICE_MS}
|
|
234
|
-
* 之前的 flash / flash-vision-exp 事件按此回算(峰档 = 谷档 × 2)。
|
|
234
|
+
* 之前的 flash / flash-vision-exp 事件按此回算(峰档 = 谷档 × 2)。pro 的
|
|
235
|
+
* 同表条目是 V4 Pro 峰谷时代刊例(谷 4.5/0.15/13.5):官方公告自
|
|
236
|
+
* {@link FLASH_REPRICE_MS} 起 V4 Pro 请求全部路由至 V4.1 Flash 并按其单价
|
|
237
|
+
* 计费(V4.1 Pro 上线前),故分界前回算 V4 Pro 刊例、分界后走目录的 Flash 价。
|
|
235
238
|
* 用户价 = 实付价,与 legacy 口径相同地跳过本表。
|
|
236
239
|
*/
|
|
240
|
+
const FLASH_PRE_REPRICE_BAND = {
|
|
241
|
+
input: 1.5,
|
|
242
|
+
cacheHit: .05,
|
|
243
|
+
output: 4.5
|
|
244
|
+
};
|
|
237
245
|
const FLASH_REPRICED_OFFPEAK = {
|
|
238
|
-
flash:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
input: 1.5,
|
|
245
|
-
cacheHit: .05,
|
|
246
|
-
output: 4.5
|
|
246
|
+
flash: FLASH_PRE_REPRICE_BAND,
|
|
247
|
+
"flash-vision-exp": FLASH_PRE_REPRICE_BAND,
|
|
248
|
+
pro: {
|
|
249
|
+
input: 4.5,
|
|
250
|
+
cacheHit: .15,
|
|
251
|
+
output: 13.5
|
|
247
252
|
}
|
|
248
253
|
};
|
|
249
254
|
/**
|
|
@@ -309,6 +314,8 @@ function isBeijingWeekend(timeMs) {
|
|
|
309
314
|
* splits peak (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate
|
|
310
315
|
* from 2026-08-17, and Gemini's Flex tier discounts spare-capacity traffic.
|
|
311
316
|
*/
|
|
317
|
+
/** DeepSeek 官方高峰时段说明(峰谷分时计费目录条目共用)。 */
|
|
318
|
+
const DEEPSEEK_PEAK_HOURS = "09:00-12:00 / 14:00-18:00";
|
|
312
319
|
const MODEL_CATALOG = [
|
|
313
320
|
{
|
|
314
321
|
key: "flash",
|
|
@@ -326,7 +333,7 @@ const MODEL_CATALOG = [
|
|
|
326
333
|
output: 4
|
|
327
334
|
}
|
|
328
335
|
},
|
|
329
|
-
peakHours:
|
|
336
|
+
peakHours: DEEPSEEK_PEAK_HOURS
|
|
330
337
|
},
|
|
331
338
|
{
|
|
332
339
|
key: "flash-vision-exp",
|
|
@@ -344,7 +351,7 @@ const MODEL_CATALOG = [
|
|
|
344
351
|
output: 4
|
|
345
352
|
}
|
|
346
353
|
},
|
|
347
|
-
peakHours:
|
|
354
|
+
peakHours: DEEPSEEK_PEAK_HOURS
|
|
348
355
|
},
|
|
349
356
|
{
|
|
350
357
|
key: "pro",
|
|
@@ -353,16 +360,16 @@ const MODEL_CATALOG = [
|
|
|
353
360
|
colorVar: "dsw-static-deepseek-500",
|
|
354
361
|
price: {
|
|
355
362
|
currency: "CNY",
|
|
356
|
-
input:
|
|
357
|
-
cacheHit: .
|
|
358
|
-
output:
|
|
363
|
+
input: 2,
|
|
364
|
+
cacheHit: .04,
|
|
365
|
+
output: 8,
|
|
359
366
|
offPeak: {
|
|
360
|
-
input:
|
|
361
|
-
cacheHit: .
|
|
362
|
-
output:
|
|
367
|
+
input: 1,
|
|
368
|
+
cacheHit: .02,
|
|
369
|
+
output: 4
|
|
363
370
|
}
|
|
364
371
|
},
|
|
365
|
-
peakHours:
|
|
372
|
+
peakHours: DEEPSEEK_PEAK_HOURS
|
|
366
373
|
},
|
|
367
374
|
{
|
|
368
375
|
key: "glm",
|
|
@@ -1397,6 +1404,7 @@ const MODEL_CATALOG = [
|
|
|
1397
1404
|
const MODEL_KEY_ALIASES = {
|
|
1398
1405
|
"deepseek-v4-flash": "flash",
|
|
1399
1406
|
"deepseek-v4-flash-vision-exp": "flash-vision-exp",
|
|
1407
|
+
"deepseek-v4.1-flash": "flash",
|
|
1400
1408
|
"deepseek-v4-pro": "pro",
|
|
1401
1409
|
"glm-5.2": "glm",
|
|
1402
1410
|
"glm-4.5-air": "glm-4.5-air",
|
|
@@ -2360,7 +2368,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2360
2368
|
id,
|
|
2361
2369
|
...cwd === void 0 ? {} : { cwd },
|
|
2362
2370
|
...stamp === null ? {} : { stamp },
|
|
2363
|
-
foldVersion:
|
|
2371
|
+
foldVersion: 8,
|
|
2364
2372
|
fold: serializeFold(fold)
|
|
2365
2373
|
};
|
|
2366
2374
|
const row = ledger.get(id);
|
|
@@ -2450,7 +2458,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2450
2458
|
try {
|
|
2451
2459
|
const cwd = meta.cwd;
|
|
2452
2460
|
const ledgerRow = ledger.get(id);
|
|
2453
|
-
if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) ===
|
|
2461
|
+
if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 8) {
|
|
2454
2462
|
const fold = deserializeFold(ledgerRow.fold);
|
|
2455
2463
|
folds.push({
|
|
2456
2464
|
id,
|
|
@@ -2533,7 +2541,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2533
2541
|
for (const entry of ledger.values()) {
|
|
2534
2542
|
if (included.has(entry.id)) continue;
|
|
2535
2543
|
try {
|
|
2536
|
-
const stale = (entry.foldVersion ?? 1) <
|
|
2544
|
+
const stale = (entry.foldVersion ?? 1) < 8;
|
|
2537
2545
|
folds.push({
|
|
2538
2546
|
id: entry.id,
|
|
2539
2547
|
...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
|
|
@@ -5250,13 +5258,7 @@ function apply(ctx, config = {}) {
|
|
|
5250
5258
|
const now = Date.now();
|
|
5251
5259
|
if (now - lastSnapshotAt < SNAPSHOT_INTERVAL_MS) return;
|
|
5252
5260
|
lastSnapshotAt = now;
|
|
5253
|
-
const payload = JSON.stringify(
|
|
5254
|
-
...doc,
|
|
5255
|
-
_writer: {
|
|
5256
|
-
pid: process.pid,
|
|
5257
|
-
at: now
|
|
5258
|
-
}
|
|
5259
|
-
});
|
|
5261
|
+
const payload = JSON.stringify(doc);
|
|
5260
5262
|
(async () => {
|
|
5261
5263
|
try {
|
|
5262
5264
|
const existing = await readFile(snapshotPath, "utf8");
|
|
@@ -5280,13 +5282,67 @@ function apply(ctx, config = {}) {
|
|
|
5280
5282
|
} catch {}
|
|
5281
5283
|
return null;
|
|
5282
5284
|
};
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5285
|
+
const notifyClaimsPath = `${snapshotPath}.notify-claims.json`;
|
|
5286
|
+
const notifyClaims = {
|
|
5287
|
+
loaded: false,
|
|
5288
|
+
keys: []
|
|
5289
|
+
};
|
|
5290
|
+
ctx.effect(() => ctx.webServer.register({
|
|
5291
|
+
kind: "exact",
|
|
5292
|
+
path: "/api/billing/notify-claim",
|
|
5293
|
+
handler: async (req, res) => {
|
|
5294
|
+
if (!guardLoopback(req, res)) return;
|
|
5295
|
+
if (req.method !== "POST") {
|
|
5296
|
+
res.writeHead(405, { "content-type": "application/json; charset=utf-8" });
|
|
5297
|
+
res.end(JSON.stringify({ error: "method not allowed" }));
|
|
5298
|
+
return;
|
|
5299
|
+
}
|
|
5300
|
+
if (!isLoopbackOrigin(req.headers.origin) || !(req.headers["content-type"] ?? "").toLowerCase().includes("application/json")) {
|
|
5301
|
+
res.writeHead(403, { "content-type": "application/json; charset=utf-8" });
|
|
5302
|
+
res.end(JSON.stringify({ error: "forbidden: loopback only" }));
|
|
5303
|
+
return;
|
|
5304
|
+
}
|
|
5305
|
+
try {
|
|
5306
|
+
let body = "";
|
|
5307
|
+
for await (const chunk of req) {
|
|
5308
|
+
body += String(chunk);
|
|
5309
|
+
if (body.length > 2048) {
|
|
5310
|
+
res.end(JSON.stringify({ error: "body too large" }));
|
|
5311
|
+
return;
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
const parsed = JSON.parse(body === "" ? "{}" : body);
|
|
5315
|
+
if (typeof parsed.key !== "string" || parsed.key === "" || parsed.key.length > 200) {
|
|
5316
|
+
res.writeHead(400, { "content-type": "application/json; charset=utf-8" });
|
|
5317
|
+
res.end(JSON.stringify({ error: "invalid key" }));
|
|
5318
|
+
return;
|
|
5319
|
+
}
|
|
5320
|
+
if (!notifyClaims.loaded) {
|
|
5321
|
+
notifyClaims.loaded = true;
|
|
5322
|
+
try {
|
|
5323
|
+
const stored = JSON.parse(await readFile(notifyClaimsPath, "utf8"));
|
|
5324
|
+
if (Array.isArray(stored.keys)) notifyClaims.keys = stored.keys.filter((k) => typeof k === "string");
|
|
5325
|
+
} catch {}
|
|
5326
|
+
}
|
|
5327
|
+
if (notifyClaims.keys.includes(parsed.key)) {
|
|
5328
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
5329
|
+
res.end(JSON.stringify({ claimed: false }));
|
|
5330
|
+
return;
|
|
5331
|
+
}
|
|
5332
|
+
notifyClaims.keys.push(parsed.key);
|
|
5333
|
+
if (notifyClaims.keys.length > 400) notifyClaims.keys = notifyClaims.keys.slice(-200);
|
|
5334
|
+
writeFileAtomic(notifyClaimsPath, JSON.stringify({ keys: notifyClaims.keys }), {
|
|
5335
|
+
mode: 384,
|
|
5336
|
+
dirMode: 448
|
|
5337
|
+
}).catch(() => {});
|
|
5338
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
5339
|
+
res.end(JSON.stringify({ claimed: true }));
|
|
5340
|
+
} catch {
|
|
5341
|
+
res.writeHead(400, { "content-type": "application/json; charset=utf-8" });
|
|
5342
|
+
res.end(JSON.stringify({ error: "invalid" }));
|
|
5343
|
+
}
|
|
5344
|
+
}
|
|
5345
|
+
}), "usage-billing: notify-claim route");
|
|
5290
5346
|
ctx.inject(["settings"], (sctx) => {
|
|
5291
5347
|
const scope = sctx.settings.register(usageBillingSettingsNs, UsageBillingSettingsSchema, { base: { enableUsageStatsTool: config.enableUsageStatsTool ?? false } });
|
|
5292
5348
|
usageSettingsScope = scope;
|
package/lib/types/aggregate.d.ts
CHANGED
|
@@ -427,8 +427,12 @@ export interface UsageLedgerDocument {
|
|
|
427
427
|
* `session/end-seed`,resume 续写后继续对话时把本会话历史段误判为种子丢弃,
|
|
428
428
|
* 会话费用只剩最近一段,issue #29)。
|
|
429
429
|
* 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
|
|
430
|
+
* 8:官方公告自 2026-09-10 12:00(北京)起 V4 Pro 请求路由至 V4.1 Flash 按 Flash
|
|
431
|
+
* 单价计费(V4.1 Pro 上线前)——pro 目录价改写为 Flash 价、V4 Pro 刊例(峰
|
|
432
|
+
* 9/0.3/27、谷 4.5/0.15/13.5)进 FLASH_REPRICED_OFFPEAK 供分界前回算;v7 及更早
|
|
433
|
+
* 的行把分界后的 pro 全部按 V4 Pro 刊例折算(高估数倍),bump 全量重折对齐时间线。
|
|
430
434
|
*/
|
|
431
|
-
export declare const FOLD_VERSION =
|
|
435
|
+
export declare const FOLD_VERSION = 8;
|
|
432
436
|
/**
|
|
433
437
|
* 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
|
|
434
438
|
* 未来账本/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, {
|
|
@@ -265,21 +265,6 @@ export interface ModelEntry {
|
|
|
265
265
|
/** 该条目当前按用户自定义单价计价(设置面板可维护);费率表标注「自定义」。 */
|
|
266
266
|
userPriced?: boolean;
|
|
267
267
|
}
|
|
268
|
-
/**
|
|
269
|
-
* Built-in catalog of current mainstream models as of 2026-08-16, priced from
|
|
270
|
-
* each provider's official price page. Domestic providers are OpenAI-API
|
|
271
|
-
* compatible and publish RMB prices directly; overseas providers publish USD
|
|
272
|
-
* and convert through the exchange rate at estimate time. Retired models
|
|
273
|
-
* (GPT-4o family, Gemini 2.x, GLM-4.x-lite, older Qwen) are deliberately
|
|
274
|
-
* absent, as are Anthropic Claude models (their native API is not
|
|
275
|
-
* OpenAI-compatible, so the harness cannot drive them directly). DeepSeek
|
|
276
|
-
* keys match the harness stats file so real usage prices from the catalog;
|
|
277
|
-
* unknown keys fall back to `other`.
|
|
278
|
-
*
|
|
279
|
-
* Time-of-day billing (peak/off-peak) is now real: DeepSeek V4 officially
|
|
280
|
-
* splits peak (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate
|
|
281
|
-
* from 2026-08-17, and Gemini's Flex tier discounts spare-capacity traffic.
|
|
282
|
-
*/
|
|
283
268
|
export declare const MODEL_CATALOG: readonly ModelEntry[];
|
|
284
269
|
/**
|
|
285
270
|
* 真实 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.1.
|
|
4
|
+
"version": "1.1.13",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|