@kenz1117/dsh-ui-usage-billing 1.0.6 → 1.0.8
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/client.js +5 -5
- package/lib/index.js +78 -12
- package/lib/types/aggregate.d.ts +51 -5
- 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 +19 -0
- package/package.json +4 -2
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",
|
|
@@ -1638,8 +1640,9 @@ function formatTokens(value) {
|
|
|
1638
1640
|
* Real-usage aggregation: folds every persisted session log into the
|
|
1639
1641
|
* usage-stats document the dashboard renders.
|
|
1640
1642
|
*
|
|
1641
|
-
* Each LLM call is attributed to the
|
|
1642
|
-
*
|
|
1643
|
+
* Each LLM call is attributed to the `message.source` carried by its own
|
|
1644
|
+
* `assistant/message` event (copied from the request at write time); the
|
|
1645
|
+
* sparse `request/header` is only a fallback. Costs are estimated with the
|
|
1643
1646
|
* shared billing catalog (`pricing.ts`, in CNY), so only models the catalog
|
|
1644
1647
|
* prices incur a cost — subscription-plan routes and unknown models price
|
|
1645
1648
|
* zero while their tokens still count. Pure functions only: the persistence
|
|
@@ -1745,6 +1748,7 @@ function foldUsage(acc, usage, key, subscription, timeMs, official = false) {
|
|
|
1745
1748
|
acc.reasoning += usage.reasoningTokens ?? 0;
|
|
1746
1749
|
acc.cacheHit += cacheHit;
|
|
1747
1750
|
acc.cacheMiss += cacheMiss;
|
|
1751
|
+
if ((usage.cacheWriteTokens ?? 0) > 0) acc.cacheWrite = (acc.cacheWrite ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
1748
1752
|
if (official) acc.officialCalls += 1;
|
|
1749
1753
|
if (!subscription && isPriced(key)) {
|
|
1750
1754
|
const thisCost = computeCostAt(modelOf(key), {
|
|
@@ -1796,11 +1800,23 @@ function workspaceNameOf(cwd) {
|
|
|
1796
1800
|
if (cwd === void 0 || cwd === "") return "—";
|
|
1797
1801
|
return cwd.split(/[\\/]/).filter(Boolean).at(-1) ?? "—";
|
|
1798
1802
|
}
|
|
1803
|
+
/** TTFT 尖峰阈值(毫秒):超过计为一次尖峰样本,用于定位服务端抖动。 */
|
|
1804
|
+
const PERF_SPIKE_MS = 1e4;
|
|
1799
1805
|
/**
|
|
1800
|
-
*
|
|
1801
|
-
*
|
|
1806
|
+
* 账本迁移注册表。首条迁移给 1.0.6 及更早的行回填 foldVersion = 1(它们全部出自
|
|
1807
|
+
* header 归因算法);此后新写入的行总带当前 {@link FOLD_VERSION}。
|
|
1802
1808
|
*/
|
|
1803
|
-
const LEDGER_MIGRATIONS = [
|
|
1809
|
+
const LEDGER_MIGRATIONS = [{
|
|
1810
|
+
id: "fold-version-backfill",
|
|
1811
|
+
apply(document) {
|
|
1812
|
+
let changed = false;
|
|
1813
|
+
for (const session of document.sessions) if (session.foldVersion === void 0) {
|
|
1814
|
+
session.foldVersion = 1;
|
|
1815
|
+
changed = true;
|
|
1816
|
+
}
|
|
1817
|
+
return changed;
|
|
1818
|
+
}
|
|
1819
|
+
}];
|
|
1804
1820
|
/**
|
|
1805
1821
|
* 在加载边界对账本文档应用未执行的迁移,并记录已应用 id 供写回。
|
|
1806
1822
|
* @param document - 从持久化读出的原始账本文档。
|
|
@@ -1828,6 +1844,8 @@ function serializeFold(fold) {
|
|
|
1828
1844
|
byModel: Object.fromEntries(fold.byModel),
|
|
1829
1845
|
byDay: Object.fromEntries(fold.byDay),
|
|
1830
1846
|
byDayModels: Object.fromEntries([...fold.byDayModels].map(([day, models]) => [day, Object.fromEntries(models)])),
|
|
1847
|
+
byTier: Object.fromEntries(fold.byTier),
|
|
1848
|
+
byTool: Object.fromEntries(fold.byTool),
|
|
1831
1849
|
bySite: Object.fromEntries(fold.bySite),
|
|
1832
1850
|
unpricedModels: [...fold.unpricedModels],
|
|
1833
1851
|
planCalls: Object.fromEntries(fold.planCalls),
|
|
@@ -1844,6 +1862,8 @@ function deserializeFold(fold) {
|
|
|
1844
1862
|
byModel: new Map(Object.entries(fold.byModel)),
|
|
1845
1863
|
byDay: new Map(Object.entries(fold.byDay)),
|
|
1846
1864
|
byDayModels: new Map(Object.entries(fold.byDayModels).map(([day, models]) => [day, new Map(Object.entries(models))])),
|
|
1865
|
+
byTier: new Map(Object.entries(fold.byTier ?? {})),
|
|
1866
|
+
byTool: new Map(Object.entries(fold.byTool ?? {})),
|
|
1847
1867
|
bySite: new Map(Object.entries(fold.bySite)),
|
|
1848
1868
|
unpricedModels: new Set(fold.unpricedModels),
|
|
1849
1869
|
planCalls: new Map(Object.entries(fold.planCalls)),
|
|
@@ -1918,7 +1938,9 @@ function turnState(turns, turn) {
|
|
|
1918
1938
|
}
|
|
1919
1939
|
/**
|
|
1920
1940
|
* Fold one session's events into a {@link SessionFold}. 每个 LLM 调用归属到
|
|
1921
|
-
*
|
|
1941
|
+
* 其 `assistant/message` 自带 `message.source` 记录的模型(agent-loop 落盘时从
|
|
1942
|
+
* 当次请求复制,每个调用一条,不依赖稀疏的 request/header);source 缺失时
|
|
1943
|
+
* 兜底到最近一次 request/header 的状态。同时提取最新会话标题、最后活跃时间,
|
|
1922
1944
|
* 并按轮次折叠每轮费用明细(turn/start → turn/end;调用按 (turn) 归组)。
|
|
1923
1945
|
* @param events - the session's persisted events in log order.
|
|
1924
1946
|
* @param subscriptionProviders - provider ids billed through subscription plans.
|
|
@@ -1937,6 +1959,8 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
|
|
|
1937
1959
|
byModel: /* @__PURE__ */ new Map(),
|
|
1938
1960
|
byDay: /* @__PURE__ */ new Map(),
|
|
1939
1961
|
byDayModels: /* @__PURE__ */ new Map(),
|
|
1962
|
+
byTier: /* @__PURE__ */ new Map(),
|
|
1963
|
+
byTool: /* @__PURE__ */ new Map(),
|
|
1940
1964
|
bySite: /* @__PURE__ */ new Map(),
|
|
1941
1965
|
unpricedModels: /* @__PURE__ */ new Set(),
|
|
1942
1966
|
planCalls: /* @__PURE__ */ new Map(),
|
|
@@ -1957,6 +1981,7 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
|
|
|
1957
1981
|
const turns = /* @__PURE__ */ new Map();
|
|
1958
1982
|
const steps = /* @__PURE__ */ new Map();
|
|
1959
1983
|
let lastOpenStepKey;
|
|
1984
|
+
const toolSeen = /* @__PURE__ */ new Set();
|
|
1960
1985
|
for (const event of events) {
|
|
1961
1986
|
if (seedBoundary >= 0 && typeof event.seq === "number" && Number.isFinite(event.seq) && event.seq < seedBoundary) continue;
|
|
1962
1987
|
fold.lastActive = Math.max(fold.lastActive, event.time);
|
|
@@ -2018,11 +2043,28 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
|
|
|
2018
2043
|
state.lastContentTime = event.time;
|
|
2019
2044
|
}
|
|
2020
2045
|
}
|
|
2046
|
+
if (chunk?.type === "tool-call-delta" && typeof turn === "number" && typeof step === "number") {
|
|
2047
|
+
const index = chunk.index;
|
|
2048
|
+
const name = chunk.name;
|
|
2049
|
+
const seenKey = `${turn}:${step}:${typeof index === "number" ? index : "-"}`;
|
|
2050
|
+
if (!toolSeen.has(seenKey)) {
|
|
2051
|
+
toolSeen.add(seenKey);
|
|
2052
|
+
const toolName = typeof name === "string" && name !== "" ? name : "unknown";
|
|
2053
|
+
fold.byTool.set(toolName, (fold.byTool.get(toolName) ?? 0) + 1);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2021
2056
|
continue;
|
|
2022
2057
|
}
|
|
2023
2058
|
if (event.type !== "assistant/message") continue;
|
|
2024
2059
|
const usage = event.data.usage;
|
|
2025
2060
|
if (usage === void 0) continue;
|
|
2061
|
+
const source = event.data.message?.source;
|
|
2062
|
+
if (source?.kind === "model" && typeof source.provider === "string" && typeof source.model === "string") {
|
|
2063
|
+
key = resolveCatalogKey(source.model);
|
|
2064
|
+
subscription = subscriptionProviders.has(source.provider);
|
|
2065
|
+
official = officialProviderIds === void 0 ? isOfficialProvider(source.provider) : officialProviderIds.has(source.provider);
|
|
2066
|
+
siteBucket = siteBucketKey(siteRefOf(source.provider, routes));
|
|
2067
|
+
}
|
|
2026
2068
|
const modelKey = key;
|
|
2027
2069
|
const day = dayStamp(event.time);
|
|
2028
2070
|
if (!subscription && !isPriced(modelKey)) fold.unpricedModels.add(modelKey);
|
|
@@ -2031,6 +2073,7 @@ function foldSession(events, subscriptionProviders, officialProviderIds, routes
|
|
|
2031
2073
|
foldUsage(usageCell(fold.byDay, day), usage, modelKey, subscription, event.time, official);
|
|
2032
2074
|
foldUsage(modelDayCell(fold.byDayModels, day, modelKey), usage, modelKey, subscription, event.time, official);
|
|
2033
2075
|
foldUsage(usageCell(fold.bySite, siteBucket), usage, modelKey, subscription, event.time, official);
|
|
2076
|
+
foldUsage(usageCell(fold.byTier, tierAt(event.time)), usage, modelKey, subscription, event.time, official);
|
|
2034
2077
|
if (subscription) fold.planCalls.set(modelKey, (fold.planCalls.get(modelKey) ?? 0) + 1);
|
|
2035
2078
|
const turn = event.data.turn ?? -1;
|
|
2036
2079
|
const state = turnState(turns, turn);
|
|
@@ -2112,6 +2155,7 @@ function mergeUsageInto(acc, cell) {
|
|
|
2112
2155
|
acc.reasoning += cell.reasoning;
|
|
2113
2156
|
acc.cacheHit += cell.cacheHit;
|
|
2114
2157
|
acc.cacheMiss += cell.cacheMiss;
|
|
2158
|
+
if (cell.cacheWrite !== void 0) acc.cacheWrite = (acc.cacheWrite ?? 0) + cell.cacheWrite;
|
|
2115
2159
|
acc.cost += cell.cost;
|
|
2116
2160
|
acc.officialCalls += cell.officialCalls;
|
|
2117
2161
|
acc.officialCost += cell.officialCost;
|
|
@@ -2151,6 +2195,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2151
2195
|
const ledger = /* @__PURE__ */ new Map();
|
|
2152
2196
|
let ledgerLoaded = false;
|
|
2153
2197
|
let ledgerNeedsSave = false;
|
|
2198
|
+
let ledgerAppliedMigrations;
|
|
2154
2199
|
let lastDoc;
|
|
2155
2200
|
let lastAt = 0;
|
|
2156
2201
|
/** 每次聚合取最新的 provider 路由视图(中转站零配置发现);缺省按空处理(全部未知路由)。 */
|
|
@@ -2161,7 +2206,9 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2161
2206
|
try {
|
|
2162
2207
|
const stored = await options.ledger.load();
|
|
2163
2208
|
if (stored !== null && typeof stored === "object" && stored.sessions !== void 0) {
|
|
2164
|
-
|
|
2209
|
+
const document = stored;
|
|
2210
|
+
if (runLedgerMigrations(document)) ledgerNeedsSave = true;
|
|
2211
|
+
ledgerAppliedMigrations = document.appliedMigrations;
|
|
2165
2212
|
}
|
|
2166
2213
|
for (const entry of ledgerSessionsOf(stored)) ledger.set(entry.id, entry);
|
|
2167
2214
|
} catch (error) {
|
|
@@ -2196,6 +2243,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2196
2243
|
const included = /* @__PURE__ */ new Set();
|
|
2197
2244
|
const folds = [];
|
|
2198
2245
|
const skipped = [];
|
|
2246
|
+
let staleLedgerSessions = 0;
|
|
2199
2247
|
for (const meta of metas) {
|
|
2200
2248
|
const id = String(meta.id);
|
|
2201
2249
|
seen.add(id);
|
|
@@ -2232,10 +2280,11 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2232
2280
|
id,
|
|
2233
2281
|
...meta.cwd === void 0 ? {} : { cwd: meta.cwd },
|
|
2234
2282
|
...stamp === null ? {} : { stamp },
|
|
2283
|
+
foldVersion: 2,
|
|
2235
2284
|
fold: serializeFold(fold)
|
|
2236
2285
|
};
|
|
2237
2286
|
const previous = ledger.get(id);
|
|
2238
|
-
if (previous === void 0 || previous.stamp !== entry.stamp || previous.cwd !== entry.cwd || stamp === null && JSON.stringify(previous.fold) !== JSON.stringify(entry.fold)) {
|
|
2287
|
+
if (previous === void 0 || previous.stamp !== entry.stamp || previous.cwd !== entry.cwd || previous.foldVersion !== entry.foldVersion || stamp === null && JSON.stringify(previous.fold) !== JSON.stringify(entry.fold)) {
|
|
2239
2288
|
ledger.set(id, entry);
|
|
2240
2289
|
ledgerNeedsSave = true;
|
|
2241
2290
|
}
|
|
@@ -2255,12 +2304,15 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2255
2304
|
for (const entry of ledger.values()) {
|
|
2256
2305
|
if (included.has(entry.id)) continue;
|
|
2257
2306
|
try {
|
|
2307
|
+
const stale = (entry.foldVersion ?? 1) < 2;
|
|
2258
2308
|
folds.push({
|
|
2259
2309
|
id: entry.id,
|
|
2260
2310
|
...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
|
|
2311
|
+
...stale ? { staleLedger: true } : {},
|
|
2261
2312
|
fold: deserializeFold(entry.fold)
|
|
2262
2313
|
});
|
|
2263
2314
|
included.add(entry.id);
|
|
2315
|
+
if (stale) staleLedgerSessions += 1;
|
|
2264
2316
|
} catch (error) {
|
|
2265
2317
|
console.warn("[usage-billing] skip invalid durable ledger session", entry.id, error);
|
|
2266
2318
|
}
|
|
@@ -2269,7 +2321,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2269
2321
|
await options.ledger.save({
|
|
2270
2322
|
version: 1,
|
|
2271
2323
|
updatedAt: now,
|
|
2272
|
-
sessions: [...ledger.values()]
|
|
2324
|
+
sessions: [...ledger.values()],
|
|
2325
|
+
...ledgerAppliedMigrations === void 0 ? {} : { appliedMigrations: ledgerAppliedMigrations }
|
|
2273
2326
|
});
|
|
2274
2327
|
ledgerNeedsSave = false;
|
|
2275
2328
|
} catch (error) {
|
|
@@ -2281,6 +2334,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2281
2334
|
const byModel = /* @__PURE__ */ new Map();
|
|
2282
2335
|
const byDay = /* @__PURE__ */ new Map();
|
|
2283
2336
|
const byDayModels = /* @__PURE__ */ new Map();
|
|
2337
|
+
const byTier = /* @__PURE__ */ new Map();
|
|
2338
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
2284
2339
|
const bySite = /* @__PURE__ */ new Map();
|
|
2285
2340
|
const unpricedModels = /* @__PURE__ */ new Set();
|
|
2286
2341
|
const planCalls = /* @__PURE__ */ new Map();
|
|
@@ -2295,7 +2350,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2295
2350
|
};
|
|
2296
2351
|
const perfModel = /* @__PURE__ */ new Map();
|
|
2297
2352
|
const perfHour = /* @__PURE__ */ new Map();
|
|
2298
|
-
for (const { id: sessionId, cwd, fold } of folds) {
|
|
2353
|
+
for (const { id: sessionId, cwd, fold, staleLedger } of folds) {
|
|
2299
2354
|
mergeUsageInto(total, fold.total);
|
|
2300
2355
|
roles.userChars += fold.roles.userChars;
|
|
2301
2356
|
roles.toolChars += fold.roles.toolChars;
|
|
@@ -2305,6 +2360,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2305
2360
|
for (const [day, cell] of fold.byDay) mergeUsageInto(usageCell(byDay, day), cell);
|
|
2306
2361
|
for (const [day, models] of fold.byDayModels) for (const [modelKey, cell] of models) mergeUsageInto(modelDayCell(byDayModels, day, modelKey), cell);
|
|
2307
2362
|
for (const [siteKey, cell] of fold.bySite) mergeUsageInto(usageCell(bySite, siteKey), cell);
|
|
2363
|
+
for (const [tierKey, cell] of fold.byTier) mergeUsageInto(usageCell(byTier, tierKey), cell);
|
|
2364
|
+
for (const [toolName, count] of fold.byTool) byTool.set(toolName, (byTool.get(toolName) ?? 0) + count);
|
|
2308
2365
|
for (const id of fold.unpricedModels) unpricedModels.add(id);
|
|
2309
2366
|
for (const [modelKey, count] of fold.planCalls) planCalls.set(modelKey, (planCalls.get(modelKey) ?? 0) + count);
|
|
2310
2367
|
for (const sample of fold.perf) {
|
|
@@ -2356,6 +2413,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2356
2413
|
id: sessionId,
|
|
2357
2414
|
...fold.title !== void 0 ? { title: fold.title } : {},
|
|
2358
2415
|
...cwd !== void 0 ? { cwd } : {},
|
|
2416
|
+
...staleLedger === true ? { stale: true } : {},
|
|
2359
2417
|
calls: fold.total.calls,
|
|
2360
2418
|
cost: fold.total.cost,
|
|
2361
2419
|
lastActive: fold.lastActive
|
|
@@ -2380,6 +2438,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2380
2438
|
ttftAvg: mean(acc.ttfts),
|
|
2381
2439
|
ttftP50: percentile(acc.ttfts, .5),
|
|
2382
2440
|
ttftP90: percentile(acc.ttfts, .9),
|
|
2441
|
+
ttftMax: Math.max(...acc.ttfts),
|
|
2442
|
+
ttftSpikes: acc.ttfts.filter((ttft) => ttft > PERF_SPIKE_MS).length,
|
|
2383
2443
|
...acc.tps.length === 0 ? {} : { tpsAvg: mean(acc.tps) },
|
|
2384
2444
|
latencyAvg: acc.latencies.length === 0 ? 0 : mean(acc.latencies),
|
|
2385
2445
|
estimatedSamples: acc.estimated
|
|
@@ -2402,9 +2462,15 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2402
2462
|
bySession: sessionRows.slice(0, 100),
|
|
2403
2463
|
byTurn: turnRows.slice(0, 200),
|
|
2404
2464
|
byWorkspace: workspaces.slice(0, 100),
|
|
2465
|
+
...byTier.size === 0 ? {} : { byTier: {
|
|
2466
|
+
peak: byTier.get("peak") ?? emptyUsage(),
|
|
2467
|
+
offPeak: byTier.get("offPeak") ?? emptyUsage()
|
|
2468
|
+
} },
|
|
2469
|
+
...byTool.size === 0 ? {} : { byTool: Object.fromEntries([...byTool].sort((a, b) => b[1] - a[1])) },
|
|
2405
2470
|
...bySite.size === 0 ? {} : { bySite: toRecord(bySite) },
|
|
2406
2471
|
...unpricedModels.size === 0 ? {} : { unpricedModels: [...unpricedModels].sort() },
|
|
2407
2472
|
...perf === void 0 ? {} : { perf },
|
|
2473
|
+
...staleLedgerSessions > 0 ? { staleLedgerSessions } : {},
|
|
2408
2474
|
byRole: (() => {
|
|
2409
2475
|
const chars = roles.userChars + roles.toolChars;
|
|
2410
2476
|
const userShare = chars > 0 ? roles.userChars / chars : .5;
|
package/lib/types/aggregate.d.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Real-usage aggregation: folds every persisted session log into the
|
|
3
3
|
* usage-stats document the dashboard renders.
|
|
4
4
|
*
|
|
5
|
-
* Each LLM call is attributed to the
|
|
6
|
-
*
|
|
5
|
+
* Each LLM call is attributed to the `message.source` carried by its own
|
|
6
|
+
* `assistant/message` event (copied from the request at write time); the
|
|
7
|
+
* sparse `request/header` is only a fallback. Costs are estimated with the
|
|
7
8
|
* shared billing catalog (`pricing.ts`, in CNY), so only models the catalog
|
|
8
9
|
* prices incur a cost — subscription-plan routes and unknown models price
|
|
9
10
|
* zero while their tokens still count. Pure functions only: the persistence
|
|
@@ -83,6 +84,11 @@ export interface ModelUsage {
|
|
|
83
84
|
output: number;
|
|
84
85
|
cacheHit: number;
|
|
85
86
|
cacheMiss: number;
|
|
87
|
+
/**
|
|
88
|
+
* 显式缓存写入 token(部分厂商单独计价的 cache creation)——已包含在
|
|
89
|
+
* `cacheMiss` 内,单列供结构展示;旧快照缺失。
|
|
90
|
+
*/
|
|
91
|
+
cacheWrite?: number;
|
|
86
92
|
cost: number;
|
|
87
93
|
/** 输出中的 reasoning(思考)token;已包含在 `output` 内,单列用于结构展示。 */
|
|
88
94
|
reasoning: number;
|
|
@@ -146,6 +152,16 @@ export interface UsageStatsDocument {
|
|
|
146
152
|
byDay: Record<string, ModelUsage>;
|
|
147
153
|
/** 模型 × 日期 二维统计:趋势图按模型堆叠的输入([date][modelKey])。 */
|
|
148
154
|
byDayModels: Record<string, Record<string, ModelUsage>>;
|
|
155
|
+
/**
|
|
156
|
+
* 峰谷分桶(真实判档):折叠时逐调用按 `tierAt(event.time)` 归入高峰/低谷桶,
|
|
157
|
+
* 峰谷占比与「挪谷省钱」据此展示,不再按比例估算。旧快照可能缺失。
|
|
158
|
+
*/
|
|
159
|
+
byTier?: {
|
|
160
|
+
peak: ModelUsage;
|
|
161
|
+
offPeak: ModelUsage;
|
|
162
|
+
};
|
|
163
|
+
/** 工具调用次数排行(键 = 工具名,按调用数倒序);token 无法按工具归因,仅计次。旧快照可能缺失。 */
|
|
164
|
+
byTool?: Record<string, number>;
|
|
149
165
|
/** 会话明细:按费用倒序,封顶 {@link SESSION_ROW_LIMIT} 行;旧快照可能缺失。 */
|
|
150
166
|
bySession: SessionUsageRow[];
|
|
151
167
|
/** 每轮费用明细:按起始时间倒序,封顶 {@link TURN_ROW_LIMIT} 行;旧快照可能缺失。 */
|
|
@@ -200,6 +216,10 @@ export interface ModelPerf {
|
|
|
200
216
|
ttftP50: number;
|
|
201
217
|
/** 首字延时 P90(毫秒)。 */
|
|
202
218
|
ttftP90: number;
|
|
219
|
+
/** 首字延时最大值(毫秒);定位偶发慢响应。 */
|
|
220
|
+
ttftMax: number;
|
|
221
|
+
/** 首字延时尖峰样本数(> 10s);定位服务端抖动。 */
|
|
222
|
+
ttftSpikes: number;
|
|
203
223
|
/** 平均生成速度(tokens/s);生成了有效输出且时长可测时存在。 */
|
|
204
224
|
tpsAvg?: number;
|
|
205
225
|
/** 平均总延迟(首次请求 → 响应完成,毫秒)。 */
|
|
@@ -226,6 +246,8 @@ export interface SessionUsageRow {
|
|
|
226
246
|
cost: number;
|
|
227
247
|
/** 最后一个事件的时间戳(毫秒)。 */
|
|
228
248
|
lastActive: number;
|
|
249
|
+
/** 数据来自旧算法折叠的持久账本行(日志已删/不可读,无法重算);UI 据此标注置信度。 */
|
|
250
|
+
stale?: boolean;
|
|
229
251
|
}
|
|
230
252
|
/** 每轮费用明细行:仪表盘「每轮费用」图的数据源。 */
|
|
231
253
|
export interface TurnUsageRow {
|
|
@@ -265,6 +287,8 @@ export declare const SESSION_ROW_LIMIT = 100;
|
|
|
265
287
|
export declare const TURN_ROW_LIMIT = 200;
|
|
266
288
|
/** 聚合文档的短 TTL(毫秒):合并密集轮询,TTL 内直接复用上次的合并结果。 */
|
|
267
289
|
export declare const AGGREGATE_TTL_MS = 5000;
|
|
290
|
+
/** TTFT 尖峰阈值(毫秒):超过计为一次尖峰样本,用于定位服务端抖动。 */
|
|
291
|
+
export declare const PERF_SPIKE_MS = 10000;
|
|
268
292
|
/** 单步性能样本(foldSession 的折叠产物;跨会话合并时按模型/小时再聚合)。 */
|
|
269
293
|
export interface PerfSample {
|
|
270
294
|
/** 计费目录键(模型;未收录模型原样保留)。 */
|
|
@@ -286,6 +310,10 @@ export interface SessionFold {
|
|
|
286
310
|
byModel: Map<string, ModelUsage>;
|
|
287
311
|
byDay: Map<string, ModelUsage>;
|
|
288
312
|
byDayModels: Map<string, Map<string, ModelUsage>>;
|
|
313
|
+
/** 峰谷分桶:折叠时按调用时刻精确判档(tierAt),键 = 'peak' / 'offPeak'。 */
|
|
314
|
+
byTier: Map<string, ModelUsage>;
|
|
315
|
+
/** 工具调用次数(键 = 工具名;tool-call-delta 首见计数)。 */
|
|
316
|
+
byTool: Map<string, number>;
|
|
289
317
|
/** 中转站归组:按 provider 路由归类到站点/直连/未知路由(key = {@link siteBucketKey})。 */
|
|
290
318
|
bySite: Map<string, ModelUsage>;
|
|
291
319
|
/** 不可计价模型 id(未收录/无价,且非订阅)集合;跨会话合并后输出给面板提示。 */
|
|
@@ -318,6 +346,9 @@ export interface SerializedSessionFold {
|
|
|
318
346
|
byModel: Record<string, ModelUsage>;
|
|
319
347
|
byDay: Record<string, ModelUsage>;
|
|
320
348
|
byDayModels: Record<string, Record<string, ModelUsage>>;
|
|
349
|
+
/** 1.0.8 起新增;旧账本行缺失(合并时按空处理,不触发重折算)。 */
|
|
350
|
+
byTier?: Record<string, ModelUsage>;
|
|
351
|
+
byTool?: Record<string, number>;
|
|
321
352
|
bySite: Record<string, ModelUsage>;
|
|
322
353
|
unpricedModels: string[];
|
|
323
354
|
planCalls: Record<string, number>;
|
|
@@ -332,6 +363,11 @@ export interface UsageLedgerSession {
|
|
|
332
363
|
cwd?: string;
|
|
333
364
|
/** Stable log stamp (mtime + size) when the persistence backend exposes it. */
|
|
334
365
|
stamp?: string;
|
|
366
|
+
/**
|
|
367
|
+
* 折叠该行时的算法版本({@link FOLD_VERSION})。缺失 = 1.0.6 及更早写入的
|
|
368
|
+
* 旧算法行(加载边界由迁移统一回填为 1)。
|
|
369
|
+
*/
|
|
370
|
+
foldVersion?: number;
|
|
335
371
|
fold: SerializedSessionFold;
|
|
336
372
|
}
|
|
337
373
|
/** On-disk durable usage ledger. Versioned independently from the dashboard document. */
|
|
@@ -342,10 +378,18 @@ export interface UsageLedgerDocument {
|
|
|
342
378
|
/** 已应用的一次性配置迁移 id 列表(随文档落盘;缺省 = 尚未跑过任何迁移)。 */
|
|
343
379
|
appliedMigrations?: string[];
|
|
344
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* 折叠算法版本:归账语义变化时递增。v1 = 按 request/header 归账(稀疏 header 把
|
|
383
|
+
* 两次 header 之间的用量串到上一个模型,订阅模型首当其冲,issue #14);v2 =
|
|
384
|
+
* `assistant/message` 自带 source 归账(1.0.7 起)。持久账本行据此区分新旧算法:
|
|
385
|
+
* 日志已删/不可读而只能沿用旧行时,UI 标注置信度提示。
|
|
386
|
+
*/
|
|
387
|
+
export declare const FOLD_VERSION = 2;
|
|
345
388
|
/**
|
|
346
389
|
* 一次性账本迁移:id 唯一,apply 在加载边界对原始文档执行,已应用过的跳过。
|
|
347
390
|
* 未来账本/schema 字段变更(重命名、拆桶、语义调整)时,在此追加一条迁移并
|
|
348
391
|
* bump {@link UsageLedgerDocument.version};引擎保证幂等,重启不会重复执行。
|
|
392
|
+
* 可选字段的向后兼容回填(如 foldVersion)不 bump version:旧版本插件仍能读新文件。
|
|
349
393
|
*/
|
|
350
394
|
export interface LedgerMigration {
|
|
351
395
|
id: string;
|
|
@@ -353,8 +397,8 @@ export interface LedgerMigration {
|
|
|
353
397
|
apply(document: UsageLedgerDocument): boolean;
|
|
354
398
|
}
|
|
355
399
|
/**
|
|
356
|
-
*
|
|
357
|
-
*
|
|
400
|
+
* 账本迁移注册表。首条迁移给 1.0.6 及更早的行回填 foldVersion = 1(它们全部出自
|
|
401
|
+
* header 归因算法);此后新写入的行总带当前 {@link FOLD_VERSION}。
|
|
358
402
|
*/
|
|
359
403
|
export declare const LEDGER_MIGRATIONS: readonly LedgerMigration[];
|
|
360
404
|
/**
|
|
@@ -376,7 +420,9 @@ export interface UsageLedgerStore {
|
|
|
376
420
|
export declare function messageTextLength(message: unknown): number;
|
|
377
421
|
/**
|
|
378
422
|
* Fold one session's events into a {@link SessionFold}. 每个 LLM 调用归属到
|
|
379
|
-
*
|
|
423
|
+
* 其 `assistant/message` 自带 `message.source` 记录的模型(agent-loop 落盘时从
|
|
424
|
+
* 当次请求复制,每个调用一条,不依赖稀疏的 request/header);source 缺失时
|
|
425
|
+
* 兜底到最近一次 request/header 的状态。同时提取最新会话标题、最后活跃时间,
|
|
380
426
|
* 并按轮次折叠每轮费用明细(turn/start → turn/end;调用按 (turn) 归组)。
|
|
381
427
|
* @param events - the session's persisted events in log order.
|
|
382
428
|
* @param subscriptionProviders - provider ids billed through subscription plans.
|
|
@@ -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, {
|
|
@@ -114,6 +111,8 @@ interface SessionBillingRow {
|
|
|
114
111
|
id: string;
|
|
115
112
|
title?: string;
|
|
116
113
|
cwd?: string;
|
|
114
|
+
/** 数据来自旧算法折叠的持久账本行(原始日志已删,无法重算)。 */
|
|
115
|
+
stale?: boolean;
|
|
117
116
|
calls: number;
|
|
118
117
|
cost: number;
|
|
119
118
|
lastActive: number;
|
|
@@ -139,6 +138,8 @@ export interface UsageStats {
|
|
|
139
138
|
output: number;
|
|
140
139
|
cacheHit: number;
|
|
141
140
|
cacheMiss: number;
|
|
141
|
+
/** 显式缓存写入(cacheMiss 子集,部分厂商单独报告);1.0.8 起新增,旧快照缺失。 */
|
|
142
|
+
cacheWrite?: number;
|
|
142
143
|
cost: number;
|
|
143
144
|
/** 输出中的 reasoning(思考)token;已含在 output 内。 */
|
|
144
145
|
reasoning: number;
|
|
@@ -176,6 +177,22 @@ export interface UsageStats {
|
|
|
176
177
|
cacheMiss: number;
|
|
177
178
|
cost: number;
|
|
178
179
|
}>>;
|
|
180
|
+
/**
|
|
181
|
+
* 峰谷分桶(全量逐调用真实判档):1.0.8 起服务端按调用时刻精确归桶,
|
|
182
|
+
* 峰谷占比条优先用它(覆盖全部历史调用);旧快照缺失时回退逐轮估算。
|
|
183
|
+
*/
|
|
184
|
+
byTier?: {
|
|
185
|
+
peak: {
|
|
186
|
+
cost: number;
|
|
187
|
+
calls: number;
|
|
188
|
+
};
|
|
189
|
+
offPeak: {
|
|
190
|
+
cost: number;
|
|
191
|
+
calls: number;
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
/** 工具调用次数排行(键 = 工具名,按次数倒序);旧快照缺失。 */
|
|
195
|
+
byTool?: Record<string, number>;
|
|
179
196
|
/** 每轮费用明细(服务端聚合路径恒带);旧快照可能缺失。 */
|
|
180
197
|
byTurn?: readonly {
|
|
181
198
|
sessionId: string;
|
|
@@ -218,6 +235,8 @@ export interface UsageStats {
|
|
|
218
235
|
};
|
|
219
236
|
/** 性能指标(TTFT/生成速度/总延迟)按模型与按小时;旧快照可能缺失。 */
|
|
220
237
|
perf?: ClientPerf;
|
|
238
|
+
/** 旧版算法账本行兜底的会话数(模型归属可能失真);0 或缺省 = 全部数据可信。 */
|
|
239
|
+
staleLedgerSessions?: number;
|
|
221
240
|
/** 插件版本号(服务端读自包 package.json;旧快照缺失)。 */
|
|
222
241
|
pluginVersion?: string;
|
|
223
242
|
}
|
|
@@ -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.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.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,23 @@ 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
|
+
/** 用户自定义单价(与 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';
|
|
69
|
+
}
|
|
70
|
+
/** 自定义价表:计费目录键 → 单价。 */
|
|
71
|
+
export type UserPriceMap = Record<string, StoredUserPrice>;
|
|
72
|
+
/** localStorage key(与其他 `dsh.ui-usage-billing.*` 偏好同命名空间)。 */
|
|
73
|
+
export declare const USER_PRICES_STORAGE_KEY = "dsh.ui-usage-billing.prices";
|
|
74
|
+
/** 读取用户自定义价(写入侧已校验,这里只挡住手工改坏的非数字行)。仅在浏览器半区调用。 */
|
|
75
|
+
export declare function loadUserPrices(): UserPriceMap;
|
|
76
|
+
/** 写入用户自定义价。失败静默(展示偏好非关键)。 */
|
|
77
|
+
export declare function saveUserPrices(prices: UserPriceMap): void;
|
|
59
78
|
//# sourceMappingURL=usage-billing-settings.d.ts.map
|