@kenz1117/dsh-ui-usage-billing 1.4.29 → 1.4.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -264,7 +264,7 @@ npm publish --access public
264
264
  ## ⚠️ Known Limitations and Deferred Work
265
265
 
266
266
  - **余额查询已接入 DeepSeek / 月之暗面(Kimi)/ 阶跃星辰(StepFun)/ 硅基流动 / xAI / 智谱 GLM(Z.ai 国内域)/ 腾讯云 TokenHub(Token Plan 余量)**:前六家用标准 Bearer API key,腾讯云用云 API 密钥对(TC3 签名)。其余厂商因无公开余额接口或需非 Bearer 鉴权(小米 MiMo 走控制台 Cookie、商汤走 AccessKey 签名、MiniMax/字节豆包走额度制或 AK/SK),暂显示「未配置」;扩展点在 `src/balance.ts`(按厂商余额 API 增加查询器)。
267
- - **中转站额度依赖上游私有 schema**:New API / Sub2API 的接口字段未公开,读不出时标「未读出额度」而非臆造金额;若某中转站响应字段不同,需按 `src/relay.ts` 的解析器扩展。未知路由表示该路由在当前 provider 配置里已不存在(改过名 / 删除过),历史调用数据未丢,重新配置同名路由即可自动归位。未知路由桶的费用一律记 0(issue #82):支出主体无法核实,与未收录模型、订阅通道同姿态——token 照常统计,面板组头费用显示「—」并附说明。
267
+ - **中转站额度依赖上游私有 schema**:New API / Sub2API 的接口字段未公开,读不出时标「未读出额度」而非臆造金额;若某中转站响应字段不同,需按 `src/relay.ts` 的解析器扩展。未知路由表示该路由在当前 provider 配置与宿主 llm 注册表里都不存在(配置已删、日志比安装更早等),历史调用数据未丢,重新配置同名路由即可自动归位。未知路由桶的费用一律记 0(issue #82):支出主体无法核实,与未收录模型、订阅通道同姿态——token 照常统计,面板组头费用显示「—」并附说明。由独立插件经 `ctx.llm.registerAdapter` 注册的通道(如 CommandCode)不属于未知路由:插件从宿主 llm 注册表发现它们,按 `direct:<provider>` 桶以模型官方目录价计费(issue #91)。
268
268
  - **超支通知依赖浏览器 Notification**:权限被拒绝或平台不支持时只有界面红色脉冲兜底,没有宿主级通知通道;通知上限为每天一次。
269
269
  - **会话明细不可跳转**:点击会话行不会打开对应会话(跨插件导航需要宿主会话选择通道);会话数封顶 100 行、面板只显示前 20 行。
270
270
  - **费用为目录价估算**:讯飞 / 商汤 / 小米等未公布按量单价的模型使用估算价(特性表脚注 ¹),正式定价以厂商账单为准。
package/lib/index.js CHANGED
@@ -2301,6 +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/deepseek-v4.1-flash": "flash",
2305
+ "deepseek/deepseek-flash": "flash",
2306
+ "deepseek/deepseek-v4-pro": "pro",
2304
2307
  "deepseek-v4-pro": "pro",
2305
2308
  "glm-5.2": "glm",
2306
2309
  "glm-4.5-air": "glm-4.5-air",
@@ -2491,6 +2494,29 @@ const DEFAULT_SUBSCRIPTION_PROVIDERS = [
2491
2494
  function isOfficialProvider(provider) {
2492
2495
  return /^deepseek(?:-[a-z0-9-]+)?$/i.test(provider.trim());
2493
2496
  }
2497
+ /**
2498
+ * 已知的「第三方插件注册 adapter」通道名单:这些 provider 由别的插件经
2499
+ * `ctx.llm.registerAdapter` 注册,**不经 llm-pi-ai 路由表**,因此
2500
+ * {@link siteRefOf} 的路由查询必然 miss。与订阅豁免(issue #37 的 grok
2501
+ * build)同构:显式列出、不做「猜通道」的通用发现——猜错会把来源不明的
2502
+ * 路由也归位成可计价通道,违背 issue #82「支出主体无法核实不计价」的初衷。
2503
+ *
2504
+ * 收录语义:这些通道的支出主体是可核实的(WorkBuddy = 腾讯 WorkBuddy
2505
+ * 桌面 App 账号),计费用模型官方目录价折算 Token 成本,与积分制解耦。
2506
+ * 它们**不是** DeepSeek 官方渠道——{@link isOfficialProvider} 与
2507
+ * {@link officialChannelOf} 都不会因本名单把调用算进官方口径。
2508
+ */
2509
+ const KNOWN_ADAPTER_PROVIDERS = /* @__PURE__ */ new Set(["workbuddy", "workbuddy-ai"]);
2510
+ /**
2511
+ * 一个 provider 是否命中已知 adapter 通道名单。判定与 {@link isOfficialProvider}
2512
+ * 同姿态:trim + 大小写不敏感——dsh-workbuddy-connect 注册的是小写 id
2513
+ *(`workbuddy` / `workbuddy-ai`),宿主不强制 route 小写,按大小写归一化可避免
2514
+ * 日志里出现大小写变体时静默落回 unknown 桶。路由表命中仍优先于本名单
2515
+ *(见 {@link siteRefOf}),用户自配的大小写变体走配置路径。
2516
+ */
2517
+ function isKnownAdapterProvider(provider) {
2518
+ return KNOWN_ADAPTER_PROVIDERS.has(provider.trim().toLowerCase());
2519
+ }
2494
2520
  /** DeepSeek 官方直连端点的归一化 origin(`siteOriginOf` 口径):有 baseURL 的路由只有它算官方。 */
2495
2521
  const OFFICIAL_DEEPSEEK_ORIGIN = "https://api.deepseek.com";
2496
2522
  /**
@@ -2527,8 +2553,10 @@ function siteOriginOf(baseURL) {
2527
2553
  * 网关)按 `direct` 归位——unknown 一刀切会把官方直连的费用堆进「未知路由」组、
2528
2554
  * 且不算官方渠道(回归修复);订阅豁免命中的通道(显式 `subscriptionProviders`
2529
2555
  * 配置或判定函数)同样按 `direct` 归位——订阅管理类插件注册的通道不经路由表,
2530
- * unknown 桶既掩盖归属又按 token 误计费(issue #37 的 grok build);其余配置外
2531
- * 名称保留 unknown(无法核实通道)。
2556
+ * unknown 桶既掩盖归属又按 token 误计费(issue #37 的 grok build);已知的第三方
2557
+ * 插件注册 adapter 通道({@link KNOWN_ADAPTER_PROVIDERS},如 workbuddy)同样按
2558
+ * `direct` 归位——支出主体可核实、按模型官方目录价计 Token 成本,unknown 桶会让
2559
+ * 费用恒为 0;其余配置外名称保留 unknown(无法核实通道)。
2532
2560
  * @param provider - 会话日志里的 provider 路由名(request/header 的 `config.provider`)。
2533
2561
  * @param routes - 当前 provider 路由视图(来自 llm-pi-ai providers)。
2534
2562
  * @param opts - `subscription`:该调用是否已被订阅豁免判定命中。
@@ -2554,6 +2582,10 @@ function siteRefOf(provider, routes, opts = {}) {
2554
2582
  kind: "direct",
2555
2583
  provider
2556
2584
  };
2585
+ if (isKnownAdapterProvider(provider)) return {
2586
+ kind: "direct",
2587
+ provider
2588
+ };
2557
2589
  return {
2558
2590
  kind: "unknown",
2559
2591
  provider
@@ -3378,12 +3410,19 @@ function hourPerfOf(digest) {
3378
3410
  * 订阅豁免),历史账本行立即失效并全量重折,配置变更即时生效(issue #37)。
3379
3411
  * 判定函数无法序列化,统一记为 `fn`(任何函数形态互视为同一指纹)。
3380
3412
  */
3381
- function configFingerprint(subscriptionMatcher, officialProviderIds, routeAliases, searchEstimate) {
3413
+ function configFingerprint(subscriptionMatcher, officialProviderIds, routeAliases, searchEstimate, modelKeyAliases = {}, registeredProviders = /* @__PURE__ */ new Set()) {
3414
+ const subs = typeof subscriptionMatcher === "function" ? "fn" : [...subscriptionMatcher].sort().join(",");
3415
+ const officials = officialProviderIds === void 0 ? "" : [...officialProviderIds].sort().join(",");
3416
+ const aliases = Object.keys(routeAliases).sort().map((key) => `${key}=${routeAliases[key]}`).join(",");
3417
+ const modelKeys = Object.keys(modelKeyAliases).sort().map((key) => `${key}=${modelKeyAliases[key]}`).join(",");
3418
+ const registered = [...registeredProviders].sort().join(",");
3382
3419
  return [
3383
- typeof subscriptionMatcher === "function" ? "fn" : [...subscriptionMatcher].sort().join(","),
3384
- officialProviderIds === void 0 ? "" : [...officialProviderIds].sort().join(","),
3385
- Object.keys(routeAliases).sort().map((key) => `${key}=${routeAliases[key]}`).join(","),
3386
- String(searchEstimate)
3420
+ subs,
3421
+ officials,
3422
+ aliases,
3423
+ String(searchEstimate),
3424
+ modelKeys,
3425
+ registered
3387
3426
  ].join("|");
3388
3427
  }
3389
3428
  /** 会话格式拒读(宿主 SessionFormatUnsupportedError):日志完好,仅本构建读不了。
@@ -3414,11 +3453,31 @@ function createUsageAggregator(persistence, options = {}) {
3414
3453
  let ledgerAppliedMigrations;
3415
3454
  let lastDoc;
3416
3455
  let lastAt = 0;
3417
- /** 每次聚合取最新的 provider 路由视图(中转站零配置发现);缺省按空处理(全部未知路由)。 */
3418
- const routesOf = () => options.resolveRoutes?.() ?? {};
3456
+ /** 每次聚合取最新的 provider 路由视图(中转站零配置发现);缺省按空处理(全部未知路由)。
3457
+ * 叠加已注册 adapter 的通道(issue #91):宿主 llm 注册表里的 provider 合成无 baseURL
3458
+ * 的空路由视图——siteRefOf 的路由命中分支正是「无 origin 直连」的语义,归位
3459
+ * `direct:<provider>` 按官方目录价计费;llm-pi-ai 已有配置的路由优先(其 origin 可还原)。 */
3460
+ const routesOf = () => {
3461
+ const routes = { ...options.resolveRoutes?.() };
3462
+ const registered = options.resolveRegisteredProviders?.();
3463
+ if (registered !== void 0) {
3464
+ for (const provider of registered) if (routes[provider] === void 0) routes[provider] = {};
3465
+ }
3466
+ return routes;
3467
+ };
3419
3468
  const searchEstimate = options.searchCallEstimateCny ?? .02;
3420
- const foldFingerprint = configFingerprint(subscriptionMatcher, officialProviderIds, routeAliases, searchEstimate);
3469
+ const foldFingerprintOf = () => configFingerprint(subscriptionMatcher, officialProviderIds, routeAliases, searchEstimate, options.modelKeyAliases, options.resolveRegisteredProviders?.() ?? /* @__PURE__ */ new Set());
3470
+ let foldFingerprint = foldFingerprintOf();
3421
3471
  const FOLD_CHUNK_EVENTS = 8e3;
3472
+ /** 惰性失效(价目表时序):账本/缓存行若以「当时不可计价」的模型落盘,
3473
+ * 而价目表现在能计价了,该行必须重折——否则显示层按实时价算出的
3474
+ * 「目录价预估」与服务端 cost=0 并存,出现「明细比总额高」的双口径
3475
+ * (真机复现:预热折叠早于 models.dev 价目表注入,step-5-preview
3476
+ * 折叠时刻 cost=0、渲染时刻 ≈¥38)。复用判定据此刻检查:不走配置指纹
3477
+ * (价目表不进指纹,否则 6h 一次的价格刷新会让全部历史行全量重折),
3478
+ * 也不 bump FOLD_VERSION(那是归账语义版本,价目表时序不是语义变更)。
3479
+ * 价目表变化后未受影响行(无 unpriced 模型)依旧零读取复用。 */
3480
+ const foldNeedsReprice = (fold) => fold.unpricedModels.some((key) => isPriced(key));
3422
3481
  /** 把一份(全量或增量)折叠结果登记进 durable ledger;内容无变化时不落盘。 */
3423
3482
  const recordLedger = (id, cwd, stamp, fold) => {
3424
3483
  if (options.ledger === void 0) return;
@@ -3426,12 +3485,12 @@ function createUsageAggregator(persistence, options = {}) {
3426
3485
  id,
3427
3486
  ...cwd === void 0 ? {} : { cwd },
3428
3487
  ...stamp === null ? {} : { stamp },
3429
- foldVersion: 20,
3488
+ foldVersion: 22,
3430
3489
  fingerprint: foldFingerprint,
3431
3490
  fold: serializeFold(fold)
3432
3491
  };
3433
3492
  const row = ledger.get(id);
3434
- 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)) {
3493
+ 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)) {
3435
3494
  ledger.set(id, entry);
3436
3495
  ledgerNeedsSave = true;
3437
3496
  }
@@ -3490,6 +3549,7 @@ function createUsageAggregator(persistence, options = {}) {
3490
3549
  const aggregateOnce = async () => {
3491
3550
  const now = Date.now();
3492
3551
  await ensureLedgerLoaded();
3552
+ foldFingerprint = foldFingerprintOf();
3493
3553
  let metas;
3494
3554
  try {
3495
3555
  metas = await persistence.list();
@@ -3518,22 +3578,10 @@ function createUsageAggregator(persistence, options = {}) {
3518
3578
  if (!ledger.has(id)) noLedgerUnreadable += 1;
3519
3579
  continue;
3520
3580
  }
3521
- const hit = cache.get(id);
3522
- if (hit !== void 0 && stamp !== null && hit.stamp === stamp) {
3523
- cache.delete(id);
3524
- cache.set(id, hit);
3525
- folds.push({
3526
- id,
3527
- ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
3528
- fold: hit.fold
3529
- });
3530
- included.add(id);
3531
- continue;
3532
- }
3533
3581
  try {
3534
3582
  const cwd = meta.cwd;
3535
3583
  const ledgerRow = ledger.get(id);
3536
- if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 20 && ledgerRow.fingerprint === foldFingerprint) {
3584
+ if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 22 && ledgerRow.fingerprint === foldFingerprint && !foldNeedsReprice(ledgerRow.fold)) {
3537
3585
  const fold = deserializeFold(ledgerRow.fold);
3538
3586
  folds.push({
3539
3587
  id,
@@ -3543,8 +3591,20 @@ function createUsageAggregator(persistence, options = {}) {
3543
3591
  included.add(id);
3544
3592
  continue;
3545
3593
  }
3594
+ const hit = cache.get(id);
3595
+ if (hit !== void 0 && stamp !== null && hit.stamp === stamp && hit.fingerprint === foldFingerprint && ![...hit.fold.unpricedModels].some((key) => isPriced(key))) {
3596
+ cache.delete(id);
3597
+ cache.set(id, hit);
3598
+ folds.push({
3599
+ id,
3600
+ ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
3601
+ fold: hit.fold
3602
+ });
3603
+ included.add(id);
3604
+ continue;
3605
+ }
3546
3606
  const previous = cache.get(id);
3547
- if (previous?.machine !== void 0 && previous.lastSeq !== void 0) {
3607
+ if (previous?.machine !== void 0 && previous.lastSeq !== void 0 && previous.fingerprint === foldFingerprint && ![...previous.fold.unpricedModels].some((key) => isPriced(key))) {
3548
3608
  const from = previous.lastSeq + 1;
3549
3609
  const { events, inheritedEventCount } = await persistence.readFrom(meta.id, sessionLogOffsetOf(from));
3550
3610
  const after = await stampOf(meta);
@@ -3589,7 +3649,8 @@ function createUsageAggregator(persistence, options = {}) {
3589
3649
  stamp,
3590
3650
  fold,
3591
3651
  machine,
3592
- ...last !== void 0 && typeof last.seq === "number" ? { lastSeq: last.seq } : {}
3652
+ ...last !== void 0 && typeof last.seq === "number" ? { lastSeq: last.seq } : {},
3653
+ fingerprint: foldFingerprint
3593
3654
  });
3594
3655
  folds.push({
3595
3656
  id,
@@ -3621,7 +3682,7 @@ function createUsageAggregator(persistence, options = {}) {
3621
3682
  for (const entry of ledger.values()) {
3622
3683
  if (included.has(entry.id)) continue;
3623
3684
  try {
3624
- const stale = (entry.foldVersion ?? 1) < 20 || entry.fingerprint !== foldFingerprint;
3685
+ const stale = (entry.foldVersion ?? 1) < 22 || entry.fingerprint !== foldFingerprint;
3625
3686
  folds.push({
3626
3687
  id: entry.id,
3627
3688
  ...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
@@ -7177,10 +7238,19 @@ function apply(ctx, config = {}) {
7177
7238
  const workspaceTitleResolver = buildWorkspaceTitleResolver(ctx);
7178
7239
  applyBuiltinCatalog(BUILTIN_MODEL_CATALOG, BUILTIN_MODEL_KEY_ALIASES);
7179
7240
  applyUserModelAliases(config.modelKeyAliases);
7241
+ const registeredProvidersOf = () => {
7242
+ try {
7243
+ return new Set(ctx.llm.listProviders().map((provider) => provider.id));
7244
+ } catch {
7245
+ return /* @__PURE__ */ new Set();
7246
+ }
7247
+ };
7180
7248
  const aggregator = createUsageAggregator(adaptSessionPersistence(ctx.sessionPersistence), {
7181
7249
  ...config.subscriptionProviders === void 0 ? {} : { subscriptionProviders: config.subscriptionProviders },
7182
7250
  ...config.routeAliases === void 0 ? {} : { routeAliases: config.routeAliases },
7183
7251
  resolveRoutes: () => readPiAiProviderRoutes(ctx.settings),
7252
+ resolveRegisteredProviders: registeredProvidersOf,
7253
+ ...config.modelKeyAliases === void 0 ? {} : { modelKeyAliases: config.modelKeyAliases },
7184
7254
  ...workspaceTitleResolver === void 0 ? {} : { resolveWorkspaceTitle: workspaceTitleResolver },
7185
7255
  ...config.searchCallEstimateCny === void 0 ? {} : { searchCallEstimateCny: config.searchCallEstimateCny },
7186
7256
  ledger: ledgerStore
@@ -7456,11 +7526,25 @@ function apply(ctx, config = {}) {
7456
7526
  });
7457
7527
  let live = { source: "builtin" };
7458
7528
  let pricingSyncedAt = 0;
7529
+ /** 价目表代际:extraModels 的 key 集合稳定序列化。fetchLivePricing 每次都重建
7530
+ * 数组(引用必不同),引用比较恒真会退化成「每次刷新都跑一轮 reprice」;
7531
+ * key 集合变化才代表真的出现了「此前未收录、现在可计价」的模型。 */
7532
+ const priceGenerationOf = (snapshot) => (snapshot.extraModels ?? []).map((model) => model.key).sort().join(",");
7533
+ let priceGeneration = priceGenerationOf(live);
7459
7534
  const refreshPricing = async () => {
7460
7535
  live = await fetchLivePricing();
7461
7536
  pricingSyncedAt = Date.now();
7462
7537
  applyLivePricing(live);
7463
7538
  pricingBodyCache = void 0;
7539
+ const nextGeneration = priceGenerationOf(live);
7540
+ if (nextGeneration !== priceGeneration) {
7541
+ priceGeneration = nextGeneration;
7542
+ aggregator.aggregate().then(() => {
7543
+ console.info("[usage-billing] repriced folds after pricing refresh; ledger aligned with live catalog");
7544
+ }).catch((error) => {
7545
+ console.warn("[usage-billing] reprice fold after pricing refresh failed; next aggregate retries:", error);
7546
+ });
7547
+ }
7464
7548
  };
7465
7549
  let pricingBodyCache;
7466
7550
  const pricingBody = () => {
@@ -140,12 +140,20 @@ export interface AggregateOptions {
140
140
  * 官方判定都用别名后的路由名)。
141
141
  */
142
142
  routeAliases?: Readonly<Record<string, string>>;
143
+ /** 折叠时模型 id → 目录键的用户自定义别名(与 pricing 模块注入的同源):仅用于
144
+ * 折叠指纹——别名改动必须让历史行失效重折,否则 byModel 新旧 key 并存(issue #91 P2)。 */
145
+ modelKeyAliases?: Readonly<Record<string, string>>;
143
146
  /** 每会话折叠缓存的上限(默认 {@link DEFAULT_MAX_CACHE_SESSIONS});
144
147
  * 超限时按最近使用先后淘汰最久未用的会话,防长期运行内存膨胀。 */
145
148
  maxCacheSessions?: number;
146
149
  /** 中转站归组来源:返回当前 provider 路由视图(llm-pi-ai providers 的 baseURL)。
147
150
  * 每次聚合时调用取最新值;缺省时全部路由按「未知路由」处理(无配置发现)。 */
148
151
  resolveRoutes?: () => Readonly<Record<string, ProviderRouteView>>;
152
+ /** 已注册 adapter 的 provider 集合(宿主 llm 注册表,llm-pi-ai 之外的插件路由,
153
+ * issue #91):每次聚合时调用取最新值。这些通道宿主只会把请求发给已注册的
154
+ * adapter,身份可核实——合成无 baseURL 的路由视图归位 `direct:<provider>` 按
155
+ * 官方目录价计费,不再落「未知路由」记 0;缺省时保持 unknown 口径。 */
156
+ resolveRegisteredProviders?: () => ReadonlySet<string>;
149
157
  /** 工作区标题解析:给定会话 cwd 返回项目显示标题;undefined = 回退到 cwd 末级目录名
150
158
  * (host 的 workspaceRegistry 为可选依赖,缺失时不注入,行为保持不变)。 */
151
159
  resolveWorkspaceTitle?: (cwd: string) => string | undefined;
@@ -591,7 +599,7 @@ export interface UsageLedgerDocument {
591
599
  * 会话费用只剩最近一段,issue #29)。
592
600
  * 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
593
601
  */
594
- export declare const FOLD_VERSION = 21;
602
+ export declare const FOLD_VERSION = 22;
595
603
  /**
596
604
  * 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
597
605
  * 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
@@ -687,7 +695,7 @@ export interface SessionUsageSummary {
687
695
  * 订阅豁免),历史账本行立即失效并全量重折,配置变更即时生效(issue #37)。
688
696
  * 判定函数无法序列化,统一记为 `fn`(任何函数形态互视为同一指纹)。
689
697
  */
690
- export declare function configFingerprint(subscriptionMatcher: SubscriptionMatcher, officialProviderIds: ReadonlySet<string> | undefined, routeAliases: Readonly<Record<string, string>>, searchEstimate: number): string;
698
+ export declare function configFingerprint(subscriptionMatcher: SubscriptionMatcher, officialProviderIds: ReadonlySet<string> | undefined, routeAliases: Readonly<Record<string, string>>, searchEstimate: number, modelKeyAliases?: Readonly<Record<string, string>>, registeredProviders?: ReadonlySet<string>): string;
691
699
  /**
692
700
  * Create the incremental usage aggregator.
693
701
  * @param persistence - the session persistence service.
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.29",
4
+ "version": "1.4.31",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "deepseek",