@kenz1117/dsh-ui-usage-billing 1.4.3 → 1.4.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/lib/index.js CHANGED
@@ -198,7 +198,7 @@ let liveExtraModels;
198
198
  /**
199
199
  * 用户自定义模型别名(插件配置 `modelKeyAliases`,聚合启动时注入):真实日志
200
200
  * model id → 计费目录键。优先级高于内置别名表——目录外的新模型无需等发版,
201
- * 配置一条别名即完成识别与计价(键必须是 MODEL_CATALOG 的既有 key)。
201
+ * 配置一条别名即完成识别与计价(键必须是内置目录的既有 key)。
202
202
  */
203
203
  let userModelAliases;
204
204
  /**
@@ -216,6 +216,7 @@ function applyUserModelAliases(aliases) {
216
216
  */
217
217
  function applyLivePricing(pricing) {
218
218
  liveRate = typeof pricing.rate === "number" && Number.isFinite(pricing.rate) && pricing.rate > 0 ? pricing.rate : void 0;
219
+ typeof pricing.rateEur === "number" && Number.isFinite(pricing.rateEur) && pricing.rateEur > 0 && pricing.rateEur;
219
220
  livePrices = pricing.prices;
220
221
  liveExtraModels = pricing.extraModels;
221
222
  }
@@ -332,210 +333,539 @@ function beijingMillisOfDay(timeMs) {
332
333
  return ((timeMs + 8 * 36e5) % 864e5 + 864e5) % 864e5;
333
334
  }
334
335
  /**
335
- * Built-in catalog of current mainstream models as of 2026-08-16, priced from
336
- * each provider's official price page. Domestic providers are OpenAI-API
337
- * compatible and publish RMB prices directly; overseas providers publish USD
338
- * and convert through the exchange rate at estimate time. Retired models
339
- * (GPT-4o family, Gemini 2.x, GLM-4.x-lite, older Qwen) are deliberately
340
- * absent, as are Anthropic Claude models (their native API is not
341
- * OpenAI-compatible, so the harness cannot drive them directly). DeepSeek
342
- * keys match the harness stats file so real usage prices from the catalog;
343
- * unknown keys fall back to `other`.
344
- *
345
- * Time-of-day billing (peak/off-peak) is now real: DeepSeek V4 officially
346
- * splits peak (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate
347
- * from 2026-08-17, and Gemini's Flex tier discounts spare-capacity traffic.
336
+ * 内置目录与别名表的运行时注入态:数据本体在 src/builtin-catalog.ts(仅随 node
337
+ * 半构建发布)。宿主半区在插件 activate 时同步注入;客户端在 /api/billing/pricing
338
+ * 首次响应时注入。未注入时目录为空——计价消费方走 UNSEEDED_OTHER 兜底(不产生
339
+ * 费用),费率表渲染加载态,不视为错误。
348
340
  */
349
- /** DeepSeek 官方高峰时段说明(峰谷分时计费目录条目共用)。 */
350
- const DEEPSEEK_PEAK_HOURS = "09:00-12:00 / 14:00-18:00";
351
- const GEMINI_PEAK_HOURS = "Standard / Flex";
352
- const MODEL_CATALOG = [
353
- {
354
- key: "flash",
355
- name: "DeepSeek V4.1 Flash",
356
- provider: "DeepSeek",
357
- colorVar: "ds-blue",
358
- price: {
359
- currency: "CNY",
360
- input: 2,
361
- cacheHit: .04,
362
- output: 8,
363
- offPeak: {
364
- input: 1,
365
- cacheHit: .02,
366
- output: 4
367
- }
368
- },
369
- peakHours: DEEPSEEK_PEAK_HOURS
370
- },
371
- {
372
- key: "flash-vision-exp",
373
- name: "DeepSeek V4 Flash Vision (Exp)",
374
- provider: "DeepSeek",
375
- colorVar: "ds-blue",
376
- retired: true,
377
- price: {
378
- currency: "CNY",
379
- input: 2,
380
- cacheHit: .04,
381
- output: 8,
382
- offPeak: {
383
- input: 1,
384
- cacheHit: .02,
385
- output: 4
386
- }
387
- },
388
- peakHours: DEEPSEEK_PEAK_HOURS
389
- },
390
- {
391
- key: "pro",
392
- name: "DeepSeek V4 Pro",
393
- provider: "DeepSeek",
394
- colorVar: "dsw-static-deepseek-500",
395
- price: {
396
- currency: "CNY",
397
- input: 9,
398
- cacheHit: .3,
399
- output: 27,
400
- offPeak: {
401
- input: 4.5,
402
- cacheHit: .15,
403
- output: 13.5
404
- }
405
- },
406
- peakHours: DEEPSEEK_PEAK_HOURS
407
- },
408
- {
409
- key: "glm",
410
- name: "GLM-5.2",
411
- provider: "智谱 AI",
412
- colorVar: "dsw-static-blue-600",
413
- price: {
414
- currency: "CNY",
415
- input: 8,
416
- cacheHit: 2,
417
- output: 28
341
+ let builtinCatalog = [];
342
+ let builtinAliases = {};
343
+ /**
344
+ * 注入内置目录与别名表(两侧各调用一次:宿主 activate、客户端首个 pricing 响应)。
345
+ * 重复注入整体替换并重建归一化索引。
346
+ * @param catalog - 内置目录条目(BUILTIN_MODEL_CATALOG 全量)。
347
+ * @param aliases - 内置别名表(BUILTIN_MODEL_KEY_ALIASES 全量)。
348
+ */
349
+ function applyBuiltinCatalog(catalog, aliases) {
350
+ builtinCatalog = catalog;
351
+ builtinAliases = aliases;
352
+ canonIndexCache = void 0;
353
+ }
354
+ /** 内置目录当前快照:未注入为空数组(client 首帧前渲染加载态)。 */
355
+ function modelCatalog() {
356
+ return builtinCatalog;
357
+ }
358
+ /** 内置别名表当前快照:未注入为空对象。 */
359
+ function modelKeyAliases() {
360
+ return builtinAliases;
361
+ }
362
+ /** 未注入时的目录兜底条目:与内置 other 条目同价(零价,不计费)。 */
363
+ const UNSEEDED_OTHER = {
364
+ key: "other",
365
+ name: "其他模型",
366
+ provider: "Custom",
367
+ colorVar: "dsw-static-neutral-bluish-500",
368
+ price: {
369
+ currency: "CNY",
370
+ input: 0,
371
+ cacheHit: 0,
372
+ cacheMiss: 0,
373
+ output: 0
374
+ }
375
+ };
376
+ /**
377
+ * 模型 id 归一化:小写、去括号附注(如 `gpt5.6 luna(go)` 只看主体)、再去所有
378
+ * 非字母数字分隔符(空格 / 横杠 / 点 / 下划线)。用于日志里的模型 id 与计费
379
+ * 目录键做宽松匹配,提升「大小写/分隔符差异导致未收录」的识别率。
380
+ * @param id - 原始模型 id(日志或目录键)。
381
+ * @returns 归一化键(字母数字小写串)。
382
+ */
383
+ function canonModelId(id) {
384
+ return String(id).toLowerCase().replace(/\([^)]*\)/g, "").replace(/[^a-z0-9]+/g, "");
385
+ }
386
+ /**
387
+ * 目录常量键的归一化索引:归一化键 → 真实计费键。只索引静态来源(内置目录、
388
+ * 别名表、dsh-spend 兜底键);models.dev 补充条目是运行时注入,单独实时匹配。
389
+ * 惰性构建:注入前目录为空,构建无意义;每次 applyBuiltinCatalog 后重建。
390
+ */
391
+ let canonIndexCache;
392
+ function canonIndex() {
393
+ if (canonIndexCache !== void 0) return canonIndexCache;
394
+ const map = /* @__PURE__ */ new Map();
395
+ const add = (candidate, target) => {
396
+ const canon = canonModelId(candidate);
397
+ if (canon !== "" && !map.has(canon)) map.set(canon, target);
398
+ };
399
+ for (const entry of builtinCatalog) add(entry.key, entry.key);
400
+ for (const [alias, key] of Object.entries(builtinAliases)) add(alias, key);
401
+ for (const rate of FALLBACK_RATES) add(rate.key, rate.key);
402
+ canonIndexCache = map;
403
+ return map;
404
+ }
405
+ /**
406
+ * 解析真实日志模型 id → 计费目录键。先精确别名映射(既有行为);未命中时做
407
+ * 归一化匹配(忽略大小写/分隔符/括号附注),命中内置目录 / 别名目标 / 兜底键 /
408
+ * models.dev 补充键即返回其真实键;完全未知时保持原样(回退 other,不计费)。
409
+ * 供聚合层折叠与客户端渲染共用,两侧一致。
410
+ * @param id - 真实模型 id(日志里出现的形式)。
411
+ * @returns 计费目录键。
412
+ */
413
+ /**
414
+ * 由一个未知模型 id 派生候选 id(仅当直接查全部未命中时才尝试):
415
+ * - 剥离组织前缀(`deepseek/deepseek-v4-flash` → `deepseek-v4-flash`);
416
+ * - 剥离尾部纯数字段(`deepseek-v4-flash-202605` / `-0731` → `deepseek-v4-flash`,
417
+ * 覆盖 TokenHub / 官方按日期滚动的快照 id);
418
+ * - 两者组合派生。目录键本身(如 `mistral-large-2512`、`command-a-03-2025`)
419
+ * 在直接查就已命中,永不进入派生分支,不受剥段影响。
420
+ */
421
+ function derivedKeyCandidates(id) {
422
+ const out = [];
423
+ const push = (value) => {
424
+ if (value !== "" && !out.includes(value)) out.push(value);
425
+ };
426
+ const stripTrailingDigits = (value) => {
427
+ let base = value;
428
+ for (;;) {
429
+ const next = base.replace(/[-_]\d{3,}$/u, "");
430
+ if (next === base || next === "") return;
431
+ base = next;
432
+ push(base);
418
433
  }
419
- },
420
- {
421
- key: "glm-5.3",
422
- name: "GLM-5.3",
423
- provider: "智谱 AI",
424
- colorVar: "ds-blue",
425
- price: {
426
- currency: "CNY",
427
- input: 8,
428
- cacheHit: 2,
429
- output: 28
434
+ };
435
+ const slash = id.lastIndexOf("/");
436
+ if (slash > 0 && slash < id.length - 1) {
437
+ const bare = id.slice(slash + 1);
438
+ push(bare);
439
+ stripTrailingDigits(bare);
440
+ }
441
+ stripTrailingDigits(id);
442
+ return out;
443
+ }
444
+ /** 查一个候选 id(用户别名 → 内置别名 → 目录归一化 → models.dev 补充);未命中返回 undefined。 */
445
+ function lookupCandidate(candidate) {
446
+ const alias = userModelAliases?.[candidate] ?? modelKeyAliases()[candidate];
447
+ if (alias !== void 0) return alias;
448
+ const canon = canonModelId(candidate);
449
+ if (canon === "") return void 0;
450
+ const hit = canonIndex().get(canon);
451
+ if (hit !== void 0) return hit;
452
+ return (liveExtraModels ?? []).find((item) => canonModelId(item.key) === canon)?.key;
453
+ }
454
+ function resolveCatalogKey(id) {
455
+ const exact = userModelAliases?.[id] ?? modelKeyAliases()[id] ?? id;
456
+ if (exact === id) {
457
+ const canon = canonModelId(id);
458
+ if (canon !== "") {
459
+ const hit = canonIndex().get(canon);
460
+ if (hit !== void 0) return hit;
461
+ const extraHit = (liveExtraModels ?? []).find((item) => canonModelId(item.key) === canon);
462
+ if (extraHit !== void 0) return extraHit.key;
430
463
  }
431
- },
432
- {
433
- key: "glm-5.3-flash",
434
- name: "GLM-5.3-Flash",
435
- provider: "智谱 AI",
436
- colorVar: "dsw-static-blue-300",
437
- price: {
438
- currency: "CNY",
439
- input: .8,
440
- cacheHit: .23,
441
- output: 2.8
442
- },
443
- promo: {
444
- factor: .5,
445
- endsAtMs: Date.UTC(2026, 8, 8, 16, 0, 0),
446
- note: "限时 5 折"
464
+ for (const candidate of derivedKeyCandidates(id)) {
465
+ const hit = lookupCandidate(candidate);
466
+ if (hit !== void 0) return hit;
447
467
  }
448
- },
449
- {
450
- key: "glm-5.3-flashx",
451
- name: "GLM-5.3-FlashX",
452
- provider: "智谱 AI",
453
- colorVar: "dsw-static-blue-300",
468
+ }
469
+ return exact;
470
+ }
471
+ /** 取一个计费键的实时单价(实时覆盖 > dsh-spend 官方价兜底)。 */
472
+ function livePriceOf(key) {
473
+ const resolved = resolveCatalogKey(key);
474
+ const live = livePrices?.[resolved];
475
+ if (live !== void 0) return live;
476
+ const fallback = FALLBACK_RATES.find((rate) => rate.key.toLowerCase() === resolved.toLowerCase());
477
+ if (fallback === void 0) return void 0;
478
+ return {
479
+ input: fallback.input,
480
+ cacheHit: fallback.cacheHit,
481
+ output: fallback.output
482
+ };
483
+ }
484
+ /** Lookup a model by its stats key; falls back to the generic `other` entry. */
485
+ function modelOf(key) {
486
+ const resolved = resolveCatalogKey(key);
487
+ const found = modelCatalog().find((entry) => entry.key === resolved);
488
+ const extra = liveExtraModels?.find((item) => item.key === resolved);
489
+ const base = found ?? (extra !== void 0 ? extraEntryOf(extra) : modelCatalog().at(-1) ?? UNSEEDED_OTHER);
490
+ const live = livePriceOf(resolved);
491
+ if (live === void 0) return base;
492
+ return {
493
+ ...base,
454
494
  price: {
455
- currency: "CNY",
456
- input: 2,
457
- cacheHit: .57,
458
- output: 7
495
+ currency: "USD",
496
+ input: live.input,
497
+ cacheHit: live.cacheHit,
498
+ output: live.output
459
499
  }
460
- },
461
- {
462
- key: "glm-4.6",
463
- name: "GLM-4.6",
464
- provider: "智谱 AI",
465
- colorVar: "dsw-static-blue-400",
500
+ };
501
+ }
502
+ /** models.dev 补充条目转为目录条目:USD 直价(走汇率换算),无峰谷分档。 */
503
+ function extraEntryOf(extra) {
504
+ return {
505
+ key: extra.key,
506
+ name: extra.name,
507
+ provider: extra.provider,
508
+ colorVar: "dsw-static-neutral-400",
466
509
  price: {
467
- currency: "CNY",
468
- input: 4,
469
- cacheHit: .8,
470
- output: 16
510
+ currency: "USD",
511
+ input: extra.price.input,
512
+ cacheHit: extra.price.cacheHit,
513
+ output: extra.price.output
471
514
  }
472
- },
473
- {
474
- key: "glm-4.5-air",
475
- name: "GLM-4.5-Air",
476
- provider: "智谱 AI",
477
- colorVar: "dsw-static-blue-300",
515
+ };
516
+ }
517
+ /**
518
+ * 模型是否可计价:内置目录、models.dev 补充、或 dsh-spend 官方价兜底命中。
519
+ * 聚合层的计价闸门(目录外模型不产生费用,避免兜底档误估)。
520
+ */
521
+ function isPriced(key) {
522
+ const resolved = resolveCatalogKey(key);
523
+ if (modelCatalog().some((entry) => entry.key === resolved)) return true;
524
+ if ((liveExtraModels ?? []).some((item) => item.key === resolved)) return true;
525
+ return FALLBACK_RATES.some((rate) => rate.key.toLowerCase() === resolved.toLowerCase());
526
+ }
527
+ /**
528
+ * 促销在 nowMs 是否生效:factor 必须落在 (0,1) 区间,截止时刻及之后视为过期;
529
+ * endsAtMs 缺省表示长期活动,在 factor 合法期间持续生效。
530
+ * 导出供测试:纯函数。
531
+ * @param promo - 待判定的促销窗口。
532
+ * @param nowMs - 判定时刻(epoch ms)。
533
+ */
534
+ function isPromoActive(promo, nowMs) {
535
+ const expired = promo.endsAtMs !== void 0 && nowMs >= promo.endsAtMs;
536
+ return Number.isFinite(nowMs) && !expired && promo.factor > 0 && promo.factor < 1;
537
+ }
538
+ /**
539
+ * 把限时促销折入条目单价:生效期内返回 price 主档与 offPeak 逐档乘折扣系数的
540
+ * 副本(某档在 promo.factors 有合法覆盖时用覆盖值,否则用 promo.factor),
541
+ * 其余字段原样保留;不在促销期(过期/未开始/factor 非法)原样返回。
542
+ * 幂等由调用方保证——计价与费率表显示各自只折一次,勿对已折价副本重复应用。
543
+ * @param entry - 目录条目(price 保持刊例价口径)。
544
+ * @param nowMs - 判定时刻(epoch ms)。
545
+ */
546
+ function applyPromo(entry, nowMs) {
547
+ const { promo } = entry;
548
+ if (promo === void 0 || !isPromoActive(promo, nowMs)) return entry;
549
+ const factorOf = (field) => promo.factors?.[field] ?? promo.factor;
550
+ const scaled = (band) => ({
551
+ input: band.input * factorOf("input"),
552
+ cacheHit: band.cacheHit * factorOf("cacheHit"),
553
+ ...band.cacheMiss !== void 0 ? { cacheMiss: band.cacheMiss * factorOf("cacheMiss") } : {},
554
+ output: band.output * factorOf("output")
555
+ });
556
+ return {
557
+ ...entry,
478
558
  price: {
479
- currency: "CNY",
480
- input: .8,
481
- cacheHit: .16,
482
- output: 2
559
+ ...scaled(entry.price),
560
+ currency: entry.price.currency,
561
+ ...entry.price.offPeak !== void 0 ? { offPeak: scaled(entry.price.offPeak) } : {}
483
562
  }
484
- },
563
+ };
564
+ }
565
+ /**
566
+ * Price one band's token usage in CNY. The stats `input` field is the TOTAL
567
+ * prompt tokens (cacheHit + cacheMiss), so billing splits it: the cache-hit
568
+ * share prices at the hit rate and the remaining share at the miss rate.
569
+ * Providers that report only disjoint buckets carry `cacheMiss` explicitly;
570
+ * otherwise the miss share is derived as `input - cacheHit`. Only USD-priced
571
+ * bands go through the exchange rate.
572
+ */
573
+ function priceBandCost(band, buckets, currency) {
574
+ const miss = buckets.cacheMiss > 0 ? buckets.cacheMiss : Math.max(0, buckets.input - buckets.cacheHit);
575
+ const hit = Math.min(buckets.cacheHit, buckets.input);
576
+ const raw = (miss * (band.cacheMiss ?? band.input) + hit * band.cacheHit + buckets.output * band.output) / 1e6;
577
+ return currency === "USD" ? raw * currentRate() : raw;
578
+ }
579
+ /**
580
+ * Estimate the CNY cost of one model's token usage, mixing the peak and
581
+ * off-peak bands by the given peak share (flat-priced models cost the same in
582
+ * both bands).
583
+ *
584
+ * 计费维度是「缓存命中价 × 时段价」的交叉:每个时段档内部分别按缓存命中
585
+ * 价(cacheHit)与未命中价(input/cacheMiss)计价,两个时段档再按
586
+ * peakShare 混合。时段定义以北京时间为准(如 DeepSeek V4 高峰
587
+ * 09:00-12:00 / 14:00-18:00)。因聚合只有按日 token 量、没有请求级时间戳,
588
+ * 时段只能按比例估算,而非逐请求判定。
589
+ * @param entry - the catalog entry whose prices apply.
590
+ * @param buckets - token usage counts.
591
+ * @param peakShare - share of traffic in the peak band (0..1); defaults to {@link DEFAULT_PEAK_SHARE}.
592
+ * @returns the estimated cost in CNY.
593
+ */
594
+ function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE, nowMs = Date.now()) {
595
+ const priced = applyPromo(entry, nowMs);
596
+ const peak = priceBandCost(priced.price, buckets, priced.price.currency);
597
+ const off = priced.price.offPeak === void 0 ? peak : priceBandCost(priced.price.offPeak, buckets, priced.price.currency);
598
+ return peak * peakShare + off * (1 - peakShare);
599
+ }
600
+ /**
601
+ * v1 峰谷档判定(峰谷开闸起、周末全谷分界止):不豁免周末——该时段官方
602
+ * 高峰时段为每天 9-12 / 14-18(周六日同样计峰)。仅用于历史事件计费;
603
+ * 「当前时刻」的档位(提醒/时段条/费率展示)一律走 {@link tierAt} 现行规则。
604
+ */
605
+ function tariffV1At(timeMs) {
606
+ return isPeakHour((new Date(timeMs).getUTCHours() + 8) % 24) ? "peak" : "offPeak";
607
+ }
608
+ /**
609
+ * 按调用时刻精确判定高峰/空闲档并计价(P0-1:替代固定比例混合)。时刻未知
610
+ * (null/NaN,理论不发生在真实事件流)时回退 {@link DEFAULT_PEAK_SHARE} 混合,
611
+ * 保持旧语义不低估。平档模型(无 offPeak)两个时段同价。限时促销与峰谷档
612
+ * 同口径:按事件时刻判定该笔流量当时享受的单价。
613
+ *
614
+ * 历史正确性(按变更节点分段适用规则,不统一套现行价重算历史):
615
+ * - 早于 {@link PEAK_ERA_START_MS} 的事件按当时官方基础价
616
+ * ({@link LEGACY_DEEPSEEK_BANDS})计费;
617
+ * - 峰谷开闸至 {@link WEEKEND_OFFPEAK_START_MS} 之间按 v1 规则(周末不豁免,
618
+ * 周六日 9-12 / 14-18 计峰);
619
+ * - 周末全谷分界起按现行规则({@link tierAt},周六日全天低谷)。
620
+ * @param entry - the catalog entry whose prices apply.
621
+ * @param buckets - token usage counts.
622
+ * @param timeMs - the call's wall-clock time (epoch ms); null falls back to the peak-share mix.
623
+ * @param peakShare - fallback mix used only when `timeMs` is missing.
624
+ * @returns the estimated cost in CNY(USD 计价模型已按当前汇率折算)。
625
+ */
626
+ function computeCostAt(entry, buckets, timeMs, peakShare = DEFAULT_PEAK_SHARE) {
627
+ if (timeMs === null || timeMs === void 0 || !Number.isFinite(timeMs)) return computeCost(entry, buckets, peakShare);
628
+ const priced = applyPromo(entry, timeMs);
629
+ const legacy = timeMs < PEAK_ERA_START_MS && entry.userPriced !== true ? LEGACY_DEEPSEEK_BANDS[entry.key] : void 0;
630
+ if (legacy !== void 0) return priceBandCost(legacy, buckets, "CNY");
631
+ const repriced = timeMs < FLASH_REPRICE_MS && entry.userPriced !== true ? FLASH_REPRICED_OFFPEAK[entry.key] : void 0;
632
+ if (repriced !== void 0) return priceBandCost((timeMs < WEEKEND_OFFPEAK_START_MS ? tariffV1At(timeMs) : tierAt(timeMs)) === "peak" ? {
633
+ input: repriced.input * 2,
634
+ cacheHit: repriced.cacheHit * 2,
635
+ output: repriced.output * 2
636
+ } : repriced, buckets, "CNY");
637
+ if (priced.price.offPeak === void 0) return priceBandCost(priced.price, buckets, priced.price.currency);
638
+ return priceBandCost((timeMs < WEEKEND_OFFPEAK_START_MS ? tariffV1At(timeMs) : tierAt(timeMs)) === "peak" ? priced.price : priced.price.offPeak, buckets, priced.price.currency);
639
+ }
640
+ /**
641
+ * Format an amount with adaptive precision and the given currency symbol.
642
+ * @param amount - the amount (CNY by default; pass `usd` for dollar display).
643
+ * @param currency - display currency; default `cny`.
644
+ */
645
+ function formatMoney(amount, currency = "cny") {
646
+ const value = Number(amount);
647
+ const symbol = currency === "usd" ? "$" : currency === "eur" ? "€" : "¥";
648
+ if (!Number.isFinite(value)) return `${symbol}0`;
649
+ if (value <= 0) return `${symbol}0`;
650
+ if (value >= 1e3) return `${symbol}${value.toFixed(0)}`;
651
+ if (value >= 10) return `${symbol}${value.toFixed(1)}`;
652
+ if (value >= .1) return `${symbol}${value.toFixed(2)}`;
653
+ return `${symbol}${value.toFixed(3)}`;
654
+ }
655
+ /** Format a large token count with B/M/K suffix. */
656
+ function formatTokens(value) {
657
+ if (value >= 1e9) return `${(value / 1e9).toFixed(2)}B`;
658
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
659
+ if (value >= 1e3) return `${(value / 1e3).toFixed(0)}K`;
660
+ return String(value);
661
+ }
662
+ //#endregion
663
+ //#region lib/types/builtin-catalog.js
664
+ /**
665
+ * Built-in catalog of current mainstream models as of 2026-08-16, priced from
666
+ * each provider's official price page. Domestic providers are OpenAI-API
667
+ * compatible and publish RMB prices directly; overseas providers publish USD
668
+ * and convert through the exchange rate at estimate time. Retired models
669
+ * (GPT-4o family, Gemini 2.x, GLM-4.x-lite, older Qwen) are deliberately
670
+ * absent, as are Anthropic Claude models (their native API is not
671
+ * OpenAI-compatible, so the harness cannot drive them directly). DeepSeek
672
+ * keys match the harness stats file so real usage prices from the catalog;
673
+ * unknown keys fall back to `other`.
674
+ *
675
+ * Time-of-day billing (peak/off-peak) is now real: DeepSeek V4 officially
676
+ * splits peak (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate
677
+ * from 2026-08-17, and Gemini's Flex tier discounts spare-capacity traffic.
678
+ */
679
+ /** DeepSeek 官方高峰时段说明(峰谷分时计费目录条目共用)。 */
680
+ const DEEPSEEK_PEAK_HOURS = "09:00-12:00 / 14:00-18:00";
681
+ const GEMINI_PEAK_HOURS = "Standard / Flex";
682
+ const BUILTIN_MODEL_CATALOG = [
485
683
  {
486
- key: "glm-4.7",
487
- name: "GLM-4.7",
488
- provider: "智谱 AI",
489
- colorVar: "dsw-static-blue-400",
684
+ key: "flash",
685
+ name: "DeepSeek V4.1 Flash",
686
+ provider: "DeepSeek",
687
+ colorVar: "ds-blue",
490
688
  price: {
491
689
  currency: "CNY",
492
- input: 4,
493
- cacheHit: 1,
494
- output: 16
690
+ input: 2,
691
+ cacheHit: .04,
692
+ output: 8,
693
+ offPeak: {
694
+ input: 1,
695
+ cacheHit: .02,
696
+ output: 4
697
+ }
495
698
  },
496
- estimated: true
699
+ peakHours: DEEPSEEK_PEAK_HOURS
497
700
  },
498
701
  {
499
- key: "glm-5-turbo",
500
- name: "GLM-5-Turbo",
501
- provider: "智谱 AI",
702
+ key: "flash-vision-exp",
703
+ name: "DeepSeek V4 Flash Vision (Exp)",
704
+ provider: "DeepSeek",
502
705
  colorVar: "ds-blue",
706
+ retired: true,
503
707
  price: {
504
708
  currency: "CNY",
505
- input: 5,
506
- cacheHit: 1.2,
507
- output: 22
508
- }
709
+ input: 2,
710
+ cacheHit: .04,
711
+ output: 8,
712
+ offPeak: {
713
+ input: 1,
714
+ cacheHit: .02,
715
+ output: 4
716
+ }
717
+ },
718
+ peakHours: DEEPSEEK_PEAK_HOURS
509
719
  },
510
720
  {
511
- key: "glm-5.1",
512
- name: "GLM-5.1",
721
+ key: "pro",
722
+ name: "DeepSeek V4 Pro",
723
+ provider: "DeepSeek",
724
+ colorVar: "dsw-static-deepseek-500",
725
+ price: {
726
+ currency: "CNY",
727
+ input: 9,
728
+ cacheHit: .3,
729
+ output: 27,
730
+ offPeak: {
731
+ input: 4.5,
732
+ cacheHit: .15,
733
+ output: 13.5
734
+ }
735
+ },
736
+ peakHours: DEEPSEEK_PEAK_HOURS
737
+ },
738
+ {
739
+ key: "glm",
740
+ name: "GLM-5.2",
513
741
  provider: "智谱 AI",
514
742
  colorVar: "dsw-static-blue-600",
515
743
  price: {
516
744
  currency: "CNY",
517
- input: 6,
518
- cacheHit: 1.2,
519
- output: 24
745
+ input: 8,
746
+ cacheHit: 2,
747
+ output: 28
520
748
  }
521
749
  },
522
750
  {
523
- key: "glm-5v-turbo",
524
- name: "GLM-5V-Turbo",
751
+ key: "glm-5.3",
752
+ name: "GLM-5.3",
525
753
  provider: "智谱 AI",
526
- colorVar: "dsw-static-blue-300",
754
+ colorVar: "ds-blue",
527
755
  price: {
528
756
  currency: "CNY",
529
- input: 5,
530
- cacheHit: 1.2,
531
- output: 22
757
+ input: 8,
758
+ cacheHit: 2,
759
+ output: 28
532
760
  }
533
761
  },
534
762
  {
535
- key: "qwen-3.8-max",
536
- name: "Qwen3.8 Max",
537
- provider: "阿里通义",
538
- colorVar: "dsw-static-blue-600",
763
+ key: "glm-5.3-flash",
764
+ name: "GLM-5.3-Flash",
765
+ provider: "智谱 AI",
766
+ colorVar: "dsw-static-blue-300",
767
+ price: {
768
+ currency: "CNY",
769
+ input: .8,
770
+ cacheHit: .23,
771
+ output: 2.8
772
+ },
773
+ promo: {
774
+ factor: .5,
775
+ endsAtMs: Date.UTC(2026, 8, 8, 16, 0, 0),
776
+ note: "限时 5 折"
777
+ }
778
+ },
779
+ {
780
+ key: "glm-5.3-flashx",
781
+ name: "GLM-5.3-FlashX",
782
+ provider: "智谱 AI",
783
+ colorVar: "dsw-static-blue-300",
784
+ price: {
785
+ currency: "CNY",
786
+ input: 2,
787
+ cacheHit: .57,
788
+ output: 7
789
+ }
790
+ },
791
+ {
792
+ key: "glm-4.6",
793
+ name: "GLM-4.6",
794
+ provider: "智谱 AI",
795
+ colorVar: "dsw-static-blue-400",
796
+ price: {
797
+ currency: "CNY",
798
+ input: 4,
799
+ cacheHit: .8,
800
+ output: 16
801
+ }
802
+ },
803
+ {
804
+ key: "glm-4.5-air",
805
+ name: "GLM-4.5-Air",
806
+ provider: "智谱 AI",
807
+ colorVar: "dsw-static-blue-300",
808
+ price: {
809
+ currency: "CNY",
810
+ input: .8,
811
+ cacheHit: .16,
812
+ output: 2
813
+ }
814
+ },
815
+ {
816
+ key: "glm-4.7",
817
+ name: "GLM-4.7",
818
+ provider: "智谱 AI",
819
+ colorVar: "dsw-static-blue-400",
820
+ price: {
821
+ currency: "CNY",
822
+ input: 4,
823
+ cacheHit: 1,
824
+ output: 16
825
+ },
826
+ estimated: true
827
+ },
828
+ {
829
+ key: "glm-5-turbo",
830
+ name: "GLM-5-Turbo",
831
+ provider: "智谱 AI",
832
+ colorVar: "ds-blue",
833
+ price: {
834
+ currency: "CNY",
835
+ input: 5,
836
+ cacheHit: 1.2,
837
+ output: 22
838
+ }
839
+ },
840
+ {
841
+ key: "glm-5.1",
842
+ name: "GLM-5.1",
843
+ provider: "智谱 AI",
844
+ colorVar: "dsw-static-blue-600",
845
+ price: {
846
+ currency: "CNY",
847
+ input: 6,
848
+ cacheHit: 1.2,
849
+ output: 24
850
+ }
851
+ },
852
+ {
853
+ key: "glm-5v-turbo",
854
+ name: "GLM-5V-Turbo",
855
+ provider: "智谱 AI",
856
+ colorVar: "dsw-static-blue-300",
857
+ price: {
858
+ currency: "CNY",
859
+ input: 5,
860
+ cacheHit: 1.2,
861
+ output: 22
862
+ }
863
+ },
864
+ {
865
+ key: "qwen-3.8-max",
866
+ name: "Qwen3.8 Max",
867
+ provider: "阿里通义",
868
+ colorVar: "dsw-static-blue-600",
539
869
  price: {
540
870
  currency: "CNY",
541
871
  input: 12,
@@ -1462,425 +1792,139 @@ const MODEL_CATALOG = [
1462
1792
  key: "doubao-seed-2.0-lite",
1463
1793
  name: "Doubao Seed-2.0 Lite",
1464
1794
  provider: "字节豆包",
1465
- colorVar: "dsw-static-red-300",
1466
- price: {
1467
- currency: "CNY",
1468
- input: .6,
1469
- cacheHit: .12,
1470
- output: 3.6
1471
- }
1472
- },
1473
- {
1474
- key: "other",
1475
- name: "其他模型",
1476
- provider: "Custom",
1477
- colorVar: "dsw-static-neutral-bluish-500",
1478
- price: {
1479
- currency: "CNY",
1480
- input: 0,
1481
- cacheHit: 0,
1482
- cacheMiss: 0,
1483
- output: 0
1484
- }
1485
- }
1486
- ];
1487
- /**
1488
- * 真实 provider model id → 计费目录键(`MODEL_CATALOG[].key`)的映射。未知 id
1489
- * 原样保留并落回 `other`(未知模型不估算费用)。聚合层(aggregate.ts)在折叠时
1490
- * 用同一张表把日志里的 model id 归并为目录键,客户端渲染(`modelOf`)也按它
1491
- * 解析,两侧共用一份映射,避免同一模型两侧不一致导致「未收录」。
1492
- */
1493
- const MODEL_KEY_ALIASES = {
1494
- "deepseek-v4-flash": "flash",
1495
- "deepseek-v4-flash-vision-exp": "flash-vision-exp",
1496
- "deepseek-v4.1-flash": "flash",
1497
- "deepseek-v4.1-flash-expires-on-0910": "flash",
1498
- "deepseek-flash": "flash",
1499
- "deepseek-v4-pro": "pro",
1500
- "glm-5.2": "glm",
1501
- "glm-4.5-air": "glm-4.5-air",
1502
- "glm-4.5air": "glm-4.5-air",
1503
- "glm-4.7": "glm-4.7",
1504
- "glm-5-turbo": "glm-5-turbo",
1505
- "glm-5.1": "glm-5.1",
1506
- "glm-5.3-flash": "glm-5.3-flash",
1507
- "glm-5.3-flashx": "glm-5.3-flashx",
1508
- "glm-5.3-flash-x": "glm-5.3-flashx",
1509
- "glm-5v-turbo": "glm-5v-turbo",
1510
- "glm-5v.1": "glm-5v-turbo",
1511
- "claude-opus-4-6": "claude-opus-4-6",
1512
- "claude-opus-4.6": "claude-opus-4-6",
1513
- "claude-sonnet-4-6": "claude-sonnet-4-6",
1514
- "claude-sonnet-4.6": "claude-sonnet-4-6",
1515
- "claude-haiku-4-5": "claude-haiku-4-5",
1516
- "claude-haiku-4.5": "claude-haiku-4-5",
1517
- "claude-opus-5": "claude-opus-5",
1518
- "claude-sonnet-5": "claude-sonnet-5",
1519
- "mistral-large-2512": "mistral-large-2512",
1520
- "mistral-large-3": "mistral-large-2512",
1521
- "mistral-small-2603": "mistral-small-2603",
1522
- "mistral-small-4": "mistral-small-2603",
1523
- "ministral-8b-latest": "ministral-8b-latest",
1524
- "ministral-8b": "ministral-8b-latest",
1525
- "command-a-03-2025": "command-a-03-2025",
1526
- "command-a": "command-a-03-2025",
1527
- "command-r-08-2024": "command-r-08-2024",
1528
- "command-r": "command-r-08-2024",
1529
- "hy3": "hunyuan",
1530
- "hy4-preview": "hunyuan-hy4-preview",
1531
- "hy4": "hunyuan-hy4-preview",
1532
- "hunyuan-hy4-preview": "hunyuan-hy4-preview",
1533
- "hunyuan-hy4": "hunyuan-hy4-preview",
1534
- "longcat-2.0": "longcat-2.0",
1535
- "longcat-2": "longcat-2.0",
1536
- "minicpm-v-4.5": "minicpm-v-4.5",
1537
- "minicpm-v-4.5-thinking": "minicpm-v-4.5",
1538
- "ernie-4.5": "ernie-4.5",
1539
- "ernie-4.5-300b": "ernie-4.5",
1540
- "dots-3-note-preview": "dots-3-note-preview",
1541
- "dots-3-note": "dots-3-note-preview",
1542
- "dots3-note-preview": "dots-3-note-preview",
1543
- "rednote-dots3": "dots-3-note-preview",
1544
- "doubao-seed-evolving": "doubao-seed-evolving",
1545
- "doubao-seed-evolve": "doubao-seed-evolving",
1546
- "doubao-seed-2.1-pro": "doubao-seed-2.1-pro",
1547
- "doubao-seed-2.1-pro-290000": "doubao-seed-2.1-pro",
1548
- "doubao-seed-2.1-turbo": "doubao-seed-2.1-turbo",
1549
- "doubao-seed-2-1-turbo": "doubao-seed-2.1-turbo",
1550
- "qwen3.8-max": "qwen-3.8-max",
1551
- "qwen3.8-flash": "qwen-3.8-flash",
1552
- "qwen3.7-max": "qwen-max",
1553
- "qwen3.6-max": "qwen3.6-max",
1554
- "qwen3.6-max-preview": "qwen3.6-max",
1555
- "qwen3-coder-plus": "qwen3-coder-plus",
1556
- "qwen3-coder": "qwen3-coder",
1557
- "qwen3-coder-480b": "qwen3-coder",
1558
- "glm-4.5-x": "glm-4.5-x",
1559
- "glm-4.5x": "glm-4.5-x",
1560
- "glm-5.2-fast": "glm-5.2-fast",
1561
- "glm-5.2f": "glm-5.2-fast",
1562
- "kimi-k3-fast": "kimi-k3-fast",
1563
- "kimi-k3f": "kimi-k3-fast",
1564
- "kimi-for-coding": "kimi-k2.8-preview",
1565
- "kimi-k2.7-code-fast": "kimi-k2.7-code-fast",
1566
- "kimi-k2.7-code-f": "kimi-k2.7-code-fast",
1567
- "kimi-k2.6-fast": "kimi-k2.6-fast",
1568
- "kimi-k2.6-turbo": "kimi-k2.6-turbo",
1569
- "kimi-k2-thinking-turbo": "kimi-k2-thinking-turbo",
1570
- "doubao-seed-2.0-code": "doubao-seed-2.0-code",
1571
- "doubao-seed-2-0-code": "doubao-seed-2.0-code",
1572
- "doubao-seed-2.0-lite": "doubao-seed-2.0-lite",
1573
- "doubao-seed-2-0-lite": "doubao-seed-2.0-lite",
1574
- "qwen-max": "qwen-max",
1575
- "hunyuan-t1": "hunyuan-t1",
1576
- "step-3.7-flash": "step",
1577
- "seed-2.0-mini": "doubao-mini",
1578
- "k3": "kimi-k3",
1579
- "kimi-k3": "kimi-k3",
1580
- "kimi-k2.8-preview": "kimi-k2.8-preview",
1581
- "kimi-k2.8": "kimi-k2.8-preview",
1582
- "kimi-k2-8-preview": "kimi-k2.8-preview",
1583
- "kimi-k2-8": "kimi-k2.8-preview",
1584
- "k2.8-preview": "kimi-k2.8-preview",
1585
- "k2.8": "kimi-k2.8-preview",
1586
- "minimax-m1": "minimax",
1587
- "minimax-m2": "minimax",
1588
- "minimax-m3": "minimax",
1589
- "minimax-m2.7": "minimax-m2.7",
1590
- "minimax-m2.7-highspeed": "minimax-m2.7-highspeed",
1591
- "minimax-m2.7-high-speed": "minimax-m2.7-highspeed",
1592
- "minimax-m2-7": "minimax-m2.7",
1593
- "minimax-m2-7-highspeed": "minimax-m2.7-highspeed",
1594
- "minimax-m2-7-high-speed": "minimax-m2.7-highspeed",
1595
- "gpt-6": "gpt-6-astra",
1596
- "gpt-6-astra": "gpt-6-astra"
1597
- };
1598
- /**
1599
- * 模型 id 归一化:小写、去括号附注(如 `gpt5.6 luna(go)` 只看主体)、再去所有
1600
- * 非字母数字分隔符(空格 / 横杠 / 点 / 下划线)。用于日志里的模型 id 与计费
1601
- * 目录键做宽松匹配,提升「大小写/分隔符差异导致未收录」的识别率。
1602
- * @param id - 原始模型 id(日志或目录键)。
1603
- * @returns 归一化键(字母数字小写串)。
1604
- */
1605
- function canonModelId(id) {
1606
- return String(id).toLowerCase().replace(/\([^)]*\)/g, "").replace(/[^a-z0-9]+/g, "");
1607
- }
1608
- /**
1609
- * 目录常量键的归一化索引:归一化键 → 真实计费键。只索引静态来源(内置目录、
1610
- * 别名表、dsh-spend 兜底键);models.dev 补充条目是运行时注入,单独实时匹配。
1611
- */
1612
- const CATALOG_CANON_INDEX = (() => {
1613
- const map = /* @__PURE__ */ new Map();
1614
- const add = (candidate, target) => {
1615
- const canon = canonModelId(candidate);
1616
- if (canon !== "" && !map.has(canon)) map.set(canon, target);
1617
- };
1618
- for (const entry of MODEL_CATALOG) add(entry.key, entry.key);
1619
- for (const [alias, key] of Object.entries(MODEL_KEY_ALIASES)) add(alias, key);
1620
- for (const rate of FALLBACK_RATES) add(rate.key, rate.key);
1621
- return map;
1622
- })();
1623
- /**
1624
- * 解析真实日志模型 id → 计费目录键。先精确别名映射(既有行为);未命中时做
1625
- * 归一化匹配(忽略大小写/分隔符/括号附注),命中内置目录 / 别名目标 / 兜底键 /
1626
- * models.dev 补充键即返回其真实键;完全未知时保持原样(回退 other,不计费)。
1627
- * 供聚合层折叠与客户端渲染共用,两侧一致。
1628
- * @param id - 真实模型 id(日志里出现的形式)。
1629
- * @returns 计费目录键。
1630
- */
1631
- /**
1632
- * 由一个未知模型 id 派生候选 id(仅当直接查全部未命中时才尝试):
1633
- * - 剥离组织前缀(`deepseek/deepseek-v4-flash` → `deepseek-v4-flash`);
1634
- * - 剥离尾部纯数字段(`deepseek-v4-flash-202605` / `-0731` → `deepseek-v4-flash`,
1635
- * 覆盖 TokenHub / 官方按日期滚动的快照 id);
1636
- * - 两者组合派生。目录键本身(如 `mistral-large-2512`、`command-a-03-2025`)
1637
- * 在直接查就已命中,永不进入派生分支,不受剥段影响。
1638
- */
1639
- function derivedKeyCandidates(id) {
1640
- const out = [];
1641
- const push = (value) => {
1642
- if (value !== "" && !out.includes(value)) out.push(value);
1643
- };
1644
- const stripTrailingDigits = (value) => {
1645
- let base = value;
1646
- for (;;) {
1647
- const next = base.replace(/[-_]\d{3,}$/u, "");
1648
- if (next === base || next === "") return;
1649
- base = next;
1650
- push(base);
1651
- }
1652
- };
1653
- const slash = id.lastIndexOf("/");
1654
- if (slash > 0 && slash < id.length - 1) {
1655
- const bare = id.slice(slash + 1);
1656
- push(bare);
1657
- stripTrailingDigits(bare);
1658
- }
1659
- stripTrailingDigits(id);
1660
- return out;
1661
- }
1662
- /** 查一个候选 id(用户别名 → 内置别名 → 目录归一化 → models.dev 补充);未命中返回 undefined。 */
1663
- function lookupCandidate(candidate) {
1664
- const alias = userModelAliases?.[candidate] ?? MODEL_KEY_ALIASES[candidate];
1665
- if (alias !== void 0) return alias;
1666
- const canon = canonModelId(candidate);
1667
- if (canon === "") return void 0;
1668
- const hit = CATALOG_CANON_INDEX.get(canon);
1669
- if (hit !== void 0) return hit;
1670
- return (liveExtraModels ?? []).find((item) => canonModelId(item.key) === canon)?.key;
1671
- }
1672
- function resolveCatalogKey(id) {
1673
- const exact = userModelAliases?.[id] ?? MODEL_KEY_ALIASES[id] ?? id;
1674
- if (exact === id) {
1675
- const canon = canonModelId(id);
1676
- if (canon !== "") {
1677
- const hit = CATALOG_CANON_INDEX.get(canon);
1678
- if (hit !== void 0) return hit;
1679
- const extraHit = (liveExtraModels ?? []).find((item) => canonModelId(item.key) === canon);
1680
- if (extraHit !== void 0) return extraHit.key;
1681
- }
1682
- for (const candidate of derivedKeyCandidates(id)) {
1683
- const hit = lookupCandidate(candidate);
1684
- if (hit !== void 0) return hit;
1685
- }
1686
- }
1687
- return exact;
1688
- }
1689
- /** 取一个计费键的实时单价(实时覆盖 > dsh-spend 官方价兜底)。 */
1690
- function livePriceOf(key) {
1691
- const resolved = resolveCatalogKey(key);
1692
- const live = livePrices?.[resolved];
1693
- if (live !== void 0) return live;
1694
- const fallback = FALLBACK_RATES.find((rate) => rate.key.toLowerCase() === resolved.toLowerCase());
1695
- if (fallback === void 0) return void 0;
1696
- return {
1697
- input: fallback.input,
1698
- cacheHit: fallback.cacheHit,
1699
- output: fallback.output
1700
- };
1701
- }
1702
- /** Lookup a model by its stats key; falls back to the generic `other` entry. */
1703
- function modelOf(key) {
1704
- const resolved = resolveCatalogKey(key);
1705
- const found = MODEL_CATALOG.find((entry) => entry.key === resolved);
1706
- const extra = liveExtraModels?.find((item) => item.key === resolved);
1707
- const base = found ?? (extra !== void 0 ? extraEntryOf(extra) : (() => {
1708
- const fallback = MODEL_CATALOG.at(-1);
1709
- if (fallback !== void 0) return fallback;
1710
- throw new Error("MODEL_CATALOG must not be empty");
1711
- })());
1712
- const live = livePriceOf(resolved);
1713
- if (live === void 0) return base;
1714
- return {
1715
- ...base,
1716
- price: {
1717
- currency: "USD",
1718
- input: live.input,
1719
- cacheHit: live.cacheHit,
1720
- output: live.output
1721
- }
1722
- };
1723
- }
1724
- /** models.dev 补充条目转为目录条目:USD 直价(走汇率换算),无峰谷分档。 */
1725
- function extraEntryOf(extra) {
1726
- return {
1727
- key: extra.key,
1728
- name: extra.name,
1729
- provider: extra.provider,
1730
- colorVar: "dsw-static-neutral-400",
1731
- price: {
1732
- currency: "USD",
1733
- input: extra.price.input,
1734
- cacheHit: extra.price.cacheHit,
1735
- output: extra.price.output
1736
- }
1737
- };
1738
- }
1739
- /**
1740
- * 模型是否可计价:内置目录、models.dev 补充、或 dsh-spend 官方价兜底命中。
1741
- * 聚合层的计价闸门(目录外模型不产生费用,避免兜底档误估)。
1742
- */
1743
- function isPriced(key) {
1744
- const resolved = resolveCatalogKey(key);
1745
- if (MODEL_CATALOG.some((entry) => entry.key === resolved)) return true;
1746
- if ((liveExtraModels ?? []).some((item) => item.key === resolved)) return true;
1747
- return FALLBACK_RATES.some((rate) => rate.key.toLowerCase() === resolved.toLowerCase());
1748
- }
1749
- /**
1750
- * 促销在 nowMs 是否生效:factor 必须落在 (0,1) 区间,截止时刻及之后视为过期;
1751
- * endsAtMs 缺省表示长期活动,在 factor 合法期间持续生效。
1752
- * 导出供测试:纯函数。
1753
- * @param promo - 待判定的促销窗口。
1754
- * @param nowMs - 判定时刻(epoch ms)。
1755
- */
1756
- function isPromoActive(promo, nowMs) {
1757
- const expired = promo.endsAtMs !== void 0 && nowMs >= promo.endsAtMs;
1758
- return Number.isFinite(nowMs) && !expired && promo.factor > 0 && promo.factor < 1;
1759
- }
1760
- /**
1761
- * 把限时促销折入条目单价:生效期内返回 price 主档与 offPeak 逐档乘折扣系数的
1762
- * 副本(某档在 promo.factors 有合法覆盖时用覆盖值,否则用 promo.factor),
1763
- * 其余字段原样保留;不在促销期(过期/未开始/factor 非法)原样返回。
1764
- * 幂等由调用方保证——计价与费率表显示各自只折一次,勿对已折价副本重复应用。
1765
- * @param entry - 目录条目(price 保持刊例价口径)。
1766
- * @param nowMs - 判定时刻(epoch ms)。
1767
- */
1768
- function applyPromo(entry, nowMs) {
1769
- const { promo } = entry;
1770
- if (promo === void 0 || !isPromoActive(promo, nowMs)) return entry;
1771
- const factorOf = (field) => promo.factors?.[field] ?? promo.factor;
1772
- const scaled = (band) => ({
1773
- input: band.input * factorOf("input"),
1774
- cacheHit: band.cacheHit * factorOf("cacheHit"),
1775
- ...band.cacheMiss !== void 0 ? { cacheMiss: band.cacheMiss * factorOf("cacheMiss") } : {},
1776
- output: band.output * factorOf("output")
1777
- });
1778
- return {
1779
- ...entry,
1795
+ colorVar: "dsw-static-red-300",
1780
1796
  price: {
1781
- ...scaled(entry.price),
1782
- currency: entry.price.currency,
1783
- ...entry.price.offPeak !== void 0 ? { offPeak: scaled(entry.price.offPeak) } : {}
1797
+ currency: "CNY",
1798
+ input: .6,
1799
+ cacheHit: .12,
1800
+ output: 3.6
1784
1801
  }
1785
- };
1786
- }
1787
- /**
1788
- * Price one band's token usage in CNY. The stats `input` field is the TOTAL
1789
- * prompt tokens (cacheHit + cacheMiss), so billing splits it: the cache-hit
1790
- * share prices at the hit rate and the remaining share at the miss rate.
1791
- * Providers that report only disjoint buckets carry `cacheMiss` explicitly;
1792
- * otherwise the miss share is derived as `input - cacheHit`. Only USD-priced
1793
- * bands go through the exchange rate.
1794
- */
1795
- function priceBandCost(band, buckets, currency) {
1796
- const miss = buckets.cacheMiss > 0 ? buckets.cacheMiss : Math.max(0, buckets.input - buckets.cacheHit);
1797
- const hit = Math.min(buckets.cacheHit, buckets.input);
1798
- const raw = (miss * (band.cacheMiss ?? band.input) + hit * band.cacheHit + buckets.output * band.output) / 1e6;
1799
- return currency === "USD" ? raw * currentRate() : raw;
1800
- }
1801
- /**
1802
- * Estimate the CNY cost of one model's token usage, mixing the peak and
1803
- * off-peak bands by the given peak share (flat-priced models cost the same in
1804
- * both bands).
1805
- *
1806
- * 计费维度是「缓存命中价 × 时段价」的交叉:每个时段档内部分别按缓存命中
1807
- * 价(cacheHit)与未命中价(input/cacheMiss)计价,两个时段档再按
1808
- * peakShare 混合。时段定义以北京时间为准(如 DeepSeek V4 高峰
1809
- * 09:00-12:00 / 14:00-18:00)。因聚合只有按日 token 量、没有请求级时间戳,
1810
- * 时段只能按比例估算,而非逐请求判定。
1811
- * @param entry - the catalog entry whose prices apply.
1812
- * @param buckets - token usage counts.
1813
- * @param peakShare - share of traffic in the peak band (0..1); defaults to {@link DEFAULT_PEAK_SHARE}.
1814
- * @returns the estimated cost in CNY.
1815
- */
1816
- function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE, nowMs = Date.now()) {
1817
- const priced = applyPromo(entry, nowMs);
1818
- const peak = priceBandCost(priced.price, buckets, priced.price.currency);
1819
- const off = priced.price.offPeak === void 0 ? peak : priceBandCost(priced.price.offPeak, buckets, priced.price.currency);
1820
- return peak * peakShare + off * (1 - peakShare);
1821
- }
1822
- /**
1823
- * v1 峰谷档判定(峰谷开闸起、周末全谷分界止):不豁免周末——该时段官方
1824
- * 高峰时段为每天 9-12 / 14-18(周六日同样计峰)。仅用于历史事件计费;
1825
- * 「当前时刻」的档位(提醒/时段条/费率展示)一律走 {@link tierAt} 现行规则。
1826
- */
1827
- function tariffV1At(timeMs) {
1828
- return isPeakHour((new Date(timeMs).getUTCHours() + 8) % 24) ? "peak" : "offPeak";
1829
- }
1830
- /**
1831
- * 按调用时刻精确判定高峰/空闲档并计价(P0-1:替代固定比例混合)。时刻未知
1832
- * (null/NaN,理论不发生在真实事件流)时回退 {@link DEFAULT_PEAK_SHARE} 混合,
1833
- * 保持旧语义不低估。平档模型(无 offPeak)两个时段同价。限时促销与峰谷档
1834
- * 同口径:按事件时刻判定该笔流量当时享受的单价。
1835
- *
1836
- * 历史正确性(按变更节点分段适用规则,不统一套现行价重算历史):
1837
- * - 早于 {@link PEAK_ERA_START_MS} 的事件按当时官方基础价
1838
- * ({@link LEGACY_DEEPSEEK_BANDS})计费;
1839
- * - 峰谷开闸至 {@link WEEKEND_OFFPEAK_START_MS} 之间按 v1 规则(周末不豁免,
1840
- * 周六日 9-12 / 14-18 计峰);
1841
- * - 周末全谷分界起按现行规则({@link tierAt},周六日全天低谷)。
1842
- * @param entry - the catalog entry whose prices apply.
1843
- * @param buckets - token usage counts.
1844
- * @param timeMs - the call's wall-clock time (epoch ms); null falls back to the peak-share mix.
1845
- * @param peakShare - fallback mix used only when `timeMs` is missing.
1846
- * @returns the estimated cost in CNY(USD 计价模型已按当前汇率折算)。
1847
- */
1848
- function computeCostAt(entry, buckets, timeMs, peakShare = DEFAULT_PEAK_SHARE) {
1849
- if (timeMs === null || timeMs === void 0 || !Number.isFinite(timeMs)) return computeCost(entry, buckets, peakShare);
1850
- const priced = applyPromo(entry, timeMs);
1851
- const legacy = timeMs < PEAK_ERA_START_MS && entry.userPriced !== true ? LEGACY_DEEPSEEK_BANDS[entry.key] : void 0;
1852
- if (legacy !== void 0) return priceBandCost(legacy, buckets, "CNY");
1853
- const repriced = timeMs < FLASH_REPRICE_MS && entry.userPriced !== true ? FLASH_REPRICED_OFFPEAK[entry.key] : void 0;
1854
- if (repriced !== void 0) return priceBandCost((timeMs < WEEKEND_OFFPEAK_START_MS ? tariffV1At(timeMs) : tierAt(timeMs)) === "peak" ? {
1855
- input: repriced.input * 2,
1856
- cacheHit: repriced.cacheHit * 2,
1857
- output: repriced.output * 2
1858
- } : repriced, buckets, "CNY");
1859
- if (priced.price.offPeak === void 0) return priceBandCost(priced.price, buckets, priced.price.currency);
1860
- return priceBandCost((timeMs < WEEKEND_OFFPEAK_START_MS ? tariffV1At(timeMs) : tierAt(timeMs)) === "peak" ? priced.price : priced.price.offPeak, buckets, priced.price.currency);
1861
- }
1802
+ },
1803
+ {
1804
+ key: "other",
1805
+ name: "其他模型",
1806
+ provider: "Custom",
1807
+ colorVar: "dsw-static-neutral-bluish-500",
1808
+ price: {
1809
+ currency: "CNY",
1810
+ input: 0,
1811
+ cacheHit: 0,
1812
+ cacheMiss: 0,
1813
+ output: 0
1814
+ }
1815
+ }
1816
+ ];
1862
1817
  /**
1863
- * Format an amount with adaptive precision and the given currency symbol.
1864
- * @param amount - the amount (CNY by default; pass `usd` for dollar display).
1865
- * @param currency - display currency; default `cny`.
1818
+ * 真实 provider model id → 计费目录键(`MODEL_CATALOG[].key`)的映射。未知 id
1819
+ * 原样保留并落回 `other`(未知模型不估算费用)。聚合层(aggregate.ts)在折叠时
1820
+ * 用同一张表把日志里的 model id 归并为目录键,客户端渲染(`modelOf`)也按它
1821
+ * 解析,两侧共用一份映射,避免同一模型两侧不一致导致「未收录」。
1866
1822
  */
1867
- function formatMoney(amount, currency = "cny") {
1868
- const value = Number(amount);
1869
- if (!Number.isFinite(value)) return currency === "cny" ? "¥0" : "$0";
1870
- const symbol = currency === "cny" ? "¥" : "$";
1871
- if (value <= 0) return `${symbol}0`;
1872
- if (value >= 1e3) return `${symbol}${value.toFixed(0)}`;
1873
- if (value >= 10) return `${symbol}${value.toFixed(1)}`;
1874
- if (value >= .1) return `${symbol}${value.toFixed(2)}`;
1875
- return `${symbol}${value.toFixed(3)}`;
1876
- }
1877
- /** Format a large token count with B/M/K suffix. */
1878
- function formatTokens(value) {
1879
- if (value >= 1e9) return `${(value / 1e9).toFixed(2)}B`;
1880
- if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
1881
- if (value >= 1e3) return `${(value / 1e3).toFixed(0)}K`;
1882
- return String(value);
1883
- }
1823
+ const BUILTIN_MODEL_KEY_ALIASES = {
1824
+ "deepseek-v4-flash": "flash",
1825
+ "deepseek-v4-flash-vision-exp": "flash-vision-exp",
1826
+ "deepseek-v4.1-flash": "flash",
1827
+ "deepseek-v4.1-flash-expires-on-0910": "flash",
1828
+ "deepseek-flash": "flash",
1829
+ "deepseek-v4-pro": "pro",
1830
+ "glm-5.2": "glm",
1831
+ "glm-4.5-air": "glm-4.5-air",
1832
+ "glm-4.5air": "glm-4.5-air",
1833
+ "glm-4.7": "glm-4.7",
1834
+ "glm-5-turbo": "glm-5-turbo",
1835
+ "glm-5.1": "glm-5.1",
1836
+ "glm-5.3-flash": "glm-5.3-flash",
1837
+ "glm-5.3-flashx": "glm-5.3-flashx",
1838
+ "glm-5.3-flash-x": "glm-5.3-flashx",
1839
+ "glm-5v-turbo": "glm-5v-turbo",
1840
+ "glm-5v.1": "glm-5v-turbo",
1841
+ "claude-opus-4-6": "claude-opus-4-6",
1842
+ "claude-opus-4.6": "claude-opus-4-6",
1843
+ "claude-sonnet-4-6": "claude-sonnet-4-6",
1844
+ "claude-sonnet-4.6": "claude-sonnet-4-6",
1845
+ "claude-haiku-4-5": "claude-haiku-4-5",
1846
+ "claude-haiku-4.5": "claude-haiku-4-5",
1847
+ "claude-opus-5": "claude-opus-5",
1848
+ "claude-sonnet-5": "claude-sonnet-5",
1849
+ "mistral-large-2512": "mistral-large-2512",
1850
+ "mistral-large-3": "mistral-large-2512",
1851
+ "mistral-small-2603": "mistral-small-2603",
1852
+ "mistral-small-4": "mistral-small-2603",
1853
+ "ministral-8b-latest": "ministral-8b-latest",
1854
+ "ministral-8b": "ministral-8b-latest",
1855
+ "command-a-03-2025": "command-a-03-2025",
1856
+ "command-a": "command-a-03-2025",
1857
+ "command-r-08-2024": "command-r-08-2024",
1858
+ "command-r": "command-r-08-2024",
1859
+ "hy3": "hunyuan",
1860
+ "hy4-preview": "hunyuan-hy4-preview",
1861
+ "hy4": "hunyuan-hy4-preview",
1862
+ "hunyuan-hy4-preview": "hunyuan-hy4-preview",
1863
+ "hunyuan-hy4": "hunyuan-hy4-preview",
1864
+ "longcat-2.0": "longcat-2.0",
1865
+ "longcat-2": "longcat-2.0",
1866
+ "minicpm-v-4.5": "minicpm-v-4.5",
1867
+ "minicpm-v-4.5-thinking": "minicpm-v-4.5",
1868
+ "ernie-4.5": "ernie-4.5",
1869
+ "ernie-4.5-300b": "ernie-4.5",
1870
+ "dots-3-note-preview": "dots-3-note-preview",
1871
+ "dots-3-note": "dots-3-note-preview",
1872
+ "dots3-note-preview": "dots-3-note-preview",
1873
+ "rednote-dots3": "dots-3-note-preview",
1874
+ "doubao-seed-evolving": "doubao-seed-evolving",
1875
+ "doubao-seed-evolve": "doubao-seed-evolving",
1876
+ "doubao-seed-2.1-pro": "doubao-seed-2.1-pro",
1877
+ "doubao-seed-2.1-pro-290000": "doubao-seed-2.1-pro",
1878
+ "doubao-seed-2.1-turbo": "doubao-seed-2.1-turbo",
1879
+ "doubao-seed-2-1-turbo": "doubao-seed-2.1-turbo",
1880
+ "qwen3.8-max": "qwen-3.8-max",
1881
+ "qwen3.8-flash": "qwen-3.8-flash",
1882
+ "qwen3.7-max": "qwen-max",
1883
+ "qwen3.6-max": "qwen3.6-max",
1884
+ "qwen3.6-max-preview": "qwen3.6-max",
1885
+ "qwen3-coder-plus": "qwen3-coder-plus",
1886
+ "qwen3-coder": "qwen3-coder",
1887
+ "qwen3-coder-480b": "qwen3-coder",
1888
+ "glm-4.5-x": "glm-4.5-x",
1889
+ "glm-4.5x": "glm-4.5-x",
1890
+ "glm-5.2-fast": "glm-5.2-fast",
1891
+ "glm-5.2f": "glm-5.2-fast",
1892
+ "kimi-k3-fast": "kimi-k3-fast",
1893
+ "kimi-k3f": "kimi-k3-fast",
1894
+ "kimi-for-coding": "kimi-k2.8-preview",
1895
+ "kimi-k2.7-code-fast": "kimi-k2.7-code-fast",
1896
+ "kimi-k2.7-code-f": "kimi-k2.7-code-fast",
1897
+ "kimi-k2.6-fast": "kimi-k2.6-fast",
1898
+ "kimi-k2.6-turbo": "kimi-k2.6-turbo",
1899
+ "kimi-k2-thinking-turbo": "kimi-k2-thinking-turbo",
1900
+ "doubao-seed-2.0-code": "doubao-seed-2.0-code",
1901
+ "doubao-seed-2-0-code": "doubao-seed-2.0-code",
1902
+ "doubao-seed-2.0-lite": "doubao-seed-2.0-lite",
1903
+ "doubao-seed-2-0-lite": "doubao-seed-2.0-lite",
1904
+ "qwen-max": "qwen-max",
1905
+ "hunyuan-t1": "hunyuan-t1",
1906
+ "step-3.7-flash": "step",
1907
+ "seed-2.0-mini": "doubao-mini",
1908
+ "k3": "kimi-k3",
1909
+ "kimi-k3": "kimi-k3",
1910
+ "kimi-k2.8-preview": "kimi-k2.8-preview",
1911
+ "kimi-k2.8": "kimi-k2.8-preview",
1912
+ "kimi-k2-8-preview": "kimi-k2.8-preview",
1913
+ "kimi-k2-8": "kimi-k2.8-preview",
1914
+ "k2.8-preview": "kimi-k2.8-preview",
1915
+ "k2.8": "kimi-k2.8-preview",
1916
+ "minimax-m1": "minimax",
1917
+ "minimax-m2": "minimax",
1918
+ "minimax-m3": "minimax",
1919
+ "minimax-m2.7": "minimax-m2.7",
1920
+ "minimax-m2.7-highspeed": "minimax-m2.7-highspeed",
1921
+ "minimax-m2.7-high-speed": "minimax-m2.7-highspeed",
1922
+ "minimax-m2-7": "minimax-m2.7",
1923
+ "minimax-m2-7-highspeed": "minimax-m2.7-highspeed",
1924
+ "minimax-m2-7-high-speed": "minimax-m2.7-highspeed",
1925
+ "gpt-6": "gpt-6-astra",
1926
+ "gpt-6-astra": "gpt-6-astra"
1927
+ };
1884
1928
  //#endregion
1885
1929
  //#region lib/types/aggregate.js
1886
1930
  /**
@@ -4577,6 +4621,40 @@ async function fetchJson(url) {
4577
4621
  }
4578
4622
  }
4579
4623
  /** Latest USD → CNY rate from the first working source, or undefined when none respond. */
4624
+ /**
4625
+ * EUR → CNY 汇率源。与 USD 同一套降级约定:腾讯行情优先(免 key、国内可达),
4626
+ * open.er-api.com 兑底;全部失败由调用方降级内置汇率,不阻塞其他价格拉取。
4627
+ */
4628
+ const EUR_RATE_SOURCES = [{
4629
+ url: "https://qt.gtimg.cn/q=whEURCNY",
4630
+ parse: (text) => {
4631
+ const price = /"([^"]*)"/.exec(text)?.[1]?.split("~")[3];
4632
+ return price !== void 0 && price !== "" ? Number(price) : void 0;
4633
+ }
4634
+ }, {
4635
+ url: "https://open.er-api.com/v6/latest/EUR",
4636
+ parse: (text) => {
4637
+ try {
4638
+ const cny = JSON.parse(text).rates?.CNY;
4639
+ return typeof cny === "number" && Number.isFinite(cny) && cny > 0 ? cny : void 0;
4640
+ } catch {
4641
+ return;
4642
+ }
4643
+ }
4644
+ }];
4645
+ /**
4646
+ * 按顺序尝试汇率源,返回第一个合法正数。
4647
+ * @param sources - 待尝试的汇率源。
4648
+ * @returns 汇率;全部失败时 undefined。
4649
+ */
4650
+ async function fetchRateFrom(sources) {
4651
+ for (const source of sources) {
4652
+ const text = await fetchText(source.url);
4653
+ if (text === null) continue;
4654
+ const value = source.parse(text);
4655
+ if (value !== void 0 && Number.isFinite(value) && value > 0) return value;
4656
+ }
4657
+ }
4580
4658
  async function fetchRate() {
4581
4659
  for (const source of RATE_SOURCES) {
4582
4660
  const text = await fetchText(source.url);
@@ -4668,7 +4746,7 @@ const FIRST_PARTY_PRICE_SOURCES = new Set([
4668
4746
  */
4669
4747
  function buildExtraModels(data) {
4670
4748
  if (data === null || typeof data !== "object") return [];
4671
- const catalogKeys = new Set([...MODEL_CATALOG.map((entry) => entry.key.toLowerCase()), ...Object.keys(MODEL_KEY_ALIASES).map((key) => MODEL_KEY_ALIASES[key]?.toLowerCase() ?? key.toLowerCase())]);
4749
+ const catalogKeys = new Set([...BUILTIN_MODEL_CATALOG.map((entry) => entry.key.toLowerCase()), ...Object.keys(BUILTIN_MODEL_KEY_ALIASES).map((key) => BUILTIN_MODEL_KEY_ALIASES[key]?.toLowerCase() ?? key.toLowerCase())]);
4672
4750
  const offers = /* @__PURE__ */ new Map();
4673
4751
  const familyVendorVotes = /* @__PURE__ */ new Map();
4674
4752
  for (const [providerId, providerDoc] of Object.entries(data)) {
@@ -4679,7 +4757,7 @@ function buildExtraModels(data) {
4679
4757
  const providerName = providerDoc.name;
4680
4758
  const providerDisplay = MODELS_DEV_PROVIDERS[providerKey] ?? (typeof providerName === "string" && providerName !== "" ? providerName : providerId);
4681
4759
  for (const [modelId, modelDoc] of Object.entries(models)) {
4682
- const catalogKey = (MODEL_KEY_ALIASES[modelId] ?? modelId).toLowerCase();
4760
+ const catalogKey = (BUILTIN_MODEL_KEY_ALIASES[modelId] ?? modelId).toLowerCase();
4683
4761
  if (catalogKeys.has(catalogKey)) continue;
4684
4762
  const key = catalogKey;
4685
4763
  if (modelDoc === null || typeof modelDoc !== "object") continue;
@@ -4777,17 +4855,19 @@ function buildExtraModels(data) {
4777
4855
  * @returns the live pricing snapshot (builtin when everything failed).
4778
4856
  */
4779
4857
  async function fetchLivePricing() {
4780
- const [rate, models, modelsDev] = await Promise.all([
4858
+ const [rate, rateEur, models, modelsDev] = await Promise.all([
4781
4859
  fetchRate(),
4860
+ fetchRateFrom(EUR_RATE_SOURCES),
4782
4861
  fetchRouterModels(),
4783
4862
  fetchJson(MODELS_DEV_URL)
4784
4863
  ]);
4785
4864
  const prices = models === void 0 ? void 0 : buildPrices(models);
4786
4865
  const extraModels = modelsDev === null ? void 0 : buildExtraModels(modelsDev);
4787
- if (rate === void 0 && prices === void 0 && (extraModels === void 0 || extraModels.length === 0)) return { source: "builtin" };
4866
+ if (rate === void 0 && rateEur === void 0 && prices === void 0 && (extraModels === void 0 || extraModels.length === 0)) return { source: "builtin" };
4788
4867
  return {
4789
4868
  source: "live",
4790
4869
  ...rate !== void 0 ? { rate } : {},
4870
+ ...rateEur !== void 0 ? { rateEur } : {},
4791
4871
  ...prices !== void 0 ? { prices } : {},
4792
4872
  ...extraModels !== void 0 && extraModels.length > 0 ? { extraModels } : {}
4793
4873
  };
@@ -6375,6 +6455,7 @@ function apply(ctx, config = {}) {
6375
6455
  }).catch(() => {});
6376
6456
  };
6377
6457
  const workspaceTitleResolver = buildWorkspaceTitleResolver(ctx);
6458
+ applyBuiltinCatalog(BUILTIN_MODEL_CATALOG, BUILTIN_MODEL_KEY_ALIASES);
6378
6459
  applyUserModelAliases(config.modelKeyAliases);
6379
6460
  const aggregator = createUsageAggregator(adaptSessionPersistence(ctx.sessionPersistence), {
6380
6461
  ...config.subscriptionProviders === void 0 ? {} : { subscriptionProviders: config.subscriptionProviders },
@@ -6651,6 +6732,17 @@ function apply(ctx, config = {}) {
6651
6732
  live = await fetchLivePricing();
6652
6733
  pricingSyncedAt = Date.now();
6653
6734
  applyLivePricing(live);
6735
+ pricingBodyCache = void 0;
6736
+ };
6737
+ let pricingBodyCache;
6738
+ const pricingBody = () => {
6739
+ if (pricingBodyCache === void 0) pricingBodyCache = JSON.stringify({
6740
+ ...live,
6741
+ syncedAt: pricingSyncedAt,
6742
+ catalog: BUILTIN_MODEL_CATALOG,
6743
+ aliases: BUILTIN_MODEL_KEY_ALIASES
6744
+ });
6745
+ return pricingBodyCache;
6654
6746
  };
6655
6747
  pricingReady = refreshPricing();
6656
6748
  ctx.effect(() => {
@@ -6667,10 +6759,7 @@ function apply(ctx, config = {}) {
6667
6759
  handler: async (req, res) => {
6668
6760
  if (!guardLoopback(req, res, trustedHosts)) return;
6669
6761
  res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
6670
- res.end(JSON.stringify({
6671
- ...live,
6672
- syncedAt: pricingSyncedAt
6673
- }));
6762
+ res.end(pricingBody());
6674
6763
  }
6675
6764
  }), "usage-billing: pricing route");
6676
6765
  ctx.effect(() => ctx.webServer.register({
@@ -6695,10 +6784,7 @@ function apply(ctx, config = {}) {
6695
6784
  }
6696
6785
  await refreshPricing();
6697
6786
  res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
6698
- res.end(JSON.stringify({
6699
- ...live,
6700
- syncedAt: pricingSyncedAt
6701
- }));
6787
+ res.end(pricingBody());
6702
6788
  }
6703
6789
  }), "usage-billing: pricing refresh route");
6704
6790
  let balanceCache = {