@cassius0924/dsh-usage-dashboard 0.4.0 → 0.6.0
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 +29 -10
- package/lib/client.js +488 -124
- package/lib/client.js.map +4 -4
- package/lib/index.js +198 -27
- package/lib/index.js.map +3 -3
- package/lib/types/client/api.d.ts +2 -0
- package/lib/types/client/context.d.ts +3 -0
- package/lib/types/client/dashboard.d.ts +2 -0
- package/lib/types/client/dom.d.ts +23 -1
- package/lib/types/client/locales.d.ts +26 -9
- package/lib/types/client/message-cost.d.ts +10 -0
- package/lib/types/client/store.d.ts +4 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/client/views.d.ts +23 -0
- package/lib/types/client/widget.d.ts +10 -1
- package/lib/types/context.d.ts +44 -1
- package/lib/types/contract.d.ts +62 -10
- package/lib/types/pricing.d.ts +46 -15
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -60,32 +60,71 @@ function isTrustedApiRequest(req) {
|
|
|
60
60
|
|
|
61
61
|
// src/pricing.ts
|
|
62
62
|
var PEAK_PRICING_FROM_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
|
|
63
|
+
var V41_FLASH_PRICING_FROM_MS = Date.UTC(2026, 8, 10, 4, 0, 0);
|
|
64
|
+
var PRO_ROUTED_TO_FLASH_FROM_MS = Date.UTC(2026, 8, 14, 4, 0, 0);
|
|
63
65
|
var PEAK_WINDOWS = [[9, 12], [14, 18]];
|
|
64
66
|
var BEIJING_OFFSET_MS = 8 * 36e5;
|
|
65
|
-
var
|
|
67
|
+
var FLAT_RATES = {
|
|
66
68
|
pro: { cacheHit: 0.025, input: 3, output: 6 },
|
|
67
|
-
flash: { cacheHit: 0.02, input: 1, output: 2 }
|
|
69
|
+
flash: { cacheHit: 0.02, input: 1, output: 2 },
|
|
70
|
+
// The vision model billed at flash rates from day one, so its pre-switch
|
|
71
|
+
// history is costed at the same flat prices as flash.
|
|
72
|
+
vision: { cacheHit: 0.02, input: 1, output: 2 }
|
|
68
73
|
};
|
|
69
|
-
var
|
|
74
|
+
var PEAK_RATES_08_17 = {
|
|
70
75
|
pro: { cacheHit: 0.3, input: 9, output: 27 },
|
|
71
|
-
flash: { cacheHit: 0.1, input: 3, output: 9 }
|
|
76
|
+
flash: { cacheHit: 0.1, input: 3, output: 9 },
|
|
77
|
+
// Official policy: the vision model is priced identically to flash; images
|
|
78
|
+
// cost tokens, not their own multiplier.
|
|
79
|
+
vision: { cacheHit: 0.1, input: 3, output: 9 }
|
|
72
80
|
};
|
|
81
|
+
var PEAK_RATES_09_10 = {
|
|
82
|
+
pro: { cacheHit: 0.3, input: 9, output: 27 },
|
|
83
|
+
flash: { cacheHit: 0.04, input: 2, output: 8 },
|
|
84
|
+
vision: { cacheHit: 0.04, input: 2, output: 8 }
|
|
85
|
+
};
|
|
86
|
+
var PRICE_ERAS = [
|
|
87
|
+
{ fromMs: PEAK_PRICING_FROM_MS, peak: PEAK_RATES_08_17, weekdaysOnly: false },
|
|
88
|
+
{ fromMs: V41_FLASH_PRICING_FROM_MS, peak: PEAK_RATES_09_10, weekdaysOnly: true }
|
|
89
|
+
];
|
|
90
|
+
function eraAt(timeMs) {
|
|
91
|
+
let era = PRICE_ERAS[0];
|
|
92
|
+
for (const candidate of PRICE_ERAS) if (timeMs >= candidate.fromMs) era = candidate;
|
|
93
|
+
return era;
|
|
94
|
+
}
|
|
73
95
|
var halved = (rates) => ({
|
|
74
96
|
cacheHit: rates.cacheHit / 2,
|
|
75
97
|
input: rates.input / 2,
|
|
76
98
|
output: rates.output / 2
|
|
77
99
|
});
|
|
78
100
|
function tierOf(model) {
|
|
79
|
-
|
|
101
|
+
const name2 = model.toLowerCase();
|
|
102
|
+
if (name2.includes("vision")) return "vision";
|
|
103
|
+
if (name2.includes("flash")) return "flash";
|
|
104
|
+
return "pro";
|
|
105
|
+
}
|
|
106
|
+
function billedTierOf(timeMs, model) {
|
|
107
|
+
const tier = tierOf(model);
|
|
108
|
+
return tier === "pro" && timeMs >= PRO_ROUTED_TO_FLASH_FROM_MS ? "flash" : tier;
|
|
109
|
+
}
|
|
110
|
+
function isBeijingWeekday(timeMs) {
|
|
111
|
+
const day = new Date(timeMs + BEIJING_OFFSET_MS).getUTCDay();
|
|
112
|
+
return day >= 1 && day <= 5;
|
|
113
|
+
}
|
|
114
|
+
function estimateImageTokens(width, height) {
|
|
115
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return 0;
|
|
116
|
+
const area = width * height;
|
|
117
|
+
const scaledArea = Math.min(800 * 800, Math.max(384 * 384, area));
|
|
118
|
+
return 384 * (scaledArea / (800 * 800));
|
|
80
119
|
}
|
|
81
120
|
function isPeak(timeMs) {
|
|
121
|
+
if (eraAt(timeMs).weekdaysOnly && !isBeijingWeekday(timeMs)) return false;
|
|
82
122
|
const hour = Math.floor(((timeMs + BEIJING_OFFSET_MS) % 864e5 + 864e5) % 864e5 / 36e5);
|
|
83
123
|
return PEAK_WINDOWS.some(([from, to]) => hour >= from && hour < to);
|
|
84
124
|
}
|
|
85
125
|
function ratesAt(timeMs, model) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const peak = PEAK_RATES[tier];
|
|
126
|
+
if (timeMs < PEAK_PRICING_FROM_MS) return FLAT_RATES[tierOf(model)];
|
|
127
|
+
const peak = eraAt(timeMs).peak[billedTierOf(timeMs, model)];
|
|
89
128
|
return isPeak(timeMs) ? peak : halved(peak);
|
|
90
129
|
}
|
|
91
130
|
function cacheSavingOf(timeMs, model, cache) {
|
|
@@ -97,23 +136,19 @@ function costOf(timeMs, model, input, cache, output) {
|
|
|
97
136
|
return applyRates(ratesAt(timeMs, model), input, cache, output);
|
|
98
137
|
}
|
|
99
138
|
function costUnderPeakEra(timeMs, model, input, cache, output, forceOffPeak = false) {
|
|
100
|
-
const peak =
|
|
139
|
+
const peak = PRICE_ERAS[PRICE_ERAS.length - 1].peak[billedTierOf(timeMs, model)];
|
|
101
140
|
const rates = !forceOffPeak && isPeak(timeMs) ? peak : halved(peak);
|
|
102
141
|
return applyRates(rates, input, cache, output);
|
|
103
142
|
}
|
|
104
143
|
function pricingInfo(nowMs) {
|
|
105
|
-
const
|
|
144
|
+
const era = eraAt(nowMs);
|
|
145
|
+
const row = (model, tier) => ({ model, peak: era.peak[tier], offPeak: halved(era.peak[tier]) });
|
|
106
146
|
return {
|
|
107
147
|
currency: "CNY",
|
|
108
148
|
switchDate: "2026-08-17",
|
|
109
|
-
|
|
110
|
-
inPeakNow: split && isPeak(nowMs),
|
|
149
|
+
inPeakNow: isPeak(nowMs),
|
|
111
150
|
peakWindows: ["09:00\u201312:00", "14:00\u201318:00"],
|
|
112
|
-
tiers: ["
|
|
113
|
-
model: tier === "pro" ? "deepseek-v4-pro" : "deepseek-v4-flash",
|
|
114
|
-
peak: split ? PEAK_RATES[tier] : LEGACY_RATES[tier],
|
|
115
|
-
offPeak: split ? halved(PEAK_RATES[tier]) : null
|
|
116
|
-
}))
|
|
151
|
+
tiers: [row("deepseek-flash", "flash"), row("deepseek-v4-pro", "pro")]
|
|
117
152
|
};
|
|
118
153
|
}
|
|
119
154
|
|
|
@@ -175,6 +210,18 @@ function bump(map, key, input, output, cache, cost) {
|
|
|
175
210
|
map.set(key, bucket);
|
|
176
211
|
}
|
|
177
212
|
var emptyOverall = () => ({ ...emptyBucket(), reasoning: 0, cacheSavings: 0 });
|
|
213
|
+
var emptyVision = () => ({ images: 0, imageTokens: 0, cost: 0, bytes: 0 });
|
|
214
|
+
function bumpVisionInto(acc, images, imageTokens, cost, bytes) {
|
|
215
|
+
acc.images += images;
|
|
216
|
+
acc.imageTokens += imageTokens;
|
|
217
|
+
acc.cost += cost;
|
|
218
|
+
acc.bytes += bytes;
|
|
219
|
+
}
|
|
220
|
+
function bumpVisionMap(map, key, images, imageTokens, cost, bytes) {
|
|
221
|
+
const acc = map.get(key) ?? emptyVision();
|
|
222
|
+
bumpVisionInto(acc, images, imageTokens, cost, bytes);
|
|
223
|
+
map.set(key, acc);
|
|
224
|
+
}
|
|
178
225
|
var SESSION_TOP_N = 6;
|
|
179
226
|
function titleOf(events, id) {
|
|
180
227
|
let title = "";
|
|
@@ -217,6 +264,7 @@ function addUsageEvent(events, onEvent) {
|
|
|
217
264
|
if (events === void 0) return result;
|
|
218
265
|
let provider = "";
|
|
219
266
|
let model = "";
|
|
267
|
+
const pendingImages = [];
|
|
220
268
|
for (const ev of events) {
|
|
221
269
|
if (ev?.type === "request/header") {
|
|
222
270
|
const cfg = ev.data?.header?.config;
|
|
@@ -226,6 +274,7 @@ function addUsageEvent(events, onEvent) {
|
|
|
226
274
|
}
|
|
227
275
|
continue;
|
|
228
276
|
}
|
|
277
|
+
pendingImages.push(...imagesInEvent(ev));
|
|
229
278
|
if (ev?.type !== "assistant/message") continue;
|
|
230
279
|
const usage = ev.data?.usage;
|
|
231
280
|
if (usage === void 0) continue;
|
|
@@ -235,11 +284,60 @@ function addUsageEvent(events, onEvent) {
|
|
|
235
284
|
continue;
|
|
236
285
|
}
|
|
237
286
|
const [input = 0, output = 0, cache = 0, reasoning = 0] = values;
|
|
238
|
-
|
|
287
|
+
const rawMessageId = ev.data?.message?.id;
|
|
288
|
+
const messageId = typeof rawMessageId === "string" && rawMessageId !== "" ? rawMessageId : void 0;
|
|
289
|
+
const rawTurn = ev.data?.turn;
|
|
290
|
+
const turn = typeof rawTurn === "number" && Number.isFinite(rawTurn) ? rawTurn : void 0;
|
|
291
|
+
onEvent(ev.time, input, output, cache, reasoning, provider, model, messageId, turn, pendingImages);
|
|
292
|
+
pendingImages.length = 0;
|
|
239
293
|
result.usageRecords += 1;
|
|
240
294
|
}
|
|
241
295
|
return result;
|
|
242
296
|
}
|
|
297
|
+
function imageBlocks(blocks) {
|
|
298
|
+
const found = [];
|
|
299
|
+
const walkAll = (items) => {
|
|
300
|
+
for (const item of items ?? []) {
|
|
301
|
+
if (item?.type === "image" && item.attachment !== void 0 && item.attachment !== null) {
|
|
302
|
+
found.push(item.attachment);
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(item?.content)) walkAll(item.content);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
walkAll(blocks);
|
|
308
|
+
return found;
|
|
309
|
+
}
|
|
310
|
+
function imagesInEvent(ev) {
|
|
311
|
+
if (ev === void 0) return [];
|
|
312
|
+
return [
|
|
313
|
+
...imageBlocks(ev.data?.content),
|
|
314
|
+
...imageBlocks(ev.data?.message?.content)
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
async function readSessionEvents(persistence, id) {
|
|
318
|
+
if (typeof persistence.open === "function") {
|
|
319
|
+
const handle = await persistence.open(id, "read");
|
|
320
|
+
try {
|
|
321
|
+
const events2 = [];
|
|
322
|
+
for (; ; ) {
|
|
323
|
+
const slice = await handle.read(events2.length);
|
|
324
|
+
const batch = slice.events ?? [];
|
|
325
|
+
if (batch.length === 0) return events2;
|
|
326
|
+
for (const event of batch) events2.push(event);
|
|
327
|
+
}
|
|
328
|
+
} finally {
|
|
329
|
+
try {
|
|
330
|
+
await handle.close();
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (persistence.readFrom === void 0) {
|
|
336
|
+
throw new Error("\u5BBF\u4E3B sessionPersistence \u7F3A\u5C11 open() \u4E0E readFrom()\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u4F1A\u8BDD\u65E5\u5FD7");
|
|
337
|
+
}
|
|
338
|
+
const { events } = await persistence.readFrom(id, 0);
|
|
339
|
+
return events;
|
|
340
|
+
}
|
|
243
341
|
async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
244
342
|
if (persistence === void 0) return { ok: false, error: "\u4F1A\u8BDD\u6301\u4E45\u5316\u670D\u52A1\u4E0D\u53EF\u7528" };
|
|
245
343
|
let headers;
|
|
@@ -255,6 +353,9 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
255
353
|
const modelDays = /* @__PURE__ */ new Map();
|
|
256
354
|
const modelHours = /* @__PURE__ */ new Map();
|
|
257
355
|
const sessions = [];
|
|
356
|
+
const vision = emptyVision();
|
|
357
|
+
const visionDays = /* @__PURE__ */ new Map();
|
|
358
|
+
const visionSessions = /* @__PURE__ */ new Map();
|
|
258
359
|
const coverage = {
|
|
259
360
|
scope: "local-dsh-session-logs",
|
|
260
361
|
listedSessions: 0,
|
|
@@ -283,7 +384,10 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
283
384
|
modelTotals: /* @__PURE__ */ new Map(),
|
|
284
385
|
modelDays: /* @__PURE__ */ new Map(),
|
|
285
386
|
modelHours: /* @__PURE__ */ new Map(),
|
|
286
|
-
sessions: []
|
|
387
|
+
sessions: [],
|
|
388
|
+
vision: emptyVision(),
|
|
389
|
+
visionDays: /* @__PURE__ */ new Map(),
|
|
390
|
+
visionSessions: /* @__PURE__ */ new Map()
|
|
287
391
|
}));
|
|
288
392
|
const bumpOverall = (bucket, input, output, cache, reasoning, cost, cacheSavings) => {
|
|
289
393
|
bucket.input += input;
|
|
@@ -316,15 +420,17 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
316
420
|
session.calls += 1;
|
|
317
421
|
if (time > session.lastActive) session.lastActive = time;
|
|
318
422
|
};
|
|
319
|
-
const sessionIds = headers.map((header) => header.id ?? header.sessionId).filter((sid) => sid !== void 0 && sid !== "");
|
|
423
|
+
const sessionIds = headers.map((header) => header.id ?? header.sessionId ?? header.header?.id ?? header.header?.sessionId).filter((sid) => sid !== void 0 && sid !== "");
|
|
320
424
|
coverage.listedSessions = sessionIds.length;
|
|
321
425
|
for (const sid of sessionIds) {
|
|
322
426
|
try {
|
|
323
|
-
const
|
|
427
|
+
const events = await readSessionEvents(persistence, sid);
|
|
324
428
|
coverage.scannedSessions += 1;
|
|
325
429
|
const session = { id: sid, title: "", total: 0, cost: 0, calls: 0, lastActive: 0 };
|
|
326
430
|
const windowSessions = windowAggregates.map(() => ({ id: sid, title: "", total: 0, cost: 0, calls: 0, lastActive: 0 }));
|
|
327
|
-
const
|
|
431
|
+
const sessionVision = { ...emptyVision(), title: "" };
|
|
432
|
+
const windowVisionSessions = windowAggregates.map(() => ({ ...emptyVision(), title: "" }));
|
|
433
|
+
const scanned = addUsageEvent(events, (time, input, output, cache, reasoning, provider, model, _messageId, _turn, images) => {
|
|
328
434
|
if (coverage.earliestAt === null || time < coverage.earliestAt) coverage.earliestAt = time;
|
|
329
435
|
if (coverage.latestAt === null || time > coverage.latestAt) coverage.latestAt = time;
|
|
330
436
|
const d = new Date(time);
|
|
@@ -339,6 +445,29 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
339
445
|
bumpModel(modelTotals, modelDays, modelHours, modelKey, dayKey, hourKey, input, output, cache, cost);
|
|
340
446
|
bumpOverall(overall, input, output, cache, reasoning, cost, cacheSavings);
|
|
341
447
|
bumpSession(session, time, eventTotal, cost);
|
|
448
|
+
if (images.length > 0) {
|
|
449
|
+
let imageTokensSum = 0;
|
|
450
|
+
let imageCostSum = 0;
|
|
451
|
+
let imageBytesSum = 0;
|
|
452
|
+
for (const image of images) {
|
|
453
|
+
const width = typeof image.width === "number" ? image.width : 0;
|
|
454
|
+
const height = typeof image.height === "number" ? image.height : 0;
|
|
455
|
+
const tokens = estimateImageTokens(width, height);
|
|
456
|
+
imageTokensSum += tokens;
|
|
457
|
+
imageCostSum += costOf(time, model, tokens, 0, 0);
|
|
458
|
+
imageBytesSum += typeof image.bytes === "number" ? image.bytes : 0;
|
|
459
|
+
}
|
|
460
|
+
bumpVisionInto(vision, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
461
|
+
bumpVisionMap(visionDays, dayKey, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
462
|
+
bumpVisionInto(sessionVision, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
463
|
+
for (let i = 0; i < windowAggregates.length; i++) {
|
|
464
|
+
const aggregate = windowAggregates[i];
|
|
465
|
+
if (dayKey < aggregate.startKey || dayKey > aggregate.endKey) continue;
|
|
466
|
+
bumpVisionInto(aggregate.vision, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
467
|
+
bumpVisionMap(aggregate.visionDays, dayKey, images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
468
|
+
bumpVisionInto(windowVisionSessions[i], images.length, imageTokensSum, imageCostSum, imageBytesSum);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
342
471
|
for (let i = 0; i < windowAggregates.length; i++) {
|
|
343
472
|
const aggregate = windowAggregates[i];
|
|
344
473
|
if (dayKey < aggregate.startKey || dayKey > aggregate.endKey) continue;
|
|
@@ -368,6 +497,16 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
368
497
|
windowAggregates[i].sessions.push(windowSession);
|
|
369
498
|
}
|
|
370
499
|
}
|
|
500
|
+
if (sessionVision.images > 0) {
|
|
501
|
+
sessionVision.title = titleOf(events, sid);
|
|
502
|
+
visionSessions.set(sid, sessionVision);
|
|
503
|
+
for (let i = 0; i < windowAggregates.length; i++) {
|
|
504
|
+
const windowVision = windowVisionSessions[i];
|
|
505
|
+
if (windowVision.images === 0) continue;
|
|
506
|
+
windowVision.title = sessionVision.title;
|
|
507
|
+
windowAggregates[i].visionSessions.set(sid, windowVision);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
371
510
|
} catch {
|
|
372
511
|
coverage.failedSessions += 1;
|
|
373
512
|
}
|
|
@@ -423,6 +562,22 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
423
562
|
const b = dayMap.get(key) ?? emptyBucket();
|
|
424
563
|
heatmap.push({ date: key, total: b.total, cost: b.cost, calls: b.calls });
|
|
425
564
|
}
|
|
565
|
+
const makeVisionDaily = (map, count) => {
|
|
566
|
+
const result = [];
|
|
567
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
568
|
+
const key = dayKeyOf(dateBefore(i));
|
|
569
|
+
const b = map.get(key) ?? emptyVision();
|
|
570
|
+
result.push({ date: key, images: b.images, imageTokens: b.imageTokens, cost: b.cost });
|
|
571
|
+
}
|
|
572
|
+
return result;
|
|
573
|
+
};
|
|
574
|
+
const makeVisionSessions = (map) => [...map.entries()].map(([id, acc]) => ({
|
|
575
|
+
id,
|
|
576
|
+
title: acc.title !== "" ? acc.title : `\u4F1A\u8BDD ${id.slice(0, 8)}`,
|
|
577
|
+
images: acc.images,
|
|
578
|
+
imageTokens: acc.imageTokens,
|
|
579
|
+
cost: acc.cost
|
|
580
|
+
})).sort((a, b) => b.images - a.images).slice(0, SESSION_TOP_N);
|
|
426
581
|
const models = makeModels(modelTotals, modelDays, modelHours, 30);
|
|
427
582
|
const windows = windowAggregates.map((aggregate) => ({
|
|
428
583
|
days: aggregate.days,
|
|
@@ -431,7 +586,10 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
431
586
|
totals: { ...aggregate.overall },
|
|
432
587
|
models: makeModels(aggregate.modelTotals, aggregate.modelDays, aggregate.modelHours, aggregate.days),
|
|
433
588
|
sessions: [...aggregate.sessions].sort((a, b) => b.cost - a.cost).slice(0, SESSION_TOP_N),
|
|
434
|
-
sessionCount: aggregate.sessions.length
|
|
589
|
+
sessionCount: aggregate.sessions.length,
|
|
590
|
+
vision: { ...aggregate.vision },
|
|
591
|
+
visionDaily: makeVisionDaily(aggregate.visionDays, aggregate.days),
|
|
592
|
+
visionSessions: makeVisionSessions(aggregate.visionSessions)
|
|
435
593
|
}));
|
|
436
594
|
return {
|
|
437
595
|
ok: true,
|
|
@@ -447,6 +605,9 @@ async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
|
447
605
|
sessionCount: sessions.length,
|
|
448
606
|
coverage,
|
|
449
607
|
windows,
|
|
608
|
+
vision: { ...vision },
|
|
609
|
+
visionDaily: makeVisionDaily(visionDays, 30),
|
|
610
|
+
visionSessions: makeVisionSessions(visionSessions),
|
|
450
611
|
totals: {
|
|
451
612
|
input: overall.input,
|
|
452
613
|
output: overall.output,
|
|
@@ -466,8 +627,7 @@ async function fetchSessionUsage(persistence, sessionId) {
|
|
|
466
627
|
if (!isValidSessionId(sessionId)) return { ok: false, error: "\u4F1A\u8BDD id \u683C\u5F0F\u4E0D\u5408\u6CD5" };
|
|
467
628
|
let events;
|
|
468
629
|
try {
|
|
469
|
-
|
|
470
|
-
events = result.events;
|
|
630
|
+
events = await readSessionEvents(persistence, sessionId);
|
|
471
631
|
} catch (err) {
|
|
472
632
|
return { ok: false, error: `\u8BFB\u53D6\u4F1A\u8BDD\u65E5\u5FD7\u5931\u8D25\uFF1A${errorMessage(err)}` };
|
|
473
633
|
}
|
|
@@ -477,7 +637,8 @@ async function fetchSessionUsage(persistence, sessionId) {
|
|
|
477
637
|
let calls = 0;
|
|
478
638
|
let firstActive = null;
|
|
479
639
|
let lastActive = null;
|
|
480
|
-
|
|
640
|
+
const turnTotals = /* @__PURE__ */ new Map();
|
|
641
|
+
addUsageEvent(events, (time, input, output, cache, _reasoning, provider, model, messageId, turn) => {
|
|
481
642
|
const modelKey = `${provider}/${model}`;
|
|
482
643
|
const eventCost = costOf(time, model, input, cache, output);
|
|
483
644
|
const eventTotal = input + output + cache;
|
|
@@ -487,6 +648,15 @@ async function fetchSessionUsage(persistence, sessionId) {
|
|
|
487
648
|
calls += 1;
|
|
488
649
|
if (firstActive === null || time < firstActive) firstActive = time;
|
|
489
650
|
if (lastActive === null || time > lastActive) lastActive = time;
|
|
651
|
+
if (messageId !== void 0) {
|
|
652
|
+
const key = turn === void 0 ? `message:${messageId}` : `turn:${turn}`;
|
|
653
|
+
const aggregate = turnTotals.get(key) ?? { messageId, total: 0, cost: 0, calls: 0 };
|
|
654
|
+
aggregate.messageId = messageId;
|
|
655
|
+
aggregate.total += eventTotal;
|
|
656
|
+
aggregate.cost += eventCost;
|
|
657
|
+
aggregate.calls += 1;
|
|
658
|
+
turnTotals.set(key, aggregate);
|
|
659
|
+
}
|
|
490
660
|
});
|
|
491
661
|
const models = [...modelTotals.entries()].map(([modelKey, bucket]) => {
|
|
492
662
|
const slash = modelKey.indexOf("/");
|
|
@@ -513,7 +683,8 @@ async function fetchSessionUsage(persistence, sessionId) {
|
|
|
513
683
|
calls,
|
|
514
684
|
firstActive,
|
|
515
685
|
lastActive,
|
|
516
|
-
models
|
|
686
|
+
models,
|
|
687
|
+
turns: [...turnTotals.values()]
|
|
517
688
|
}
|
|
518
689
|
};
|
|
519
690
|
}
|