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

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
@@ -1761,6 +1761,28 @@ function foldUsage(acc, usage, key, subscription, timeMs, official = false) {
1761
1761
  if (official) acc.officialCost += thisCost;
1762
1762
  }
1763
1763
  }
1764
+ /**
1765
+ * 联网搜索辅助请求的单次费用估算默认值(人民币元)。DeepSeek 官方对搜索请求
1766
+ * (web_search 服务端工具注入上下文)照常计费,实测每次约 0.01~0.03 元,取中值;
1767
+ * 部署可在插件配置 `searchCallEstimateCny` 覆盖(设 0 关闭估算)。
1768
+ */
1769
+ const DEFAULT_SEARCH_CALL_ESTIMATE_CNY = .02;
1770
+ /**
1771
+ * Fold one auxiliary web-search LLM request (issue #15) into an accumulator.
1772
+ * 这类调用绕过对话通道直连官方端点,日志只记请求(无响应/用量事件),token
1773
+ * 不可知:按「每次估值」计入费用并单独累计 `searchCalls`,不产生 token 维度。
1774
+ * @param acc - the accumulator to mutate.
1775
+ * @param estimateCny - per-call cost estimate in CNY; 0 disables the estimate.
1776
+ */
1777
+ function foldSearchCall(acc, estimateCny) {
1778
+ acc.calls += 1;
1779
+ acc.searchCalls = (acc.searchCalls ?? 0) + 1;
1780
+ acc.officialCalls += 1;
1781
+ if (estimateCny > 0) {
1782
+ acc.cost += estimateCny;
1783
+ acc.officialCost += estimateCny;
1784
+ }
1785
+ }
1764
1786
  /** Local-time date stamp (the host runs in the user's timezone). */
1765
1787
  function dayStamp(time) {
1766
1788
  const date = new Date(time);
@@ -1946,9 +1968,11 @@ function turnState(turns, turn) {
1946
1968
  * @param subscriptionProviders - provider ids billed through subscription plans.
1947
1969
  * @param officialProviderIds - provider ids treated as the official DeepSeek channel
1948
1970
  * (default: any `deepseek`-prefixed id). Others count as third-party.
1971
+ * @param routes - 当前 provider 路由视图(中转站归组)。
1972
+ * @param searchCallEstimateCny - 联网搜索请求的单次费用估算(人民币元;0 关闭估算)。
1949
1973
  * @returns the per-session fold (cached by the incremental aggregator).
1950
1974
  */
1951
- function foldSession(events, subscriptionProviders, officialProviderIds, routes = {}) {
1975
+ function foldSession(events, subscriptionProviders, officialProviderIds, routes = {}, searchCallEstimateCny = DEFAULT_SEARCH_CALL_ESTIMATE_CNY) {
1952
1976
  let seedBoundary = -1;
1953
1977
  for (const event of events) if (event.type === "session/end-seed" && typeof event.seq === "number" && Number.isFinite(event.seq)) seedBoundary = Math.max(seedBoundary, event.seq);
1954
1978
  if (seedBoundary >= 0) {
@@ -2031,6 +2055,21 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
2031
2055
  }
2032
2056
  continue;
2033
2057
  }
2058
+ if (event.type === "web/deepseek-search-llm-request") {
2059
+ const model = event.data.body?.model;
2060
+ const modelKey = typeof model === "string" && model !== "" ? resolveCatalogKey(model) : "other";
2061
+ const day = dayStamp(event.time);
2062
+ foldSearchCall(fold.total, searchCallEstimateCny);
2063
+ foldSearchCall(usageCell(fold.byModel, modelKey), searchCallEstimateCny);
2064
+ foldSearchCall(usageCell(fold.byDay, day), searchCallEstimateCny);
2065
+ foldSearchCall(modelDayCell(fold.byDayModels, day, modelKey), searchCallEstimateCny);
2066
+ foldSearchCall(usageCell(fold.bySite, siteBucketKey({
2067
+ kind: "direct",
2068
+ provider: "deepseek"
2069
+ })), searchCallEstimateCny);
2070
+ foldSearchCall(usageCell(fold.byTier, tierAt(event.time)), searchCallEstimateCny);
2071
+ continue;
2072
+ }
2034
2073
  if (event.type === "assistant/chunk") {
2035
2074
  const data = event.data;
2036
2075
  const turn = data.turn;
@@ -2159,6 +2198,7 @@ function mergeUsageInto(acc, cell) {
2159
2198
  acc.cost += cell.cost;
2160
2199
  acc.officialCalls += cell.officialCalls;
2161
2200
  acc.officialCost += cell.officialCost;
2201
+ if (cell.searchCalls !== void 0) acc.searchCalls = (acc.searchCalls ?? 0) + cell.searchCalls;
2162
2202
  }
2163
2203
  /** 均值(数组非空时调用;空数组按 0 兜底)。 */
2164
2204
  function mean(values) {
@@ -2200,6 +2240,7 @@ function createUsageAggregator(persistence, options = {}) {
2200
2240
  let lastAt = 0;
2201
2241
  /** 每次聚合取最新的 provider 路由视图(中转站零配置发现);缺省按空处理(全部未知路由)。 */
2202
2242
  const routesOf = () => options.resolveRoutes?.() ?? {};
2243
+ const searchEstimate = options.searchCallEstimateCny ?? .02;
2203
2244
  const ensureLedgerLoaded = async () => {
2204
2245
  if (ledgerLoaded || options.ledger === void 0) return;
2205
2246
  ledgerLoaded = true;
@@ -2264,7 +2305,7 @@ function createUsageAggregator(persistence, options = {}) {
2264
2305
  const { events } = await persistence.readFrom(meta.id, 0);
2265
2306
  const after = await stampOf(meta);
2266
2307
  if (stamp !== null && after !== stamp) continue;
2267
- const fold = foldSession(events, subscriptionProviders, officialProviderIds, routesOf());
2308
+ const fold = foldSession(events, subscriptionProviders, officialProviderIds, routesOf(), searchEstimate);
2268
2309
  cache.set(id, {
2269
2310
  stamp,
2270
2311
  fold
@@ -2280,7 +2321,7 @@ function createUsageAggregator(persistence, options = {}) {
2280
2321
  id,
2281
2322
  ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
2282
2323
  ...stamp === null ? {} : { stamp },
2283
- foldVersion: 2,
2324
+ foldVersion: 3,
2284
2325
  fold: serializeFold(fold)
2285
2326
  };
2286
2327
  const previous = ledger.get(id);
@@ -2304,7 +2345,7 @@ function createUsageAggregator(persistence, options = {}) {
2304
2345
  for (const entry of ledger.values()) {
2305
2346
  if (included.has(entry.id)) continue;
2306
2347
  try {
2307
- const stale = (entry.foldVersion ?? 1) < 2;
2348
+ const stale = (entry.foldVersion ?? 1) < 3;
2308
2349
  folds.push({
2309
2350
  id: entry.id,
2310
2351
  ...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
@@ -2469,6 +2510,7 @@ function createUsageAggregator(persistence, options = {}) {
2469
2510
  ...byTool.size === 0 ? {} : { byTool: Object.fromEntries([...byTool].sort((a, b) => b[1] - a[1])) },
2470
2511
  ...bySite.size === 0 ? {} : { bySite: toRecord(bySite) },
2471
2512
  ...unpricedModels.size === 0 ? {} : { unpricedModels: [...unpricedModels].sort() },
2513
+ ...searchEstimate > 0 ? { searchCallEstimateCny: searchEstimate } : {},
2472
2514
  ...perf === void 0 ? {} : { perf },
2473
2515
  ...staleLedgerSessions > 0 ? { staleLedgerSessions } : {},
2474
2516
  byRole: (() => {
@@ -4921,6 +4963,7 @@ function apply(ctx, config = {}) {
4921
4963
  ...config.subscriptionProviders === void 0 ? {} : { subscriptionProviders: config.subscriptionProviders },
4922
4964
  resolveRoutes: () => readPiAiProviderRoutes(ctx.settings),
4923
4965
  ...workspaceTitleResolver === void 0 ? {} : { resolveWorkspaceTitle: workspaceTitleResolver },
4966
+ ...config.searchCallEstimateCny === void 0 ? {} : { searchCallEstimateCny: config.searchCallEstimateCny },
4924
4967
  ledger: ledgerStore
4925
4968
  });
4926
4969
  const candidates = [
@@ -74,6 +74,9 @@ export interface AggregateOptions {
74
74
  /** 独立的持久用量账本。启用后,已经成功折叠过的会话即使随后从
75
75
  * sessionPersistence 中永久删除,也会继续计入累计用量。 */
76
76
  ledger?: UsageLedgerStore;
77
+ /** 联网搜索请求(`web/deepseek-search-llm-request`,无用量事件)的单次费用
78
+ * 估算(人民币元);默认 {@link DEFAULT_SEARCH_CALL_ESTIMATE_CNY},设 0 关闭。 */
79
+ searchCallEstimateCny?: number;
77
80
  }
78
81
  /** 每会话折叠缓存默认上限:超过则按 LRU 淘汰(P1-6 峰值内存治理)。 */
79
82
  export declare const DEFAULT_MAX_CACHE_SESSIONS = 400;
@@ -98,6 +101,12 @@ export interface ModelUsage {
98
101
  officialCalls: number;
99
102
  /** 走官方渠道的费用(CNY);三方费用 = cost - officialCost。 */
100
103
  officialCost: number;
104
+ /**
105
+ * 联网搜索辅助请求的估算调用数(`web/deepseek-search-llm-request`,日志只有
106
+ * 请求无用量事件);已按每次 `searchCallEstimateCny` 估算计入 `cost`,不计
107
+ * token。旧快照缺失。
108
+ */
109
+ searchCalls?: number;
101
110
  }
102
111
  /** Zeroed usage accumulator. */
103
112
  export declare function emptyUsage(): ModelUsage;
@@ -113,6 +122,20 @@ export declare function emptyUsage(): ModelUsage;
113
122
  * @param official - whether the call went through the official DeepSeek channel (vs a third-party relay).
114
123
  */
115
124
  export declare function foldUsage(acc: ModelUsage, usage: TokenUsage, key: string, subscription: boolean, timeMs: number, official?: boolean): void;
125
+ /**
126
+ * 联网搜索辅助请求的单次费用估算默认值(人民币元)。DeepSeek 官方对搜索请求
127
+ * (web_search 服务端工具注入上下文)照常计费,实测每次约 0.01~0.03 元,取中值;
128
+ * 部署可在插件配置 `searchCallEstimateCny` 覆盖(设 0 关闭估算)。
129
+ */
130
+ export declare const DEFAULT_SEARCH_CALL_ESTIMATE_CNY = 0.02;
131
+ /**
132
+ * Fold one auxiliary web-search LLM request (issue #15) into an accumulator.
133
+ * 这类调用绕过对话通道直连官方端点,日志只记请求(无响应/用量事件),token
134
+ * 不可知:按「每次估值」计入费用并单独累计 `searchCalls`,不产生 token 维度。
135
+ * @param acc - the accumulator to mutate.
136
+ * @param estimateCny - per-call cost estimate in CNY; 0 disables the estimate.
137
+ */
138
+ export declare function foldSearchCall(acc: ModelUsage, estimateCny: number): void;
116
139
  /** Local-time date stamp (the host runs in the user's timezone). */
117
140
  export declare function dayStamp(time: number): string;
118
141
  /** Local-time hour stamp `YYYY-MM-DDTHH` — the performance series bucket key. */
@@ -182,6 +205,8 @@ export interface UsageStatsDocument {
182
205
  byRole?: RoleCost;
183
206
  /** 不可计价的模型 id(未收录 / 无价,费用按 0 计);供面板提示用户自查与反馈。 */
184
207
  unpricedModels?: readonly string[];
208
+ /** 联网搜索请求的单次费用估算(人民币元,配置回显);0 或缺省 = 未启用估算。 */
209
+ searchCallEstimateCny?: number;
185
210
  /**
186
211
  * 性能指标(TTFT / 生成速度 / 总延迟)按模型与按小时聚合;旧快照可能缺失。
187
212
  * 口径:TTFT = request/header → 首个内容 chunk;生成速度 = 输出 token ÷ 生成时长;
@@ -381,10 +406,11 @@ export interface UsageLedgerDocument {
381
406
  /**
382
407
  * 折叠算法版本:归账语义变化时递增。v1 = 按 request/header 归账(稀疏 header 把
383
408
  * 两次 header 之间的用量串到上一个模型,订阅模型首当其冲,issue #14);v2 =
384
- * `assistant/message` 自带 source 归账(1.0.7 起)。持久账本行据此区分新旧算法:
385
- * 日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
409
+ * `assistant/message` 自带 source 归账(1.0.7 起);v3 = 联网搜索请求按次估算
410
+ * 计费(issue #15,1.0.9 起)——旧行缺 `searchCalls` 维度与搜索估算费用。
411
+ * 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
386
412
  */
387
- export declare const FOLD_VERSION = 2;
413
+ export declare const FOLD_VERSION = 3;
388
414
  /**
389
415
  * 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
390
416
  * 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
@@ -428,6 +454,8 @@ export declare function messageTextLength(message: unknown): number;
428
454
  * @param subscriptionProviders - provider ids billed through subscription plans.
429
455
  * @param officialProviderIds - provider ids treated as the official DeepSeek channel
430
456
  * (default: any `deepseek`-prefixed id). Others count as third-party.
457
+ * @param routes - 当前 provider 路由视图(中转站归组)。
458
+ * @param searchCallEstimateCny - 联网搜索请求的单次费用估算(人民币元;0 关闭估算)。
431
459
  * @returns the per-session fold (cached by the incremental aggregator).
432
460
  */
433
461
  export declare function foldSession(events: readonly {
@@ -435,7 +463,7 @@ export declare function foldSession(events: readonly {
435
463
  time: number;
436
464
  data: never;
437
465
  seq?: number;
438
- }[], subscriptionProviders: ReadonlySet<string>, officialProviderIds?: ReadonlySet<string>, routes?: Readonly<Record<string, ProviderRouteView>>): SessionFold;
466
+ }[], subscriptionProviders: ReadonlySet<string>, officialProviderIds?: ReadonlySet<string>, routes?: Readonly<Record<string, ProviderRouteView>>, searchCallEstimateCny?: number): SessionFold;
439
467
  /**
440
468
  * 增量聚合器:按会话缓存折叠结果,用日志文件的 mtime+size 作失效键——
441
469
  * 日志没动的会话直接复用,只有写过的会话重新折叠;整份文档另有短 TTL
@@ -143,6 +143,8 @@ export interface UsageStats {
143
143
  cost: number;
144
144
  /** 输出中的 reasoning(思考)token;已含在 output 内。 */
145
145
  reasoning: number;
146
+ /** 联网搜索请求的估算调用数(已按次估值计入 cost);1.0.9 起新增,旧快照缺失。 */
147
+ searchCalls?: number;
146
148
  };
147
149
  byModel: Record<string, {
148
150
  calls: number;
@@ -227,6 +229,8 @@ export interface UsageStats {
227
229
  }>;
228
230
  /** 不可计价模型 id(未收录/无价,费用按 0 计);旧快照可能缺失。 */
229
231
  unpricedModels?: readonly string[];
232
+ /** 联网搜索请求的单次费用估算(人民币元,配置回显);0 或缺省 = 未启用估算。 */
233
+ searchCallEstimateCny?: number;
230
234
  /** 按角色费用归因(估算口径:输出实测,输入按消息长度摊分);旧快照可能缺失。 */
231
235
  byRole?: {
232
236
  user: number;
@@ -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.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.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>;
@@ -56,6 +56,22 @@ export declare const BILLING_CARD_STORAGE_KEY = "dsh.ui-usage-billing.card";
56
56
  export declare function loadBillingCardPrefs(): BillingCardPrefs;
57
57
  /** 写入计费卡偏好。失败静默(展示偏好非关键)。 */
58
58
  export declare function saveBillingCardPrefs(prefs: BillingCardPrefs): void;
59
+ /**
60
+ * 中转站列表(中转站分布 / 中转站额度)的展示偏好(issue #17)。
61
+ * 纯 client 偏好,存 localStorage(不依赖 node 半区接口/设置 schema)。
62
+ */
63
+ export interface SiteListPrefs {
64
+ /** 隐藏「未知路由」(bySite 的 unknown 桶)与「未识别」类型的中转站占位条目;默认隐藏。 */
65
+ hideUnidentified: boolean;
66
+ }
67
+ /** 默认站点列表偏好:隐藏无参考价值的占位条目,净化账单列表。 */
68
+ export declare const DEFAULT_SITE_LIST_PREFS: SiteListPrefs;
69
+ /** localStorage key(与其他 `dsh.ui-usage-billing.*` 偏好同命名空间)。 */
70
+ export declare const SITE_LIST_STORAGE_KEY = "dsh.ui-usage-billing.sites";
71
+ /** 读取站点列表偏好(含损坏/缺失回退到默认)。仅在浏览器半区调用。 */
72
+ export declare function loadSiteListPrefs(): SiteListPrefs;
73
+ /** 写入站点列表偏好。失败静默(展示偏好非关键)。 */
74
+ export declare function saveSiteListPrefs(prefs: SiteListPrefs): void;
59
75
  /** 用户自定义单价(与 client/pricing.ts 的 `UserPrice` 同形;此处不 import 以保持 node 半区无 client 依赖)。 */
60
76
  export interface StoredUserPrice {
61
77
  /** 未命中输入单价(元或美元 / 每百万 token)。 */
@@ -59,6 +59,12 @@ export interface UsageBillingConfig {
59
59
  /** `usage_stats` 工具注入的组合 base(默认 false:不注入);与设置命名空间同字段,
60
60
  * 作为用户设置(设置 Tab 开关)的组合兜底。该工具占用每次请求的上下文,coding 场景多在仪表盘查看。 */
61
61
  enableUsageStatsTool?: boolean;
62
+ /**
63
+ * 联网搜索请求(`web/deepseek-search-llm-request`,日志只有请求、无用量事件)
64
+ * 的单次费用估算(人民币元)。这类调用直连官方 api.deepseek.com,开放平台照常
65
+ * 计费但本地日志无 usage,按次估值计入今日/本月费用;默认 0.02 元/次,设 0 关闭。
66
+ */
67
+ searchCallEstimateCny?: number;
62
68
  }
63
69
  /**
64
70
  * Create the atomic file-backed durable-ledger store. The previous complete file
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.8",
4
+ "version": "1.0.9",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },