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

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);
@@ -1844,6 +1866,7 @@ function serializeFold(fold) {
1844
1866
  byModel: Object.fromEntries(fold.byModel),
1845
1867
  byDay: Object.fromEntries(fold.byDay),
1846
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)]))])),
1847
1870
  byTier: Object.fromEntries(fold.byTier),
1848
1871
  byTool: Object.fromEntries(fold.byTool),
1849
1872
  bySite: Object.fromEntries(fold.bySite),
@@ -1862,6 +1885,7 @@ function deserializeFold(fold) {
1862
1885
  byModel: new Map(Object.entries(fold.byModel)),
1863
1886
  byDay: new Map(Object.entries(fold.byDay)),
1864
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))]))])),
1865
1889
  byTier: new Map(Object.entries(fold.byTier ?? {})),
1866
1890
  byTool: new Map(Object.entries(fold.byTool ?? {})),
1867
1891
  bySite: new Map(Object.entries(fold.bySite)),
@@ -1919,6 +1943,20 @@ function modelDayCell(map, day, modelKey) {
1919
1943
  }
1920
1944
  return usageCell(models, modelKey);
1921
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
+ }
1922
1960
  /** Get-or-create one turn's accumulation state. */
1923
1961
  function turnState(turns, turn) {
1924
1962
  const existing = turns.get(turn);
@@ -1946,9 +1984,11 @@ function turnState(turns, turn) {
1946
1984
  * @param subscriptionProviders - provider ids billed through subscription plans.
1947
1985
  * @param officialProviderIds - provider ids treated as the official DeepSeek channel
1948
1986
  * (default: any `deepseek`-prefixed id). Others count as third-party.
1987
+ * @param routes - 当前 provider 路由视图(中转站归组)。
1988
+ * @param searchCallEstimateCny - 联网搜索请求的单次费用估算(人民币元;0 关闭估算)。
1949
1989
  * @returns the per-session fold (cached by the incremental aggregator).
1950
1990
  */
1951
- function foldSession(events, subscriptionProviders, officialProviderIds, routes = {}) {
1991
+ function foldSession(events, subscriptionProviders, officialProviderIds, routes = {}, searchCallEstimateCny = DEFAULT_SEARCH_CALL_ESTIMATE_CNY) {
1952
1992
  let seedBoundary = -1;
1953
1993
  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
1994
  if (seedBoundary >= 0) {
@@ -1959,6 +1999,7 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
1959
1999
  byModel: /* @__PURE__ */ new Map(),
1960
2000
  byDay: /* @__PURE__ */ new Map(),
1961
2001
  byDayModels: /* @__PURE__ */ new Map(),
2002
+ byDayModelsSite: /* @__PURE__ */ new Map(),
1962
2003
  byTier: /* @__PURE__ */ new Map(),
1963
2004
  byTool: /* @__PURE__ */ new Map(),
1964
2005
  bySite: /* @__PURE__ */ new Map(),
@@ -2031,6 +2072,23 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
2031
2072
  }
2032
2073
  continue;
2033
2074
  }
2075
+ if (event.type === "web/deepseek-search-llm-request") {
2076
+ const model = event.data.body?.model;
2077
+ const modelKey = typeof model === "string" && model !== "" ? resolveCatalogKey(model) : "other";
2078
+ const day = dayStamp(event.time);
2079
+ const siteKey = siteBucketKey({
2080
+ kind: "direct",
2081
+ provider: "deepseek"
2082
+ });
2083
+ foldSearchCall(fold.total, searchCallEstimateCny);
2084
+ foldSearchCall(usageCell(fold.byModel, modelKey), searchCallEstimateCny);
2085
+ foldSearchCall(usageCell(fold.byDay, day), searchCallEstimateCny);
2086
+ foldSearchCall(modelDayCell(fold.byDayModels, day, modelKey), searchCallEstimateCny);
2087
+ foldSearchCall(modelDaySiteCell(fold.byDayModelsSite, day, modelKey, siteKey), searchCallEstimateCny);
2088
+ foldSearchCall(usageCell(fold.bySite, siteKey), searchCallEstimateCny);
2089
+ foldSearchCall(usageCell(fold.byTier, tierAt(event.time)), searchCallEstimateCny);
2090
+ continue;
2091
+ }
2034
2092
  if (event.type === "assistant/chunk") {
2035
2093
  const data = event.data;
2036
2094
  const turn = data.turn;
@@ -2072,6 +2130,7 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
2072
2130
  foldUsage(usageCell(fold.byModel, modelKey), usage, modelKey, subscription, event.time, official);
2073
2131
  foldUsage(usageCell(fold.byDay, day), usage, modelKey, subscription, event.time, official);
2074
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);
2075
2134
  foldUsage(usageCell(fold.bySite, siteBucket), usage, modelKey, subscription, event.time, official);
2076
2135
  foldUsage(usageCell(fold.byTier, tierAt(event.time)), usage, modelKey, subscription, event.time, official);
2077
2136
  if (subscription) fold.planCalls.set(modelKey, (fold.planCalls.get(modelKey) ?? 0) + 1);
@@ -2159,6 +2218,7 @@ function mergeUsageInto(acc, cell) {
2159
2218
  acc.cost += cell.cost;
2160
2219
  acc.officialCalls += cell.officialCalls;
2161
2220
  acc.officialCost += cell.officialCost;
2221
+ if (cell.searchCalls !== void 0) acc.searchCalls = (acc.searchCalls ?? 0) + cell.searchCalls;
2162
2222
  }
2163
2223
  /** 均值(数组非空时调用;空数组按 0 兜底)。 */
2164
2224
  function mean(values) {
@@ -2200,6 +2260,7 @@ function createUsageAggregator(persistence, options = {}) {
2200
2260
  let lastAt = 0;
2201
2261
  /** 每次聚合取最新的 provider 路由视图(中转站零配置发现);缺省按空处理(全部未知路由)。 */
2202
2262
  const routesOf = () => options.resolveRoutes?.() ?? {};
2263
+ const searchEstimate = options.searchCallEstimateCny ?? .02;
2203
2264
  const ensureLedgerLoaded = async () => {
2204
2265
  if (ledgerLoaded || options.ledger === void 0) return;
2205
2266
  ledgerLoaded = true;
@@ -2264,7 +2325,7 @@ function createUsageAggregator(persistence, options = {}) {
2264
2325
  const { events } = await persistence.readFrom(meta.id, 0);
2265
2326
  const after = await stampOf(meta);
2266
2327
  if (stamp !== null && after !== stamp) continue;
2267
- const fold = foldSession(events, subscriptionProviders, officialProviderIds, routesOf());
2328
+ const fold = foldSession(events, subscriptionProviders, officialProviderIds, routesOf(), searchEstimate);
2268
2329
  cache.set(id, {
2269
2330
  stamp,
2270
2331
  fold
@@ -2280,7 +2341,7 @@ function createUsageAggregator(persistence, options = {}) {
2280
2341
  id,
2281
2342
  ...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
2282
2343
  ...stamp === null ? {} : { stamp },
2283
- foldVersion: 2,
2344
+ foldVersion: 4,
2284
2345
  fold: serializeFold(fold)
2285
2346
  };
2286
2347
  const previous = ledger.get(id);
@@ -2304,7 +2365,7 @@ function createUsageAggregator(persistence, options = {}) {
2304
2365
  for (const entry of ledger.values()) {
2305
2366
  if (included.has(entry.id)) continue;
2306
2367
  try {
2307
- const stale = (entry.foldVersion ?? 1) < 2;
2368
+ const stale = (entry.foldVersion ?? 1) < 4;
2308
2369
  folds.push({
2309
2370
  id: entry.id,
2310
2371
  ...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
@@ -2334,6 +2395,7 @@ function createUsageAggregator(persistence, options = {}) {
2334
2395
  const byModel = /* @__PURE__ */ new Map();
2335
2396
  const byDay = /* @__PURE__ */ new Map();
2336
2397
  const byDayModels = /* @__PURE__ */ new Map();
2398
+ const byDayModelsSite = /* @__PURE__ */ new Map();
2337
2399
  const byTier = /* @__PURE__ */ new Map();
2338
2400
  const byTool = /* @__PURE__ */ new Map();
2339
2401
  const bySite = /* @__PURE__ */ new Map();
@@ -2359,6 +2421,7 @@ function createUsageAggregator(persistence, options = {}) {
2359
2421
  for (const [modelKey, cell] of fold.byModel) mergeUsageInto(usageCell(byModel, modelKey), cell);
2360
2422
  for (const [day, cell] of fold.byDay) mergeUsageInto(usageCell(byDay, day), cell);
2361
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);
2362
2425
  for (const [siteKey, cell] of fold.bySite) mergeUsageInto(usageCell(bySite, siteKey), cell);
2363
2426
  for (const [tierKey, cell] of fold.byTier) mergeUsageInto(usageCell(byTier, tierKey), cell);
2364
2427
  for (const [toolName, count] of fold.byTool) byTool.set(toolName, (byTool.get(toolName) ?? 0) + count);
@@ -2432,6 +2495,7 @@ function createUsageAggregator(persistence, options = {}) {
2432
2495
  return record;
2433
2496
  };
2434
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)]))]));
2435
2499
  const perf = perfModel.size === 0 ? void 0 : {
2436
2500
  byModel: Object.fromEntries([...perfModel].map(([model, acc]) => [model, {
2437
2501
  samples: acc.ttfts.length,
@@ -2459,6 +2523,7 @@ function createUsageAggregator(persistence, options = {}) {
2459
2523
  byModel: toRecord(byModel),
2460
2524
  byDay: toRecord(byDay),
2461
2525
  byDayModels: toModelDayRecord(byDayModels),
2526
+ ...byDayModelsSite.size === 0 ? {} : { byDayModelsSite: toModelDaySiteRecord(byDayModelsSite) },
2462
2527
  bySession: sessionRows.slice(0, 100),
2463
2528
  byTurn: turnRows.slice(0, 200),
2464
2529
  byWorkspace: workspaces.slice(0, 100),
@@ -2469,6 +2534,7 @@ function createUsageAggregator(persistence, options = {}) {
2469
2534
  ...byTool.size === 0 ? {} : { byTool: Object.fromEntries([...byTool].sort((a, b) => b[1] - a[1])) },
2470
2535
  ...bySite.size === 0 ? {} : { bySite: toRecord(bySite) },
2471
2536
  ...unpricedModels.size === 0 ? {} : { unpricedModels: [...unpricedModels].sort() },
2537
+ ...searchEstimate > 0 ? { searchCallEstimateCny: searchEstimate } : {},
2472
2538
  ...perf === void 0 ? {} : { perf },
2473
2539
  ...staleLedgerSessions > 0 ? { staleLedgerSessions } : {},
2474
2540
  byRole: (() => {
@@ -4921,6 +4987,7 @@ function apply(ctx, config = {}) {
4921
4987
  ...config.subscriptionProviders === void 0 ? {} : { subscriptionProviders: config.subscriptionProviders },
4922
4988
  resolveRoutes: () => readPiAiProviderRoutes(ctx.settings),
4923
4989
  ...workspaceTitleResolver === void 0 ? {} : { resolveWorkspaceTitle: workspaceTitleResolver },
4990
+ ...config.searchCallEstimateCny === void 0 ? {} : { searchCallEstimateCny: config.searchCallEstimateCny },
4924
4991
  ledger: ledgerStore
4925
4992
  });
4926
4993
  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. */
@@ -152,6 +175,12 @@ export interface UsageStatsDocument {
152
175
  byDay: Record<string, ModelUsage>;
153
176
  /** 模型 × 日期 二维统计:趋势图按模型堆叠的输入([date][modelKey])。 */
154
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>>>;
155
184
  /**
156
185
  * 峰谷分桶(真实判档):折叠时逐调用按 `tierAt(event.time)` 归入高峰/低谷桶,
157
186
  * 峰谷占比与「挪谷省钱」据此展示,不再按比例估算。旧快照可能缺失。
@@ -182,6 +211,8 @@ export interface UsageStatsDocument {
182
211
  byRole?: RoleCost;
183
212
  /** 不可计价的模型 id(未收录 / 无价,费用按 0 计);供面板提示用户自查与反馈。 */
184
213
  unpricedModels?: readonly string[];
214
+ /** 联网搜索请求的单次费用估算(人民币元,配置回显);0 或缺省 = 未启用估算。 */
215
+ searchCallEstimateCny?: number;
185
216
  /**
186
217
  * 性能指标(TTFT / 生成速度 / 总延迟)按模型与按小时聚合;旧快照可能缺失。
187
218
  * 口径:TTFT = request/header → 首个内容 chunk;生成速度 = 输出 token ÷ 生成时长;
@@ -310,6 +341,8 @@ export interface SessionFold {
310
341
  byModel: Map<string, ModelUsage>;
311
342
  byDay: Map<string, ModelUsage>;
312
343
  byDayModels: Map<string, Map<string, ModelUsage>>;
344
+ /** 模型×日期×站点三维(issue #16):供按 origin 绑定自定义价的显示层重估。 */
345
+ byDayModelsSite: Map<string, Map<string, Map<string, ModelUsage>>>;
313
346
  /** 峰谷分桶:折叠时按调用时刻精确判档(tierAt),键 = 'peak' / 'offPeak'。 */
314
347
  byTier: Map<string, ModelUsage>;
315
348
  /** 工具调用次数(键 = 工具名;tool-call-delta 首见计数)。 */
@@ -346,6 +379,8 @@ export interface SerializedSessionFold {
346
379
  byModel: Record<string, ModelUsage>;
347
380
  byDay: Record<string, ModelUsage>;
348
381
  byDayModels: Record<string, Record<string, ModelUsage>>;
382
+ /** 1.0.10(issue #16)新增;模型×日期×站点三维,旧账本行缺失(合并按空处理)。 */
383
+ byDayModelsSite?: Record<string, Record<string, Record<string, ModelUsage>>>;
349
384
  /** 1.0.8 起新增;旧账本行缺失(合并时按空处理,不触发重折算)。 */
350
385
  byTier?: Record<string, ModelUsage>;
351
386
  byTool?: Record<string, number>;
@@ -381,10 +416,12 @@ export interface UsageLedgerDocument {
381
416
  /**
382
417
  * 折叠算法版本:归账语义变化时递增。v1 = 按 request/header 归账(稀疏 header 把
383
418
  * 两次 header 之间的用量串到上一个模型,订阅模型首当其冲,issue #14);v2 =
384
- * `assistant/message` 自带 source 归账(1.0.7 起)。持久账本行据此区分新旧算法:
385
- * 日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
419
+ * `assistant/message` 自带 source 归账(1.0.7 起);v3 = 联网搜索请求按次估算
420
+ * 计费(issue #15,1.0.9 起);v4 = 模型×日期×站点三维桶(issue #16,按 origin
421
+ * 绑定自定义价的显示层重估)——旧行缺该维度,按无 origin 价处理。
422
+ * 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
386
423
  */
387
- export declare const FOLD_VERSION = 2;
424
+ export declare const FOLD_VERSION = 4;
388
425
  /**
389
426
  * 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
390
427
  * 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
@@ -428,6 +465,8 @@ export declare function messageTextLength(message: unknown): number;
428
465
  * @param subscriptionProviders - provider ids billed through subscription plans.
429
466
  * @param officialProviderIds - provider ids treated as the official DeepSeek channel
430
467
  * (default: any `deepseek`-prefixed id). Others count as third-party.
468
+ * @param routes - 当前 provider 路由视图(中转站归组)。
469
+ * @param searchCallEstimateCny - 联网搜索请求的单次费用估算(人民币元;0 关闭估算)。
431
470
  * @returns the per-session fold (cached by the incremental aggregator).
432
471
  */
433
472
  export declare function foldSession(events: readonly {
@@ -435,7 +474,7 @@ export declare function foldSession(events: readonly {
435
474
  time: number;
436
475
  data: never;
437
476
  seq?: number;
438
- }[], subscriptionProviders: ReadonlySet<string>, officialProviderIds?: ReadonlySet<string>, routes?: Readonly<Record<string, ProviderRouteView>>): SessionFold;
477
+ }[], subscriptionProviders: ReadonlySet<string>, officialProviderIds?: ReadonlySet<string>, routes?: Readonly<Record<string, ProviderRouteView>>, searchCallEstimateCny?: number): SessionFold;
439
478
  /**
440
479
  * 增量聚合器:按会话缓存折叠结果,用日志文件的 mtime+size 作失效键——
441
480
  * 日志没动的会话直接复用,只有写过的会话重新折叠;整份文档另有短 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;
@@ -177,6 +179,16 @@ export interface UsageStats {
177
179
  cacheMiss: number;
178
180
  cost: number;
179
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
+ }>>>;
180
192
  /**
181
193
  * 峰谷分桶(全量逐调用真实判档):1.0.8 起服务端按调用时刻精确归桶,
182
194
  * 峰谷占比条优先用它(覆盖全部历史调用);旧快照缺失时回退逐轮估算。
@@ -227,6 +239,8 @@ export interface UsageStats {
227
239
  }>;
228
240
  /** 不可计价模型 id(未收录/无价,费用按 0 计);旧快照可能缺失。 */
229
241
  unpricedModels?: readonly string[];
242
+ /** 联网搜索请求的单次费用估算(人民币元,配置回显);0 或缺省 = 未启用估算。 */
243
+ searchCallEstimateCny?: number;
230
244
  /** 按角色费用归因(估算口径:输出实测,输入按消息长度摊分);旧快照可能缺失。 */
231
245
  byRole?: {
232
246
  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.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
  /** 该命名空间下用户可编辑的字段名。 */
@@ -56,22 +57,33 @@ export declare const BILLING_CARD_STORAGE_KEY = "dsh.ui-usage-billing.card";
56
57
  export declare function loadBillingCardPrefs(): BillingCardPrefs;
57
58
  /** 写入计费卡偏好。失败静默(展示偏好非关键)。 */
58
59
  export declare function saveBillingCardPrefs(prefs: BillingCardPrefs): void;
59
- /** 用户自定义单价(与 client/pricing.ts 的 `UserPrice` 同形;此处不 import 以保持 node 半区无 client 依赖)。 */
60
- export interface StoredUserPrice {
61
- /** 未命中输入单价(元或美元 / 每百万 token)。 */
62
- input: number;
63
- /** 缓存命中输入单价。 */
64
- cacheHit: number;
65
- /** 输出单价。 */
66
- output: number;
67
- /** 计价币种;缺省 CNY。 */
68
- currency?: 'CNY' | 'USD';
60
+ /**
61
+ * 中转站列表(中转站分布 / 中转站额度)的展示偏好(issue #17)。
62
+ * 纯 client 偏好,存 localStorage(不依赖 node 半区接口/设置 schema)。
63
+ */
64
+ export interface SiteListPrefs {
65
+ /** 隐藏「未知路由」(bySite 的 unknown 桶)与「未识别」类型的中转站占位条目;默认隐藏。 */
66
+ hideUnidentified: boolean;
69
67
  }
70
- /** 自定义价表:计费目录键 → 单价。 */
71
- export type UserPriceMap = Record<string, StoredUserPrice>;
68
+ /** 默认站点列表偏好:隐藏无参考价值的占位条目,净化账单列表。 */
69
+ export declare const DEFAULT_SITE_LIST_PREFS: SiteListPrefs;
70
+ /** localStorage key(与其他 `dsh.ui-usage-billing.*` 偏好同命名空间)。 */
71
+ export declare const SITE_LIST_STORAGE_KEY = "dsh.ui-usage-billing.sites";
72
+ /** 读取站点列表偏好(含损坏/缺失回退到默认)。仅在浏览器半区调用。 */
73
+ export declare function loadSiteListPrefs(): SiteListPrefs;
74
+ /** 写入站点列表偏好。失败静默(展示偏好非关键)。 */
75
+ export declare function saveSiteListPrefs(prefs: SiteListPrefs): void;
76
+ /** 用户自定义单价(与 client/pricing.ts 的 `UserPriceEntry` 同形;type import,无运行时依赖)。 */
77
+ export type StoredUserPrice = UserPriceEntry;
78
+ /** 自定义价表:模型(+可选来源)→ 单价,条目列表(支持同名模型绑定不同中转站 origin)。 */
79
+ export type UserPriceMap = UserPriceEntry[];
72
80
  /** localStorage key(与其他 `dsh.ui-usage-billing.*` 偏好同命名空间)。 */
73
81
  export declare const USER_PRICES_STORAGE_KEY = "dsh.ui-usage-billing.prices";
74
- /** 读取用户自定义价(写入侧已校验,这里只挡住手工改坏的非数字行)。仅在浏览器半区调用。 */
82
+ /**
83
+ * 读取用户自定义价(写入侧已校验,这里挡住手工改坏的非数字行 与 旧版单行对象格式)。
84
+ * 旧版(1.0.9 及更早)存的是 `Record<目录键, {input,cacheHit,output,currency?}>`,
85
+ * 迁移为「条目列表」(origin 缺省 = 该模型默认价)。仅在浏览器半区调用。
86
+ */
75
87
  export declare function loadUserPrices(): UserPriceMap;
76
88
  /** 写入用户自定义价。失败静默(展示偏好非关键)。 */
77
89
  export declare function saveUserPrices(prices: UserPriceMap): void;
@@ -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.10",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },