@kenz1117/dsh-ui-usage-billing 1.7.1 → 1.7.3

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
@@ -1225,6 +1225,30 @@ const BUILTIN_MODEL_CATALOG = [
1225
1225
  output: 100
1226
1226
  }
1227
1227
  },
1228
+ {
1229
+ key: "kimi-k3.1-pro",
1230
+ name: "Kimi K3.1 Pro",
1231
+ provider: "月之暗面",
1232
+ colorVar: "dsw-static-neutral-bluish-700",
1233
+ price: {
1234
+ currency: "CNY",
1235
+ input: 13,
1236
+ cacheHit: 1.3,
1237
+ output: 68
1238
+ }
1239
+ },
1240
+ {
1241
+ key: "kimi-k3.1-pro-highspeed",
1242
+ name: "Kimi K3.1 Pro Highspeed",
1243
+ provider: "月之暗面",
1244
+ colorVar: "dsw-static-neutral-bluish-600",
1245
+ price: {
1246
+ currency: "CNY",
1247
+ input: 26,
1248
+ cacheHit: 1.3,
1249
+ output: 136
1250
+ }
1251
+ },
1228
1252
  {
1229
1253
  key: "mimo-v2.6-pro",
1230
1254
  name: "MiMo V2.6 Pro",
@@ -3988,6 +4012,11 @@ const BUILTIN_MODEL_KEY_ALIASES = {
3988
4012
  "seed-2.0-mini": "doubao-mini",
3989
4013
  "k3": "kimi-k3",
3990
4014
  "kimi-k3": "kimi-k3",
4015
+ "kimi-k3.1-pro": "kimi-k3.1-pro",
4016
+ "kimi-k3.1-pro-highspeed": "kimi-k3.1-pro-highspeed",
4017
+ "k3.1-pro": "kimi-k3.1-pro",
4018
+ "k3.1-pro-highspeed": "kimi-k3.1-pro-highspeed",
4019
+ "k3-256k": "kimi-k3",
3991
4020
  "kimi-k2.8-preview": "kimi-k2.8-preview",
3992
4021
  "kimi-k2.8": "kimi-k2.8-preview",
3993
4022
  "kimi-k2-8-preview": "kimi-k2.8-preview",
@@ -5159,7 +5188,8 @@ function createUsageAggregator(persistence, options = {}) {
5159
5188
  const foldFingerprintOf = (registered, routes) => configFingerprint(subscriptionMatcher, officialProviderIds, routeAliases, searchEstimate, options.modelKeyAliases, registered, routeOriginsOf(routes));
5160
5189
  let foldFingerprint = foldFingerprintOf(/* @__PURE__ */ new Set(), {});
5161
5190
  const FOLD_CHUNK_EVENTS = 8e3;
5162
- const FOLD_YIELD_SESSIONS = 8;
5191
+ const FOLD_SLICE_MS = 8;
5192
+ const foldAllowedAt = Date.now() + Math.max(0, options.deferFoldMs ?? 0);
5163
5193
  /** 惰性失效(价目表时序):账本/缓存行若以「当时不可计价」的模型落盘,
5164
5194
  * 而价目表现在能计价了,该行必须重折——否则显示层按实时价算出的
5165
5195
  * 「目录价预估」与服务端 cost=0 并存,出现「明细比总额高」的双口径
@@ -5197,6 +5227,10 @@ function createUsageAggregator(persistence, options = {}) {
5197
5227
  const document = stored;
5198
5228
  if (runLedgerMigrations(document)) ledgerNeedsSave = true;
5199
5229
  ledgerAppliedMigrations = document.appliedMigrations;
5230
+ for (const refusal of document.refused ?? []) if (typeof refusal?.id === "string" && typeof refusal?.stamp === "string") unreadable.set(refusal.id, {
5231
+ stamp: refusal.stamp,
5232
+ format: refusal.format === true
5233
+ });
5200
5234
  }
5201
5235
  for (const entry of ledgerSessionsOf(stored)) ledger.set(entry.id, entry);
5202
5236
  } catch (error) {
@@ -5231,6 +5265,11 @@ function createUsageAggregator(persistence, options = {}) {
5231
5265
  version: 1,
5232
5266
  updatedAt: Date.now(),
5233
5267
  sessions: [...ledger.values()],
5268
+ ...unreadable.size === 0 ? {} : { refused: [...unreadable].slice(0, 500).map(([id, mark]) => ({
5269
+ id,
5270
+ stamp: mark.stamp,
5271
+ format: mark.format
5272
+ })) },
5234
5273
  ...ledgerAppliedMigrations === void 0 ? {} : { appliedMigrations: ledgerAppliedMigrations }
5235
5274
  });
5236
5275
  ledgerNeedsSave = false;
@@ -5239,20 +5278,19 @@ function createUsageAggregator(persistence, options = {}) {
5239
5278
  console.warn("[usage-billing] failed to persist durable usage ledger:", error);
5240
5279
  }
5241
5280
  };
5242
- const aggregateOnce = async () => {
5281
+ /**
5282
+ * 一轮聚合。mode='full' 走完整流程(list + 逐会话失效判定 + 折叠/复用);
5283
+ * mode='ledger-only' 只吃账本存档出轻文档(冷phase,issue #105):不 list、
5284
+ * 不读任何日志,doc 带 staleSnapshot=true 且**不进 TTL 缓存**(lastDoc 不动,
5285
+ * 开门后的下一轮即完整折叠)——轻文档的每行折叠反序列化有 WeakMap 记忆,
5286
+ * 重复构建只付 O(行数) 的汇总合并,冷phase里每个请求都可负担。
5287
+ */
5288
+ const aggregateOnce = async (mode) => {
5243
5289
  const now = Date.now();
5244
5290
  await ensureLedgerLoaded();
5245
5291
  const registeredProviders = options.resolveRegisteredProviders?.() ?? /* @__PURE__ */ new Set();
5246
5292
  const roundRoutes = routesOf(registeredProviders);
5247
5293
  foldFingerprint = foldFingerprintOf(registeredProviders, roundRoutes);
5248
- let metas;
5249
- try {
5250
- metas = await persistence.list();
5251
- } catch (error) {
5252
- if (options.ledger === void 0 || ledger.size === 0) throw error;
5253
- console.warn("[usage-billing] session list unavailable; serving durable usage ledger:", error);
5254
- metas = [];
5255
- }
5256
5294
  const seen = /* @__PURE__ */ new Set();
5257
5295
  const included = /* @__PURE__ */ new Set();
5258
5296
  const folds = [];
@@ -5262,132 +5300,164 @@ function createUsageAggregator(persistence, options = {}) {
5262
5300
  let formatUnreadable = 0;
5263
5301
  let noLedgerUnreadable = 0;
5264
5302
  let staleLedgerSessions = 0;
5265
- for (const [metaIndex, meta] of metas.entries()) {
5266
- if (metaIndex > 0 && metaIndex % FOLD_YIELD_SESSIONS === 0) await new Promise((resolve) => {
5267
- setImmediate(resolve);
5268
- });
5269
- const id = String(meta.id);
5270
- seen.add(id);
5271
- const stamp = await stampOf(meta);
5272
- const mark = unreadable.get(id);
5273
- if (stamp !== null && mark !== void 0 && mark.stamp === stamp) {
5274
- carriedUnreadable += 1;
5275
- if (mark.format) carriedFormatUnreadable += 1;
5276
- if (!ledger.has(id)) noLedgerUnreadable += 1;
5277
- continue;
5278
- }
5303
+ if (mode === "full") {
5304
+ let metas;
5279
5305
  try {
5280
- const cwd = meta.cwd;
5281
- const subOf = subParentOf(meta);
5282
- const ledgerRow = ledger.get(id);
5283
- if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 23 && ledgerRow.fingerprint === foldFingerprint && !foldNeedsReprice(ledgerRow.fold)) {
5284
- const fold = deserializedFoldOf(ledgerRow.fold);
5285
- folds.push({
5286
- id,
5287
- ...cwd === void 0 ? {} : { cwd },
5288
- ...subOf === void 0 ? {} : { subOf },
5289
- fold
5306
+ metas = await persistence.list();
5307
+ } catch (error) {
5308
+ if (options.ledger === void 0 || ledger.size === 0) throw error;
5309
+ console.warn("[usage-billing] session list unavailable; serving durable usage ledger:", error);
5310
+ metas = [];
5311
+ }
5312
+ let sliceAt = Date.now();
5313
+ for (const meta of metas) {
5314
+ if (Date.now() - sliceAt >= FOLD_SLICE_MS) {
5315
+ await new Promise((resolve) => {
5316
+ setImmediate(resolve);
5290
5317
  });
5291
- included.add(id);
5292
- continue;
5318
+ sliceAt = Date.now();
5293
5319
  }
5294
- const hit = cache.get(id);
5295
- if (hit !== void 0 && stamp !== null && hit.stamp === stamp && hit.fingerprint === foldFingerprint && ![...hit.fold.unpricedModels].some((key) => isPriced(key))) {
5296
- cache.delete(id);
5297
- cache.set(id, hit);
5298
- folds.push({
5299
- id,
5300
- ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
5301
- ...subOf === void 0 ? {} : { subOf },
5302
- fold: hit.fold
5303
- });
5304
- included.add(id);
5320
+ const id = String(meta.id);
5321
+ seen.add(id);
5322
+ const stamp = await stampOf(meta);
5323
+ const mark = unreadable.get(id);
5324
+ if (stamp !== null && mark !== void 0 && mark.stamp === stamp) {
5325
+ carriedUnreadable += 1;
5326
+ if (mark.format) carriedFormatUnreadable += 1;
5327
+ if (!ledger.has(id)) noLedgerUnreadable += 1;
5305
5328
  continue;
5306
5329
  }
5307
- const previous = cache.get(id);
5308
- if (previous?.machine !== void 0 && previous.lastSeq !== void 0 && previous.fingerprint === foldFingerprint && ![...previous.fold.unpricedModels].some((key) => isPriced(key))) {
5309
- const from = previous.lastSeq + 1;
5310
- const { events, inheritedEventCount } = await persistence.readFrom(meta.id, sessionLogOffsetOf(from));
5311
- const after = await stampOf(meta);
5312
- if (stamp !== null && after !== stamp) {
5330
+ try {
5331
+ const cwd = meta.cwd;
5332
+ const subOf = subParentOf(meta);
5333
+ const ledgerRow = ledger.get(id);
5334
+ if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 23 && ledgerRow.fingerprint === foldFingerprint && !foldNeedsReprice(ledgerRow.fold)) {
5335
+ const fold = deserializedFoldOf(ledgerRow.fold);
5336
+ folds.push({
5337
+ id,
5338
+ ...cwd === void 0 ? {} : { cwd },
5339
+ ...subOf === void 0 ? {} : { subOf },
5340
+ fold
5341
+ });
5342
+ included.add(id);
5343
+ continue;
5344
+ }
5345
+ const hit = cache.get(id);
5346
+ if (hit !== void 0 && stamp !== null && hit.stamp === stamp && hit.fingerprint === foldFingerprint && ![...hit.fold.unpricedModels].some((key) => isPriced(key))) {
5313
5347
  cache.delete(id);
5348
+ cache.set(id, hit);
5349
+ folds.push({
5350
+ id,
5351
+ ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
5352
+ ...subOf === void 0 ? {} : { subOf },
5353
+ fold: hit.fold
5354
+ });
5355
+ included.add(id);
5314
5356
  continue;
5315
5357
  }
5316
- const first = events[0]?.seq;
5317
- if (events.length === 0 || first !== from) {
5358
+ const previous = cache.get(id);
5359
+ if (previous?.machine !== void 0 && previous.lastSeq !== void 0 && previous.fingerprint === foldFingerprint && ![...previous.fold.unpricedModels].some((key) => isPriced(key))) {
5360
+ const from = previous.lastSeq + 1;
5361
+ const { events, inheritedEventCount } = await persistence.readFrom(meta.id, sessionLogOffsetOf(from));
5362
+ const after = await stampOf(meta);
5363
+ if (stamp !== null && after !== stamp) {
5364
+ cache.delete(id);
5365
+ continue;
5366
+ }
5367
+ const first = events[0]?.seq;
5368
+ if (events.length === 0 || first !== from) {
5369
+ cache.delete(id);
5370
+ continue;
5371
+ }
5372
+ foldInto(previous.fold, previous.machine, events, subscriptionMatcher, officialProviderIds, roundRoutes, searchEstimate, inheritedEventCount, routeAliases);
5373
+ refreshTurns(previous.fold, previous.machine);
5374
+ previous.lastSeq = events[events.length - 1]?.seq ?? previous.lastSeq;
5375
+ previous.stamp = stamp;
5318
5376
  cache.delete(id);
5377
+ cache.set(id, previous);
5378
+ folds.push({
5379
+ id,
5380
+ ...cwd === void 0 ? {} : { cwd },
5381
+ ...subOf === void 0 ? {} : { subOf },
5382
+ fold: previous.fold
5383
+ });
5384
+ included.add(id);
5385
+ recordLedger(id, cwd, stamp, previous.fold, subOf);
5319
5386
  continue;
5320
5387
  }
5321
- foldInto(previous.fold, previous.machine, events, subscriptionMatcher, officialProviderIds, roundRoutes, searchEstimate, inheritedEventCount, routeAliases);
5322
- refreshTurns(previous.fold, previous.machine);
5323
- previous.lastSeq = events[events.length - 1]?.seq ?? previous.lastSeq;
5324
- previous.stamp = stamp;
5325
- cache.delete(id);
5326
- cache.set(id, previous);
5388
+ const { events, inheritedEventCount } = await persistence.readFrom(meta.id, sessionLogOffsetOf(0));
5389
+ const after = await stampOf(meta);
5390
+ if (stamp !== null && after !== stamp) continue;
5391
+ const fold = freshFold();
5392
+ const machine = freshMachine();
5393
+ for (let start = 0; start < events.length; start += FOLD_CHUNK_EVENTS) {
5394
+ foldInto(fold, machine, events.slice(start, start + FOLD_CHUNK_EVENTS), subscriptionMatcher, officialProviderIds, roundRoutes, searchEstimate, inheritedEventCount, routeAliases);
5395
+ if (start + FOLD_CHUNK_EVENTS < events.length && Date.now() - sliceAt >= FOLD_SLICE_MS) {
5396
+ await new Promise((resolve) => {
5397
+ setImmediate(resolve);
5398
+ });
5399
+ sliceAt = Date.now();
5400
+ }
5401
+ }
5402
+ refreshTurns(fold, machine);
5403
+ const last = events[events.length - 1];
5404
+ cache.set(id, {
5405
+ stamp,
5406
+ fold,
5407
+ machine,
5408
+ ...last !== void 0 && typeof last.seq === "number" ? { lastSeq: last.seq } : {},
5409
+ fingerprint: foldFingerprint
5410
+ });
5327
5411
  folds.push({
5328
5412
  id,
5329
5413
  ...cwd === void 0 ? {} : { cwd },
5330
5414
  ...subOf === void 0 ? {} : { subOf },
5331
- fold: previous.fold
5415
+ fold
5332
5416
  });
5333
5417
  included.add(id);
5334
- recordLedger(id, cwd, stamp, previous.fold, subOf);
5335
- continue;
5336
- }
5337
- const { events, inheritedEventCount } = await persistence.readFrom(meta.id, sessionLogOffsetOf(0));
5338
- const after = await stampOf(meta);
5339
- if (stamp !== null && after !== stamp) continue;
5340
- const fold = freshFold();
5341
- const machine = freshMachine();
5342
- for (let start = 0; start < events.length; start += FOLD_CHUNK_EVENTS) {
5343
- foldInto(fold, machine, events.slice(start, start + FOLD_CHUNK_EVENTS), subscriptionMatcher, officialProviderIds, roundRoutes, searchEstimate, inheritedEventCount, routeAliases);
5344
- if (start + FOLD_CHUNK_EVENTS < events.length) await new Promise((resolve) => {
5345
- setImmediate(resolve);
5346
- });
5347
- }
5348
- refreshTurns(fold, machine);
5349
- const last = events[events.length - 1];
5350
- cache.set(id, {
5351
- stamp,
5352
- fold,
5353
- machine,
5354
- ...last !== void 0 && typeof last.seq === "number" ? { lastSeq: last.seq } : {},
5355
- fingerprint: foldFingerprint
5356
- });
5357
- folds.push({
5358
- id,
5359
- ...cwd === void 0 ? {} : { cwd },
5360
- ...subOf === void 0 ? {} : { subOf },
5361
- fold
5362
- });
5363
- included.add(id);
5364
- recordLedger(id, cwd, stamp, fold, subOf);
5365
- } catch (error) {
5366
- skipped.push(id);
5367
- const format = isFormatUnsupportedError(error);
5368
- if (format) formatUnreadable += 1;
5369
- if (!ledger.has(id)) noLedgerUnreadable += 1;
5370
- if (stamp !== null) unreadable.set(id, {
5371
- stamp,
5372
- format
5373
- });
5374
- if (options.resolveSessionTitle !== void 0) {
5375
- const hint = options.resolveSessionTitle(meta);
5376
- if (hint !== void 0) titleHints.set(id, hint);
5418
+ recordLedger(id, cwd, stamp, fold, subOf);
5419
+ } catch (error) {
5420
+ skipped.push(id);
5421
+ const format = isFormatUnsupportedError(error);
5422
+ if (format) formatUnreadable += 1;
5423
+ if (!ledger.has(id)) noLedgerUnreadable += 1;
5424
+ if (stamp !== null) {
5425
+ unreadable.set(id, {
5426
+ stamp,
5427
+ format
5428
+ });
5429
+ ledgerNeedsSave = true;
5430
+ }
5431
+ if (options.resolveSessionTitle !== void 0) {
5432
+ const hint = options.resolveSessionTitle(meta);
5433
+ if (hint !== void 0) titleHints.set(id, hint);
5434
+ }
5435
+ console.warn("[usage-billing] skip unreadable session", id, error);
5377
5436
  }
5378
- console.warn("[usage-billing] skip unreadable session", id, error);
5437
+ if (included.has(id)) titleHints.delete(id);
5379
5438
  }
5380
- if (included.has(id)) titleHints.delete(id);
5381
5439
  }
5382
- for (const key of [...cache.keys()]) if (!seen.has(key)) cache.delete(key);
5383
- for (const key of [...unreadable.keys()]) if (!seen.has(key)) unreadable.delete(key);
5384
- while (cache.size > maxCacheSessions) {
5385
- const oldest = cache.keys().next();
5386
- if (oldest.done === true) break;
5387
- cache.delete(oldest.value);
5440
+ if (mode === "full") {
5441
+ for (const key of [...cache.keys()]) if (!seen.has(key)) cache.delete(key);
5442
+ for (const key of [...unreadable.keys()]) if (!seen.has(key)) {
5443
+ unreadable.delete(key);
5444
+ ledgerNeedsSave = true;
5445
+ }
5446
+ while (cache.size > maxCacheSessions) {
5447
+ const oldest = cache.keys().next();
5448
+ if (oldest.done === true) break;
5449
+ cache.delete(oldest.value);
5450
+ }
5388
5451
  }
5389
5452
  if (options.ledger !== void 0) {
5453
+ let fallbackSliceAt = Date.now();
5390
5454
  for (const entry of ledger.values()) {
5455
+ if (Date.now() - fallbackSliceAt >= FOLD_SLICE_MS) {
5456
+ await new Promise((resolve) => {
5457
+ setImmediate(resolve);
5458
+ });
5459
+ fallbackSliceAt = Date.now();
5460
+ }
5391
5461
  if (included.has(entry.id)) continue;
5392
5462
  try {
5393
5463
  const stale = (entry.foldVersion ?? 1) < 23 || entry.fingerprint !== foldFingerprint;
@@ -5565,7 +5635,7 @@ function createUsageAggregator(persistence, options = {}) {
5565
5635
  };
5566
5636
  const unreadableTotal = skipped.length + carriedUnreadable;
5567
5637
  const formatUnreadableTotal = formatUnreadable + carriedFormatUnreadable;
5568
- lastDoc = {
5638
+ const document = {
5569
5639
  version: 4,
5570
5640
  updatedAt: now,
5571
5641
  source: "session-logs",
@@ -5599,15 +5669,21 @@ function createUsageAggregator(persistence, options = {}) {
5599
5669
  assistant: roles.outputCost,
5600
5670
  tool: roles.inputCost * (1 - userShare)
5601
5671
  };
5602
- })()
5672
+ })(),
5673
+ ...mode === "ledger-only" ? { staleSnapshot: true } : {}
5603
5674
  };
5604
- lastAt = now;
5605
- return lastDoc;
5675
+ if (mode === "full") {
5676
+ lastDoc = document;
5677
+ lastAt = now;
5678
+ }
5679
+ return document;
5606
5680
  };
5607
5681
  return {
5608
5682
  aggregate() {
5609
- if (lastDoc !== void 0 && Date.now() - lastAt < 5e3) return Promise.resolve(lastDoc);
5610
- inflight ??= aggregateOnce().finally(() => {
5683
+ const now = Date.now();
5684
+ if (lastDoc !== void 0 && now - lastAt < 5e3) return Promise.resolve(lastDoc);
5685
+ if (now < foldAllowedAt) return inflight ?? aggregateOnce("ledger-only");
5686
+ inflight ??= aggregateOnce("full").finally(() => {
5611
5687
  inflight = void 0;
5612
5688
  });
5613
5689
  return inflight;
@@ -9306,9 +9382,19 @@ const UsageBillingSettingsSchema = z.object({ [ENABLE_USAGE_STATS_TOOL_FIELD]: z
9306
9382
  /** 实时定价的后台刷新间隔(毫秒):汇率/模型价低频变化,6 小时一次足够。 */
9307
9383
  /** 实时定价的后台刷新间隔(毫秒):汇率/模型价低频变化,6 小时一次足够。 */
9308
9384
  const PRICING_REFRESH_INTERVAL_MS = 216e5;
9309
- /** 历史回放预热的延迟:等宿主启动高峰(插件加载 / 路由挂载)过去再全量折叠,
9310
- * 避免抢启动期的 CPU;纯延迟不阻塞任何请求,面板提前打开也只会提前聚合。 */
9311
- const WARMUP_DELAY_MS = 3e3;
9385
+ /** 启动后延迟全量折叠的默认时长(毫秒,issue #105):冷phase内 /api/billing/*
9386
+ * 只出账本轻文档(不读日志),到点后预热定时器照常完整折叠——UI 一加载的
9387
+ * 轮询不再把全量折叠拉进启动关键路径。可用 config.replayDelayMs 覆盖;0 = 旧
9388
+ * 行为(随到随折)。20 秒的取值:小账本用户只损失十几秒新鲜度,重账本用户的
9389
+ * 启动高峰(插件加载/索引/首批会话写入)已经过去;配合聚合器的墙钟分片让出
9390
+ * (FOLD_SLICE_MS),折叠真跑起来时单片阻塞也被压在 ~8ms。
9391
+ */
9392
+ const DEFAULT_REPLAY_DELAY_MS = 2e4;
9393
+ /** 手写收窄(schemastery 不能当形状校验器,S-7):非有限非负数字回落默认值。 */
9394
+ function readReplayDelayMs(value) {
9395
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) return value;
9396
+ return DEFAULT_REPLAY_DELAY_MS;
9397
+ }
9312
9398
  /** 订阅套餐额度缓存时长(毫秒):上游配额 API 低频变化,5 分钟足够。 */
9313
9399
  const SUBSCRIPTION_CACHE_MS = 3e5;
9314
9400
  const BALANCE_CACHE_MS = 3e5;
@@ -9792,6 +9878,7 @@ function apply(ctx, config = {}) {
9792
9878
  swallowed("minimax-plan-classify-failed", error);
9793
9879
  }
9794
9880
  };
9881
+ const replayDelayMs = readReplayDelayMs(config.replayDelayMs);
9795
9882
  const aggregator = createUsageAggregator(adaptSessionPersistence(ctx.sessionPersistence), {
9796
9883
  subscriptionMatcher: subscriptionRoutes,
9797
9884
  ...config.routeAliases === void 0 ? {} : { routeAliases: config.routeAliases },
@@ -9801,6 +9888,7 @@ function apply(ctx, config = {}) {
9801
9888
  ...workspaceTitleResolver === void 0 ? {} : { resolveWorkspaceTitle: workspaceTitleResolver },
9802
9889
  ...sessionTitleResolver === void 0 ? {} : { resolveSessionTitle: sessionTitleResolver },
9803
9890
  ...config.searchCallEstimateCny === void 0 ? {} : { searchCallEstimateCny: config.searchCallEstimateCny },
9891
+ deferFoldMs: replayDelayMs,
9804
9892
  ledger: ledgerStore
9805
9893
  });
9806
9894
  refreshMiniMaxPlanRoutes();
@@ -9814,7 +9902,7 @@ function apply(ctx, config = {}) {
9814
9902
  }).catch((error) => {
9815
9903
  console.warn("[usage-billing] historical replay warmup failed; will fold on first dashboard request:", error);
9816
9904
  });
9817
- }, WARMUP_DELAY_MS);
9905
+ }, replayDelayMs);
9818
9906
  ctx.effect(() => () => {
9819
9907
  clearTimeout(warmupTimer);
9820
9908
  }, "usage-billing: historical replay warmup timer");
@@ -179,6 +179,13 @@ export interface AggregateOptions {
179
179
  /** 联网搜索请求(`web/deepseek-search-llm-request`,无用量事件)的单次费用
180
180
  * 估算(人民币元);默认 {@link DEFAULT_SEARCH_CALL_ESTIMATE_CNY},设 0 关闭。 */
181
181
  searchCallEstimateCny?: number;
182
+ /**
183
+ * 启动冷phase时长(毫秒,自聚合器创建起算,issue #105):期间 aggregate()
184
+ * 只出账本轻文档——不 list 会话、不读任何日志,文档带 `staleSnapshot: true`,
185
+ * UI 一加载的轮询不再把「全量折叠」拉进启动关键路径。到点后首个请求(或预热
186
+ * 定时器)照常完整折叠。0 / 缺省 = 不设闸(旧行为:随到随折)。
187
+ */
188
+ deferFoldMs?: number;
182
189
  }
183
190
  /** 每会话折叠缓存默认上限:超过则按 LRU 淘汰(P1-6 峰值内存治理)。 */
184
191
  export declare const DEFAULT_MAX_CACHE_SESSIONS = 400;
@@ -352,6 +359,12 @@ export interface UsageStatsDocument {
352
359
  unreadableFormatSessions?: number;
353
360
  /** 未统计中无任何账本存档、完全未计入的子集(区别于有存档按最近可用点计入的);无存档缺失时省略。 */
354
361
  unreadableNoLedgerSessions?: number;
362
+ /**
363
+ * 冷phase轻文档标记(issue #105):本档全部出自账本存档、未读任何会话日志
364
+ * (启动后 replayDelayMs 内的请求)。客户端复用既有「数据滞后」横幅呈现
365
+ * (issue #103 加的 staleSnapshot 通道,此前无生产方),零新增客户端体积。
366
+ */
367
+ staleSnapshot?: boolean;
355
368
  }
356
369
  /** 按角色费用归因:user / tool 为输入成本的启发式摊分,assistant 为输出成本实测。 */
357
370
  export interface RoleCost {
@@ -637,6 +650,19 @@ export interface UsageLedgerDocument {
637
650
  sessions: UsageLedgerSession[];
638
651
  /** 已应用的一次性配置迁移 id 列表(随文档落盘;缺省 = 尚未跑过任何迁移)。 */
639
652
  appliedMigrations?: string[];
653
+ /**
654
+ * 拒读会话负缓存(issue #105):读失败(含格式拒读)会话的失败时 stamp。落盘
655
+ * 后重启不再重读——宿主对读不动的日志要解压解析到拒绝点才抛错,单条数百 ms、
656
+ * 老用户上百条时是每次启动的固定大头(负缓存此前只在内存,重启即失)。stamp
657
+ * 变化(日志被改写/追加)后照常失效重试;会话消失由 seen 清理兜底。
658
+ */
659
+ refused?: UsageLedgerRefusal[];
660
+ }
661
+ /** 账本里的一条拒读记录:会话 id + 失败时的失效键 + 是否格式拒读(计数细分用)。 */
662
+ export interface UsageLedgerRefusal {
663
+ id: string;
664
+ stamp: string;
665
+ format: boolean;
640
666
  }
641
667
  /**
642
668
  * 多实例账本合并(自审计延期项:save 路径 last-writer-wins 丢写):同一
@@ -666,6 +692,9 @@ export declare function mergeUsageLedgerDocuments(document: UsageLedgerDocument,
666
692
  * 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
667
693
  */
668
694
  export declare const FOLD_VERSION = 23;
695
+ /** 账本里拒读记录的条数上限:防异常环境(大量损坏日志)把账本撑爆;
696
+ * 超限按 Map 插入序保留前 N 条(最早发现的先留,stamp 失效的自然被清理让位)。 */
697
+ export declare const LEDGER_REFUSED_CAP = 500;
669
698
  /**
670
699
  * 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
671
700
  * 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
@@ -10,8 +10,14 @@ export declare function dailyBurnRate(byDay: Record<string, {
10
10
  cost: number;
11
11
  }>, today: string): number;
12
12
  /**
13
- * 本月预计总花费:按本月已有记录的平均日消耗 × 本月天数外推;无本月记录时
14
- * 回退为最近 7 天日均 × 本月天数;无任何记录时返回 0(调用方不展示)。
13
+ * 本月预计总花费:**已发生 + 近 7 天节奏 × 本月剩余天数**。
14
+ *
15
+ * 口径变更(2026-10-23):旧口径拿「本月有记录的天数」当分母求日均再乘整月天数,
16
+ * 稀疏使用的月份会被放大数倍——实测一个只有 4 天记录、其余 19 天没用的月份被外推
17
+ * 成实际值的 4 倍(¥3.4 → ¥15.0 的旧算法给 ¥572.9)。新口径只外推「还没发生的
18
+ * 那些天」,且节奏基准取近 7 个自然日(含空闲天):近期没花钱就不虚增。
19
+ *
20
+ * 无本月记录时回退为最近 7 天(活跃天)日均 × 剩余天数;无任何记录时返回 0。
15
21
  * 导出供测试:纯函数,不依赖组件。
16
22
  * @param byDay - 按日费用表。
17
23
  * @param monthPrefix - 本月前缀(YYYY-MM)。
@@ -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' | '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' | 'balanceStaleNotice' | 'staleLedgerNotice' | 'staleSnapshotNotice' | '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' | 'settingsNavDisplayPref' | 'settingsNavBudget' | 'settingsNavAlert' | 'settingsNavDisplay' | 'settingsNavAdvanced' | 'settingsNavAbout' | 'settingsSearchPlaceholder' | 'settingsSearchEmpty' | 'sessionsViewAll' | 'sessionViewerSearch' | 'modelBudgetTitle' | 'modelBudgetHint' | 'modelBudgetBody' | 'modelBudgetPlaceholder' | 'moreWorksTitle' | 'adEngramName' | 'adEngramDesc' | 'adRainbowName' | 'adRainbowDesc' | 'kpiDescCacheHit' | 'kpiDescTokens' | 'kpiDescAvgCost' | 'kpiDescCalls' | 'kpiDescReasoning' | 'kpiDescIo' | 'kpiDescPeak' | 'healthDotLive' | 'healthDotFailed' | 'healthDotUnknown' | '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' | 'commandUsageLabel' | 'commandUsageDescription' | 'sessionOpen' | 'sessionOpenFailed' | 'sessionOpenUnavailable' | 'contextOccupancy' | 'contextWindow' | 'contextSystem' | 'contextTools' | 'contextMessages' | 'contextNoData' | 'tabContext' | 'tabAgents' | 'contextNextRequest' | 'contextLastRequest' | 'contextComposition' | 'contextCompositionHint' | 'agentsSummary' | 'agentsCount' | 'agentsDelegations' | 'agentsRoots' | 'agentsMostExpensive' | 'agentsChainTotal' | 'agentsNoData' | 'agentsSubagent' | 'agentsRoot';
2
+ export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'monthProjectedHint' | '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' | 'balanceStaleNotice' | 'staleLedgerNotice' | 'staleSnapshotNotice' | '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' | 'settingsNavDisplayPref' | 'settingsNavBudget' | 'settingsNavAlert' | 'settingsNavDisplay' | 'settingsNavAdvanced' | 'settingsNavAbout' | 'settingsSearchPlaceholder' | 'settingsSearchEmpty' | 'sessionsViewAll' | 'sessionViewerSearch' | 'modelBudgetTitle' | 'modelBudgetHint' | 'modelBudgetBody' | 'modelBudgetPlaceholder' | 'moreWorksTitle' | 'adEngramName' | 'adEngramDesc' | 'adRainbowName' | 'adRainbowDesc' | 'kpiDescCacheHit' | 'kpiDescTokens' | 'kpiDescAvgCost' | 'kpiDescCalls' | 'kpiDescReasoning' | 'kpiDescIo' | 'kpiDescPeak' | 'healthDotLive' | 'healthDotFailed' | 'healthDotUnknown' | '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' | 'commandUsageLabel' | 'commandUsageDescription' | 'sessionOpen' | 'sessionOpenFailed' | 'sessionOpenUnavailable' | 'contextOccupancy' | 'contextWindow' | 'contextSystem' | 'contextTools' | 'contextMessages' | 'contextNoData' | 'tabContext' | 'tabAgents' | 'contextNextRequest' | 'contextLastRequest' | 'contextComposition' | 'contextCompositionHint' | 'agentsSummary' | 'agentsCount' | 'agentsDelegations' | 'agentsRoots' | 'agentsMostExpensive' | 'agentsChainTotal' | 'agentsNoData' | 'agentsSubagent' | 'agentsRoot';
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>;
@@ -96,5 +96,13 @@ export interface UsageBillingConfig {
96
96
  * 如 `{ "hy4-preview": "hunyuan" }`)。聚合折叠与客户端渲染共用同一映射。
97
97
  */
98
98
  modelKeyAliases?: Record<string, string>;
99
+ /**
100
+ * 启动后延迟全量折叠的时长(毫秒,issue #105):聚合器创建起 `replayDelayMs`
101
+ * 之内,`/api/billing/*` 只出账本轻文档(不读任何会话日志,面板以「数据滞后」
102
+ * 横幅提示,复用 issue #103 的 staleSnapshot 通道),到点后预热定时器照常完整
103
+ * 折叠。默认 20 000(20 秒);重账本用户(数千会话 / 数十 MB 日志)可调大到
104
+ * 180 000;0 = 旧行为(随到随折,首屏轮询即触发全量折叠)。
105
+ */
106
+ replayDelayMs?: number;
99
107
  }
100
108
  //# sourceMappingURL=config.d.ts.map
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.7.1",
4
+ "version": "1.7.3",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "deepseek",