@kenz1117/dsh-ui-usage-billing 1.0.7 → 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/README.md +3 -0
- package/lib/client.js +5 -5
- package/lib/index.js +84 -6
- package/lib/types/aggregate.d.ts +60 -4
- package/lib/types/client/PerfPanel.d.ts +4 -0
- package/lib/types/client/TokenPanel.d.ts +3 -2
- package/lib/types/client/UsageBilling.d.ts +30 -11
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/pricing.d.ts +37 -0
- package/lib/types/client/usage-billing-settings.d.ts +35 -0
- package/lib/types/index.d.ts +6 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -905,7 +905,8 @@ const MODEL_CATALOG = [
|
|
|
905
905
|
output: 6
|
|
906
906
|
}
|
|
907
907
|
},
|
|
908
|
-
peakHours: "Standard / Flex"
|
|
908
|
+
peakHours: "Standard / Flex",
|
|
909
|
+
tierSemantics: "latency"
|
|
909
910
|
},
|
|
910
911
|
{
|
|
911
912
|
key: "gemini-flash",
|
|
@@ -923,7 +924,8 @@ const MODEL_CATALOG = [
|
|
|
923
924
|
output: 3.75
|
|
924
925
|
}
|
|
925
926
|
},
|
|
926
|
-
peakHours: "Standard / Flex"
|
|
927
|
+
peakHours: "Standard / Flex",
|
|
928
|
+
tierSemantics: "latency"
|
|
927
929
|
},
|
|
928
930
|
{
|
|
929
931
|
key: "grok",
|
|
@@ -1746,6 +1748,7 @@ function foldUsage(acc, usage, key, subscription, timeMs, official = false) {
|
|
|
1746
1748
|
acc.reasoning += usage.reasoningTokens ?? 0;
|
|
1747
1749
|
acc.cacheHit += cacheHit;
|
|
1748
1750
|
acc.cacheMiss += cacheMiss;
|
|
1751
|
+
if ((usage.cacheWriteTokens ?? 0) > 0) acc.cacheWrite = (acc.cacheWrite ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
1749
1752
|
if (official) acc.officialCalls += 1;
|
|
1750
1753
|
if (!subscription && isPriced(key)) {
|
|
1751
1754
|
const thisCost = computeCostAt(modelOf(key), {
|
|
@@ -1758,6 +1761,28 @@ function foldUsage(acc, usage, key, subscription, timeMs, official = false) {
|
|
|
1758
1761
|
if (official) acc.officialCost += thisCost;
|
|
1759
1762
|
}
|
|
1760
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
|
+
}
|
|
1761
1786
|
/** Local-time date stamp (the host runs in the user's timezone). */
|
|
1762
1787
|
function dayStamp(time) {
|
|
1763
1788
|
const date = new Date(time);
|
|
@@ -1797,6 +1822,8 @@ function workspaceNameOf(cwd) {
|
|
|
1797
1822
|
if (cwd === void 0 || cwd === "") return "—";
|
|
1798
1823
|
return cwd.split(/[\\/]/).filter(Boolean).at(-1) ?? "—";
|
|
1799
1824
|
}
|
|
1825
|
+
/** TTFT 尖峰阈值(毫秒):超过计为一次尖峰样本,用于定位服务端抖动。 */
|
|
1826
|
+
const PERF_SPIKE_MS = 1e4;
|
|
1800
1827
|
/**
|
|
1801
1828
|
* 账本迁移注册表。首条迁移给 1.0.6 及更早的行回填 foldVersion = 1(它们全部出自
|
|
1802
1829
|
* header 归因算法);此后新写入的行总带当前 {@link FOLD_VERSION}。
|
|
@@ -1839,6 +1866,8 @@ function serializeFold(fold) {
|
|
|
1839
1866
|
byModel: Object.fromEntries(fold.byModel),
|
|
1840
1867
|
byDay: Object.fromEntries(fold.byDay),
|
|
1841
1868
|
byDayModels: Object.fromEntries([...fold.byDayModels].map(([day, models]) => [day, Object.fromEntries(models)])),
|
|
1869
|
+
byTier: Object.fromEntries(fold.byTier),
|
|
1870
|
+
byTool: Object.fromEntries(fold.byTool),
|
|
1842
1871
|
bySite: Object.fromEntries(fold.bySite),
|
|
1843
1872
|
unpricedModels: [...fold.unpricedModels],
|
|
1844
1873
|
planCalls: Object.fromEntries(fold.planCalls),
|
|
@@ -1855,6 +1884,8 @@ function deserializeFold(fold) {
|
|
|
1855
1884
|
byModel: new Map(Object.entries(fold.byModel)),
|
|
1856
1885
|
byDay: new Map(Object.entries(fold.byDay)),
|
|
1857
1886
|
byDayModels: new Map(Object.entries(fold.byDayModels).map(([day, models]) => [day, new Map(Object.entries(models))])),
|
|
1887
|
+
byTier: new Map(Object.entries(fold.byTier ?? {})),
|
|
1888
|
+
byTool: new Map(Object.entries(fold.byTool ?? {})),
|
|
1858
1889
|
bySite: new Map(Object.entries(fold.bySite)),
|
|
1859
1890
|
unpricedModels: new Set(fold.unpricedModels),
|
|
1860
1891
|
planCalls: new Map(Object.entries(fold.planCalls)),
|
|
@@ -1937,9 +1968,11 @@ function turnState(turns, turn) {
|
|
|
1937
1968
|
* @param subscriptionProviders - provider ids billed through subscription plans.
|
|
1938
1969
|
* @param officialProviderIds - provider ids treated as the official DeepSeek channel
|
|
1939
1970
|
* (default: any `deepseek`-prefixed id). Others count as third-party.
|
|
1971
|
+
* @param routes - 当前 provider 路由视图(中转站归组)。
|
|
1972
|
+
* @param searchCallEstimateCny - 联网搜索请求的单次费用估算(人民币元;0 关闭估算)。
|
|
1940
1973
|
* @returns the per-session fold (cached by the incremental aggregator).
|
|
1941
1974
|
*/
|
|
1942
|
-
function foldSession(events, subscriptionProviders, officialProviderIds, routes = {}) {
|
|
1975
|
+
function foldSession(events, subscriptionProviders, officialProviderIds, routes = {}, searchCallEstimateCny = DEFAULT_SEARCH_CALL_ESTIMATE_CNY) {
|
|
1943
1976
|
let seedBoundary = -1;
|
|
1944
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);
|
|
1945
1978
|
if (seedBoundary >= 0) {
|
|
@@ -1950,6 +1983,8 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
|
|
|
1950
1983
|
byModel: /* @__PURE__ */ new Map(),
|
|
1951
1984
|
byDay: /* @__PURE__ */ new Map(),
|
|
1952
1985
|
byDayModels: /* @__PURE__ */ new Map(),
|
|
1986
|
+
byTier: /* @__PURE__ */ new Map(),
|
|
1987
|
+
byTool: /* @__PURE__ */ new Map(),
|
|
1953
1988
|
bySite: /* @__PURE__ */ new Map(),
|
|
1954
1989
|
unpricedModels: /* @__PURE__ */ new Set(),
|
|
1955
1990
|
planCalls: /* @__PURE__ */ new Map(),
|
|
@@ -1970,6 +2005,7 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
|
|
|
1970
2005
|
const turns = /* @__PURE__ */ new Map();
|
|
1971
2006
|
const steps = /* @__PURE__ */ new Map();
|
|
1972
2007
|
let lastOpenStepKey;
|
|
2008
|
+
const toolSeen = /* @__PURE__ */ new Set();
|
|
1973
2009
|
for (const event of events) {
|
|
1974
2010
|
if (seedBoundary >= 0 && typeof event.seq === "number" && Number.isFinite(event.seq) && event.seq < seedBoundary) continue;
|
|
1975
2011
|
fold.lastActive = Math.max(fold.lastActive, event.time);
|
|
@@ -2019,6 +2055,21 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
|
|
|
2019
2055
|
}
|
|
2020
2056
|
continue;
|
|
2021
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
|
+
}
|
|
2022
2073
|
if (event.type === "assistant/chunk") {
|
|
2023
2074
|
const data = event.data;
|
|
2024
2075
|
const turn = data.turn;
|
|
@@ -2031,6 +2082,16 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
|
|
|
2031
2082
|
state.lastContentTime = event.time;
|
|
2032
2083
|
}
|
|
2033
2084
|
}
|
|
2085
|
+
if (chunk?.type === "tool-call-delta" && typeof turn === "number" && typeof step === "number") {
|
|
2086
|
+
const index = chunk.index;
|
|
2087
|
+
const name = chunk.name;
|
|
2088
|
+
const seenKey = `${turn}:${step}:${typeof index === "number" ? index : "-"}`;
|
|
2089
|
+
if (!toolSeen.has(seenKey)) {
|
|
2090
|
+
toolSeen.add(seenKey);
|
|
2091
|
+
const toolName = typeof name === "string" && name !== "" ? name : "unknown";
|
|
2092
|
+
fold.byTool.set(toolName, (fold.byTool.get(toolName) ?? 0) + 1);
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2034
2095
|
continue;
|
|
2035
2096
|
}
|
|
2036
2097
|
if (event.type !== "assistant/message") continue;
|
|
@@ -2051,6 +2112,7 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
|
|
|
2051
2112
|
foldUsage(usageCell(fold.byDay, day), usage, modelKey, subscription, event.time, official);
|
|
2052
2113
|
foldUsage(modelDayCell(fold.byDayModels, day, modelKey), usage, modelKey, subscription, event.time, official);
|
|
2053
2114
|
foldUsage(usageCell(fold.bySite, siteBucket), usage, modelKey, subscription, event.time, official);
|
|
2115
|
+
foldUsage(usageCell(fold.byTier, tierAt(event.time)), usage, modelKey, subscription, event.time, official);
|
|
2054
2116
|
if (subscription) fold.planCalls.set(modelKey, (fold.planCalls.get(modelKey) ?? 0) + 1);
|
|
2055
2117
|
const turn = event.data.turn ?? -1;
|
|
2056
2118
|
const state = turnState(turns, turn);
|
|
@@ -2132,9 +2194,11 @@ function mergeUsageInto(acc, cell) {
|
|
|
2132
2194
|
acc.reasoning += cell.reasoning;
|
|
2133
2195
|
acc.cacheHit += cell.cacheHit;
|
|
2134
2196
|
acc.cacheMiss += cell.cacheMiss;
|
|
2197
|
+
if (cell.cacheWrite !== void 0) acc.cacheWrite = (acc.cacheWrite ?? 0) + cell.cacheWrite;
|
|
2135
2198
|
acc.cost += cell.cost;
|
|
2136
2199
|
acc.officialCalls += cell.officialCalls;
|
|
2137
2200
|
acc.officialCost += cell.officialCost;
|
|
2201
|
+
if (cell.searchCalls !== void 0) acc.searchCalls = (acc.searchCalls ?? 0) + cell.searchCalls;
|
|
2138
2202
|
}
|
|
2139
2203
|
/** 均值(数组非空时调用;空数组按 0 兜底)。 */
|
|
2140
2204
|
function mean(values) {
|
|
@@ -2176,6 +2240,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2176
2240
|
let lastAt = 0;
|
|
2177
2241
|
/** 每次聚合取最新的 provider 路由视图(中转站零配置发现);缺省按空处理(全部未知路由)。 */
|
|
2178
2242
|
const routesOf = () => options.resolveRoutes?.() ?? {};
|
|
2243
|
+
const searchEstimate = options.searchCallEstimateCny ?? .02;
|
|
2179
2244
|
const ensureLedgerLoaded = async () => {
|
|
2180
2245
|
if (ledgerLoaded || options.ledger === void 0) return;
|
|
2181
2246
|
ledgerLoaded = true;
|
|
@@ -2240,7 +2305,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2240
2305
|
const { events } = await persistence.readFrom(meta.id, 0);
|
|
2241
2306
|
const after = await stampOf(meta);
|
|
2242
2307
|
if (stamp !== null && after !== stamp) continue;
|
|
2243
|
-
const fold = foldSession(events, subscriptionProviders, officialProviderIds, routesOf());
|
|
2308
|
+
const fold = foldSession(events, subscriptionProviders, officialProviderIds, routesOf(), searchEstimate);
|
|
2244
2309
|
cache.set(id, {
|
|
2245
2310
|
stamp,
|
|
2246
2311
|
fold
|
|
@@ -2256,7 +2321,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2256
2321
|
id,
|
|
2257
2322
|
...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
|
|
2258
2323
|
...stamp === null ? {} : { stamp },
|
|
2259
|
-
foldVersion:
|
|
2324
|
+
foldVersion: 3,
|
|
2260
2325
|
fold: serializeFold(fold)
|
|
2261
2326
|
};
|
|
2262
2327
|
const previous = ledger.get(id);
|
|
@@ -2280,7 +2345,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2280
2345
|
for (const entry of ledger.values()) {
|
|
2281
2346
|
if (included.has(entry.id)) continue;
|
|
2282
2347
|
try {
|
|
2283
|
-
const stale = (entry.foldVersion ?? 1) <
|
|
2348
|
+
const stale = (entry.foldVersion ?? 1) < 3;
|
|
2284
2349
|
folds.push({
|
|
2285
2350
|
id: entry.id,
|
|
2286
2351
|
...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
|
|
@@ -2310,6 +2375,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2310
2375
|
const byModel = /* @__PURE__ */ new Map();
|
|
2311
2376
|
const byDay = /* @__PURE__ */ new Map();
|
|
2312
2377
|
const byDayModels = /* @__PURE__ */ new Map();
|
|
2378
|
+
const byTier = /* @__PURE__ */ new Map();
|
|
2379
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
2313
2380
|
const bySite = /* @__PURE__ */ new Map();
|
|
2314
2381
|
const unpricedModels = /* @__PURE__ */ new Set();
|
|
2315
2382
|
const planCalls = /* @__PURE__ */ new Map();
|
|
@@ -2334,6 +2401,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2334
2401
|
for (const [day, cell] of fold.byDay) mergeUsageInto(usageCell(byDay, day), cell);
|
|
2335
2402
|
for (const [day, models] of fold.byDayModels) for (const [modelKey, cell] of models) mergeUsageInto(modelDayCell(byDayModels, day, modelKey), cell);
|
|
2336
2403
|
for (const [siteKey, cell] of fold.bySite) mergeUsageInto(usageCell(bySite, siteKey), cell);
|
|
2404
|
+
for (const [tierKey, cell] of fold.byTier) mergeUsageInto(usageCell(byTier, tierKey), cell);
|
|
2405
|
+
for (const [toolName, count] of fold.byTool) byTool.set(toolName, (byTool.get(toolName) ?? 0) + count);
|
|
2337
2406
|
for (const id of fold.unpricedModels) unpricedModels.add(id);
|
|
2338
2407
|
for (const [modelKey, count] of fold.planCalls) planCalls.set(modelKey, (planCalls.get(modelKey) ?? 0) + count);
|
|
2339
2408
|
for (const sample of fold.perf) {
|
|
@@ -2410,6 +2479,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2410
2479
|
ttftAvg: mean(acc.ttfts),
|
|
2411
2480
|
ttftP50: percentile(acc.ttfts, .5),
|
|
2412
2481
|
ttftP90: percentile(acc.ttfts, .9),
|
|
2482
|
+
ttftMax: Math.max(...acc.ttfts),
|
|
2483
|
+
ttftSpikes: acc.ttfts.filter((ttft) => ttft > PERF_SPIKE_MS).length,
|
|
2413
2484
|
...acc.tps.length === 0 ? {} : { tpsAvg: mean(acc.tps) },
|
|
2414
2485
|
latencyAvg: acc.latencies.length === 0 ? 0 : mean(acc.latencies),
|
|
2415
2486
|
estimatedSamples: acc.estimated
|
|
@@ -2432,8 +2503,14 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2432
2503
|
bySession: sessionRows.slice(0, 100),
|
|
2433
2504
|
byTurn: turnRows.slice(0, 200),
|
|
2434
2505
|
byWorkspace: workspaces.slice(0, 100),
|
|
2506
|
+
...byTier.size === 0 ? {} : { byTier: {
|
|
2507
|
+
peak: byTier.get("peak") ?? emptyUsage(),
|
|
2508
|
+
offPeak: byTier.get("offPeak") ?? emptyUsage()
|
|
2509
|
+
} },
|
|
2510
|
+
...byTool.size === 0 ? {} : { byTool: Object.fromEntries([...byTool].sort((a, b) => b[1] - a[1])) },
|
|
2435
2511
|
...bySite.size === 0 ? {} : { bySite: toRecord(bySite) },
|
|
2436
2512
|
...unpricedModels.size === 0 ? {} : { unpricedModels: [...unpricedModels].sort() },
|
|
2513
|
+
...searchEstimate > 0 ? { searchCallEstimateCny: searchEstimate } : {},
|
|
2437
2514
|
...perf === void 0 ? {} : { perf },
|
|
2438
2515
|
...staleLedgerSessions > 0 ? { staleLedgerSessions } : {},
|
|
2439
2516
|
byRole: (() => {
|
|
@@ -4886,6 +4963,7 @@ function apply(ctx, config = {}) {
|
|
|
4886
4963
|
...config.subscriptionProviders === void 0 ? {} : { subscriptionProviders: config.subscriptionProviders },
|
|
4887
4964
|
resolveRoutes: () => readPiAiProviderRoutes(ctx.settings),
|
|
4888
4965
|
...workspaceTitleResolver === void 0 ? {} : { resolveWorkspaceTitle: workspaceTitleResolver },
|
|
4966
|
+
...config.searchCallEstimateCny === void 0 ? {} : { searchCallEstimateCny: config.searchCallEstimateCny },
|
|
4889
4967
|
ledger: ledgerStore
|
|
4890
4968
|
});
|
|
4891
4969
|
const candidates = [
|
package/lib/types/aggregate.d.ts
CHANGED
|
@@ -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;
|
|
@@ -84,6 +87,11 @@ export interface ModelUsage {
|
|
|
84
87
|
output: number;
|
|
85
88
|
cacheHit: number;
|
|
86
89
|
cacheMiss: number;
|
|
90
|
+
/**
|
|
91
|
+
* 显式缓存写入 token(部分厂商单独计价的 cache creation)——已包含在
|
|
92
|
+
* `cacheMiss` 内,单列供结构展示;旧快照缺失。
|
|
93
|
+
*/
|
|
94
|
+
cacheWrite?: number;
|
|
87
95
|
cost: number;
|
|
88
96
|
/** 输出中的 reasoning(思考)token;已包含在 `output` 内,单列用于结构展示。 */
|
|
89
97
|
reasoning: number;
|
|
@@ -93,6 +101,12 @@ export interface ModelUsage {
|
|
|
93
101
|
officialCalls: number;
|
|
94
102
|
/** 走官方渠道的费用(CNY);三方费用 = cost - officialCost。 */
|
|
95
103
|
officialCost: number;
|
|
104
|
+
/**
|
|
105
|
+
* 联网搜索辅助请求的估算调用数(`web/deepseek-search-llm-request`,日志只有
|
|
106
|
+
* 请求无用量事件);已按每次 `searchCallEstimateCny` 估算计入 `cost`,不计
|
|
107
|
+
* token。旧快照缺失。
|
|
108
|
+
*/
|
|
109
|
+
searchCalls?: number;
|
|
96
110
|
}
|
|
97
111
|
/** Zeroed usage accumulator. */
|
|
98
112
|
export declare function emptyUsage(): ModelUsage;
|
|
@@ -108,6 +122,20 @@ export declare function emptyUsage(): ModelUsage;
|
|
|
108
122
|
* @param official - whether the call went through the official DeepSeek channel (vs a third-party relay).
|
|
109
123
|
*/
|
|
110
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;
|
|
111
139
|
/** Local-time date stamp (the host runs in the user's timezone). */
|
|
112
140
|
export declare function dayStamp(time: number): string;
|
|
113
141
|
/** Local-time hour stamp `YYYY-MM-DDTHH` — the performance series bucket key. */
|
|
@@ -147,6 +175,16 @@ export interface UsageStatsDocument {
|
|
|
147
175
|
byDay: Record<string, ModelUsage>;
|
|
148
176
|
/** 模型 × 日期 二维统计:趋势图按模型堆叠的输入([date][modelKey])。 */
|
|
149
177
|
byDayModels: Record<string, Record<string, ModelUsage>>;
|
|
178
|
+
/**
|
|
179
|
+
* 峰谷分桶(真实判档):折叠时逐调用按 `tierAt(event.time)` 归入高峰/低谷桶,
|
|
180
|
+
* 峰谷占比与「挪谷省钱」据此展示,不再按比例估算。旧快照可能缺失。
|
|
181
|
+
*/
|
|
182
|
+
byTier?: {
|
|
183
|
+
peak: ModelUsage;
|
|
184
|
+
offPeak: ModelUsage;
|
|
185
|
+
};
|
|
186
|
+
/** 工具调用次数排行(键 = 工具名,按调用数倒序);token 无法按工具归因,仅计次。旧快照可能缺失。 */
|
|
187
|
+
byTool?: Record<string, number>;
|
|
150
188
|
/** 会话明细:按费用倒序,封顶 {@link SESSION_ROW_LIMIT} 行;旧快照可能缺失。 */
|
|
151
189
|
bySession: SessionUsageRow[];
|
|
152
190
|
/** 每轮费用明细:按起始时间倒序,封顶 {@link TURN_ROW_LIMIT} 行;旧快照可能缺失。 */
|
|
@@ -167,6 +205,8 @@ export interface UsageStatsDocument {
|
|
|
167
205
|
byRole?: RoleCost;
|
|
168
206
|
/** 不可计价的模型 id(未收录 / 无价,费用按 0 计);供面板提示用户自查与反馈。 */
|
|
169
207
|
unpricedModels?: readonly string[];
|
|
208
|
+
/** 联网搜索请求的单次费用估算(人民币元,配置回显);0 或缺省 = 未启用估算。 */
|
|
209
|
+
searchCallEstimateCny?: number;
|
|
170
210
|
/**
|
|
171
211
|
* 性能指标(TTFT / 生成速度 / 总延迟)按模型与按小时聚合;旧快照可能缺失。
|
|
172
212
|
* 口径:TTFT = request/header → 首个内容 chunk;生成速度 = 输出 token ÷ 生成时长;
|
|
@@ -201,6 +241,10 @@ export interface ModelPerf {
|
|
|
201
241
|
ttftP50: number;
|
|
202
242
|
/** 首字延时 P90(毫秒)。 */
|
|
203
243
|
ttftP90: number;
|
|
244
|
+
/** 首字延时最大值(毫秒);定位偶发慢响应。 */
|
|
245
|
+
ttftMax: number;
|
|
246
|
+
/** 首字延时尖峰样本数(> 10s);定位服务端抖动。 */
|
|
247
|
+
ttftSpikes: number;
|
|
204
248
|
/** 平均生成速度(tokens/s);生成了有效输出且时长可测时存在。 */
|
|
205
249
|
tpsAvg?: number;
|
|
206
250
|
/** 平均总延迟(首次请求 → 响应完成,毫秒)。 */
|
|
@@ -268,6 +312,8 @@ export declare const SESSION_ROW_LIMIT = 100;
|
|
|
268
312
|
export declare const TURN_ROW_LIMIT = 200;
|
|
269
313
|
/** 聚合文档的短 TTL(毫秒):合并密集轮询,TTL 内直接复用上次的合并结果。 */
|
|
270
314
|
export declare const AGGREGATE_TTL_MS = 5000;
|
|
315
|
+
/** TTFT 尖峰阈值(毫秒):超过计为一次尖峰样本,用于定位服务端抖动。 */
|
|
316
|
+
export declare const PERF_SPIKE_MS = 10000;
|
|
271
317
|
/** 单步性能样本(foldSession 的折叠产物;跨会话合并时按模型/小时再聚合)。 */
|
|
272
318
|
export interface PerfSample {
|
|
273
319
|
/** 计费目录键(模型;未收录模型原样保留)。 */
|
|
@@ -289,6 +335,10 @@ export interface SessionFold {
|
|
|
289
335
|
byModel: Map<string, ModelUsage>;
|
|
290
336
|
byDay: Map<string, ModelUsage>;
|
|
291
337
|
byDayModels: Map<string, Map<string, ModelUsage>>;
|
|
338
|
+
/** 峰谷分桶:折叠时按调用时刻精确判档(tierAt),键 = 'peak' / 'offPeak'。 */
|
|
339
|
+
byTier: Map<string, ModelUsage>;
|
|
340
|
+
/** 工具调用次数(键 = 工具名;tool-call-delta 首见计数)。 */
|
|
341
|
+
byTool: Map<string, number>;
|
|
292
342
|
/** 中转站归组:按 provider 路由归类到站点/直连/未知路由(key = {@link siteBucketKey})。 */
|
|
293
343
|
bySite: Map<string, ModelUsage>;
|
|
294
344
|
/** 不可计价模型 id(未收录/无价,且非订阅)集合;跨会话合并后输出给面板提示。 */
|
|
@@ -321,6 +371,9 @@ export interface SerializedSessionFold {
|
|
|
321
371
|
byModel: Record<string, ModelUsage>;
|
|
322
372
|
byDay: Record<string, ModelUsage>;
|
|
323
373
|
byDayModels: Record<string, Record<string, ModelUsage>>;
|
|
374
|
+
/** 1.0.8 起新增;旧账本行缺失(合并时按空处理,不触发重折算)。 */
|
|
375
|
+
byTier?: Record<string, ModelUsage>;
|
|
376
|
+
byTool?: Record<string, number>;
|
|
324
377
|
bySite: Record<string, ModelUsage>;
|
|
325
378
|
unpricedModels: string[];
|
|
326
379
|
planCalls: Record<string, number>;
|
|
@@ -353,10 +406,11 @@ export interface UsageLedgerDocument {
|
|
|
353
406
|
/**
|
|
354
407
|
* 折叠算法版本:归账语义变化时递增。v1 = 按 request/header 归账(稀疏 header 把
|
|
355
408
|
* 两次 header 之间的用量串到上一个模型,订阅模型首当其冲,issue #14);v2 =
|
|
356
|
-
* `assistant/message` 自带 source 归账(1.0.7
|
|
357
|
-
*
|
|
409
|
+
* `assistant/message` 自带 source 归账(1.0.7 起);v3 = 联网搜索请求按次估算
|
|
410
|
+
* 计费(issue #15,1.0.9 起)——旧行缺 `searchCalls` 维度与搜索估算费用。
|
|
411
|
+
* 持久账本行据此区分新旧算法:日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
|
|
358
412
|
*/
|
|
359
|
-
export declare const FOLD_VERSION =
|
|
413
|
+
export declare const FOLD_VERSION = 3;
|
|
360
414
|
/**
|
|
361
415
|
* 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
|
|
362
416
|
* 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
|
|
@@ -400,6 +454,8 @@ export declare function messageTextLength(message: unknown): number;
|
|
|
400
454
|
* @param subscriptionProviders - provider ids billed through subscription plans.
|
|
401
455
|
* @param officialProviderIds - provider ids treated as the official DeepSeek channel
|
|
402
456
|
* (default: any `deepseek`-prefixed id). Others count as third-party.
|
|
457
|
+
* @param routes - 当前 provider 路由视图(中转站归组)。
|
|
458
|
+
* @param searchCallEstimateCny - 联网搜索请求的单次费用估算(人民币元;0 关闭估算)。
|
|
403
459
|
* @returns the per-session fold (cached by the incremental aggregator).
|
|
404
460
|
*/
|
|
405
461
|
export declare function foldSession(events: readonly {
|
|
@@ -407,7 +463,7 @@ export declare function foldSession(events: readonly {
|
|
|
407
463
|
time: number;
|
|
408
464
|
data: never;
|
|
409
465
|
seq?: number;
|
|
410
|
-
}[], subscriptionProviders: ReadonlySet<string>, officialProviderIds?: ReadonlySet<string>, routes?: Readonly<Record<string, ProviderRouteView
|
|
466
|
+
}[], subscriptionProviders: ReadonlySet<string>, officialProviderIds?: ReadonlySet<string>, routes?: Readonly<Record<string, ProviderRouteView>>, searchCallEstimateCny?: number): SessionFold;
|
|
411
467
|
/**
|
|
412
468
|
* 增量聚合器:按会话缓存折叠结果,用日志文件的 mtime+size 作失效键——
|
|
413
469
|
* 日志没动的会话直接复用,只有写过的会话重新折叠;整份文档另有短 TTL
|
|
@@ -16,6 +16,10 @@ export interface PerfModelData {
|
|
|
16
16
|
ttftAvg: number;
|
|
17
17
|
ttftP50: number;
|
|
18
18
|
ttftP90: number;
|
|
19
|
+
/** 首字延时最大值(毫秒);1.0.8 起新增,旧快照缺失。 */
|
|
20
|
+
ttftMax?: number;
|
|
21
|
+
/** 首字延时尖峰样本数(> 10s);1.0.8 起新增,旧快照缺失。 */
|
|
22
|
+
ttftSpikes?: number;
|
|
19
23
|
tpsAvg?: number;
|
|
20
24
|
latencyAvg: number;
|
|
21
25
|
estimatedSamples: number;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TokenPanel: 「Token」分区——把 token 从费用里独立出来洞察。
|
|
3
|
-
*
|
|
3
|
+
* 四个板块 + 导出,全部由 `UsageStats` 派生,服务端零改动:
|
|
4
4
|
* 1. 每日 Token 堆叠趋势(未命中输入 / 缓存命中 / 输出[含 reasoning]),7/30 天切换;
|
|
5
5
|
* 2. 模型 Token 总量排行 + 占比;
|
|
6
|
-
* 3. Token 结构 KPI(缓存命中率 / reasoning 占比 / 输入:输出比 /
|
|
6
|
+
* 3. Token 结构 KPI(缓存命中率 / reasoning 占比 / 输入:输出比 / 峰值日)+ 显式缓存写入;
|
|
7
|
+
* 4. 工具调用排行(byTool 计次;token 无法按工具归因)。
|
|
7
8
|
*/
|
|
8
9
|
import type { UsageBillingKey } from './locales.ts';
|
|
9
10
|
import type { UsageStats } from './UsageBilling.tsx';
|
|
@@ -75,18 +75,15 @@ export declare function projectMonthCost(byDay: Record<string, {
|
|
|
75
75
|
cost: number;
|
|
76
76
|
}>, monthPrefix: string, today: string): number;
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
78
|
+
* 用户自定义单价显示重估:聚合发生在宿主进程(按内置目录计价),用户价只在
|
|
79
|
+
* 客户端显示层生效。对 byDayModels(day×model 完整二维)中命中用户价的模型
|
|
80
|
+
* 逐格平价重算 cost,并派生 byDay / byModel / total 的 cost。其余视图
|
|
81
|
+
* (bySite / byTier / bySession / byTurn)保持宿主原值——逐格时刻或行级归属
|
|
82
|
+
* 在客户端不可得,口径差异在设置面板注明。导出供测试:纯函数。
|
|
83
|
+
* @param stats - 服务端聚合文档。
|
|
84
|
+
* @returns 重估后的文档;无用户价或缺 byDayModels 时原样返回。
|
|
82
85
|
*/
|
|
83
|
-
export declare function
|
|
84
|
-
startedAt: number;
|
|
85
|
-
cost: number;
|
|
86
|
-
}[]): {
|
|
87
|
-
peak: number;
|
|
88
|
-
offPeak: number;
|
|
89
|
-
};
|
|
86
|
+
export declare function recostWithUserPrices(stats: UsageStats): UsageStats;
|
|
90
87
|
/** 近 7 天费用序列(含今天,缺日补 0):触发卡 hover 速览的迷你柱数据源。
|
|
91
88
|
* 导出供测试:纯函数(日期取本地时区)。 */
|
|
92
89
|
export declare function activeDaysOf(byDay: Record<string, {
|
|
@@ -141,9 +138,13 @@ export interface UsageStats {
|
|
|
141
138
|
output: number;
|
|
142
139
|
cacheHit: number;
|
|
143
140
|
cacheMiss: number;
|
|
141
|
+
/** 显式缓存写入(cacheMiss 子集,部分厂商单独报告);1.0.8 起新增,旧快照缺失。 */
|
|
142
|
+
cacheWrite?: number;
|
|
144
143
|
cost: number;
|
|
145
144
|
/** 输出中的 reasoning(思考)token;已含在 output 内。 */
|
|
146
145
|
reasoning: number;
|
|
146
|
+
/** 联网搜索请求的估算调用数(已按次估值计入 cost);1.0.9 起新增,旧快照缺失。 */
|
|
147
|
+
searchCalls?: number;
|
|
147
148
|
};
|
|
148
149
|
byModel: Record<string, {
|
|
149
150
|
calls: number;
|
|
@@ -178,6 +179,22 @@ export interface UsageStats {
|
|
|
178
179
|
cacheMiss: number;
|
|
179
180
|
cost: number;
|
|
180
181
|
}>>;
|
|
182
|
+
/**
|
|
183
|
+
* 峰谷分桶(全量逐调用真实判档):1.0.8 起服务端按调用时刻精确归桶,
|
|
184
|
+
* 峰谷占比条优先用它(覆盖全部历史调用);旧快照缺失时回退逐轮估算。
|
|
185
|
+
*/
|
|
186
|
+
byTier?: {
|
|
187
|
+
peak: {
|
|
188
|
+
cost: number;
|
|
189
|
+
calls: number;
|
|
190
|
+
};
|
|
191
|
+
offPeak: {
|
|
192
|
+
cost: number;
|
|
193
|
+
calls: number;
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
/** 工具调用次数排行(键 = 工具名,按次数倒序);旧快照缺失。 */
|
|
197
|
+
byTool?: Record<string, number>;
|
|
181
198
|
/** 每轮费用明细(服务端聚合路径恒带);旧快照可能缺失。 */
|
|
182
199
|
byTurn?: readonly {
|
|
183
200
|
sessionId: string;
|
|
@@ -212,6 +229,8 @@ export interface UsageStats {
|
|
|
212
229
|
}>;
|
|
213
230
|
/** 不可计价模型 id(未收录/无价,费用按 0 计);旧快照可能缺失。 */
|
|
214
231
|
unpricedModels?: readonly string[];
|
|
232
|
+
/** 联网搜索请求的单次费用估算(人民币元,配置回显);0 或缺省 = 未启用估算。 */
|
|
233
|
+
searchCallEstimateCny?: number;
|
|
215
234
|
/** 按角色费用归因(估算口径:输出实测,输入按消息长度摊分);旧快照可能缺失。 */
|
|
216
235
|
byRole?: {
|
|
217
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.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.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.
|
|
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>;
|
|
@@ -26,6 +26,32 @@ import type { LivePricing } from '../pricing-shared.ts';
|
|
|
26
26
|
* live rate arrives the built-in value stays in force.
|
|
27
27
|
*/
|
|
28
28
|
export declare const USD_TO_CNY = 6.79;
|
|
29
|
+
/**
|
|
30
|
+
* 用户自定义单价(设置面板录入,localStorage 持久化):覆盖内置/models.dev/
|
|
31
|
+
* dsh-spend 的全部价格来源,用于新模型上线目录未跟、或厂商未公布按量价的场景。
|
|
32
|
+
* 仅在客户端显示层生效——聚合发生在宿主进程,折叠时的成本仍按内置目录计算,
|
|
33
|
+
* 客户端检测到用户价后对受影响视图做显示重估(见 UsageBilling 的 recost)。
|
|
34
|
+
*/
|
|
35
|
+
export interface UserPrice {
|
|
36
|
+
/** 未命中输入单价(元或美元 / 每百万 token)。 */
|
|
37
|
+
input: number;
|
|
38
|
+
/** 缓存命中输入单价。 */
|
|
39
|
+
cacheHit: number;
|
|
40
|
+
/** 输出单价。 */
|
|
41
|
+
output: number;
|
|
42
|
+
/** 计价币种;缺省 CNY。 */
|
|
43
|
+
currency?: 'CNY' | 'USD';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 注入用户自定义单价。键 = 计费目录键;查询侧用归一化两跳宽松命中。
|
|
47
|
+
* 空对象 = 清除全部自定义价,回退内置目录。
|
|
48
|
+
* @param prices - 目录键 → 自定义单价。
|
|
49
|
+
*/
|
|
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;
|
|
29
55
|
/**
|
|
30
56
|
* Apply the node half's live pricing snapshot. Absent fields keep the
|
|
31
57
|
* built-in catalog and rate; callers never fabricate values.
|
|
@@ -158,6 +184,13 @@ export interface PriceRow {
|
|
|
158
184
|
/** 补充说明(如与标准价的关系)。 */
|
|
159
185
|
note?: string;
|
|
160
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* 分档计价语义:厂商把「主档 / 低价档」的划分依据不同,界面需区分标注。
|
|
189
|
+
* - `timeOfDay`(缺省):按调用时刻分档(DeepSeek 峰谷时段),档位是客观的;
|
|
190
|
+
* - `latency`:按用户选择的延迟档分档(Gemini Standard/Flex,Flex 半价换 1-15
|
|
191
|
+
* 分钟延迟),与时刻无关;逐调用无法从日志判定实际档位,成本按比例估算。
|
|
192
|
+
*/
|
|
193
|
+
export type TierSemantics = 'timeOfDay' | 'latency';
|
|
161
194
|
/** One catalog entry: identity, brand color token, and price. */
|
|
162
195
|
export interface ModelEntry {
|
|
163
196
|
/** Model key used by `.dsh-usage-stats.json` `byModel`. */
|
|
@@ -172,6 +205,8 @@ export interface ModelEntry {
|
|
|
172
205
|
price: ModelPrice;
|
|
173
206
|
/** Peak-hour window label for time-of-day priced models. */
|
|
174
207
|
peakHours?: string;
|
|
208
|
+
/** 分档语义;缺省 = 按时段(timeOfDay)。 */
|
|
209
|
+
tierSemantics?: TierSemantics;
|
|
175
210
|
/**
|
|
176
211
|
* 限时促销:生效期内 price 各档位按 factor 打折,过期自动恢复。
|
|
177
212
|
* price 表本身永远保存刊例价,促销只在计价/显示出口处折算,不回写目录。
|
|
@@ -186,6 +221,8 @@ export interface ModelEntry {
|
|
|
186
221
|
estimated?: boolean;
|
|
187
222
|
/** 探活命中但无内置/models.dev 价:费率表标「未收录」,不参与计价。 */
|
|
188
223
|
uncatalogued?: boolean;
|
|
224
|
+
/** 该条目当前按用户自定义单价计价(设置面板可维护);费率表标注「自定义」。 */
|
|
225
|
+
userPriced?: boolean;
|
|
189
226
|
}
|
|
190
227
|
/**
|
|
191
228
|
* Built-in catalog of current mainstream models as of 2026-08-16, priced from
|
|
@@ -56,4 +56,39 @@ 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;
|
|
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>;
|
|
88
|
+
/** localStorage key(与其他 `dsh.ui-usage-billing.*` 偏好同命名空间)。 */
|
|
89
|
+
export declare const USER_PRICES_STORAGE_KEY = "dsh.ui-usage-billing.prices";
|
|
90
|
+
/** 读取用户自定义价(写入侧已校验,这里只挡住手工改坏的非数字行)。仅在浏览器半区调用。 */
|
|
91
|
+
export declare function loadUserPrices(): UserPriceMap;
|
|
92
|
+
/** 写入用户自定义价。失败静默(展示偏好非关键)。 */
|
|
93
|
+
export declare function saveUserPrices(prices: UserPriceMap): void;
|
|
59
94
|
//# sourceMappingURL=usage-billing-settings.d.ts.map
|