@kenz1117/dsh-ui-usage-billing 1.5.0 → 1.5.1

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
@@ -2301,7 +2301,9 @@ const BUILTIN_MODEL_KEY_ALIASES = {
2301
2301
  "deepseek-v4.1-flash": "flash",
2302
2302
  "deepseek-v4.1-flash-expires-on-0910": "flash",
2303
2303
  "deepseek-flash": "flash",
2304
+ "deepseek-v4.1-flash-1017": "flash",
2304
2305
  "deepseek/deepseek-v4.1-flash": "flash",
2306
+ "deepseek/deepseek-v4.1-flash-1017": "flash",
2305
2307
  "deepseek/deepseek-flash": "flash",
2306
2308
  "deepseek/deepseek-v4-pro": "pro",
2307
2309
  "deepseek-v4-pro": "pro",
@@ -2733,6 +2735,57 @@ function workspaceNameOf(cwd) {
2733
2735
  if (cwd === void 0 || cwd === "") return "—";
2734
2736
  return cwd.split(/[\\/]/).filter(Boolean).at(-1) ?? "—";
2735
2737
  }
2738
+ /**
2739
+ * 从会话 header 提取子智能体归并标记(直接父会话 id):`origin: "subagent"`
2740
+ * (宿主的产品分类)或 `delegationDepth > 0`(持久化委派深度)任一命中即视为
2741
+ * 子会话。子会话但 header 无 parentSession(异常数据)返回 undefined——无法
2742
+ * 归位就按顶层会话处理,保守不标。导出供测试。
2743
+ */
2744
+ function subParentOf(meta) {
2745
+ if (meta.origin !== "subagent" && !((meta.delegationDepth ?? 0) > 0)) return void 0;
2746
+ const parent = meta.parentSession;
2747
+ return typeof parent === "string" && parent !== "" ? parent : void 0;
2748
+ }
2749
+ /**
2750
+ * 给会话行补后代合计(subCost/subCalls):按 parentId 建子索引,对每行递归
2751
+ * 汇总全部后代子会话(多级委派整链计入根对话)。纯展示层归因——不改任何
2752
+ * 总额桶(子会话费用本就在 total/byDay/byModel 里,行级只是拆出来给人看)。
2753
+ * 环引用(异常数据)按 visited 跳过,不死循环。原地修改传入行。导出供测试。
2754
+ */
2755
+ function foldSubagentFamilies(rows) {
2756
+ const subsOf = /* @__PURE__ */ new Map();
2757
+ for (const row of rows) {
2758
+ const parent = row.parentId;
2759
+ if (parent === void 0) continue;
2760
+ const list = subsOf.get(parent);
2761
+ if (list !== void 0) list.push(row);
2762
+ else subsOf.set(parent, [row]);
2763
+ }
2764
+ const familyOf = (id, visited) => {
2765
+ let cost = 0;
2766
+ let calls = 0;
2767
+ for (const child of subsOf.get(id) ?? []) {
2768
+ if (visited.has(child.id)) continue;
2769
+ visited.add(child.id);
2770
+ cost += child.cost;
2771
+ calls += child.calls;
2772
+ const nested = familyOf(child.id, visited);
2773
+ cost += nested.cost;
2774
+ calls += nested.calls;
2775
+ }
2776
+ return {
2777
+ cost,
2778
+ calls
2779
+ };
2780
+ };
2781
+ for (const row of rows) {
2782
+ const family = familyOf(row.id, /* @__PURE__ */ new Set([row.id]));
2783
+ if (family.calls > 0) {
2784
+ row.subCalls = family.calls;
2785
+ if (family.cost > 0) row.subCost = family.cost;
2786
+ }
2787
+ }
2788
+ }
2736
2789
  /** TTFT 尖峰阈值(毫秒):超过计为一次尖峰样本,用于定位服务端抖动。 */
2737
2790
  const PERF_SPIKE_MS = 1e4;
2738
2791
  /** 全零摘要(合并的恒等元)。 */
@@ -3533,11 +3586,12 @@ function createUsageAggregator(persistence, options = {}) {
3533
3586
  * 价目表变化后未受影响行(无 unpriced 模型)依旧零读取复用。 */
3534
3587
  const foldNeedsReprice = (fold) => fold.unpricedModels.some((key) => isPriced(key));
3535
3588
  /** 把一份(全量或增量)折叠结果登记进 durable ledger;内容无变化时不落盘。 */
3536
- const recordLedger = (id, cwd, stamp, fold) => {
3589
+ const recordLedger = (id, cwd, stamp, fold, subOf) => {
3537
3590
  if (options.ledger === void 0) return;
3538
3591
  const entry = {
3539
3592
  id,
3540
3593
  ...cwd === void 0 ? {} : { cwd },
3594
+ ...subOf === void 0 ? {} : { subOf },
3541
3595
  ...stamp === null ? {} : { stamp },
3542
3596
  foldVersion: 23,
3543
3597
  fingerprint: foldFingerprint,
@@ -3545,7 +3599,7 @@ function createUsageAggregator(persistence, options = {}) {
3545
3599
  fold: serializeFold(fold)
3546
3600
  };
3547
3601
  const row = ledger.get(id);
3548
- 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)) {
3602
+ if (row === void 0 || row.stamp !== entry.stamp || row.cwd !== entry.cwd || row.subOf !== entry.subOf || row.foldVersion !== entry.foldVersion || row.fingerprint !== entry.fingerprint || stamp === null && JSON.stringify(row.fold) !== JSON.stringify(entry.fold) || stamp !== null && foldNeedsReprice(row.fold)) {
3549
3603
  ledger.set(id, entry);
3550
3604
  ledgerNeedsSave = true;
3551
3605
  }
@@ -3637,12 +3691,14 @@ function createUsageAggregator(persistence, options = {}) {
3637
3691
  }
3638
3692
  try {
3639
3693
  const cwd = meta.cwd;
3694
+ const subOf = subParentOf(meta);
3640
3695
  const ledgerRow = ledger.get(id);
3641
3696
  if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 23 && ledgerRow.fingerprint === foldFingerprint && !foldNeedsReprice(ledgerRow.fold)) {
3642
3697
  const fold = deserializedFoldOf(ledgerRow.fold);
3643
3698
  folds.push({
3644
3699
  id,
3645
3700
  ...cwd === void 0 ? {} : { cwd },
3701
+ ...subOf === void 0 ? {} : { subOf },
3646
3702
  fold
3647
3703
  });
3648
3704
  included.add(id);
@@ -3655,6 +3711,7 @@ function createUsageAggregator(persistence, options = {}) {
3655
3711
  folds.push({
3656
3712
  id,
3657
3713
  ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
3714
+ ...subOf === void 0 ? {} : { subOf },
3658
3715
  fold: hit.fold
3659
3716
  });
3660
3717
  included.add(id);
@@ -3683,10 +3740,11 @@ function createUsageAggregator(persistence, options = {}) {
3683
3740
  folds.push({
3684
3741
  id,
3685
3742
  ...cwd === void 0 ? {} : { cwd },
3743
+ ...subOf === void 0 ? {} : { subOf },
3686
3744
  fold: previous.fold
3687
3745
  });
3688
3746
  included.add(id);
3689
- recordLedger(id, cwd, stamp, previous.fold);
3747
+ recordLedger(id, cwd, stamp, previous.fold, subOf);
3690
3748
  continue;
3691
3749
  }
3692
3750
  const { events, inheritedEventCount } = await persistence.readFrom(meta.id, sessionLogOffsetOf(0));
@@ -3712,10 +3770,11 @@ function createUsageAggregator(persistence, options = {}) {
3712
3770
  folds.push({
3713
3771
  id,
3714
3772
  ...cwd === void 0 ? {} : { cwd },
3773
+ ...subOf === void 0 ? {} : { subOf },
3715
3774
  fold
3716
3775
  });
3717
3776
  included.add(id);
3718
- recordLedger(id, cwd, stamp, fold);
3777
+ recordLedger(id, cwd, stamp, fold, subOf);
3719
3778
  } catch (error) {
3720
3779
  skipped.push(id);
3721
3780
  const format = isFormatUnsupportedError(error);
@@ -3743,6 +3802,7 @@ function createUsageAggregator(persistence, options = {}) {
3743
3802
  folds.push({
3744
3803
  id: entry.id,
3745
3804
  ...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
3805
+ ...entry.subOf === void 0 ? {} : { subOf: entry.subOf },
3746
3806
  ...stale ? { staleLedger: true } : {},
3747
3807
  fold: deserializedFoldOf(entry.fold)
3748
3808
  });
@@ -3779,7 +3839,7 @@ function createUsageAggregator(persistence, options = {}) {
3779
3839
  const perfHourModel = /* @__PURE__ */ new Map();
3780
3840
  const perfModelDigest = /* @__PURE__ */ new Map();
3781
3841
  const perfHourDigest = /* @__PURE__ */ new Map();
3782
- for (const { id: sessionId, cwd, fold, staleLedger } of folds) {
3842
+ for (const { id: sessionId, cwd, subOf, fold, staleLedger } of folds) {
3783
3843
  mergeUsageInto(total, fold.total);
3784
3844
  roles.userChars += fold.roles.userChars;
3785
3845
  roles.toolChars += fold.roles.toolChars;
@@ -3858,12 +3918,14 @@ function createUsageAggregator(persistence, options = {}) {
3858
3918
  id: sessionId,
3859
3919
  ...fold.title !== void 0 ? { title: fold.title } : {},
3860
3920
  ...cwd !== void 0 ? { cwd } : {},
3921
+ ...subOf !== void 0 ? { parentId: subOf } : {},
3861
3922
  ...staleLedger === true ? { stale: true } : {},
3862
3923
  calls: fold.total.calls,
3863
3924
  cost: fold.total.cost,
3864
3925
  lastActive: fold.lastActive
3865
3926
  });
3866
3927
  }
3928
+ foldSubagentFamilies(sessionRows);
3867
3929
  sessionRows.sort((a, b) => b.cost - a.cost || b.lastActive - a.lastActive);
3868
3930
  turnRows.sort((a, b) => b.startedAt - a.startedAt);
3869
3931
  const workspaces = [...workspaceMap.values()].sort((a, b) => b.cost - a.cost || b.lastActive - a.lastActive);
@@ -389,6 +389,17 @@ export interface SessionUsageRow {
389
389
  title?: string;
390
390
  /** 会话创建时的工作目录(项目路径);未知时缺失。 */
391
391
  cwd?: string;
392
+ /**
393
+ * 直接父会话 id(仅子智能体会话携带):宿主 header 的 `origin: "subagent"`
394
+ * 会话经 parentSession 指向委派它的对话。fork/resume 的种子血缘
395
+ * (isSeeded)不满足子智能体门,不带本字段。旧宿主 header 无标记,
396
+ * 按顶层会话处理。
397
+ */
398
+ parentId?: string;
399
+ /** 全部后代子会话的费用合计(人民币元,多级委派整链递归);无后代或后代全免费时缺失。 */
400
+ subCost?: number;
401
+ /** 全部后代子会话的调用数;无后代时缺失。 */
402
+ subCalls?: number;
392
403
  calls: number;
393
404
  cost: number;
394
405
  /** 最后一个事件的时间戳(毫秒)。 */
@@ -396,6 +407,20 @@ export interface SessionUsageRow {
396
407
  /** 数据来自旧算法折叠的持久账本行(日志已删/不可读,无法重算);UI 据此标注置信度。 */
397
408
  stale?: boolean;
398
409
  }
410
+ /**
411
+ * 从会话 header 提取子智能体归并标记(直接父会话 id):`origin: "subagent"`
412
+ * (宿主的产品分类)或 `delegationDepth > 0`(持久化委派深度)任一命中即视为
413
+ * 子会话。子会话但 header 无 parentSession(异常数据)返回 undefined——无法
414
+ * 归位就按顶层会话处理,保守不标。导出供测试。
415
+ */
416
+ export declare function subParentOf(meta: SessionHeader): string | undefined;
417
+ /**
418
+ * 给会话行补后代合计(subCost/subCalls):按 parentId 建子索引,对每行递归
419
+ * 汇总全部后代子会话(多级委派整链计入根对话)。纯展示层归因——不改任何
420
+ * 总额桶(子会话费用本就在 total/byDay/byModel 里,行级只是拆出来给人看)。
421
+ * 环引用(异常数据)按 visited 跳过,不死循环。原地修改传入行。导出供测试。
422
+ */
423
+ export declare function foldSubagentFamilies(rows: SessionUsageRow[]): void;
399
424
  /** 每轮费用明细行:仪表盘「每轮费用」图的数据源。 */
400
425
  export interface TurnUsageRow {
401
426
  /** 会话 id(字符串形式):不同会话的轮次号相互独立,展示时需区分。 */
@@ -563,6 +588,12 @@ export interface SerializedSessionFold {
563
588
  export interface UsageLedgerSession {
564
589
  id: string;
565
590
  cwd?: string;
591
+ /**
592
+ * 子智能体归并标记(直接父会话 id):仅宿主 header 带 `origin: "subagent"`
593
+ * 的会话行携带。日志已删、只剩账本行时仍能归位到父对话;缺失 = 顶层会话
594
+ * (含 fork/resume 血缘)。可选字段,旧账本行缺失按顶层处理,无需迁移。
595
+ */
596
+ subOf?: string;
566
597
  /** Stable log stamp (mtime + size) when the persistence backend exposes it. */
567
598
  stamp?: string;
568
599
  /**
@@ -178,6 +178,15 @@ interface SessionBillingRow {
178
178
  id: string;
179
179
  title?: string;
180
180
  cwd?: string;
181
+ /**
182
+ * 直接父会话 id(仅子智能体会话携带):宿主把子智能体落成独立会话,
183
+ * header 的 origin: subagent + parentSession 指向委派它的对话。
184
+ */
185
+ parentId?: string;
186
+ /** 全部后代子会话费用合计(服务端递归算好);无后代时缺失。 */
187
+ subCost?: number;
188
+ /** 全部后代子会话调用数;无后代时缺失。 */
189
+ subCalls?: number;
181
190
  /** 数据来自旧算法折叠的持久账本行(原始日志已删,无法重算)。 */
182
191
  stale?: boolean;
183
192
  calls: number;
@@ -187,6 +196,8 @@ interface SessionBillingRow {
187
196
  mergedCount?: number;
188
197
  /** 合并组的段明细(展示层专用):一级行默认收起,展开后逐段显示。合并函数需 push,故非 readonly。 */
189
198
  children?: SessionBillingRow[];
199
+ /** 挂到本组下的子智能体行(展示层专用):与段明细并列展开,带子智能体徽标。 */
200
+ subs?: SessionBillingRow[];
190
201
  }
191
202
  /** Usage stats structure from `.dsh-usage-stats.json`. */
192
203
  export interface UsageStats {
@@ -18,6 +18,12 @@ export interface SessionExportRow {
18
18
  id: string;
19
19
  title?: string;
20
20
  cwd?: string;
21
+ /** 直接父会话 id(仅子智能体会话携带);顶层会话缺失。 */
22
+ parentId?: string;
23
+ /** 后代子会话费用合计(人民币元);无后代时缺失。 */
24
+ subCost?: number;
25
+ /** 后代子会话调用数;无后代时缺失。 */
26
+ subCalls?: number;
21
27
  calls: number;
22
28
  cost: number;
23
29
  lastActive: number;
@@ -26,7 +32,7 @@ export interface SessionExportRow {
26
32
  export declare function dayRowsCsv(byDay: Record<string, DayExportRow>): string;
27
33
  /** 按站点(中转站/直连/未知路由)CSV:站点,类别,调用,费用(元)。 */
28
34
  export declare function siteRowsCsv(bySite: Record<string, DayExportRow>): string;
29
- /** 按会话 CSV:会话 id,标题,项目,调用,费用(元),最后活跃(ISO)。 */
35
+ /** 按会话 CSV:会话 id,标题,项目,调用,费用(元),最后活跃(ISO),父会话,子会话调用,子会话费用(元)。 */
30
36
  export declare function sessionRowsCsv(rows: readonly SessionExportRow[]): string;
31
37
  /** 导出文件名:带日期范围(usage-2026-08-01_2026-08-22.csv);无日期时只带前缀。 */
32
38
  export declare function exportFileName(prefix: string, ext: 'csv' | 'json', dates: readonly string[]): string;
@@ -22,6 +22,7 @@ export interface LiveStats {
22
22
  bySession?: readonly {
23
23
  id: string;
24
24
  cost: number;
25
+ subCost?: number;
25
26
  }[];
26
27
  byTurn?: readonly {
27
28
  sessionId: string;
@@ -40,13 +41,22 @@ export interface QuotaSlice {
40
41
  }[];
41
42
  }
42
43
  /**
43
- * 当前会话累计费用:bySession 里会话 id 匹配的那行;缺省为 0。
44
- * 导出供测试:纯函数。
44
+ * 当前会话累计费用:bySession 里会话 id 匹配的那行(含后代子智能体费用
45
+ * subCost——子智能体是宿主的独立会话,其消耗按父链并入本对话的口径);
46
+ * 缺省为 0。导出供测试:纯函数。
45
47
  * @param stats - 薄统计切片。
46
48
  * @param sessionId - 当前会话 id。
47
- * @returns 该会话累计费用(人民币元)。
49
+ * @returns 该会话累计费用(人民币元,含子智能体)。
48
50
  */
49
51
  export declare function sessionCostOf(stats: LiveStats | null, sessionId: string | undefined): number;
52
+ /**
53
+ * 当前会话的后代子智能体费用(bySession 行的 subCost):供「含子」拆分展示,
54
+ * 让胶囊上的总额与本体会话费用对得上账。导出供测试:纯函数。
55
+ * @param stats - 薄统计切片。
56
+ * @param sessionId - 当前会话 id。
57
+ * @returns 后代子会话费用合计(人民币元);无则 0。
58
+ */
59
+ export declare function sessionSubCostOf(stats: LiveStats | null, sessionId: string | undefined): number;
50
60
  /**
51
61
  * 当前轮费用:byTurn 里该会话最新一轮的 cost;缺省为 0。
52
62
  * byTurn 服务端按起始时间倒序下发,但求 max(turn) 更稳健(不依赖顺序)。
@@ -1,5 +1,5 @@
1
1
  /** Locale dictionaries for the usage billing surface. */
2
- export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'kpiRange' | 'avgRangeToday' | 'avgRange7d' | 'avgRangeWeek' | 'avgRangeMonth' | 'avgRangeAll' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'pricingSearch' | 'bandPinHint' | 'trendEmpty' | 'trendTotal' | 'roundsUnit' | 'healthModelsLive' | 'healthVendorsDown' | 'healthVendorsUnavailable' | 'budget' | 'budgetRemain' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'channelUnknown' | 'directTag' | 'relayTag' | 'unknownTag' | 'unknownChannelHint' | 'planCountUnit' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'pricingSyncedAt' | 'pricingSyncNow' | 'pricingSyncing' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'recharge' | 'rechargeHint' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatPrimaryToday' | 'floatPrimaryWeek' | 'floatPrimaryMonth' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'cardSpan' | 'cardSpanDay' | 'cardSpanWeek' | 'cardSpanMonth' | 'triggerMonthTokens' | 'triggerTodayTokens' | 'triggerWeekTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'unreadableNotice' | 'unreadableFormatNotice' | 'unreadableNoLedgerNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'sessionUntitled' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'currencyEur' | 'language' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'peakAlertDescPeakZhipu' | 'peakAlertDescOffZhipu' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'tierAlertEnterPeakZhipu' | 'tierAlertEnterOffZhipu' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | 'subscriptionTag' | 'cellEstimateHint' | 'free' | 'perfSamples' | 'perfTtft' | 'perfP50' | 'perfP90' | 'perfTps' | 'perfLatency' | 'perfEstimated' | 'perfEmpty' | 'perfTpsUnit' | 'perfTitle' | 'perfHint' | 'perfAll' | 'perfChartEmpty' | 'heatmapYear' | 'heatmapHalf' | 'heatmapMonth' | 'metricCost' | 'metricTokens' | 'heatmapTotal' | 'activeDays' | 'streakDays' | 'subscriptionAutoDetect' | 'pluginAuthor' | 'pluginRepository' | 'pluginNpm' | 'pluginLicense' | 'tabToken' | 'tokenExport' | 'tokenExportCsv' | 'tokenReasoningShare' | 'tokenReasoningShort' | 'tokenIo' | 'tokenPeak' | 'densityLabel' | 'densityHint' | 'densityCompact' | 'densityDefault' | 'densityComfortable' | 'motionLabel' | 'motionHint' | 'motionOff' | 'motionSubtle' | 'motionRich' | 'settingsNavBudget' | 'settingsNavAlert' | 'settingsNavDisplay' | 'settingsNavAdvanced' | 'settingsNavAbout' | 'settingsSectionDisplay' | 'emptyHint' | 'tokenDaily' | 'tokenByModel' | 'tokenMiss' | 'tokenHit' | 'tokenOutput' | 'tokenViewStructure' | 'tokenViewModel' | 'tokenHitShort' | 'tokenMissShort' | 'tokenTotal' | 'tokenShare' | 'usageStatsTool' | 'usageStatsToolHint' | 'balanceGranted' | 'balanceTopped' | 'balanceDaily' | 'balanceDaysLong' | 'balanceDaysUnit' | 'popTodayModel' | 'popNoConsumption' | 'popTitle' | 'popDirectLead' | 'popSubLead' | 'unpricedHint' | 'searchEstimateHint' | 'siteListDisplay' | 'siteListDisplayHint' | 'liveCostBar' | 'liveCostBarHint' | 'liveCostBarPosition' | 'liveCostBarPosBelow' | 'liveCostBarPosAbove' | 'liveCostBarPosToolbar' | 'exportCsvSite' | 'panelRelayQuota' | 'relayBalance' | 'relayNoQuota' | 'relayWindowUsed' | 'relayKindNewApi' | 'relayKindSub2Api' | 'relayKindUnknown' | 'expandAll' | 'collapseAll' | 'todayOnly' | 'todayNoConsumption';
2
+ export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'liveSessionSub' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'kpiRange' | 'avgRangeToday' | 'avgRange7d' | 'avgRangeWeek' | 'avgRangeMonth' | 'avgRangeAll' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'pricingSearch' | 'bandPinHint' | 'trendEmpty' | 'trendTotal' | 'roundsUnit' | 'healthModelsLive' | 'healthVendorsDown' | 'healthVendorsUnavailable' | 'budget' | 'budgetRemain' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'channelUnknown' | 'directTag' | 'relayTag' | 'unknownTag' | 'unknownChannelHint' | 'planCountUnit' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'pricingSyncedAt' | 'pricingSyncNow' | 'pricingSyncing' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'recharge' | 'rechargeHint' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatPrimaryToday' | 'floatPrimaryWeek' | 'floatPrimaryMonth' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'cardSpan' | 'cardSpanDay' | 'cardSpanWeek' | 'cardSpanMonth' | 'cardDeltaBadge' | 'cardDeltaShow' | 'cardDeltaHide' | 'triggerMonthTokens' | 'triggerTodayTokens' | 'triggerWeekTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'unreadableNotice' | 'unreadableFormatNotice' | 'unreadableNoLedgerNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'sessionUntitled' | 'sessionSubCost' | 'sessionSubCalls' | 'sessionSubRow' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'currencyEur' | 'language' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'peakAlertDescPeakZhipu' | 'peakAlertDescOffZhipu' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'tierAlertEnterPeakZhipu' | 'tierAlertEnterOffZhipu' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | 'subscriptionTag' | 'cellEstimateHint' | 'free' | 'perfSamples' | 'perfTtft' | 'perfP50' | 'perfP90' | 'perfTps' | 'perfLatency' | 'perfEstimated' | 'perfEmpty' | 'perfTpsUnit' | 'perfTitle' | 'perfHint' | 'perfAll' | 'perfChartEmpty' | 'heatmapYear' | 'heatmapHalf' | 'heatmapMonth' | 'metricCost' | 'metricTokens' | 'heatmapTotal' | 'activeDays' | 'streakDays' | 'subscriptionAutoDetect' | 'pluginAuthor' | 'pluginRepository' | 'pluginNpm' | 'pluginLicense' | 'tabToken' | 'tokenExport' | 'tokenExportCsv' | 'tokenReasoningShare' | 'tokenReasoningShort' | 'tokenIo' | 'tokenPeak' | 'densityLabel' | 'densityHint' | 'densityCompact' | 'densityDefault' | 'densityComfortable' | 'motionLabel' | 'motionHint' | 'motionOff' | 'motionSubtle' | 'motionRich' | 'settingsNavBudget' | 'settingsNavAlert' | 'settingsNavDisplay' | 'settingsNavAdvanced' | 'settingsNavAbout' | 'settingsSectionDisplay' | 'emptyHint' | 'tokenDaily' | 'tokenByModel' | 'tokenMiss' | 'tokenHit' | 'tokenOutput' | 'tokenViewStructure' | 'tokenViewModel' | 'tokenHitShort' | 'tokenMissShort' | 'tokenTotal' | 'tokenShare' | 'usageStatsTool' | 'usageStatsToolHint' | 'balanceGranted' | 'balanceTopped' | 'balanceDaily' | 'balanceDaysLong' | 'balanceDaysUnit' | 'popTodayModel' | 'popNoConsumption' | 'popTitle' | 'popDirectLead' | 'popSubLead' | 'unpricedHint' | 'searchEstimateHint' | 'siteListDisplay' | 'siteListDisplayHint' | 'liveCostBar' | 'liveCostBarHint' | 'liveCostBarPosition' | 'liveCostBarPosBelow' | 'liveCostBarPosAbove' | 'liveCostBarPosToolbar' | 'exportCsvSite' | 'panelRelayQuota' | 'relayBalance' | 'relayNoQuota' | 'relayWindowUsed' | 'relayKindNewApi' | 'relayKindSub2Api' | 'relayKindUnknown' | 'expandAll' | 'collapseAll' | 'todayOnly' | 'todayNoConsumption';
3
3
  export declare const NS = "usageBilling";
4
4
  export declare const zh: Record<UsageBillingKey, string>;
5
5
  export declare const en: Record<UsageBillingKey, string>;
@@ -54,8 +54,10 @@ export interface BillingCardPrefs {
54
54
  metric: BillingCardMetric;
55
55
  /** 主数字统计范围:今日 / 本周 / 本月(默认今日)。 */
56
56
  span: BillingCardSpan;
57
+ /** 是否显示「今日环比」涨跌徽章(默认显示;issue #93:部分用户觉得红色横条太醒目)。 */
58
+ showDelta: boolean;
57
59
  }
58
- /** 默认计费卡偏好:花费金额 + 今日口径。 */
60
+ /** 默认计费卡偏好:花费金额 + 今日口径 + 显示环比徽章(保持 v1.5.0 行为)。 */
59
61
  export declare const DEFAULT_BILLING_CARD_PREFS: BillingCardPrefs;
60
62
  /** localStorage key(与 budget store 的 `dsh.ui-usage-billing.*` 命名空间一致)。 */
61
63
  export declare const BILLING_CARD_STORAGE_KEY = "dsh.ui-usage-billing.card";
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.5.0",
4
+ "version": "1.5.1",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "deepseek",
@@ -77,7 +77,8 @@
77
77
  "0.2.0-rc.2": "compatible",
78
78
  "0.2.1-alpha.1": "compatible",
79
79
  "0.2.1-alpha.2": "compatible",
80
- "0.2.1-alpha.3": "compatible"
80
+ "0.2.1-alpha.3": "compatible",
81
+ "0.2.1-rc.2": "compatible"
81
82
  }
82
83
  }
83
84
  },
@@ -122,35 +123,36 @@
122
123
  "@deepseek-ai/cordis": "*",
123
124
  "@deepseek-ai/cordis-plugin-include": "^1.0.7",
124
125
  "@deepseek-ai/cordis-plugin-loader": "^1.0.5",
125
- "@deepseek-ai/dsh-api-gateway": "0.1.7-rc.1",
126
- "@deepseek-ai/dsh-api-remotes": "0.1.7-rc.1",
127
- "@deepseek-ai/dsh-api-session-controller": "0.1.7-rc.1",
128
- "@deepseek-ai/dsh-atomic-write": "0.1.7-rc.1",
129
- "@deepseek-ai/dsh-agent": "0.1.7-rc.1",
130
- "@deepseek-ai/dsh-client-locale": "0.1.7-rc.1",
131
- "@deepseek-ai/dsh-client-store": "0.1.7-rc.1",
132
- "@deepseek-ai/dsh-client-ui-conversation": "0.1.7-rc.1",
133
- "@deepseek-ai/dsh-client-ui-primitives": "0.1.7-rc.1",
134
- "@deepseek-ai/dsh-client-ui-renderer": "0.1.7-rc.1",
135
- "@deepseek-ai/dsh-client-ui-sidebar": "0.1.7-rc.1",
136
- "@deepseek-ai/dsh-client-ui-slots": "0.1.7-rc.1",
137
- "@deepseek-ai/dsh-credentials": "0.1.7-rc.1",
138
- "@deepseek-ai/dsh-host-webserver": "0.1.7-rc.1",
139
- "@deepseek-ai/dsh-home-paths": "0.1.7-rc.1",
140
- "@deepseek-ai/dsh-invariants": "0.1.7-rc.1",
141
- "@deepseek-ai/dsh-llm": "0.1.7-rc.1",
142
- "@deepseek-ai/dsh-ptc-runtime": "0.1.7-rc.1",
143
- "@deepseek-ai/dsh-sandbox": "0.1.7-rc.1",
144
- "@deepseek-ai/dsh-sandbox-policy": "0.1.7-rc.1",
145
- "@deepseek-ai/dsh-scope": "0.1.7-rc.1",
146
- "@deepseek-ai/dsh-session": "0.1.7-rc.1",
147
- "@deepseek-ai/dsh-session-persistence": "0.1.7-rc.1",
148
- "@deepseek-ai/dsh-settings": "0.1.7-rc.1",
149
- "@deepseek-ai/dsh-system-prompt": "0.1.7-rc.1",
150
- "@deepseek-ai/dsh-tools": "0.1.7-rc.1",
151
- "@deepseek-ai/dsh-typert-protocol": "0.1.7-rc.1",
152
- "@deepseek-ai/dsh-user-approval": "0.1.7-rc.1",
153
- "@deepseek-ai/dsh-util-workspace-path": "0.1.7-rc.1",
126
+ "@deepseek-ai/dsh-api-gateway": "0.2.1-rc.2",
127
+ "@deepseek-ai/dsh-api-remotes": "0.2.1-rc.2",
128
+ "@deepseek-ai/dsh-api-session-controller": "0.2.1-rc.2",
129
+ "@deepseek-ai/dsh-atomic-write": "0.2.1-rc.2",
130
+ "@deepseek-ai/dsh-agent": "0.2.1-rc.2",
131
+ "@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
132
+ "@deepseek-ai/dsh-client-store": "0.2.1-rc.2",
133
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.2",
134
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
135
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.2",
136
+ "@deepseek-ai/dsh-client-ui-sidebar": "0.2.1-rc.2",
137
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
138
+ "@deepseek-ai/dsh-credentials": "0.2.1-rc.2",
139
+ "@deepseek-ai/dsh-host-webserver": "0.2.1-rc.2",
140
+ "@deepseek-ai/dsh-home-paths": "0.2.1-rc.2",
141
+ "@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
142
+ "@deepseek-ai/dsh-util-code-language": "0.2.1-rc.2",
143
+ "@deepseek-ai/dsh-llm": "0.2.1-rc.2",
144
+ "@deepseek-ai/dsh-ptc-runtime": "0.2.1-rc.2",
145
+ "@deepseek-ai/dsh-sandbox": "0.2.1-rc.2",
146
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.2",
147
+ "@deepseek-ai/dsh-scope": "0.2.1-rc.2",
148
+ "@deepseek-ai/dsh-session": "0.2.1-rc.2",
149
+ "@deepseek-ai/dsh-session-persistence": "0.2.1-rc.2",
150
+ "@deepseek-ai/dsh-settings": "0.2.1-rc.2",
151
+ "@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
152
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
153
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
154
+ "@deepseek-ai/dsh-user-approval": "0.2.1-rc.2",
155
+ "@deepseek-ai/dsh-util-workspace-path": "0.2.1-rc.2",
154
156
  "@deepseek-ai/schemastery": "^3.18.4",
155
157
  "@shikijs/langs": "^4.3.1",
156
158
  "@testing-library/dom": "^10.4.1",