@kenz1117/dsh-ui-usage-billing 1.0.2 → 1.0.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 +4 -0
- package/README.md +4 -0
- package/lib/client.js +725 -482
- package/lib/index.js +249 -47
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/pricing.d.ts +68 -6
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -326,6 +326,23 @@ const MODEL_CATALOG = [
|
|
|
326
326
|
output: 28
|
|
327
327
|
}
|
|
328
328
|
},
|
|
329
|
+
{
|
|
330
|
+
key: "glm-5.3-flash",
|
|
331
|
+
name: "GLM-5.3-Flash",
|
|
332
|
+
provider: "智谱 AI",
|
|
333
|
+
colorVar: "dsw-static-blue-300",
|
|
334
|
+
price: {
|
|
335
|
+
currency: "CNY",
|
|
336
|
+
input: .8,
|
|
337
|
+
cacheHit: .23,
|
|
338
|
+
output: 2.8
|
|
339
|
+
},
|
|
340
|
+
promo: {
|
|
341
|
+
factor: .5,
|
|
342
|
+
endsAtMs: Date.UTC(2026, 8, 8, 16, 0, 0),
|
|
343
|
+
note: "限时 5 折"
|
|
344
|
+
}
|
|
345
|
+
},
|
|
329
346
|
{
|
|
330
347
|
key: "glm-4.6",
|
|
331
348
|
name: "GLM-4.6",
|
|
@@ -406,10 +423,66 @@ const MODEL_CATALOG = [
|
|
|
406
423
|
colorVar: "dsw-static-blue-600",
|
|
407
424
|
price: {
|
|
408
425
|
currency: "CNY",
|
|
409
|
-
input:
|
|
410
|
-
cacheHit: 1.
|
|
411
|
-
output:
|
|
412
|
-
}
|
|
426
|
+
input: 12,
|
|
427
|
+
cacheHit: 1.5,
|
|
428
|
+
output: 36
|
|
429
|
+
},
|
|
430
|
+
extraRows: [
|
|
431
|
+
{
|
|
432
|
+
label: "显式缓存创建",
|
|
433
|
+
input: 15
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
label: "显式缓存命中",
|
|
437
|
+
input: 1
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
label: "Batch File",
|
|
441
|
+
input: 6,
|
|
442
|
+
output: 18,
|
|
443
|
+
note: "长期半价"
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
label: "Batch Chat",
|
|
447
|
+
input: 12,
|
|
448
|
+
output: 36,
|
|
449
|
+
note: "与标准价一致"
|
|
450
|
+
}
|
|
451
|
+
]
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
key: "qwen-3.8-flash",
|
|
455
|
+
name: "Qwen3.8 Flash",
|
|
456
|
+
provider: "阿里通义",
|
|
457
|
+
colorVar: "dsw-static-blue-400",
|
|
458
|
+
price: {
|
|
459
|
+
currency: "CNY",
|
|
460
|
+
input: 1,
|
|
461
|
+
cacheHit: .1,
|
|
462
|
+
output: 3
|
|
463
|
+
},
|
|
464
|
+
extraRows: [
|
|
465
|
+
{
|
|
466
|
+
label: "显式缓存创建",
|
|
467
|
+
input: 1.25
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
label: "显式缓存命中",
|
|
471
|
+
input: .1
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
label: "Batch File",
|
|
475
|
+
input: .5,
|
|
476
|
+
output: 1.5,
|
|
477
|
+
note: "长期半价"
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
label: "Batch Chat",
|
|
481
|
+
input: 1,
|
|
482
|
+
output: 3,
|
|
483
|
+
note: "与标准价一致"
|
|
484
|
+
}
|
|
485
|
+
]
|
|
413
486
|
},
|
|
414
487
|
{
|
|
415
488
|
key: "qwen-max",
|
|
@@ -418,10 +491,36 @@ const MODEL_CATALOG = [
|
|
|
418
491
|
colorVar: "dsw-static-blue-300",
|
|
419
492
|
price: {
|
|
420
493
|
currency: "CNY",
|
|
421
|
-
input:
|
|
422
|
-
cacheHit: .
|
|
423
|
-
output:
|
|
424
|
-
}
|
|
494
|
+
input: 12,
|
|
495
|
+
cacheHit: 1.2,
|
|
496
|
+
output: 36
|
|
497
|
+
},
|
|
498
|
+
promo: {
|
|
499
|
+
factor: .5,
|
|
500
|
+
note: "限时 5 折"
|
|
501
|
+
},
|
|
502
|
+
extraRows: [
|
|
503
|
+
{
|
|
504
|
+
label: "显式缓存创建",
|
|
505
|
+
input: 15
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
label: "显式缓存命中",
|
|
509
|
+
input: 1.2
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
label: "Batch File",
|
|
513
|
+
input: 6,
|
|
514
|
+
output: 18,
|
|
515
|
+
note: "长期半价"
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
label: "Batch Chat",
|
|
519
|
+
input: 12,
|
|
520
|
+
output: 36,
|
|
521
|
+
note: "与标准价一致"
|
|
522
|
+
}
|
|
523
|
+
]
|
|
425
524
|
},
|
|
426
525
|
{
|
|
427
526
|
key: "qwen-plus",
|
|
@@ -433,7 +532,29 @@ const MODEL_CATALOG = [
|
|
|
433
532
|
input: .8,
|
|
434
533
|
cacheHit: .08,
|
|
435
534
|
output: 4.8
|
|
436
|
-
}
|
|
535
|
+
},
|
|
536
|
+
extraRows: [
|
|
537
|
+
{
|
|
538
|
+
label: "显式缓存创建",
|
|
539
|
+
input: 1
|
|
540
|
+
},
|
|
541
|
+
{
|
|
542
|
+
label: "显式缓存命中",
|
|
543
|
+
input: .08
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
label: "Batch File",
|
|
547
|
+
input: .4,
|
|
548
|
+
output: 2.4,
|
|
549
|
+
note: "长期半价"
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
label: "Batch Chat",
|
|
553
|
+
input: .8,
|
|
554
|
+
output: 4.8,
|
|
555
|
+
note: "与标准价一致"
|
|
556
|
+
}
|
|
557
|
+
]
|
|
437
558
|
},
|
|
438
559
|
{
|
|
439
560
|
key: "qwen-flash",
|
|
@@ -445,7 +566,14 @@ const MODEL_CATALOG = [
|
|
|
445
566
|
input: .2,
|
|
446
567
|
cacheHit: .02,
|
|
447
568
|
output: 2
|
|
448
|
-
}
|
|
569
|
+
},
|
|
570
|
+
extraRows: [{
|
|
571
|
+
label: "显式缓存创建",
|
|
572
|
+
input: .25
|
|
573
|
+
}, {
|
|
574
|
+
label: "显式缓存命中",
|
|
575
|
+
input: .02
|
|
576
|
+
}]
|
|
449
577
|
},
|
|
450
578
|
{
|
|
451
579
|
key: "doubao",
|
|
@@ -1027,7 +1155,14 @@ const MODEL_CATALOG = [
|
|
|
1027
1155
|
input: 9,
|
|
1028
1156
|
cacheHit: .9,
|
|
1029
1157
|
output: 54
|
|
1030
|
-
}
|
|
1158
|
+
},
|
|
1159
|
+
extraRows: [{
|
|
1160
|
+
label: "显式缓存创建",
|
|
1161
|
+
input: 11.25
|
|
1162
|
+
}, {
|
|
1163
|
+
label: "显式缓存命中",
|
|
1164
|
+
input: .9
|
|
1165
|
+
}]
|
|
1031
1166
|
},
|
|
1032
1167
|
{
|
|
1033
1168
|
key: "qwen3-coder-plus",
|
|
@@ -1039,7 +1174,14 @@ const MODEL_CATALOG = [
|
|
|
1039
1174
|
input: 4,
|
|
1040
1175
|
cacheHit: .8,
|
|
1041
1176
|
output: 16
|
|
1042
|
-
}
|
|
1177
|
+
},
|
|
1178
|
+
extraRows: [{
|
|
1179
|
+
label: "显式缓存创建",
|
|
1180
|
+
input: 5
|
|
1181
|
+
}, {
|
|
1182
|
+
label: "显式缓存命中",
|
|
1183
|
+
input: .4
|
|
1184
|
+
}]
|
|
1043
1185
|
},
|
|
1044
1186
|
{
|
|
1045
1187
|
key: "qwen3-coder",
|
|
@@ -1198,6 +1340,7 @@ const MODEL_KEY_ALIASES = {
|
|
|
1198
1340
|
"glm-4.7": "glm-4.7",
|
|
1199
1341
|
"glm-5-turbo": "glm-5-turbo",
|
|
1200
1342
|
"glm-5.1": "glm-5.1",
|
|
1343
|
+
"glm-5.3-flash": "glm-5.3-flash",
|
|
1201
1344
|
"glm-5v-turbo": "glm-5v-turbo",
|
|
1202
1345
|
"glm-5v.1": "glm-5v-turbo",
|
|
1203
1346
|
"claude-opus-4-6": "claude-opus-4-6",
|
|
@@ -1235,6 +1378,7 @@ const MODEL_KEY_ALIASES = {
|
|
|
1235
1378
|
"doubao-seed-2.1-turbo": "doubao-seed-2.1-turbo",
|
|
1236
1379
|
"doubao-seed-2-1-turbo": "doubao-seed-2.1-turbo",
|
|
1237
1380
|
"qwen3.8-max": "qwen-3.8-max",
|
|
1381
|
+
"qwen3.8-flash": "qwen-3.8-flash",
|
|
1238
1382
|
"qwen3.7-max": "qwen-max",
|
|
1239
1383
|
"qwen3.6-max": "qwen3.6-max",
|
|
1240
1384
|
"qwen3.6-max-preview": "qwen3.6-max",
|
|
@@ -1379,6 +1523,42 @@ function isPriced(key) {
|
|
|
1379
1523
|
return FALLBACK_RATES.some((rate) => rate.key.toLowerCase() === resolved.toLowerCase());
|
|
1380
1524
|
}
|
|
1381
1525
|
/**
|
|
1526
|
+
* 促销在 nowMs 是否生效:factor 必须落在 (0,1) 区间,截止时刻及之后视为过期;
|
|
1527
|
+
* endsAtMs 缺省表示长期活动,在 factor 合法期间持续生效。
|
|
1528
|
+
* 导出供测试:纯函数。
|
|
1529
|
+
* @param promo - 待判定的促销窗口。
|
|
1530
|
+
* @param nowMs - 判定时刻(epoch ms)。
|
|
1531
|
+
*/
|
|
1532
|
+
function isPromoActive(promo, nowMs) {
|
|
1533
|
+
const expired = promo.endsAtMs !== void 0 && nowMs >= promo.endsAtMs;
|
|
1534
|
+
return Number.isFinite(nowMs) && !expired && promo.factor > 0 && promo.factor < 1;
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* 把限时促销折入条目单价:生效期内返回 price 主档与 offPeak 全部乘 factor 的
|
|
1538
|
+
* 副本,其余字段原样保留;不在促销期(过期/未开始/factor 非法)原样返回。
|
|
1539
|
+
* 幂等由调用方保证——计价与费率表显示各自只折一次,勿对已折价副本重复应用。
|
|
1540
|
+
* @param entry - 目录条目(price 保持刊例价口径)。
|
|
1541
|
+
* @param nowMs - 判定时刻(epoch ms)。
|
|
1542
|
+
*/
|
|
1543
|
+
function applyPromo(entry, nowMs) {
|
|
1544
|
+
const { promo } = entry;
|
|
1545
|
+
if (promo === void 0 || !isPromoActive(promo, nowMs)) return entry;
|
|
1546
|
+
const scaled = (band) => ({
|
|
1547
|
+
input: band.input * promo.factor,
|
|
1548
|
+
cacheHit: band.cacheHit * promo.factor,
|
|
1549
|
+
...band.cacheMiss !== void 0 ? { cacheMiss: band.cacheMiss * promo.factor } : {},
|
|
1550
|
+
output: band.output * promo.factor
|
|
1551
|
+
});
|
|
1552
|
+
return {
|
|
1553
|
+
...entry,
|
|
1554
|
+
price: {
|
|
1555
|
+
...scaled(entry.price),
|
|
1556
|
+
currency: entry.price.currency,
|
|
1557
|
+
...entry.price.offPeak !== void 0 ? { offPeak: scaled(entry.price.offPeak) } : {}
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1382
1562
|
* Price one band's token usage in CNY. The stats `input` field is the TOTAL
|
|
1383
1563
|
* prompt tokens (cacheHit + cacheMiss), so billing splits it: the cache-hit
|
|
1384
1564
|
* share prices at the hit rate and the remaining share at the miss rate.
|
|
@@ -1407,15 +1587,17 @@ function priceBandCost(band, buckets, currency) {
|
|
|
1407
1587
|
* @param peakShare - share of traffic in the peak band (0..1); defaults to {@link DEFAULT_PEAK_SHARE}.
|
|
1408
1588
|
* @returns the estimated cost in CNY.
|
|
1409
1589
|
*/
|
|
1410
|
-
function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE) {
|
|
1411
|
-
const
|
|
1412
|
-
const
|
|
1590
|
+
function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE, nowMs = Date.now()) {
|
|
1591
|
+
const priced = applyPromo(entry, nowMs);
|
|
1592
|
+
const peak = priceBandCost(priced.price, buckets, priced.price.currency);
|
|
1593
|
+
const off = priced.price.offPeak === void 0 ? peak : priceBandCost(priced.price.offPeak, buckets, priced.price.currency);
|
|
1413
1594
|
return peak * peakShare + off * (1 - peakShare);
|
|
1414
1595
|
}
|
|
1415
1596
|
/**
|
|
1416
1597
|
* 按调用时刻精确判定高峰/空闲档并计价(P0-1:替代固定比例混合)。时刻未知
|
|
1417
1598
|
* (null/NaN,理论不发生在真实事件流)时回退 {@link DEFAULT_PEAK_SHARE} 混合,
|
|
1418
|
-
* 保持旧语义不低估。平档模型(无 offPeak
|
|
1599
|
+
* 保持旧语义不低估。平档模型(无 offPeak)两个时段同价。限时促销与峰谷档
|
|
1600
|
+
* 同口径:按事件时刻判定该笔流量当时享受的单价。
|
|
1419
1601
|
* @param entry - the catalog entry whose prices apply.
|
|
1420
1602
|
* @param buckets - token usage counts.
|
|
1421
1603
|
* @param timeMs - the call's wall-clock time (epoch ms); null falls back to the peak-share mix.
|
|
@@ -1423,9 +1605,10 @@ function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE) {
|
|
|
1423
1605
|
* @returns the estimated cost in CNY(USD 计价模型已按当前汇率折算)。
|
|
1424
1606
|
*/
|
|
1425
1607
|
function computeCostAt(entry, buckets, timeMs, peakShare = DEFAULT_PEAK_SHARE) {
|
|
1426
|
-
if (entry.price.offPeak === void 0) return priceBandCost(entry.price, buckets, entry.price.currency);
|
|
1427
1608
|
if (timeMs === null || timeMs === void 0 || !Number.isFinite(timeMs)) return computeCost(entry, buckets, peakShare);
|
|
1428
|
-
|
|
1609
|
+
const priced = applyPromo(entry, timeMs);
|
|
1610
|
+
if (priced.price.offPeak === void 0) return priceBandCost(priced.price, buckets, priced.price.currency);
|
|
1611
|
+
return priceBandCost(tierAt(timeMs) === "peak" ? priced.price : priced.price.offPeak, buckets, priced.price.currency);
|
|
1429
1612
|
}
|
|
1430
1613
|
/**
|
|
1431
1614
|
* Format an amount with adaptive precision and the given currency symbol.
|
|
@@ -4407,6 +4590,7 @@ const UsageBillingSettingsSchema = z.object({ [ENABLE_USAGE_STATS_TOOL_FIELD]: z
|
|
|
4407
4590
|
const PRICING_REFRESH_INTERVAL_MS = 360 * 60 * 1e3;
|
|
4408
4591
|
/** 订阅套餐额度缓存时长(毫秒):上游配额 API 低频变化,5 分钟足够。 */
|
|
4409
4592
|
const SUBSCRIPTION_CACHE_MS = 300 * 1e3;
|
|
4593
|
+
const BALANCE_CACHE_MS = 300 * 1e3;
|
|
4410
4594
|
/** DeepSeek 余额查询的默认凭据引用(与 llm-deepseek 的默认引用一致)。 */
|
|
4411
4595
|
const DEFAULT_BALANCE_API_KEY_ENV = "DEEPSEEK_API_KEY";
|
|
4412
4596
|
/**
|
|
@@ -4890,6 +5074,10 @@ function apply(ctx, config = {}) {
|
|
|
4890
5074
|
res.end(JSON.stringify(live));
|
|
4891
5075
|
}
|
|
4892
5076
|
}), "usage-billing: pricing route");
|
|
5077
|
+
let balanceCache = {
|
|
5078
|
+
at: 0,
|
|
5079
|
+
doc: { balances: [] }
|
|
5080
|
+
};
|
|
4893
5081
|
ctx.effect(() => ctx.webServer.register({
|
|
4894
5082
|
kind: "exact",
|
|
4895
5083
|
path: "/api/billing/balance",
|
|
@@ -4903,39 +5091,53 @@ function apply(ctx, config = {}) {
|
|
|
4903
5091
|
...providers["deepseek"],
|
|
4904
5092
|
apiKeyEnv: config.balanceApiKeyEnv ?? DEFAULT_BALANCE_API_KEY_ENV
|
|
4905
5093
|
};
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
for (const row of [
|
|
4910
|
-
...balances,
|
|
4911
|
-
...custom,
|
|
4912
|
-
...declared
|
|
4913
|
-
]) if (row.error === "unauthorized") warnAuthOnce("balance", row.provider, row.displayName);
|
|
4914
|
-
const official = balances.find((row) => row.provider === "deepseek");
|
|
4915
|
-
let reconcile;
|
|
4916
|
-
if (official !== void 0 && official.totalBalance !== void 0) {
|
|
4917
|
-
const now = Date.now();
|
|
4918
|
-
const today = dayStamp(now);
|
|
4919
|
-
let todayOfficialCost = 0;
|
|
4920
|
-
try {
|
|
4921
|
-
todayOfficialCost = (await aggregator.aggregate()).byDay[today]?.officialCost ?? 0;
|
|
4922
|
-
} catch {}
|
|
4923
|
-
const result = reconcileBalanceDelta(reconcileRef, official, todayOfficialCost, today, now);
|
|
4924
|
-
reconcileRef = result.ref;
|
|
4925
|
-
if (result.event !== null && result.event.kind !== "flat") reconcile = {
|
|
4926
|
-
...result.event,
|
|
4927
|
-
provider: official.displayName
|
|
4928
|
-
};
|
|
4929
|
-
if (result.ref !== null) persistReconcileRef();
|
|
5094
|
+
if (Date.now() - balanceCache.at < BALANCE_CACHE_MS) {
|
|
5095
|
+
res.end(JSON.stringify(balanceCache.doc));
|
|
5096
|
+
return;
|
|
4930
5097
|
}
|
|
4931
|
-
|
|
4932
|
-
balances
|
|
5098
|
+
try {
|
|
5099
|
+
const balances = await queryBalances(ctx, providers);
|
|
5100
|
+
const custom = await queryCustomBalances(ctx, config.customBalances ?? []);
|
|
5101
|
+
const declared = await queryDeclaredEndpoints(ctx, piProviders, config.declaredEndpoints ?? []);
|
|
5102
|
+
for (const row of [
|
|
4933
5103
|
...balances,
|
|
4934
5104
|
...custom,
|
|
4935
5105
|
...declared
|
|
4936
|
-
],
|
|
4937
|
-
|
|
4938
|
-
|
|
5106
|
+
]) if (row.error === "unauthorized") warnAuthOnce("balance", row.provider, row.displayName);
|
|
5107
|
+
const official = balances.find((row) => row.provider === "deepseek");
|
|
5108
|
+
let reconcile;
|
|
5109
|
+
if (official !== void 0 && official.totalBalance !== void 0) {
|
|
5110
|
+
const now = Date.now();
|
|
5111
|
+
const today = dayStamp(now);
|
|
5112
|
+
let todayOfficialCost = 0;
|
|
5113
|
+
try {
|
|
5114
|
+
todayOfficialCost = (await aggregator.aggregate()).byDay[today]?.officialCost ?? 0;
|
|
5115
|
+
} catch {}
|
|
5116
|
+
const result = reconcileBalanceDelta(reconcileRef, official, todayOfficialCost, today, now);
|
|
5117
|
+
reconcileRef = result.ref;
|
|
5118
|
+
if (result.event !== null && result.event.kind !== "flat") reconcile = {
|
|
5119
|
+
...result.event,
|
|
5120
|
+
provider: official.displayName
|
|
5121
|
+
};
|
|
5122
|
+
if (result.ref !== null) persistReconcileRef();
|
|
5123
|
+
}
|
|
5124
|
+
const doc = {
|
|
5125
|
+
balances: [
|
|
5126
|
+
...balances,
|
|
5127
|
+
...custom,
|
|
5128
|
+
...declared
|
|
5129
|
+
],
|
|
5130
|
+
...reconcile === void 0 ? {} : { reconcile }
|
|
5131
|
+
};
|
|
5132
|
+
balanceCache = {
|
|
5133
|
+
at: Date.now(),
|
|
5134
|
+
doc
|
|
5135
|
+
};
|
|
5136
|
+
res.end(JSON.stringify(doc));
|
|
5137
|
+
} catch (error) {
|
|
5138
|
+
console.warn("[usage-billing] refreshBalances failed; serving cached balances:", error);
|
|
5139
|
+
res.end(JSON.stringify(balanceCache.doc));
|
|
5140
|
+
}
|
|
4939
5141
|
}
|
|
4940
5142
|
}), "usage-billing: balance route");
|
|
4941
5143
|
ctx.effect(() => ctx.webServer.register({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Locale dictionaries for the usage billing surface. */
|
|
2
|
-
export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendMetric' | 'billing.trendMetricCost' | 'billing.trendMetricTokens' | 'billing.trendEmpty' | 'billing.budget' | 'billing.budgetAmount' | 'billing.budgetSummary' | 'billing.sessions' | 'billing.sessionTitle' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.footer' | 'billing.footerCredit' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.pricingTip' | 'billing.pricingUnit' | 'billing.pricingNotes' | 'billing.ubPeak' | 'billing.ubOff' | 'billing.peakBand' | 'billing.pricingSource' | 'billing.noteCache' | 'billing.noteBand' | 'billing.noteSource' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.reconcileDrift' | 'billing.reconcileDismiss' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatMode' | 'billing.floatTargets' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.subscriptionsStale' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.roundsHint' | 'billing.anomaly' | 'billing.workspaces' | 'billing.workspacesHint' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.thModel' | 'billing.thInputMiss' | 'billing.thInputHit' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakShareHint' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPos' | 'billing.peakAlertMode' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
|
|
2
|
+
export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendMetric' | 'billing.trendMetricCost' | 'billing.trendMetricTokens' | 'billing.trendEmpty' | 'billing.budget' | 'billing.budgetAmount' | 'billing.budgetSummary' | 'billing.sessions' | 'billing.sessionTitle' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.footer' | 'billing.footerCredit' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.promoBadge' | 'billing.promoUntil' | 'billing.promoOpenEnded' | 'billing.pricingTip' | 'billing.pricingUnit' | 'billing.pricingNotes' | 'billing.ubPeak' | 'billing.ubOff' | 'billing.peakBand' | 'billing.pricingSource' | 'billing.noteCache' | 'billing.noteBand' | 'billing.noteSource' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.reconcileDrift' | 'billing.reconcileDismiss' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatMode' | 'billing.floatTargets' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.subscriptionsStale' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.roundsHint' | 'billing.anomaly' | 'billing.workspaces' | 'billing.workspacesHint' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.thModel' | 'billing.thInputMiss' | 'billing.thInputHit' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakShareHint' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPos' | 'billing.peakAlertMode' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
|
|
3
3
|
export declare const NS = "usageBilling";
|
|
4
4
|
export declare const zh: Record<UsageBillingKey, string>;
|
|
5
5
|
export declare const en: Record<UsageBillingKey, string>;
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* (weekdays 09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate —
|
|
13
13
|
* weekends (Sat/Sun, Beijing) are charged at the off-peak rate all day.
|
|
14
14
|
* The estimator mixes both bands by a configured peak share ({@link DEFAULT_PEAK_SHARE}).
|
|
15
|
+
*
|
|
16
|
+
* Time-limited launch promos ({@link PricePromo}) never mutate the catalog:
|
|
17
|
+
* entries keep list price and a promo window; the estimator and the rate
|
|
18
|
+
* table apply the discount factor until the deadline, then auto-revert.
|
|
15
19
|
*/
|
|
16
20
|
import type { LivePricing } from '../pricing-shared.ts';
|
|
17
21
|
/**
|
|
@@ -124,6 +128,36 @@ export interface ModelPrice extends PriceBand {
|
|
|
124
128
|
/** Off-peak band (Gemini Flex / DeepSeek 低谷档); absent = flat pricing. */
|
|
125
129
|
offPeak?: PriceBand;
|
|
126
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* 限时促销窗口(新模型上线折扣等厂商营销活动):生效期内该条目所有档位
|
|
133
|
+
* (主档与 offPeak)单价按 factor 折扣计价与显示,截止时刻起自动恢复刊例价。
|
|
134
|
+
*/
|
|
135
|
+
export interface PricePromo {
|
|
136
|
+
/** 折扣系数(0.5 = 五折);仅 (0,1) 区间有效,非法值视为无促销。 */
|
|
137
|
+
factor: number;
|
|
138
|
+
/**
|
|
139
|
+
* 截止时刻(epoch ms):该时刻及之后恢复刊例价。缺省表示厂商未公布截止日
|
|
140
|
+
* 的长期活动(如「限时 5 折直至另行通知」),持续生效直至收到公告后补填。
|
|
141
|
+
*/
|
|
142
|
+
endsAtMs?: number;
|
|
143
|
+
/** 展示备注(如「限时 5 折至 …」),供界面提示活动性质。 */
|
|
144
|
+
note?: string;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 附加计价行(纯展示参考价):承载主三桶之外的厂商计价维度,如 Batch
|
|
148
|
+
* 半价档、显式缓存创建/命中等。不参与估算计费——用量统计源只有
|
|
149
|
+
* input/cacheHit/cacheMiss/output 四桶,无 batch 与显式缓存维度可区分。
|
|
150
|
+
*/
|
|
151
|
+
export interface PriceRow {
|
|
152
|
+
/** 行标签(沿用目录单语风格,直接中文)。 */
|
|
153
|
+
label: string;
|
|
154
|
+
/** 输入侧单价(元或美元 / 每百万 token);缺省显示 —。 */
|
|
155
|
+
input?: number;
|
|
156
|
+
/** 输出侧单价;缺省显示 —。 */
|
|
157
|
+
output?: number;
|
|
158
|
+
/** 补充说明(如与标准价的关系)。 */
|
|
159
|
+
note?: string;
|
|
160
|
+
}
|
|
127
161
|
/** One catalog entry: identity, brand color token, and price. */
|
|
128
162
|
export interface ModelEntry {
|
|
129
163
|
/** Model key used by `.dsh-usage-stats.json` `byModel`. */
|
|
@@ -138,6 +172,13 @@ export interface ModelEntry {
|
|
|
138
172
|
price: ModelPrice;
|
|
139
173
|
/** Peak-hour window label for time-of-day priced models. */
|
|
140
174
|
peakHours?: string;
|
|
175
|
+
/**
|
|
176
|
+
* 限时促销:生效期内 price 各档位按 factor 打折,过期自动恢复。
|
|
177
|
+
* price 表本身永远保存刊例价,促销只在计价/显示出口处折算,不回写目录。
|
|
178
|
+
*/
|
|
179
|
+
promo?: PricePromo;
|
|
180
|
+
/** 附加计价行(Batch / 显式缓存等展示性参考价),费率表在该模型行下方列出。 */
|
|
181
|
+
extraRows?: readonly PriceRow[];
|
|
141
182
|
/**
|
|
142
183
|
* 单价为估算价:厂商未公布按量官方单价(公测 / 套餐制),表内价格为估算,
|
|
143
184
|
* 展示时标注以免误当正式定价;正式定价公布后移除。
|
|
@@ -194,11 +235,31 @@ export declare function modelOf(key: string): ModelEntry;
|
|
|
194
235
|
*/
|
|
195
236
|
export declare function isPriced(key: string): boolean;
|
|
196
237
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
238
|
+
* 促销在 nowMs 是否生效:factor 必须落在 (0,1) 区间,截止时刻及之后视为过期;
|
|
239
|
+
* endsAtMs 缺省表示长期活动,在 factor 合法期间持续生效。
|
|
240
|
+
* 导出供测试:纯函数。
|
|
241
|
+
* @param promo - 待判定的促销窗口。
|
|
242
|
+
* @param nowMs - 判定时刻(epoch ms)。
|
|
243
|
+
*/
|
|
244
|
+
export declare function isPromoActive(promo: PricePromo, nowMs: number): boolean;
|
|
245
|
+
/**
|
|
246
|
+
* 把限时促销折入条目单价:生效期内返回 price 主档与 offPeak 全部乘 factor 的
|
|
247
|
+
* 副本,其余字段原样保留;不在促销期(过期/未开始/factor 非法)原样返回。
|
|
248
|
+
* 幂等由调用方保证——计价与费率表显示各自只折一次,勿对已折价副本重复应用。
|
|
249
|
+
* @param entry - 目录条目(price 保持刊例价口径)。
|
|
250
|
+
* @param nowMs - 判定时刻(epoch ms)。
|
|
251
|
+
*/
|
|
252
|
+
export declare function applyPromo(entry: ModelEntry, nowMs: number): ModelEntry;
|
|
253
|
+
/**
|
|
254
|
+
* 费率表渲染的完整目录:内置 + 探活命中的模型(无价标记未收录)。
|
|
255
|
+
* models.dev 补充条目**不**整表渲染——那是数百网关厂商的全量模型清单(数千行),
|
|
256
|
+
* 会把费率表撑爆;它们只作为目录外模型的计价回退源(见 {@link livePriceOf} /
|
|
257
|
+
* {@link modelOf})。探活模型在此逐个对价:内置已有的跳过去重;目录外但
|
|
258
|
+
* models.dev 有价的按归一化 id 复用其 USD 价;两者皆无的标 `uncatalogued`。
|
|
259
|
+
* 内置条目按 nowMs 折算限时促销(生效中的条目显示折后单价,过期自动恢复刊例价)。
|
|
260
|
+
* @param nowMs - 促销判定时刻;缺省当前时刻。
|
|
200
261
|
*/
|
|
201
|
-
export declare function catalogEntries(): readonly ModelEntry[];
|
|
262
|
+
export declare function catalogEntries(nowMs?: number): readonly ModelEntry[];
|
|
202
263
|
/** Resolve a price-table row by its CSS variable name (theme token or fallback color). */
|
|
203
264
|
export declare function resolveToken(name: string): string;
|
|
204
265
|
/**
|
|
@@ -216,11 +277,12 @@ export declare function resolveToken(name: string): string;
|
|
|
216
277
|
* @param peakShare - share of traffic in the peak band (0..1); defaults to {@link DEFAULT_PEAK_SHARE}.
|
|
217
278
|
* @returns the estimated cost in CNY.
|
|
218
279
|
*/
|
|
219
|
-
export declare function computeCost(entry: ModelEntry, buckets: TokenUsageBuckets, peakShare?: number): number;
|
|
280
|
+
export declare function computeCost(entry: ModelEntry, buckets: TokenUsageBuckets, peakShare?: number, nowMs?: number): number;
|
|
220
281
|
/**
|
|
221
282
|
* 按调用时刻精确判定高峰/空闲档并计价(P0-1:替代固定比例混合)。时刻未知
|
|
222
283
|
* (null/NaN,理论不发生在真实事件流)时回退 {@link DEFAULT_PEAK_SHARE} 混合,
|
|
223
|
-
* 保持旧语义不低估。平档模型(无 offPeak
|
|
284
|
+
* 保持旧语义不低估。平档模型(无 offPeak)两个时段同价。限时促销与峰谷档
|
|
285
|
+
* 同口径:按事件时刻判定该笔流量当时享受的单价。
|
|
224
286
|
* @param entry - the catalog entry whose prices apply.
|
|
225
287
|
* @param buckets - token usage counts.
|
|
226
288
|
* @param timeMs - the call's wall-clock time (epoch ms); null falls back to the peak-share mix.
|
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.4",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|