@kenz1117/dsh-ui-usage-billing 1.4.3 → 1.4.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/lib/client.js +5 -5
- package/lib/index.js +665 -616
- package/lib/types/aggregate.d.ts +3 -2
- package/lib/types/builtin-catalog.d.ts +15 -0
- package/lib/types/client/TrendChart.d.ts +2 -1
- package/lib/types/client/heatmap.d.ts +12 -6
- package/lib/types/client/label-display.d.ts +19 -0
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/pricing.d.ts +9 -6
- package/lib/types/client/round-chart.d.ts +1 -1
- package/package.json +1 -1
- package/lib/types/client/vendor-logos.d.ts +0 -12
package/lib/index.js
CHANGED
|
@@ -198,7 +198,7 @@ let liveExtraModels;
|
|
|
198
198
|
/**
|
|
199
199
|
* 用户自定义模型别名(插件配置 `modelKeyAliases`,聚合启动时注入):真实日志
|
|
200
200
|
* model id → 计费目录键。优先级高于内置别名表——目录外的新模型无需等发版,
|
|
201
|
-
*
|
|
201
|
+
* 配置一条别名即完成识别与计价(键必须是内置目录的既有 key)。
|
|
202
202
|
*/
|
|
203
203
|
let userModelAliases;
|
|
204
204
|
/**
|
|
@@ -332,208 +332,537 @@ function beijingMillisOfDay(timeMs) {
|
|
|
332
332
|
return ((timeMs + 8 * 36e5) % 864e5 + 864e5) % 864e5;
|
|
333
333
|
}
|
|
334
334
|
/**
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
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.
|
|
335
|
+
* 内置目录与别名表的运行时注入态:数据本体在 src/builtin-catalog.ts(仅随 node
|
|
336
|
+
* 半构建发布)。宿主半区在插件 activate 时同步注入;客户端在 /api/billing/pricing
|
|
337
|
+
* 首次响应时注入。未注入时目录为空——计价消费方走 UNSEEDED_OTHER 兜底(不产生
|
|
338
|
+
* 费用),费率表渲染加载态,不视为错误。
|
|
348
339
|
*/
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
340
|
+
let builtinCatalog = [];
|
|
341
|
+
let builtinAliases = {};
|
|
342
|
+
/**
|
|
343
|
+
* 注入内置目录与别名表(两侧各调用一次:宿主 activate、客户端首个 pricing 响应)。
|
|
344
|
+
* 重复注入整体替换并重建归一化索引。
|
|
345
|
+
* @param catalog - 内置目录条目(BUILTIN_MODEL_CATALOG 全量)。
|
|
346
|
+
* @param aliases - 内置别名表(BUILTIN_MODEL_KEY_ALIASES 全量)。
|
|
347
|
+
*/
|
|
348
|
+
function applyBuiltinCatalog(catalog, aliases) {
|
|
349
|
+
builtinCatalog = catalog;
|
|
350
|
+
builtinAliases = aliases;
|
|
351
|
+
canonIndexCache = void 0;
|
|
352
|
+
}
|
|
353
|
+
/** 内置目录当前快照:未注入为空数组(client 首帧前渲染加载态)。 */
|
|
354
|
+
function modelCatalog() {
|
|
355
|
+
return builtinCatalog;
|
|
356
|
+
}
|
|
357
|
+
/** 内置别名表当前快照:未注入为空对象。 */
|
|
358
|
+
function modelKeyAliases() {
|
|
359
|
+
return builtinAliases;
|
|
360
|
+
}
|
|
361
|
+
/** 未注入时的目录兜底条目:与内置 other 条目同价(零价,不计费)。 */
|
|
362
|
+
const UNSEEDED_OTHER = {
|
|
363
|
+
key: "other",
|
|
364
|
+
name: "其他模型",
|
|
365
|
+
provider: "Custom",
|
|
366
|
+
colorVar: "dsw-static-neutral-bluish-500",
|
|
367
|
+
price: {
|
|
368
|
+
currency: "CNY",
|
|
369
|
+
input: 0,
|
|
370
|
+
cacheHit: 0,
|
|
371
|
+
cacheMiss: 0,
|
|
372
|
+
output: 0
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
/**
|
|
376
|
+
* 模型 id 归一化:小写、去括号附注(如 `gpt5.6 luna(go)` 只看主体)、再去所有
|
|
377
|
+
* 非字母数字分隔符(空格 / 横杠 / 点 / 下划线)。用于日志里的模型 id 与计费
|
|
378
|
+
* 目录键做宽松匹配,提升「大小写/分隔符差异导致未收录」的识别率。
|
|
379
|
+
* @param id - 原始模型 id(日志或目录键)。
|
|
380
|
+
* @returns 归一化键(字母数字小写串)。
|
|
381
|
+
*/
|
|
382
|
+
function canonModelId(id) {
|
|
383
|
+
return String(id).toLowerCase().replace(/\([^)]*\)/g, "").replace(/[^a-z0-9]+/g, "");
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* 目录常量键的归一化索引:归一化键 → 真实计费键。只索引静态来源(内置目录、
|
|
387
|
+
* 别名表、dsh-spend 兜底键);models.dev 补充条目是运行时注入,单独实时匹配。
|
|
388
|
+
* 惰性构建:注入前目录为空,构建无意义;每次 applyBuiltinCatalog 后重建。
|
|
389
|
+
*/
|
|
390
|
+
let canonIndexCache;
|
|
391
|
+
function canonIndex() {
|
|
392
|
+
if (canonIndexCache !== void 0) return canonIndexCache;
|
|
393
|
+
const map = /* @__PURE__ */ new Map();
|
|
394
|
+
const add = (candidate, target) => {
|
|
395
|
+
const canon = canonModelId(candidate);
|
|
396
|
+
if (canon !== "" && !map.has(canon)) map.set(canon, target);
|
|
397
|
+
};
|
|
398
|
+
for (const entry of builtinCatalog) add(entry.key, entry.key);
|
|
399
|
+
for (const [alias, key] of Object.entries(builtinAliases)) add(alias, key);
|
|
400
|
+
for (const rate of FALLBACK_RATES) add(rate.key, rate.key);
|
|
401
|
+
canonIndexCache = map;
|
|
402
|
+
return map;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* 解析真实日志模型 id → 计费目录键。先精确别名映射(既有行为);未命中时做
|
|
406
|
+
* 归一化匹配(忽略大小写/分隔符/括号附注),命中内置目录 / 别名目标 / 兜底键 /
|
|
407
|
+
* models.dev 补充键即返回其真实键;完全未知时保持原样(回退 other,不计费)。
|
|
408
|
+
* 供聚合层折叠与客户端渲染共用,两侧一致。
|
|
409
|
+
* @param id - 真实模型 id(日志里出现的形式)。
|
|
410
|
+
* @returns 计费目录键。
|
|
411
|
+
*/
|
|
412
|
+
/**
|
|
413
|
+
* 由一个未知模型 id 派生候选 id(仅当直接查全部未命中时才尝试):
|
|
414
|
+
* - 剥离组织前缀(`deepseek/deepseek-v4-flash` → `deepseek-v4-flash`);
|
|
415
|
+
* - 剥离尾部纯数字段(`deepseek-v4-flash-202605` / `-0731` → `deepseek-v4-flash`,
|
|
416
|
+
* 覆盖 TokenHub / 官方按日期滚动的快照 id);
|
|
417
|
+
* - 两者组合派生。目录键本身(如 `mistral-large-2512`、`command-a-03-2025`)
|
|
418
|
+
* 在直接查就已命中,永不进入派生分支,不受剥段影响。
|
|
419
|
+
*/
|
|
420
|
+
function derivedKeyCandidates(id) {
|
|
421
|
+
const out = [];
|
|
422
|
+
const push = (value) => {
|
|
423
|
+
if (value !== "" && !out.includes(value)) out.push(value);
|
|
424
|
+
};
|
|
425
|
+
const stripTrailingDigits = (value) => {
|
|
426
|
+
let base = value;
|
|
427
|
+
for (;;) {
|
|
428
|
+
const next = base.replace(/[-_]\d{3,}$/u, "");
|
|
429
|
+
if (next === base || next === "") return;
|
|
430
|
+
base = next;
|
|
431
|
+
push(base);
|
|
418
432
|
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
433
|
+
};
|
|
434
|
+
const slash = id.lastIndexOf("/");
|
|
435
|
+
if (slash > 0 && slash < id.length - 1) {
|
|
436
|
+
const bare = id.slice(slash + 1);
|
|
437
|
+
push(bare);
|
|
438
|
+
stripTrailingDigits(bare);
|
|
439
|
+
}
|
|
440
|
+
stripTrailingDigits(id);
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
/** 查一个候选 id(用户别名 → 内置别名 → 目录归一化 → models.dev 补充);未命中返回 undefined。 */
|
|
444
|
+
function lookupCandidate(candidate) {
|
|
445
|
+
const alias = userModelAliases?.[candidate] ?? modelKeyAliases()[candidate];
|
|
446
|
+
if (alias !== void 0) return alias;
|
|
447
|
+
const canon = canonModelId(candidate);
|
|
448
|
+
if (canon === "") return void 0;
|
|
449
|
+
const hit = canonIndex().get(canon);
|
|
450
|
+
if (hit !== void 0) return hit;
|
|
451
|
+
return (liveExtraModels ?? []).find((item) => canonModelId(item.key) === canon)?.key;
|
|
452
|
+
}
|
|
453
|
+
function resolveCatalogKey(id) {
|
|
454
|
+
const exact = userModelAliases?.[id] ?? modelKeyAliases()[id] ?? id;
|
|
455
|
+
if (exact === id) {
|
|
456
|
+
const canon = canonModelId(id);
|
|
457
|
+
if (canon !== "") {
|
|
458
|
+
const hit = canonIndex().get(canon);
|
|
459
|
+
if (hit !== void 0) return hit;
|
|
460
|
+
const extraHit = (liveExtraModels ?? []).find((item) => canonModelId(item.key) === canon);
|
|
461
|
+
if (extraHit !== void 0) return extraHit.key;
|
|
430
462
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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 折"
|
|
463
|
+
for (const candidate of derivedKeyCandidates(id)) {
|
|
464
|
+
const hit = lookupCandidate(candidate);
|
|
465
|
+
if (hit !== void 0) return hit;
|
|
447
466
|
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
467
|
+
}
|
|
468
|
+
return exact;
|
|
469
|
+
}
|
|
470
|
+
/** 取一个计费键的实时单价(实时覆盖 > dsh-spend 官方价兜底)。 */
|
|
471
|
+
function livePriceOf(key) {
|
|
472
|
+
const resolved = resolveCatalogKey(key);
|
|
473
|
+
const live = livePrices?.[resolved];
|
|
474
|
+
if (live !== void 0) return live;
|
|
475
|
+
const fallback = FALLBACK_RATES.find((rate) => rate.key.toLowerCase() === resolved.toLowerCase());
|
|
476
|
+
if (fallback === void 0) return void 0;
|
|
477
|
+
return {
|
|
478
|
+
input: fallback.input,
|
|
479
|
+
cacheHit: fallback.cacheHit,
|
|
480
|
+
output: fallback.output
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
/** Lookup a model by its stats key; falls back to the generic `other` entry. */
|
|
484
|
+
function modelOf(key) {
|
|
485
|
+
const resolved = resolveCatalogKey(key);
|
|
486
|
+
const found = modelCatalog().find((entry) => entry.key === resolved);
|
|
487
|
+
const extra = liveExtraModels?.find((item) => item.key === resolved);
|
|
488
|
+
const base = found ?? (extra !== void 0 ? extraEntryOf(extra) : modelCatalog().at(-1) ?? UNSEEDED_OTHER);
|
|
489
|
+
const live = livePriceOf(resolved);
|
|
490
|
+
if (live === void 0) return base;
|
|
491
|
+
return {
|
|
492
|
+
...base,
|
|
454
493
|
price: {
|
|
455
|
-
currency: "
|
|
456
|
-
input:
|
|
457
|
-
cacheHit: .
|
|
458
|
-
output:
|
|
494
|
+
currency: "USD",
|
|
495
|
+
input: live.input,
|
|
496
|
+
cacheHit: live.cacheHit,
|
|
497
|
+
output: live.output
|
|
459
498
|
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
/** models.dev 补充条目转为目录条目:USD 直价(走汇率换算),无峰谷分档。 */
|
|
502
|
+
function extraEntryOf(extra) {
|
|
503
|
+
return {
|
|
504
|
+
key: extra.key,
|
|
505
|
+
name: extra.name,
|
|
506
|
+
provider: extra.provider,
|
|
507
|
+
colorVar: "dsw-static-neutral-400",
|
|
466
508
|
price: {
|
|
467
|
-
currency: "
|
|
468
|
-
input:
|
|
469
|
-
cacheHit: .
|
|
470
|
-
output:
|
|
509
|
+
currency: "USD",
|
|
510
|
+
input: extra.price.input,
|
|
511
|
+
cacheHit: extra.price.cacheHit,
|
|
512
|
+
output: extra.price.output
|
|
471
513
|
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* 模型是否可计价:内置目录、models.dev 补充、或 dsh-spend 官方价兜底命中。
|
|
518
|
+
* 聚合层的计价闸门(目录外模型不产生费用,避免兜底档误估)。
|
|
519
|
+
*/
|
|
520
|
+
function isPriced(key) {
|
|
521
|
+
const resolved = resolveCatalogKey(key);
|
|
522
|
+
if (modelCatalog().some((entry) => entry.key === resolved)) return true;
|
|
523
|
+
if ((liveExtraModels ?? []).some((item) => item.key === resolved)) return true;
|
|
524
|
+
return FALLBACK_RATES.some((rate) => rate.key.toLowerCase() === resolved.toLowerCase());
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* 促销在 nowMs 是否生效:factor 必须落在 (0,1) 区间,截止时刻及之后视为过期;
|
|
528
|
+
* endsAtMs 缺省表示长期活动,在 factor 合法期间持续生效。
|
|
529
|
+
* 导出供测试:纯函数。
|
|
530
|
+
* @param promo - 待判定的促销窗口。
|
|
531
|
+
* @param nowMs - 判定时刻(epoch ms)。
|
|
532
|
+
*/
|
|
533
|
+
function isPromoActive(promo, nowMs) {
|
|
534
|
+
const expired = promo.endsAtMs !== void 0 && nowMs >= promo.endsAtMs;
|
|
535
|
+
return Number.isFinite(nowMs) && !expired && promo.factor > 0 && promo.factor < 1;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* 把限时促销折入条目单价:生效期内返回 price 主档与 offPeak 逐档乘折扣系数的
|
|
539
|
+
* 副本(某档在 promo.factors 有合法覆盖时用覆盖值,否则用 promo.factor),
|
|
540
|
+
* 其余字段原样保留;不在促销期(过期/未开始/factor 非法)原样返回。
|
|
541
|
+
* 幂等由调用方保证——计价与费率表显示各自只折一次,勿对已折价副本重复应用。
|
|
542
|
+
* @param entry - 目录条目(price 保持刊例价口径)。
|
|
543
|
+
* @param nowMs - 判定时刻(epoch ms)。
|
|
544
|
+
*/
|
|
545
|
+
function applyPromo(entry, nowMs) {
|
|
546
|
+
const { promo } = entry;
|
|
547
|
+
if (promo === void 0 || !isPromoActive(promo, nowMs)) return entry;
|
|
548
|
+
const factorOf = (field) => promo.factors?.[field] ?? promo.factor;
|
|
549
|
+
const scaled = (band) => ({
|
|
550
|
+
input: band.input * factorOf("input"),
|
|
551
|
+
cacheHit: band.cacheHit * factorOf("cacheHit"),
|
|
552
|
+
...band.cacheMiss !== void 0 ? { cacheMiss: band.cacheMiss * factorOf("cacheMiss") } : {},
|
|
553
|
+
output: band.output * factorOf("output")
|
|
554
|
+
});
|
|
555
|
+
return {
|
|
556
|
+
...entry,
|
|
478
557
|
price: {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
output: 2
|
|
558
|
+
...scaled(entry.price),
|
|
559
|
+
currency: entry.price.currency,
|
|
560
|
+
...entry.price.offPeak !== void 0 ? { offPeak: scaled(entry.price.offPeak) } : {}
|
|
483
561
|
}
|
|
484
|
-
}
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Price one band's token usage in CNY. The stats `input` field is the TOTAL
|
|
566
|
+
* prompt tokens (cacheHit + cacheMiss), so billing splits it: the cache-hit
|
|
567
|
+
* share prices at the hit rate and the remaining share at the miss rate.
|
|
568
|
+
* Providers that report only disjoint buckets carry `cacheMiss` explicitly;
|
|
569
|
+
* otherwise the miss share is derived as `input - cacheHit`. Only USD-priced
|
|
570
|
+
* bands go through the exchange rate.
|
|
571
|
+
*/
|
|
572
|
+
function priceBandCost(band, buckets, currency) {
|
|
573
|
+
const miss = buckets.cacheMiss > 0 ? buckets.cacheMiss : Math.max(0, buckets.input - buckets.cacheHit);
|
|
574
|
+
const hit = Math.min(buckets.cacheHit, buckets.input);
|
|
575
|
+
const raw = (miss * (band.cacheMiss ?? band.input) + hit * band.cacheHit + buckets.output * band.output) / 1e6;
|
|
576
|
+
return currency === "USD" ? raw * currentRate() : raw;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Estimate the CNY cost of one model's token usage, mixing the peak and
|
|
580
|
+
* off-peak bands by the given peak share (flat-priced models cost the same in
|
|
581
|
+
* both bands).
|
|
582
|
+
*
|
|
583
|
+
* 计费维度是「缓存命中价 × 时段价」的交叉:每个时段档内部分别按缓存命中
|
|
584
|
+
* 价(cacheHit)与未命中价(input/cacheMiss)计价,两个时段档再按
|
|
585
|
+
* peakShare 混合。时段定义以北京时间为准(如 DeepSeek V4 高峰
|
|
586
|
+
* 09:00-12:00 / 14:00-18:00)。因聚合只有按日 token 量、没有请求级时间戳,
|
|
587
|
+
* 时段只能按比例估算,而非逐请求判定。
|
|
588
|
+
* @param entry - the catalog entry whose prices apply.
|
|
589
|
+
* @param buckets - token usage counts.
|
|
590
|
+
* @param peakShare - share of traffic in the peak band (0..1); defaults to {@link DEFAULT_PEAK_SHARE}.
|
|
591
|
+
* @returns the estimated cost in CNY.
|
|
592
|
+
*/
|
|
593
|
+
function computeCost(entry, buckets, peakShare = DEFAULT_PEAK_SHARE, nowMs = Date.now()) {
|
|
594
|
+
const priced = applyPromo(entry, nowMs);
|
|
595
|
+
const peak = priceBandCost(priced.price, buckets, priced.price.currency);
|
|
596
|
+
const off = priced.price.offPeak === void 0 ? peak : priceBandCost(priced.price.offPeak, buckets, priced.price.currency);
|
|
597
|
+
return peak * peakShare + off * (1 - peakShare);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* v1 峰谷档判定(峰谷开闸起、周末全谷分界止):不豁免周末——该时段官方
|
|
601
|
+
* 高峰时段为每天 9-12 / 14-18(周六日同样计峰)。仅用于历史事件计费;
|
|
602
|
+
* 「当前时刻」的档位(提醒/时段条/费率展示)一律走 {@link tierAt} 现行规则。
|
|
603
|
+
*/
|
|
604
|
+
function tariffV1At(timeMs) {
|
|
605
|
+
return isPeakHour((new Date(timeMs).getUTCHours() + 8) % 24) ? "peak" : "offPeak";
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* 按调用时刻精确判定高峰/空闲档并计价(P0-1:替代固定比例混合)。时刻未知
|
|
609
|
+
* (null/NaN,理论不发生在真实事件流)时回退 {@link DEFAULT_PEAK_SHARE} 混合,
|
|
610
|
+
* 保持旧语义不低估。平档模型(无 offPeak)两个时段同价。限时促销与峰谷档
|
|
611
|
+
* 同口径:按事件时刻判定该笔流量当时享受的单价。
|
|
612
|
+
*
|
|
613
|
+
* 历史正确性(按变更节点分段适用规则,不统一套现行价重算历史):
|
|
614
|
+
* - 早于 {@link PEAK_ERA_START_MS} 的事件按当时官方基础价
|
|
615
|
+
* ({@link LEGACY_DEEPSEEK_BANDS})计费;
|
|
616
|
+
* - 峰谷开闸至 {@link WEEKEND_OFFPEAK_START_MS} 之间按 v1 规则(周末不豁免,
|
|
617
|
+
* 周六日 9-12 / 14-18 计峰);
|
|
618
|
+
* - 周末全谷分界起按现行规则({@link tierAt},周六日全天低谷)。
|
|
619
|
+
* @param entry - the catalog entry whose prices apply.
|
|
620
|
+
* @param buckets - token usage counts.
|
|
621
|
+
* @param timeMs - the call's wall-clock time (epoch ms); null falls back to the peak-share mix.
|
|
622
|
+
* @param peakShare - fallback mix used only when `timeMs` is missing.
|
|
623
|
+
* @returns the estimated cost in CNY(USD 计价模型已按当前汇率折算)。
|
|
624
|
+
*/
|
|
625
|
+
function computeCostAt(entry, buckets, timeMs, peakShare = DEFAULT_PEAK_SHARE) {
|
|
626
|
+
if (timeMs === null || timeMs === void 0 || !Number.isFinite(timeMs)) return computeCost(entry, buckets, peakShare);
|
|
627
|
+
const priced = applyPromo(entry, timeMs);
|
|
628
|
+
const legacy = timeMs < PEAK_ERA_START_MS && entry.userPriced !== true ? LEGACY_DEEPSEEK_BANDS[entry.key] : void 0;
|
|
629
|
+
if (legacy !== void 0) return priceBandCost(legacy, buckets, "CNY");
|
|
630
|
+
const repriced = timeMs < FLASH_REPRICE_MS && entry.userPriced !== true ? FLASH_REPRICED_OFFPEAK[entry.key] : void 0;
|
|
631
|
+
if (repriced !== void 0) return priceBandCost((timeMs < WEEKEND_OFFPEAK_START_MS ? tariffV1At(timeMs) : tierAt(timeMs)) === "peak" ? {
|
|
632
|
+
input: repriced.input * 2,
|
|
633
|
+
cacheHit: repriced.cacheHit * 2,
|
|
634
|
+
output: repriced.output * 2
|
|
635
|
+
} : repriced, buckets, "CNY");
|
|
636
|
+
if (priced.price.offPeak === void 0) return priceBandCost(priced.price, buckets, priced.price.currency);
|
|
637
|
+
return priceBandCost((timeMs < WEEKEND_OFFPEAK_START_MS ? tariffV1At(timeMs) : tierAt(timeMs)) === "peak" ? priced.price : priced.price.offPeak, buckets, priced.price.currency);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Format an amount with adaptive precision and the given currency symbol.
|
|
641
|
+
* @param amount - the amount (CNY by default; pass `usd` for dollar display).
|
|
642
|
+
* @param currency - display currency; default `cny`.
|
|
643
|
+
*/
|
|
644
|
+
function formatMoney(amount, currency = "cny") {
|
|
645
|
+
const value = Number(amount);
|
|
646
|
+
if (!Number.isFinite(value)) return currency === "cny" ? "¥0" : "$0";
|
|
647
|
+
const symbol = currency === "cny" ? "¥" : "$";
|
|
648
|
+
if (value <= 0) return `${symbol}0`;
|
|
649
|
+
if (value >= 1e3) return `${symbol}${value.toFixed(0)}`;
|
|
650
|
+
if (value >= 10) return `${symbol}${value.toFixed(1)}`;
|
|
651
|
+
if (value >= .1) return `${symbol}${value.toFixed(2)}`;
|
|
652
|
+
return `${symbol}${value.toFixed(3)}`;
|
|
653
|
+
}
|
|
654
|
+
/** Format a large token count with B/M/K suffix. */
|
|
655
|
+
function formatTokens(value) {
|
|
656
|
+
if (value >= 1e9) return `${(value / 1e9).toFixed(2)}B`;
|
|
657
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
658
|
+
if (value >= 1e3) return `${(value / 1e3).toFixed(0)}K`;
|
|
659
|
+
return String(value);
|
|
660
|
+
}
|
|
661
|
+
//#endregion
|
|
662
|
+
//#region lib/types/builtin-catalog.js
|
|
663
|
+
/**
|
|
664
|
+
* Built-in catalog of current mainstream models as of 2026-08-16, priced from
|
|
665
|
+
* each provider's official price page. Domestic providers are OpenAI-API
|
|
666
|
+
* compatible and publish RMB prices directly; overseas providers publish USD
|
|
667
|
+
* and convert through the exchange rate at estimate time. Retired models
|
|
668
|
+
* (GPT-4o family, Gemini 2.x, GLM-4.x-lite, older Qwen) are deliberately
|
|
669
|
+
* absent, as are Anthropic Claude models (their native API is not
|
|
670
|
+
* OpenAI-compatible, so the harness cannot drive them directly). DeepSeek
|
|
671
|
+
* keys match the harness stats file so real usage prices from the catalog;
|
|
672
|
+
* unknown keys fall back to `other`.
|
|
673
|
+
*
|
|
674
|
+
* Time-of-day billing (peak/off-peak) is now real: DeepSeek V4 officially
|
|
675
|
+
* splits peak (09:00-12:00 / 14:00-18:00 Beijing) at 2x the off-peak rate
|
|
676
|
+
* from 2026-08-17, and Gemini's Flex tier discounts spare-capacity traffic.
|
|
677
|
+
*/
|
|
678
|
+
/** DeepSeek 官方高峰时段说明(峰谷分时计费目录条目共用)。 */
|
|
679
|
+
const DEEPSEEK_PEAK_HOURS = "09:00-12:00 / 14:00-18:00";
|
|
680
|
+
const GEMINI_PEAK_HOURS = "Standard / Flex";
|
|
681
|
+
const BUILTIN_MODEL_CATALOG = [
|
|
485
682
|
{
|
|
486
|
-
key: "
|
|
487
|
-
name: "
|
|
488
|
-
provider: "
|
|
489
|
-
colorVar: "
|
|
683
|
+
key: "flash",
|
|
684
|
+
name: "DeepSeek V4.1 Flash",
|
|
685
|
+
provider: "DeepSeek",
|
|
686
|
+
colorVar: "ds-blue",
|
|
490
687
|
price: {
|
|
491
688
|
currency: "CNY",
|
|
492
|
-
input:
|
|
493
|
-
cacheHit:
|
|
494
|
-
output:
|
|
689
|
+
input: 2,
|
|
690
|
+
cacheHit: .04,
|
|
691
|
+
output: 8,
|
|
692
|
+
offPeak: {
|
|
693
|
+
input: 1,
|
|
694
|
+
cacheHit: .02,
|
|
695
|
+
output: 4
|
|
696
|
+
}
|
|
495
697
|
},
|
|
496
|
-
|
|
698
|
+
peakHours: DEEPSEEK_PEAK_HOURS
|
|
497
699
|
},
|
|
498
700
|
{
|
|
499
|
-
key: "
|
|
500
|
-
name: "
|
|
501
|
-
provider: "
|
|
701
|
+
key: "flash-vision-exp",
|
|
702
|
+
name: "DeepSeek V4 Flash Vision (Exp)",
|
|
703
|
+
provider: "DeepSeek",
|
|
502
704
|
colorVar: "ds-blue",
|
|
705
|
+
retired: true,
|
|
503
706
|
price: {
|
|
504
707
|
currency: "CNY",
|
|
505
|
-
input:
|
|
506
|
-
cacheHit:
|
|
507
|
-
output:
|
|
508
|
-
|
|
708
|
+
input: 2,
|
|
709
|
+
cacheHit: .04,
|
|
710
|
+
output: 8,
|
|
711
|
+
offPeak: {
|
|
712
|
+
input: 1,
|
|
713
|
+
cacheHit: .02,
|
|
714
|
+
output: 4
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
peakHours: DEEPSEEK_PEAK_HOURS
|
|
509
718
|
},
|
|
510
719
|
{
|
|
511
|
-
key: "
|
|
512
|
-
name: "
|
|
720
|
+
key: "pro",
|
|
721
|
+
name: "DeepSeek V4 Pro",
|
|
722
|
+
provider: "DeepSeek",
|
|
723
|
+
colorVar: "dsw-static-deepseek-500",
|
|
724
|
+
price: {
|
|
725
|
+
currency: "CNY",
|
|
726
|
+
input: 9,
|
|
727
|
+
cacheHit: .3,
|
|
728
|
+
output: 27,
|
|
729
|
+
offPeak: {
|
|
730
|
+
input: 4.5,
|
|
731
|
+
cacheHit: .15,
|
|
732
|
+
output: 13.5
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
peakHours: DEEPSEEK_PEAK_HOURS
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
key: "glm",
|
|
739
|
+
name: "GLM-5.2",
|
|
513
740
|
provider: "智谱 AI",
|
|
514
741
|
colorVar: "dsw-static-blue-600",
|
|
515
742
|
price: {
|
|
516
743
|
currency: "CNY",
|
|
517
|
-
input:
|
|
518
|
-
cacheHit:
|
|
519
|
-
output:
|
|
744
|
+
input: 8,
|
|
745
|
+
cacheHit: 2,
|
|
746
|
+
output: 28
|
|
520
747
|
}
|
|
521
748
|
},
|
|
522
749
|
{
|
|
523
|
-
key: "glm-
|
|
524
|
-
name: "GLM-
|
|
750
|
+
key: "glm-5.3",
|
|
751
|
+
name: "GLM-5.3",
|
|
525
752
|
provider: "智谱 AI",
|
|
526
|
-
colorVar: "
|
|
753
|
+
colorVar: "ds-blue",
|
|
527
754
|
price: {
|
|
528
755
|
currency: "CNY",
|
|
529
|
-
input:
|
|
530
|
-
cacheHit:
|
|
531
|
-
output:
|
|
756
|
+
input: 8,
|
|
757
|
+
cacheHit: 2,
|
|
758
|
+
output: 28
|
|
532
759
|
}
|
|
533
760
|
},
|
|
534
761
|
{
|
|
535
|
-
key: "
|
|
536
|
-
name: "
|
|
762
|
+
key: "glm-5.3-flash",
|
|
763
|
+
name: "GLM-5.3-Flash",
|
|
764
|
+
provider: "智谱 AI",
|
|
765
|
+
colorVar: "dsw-static-blue-300",
|
|
766
|
+
price: {
|
|
767
|
+
currency: "CNY",
|
|
768
|
+
input: .8,
|
|
769
|
+
cacheHit: .23,
|
|
770
|
+
output: 2.8
|
|
771
|
+
},
|
|
772
|
+
promo: {
|
|
773
|
+
factor: .5,
|
|
774
|
+
endsAtMs: Date.UTC(2026, 8, 8, 16, 0, 0),
|
|
775
|
+
note: "限时 5 折"
|
|
776
|
+
}
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
key: "glm-5.3-flashx",
|
|
780
|
+
name: "GLM-5.3-FlashX",
|
|
781
|
+
provider: "智谱 AI",
|
|
782
|
+
colorVar: "dsw-static-blue-300",
|
|
783
|
+
price: {
|
|
784
|
+
currency: "CNY",
|
|
785
|
+
input: 2,
|
|
786
|
+
cacheHit: .57,
|
|
787
|
+
output: 7
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
key: "glm-4.6",
|
|
792
|
+
name: "GLM-4.6",
|
|
793
|
+
provider: "智谱 AI",
|
|
794
|
+
colorVar: "dsw-static-blue-400",
|
|
795
|
+
price: {
|
|
796
|
+
currency: "CNY",
|
|
797
|
+
input: 4,
|
|
798
|
+
cacheHit: .8,
|
|
799
|
+
output: 16
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
key: "glm-4.5-air",
|
|
804
|
+
name: "GLM-4.5-Air",
|
|
805
|
+
provider: "智谱 AI",
|
|
806
|
+
colorVar: "dsw-static-blue-300",
|
|
807
|
+
price: {
|
|
808
|
+
currency: "CNY",
|
|
809
|
+
input: .8,
|
|
810
|
+
cacheHit: .16,
|
|
811
|
+
output: 2
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
key: "glm-4.7",
|
|
816
|
+
name: "GLM-4.7",
|
|
817
|
+
provider: "智谱 AI",
|
|
818
|
+
colorVar: "dsw-static-blue-400",
|
|
819
|
+
price: {
|
|
820
|
+
currency: "CNY",
|
|
821
|
+
input: 4,
|
|
822
|
+
cacheHit: 1,
|
|
823
|
+
output: 16
|
|
824
|
+
},
|
|
825
|
+
estimated: true
|
|
826
|
+
},
|
|
827
|
+
{
|
|
828
|
+
key: "glm-5-turbo",
|
|
829
|
+
name: "GLM-5-Turbo",
|
|
830
|
+
provider: "智谱 AI",
|
|
831
|
+
colorVar: "ds-blue",
|
|
832
|
+
price: {
|
|
833
|
+
currency: "CNY",
|
|
834
|
+
input: 5,
|
|
835
|
+
cacheHit: 1.2,
|
|
836
|
+
output: 22
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
key: "glm-5.1",
|
|
841
|
+
name: "GLM-5.1",
|
|
842
|
+
provider: "智谱 AI",
|
|
843
|
+
colorVar: "dsw-static-blue-600",
|
|
844
|
+
price: {
|
|
845
|
+
currency: "CNY",
|
|
846
|
+
input: 6,
|
|
847
|
+
cacheHit: 1.2,
|
|
848
|
+
output: 24
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
key: "glm-5v-turbo",
|
|
853
|
+
name: "GLM-5V-Turbo",
|
|
854
|
+
provider: "智谱 AI",
|
|
855
|
+
colorVar: "dsw-static-blue-300",
|
|
856
|
+
price: {
|
|
857
|
+
currency: "CNY",
|
|
858
|
+
input: 5,
|
|
859
|
+
cacheHit: 1.2,
|
|
860
|
+
output: 22
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
key: "qwen-3.8-max",
|
|
865
|
+
name: "Qwen3.8 Max",
|
|
537
866
|
provider: "阿里通义",
|
|
538
867
|
colorVar: "dsw-static-blue-600",
|
|
539
868
|
price: {
|
|
@@ -1440,447 +1769,161 @@ const MODEL_CATALOG = [
|
|
|
1440
1769
|
provider: "月之暗面",
|
|
1441
1770
|
colorVar: "dsw-static-cyan-300",
|
|
1442
1771
|
price: {
|
|
1443
|
-
currency: "CNY",
|
|
1444
|
-
input: 8,
|
|
1445
|
-
cacheHit: 1,
|
|
1446
|
-
output: 58
|
|
1447
|
-
}
|
|
1448
|
-
},
|
|
1449
|
-
{
|
|
1450
|
-
key: "doubao-seed-2.0-code",
|
|
1451
|
-
name: "Doubao Seed-2.0 Code",
|
|
1452
|
-
provider: "字节豆包",
|
|
1453
|
-
colorVar: "ds-red",
|
|
1454
|
-
price: {
|
|
1455
|
-
currency: "CNY",
|
|
1456
|
-
input: 3.2,
|
|
1457
|
-
cacheHit: .64,
|
|
1458
|
-
output: 16
|
|
1459
|
-
}
|
|
1460
|
-
},
|
|
1461
|
-
{
|
|
1462
|
-
key: "doubao-seed-2.0-lite",
|
|
1463
|
-
name: "Doubao Seed-2.0 Lite",
|
|
1464
|
-
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,
|
|
1772
|
+
currency: "CNY",
|
|
1773
|
+
input: 8,
|
|
1774
|
+
cacheHit: 1,
|
|
1775
|
+
output: 58
|
|
1776
|
+
}
|
|
1777
|
+
},
|
|
1778
|
+
{
|
|
1779
|
+
key: "doubao-seed-2.0-code",
|
|
1780
|
+
name: "Doubao Seed-2.0 Code",
|
|
1781
|
+
provider: "字节豆包",
|
|
1782
|
+
colorVar: "ds-red",
|
|
1780
1783
|
price: {
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
+
currency: "CNY",
|
|
1785
|
+
input: 3.2,
|
|
1786
|
+
cacheHit: .64,
|
|
1787
|
+
output: 16
|
|
1784
1788
|
}
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
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
|
-
}
|
|
1789
|
+
},
|
|
1790
|
+
{
|
|
1791
|
+
key: "doubao-seed-2.0-lite",
|
|
1792
|
+
name: "Doubao Seed-2.0 Lite",
|
|
1793
|
+
provider: "字节豆包",
|
|
1794
|
+
colorVar: "dsw-static-red-300",
|
|
1795
|
+
price: {
|
|
1796
|
+
currency: "CNY",
|
|
1797
|
+
input: .6,
|
|
1798
|
+
cacheHit: .12,
|
|
1799
|
+
output: 3.6
|
|
1800
|
+
}
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
key: "other",
|
|
1804
|
+
name: "其他模型",
|
|
1805
|
+
provider: "Custom",
|
|
1806
|
+
colorVar: "dsw-static-neutral-bluish-500",
|
|
1807
|
+
price: {
|
|
1808
|
+
currency: "CNY",
|
|
1809
|
+
input: 0,
|
|
1810
|
+
cacheHit: 0,
|
|
1811
|
+
cacheMiss: 0,
|
|
1812
|
+
output: 0
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
];
|
|
1862
1816
|
/**
|
|
1863
|
-
*
|
|
1864
|
-
*
|
|
1865
|
-
*
|
|
1817
|
+
* 真实 provider model id → 计费目录键(`MODEL_CATALOG[].key`)的映射。未知 id
|
|
1818
|
+
* 原样保留并落回 `other`(未知模型不估算费用)。聚合层(aggregate.ts)在折叠时
|
|
1819
|
+
* 用同一张表把日志里的 model id 归并为目录键,客户端渲染(`modelOf`)也按它
|
|
1820
|
+
* 解析,两侧共用一份映射,避免同一模型两侧不一致导致「未收录」。
|
|
1866
1821
|
*/
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1822
|
+
const BUILTIN_MODEL_KEY_ALIASES = {
|
|
1823
|
+
"deepseek-v4-flash": "flash",
|
|
1824
|
+
"deepseek-v4-flash-vision-exp": "flash-vision-exp",
|
|
1825
|
+
"deepseek-v4.1-flash": "flash",
|
|
1826
|
+
"deepseek-v4.1-flash-expires-on-0910": "flash",
|
|
1827
|
+
"deepseek-flash": "flash",
|
|
1828
|
+
"deepseek-v4-pro": "pro",
|
|
1829
|
+
"glm-5.2": "glm",
|
|
1830
|
+
"glm-4.5-air": "glm-4.5-air",
|
|
1831
|
+
"glm-4.5air": "glm-4.5-air",
|
|
1832
|
+
"glm-4.7": "glm-4.7",
|
|
1833
|
+
"glm-5-turbo": "glm-5-turbo",
|
|
1834
|
+
"glm-5.1": "glm-5.1",
|
|
1835
|
+
"glm-5.3-flash": "glm-5.3-flash",
|
|
1836
|
+
"glm-5.3-flashx": "glm-5.3-flashx",
|
|
1837
|
+
"glm-5.3-flash-x": "glm-5.3-flashx",
|
|
1838
|
+
"glm-5v-turbo": "glm-5v-turbo",
|
|
1839
|
+
"glm-5v.1": "glm-5v-turbo",
|
|
1840
|
+
"claude-opus-4-6": "claude-opus-4-6",
|
|
1841
|
+
"claude-opus-4.6": "claude-opus-4-6",
|
|
1842
|
+
"claude-sonnet-4-6": "claude-sonnet-4-6",
|
|
1843
|
+
"claude-sonnet-4.6": "claude-sonnet-4-6",
|
|
1844
|
+
"claude-haiku-4-5": "claude-haiku-4-5",
|
|
1845
|
+
"claude-haiku-4.5": "claude-haiku-4-5",
|
|
1846
|
+
"claude-opus-5": "claude-opus-5",
|
|
1847
|
+
"claude-sonnet-5": "claude-sonnet-5",
|
|
1848
|
+
"mistral-large-2512": "mistral-large-2512",
|
|
1849
|
+
"mistral-large-3": "mistral-large-2512",
|
|
1850
|
+
"mistral-small-2603": "mistral-small-2603",
|
|
1851
|
+
"mistral-small-4": "mistral-small-2603",
|
|
1852
|
+
"ministral-8b-latest": "ministral-8b-latest",
|
|
1853
|
+
"ministral-8b": "ministral-8b-latest",
|
|
1854
|
+
"command-a-03-2025": "command-a-03-2025",
|
|
1855
|
+
"command-a": "command-a-03-2025",
|
|
1856
|
+
"command-r-08-2024": "command-r-08-2024",
|
|
1857
|
+
"command-r": "command-r-08-2024",
|
|
1858
|
+
"hy3": "hunyuan",
|
|
1859
|
+
"hy4-preview": "hunyuan-hy4-preview",
|
|
1860
|
+
"hy4": "hunyuan-hy4-preview",
|
|
1861
|
+
"hunyuan-hy4-preview": "hunyuan-hy4-preview",
|
|
1862
|
+
"hunyuan-hy4": "hunyuan-hy4-preview",
|
|
1863
|
+
"longcat-2.0": "longcat-2.0",
|
|
1864
|
+
"longcat-2": "longcat-2.0",
|
|
1865
|
+
"minicpm-v-4.5": "minicpm-v-4.5",
|
|
1866
|
+
"minicpm-v-4.5-thinking": "minicpm-v-4.5",
|
|
1867
|
+
"ernie-4.5": "ernie-4.5",
|
|
1868
|
+
"ernie-4.5-300b": "ernie-4.5",
|
|
1869
|
+
"dots-3-note-preview": "dots-3-note-preview",
|
|
1870
|
+
"dots-3-note": "dots-3-note-preview",
|
|
1871
|
+
"dots3-note-preview": "dots-3-note-preview",
|
|
1872
|
+
"rednote-dots3": "dots-3-note-preview",
|
|
1873
|
+
"doubao-seed-evolving": "doubao-seed-evolving",
|
|
1874
|
+
"doubao-seed-evolve": "doubao-seed-evolving",
|
|
1875
|
+
"doubao-seed-2.1-pro": "doubao-seed-2.1-pro",
|
|
1876
|
+
"doubao-seed-2.1-pro-290000": "doubao-seed-2.1-pro",
|
|
1877
|
+
"doubao-seed-2.1-turbo": "doubao-seed-2.1-turbo",
|
|
1878
|
+
"doubao-seed-2-1-turbo": "doubao-seed-2.1-turbo",
|
|
1879
|
+
"qwen3.8-max": "qwen-3.8-max",
|
|
1880
|
+
"qwen3.8-flash": "qwen-3.8-flash",
|
|
1881
|
+
"qwen3.7-max": "qwen-max",
|
|
1882
|
+
"qwen3.6-max": "qwen3.6-max",
|
|
1883
|
+
"qwen3.6-max-preview": "qwen3.6-max",
|
|
1884
|
+
"qwen3-coder-plus": "qwen3-coder-plus",
|
|
1885
|
+
"qwen3-coder": "qwen3-coder",
|
|
1886
|
+
"qwen3-coder-480b": "qwen3-coder",
|
|
1887
|
+
"glm-4.5-x": "glm-4.5-x",
|
|
1888
|
+
"glm-4.5x": "glm-4.5-x",
|
|
1889
|
+
"glm-5.2-fast": "glm-5.2-fast",
|
|
1890
|
+
"glm-5.2f": "glm-5.2-fast",
|
|
1891
|
+
"kimi-k3-fast": "kimi-k3-fast",
|
|
1892
|
+
"kimi-k3f": "kimi-k3-fast",
|
|
1893
|
+
"kimi-for-coding": "kimi-k2.8-preview",
|
|
1894
|
+
"kimi-k2.7-code-fast": "kimi-k2.7-code-fast",
|
|
1895
|
+
"kimi-k2.7-code-f": "kimi-k2.7-code-fast",
|
|
1896
|
+
"kimi-k2.6-fast": "kimi-k2.6-fast",
|
|
1897
|
+
"kimi-k2.6-turbo": "kimi-k2.6-turbo",
|
|
1898
|
+
"kimi-k2-thinking-turbo": "kimi-k2-thinking-turbo",
|
|
1899
|
+
"doubao-seed-2.0-code": "doubao-seed-2.0-code",
|
|
1900
|
+
"doubao-seed-2-0-code": "doubao-seed-2.0-code",
|
|
1901
|
+
"doubao-seed-2.0-lite": "doubao-seed-2.0-lite",
|
|
1902
|
+
"doubao-seed-2-0-lite": "doubao-seed-2.0-lite",
|
|
1903
|
+
"qwen-max": "qwen-max",
|
|
1904
|
+
"hunyuan-t1": "hunyuan-t1",
|
|
1905
|
+
"step-3.7-flash": "step",
|
|
1906
|
+
"seed-2.0-mini": "doubao-mini",
|
|
1907
|
+
"k3": "kimi-k3",
|
|
1908
|
+
"kimi-k3": "kimi-k3",
|
|
1909
|
+
"kimi-k2.8-preview": "kimi-k2.8-preview",
|
|
1910
|
+
"kimi-k2.8": "kimi-k2.8-preview",
|
|
1911
|
+
"kimi-k2-8-preview": "kimi-k2.8-preview",
|
|
1912
|
+
"kimi-k2-8": "kimi-k2.8-preview",
|
|
1913
|
+
"k2.8-preview": "kimi-k2.8-preview",
|
|
1914
|
+
"k2.8": "kimi-k2.8-preview",
|
|
1915
|
+
"minimax-m1": "minimax",
|
|
1916
|
+
"minimax-m2": "minimax",
|
|
1917
|
+
"minimax-m3": "minimax",
|
|
1918
|
+
"minimax-m2.7": "minimax-m2.7",
|
|
1919
|
+
"minimax-m2.7-highspeed": "minimax-m2.7-highspeed",
|
|
1920
|
+
"minimax-m2.7-high-speed": "minimax-m2.7-highspeed",
|
|
1921
|
+
"minimax-m2-7": "minimax-m2.7",
|
|
1922
|
+
"minimax-m2-7-highspeed": "minimax-m2.7-highspeed",
|
|
1923
|
+
"minimax-m2-7-high-speed": "minimax-m2.7-highspeed",
|
|
1924
|
+
"gpt-6": "gpt-6-astra",
|
|
1925
|
+
"gpt-6-astra": "gpt-6-astra"
|
|
1926
|
+
};
|
|
1884
1927
|
//#endregion
|
|
1885
1928
|
//#region lib/types/aggregate.js
|
|
1886
1929
|
/**
|
|
@@ -4668,7 +4711,7 @@ const FIRST_PARTY_PRICE_SOURCES = new Set([
|
|
|
4668
4711
|
*/
|
|
4669
4712
|
function buildExtraModels(data) {
|
|
4670
4713
|
if (data === null || typeof data !== "object") return [];
|
|
4671
|
-
const catalogKeys = new Set([...
|
|
4714
|
+
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
4715
|
const offers = /* @__PURE__ */ new Map();
|
|
4673
4716
|
const familyVendorVotes = /* @__PURE__ */ new Map();
|
|
4674
4717
|
for (const [providerId, providerDoc] of Object.entries(data)) {
|
|
@@ -4679,7 +4722,7 @@ function buildExtraModels(data) {
|
|
|
4679
4722
|
const providerName = providerDoc.name;
|
|
4680
4723
|
const providerDisplay = MODELS_DEV_PROVIDERS[providerKey] ?? (typeof providerName === "string" && providerName !== "" ? providerName : providerId);
|
|
4681
4724
|
for (const [modelId, modelDoc] of Object.entries(models)) {
|
|
4682
|
-
const catalogKey = (
|
|
4725
|
+
const catalogKey = (BUILTIN_MODEL_KEY_ALIASES[modelId] ?? modelId).toLowerCase();
|
|
4683
4726
|
if (catalogKeys.has(catalogKey)) continue;
|
|
4684
4727
|
const key = catalogKey;
|
|
4685
4728
|
if (modelDoc === null || typeof modelDoc !== "object") continue;
|
|
@@ -6375,6 +6418,7 @@ function apply(ctx, config = {}) {
|
|
|
6375
6418
|
}).catch(() => {});
|
|
6376
6419
|
};
|
|
6377
6420
|
const workspaceTitleResolver = buildWorkspaceTitleResolver(ctx);
|
|
6421
|
+
applyBuiltinCatalog(BUILTIN_MODEL_CATALOG, BUILTIN_MODEL_KEY_ALIASES);
|
|
6378
6422
|
applyUserModelAliases(config.modelKeyAliases);
|
|
6379
6423
|
const aggregator = createUsageAggregator(adaptSessionPersistence(ctx.sessionPersistence), {
|
|
6380
6424
|
...config.subscriptionProviders === void 0 ? {} : { subscriptionProviders: config.subscriptionProviders },
|
|
@@ -6651,6 +6695,17 @@ function apply(ctx, config = {}) {
|
|
|
6651
6695
|
live = await fetchLivePricing();
|
|
6652
6696
|
pricingSyncedAt = Date.now();
|
|
6653
6697
|
applyLivePricing(live);
|
|
6698
|
+
pricingBodyCache = void 0;
|
|
6699
|
+
};
|
|
6700
|
+
let pricingBodyCache;
|
|
6701
|
+
const pricingBody = () => {
|
|
6702
|
+
if (pricingBodyCache === void 0) pricingBodyCache = JSON.stringify({
|
|
6703
|
+
...live,
|
|
6704
|
+
syncedAt: pricingSyncedAt,
|
|
6705
|
+
catalog: BUILTIN_MODEL_CATALOG,
|
|
6706
|
+
aliases: BUILTIN_MODEL_KEY_ALIASES
|
|
6707
|
+
});
|
|
6708
|
+
return pricingBodyCache;
|
|
6654
6709
|
};
|
|
6655
6710
|
pricingReady = refreshPricing();
|
|
6656
6711
|
ctx.effect(() => {
|
|
@@ -6667,10 +6722,7 @@ function apply(ctx, config = {}) {
|
|
|
6667
6722
|
handler: async (req, res) => {
|
|
6668
6723
|
if (!guardLoopback(req, res, trustedHosts)) return;
|
|
6669
6724
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
6670
|
-
res.end(
|
|
6671
|
-
...live,
|
|
6672
|
-
syncedAt: pricingSyncedAt
|
|
6673
|
-
}));
|
|
6725
|
+
res.end(pricingBody());
|
|
6674
6726
|
}
|
|
6675
6727
|
}), "usage-billing: pricing route");
|
|
6676
6728
|
ctx.effect(() => ctx.webServer.register({
|
|
@@ -6695,10 +6747,7 @@ function apply(ctx, config = {}) {
|
|
|
6695
6747
|
}
|
|
6696
6748
|
await refreshPricing();
|
|
6697
6749
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
6698
|
-
res.end(
|
|
6699
|
-
...live,
|
|
6700
|
-
syncedAt: pricingSyncedAt
|
|
6701
|
-
}));
|
|
6750
|
+
res.end(pricingBody());
|
|
6702
6751
|
}
|
|
6703
6752
|
}), "usage-billing: pricing refresh route");
|
|
6704
6753
|
let balanceCache = {
|