@kenz1117/dsh-ui-usage-billing 1.0.9 → 1.0.11

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
@@ -1866,6 +1866,7 @@ function serializeFold(fold) {
1866
1866
  byModel: Object.fromEntries(fold.byModel),
1867
1867
  byDay: Object.fromEntries(fold.byDay),
1868
1868
  byDayModels: Object.fromEntries([...fold.byDayModels].map(([day, models]) => [day, Object.fromEntries(models)])),
1869
+ byDayModelsSite: Object.fromEntries([...fold.byDayModelsSite].map(([day, models]) => [day, Object.fromEntries([...models].map(([model, sites]) => [model, Object.fromEntries(sites)]))])),
1869
1870
  byTier: Object.fromEntries(fold.byTier),
1870
1871
  byTool: Object.fromEntries(fold.byTool),
1871
1872
  bySite: Object.fromEntries(fold.bySite),
@@ -1884,6 +1885,7 @@ function deserializeFold(fold) {
1884
1885
  byModel: new Map(Object.entries(fold.byModel)),
1885
1886
  byDay: new Map(Object.entries(fold.byDay)),
1886
1887
  byDayModels: new Map(Object.entries(fold.byDayModels).map(([day, models]) => [day, new Map(Object.entries(models))])),
1888
+ byDayModelsSite: new Map(Object.entries(fold.byDayModelsSite ?? {}).map(([day, models]) => [day, new Map(Object.entries(models).map(([model, sites]) => [model, new Map(Object.entries(sites))]))])),
1887
1889
  byTier: new Map(Object.entries(fold.byTier ?? {})),
1888
1890
  byTool: new Map(Object.entries(fold.byTool ?? {})),
1889
1891
  bySite: new Map(Object.entries(fold.bySite)),
@@ -1941,6 +1943,20 @@ function modelDayCell(map, day, modelKey) {
1941
1943
  }
1942
1944
  return usageCell(models, modelKey);
1943
1945
  }
1946
+ /** Get-or-create one day×model×site cell inside the three-dimensional map. */
1947
+ function modelDaySiteCell(map, day, modelKey, siteKey) {
1948
+ let models = map.get(day);
1949
+ if (models === void 0) {
1950
+ models = /* @__PURE__ */ new Map();
1951
+ map.set(day, models);
1952
+ }
1953
+ let sites = models.get(modelKey);
1954
+ if (sites === void 0) {
1955
+ sites = /* @__PURE__ */ new Map();
1956
+ models.set(modelKey, sites);
1957
+ }
1958
+ return usageCell(sites, siteKey);
1959
+ }
1944
1960
  /** Get-or-create one turn's accumulation state. */
1945
1961
  function turnState(turns, turn) {
1946
1962
  const existing = turns.get(turn);
@@ -1983,6 +1999,7 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
1983
1999
  byModel: /* @__PURE__ */ new Map(),
1984
2000
  byDay: /* @__PURE__ */ new Map(),
1985
2001
  byDayModels: /* @__PURE__ */ new Map(),
2002
+ byDayModelsSite: /* @__PURE__ */ new Map(),
1986
2003
  byTier: /* @__PURE__ */ new Map(),
1987
2004
  byTool: /* @__PURE__ */ new Map(),
1988
2005
  bySite: /* @__PURE__ */ new Map(),
@@ -2059,14 +2076,16 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
2059
2076
  const model = event.data.body?.model;
2060
2077
  const modelKey = typeof model === "string" && model !== "" ? resolveCatalogKey(model) : "other";
2061
2078
  const day = dayStamp(event.time);
2079
+ const siteKey = siteBucketKey({
2080
+ kind: "direct",
2081
+ provider: "deepseek"
2082
+ });
2062
2083
  foldSearchCall(fold.total, searchCallEstimateCny);
2063
2084
  foldSearchCall(usageCell(fold.byModel, modelKey), searchCallEstimateCny);
2064
2085
  foldSearchCall(usageCell(fold.byDay, day), searchCallEstimateCny);
2065
2086
  foldSearchCall(modelDayCell(fold.byDayModels, day, modelKey), searchCallEstimateCny);
2066
- foldSearchCall(usageCell(fold.bySite, siteBucketKey({
2067
- kind: "direct",
2068
- provider: "deepseek"
2069
- })), searchCallEstimateCny);
2087
+ foldSearchCall(modelDaySiteCell(fold.byDayModelsSite, day, modelKey, siteKey), searchCallEstimateCny);
2088
+ foldSearchCall(usageCell(fold.bySite, siteKey), searchCallEstimateCny);
2070
2089
  foldSearchCall(usageCell(fold.byTier, tierAt(event.time)), searchCallEstimateCny);
2071
2090
  continue;
2072
2091
  }
@@ -2111,6 +2130,7 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
2111
2130
  foldUsage(usageCell(fold.byModel, modelKey), usage, modelKey, subscription, event.time, official);
2112
2131
  foldUsage(usageCell(fold.byDay, day), usage, modelKey, subscription, event.time, official);
2113
2132
  foldUsage(modelDayCell(fold.byDayModels, day, modelKey), usage, modelKey, subscription, event.time, official);
2133
+ foldUsage(modelDaySiteCell(fold.byDayModelsSite, day, modelKey, siteBucket), usage, modelKey, subscription, event.time, official);
2114
2134
  foldUsage(usageCell(fold.bySite, siteBucket), usage, modelKey, subscription, event.time, official);
2115
2135
  foldUsage(usageCell(fold.byTier, tierAt(event.time)), usage, modelKey, subscription, event.time, official);
2116
2136
  if (subscription) fold.planCalls.set(modelKey, (fold.planCalls.get(modelKey) ?? 0) + 1);
@@ -2321,7 +2341,7 @@ function createUsageAggregator(persistence, options = {}) {
2321
2341
  id,
2322
2342
  ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
2323
2343
  ...stamp === null ? {} : { stamp },
2324
- foldVersion: 3,
2344
+ foldVersion: 4,
2325
2345
  fold: serializeFold(fold)
2326
2346
  };
2327
2347
  const previous = ledger.get(id);
@@ -2345,7 +2365,7 @@ function createUsageAggregator(persistence, options = {}) {
2345
2365
  for (const entry of ledger.values()) {
2346
2366
  if (included.has(entry.id)) continue;
2347
2367
  try {
2348
- const stale = (entry.foldVersion ?? 1) < 3;
2368
+ const stale = (entry.foldVersion ?? 1) < 4;
2349
2369
  folds.push({
2350
2370
  id: entry.id,
2351
2371
  ...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
@@ -2375,6 +2395,7 @@ function createUsageAggregator(persistence, options = {}) {
2375
2395
  const byModel = /* @__PURE__ */ new Map();
2376
2396
  const byDay = /* @__PURE__ */ new Map();
2377
2397
  const byDayModels = /* @__PURE__ */ new Map();
2398
+ const byDayModelsSite = /* @__PURE__ */ new Map();
2378
2399
  const byTier = /* @__PURE__ */ new Map();
2379
2400
  const byTool = /* @__PURE__ */ new Map();
2380
2401
  const bySite = /* @__PURE__ */ new Map();
@@ -2400,6 +2421,7 @@ function createUsageAggregator(persistence, options = {}) {
2400
2421
  for (const [modelKey, cell] of fold.byModel) mergeUsageInto(usageCell(byModel, modelKey), cell);
2401
2422
  for (const [day, cell] of fold.byDay) mergeUsageInto(usageCell(byDay, day), cell);
2402
2423
  for (const [day, models] of fold.byDayModels) for (const [modelKey, cell] of models) mergeUsageInto(modelDayCell(byDayModels, day, modelKey), cell);
2424
+ for (const [day, models] of fold.byDayModelsSite) for (const [modelKey, sites] of models) for (const [siteKey, cell] of sites) mergeUsageInto(modelDaySiteCell(byDayModelsSite, day, modelKey, siteKey), cell);
2403
2425
  for (const [siteKey, cell] of fold.bySite) mergeUsageInto(usageCell(bySite, siteKey), cell);
2404
2426
  for (const [tierKey, cell] of fold.byTier) mergeUsageInto(usageCell(byTier, tierKey), cell);
2405
2427
  for (const [toolName, count] of fold.byTool) byTool.set(toolName, (byTool.get(toolName) ?? 0) + count);
@@ -2473,6 +2495,7 @@ function createUsageAggregator(persistence, options = {}) {
2473
2495
  return record;
2474
2496
  };
2475
2497
  const toModelDayRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries(models)]));
2498
+ const toModelDaySiteRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries([...models].map(([model, sites]) => [model, Object.fromEntries(sites)]))]));
2476
2499
  const perf = perfModel.size === 0 ? void 0 : {
2477
2500
  byModel: Object.fromEntries([...perfModel].map(([model, acc]) => [model, {
2478
2501
  samples: acc.ttfts.length,
@@ -2500,6 +2523,7 @@ function createUsageAggregator(persistence, options = {}) {
2500
2523
  byModel: toRecord(byModel),
2501
2524
  byDay: toRecord(byDay),
2502
2525
  byDayModels: toModelDayRecord(byDayModels),
2526
+ ...byDayModelsSite.size === 0 ? {} : { byDayModelsSite: toModelDaySiteRecord(byDayModelsSite) },
2503
2527
  bySession: sessionRows.slice(0, 100),
2504
2528
  byTurn: turnRows.slice(0, 200),
2505
2529
  byWorkspace: workspaces.slice(0, 100),
@@ -175,6 +175,12 @@ export interface UsageStatsDocument {
175
175
  byDay: Record<string, ModelUsage>;
176
176
  /** 模型 × 日期 二维统计:趋势图按模型堆叠的输入([date][modelKey])。 */
177
177
  byDayModels: Record<string, Record<string, ModelUsage>>;
178
+ /**
179
+ * 模型 × 日期 × 站点 三维统计([date][modelKey][siteKey]):供「按 origin 绑定
180
+ * 自定义价」的显示层重估(issue #16)——只有这个维度能知道「某模型从某中转站
181
+ * 消耗了多少 token」。旧算法快照缺失。
182
+ */
183
+ byDayModelsSite?: Record<string, Record<string, Record<string, ModelUsage>>>;
178
184
  /**
179
185
  * 峰谷分桶(真实判档):折叠时逐调用按 `tierAt(event.time)` 归入高峰/低谷桶,
180
186
  * 峰谷占比与「挪谷省钱」据此展示,不再按比例估算。旧快照可能缺失。
@@ -335,6 +341,8 @@ export interface SessionFold {
335
341
  byModel: Map<string, ModelUsage>;
336
342
  byDay: Map<string, ModelUsage>;
337
343
  byDayModels: Map<string, Map<string, ModelUsage>>;
344
+ /** 模型×日期×站点三维(issue #16):供按 origin 绑定自定义价的显示层重估。 */
345
+ byDayModelsSite: Map<string, Map<string, Map<string, ModelUsage>>>;
338
346
  /** 峰谷分桶:折叠时按调用时刻精确判档(tierAt),键 = 'peak' / 'offPeak'。 */
339
347
  byTier: Map<string, ModelUsage>;
340
348
  /** 工具调用次数(键 = 工具名;tool-call-delta 首见计数)。 */
@@ -371,6 +379,8 @@ export interface SerializedSessionFold {
371
379
  byModel: Record<string, ModelUsage>;
372
380
  byDay: Record<string, ModelUsage>;
373
381
  byDayModels: Record<string, Record<string, ModelUsage>>;
382
+ /** 1.0.10(issue #16)新增;模型×日期×站点三维,旧账本行缺失(合并按空处理)。 */
383
+ byDayModelsSite?: Record<string, Record<string, Record<string, ModelUsage>>>;
374
384
  /** 1.0.8 起新增;旧账本行缺失(合并时按空处理,不触发重折算)。 */
375
385
  byTier?: Record<string, ModelUsage>;
376
386
  byTool?: Record<string, number>;
@@ -407,10 +417,11 @@ export interface UsageLedgerDocument {
407
417
  * 折叠算法版本:归账语义变化时递增。v1 = 按 request/header 归账(稀疏 header 把
408
418
  * 两次 header 之间的用量串到上一个模型,订阅模型首当其冲,issue #14);v2 =
409
419
  * `assistant/message` 自带 source 归账(1.0.7 起);v3 = 联网搜索请求按次估算
410
- * 计费(issue #15,1.0.9 起)——旧行缺 `searchCalls` 维度与搜索估算费用。
420
+ * 计费(issue #15,1.0.9 起);v4 = 模型×日期×站点三维桶(issue #16,按 origin
421
+ * 绑定自定义价的显示层重估)——旧行缺该维度,按无 origin 价处理。
411
422
  * 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
412
423
  */
413
- export declare const FOLD_VERSION = 3;
424
+ export declare const FOLD_VERSION = 4;
414
425
  /**
415
426
  * 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
416
427
  * 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
@@ -179,6 +179,16 @@ export interface UsageStats {
179
179
  cacheMiss: number;
180
180
  cost: number;
181
181
  }>>;
182
+ /** 模型 × 日期 × 站点 三维统计([date][modelKey][siteKey]):按 origin 绑定自定义价的
183
+ * 显示层重估数据源;旧快照可能缺失。 */
184
+ byDayModelsSite?: Record<string, Record<string, Record<string, {
185
+ calls: number;
186
+ input: number;
187
+ output: number;
188
+ cacheHit: number;
189
+ cacheMiss: number;
190
+ cost: number;
191
+ }>>>;
182
192
  /**
183
193
  * 峰谷分桶(全量逐调用真实判档):1.0.8 起服务端按调用时刻精确归桶,
184
194
  * 峰谷占比条优先用它(覆盖全部历史调用);旧快照缺失时回退逐轮估算。
@@ -1,5 +1,5 @@
1
1
  /** Locale dictionaries for the usage billing surface. */
2
- export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendMetric' | 'billing.trendMetricCost' | 'billing.trendMetricTokens' | 'billing.trendEmpty' | 'billing.budget' | 'billing.budgetAmount' | 'billing.budgetSummary' | 'billing.sessions' | 'billing.sessionTitle' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.footer' | 'billing.footerCredit' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.promoBadge' | 'billing.promoUntil' | 'billing.promoOpenEnded' | 'billing.pricingTip' | 'billing.pricingUnit' | 'billing.pricingNotes' | 'billing.ubPeak' | 'billing.ubOff' | 'billing.ubStd' | 'billing.peakBand' | 'billing.pricingSource' | 'billing.noteCache' | 'billing.noteBand' | 'billing.noteSource' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.reconcileDrift' | 'billing.reconcileDismiss' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatMode' | 'billing.floatTargets' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.cardDisplay' | 'billing.cardDisplayHint' | 'billing.cardMetric' | 'billing.cardMetricMoney' | 'billing.cardMetricTokens' | 'billing.triggerMonthTokens' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.subscriptionsStale' | 'billing.staleLedgerNotice' | 'billing.tokenCacheWrite' | 'billing.toolRank' | 'billing.toolName' | 'billing.userPrices' | 'billing.userPricesHint' | 'billing.userPriceSave' | 'billing.sessionStaleBadge' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.roundsHint' | 'billing.anomaly' | 'billing.workspaces' | 'billing.workspacesHint' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.thModel' | 'billing.thInputMiss' | 'billing.thInputHit' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakSharePerCall' | 'billing.offPeakSavings' | 'billing.perfMax' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPos' | 'billing.peakAlertMode' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.searchEstimateHint' | 'billing.siteListDisplay' | 'billing.siteListDisplayHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
2
+ export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendMetric' | 'billing.trendMetricCost' | 'billing.trendMetricTokens' | 'billing.trendEmpty' | 'billing.budget' | 'billing.budgetAmount' | 'billing.budgetSummary' | 'billing.sessions' | 'billing.sessionTitle' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.footer' | 'billing.footerCredit' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.promoBadge' | 'billing.promoUntil' | 'billing.promoOpenEnded' | 'billing.pricingTip' | 'billing.pricingUnit' | 'billing.pricingNotes' | 'billing.ubPeak' | 'billing.ubOff' | 'billing.ubStd' | 'billing.peakBand' | 'billing.pricingSource' | 'billing.noteCache' | 'billing.noteBand' | 'billing.noteSource' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.reconcileDrift' | 'billing.reconcileDismiss' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionExhausted' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.floatWindow' | 'billing.floatModeCombined' | 'billing.floatModeSubscription' | 'billing.floatMode' | 'billing.floatTargets' | 'billing.floatWindowHint' | 'billing.floatNoTargets' | 'billing.floatNoTargetsHint' | 'billing.cardDisplay' | 'billing.cardDisplayHint' | 'billing.cardMetric' | 'billing.cardMetricMoney' | 'billing.cardMetricTokens' | 'billing.triggerMonthTokens' | 'billing.floatPrev' | 'billing.floatNext' | 'billing.subscriptionsStale' | 'billing.staleLedgerNotice' | 'billing.tokenCacheWrite' | 'billing.toolRank' | 'billing.toolName' | 'billing.userPrices' | 'billing.userPricesHint' | 'billing.userPriceSave' | 'billing.userPriceModel' | 'billing.userPriceSource' | 'billing.userPriceSourceHint' | 'billing.userPriceCurrency' | 'billing.userPriceAdd' | 'billing.userPriceRemove' | 'billing.sessionStaleBadge' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.roundsHint' | 'billing.anomaly' | 'billing.workspaces' | 'billing.workspacesHint' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.thModel' | 'billing.thInputMiss' | 'billing.thInputHit' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.tabSettings' | 'billing.settingsHead' | 'billing.settingsHint' | 'billing.budgetHint' | 'billing.peakAlertHint' | 'billing.peakAlertDescPeak' | 'billing.peakAlertDescOff' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakSharePerCall' | 'billing.offPeakSavings' | 'billing.perfMax' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint' | 'billing.tierPeak' | 'billing.tierOff' | 'billing.tierToPeak' | 'billing.tierToOff' | 'billing.tierAlertEnterPeak' | 'billing.tierAlertEnterOff' | 'billing.peakAlertTitlePeak' | 'billing.peakAlertTitleOff' | 'billing.peakAlert' | 'billing.peakAlertLeadMin' | 'billing.peakAlertPos' | 'billing.peakAlertMode' | 'billing.peakAlertPosCorner' | 'billing.peakAlertPosCenter' | 'billing.peakAlertModePeak' | 'billing.peakAlertModeOff' | 'billing.peakAlertModeBoth' | 'billing.peakAlertWebNotify' | 'billing.peakAlertPreview' | 'billing.planTypeCode' | 'billing.planTypeToken' | 'billing.subscriptionFeePerMonth' | 'billing.triggerToday' | 'billing.triggerMonth' | 'billing.subscriptionIncluded' | 'billing.free' | 'billing.official' | 'billing.thirdParty' | 'billing.officialCost' | 'billing.thirdPartyCost' | 'billing.perfSamples' | 'billing.perfTtft' | 'billing.perfP50' | 'billing.perfP90' | 'billing.perfTps' | 'billing.perfLatency' | 'billing.perfEstimated' | 'billing.perfEmpty' | 'billing.perfTpsUnit' | 'billing.perfTitle' | 'billing.perfHint' | 'billing.heatmapYear' | 'billing.heatmapMonth' | 'billing.activeDays' | 'billing.streakDays' | 'billing.subscriptionAutoDetect' | 'billing.pluginInfo' | 'billing.pluginName' | 'billing.pluginDescription' | 'billing.pluginVersion' | 'billing.pluginAuthor' | 'billing.pluginRepository' | 'billing.pluginNpm' | 'billing.pluginLicense' | 'billing.tabToken' | 'billing.tokenExport' | 'billing.tokenExportCsv' | 'billing.tokenCacheHitRate' | 'billing.tokenReasoningShare' | 'billing.tokenReasoningShort' | 'billing.tokenIo' | 'billing.tokenPeak' | 'billing.tokenDaily' | 'billing.tokenByModel' | 'billing.tokenMiss' | 'billing.tokenHit' | 'billing.tokenOutput' | 'billing.tokenTotal' | 'billing.tokenShare' | 'billing.usageStatsTool' | 'billing.usageStatsToolHint' | 'billing.balanceGranted' | 'billing.balanceTopped' | 'billing.balanceDaily' | 'billing.balanceDaysLong' | 'billing.balanceDaysUnit' | 'billing.popTodayModel' | 'billing.popNoConsumption' | 'billing.popQuotaAlert' | 'billing.popRiskNone' | 'billing.popTitle' | 'billing.popDirectLead' | 'billing.popSubLead' | 'billing.popBalanceNormal' | 'billing.popBalanceLow' | 'billing.popQuotaNormal' | 'billing.popQuotaLow' | 'billing.alertBalanceLow' | 'billing.alertQuotaLow' | 'billing.unpricedHint' | 'billing.searchEstimateHint' | 'billing.siteListDisplay' | 'billing.siteListDisplayHint' | 'billing.exportCsvSite' | 'billing.panelRelay' | 'billing.relaySite' | 'billing.relayDirect' | 'billing.relayUnknown' | 'billing.panelRelayQuota' | 'billing.relayBalance' | 'billing.relayNoQuota' | 'billing.relayWindowUsed' | 'billing.relayKindNewApi' | 'billing.relayKindSub2Api' | 'billing.relayKindUnknown' | 'billing.relayCalls';
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>;
@@ -42,16 +42,29 @@ export interface UserPrice {
42
42
  /** 计价币种;缺省 CNY。 */
43
43
  currency?: 'CNY' | 'USD';
44
44
  }
45
+ /** 一条用户自定义价:绑定「模型(计费目录键)+ 可选来源(中转站 origin)」。
46
+ * origin 缺省 = 该模型的默认价;带 origin = 仅该中转站的同名模型用此价。 */
47
+ export interface UserPriceEntry extends UserPrice {
48
+ /** 计费目录键(如 `flash`、`minimax-m2.7`)。 */
49
+ key: string;
50
+ /** 绑定来源(中转站 origin,如 `https://api.my-relay.com`);缺省 = 默认价。 */
51
+ origin?: string;
52
+ }
53
+ /**
54
+ * 注入用户自定义单价列表。每条含模型目录键 + 可选来源(origin)。空数组 = 清除全部
55
+ * 自定义价,回退内置目录。
56
+ * @param entries - 用户自定义价条目(列表)。
57
+ */
58
+ export declare function applyUserPrices(entries: Readonly<UserPriceEntry[]>): void;
59
+ /** 当前生效的用户自定义价条目(设置面板回显用);未设置时 undefined。 */
60
+ export declare function getUserPrices(): Readonly<UserPriceEntry[]> | undefined;
45
61
  /**
46
- * 注入用户自定义单价。键 = 计费目录键;查询侧用归一化两跳宽松命中。
47
- * 空对象 = 清除全部自定义价,回退内置目录。
48
- * @param prices - 目录键 → 自定义单价。
62
+ * 查一个模型(可选来源)的用户自定义价:优先「模型×来源」精确命中;无来源匹配时
63
+ * 回落该模型的无来源默认价;再无则 undefined(走内置目录)。
64
+ * @param key - 计费目录键。
65
+ * @param origin - 调用来源(中转站 origin);缺省仅查默认价。
49
66
  */
50
- export declare function applyUserPrices(prices: Readonly<Record<string, UserPrice>>): void;
51
- /** 当前生效的用户自定义单价(设置面板回显用);未设置时 undefined。 */
52
- export declare function getUserPrices(): Readonly<Record<string, UserPrice>> | undefined;
53
- /** 查一个计费键的用户自定义价(精确键 → 归一化键两跳)。 */
54
- export declare function userPriceOf(key: string): UserPrice | undefined;
67
+ export declare function userPriceOf(key: string, origin?: string): UserPrice | undefined;
55
68
  /**
56
69
  * Apply the node half's live pricing snapshot. Absent fields keep the
57
70
  * built-in catalog and rate; callers never fabricate values.
@@ -6,6 +6,7 @@
6
6
  * client 半区在「设置」Tab 渲染开关并写入同一命名空间。缺省的默认行为是关闭——
7
7
  * 避免该工具默认占用模型每次请求的上下文(coding 场景通常在仪表盘看用量)。
8
8
  */
9
+ import type { UserPriceEntry } from './pricing.ts';
9
10
  /** 设置命名空间 id(小写 kebab-case)。 */
10
11
  export declare const BILLING_SETTINGS_NAMESPACE = "ui-usage-billing";
11
12
  /** 该命名空间下用户可编辑的字段名。 */
@@ -72,22 +73,17 @@ export declare const SITE_LIST_STORAGE_KEY = "dsh.ui-usage-billing.sites";
72
73
  export declare function loadSiteListPrefs(): SiteListPrefs;
73
74
  /** 写入站点列表偏好。失败静默(展示偏好非关键)。 */
74
75
  export declare function saveSiteListPrefs(prefs: SiteListPrefs): void;
75
- /** 用户自定义单价(与 client/pricing.ts 的 `UserPrice` 同形;此处不 import 以保持 node 半区无 client 依赖)。 */
76
- export interface StoredUserPrice {
77
- /** 未命中输入单价(元或美元 / 每百万 token)。 */
78
- input: number;
79
- /** 缓存命中输入单价。 */
80
- cacheHit: number;
81
- /** 输出单价。 */
82
- output: number;
83
- /** 计价币种;缺省 CNY。 */
84
- currency?: 'CNY' | 'USD';
85
- }
86
- /** 自定义价表:计费目录键 → 单价。 */
87
- export type UserPriceMap = Record<string, StoredUserPrice>;
76
+ /** 用户自定义单价(与 client/pricing.ts 的 `UserPriceEntry` 同形;type import,无运行时依赖)。 */
77
+ export type StoredUserPrice = UserPriceEntry;
78
+ /** 自定义价表:模型(+可选来源)→ 单价,条目列表(支持同名模型绑定不同中转站 origin)。 */
79
+ export type UserPriceMap = UserPriceEntry[];
88
80
  /** localStorage key(与其他 `dsh.ui-usage-billing.*` 偏好同命名空间)。 */
89
81
  export declare const USER_PRICES_STORAGE_KEY = "dsh.ui-usage-billing.prices";
90
- /** 读取用户自定义价(写入侧已校验,这里只挡住手工改坏的非数字行)。仅在浏览器半区调用。 */
82
+ /**
83
+ * 读取用户自定义价(写入侧已校验,这里挡住手工改坏的非数字行 与 旧版单行对象格式)。
84
+ * 旧版(1.0.9 及更早)存的是 `Record<目录键, {input,cacheHit,output,currency?}>`,
85
+ * 迁移为「条目列表」(origin 缺省 = 该模型默认价)。仅在浏览器半区调用。
86
+ */
91
87
  export declare function loadUserPrices(): UserPriceMap;
92
88
  /** 写入用户自定义价。失败静默(展示偏好非关键)。 */
93
89
  export declare function saveUserPrices(prices: UserPriceMap): void;
@@ -1,8 +1,11 @@
1
1
  /**
2
- * Vendandor logos: inlined data URIs fetched from models.dev /logos/{id}.svg.
3
- * Keyed by the MODEL_CATALOG entry provider display name so the pricing table
4
- * / model rows can render a real vendor mark before the model name. Vendors not
5
- * listed here fall back to a brand-color letter badge in the UI.
2
+ * Vendor logos: inlined data URIs fetched from models.dev /logos/{id}.svg.
3
+ * Kept to the highest-traffic providers only to hold the client bundle under the
4
+ * DSH Store 256 KiB single-file cap. Every other provider falls back to a
5
+ * brand-color letter badge in the UI (see VendorLogo in UsageBilling.tsx), so no
6
+ * external asset or copyright risk is introduced for unlisted vendors. 月之暗面 is
7
+ * a hand-redrawn minimal crescent — its official SVG is a multi-thousand-node path
8
+ * that alone cost ~6.8 KiB.
6
9
  */
7
10
  export declare const VENDOR_LOGOS: Readonly<Record<string, string>>;
8
11
  export declare function vendorLogoOf(provider: string): string | undefined;
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.0.9",
4
+ "version": "1.0.11",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },