@kenz1117/dsh-ui-usage-billing 1.4.26 → 1.4.28

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
@@ -1576,6 +1576,30 @@ const BUILTIN_MODEL_CATALOG = [
1576
1576
  output: 10
1577
1577
  }
1578
1578
  },
1579
+ {
1580
+ key: "gpt-image-2",
1581
+ name: "GPT Image 2",
1582
+ provider: "OpenAI",
1583
+ colorVar: "ds-green",
1584
+ price: {
1585
+ currency: "CNY",
1586
+ input: 14,
1587
+ cacheHit: 5.6,
1588
+ output: 84
1589
+ }
1590
+ },
1591
+ {
1592
+ key: "gpt-image-2.5-sunburst",
1593
+ name: "GPT Image 2.5 Sunburst",
1594
+ provider: "OpenAI",
1595
+ colorVar: "ds-green",
1596
+ price: {
1597
+ currency: "CNY",
1598
+ input: 14,
1599
+ cacheHit: 5.6,
1600
+ output: 84
1601
+ }
1602
+ },
1579
1603
  {
1580
1604
  key: "gemini-pro",
1581
1605
  name: "Gemini 3.1 Pro",
@@ -2415,7 +2439,10 @@ const BUILTIN_MODEL_KEY_ALIASES = {
2415
2439
  "grok-4.5": "grok-4.5",
2416
2440
  "grok-4-5": "grok-4.5",
2417
2441
  "grok-4.6": "grok",
2418
- "grok-4-6": "grok"
2442
+ "grok-4-6": "grok",
2443
+ "gpt-image-2": "gpt-image-2",
2444
+ "gpt-image-2.5-sunburst": "gpt-image-2.5-sunburst",
2445
+ "gpt-image-2-5-sunburst": "gpt-image-2.5-sunburst"
2419
2446
  };
2420
2447
  //#endregion
2421
2448
  //#region lib/types/aggregate.js
@@ -2464,6 +2491,29 @@ const DEFAULT_SUBSCRIPTION_PROVIDERS = [
2464
2491
  function isOfficialProvider(provider) {
2465
2492
  return /^deepseek(?:-[a-z0-9-]+)?$/i.test(provider.trim());
2466
2493
  }
2494
+ /**
2495
+ * 已知的「第三方插件注册 adapter」通道名单:这些 provider 由别的插件经
2496
+ * `ctx.llm.registerAdapter` 注册,**不经 llm-pi-ai 路由表**,因此
2497
+ * {@link siteRefOf} 的路由查询必然 miss。与订阅豁免(issue #37 的 grok
2498
+ * build)同构:显式列出、不做「猜通道」的通用发现——猜错会把来源不明的
2499
+ * 路由也归位成可计价通道,违背 issue #82「支出主体无法核实不计价」的初衷。
2500
+ *
2501
+ * 收录语义:这些通道的支出主体是可核实的(WorkBuddy = 腾讯 WorkBuddy
2502
+ * 桌面 App 账号),计费用模型官方目录价折算 Token 成本,与积分制解耦。
2503
+ * 它们**不是** DeepSeek 官方渠道——{@link isOfficialProvider} 与
2504
+ * {@link officialChannelOf} 都不会因本名单把调用算进官方口径。
2505
+ */
2506
+ const KNOWN_ADAPTER_PROVIDERS = /* @__PURE__ */ new Set(["workbuddy", "workbuddy-ai"]);
2507
+ /**
2508
+ * 一个 provider 是否命中已知 adapter 通道名单。判定与 {@link isOfficialProvider}
2509
+ * 同姿态:trim + 大小写不敏感——dsh-workbuddy-connect 注册的是小写 id
2510
+ *(`workbuddy` / `workbuddy-ai`),宿主不强制 route 小写,按大小写归一化可避免
2511
+ * 日志里出现大小写变体时静默落回 unknown 桶。路由表命中仍优先于本名单
2512
+ *(见 {@link siteRefOf}),用户自配的大小写变体走配置路径。
2513
+ */
2514
+ function isKnownAdapterProvider(provider) {
2515
+ return KNOWN_ADAPTER_PROVIDERS.has(provider.trim().toLowerCase());
2516
+ }
2467
2517
  /** DeepSeek 官方直连端点的归一化 origin(`siteOriginOf` 口径):有 baseURL 的路由只有它算官方。 */
2468
2518
  const OFFICIAL_DEEPSEEK_ORIGIN = "https://api.deepseek.com";
2469
2519
  /**
@@ -2500,8 +2550,10 @@ function siteOriginOf(baseURL) {
2500
2550
  * 网关)按 `direct` 归位——unknown 一刀切会把官方直连的费用堆进「未知路由」组、
2501
2551
  * 且不算官方渠道(回归修复);订阅豁免命中的通道(显式 `subscriptionProviders`
2502
2552
  * 配置或判定函数)同样按 `direct` 归位——订阅管理类插件注册的通道不经路由表,
2503
- * unknown 桶既掩盖归属又按 token 误计费(issue #37 的 grok build);其余配置外
2504
- * 名称保留 unknown(无法核实通道)。
2553
+ * unknown 桶既掩盖归属又按 token 误计费(issue #37 的 grok build);已知的第三方
2554
+ * 插件注册 adapter 通道({@link KNOWN_ADAPTER_PROVIDERS},如 workbuddy)同样按
2555
+ * `direct` 归位——支出主体可核实、按模型官方目录价计 Token 成本,unknown 桶会让
2556
+ * 费用恒为 0;其余配置外名称保留 unknown(无法核实通道)。
2505
2557
  * @param provider - 会话日志里的 provider 路由名(request/header 的 `config.provider`)。
2506
2558
  * @param routes - 当前 provider 路由视图(来自 llm-pi-ai providers)。
2507
2559
  * @param opts - `subscription`:该调用是否已被订阅豁免判定命中。
@@ -2527,6 +2579,10 @@ function siteRefOf(provider, routes, opts = {}) {
2527
2579
  kind: "direct",
2528
2580
  provider
2529
2581
  };
2582
+ if (isKnownAdapterProvider(provider)) return {
2583
+ kind: "direct",
2584
+ provider
2585
+ };
2530
2586
  return {
2531
2587
  kind: "unknown",
2532
2588
  provider
@@ -3392,6 +3448,15 @@ function createUsageAggregator(persistence, options = {}) {
3392
3448
  const searchEstimate = options.searchCallEstimateCny ?? .02;
3393
3449
  const foldFingerprint = configFingerprint(subscriptionMatcher, officialProviderIds, routeAliases, searchEstimate);
3394
3450
  const FOLD_CHUNK_EVENTS = 8e3;
3451
+ /** 惰性失效(价目表时序):账本/缓存行若以「当时不可计价」的模型落盘,
3452
+ * 而价目表现在能计价了,该行必须重折——否则显示层按实时价算出的
3453
+ * 「目录价预估」与服务端 cost=0 并存,出现「明细比总额高」的双口径
3454
+ * (真机复现:预热折叠早于 models.dev 价目表注入,step-5-preview
3455
+ * 折叠时刻 cost=0、渲染时刻 ≈¥38)。复用判定据此刻检查:不走配置指纹
3456
+ * (价目表不进指纹,否则 6h 一次的价格刷新会让全部历史行全量重折),
3457
+ * 也不 bump FOLD_VERSION(那是归账语义版本,价目表时序不是语义变更)。
3458
+ * 价目表变化后未受影响行(无 unpriced 模型)依旧零读取复用。 */
3459
+ const foldNeedsReprice = (fold) => fold.unpricedModels.some((key) => isPriced(key));
3395
3460
  /** 把一份(全量或增量)折叠结果登记进 durable ledger;内容无变化时不落盘。 */
3396
3461
  const recordLedger = (id, cwd, stamp, fold) => {
3397
3462
  if (options.ledger === void 0) return;
@@ -3399,12 +3464,12 @@ function createUsageAggregator(persistence, options = {}) {
3399
3464
  id,
3400
3465
  ...cwd === void 0 ? {} : { cwd },
3401
3466
  ...stamp === null ? {} : { stamp },
3402
- foldVersion: 19,
3467
+ foldVersion: 21,
3403
3468
  fingerprint: foldFingerprint,
3404
3469
  fold: serializeFold(fold)
3405
3470
  };
3406
3471
  const row = ledger.get(id);
3407
- if (row === void 0 || row.stamp !== entry.stamp || row.cwd !== entry.cwd || row.foldVersion !== entry.foldVersion || row.fingerprint !== entry.fingerprint || stamp === null && JSON.stringify(row.fold) !== JSON.stringify(entry.fold)) {
3472
+ if (row === void 0 || row.stamp !== entry.stamp || row.cwd !== entry.cwd || row.foldVersion !== entry.foldVersion || row.fingerprint !== entry.fingerprint || stamp === null && JSON.stringify(row.fold) !== JSON.stringify(entry.fold) || stamp !== null && foldNeedsReprice(row.fold)) {
3408
3473
  ledger.set(id, entry);
3409
3474
  ledgerNeedsSave = true;
3410
3475
  }
@@ -3491,22 +3556,10 @@ function createUsageAggregator(persistence, options = {}) {
3491
3556
  if (!ledger.has(id)) noLedgerUnreadable += 1;
3492
3557
  continue;
3493
3558
  }
3494
- const hit = cache.get(id);
3495
- if (hit !== void 0 && stamp !== null && hit.stamp === stamp) {
3496
- cache.delete(id);
3497
- cache.set(id, hit);
3498
- folds.push({
3499
- id,
3500
- ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
3501
- fold: hit.fold
3502
- });
3503
- included.add(id);
3504
- continue;
3505
- }
3506
3559
  try {
3507
3560
  const cwd = meta.cwd;
3508
3561
  const ledgerRow = ledger.get(id);
3509
- if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 19 && ledgerRow.fingerprint === foldFingerprint) {
3562
+ if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 21 && ledgerRow.fingerprint === foldFingerprint && !foldNeedsReprice(ledgerRow.fold)) {
3510
3563
  const fold = deserializeFold(ledgerRow.fold);
3511
3564
  folds.push({
3512
3565
  id,
@@ -3516,8 +3569,20 @@ function createUsageAggregator(persistence, options = {}) {
3516
3569
  included.add(id);
3517
3570
  continue;
3518
3571
  }
3572
+ const hit = cache.get(id);
3573
+ if (hit !== void 0 && stamp !== null && hit.stamp === stamp && ![...hit.fold.unpricedModels].some((key) => isPriced(key))) {
3574
+ cache.delete(id);
3575
+ cache.set(id, hit);
3576
+ folds.push({
3577
+ id,
3578
+ ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
3579
+ fold: hit.fold
3580
+ });
3581
+ included.add(id);
3582
+ continue;
3583
+ }
3519
3584
  const previous = cache.get(id);
3520
- if (previous?.machine !== void 0 && previous.lastSeq !== void 0) {
3585
+ if (previous?.machine !== void 0 && previous.lastSeq !== void 0 && ![...previous.fold.unpricedModels].some((key) => isPriced(key))) {
3521
3586
  const from = previous.lastSeq + 1;
3522
3587
  const { events, inheritedEventCount } = await persistence.readFrom(meta.id, sessionLogOffsetOf(from));
3523
3588
  const after = await stampOf(meta);
@@ -3594,7 +3659,7 @@ function createUsageAggregator(persistence, options = {}) {
3594
3659
  for (const entry of ledger.values()) {
3595
3660
  if (included.has(entry.id)) continue;
3596
3661
  try {
3597
- const stale = (entry.foldVersion ?? 1) < 19 || entry.fingerprint !== foldFingerprint;
3662
+ const stale = (entry.foldVersion ?? 1) < 21 || entry.fingerprint !== foldFingerprint;
3598
3663
  folds.push({
3599
3664
  id: entry.id,
3600
3665
  ...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
@@ -7429,11 +7494,25 @@ function apply(ctx, config = {}) {
7429
7494
  });
7430
7495
  let live = { source: "builtin" };
7431
7496
  let pricingSyncedAt = 0;
7497
+ /** 价目表代际:extraModels 的 key 集合稳定序列化。fetchLivePricing 每次都重建
7498
+ * 数组(引用必不同),引用比较恒真会退化成「每次刷新都跑一轮 reprice」;
7499
+ * key 集合变化才代表真的出现了「此前未收录、现在可计价」的模型。 */
7500
+ const priceGenerationOf = (snapshot) => (snapshot.extraModels ?? []).map((model) => model.key).sort().join(",");
7501
+ let priceGeneration = priceGenerationOf(live);
7432
7502
  const refreshPricing = async () => {
7433
7503
  live = await fetchLivePricing();
7434
7504
  pricingSyncedAt = Date.now();
7435
7505
  applyLivePricing(live);
7436
7506
  pricingBodyCache = void 0;
7507
+ const nextGeneration = priceGenerationOf(live);
7508
+ if (nextGeneration !== priceGeneration) {
7509
+ priceGeneration = nextGeneration;
7510
+ aggregator.aggregate().then(() => {
7511
+ console.info("[usage-billing] repriced folds after pricing refresh; ledger aligned with live catalog");
7512
+ }).catch((error) => {
7513
+ console.warn("[usage-billing] reprice fold after pricing refresh failed; next aggregate retries:", error);
7514
+ });
7515
+ }
7437
7516
  };
7438
7517
  let pricingBodyCache;
7439
7518
  const pricingBody = () => {
@@ -34,6 +34,27 @@ export declare const DEFAULT_SUBSCRIPTION_PROVIDERS: readonly string[];
34
34
  * 调用与费用分桶展示;部署可由配置覆盖(见 {@link AggregateOptions})。
35
35
  */
36
36
  export declare function isOfficialProvider(provider: string): boolean;
37
+ /**
38
+ * 已知的「第三方插件注册 adapter」通道名单:这些 provider 由别的插件经
39
+ * `ctx.llm.registerAdapter` 注册,**不经 llm-pi-ai 路由表**,因此
40
+ * {@link siteRefOf} 的路由查询必然 miss。与订阅豁免(issue #37 的 grok
41
+ * build)同构:显式列出、不做「猜通道」的通用发现——猜错会把来源不明的
42
+ * 路由也归位成可计价通道,违背 issue #82「支出主体无法核实不计价」的初衷。
43
+ *
44
+ * 收录语义:这些通道的支出主体是可核实的(WorkBuddy = 腾讯 WorkBuddy
45
+ * 桌面 App 账号),计费用模型官方目录价折算 Token 成本,与积分制解耦。
46
+ * 它们**不是** DeepSeek 官方渠道——{@link isOfficialProvider} 与
47
+ * {@link officialChannelOf} 都不会因本名单把调用算进官方口径。
48
+ */
49
+ export declare const KNOWN_ADAPTER_PROVIDERS: ReadonlySet<string>;
50
+ /**
51
+ * 一个 provider 是否命中已知 adapter 通道名单。判定与 {@link isOfficialProvider}
52
+ * 同姿态:trim + 大小写不敏感——dsh-workbuddy-connect 注册的是小写 id
53
+ *(`workbuddy` / `workbuddy-ai`),宿主不强制 route 小写,按大小写归一化可避免
54
+ * 日志里出现大小写变体时静默落回 unknown 桶。路由表命中仍优先于本名单
55
+ *(见 {@link siteRefOf}),用户自配的大小写变体走配置路径。
56
+ */
57
+ export declare function isKnownAdapterProvider(provider: string): boolean;
37
58
  /** DeepSeek 官方直连端点的归一化 origin(`siteOriginOf` 口径):有 baseURL 的路由只有它算官方。 */
38
59
  export declare const OFFICIAL_DEEPSEEK_ORIGIN = "https://api.deepseek.com";
39
60
  /**
@@ -78,8 +99,10 @@ export declare function siteOriginOf(baseURL: string): string;
78
99
  * 网关)按 `direct` 归位——unknown 一刀切会把官方直连的费用堆进「未知路由」组、
79
100
  * 且不算官方渠道(回归修复);订阅豁免命中的通道(显式 `subscriptionProviders`
80
101
  * 配置或判定函数)同样按 `direct` 归位——订阅管理类插件注册的通道不经路由表,
81
- * unknown 桶既掩盖归属又按 token 误计费(issue #37 的 grok build);其余配置外
82
- * 名称保留 unknown(无法核实通道)。
102
+ * unknown 桶既掩盖归属又按 token 误计费(issue #37 的 grok build);已知的第三方
103
+ * 插件注册 adapter 通道({@link KNOWN_ADAPTER_PROVIDERS},如 workbuddy)同样按
104
+ * `direct` 归位——支出主体可核实、按模型官方目录价计 Token 成本,unknown 桶会让
105
+ * 费用恒为 0;其余配置外名称保留 unknown(无法核实通道)。
83
106
  * @param provider - 会话日志里的 provider 路由名(request/header 的 `config.provider`)。
84
107
  * @param routes - 当前 provider 路由视图(来自 llm-pi-ai providers)。
85
108
  * @param opts - `subscription`:该调用是否已被订阅豁免判定命中。
@@ -568,7 +591,7 @@ export interface UsageLedgerDocument {
568
591
  * 会话费用只剩最近一段,issue #29)。
569
592
  * 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
570
593
  */
571
- export declare const FOLD_VERSION = 19;
594
+ export declare const FOLD_VERSION = 21;
572
595
  /**
573
596
  * 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
574
597
  * 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kenz1117/dsh-ui-usage-billing",
3
3
  "description": "Usage billing dashboard for DeepSeek Harness: sidebar cost metrics plus a full dashboard modal, priced from a current multi-provider catalog with real usage aggregated from session logs.",
4
- "version": "1.4.26",
4
+ "version": "1.4.28",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "deepseek",