@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/README.md +5 -0
- package/lib/client.js +5 -5
- package/lib/index.js +71 -4
- package/lib/types/aggregate.d.ts +43 -4
- package/lib/types/client/UsageBilling.d.ts +14 -0
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/pricing.d.ts +21 -8
- package/lib/types/client/usage-billing-settings.d.ts +25 -13
- package/lib/types/index.d.ts +6 -0
- package/package.json +1 -1
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:
|
|
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) <
|
|
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 = [
|
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;
|
|
@@ -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
|
-
*
|
|
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 =
|
|
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
|
|
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
|
|
62
|
+
* 查一个模型(可选来源)的用户自定义价:优先「模型×来源」精确命中;无来源匹配时
|
|
63
|
+
* 回落该模型的无来源默认价;再无则 undefined(走内置目录)。
|
|
64
|
+
* @param key - 计费目录键。
|
|
65
|
+
* @param origin - 调用来源(中转站 origin);缺省仅查默认价。
|
|
49
66
|
*/
|
|
50
|
-
export declare function
|
|
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
|
-
/**
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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.
|
|
4
|
+
"version": "1.0.10",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|