@cassius0924/dsh-usage-dashboard 0.3.0 → 0.4.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 +10 -1
- package/lib/client.js +2288 -645
- package/lib/client.js.map +4 -4
- package/lib/index.js +236 -91
- package/lib/index.js.map +3 -3
- package/lib/types/client/api.d.ts +2 -1
- package/lib/types/client/boundary.d.ts +28 -0
- package/lib/types/client/budget.d.ts +16 -0
- package/lib/types/client/cache.d.ts +1 -1
- package/lib/types/client/chart-focus.d.ts +35 -0
- package/lib/types/client/charts.d.ts +23 -5
- package/lib/types/client/context.d.ts +10 -0
- package/lib/types/client/dashboard.d.ts +5 -2
- package/lib/types/client/dom.d.ts +23 -0
- package/lib/types/client/export.d.ts +9 -0
- package/lib/types/client/freshness.d.ts +5 -0
- package/lib/types/client/i18n.d.ts +15 -0
- package/lib/types/client/index.d.ts +0 -5
- package/lib/types/client/locales.d.ts +245 -0
- package/lib/types/client/metric.d.ts +12 -0
- package/lib/types/client/store.d.ts +7 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/contract.d.ts +92 -0
- package/lib/types/index.d.ts +9 -6
- package/lib/types/usage.d.ts +10 -10
- package/package.json +4 -2
package/lib/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// src/contract.ts
|
|
2
|
+
var USAGE_WINDOW_DAYS = [7, 30, 90, 365];
|
|
3
|
+
var SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
4
|
+
var isValidSessionId = (id) => SESSION_ID_PATTERN.test(id);
|
|
5
|
+
|
|
1
6
|
// src/memo.ts
|
|
2
7
|
function memoize(ttlMs, compute) {
|
|
3
8
|
const state = { entry: void 0, inflight: void 0 };
|
|
@@ -159,6 +164,17 @@ async function fetchBalance(credentials) {
|
|
|
159
164
|
};
|
|
160
165
|
}
|
|
161
166
|
var emptyBucket = () => ({ input: 0, output: 0, cache: 0, total: 0, cost: 0, calls: 0 });
|
|
167
|
+
function bump(map, key, input, output, cache, cost) {
|
|
168
|
+
const bucket = map.get(key) ?? emptyBucket();
|
|
169
|
+
bucket.input += input;
|
|
170
|
+
bucket.output += output;
|
|
171
|
+
bucket.cache += cache;
|
|
172
|
+
bucket.total += input + output + cache;
|
|
173
|
+
bucket.cost += cost;
|
|
174
|
+
bucket.calls += 1;
|
|
175
|
+
map.set(key, bucket);
|
|
176
|
+
}
|
|
177
|
+
var emptyOverall = () => ({ ...emptyBucket(), reasoning: 0, cacheSavings: 0 });
|
|
162
178
|
var SESSION_TOP_N = 6;
|
|
163
179
|
function titleOf(events, id) {
|
|
164
180
|
let title = "";
|
|
@@ -197,7 +213,8 @@ function summarize(dayMap, now) {
|
|
|
197
213
|
};
|
|
198
214
|
}
|
|
199
215
|
function addUsageEvent(events, onEvent) {
|
|
200
|
-
|
|
216
|
+
const result = { usageRecords: 0, skippedRecords: 0 };
|
|
217
|
+
if (events === void 0) return result;
|
|
201
218
|
let provider = "";
|
|
202
219
|
let model = "";
|
|
203
220
|
for (const ev of events) {
|
|
@@ -212,15 +229,18 @@ function addUsageEvent(events, onEvent) {
|
|
|
212
229
|
if (ev?.type !== "assistant/message") continue;
|
|
213
230
|
const usage = ev.data?.usage;
|
|
214
231
|
if (usage === void 0) continue;
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
const values = [usage.inputTokens, usage.outputTokens, usage.cacheReadTokens, usage.reasoningTokens].map((value) => value === void 0 ? 0 : Number(value));
|
|
233
|
+
if (typeof ev.time !== "number" || !Number.isFinite(ev.time) || values.some((value) => !Number.isFinite(value) || value < 0)) {
|
|
234
|
+
result.skippedRecords += 1;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const [input = 0, output = 0, cache = 0, reasoning = 0] = values;
|
|
220
238
|
onEvent(ev.time, input, output, cache, reasoning, provider, model);
|
|
239
|
+
result.usageRecords += 1;
|
|
221
240
|
}
|
|
241
|
+
return result;
|
|
222
242
|
}
|
|
223
|
-
async function fetchUsage(persistence) {
|
|
243
|
+
async function fetchUsage(persistence, nowMs = Date.now()) {
|
|
224
244
|
if (persistence === void 0) return { ok: false, error: "\u4F1A\u8BDD\u6301\u4E45\u5316\u670D\u52A1\u4E0D\u53EF\u7528" };
|
|
225
245
|
let headers;
|
|
226
246
|
try {
|
|
@@ -230,132 +250,189 @@ async function fetchUsage(persistence) {
|
|
|
230
250
|
}
|
|
231
251
|
const dayMap = /* @__PURE__ */ new Map();
|
|
232
252
|
const hourMap = /* @__PURE__ */ new Map();
|
|
233
|
-
const overall =
|
|
253
|
+
const overall = emptyOverall();
|
|
234
254
|
const modelTotals = /* @__PURE__ */ new Map();
|
|
235
255
|
const modelDays = /* @__PURE__ */ new Map();
|
|
236
256
|
const modelHours = /* @__PURE__ */ new Map();
|
|
237
257
|
const sessions = [];
|
|
258
|
+
const coverage = {
|
|
259
|
+
scope: "local-dsh-session-logs",
|
|
260
|
+
listedSessions: 0,
|
|
261
|
+
scannedSessions: 0,
|
|
262
|
+
usageRecords: 0,
|
|
263
|
+
skippedRecords: 0,
|
|
264
|
+
failedSessions: 0,
|
|
265
|
+
earliestAt: null,
|
|
266
|
+
latestAt: null
|
|
267
|
+
};
|
|
238
268
|
const peakSplit = {
|
|
239
269
|
peak: { total: 0, cost: 0, calls: 0 },
|
|
240
270
|
offPeak: { total: 0, cost: 0, calls: 0 },
|
|
241
271
|
peakEraCost: 0,
|
|
242
272
|
offPeakEraCost: 0
|
|
243
273
|
};
|
|
244
|
-
const
|
|
245
|
-
|
|
274
|
+
const now = new Date(nowMs);
|
|
275
|
+
const dateBefore = (days) => new Date(now.getFullYear(), now.getMonth(), now.getDate() - days);
|
|
276
|
+
const windowAggregates = USAGE_WINDOW_DAYS.map((days) => ({
|
|
277
|
+
days,
|
|
278
|
+
startKey: dayKeyOf(dateBefore(days - 1)),
|
|
279
|
+
endKey: dayKeyOf(now),
|
|
280
|
+
dayMap: /* @__PURE__ */ new Map(),
|
|
281
|
+
hourMap: /* @__PURE__ */ new Map(),
|
|
282
|
+
overall: emptyOverall(),
|
|
283
|
+
modelTotals: /* @__PURE__ */ new Map(),
|
|
284
|
+
modelDays: /* @__PURE__ */ new Map(),
|
|
285
|
+
modelHours: /* @__PURE__ */ new Map(),
|
|
286
|
+
sessions: []
|
|
287
|
+
}));
|
|
288
|
+
const bumpOverall = (bucket, input, output, cache, reasoning, cost, cacheSavings) => {
|
|
246
289
|
bucket.input += input;
|
|
247
290
|
bucket.output += output;
|
|
248
291
|
bucket.cache += cache;
|
|
292
|
+
bucket.reasoning += reasoning;
|
|
249
293
|
bucket.total += input + output + cache;
|
|
250
294
|
bucket.cost += cost;
|
|
295
|
+
bucket.cacheSavings += cacheSavings;
|
|
251
296
|
bucket.calls += 1;
|
|
252
|
-
map.set(key, bucket);
|
|
253
297
|
};
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
298
|
+
const bumpModel = (totals, days, hours, modelKey, dayKey, hourKey, input, output, cache, cost) => {
|
|
299
|
+
bump(totals, modelKey, input, output, cache, cost);
|
|
300
|
+
let dayBuckets = days.get(modelKey);
|
|
301
|
+
if (dayBuckets === void 0) {
|
|
302
|
+
dayBuckets = /* @__PURE__ */ new Map();
|
|
303
|
+
days.set(modelKey, dayBuckets);
|
|
304
|
+
}
|
|
305
|
+
bump(dayBuckets, dayKey, input, output, cache, cost);
|
|
306
|
+
let hourBuckets = hours.get(modelKey);
|
|
307
|
+
if (hourBuckets === void 0) {
|
|
308
|
+
hourBuckets = /* @__PURE__ */ new Map();
|
|
309
|
+
hours.set(modelKey, hourBuckets);
|
|
310
|
+
}
|
|
311
|
+
bump(hourBuckets, hourKey, input, output, cache, cost);
|
|
312
|
+
};
|
|
313
|
+
const bumpSession = (session, time, total, cost) => {
|
|
314
|
+
session.total += total;
|
|
315
|
+
session.cost += cost;
|
|
316
|
+
session.calls += 1;
|
|
317
|
+
if (time > session.lastActive) session.lastActive = time;
|
|
318
|
+
};
|
|
319
|
+
const sessionIds = headers.map((header) => header.id ?? header.sessionId).filter((sid) => sid !== void 0 && sid !== "");
|
|
320
|
+
coverage.listedSessions = sessionIds.length;
|
|
321
|
+
for (const sid of sessionIds) {
|
|
257
322
|
try {
|
|
258
323
|
const { events } = await persistence.readFrom(sid, 0);
|
|
324
|
+
coverage.scannedSessions += 1;
|
|
259
325
|
const session = { id: sid, title: "", total: 0, cost: 0, calls: 0, lastActive: 0 };
|
|
260
|
-
|
|
326
|
+
const windowSessions = windowAggregates.map(() => ({ id: sid, title: "", total: 0, cost: 0, calls: 0, lastActive: 0 }));
|
|
327
|
+
const scanned = addUsageEvent(events, (time, input, output, cache, reasoning, provider, model) => {
|
|
328
|
+
if (coverage.earliestAt === null || time < coverage.earliestAt) coverage.earliestAt = time;
|
|
329
|
+
if (coverage.latestAt === null || time > coverage.latestAt) coverage.latestAt = time;
|
|
261
330
|
const d = new Date(time);
|
|
262
331
|
const dayKey = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
263
332
|
const hourKey = String(d.getHours());
|
|
264
333
|
const modelKey = `${provider}/${model}`;
|
|
265
334
|
const cost = costOf(time, model, input, cache, output);
|
|
335
|
+
const cacheSavings = cacheSavingOf(time, model, cache);
|
|
336
|
+
const eventTotal = input + output + cache;
|
|
266
337
|
bump(dayMap, dayKey, input, output, cache, cost);
|
|
267
338
|
bump(hourMap, hourKey, input, output, cache, cost);
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
339
|
+
bumpModel(modelTotals, modelDays, modelHours, modelKey, dayKey, hourKey, input, output, cache, cost);
|
|
340
|
+
bumpOverall(overall, input, output, cache, reasoning, cost, cacheSavings);
|
|
341
|
+
bumpSession(session, time, eventTotal, cost);
|
|
342
|
+
for (let i = 0; i < windowAggregates.length; i++) {
|
|
343
|
+
const aggregate = windowAggregates[i];
|
|
344
|
+
if (dayKey < aggregate.startKey || dayKey > aggregate.endKey) continue;
|
|
345
|
+
bump(aggregate.dayMap, dayKey, input, output, cache, cost);
|
|
346
|
+
bump(aggregate.hourMap, hourKey, input, output, cache, cost);
|
|
347
|
+
bumpModel(aggregate.modelTotals, aggregate.modelDays, aggregate.modelHours, modelKey, dayKey, hourKey, input, output, cache, cost);
|
|
348
|
+
bumpOverall(aggregate.overall, input, output, cache, reasoning, cost, cacheSavings);
|
|
349
|
+
bumpSession(windowSessions[i], time, eventTotal, cost);
|
|
279
350
|
}
|
|
280
|
-
bump(hours, hourKey, input, output, cache, cost);
|
|
281
|
-
overall.input += input;
|
|
282
|
-
overall.output += output;
|
|
283
|
-
overall.cache += cache;
|
|
284
|
-
overall.reasoning += reasoning;
|
|
285
|
-
overall.total += input + output + cache;
|
|
286
|
-
overall.cost += cost;
|
|
287
|
-
overall.cacheSavings += cacheSavingOf(time, model, cache);
|
|
288
|
-
session.total += input + output + cache;
|
|
289
|
-
session.cost += cost;
|
|
290
|
-
session.calls += 1;
|
|
291
|
-
if (time > session.lastActive) session.lastActive = time;
|
|
292
351
|
const window = isPeak(time) ? peakSplit.peak : peakSplit.offPeak;
|
|
293
|
-
window.total +=
|
|
352
|
+
window.total += eventTotal;
|
|
294
353
|
window.cost += cost;
|
|
295
354
|
window.calls += 1;
|
|
296
355
|
peakSplit.peakEraCost += costUnderPeakEra(time, model, input, cache, output);
|
|
297
356
|
peakSplit.offPeakEraCost += costUnderPeakEra(time, model, input, cache, output, true);
|
|
298
|
-
overall.calls += 1;
|
|
299
357
|
});
|
|
358
|
+
coverage.usageRecords += scanned.usageRecords;
|
|
359
|
+
coverage.skippedRecords += scanned.skippedRecords;
|
|
300
360
|
if (session.calls > 0) {
|
|
301
|
-
|
|
361
|
+
const title = titleOf(events, sid);
|
|
362
|
+
session.title = title;
|
|
302
363
|
sessions.push(session);
|
|
364
|
+
for (let i = 0; i < windowAggregates.length; i++) {
|
|
365
|
+
const windowSession = windowSessions[i];
|
|
366
|
+
if (windowSession.calls === 0) continue;
|
|
367
|
+
windowSession.title = title;
|
|
368
|
+
windowAggregates[i].sessions.push(windowSession);
|
|
369
|
+
}
|
|
303
370
|
}
|
|
304
371
|
} catch {
|
|
372
|
+
coverage.failedSessions += 1;
|
|
305
373
|
}
|
|
306
374
|
}
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
const hourly = [];
|
|
315
|
-
for (let i = 0; i < 24; i++) {
|
|
316
|
-
const b = hourMap.get(String(i)) ?? emptyBucket();
|
|
317
|
-
hourly.push({ hour: i, total: b.total, cost: b.cost, calls: b.calls });
|
|
318
|
-
}
|
|
319
|
-
const heatmap = [];
|
|
320
|
-
for (let i = 83; i >= 0; i--) {
|
|
321
|
-
const d = new Date(Date.now() - i * 864e5);
|
|
322
|
-
const key = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
323
|
-
const b = dayMap.get(key) ?? emptyBucket();
|
|
324
|
-
heatmap.push({ date: key, total: b.total, cost: b.cost, calls: b.calls });
|
|
325
|
-
}
|
|
326
|
-
const models = [];
|
|
327
|
-
for (const [modelKey, bucket] of modelTotals) {
|
|
328
|
-
const slash = modelKey.indexOf("/");
|
|
329
|
-
const provider = slash < 0 ? "" : modelKey.slice(0, slash);
|
|
330
|
-
const model = slash < 0 ? modelKey : modelKey.slice(slash + 1);
|
|
331
|
-
const days = modelDays.get(modelKey);
|
|
332
|
-
const hours = modelHours.get(modelKey);
|
|
333
|
-
const daily2 = [];
|
|
334
|
-
for (let i = 29; i >= 0; i--) {
|
|
335
|
-
const d = new Date(Date.now() - i * 864e5);
|
|
336
|
-
const key = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
337
|
-
const b = days?.get(key) ?? emptyBucket();
|
|
338
|
-
daily2.push({ total: b.total, cost: b.cost, calls: b.calls });
|
|
375
|
+
const makeDaily = (map, count) => {
|
|
376
|
+
const result = [];
|
|
377
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
378
|
+
const key = dayKeyOf(dateBefore(i));
|
|
379
|
+
const b = map.get(key) ?? emptyBucket();
|
|
380
|
+
result.push({ date: key, input: b.input, output: b.output, cache: b.cache, total: b.total, cost: b.cost, calls: b.calls });
|
|
339
381
|
}
|
|
340
|
-
|
|
382
|
+
return result;
|
|
383
|
+
};
|
|
384
|
+
const makeHourly = (map) => {
|
|
385
|
+
const result = [];
|
|
341
386
|
for (let i = 0; i < 24; i++) {
|
|
342
|
-
const b =
|
|
343
|
-
|
|
387
|
+
const b = map.get(String(i)) ?? emptyBucket();
|
|
388
|
+
result.push({ hour: i, total: b.total, cost: b.cost, calls: b.calls });
|
|
344
389
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
390
|
+
return result;
|
|
391
|
+
};
|
|
392
|
+
const makeModels = (totals, daysByModel, hoursByModel, dayCount) => {
|
|
393
|
+
const result = [];
|
|
394
|
+
for (const [modelKey, bucket] of totals) {
|
|
395
|
+
const slash = modelKey.indexOf("/");
|
|
396
|
+
const provider = slash < 0 ? "" : modelKey.slice(0, slash);
|
|
397
|
+
const model = slash < 0 ? modelKey : modelKey.slice(slash + 1);
|
|
398
|
+
const days = daysByModel.get(modelKey);
|
|
399
|
+
const hours = hoursByModel.get(modelKey);
|
|
400
|
+
const daily2 = makeDaily(days ?? /* @__PURE__ */ new Map(), dayCount).map((point) => ({ total: point.total, cost: point.cost, calls: point.calls }));
|
|
401
|
+
const hourly2 = makeHourly(hours ?? /* @__PURE__ */ new Map()).map((point) => ({ total: point.total, cost: point.cost, calls: point.calls }));
|
|
402
|
+
result.push({
|
|
403
|
+
provider,
|
|
404
|
+
model,
|
|
405
|
+
input: bucket.input,
|
|
406
|
+
output: bucket.output,
|
|
407
|
+
cache: bucket.cache,
|
|
408
|
+
total: bucket.total,
|
|
409
|
+
cost: bucket.cost,
|
|
410
|
+
calls: bucket.calls,
|
|
411
|
+
daily: daily2,
|
|
412
|
+
hourly: hourly2
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
result.sort((a, b) => b.total - a.total);
|
|
416
|
+
return result;
|
|
417
|
+
};
|
|
418
|
+
const daily = makeDaily(dayMap, 30);
|
|
419
|
+
const hourly = makeHourly(hourMap);
|
|
420
|
+
const heatmap = [];
|
|
421
|
+
for (let i = 363; i >= 0; i--) {
|
|
422
|
+
const key = dayKeyOf(dateBefore(i));
|
|
423
|
+
const b = dayMap.get(key) ?? emptyBucket();
|
|
424
|
+
heatmap.push({ date: key, total: b.total, cost: b.cost, calls: b.calls });
|
|
357
425
|
}
|
|
358
|
-
models
|
|
426
|
+
const models = makeModels(modelTotals, modelDays, modelHours, 30);
|
|
427
|
+
const windows = windowAggregates.map((aggregate) => ({
|
|
428
|
+
days: aggregate.days,
|
|
429
|
+
daily: makeDaily(aggregate.dayMap, aggregate.days),
|
|
430
|
+
hourly: makeHourly(aggregate.hourMap),
|
|
431
|
+
totals: { ...aggregate.overall },
|
|
432
|
+
models: makeModels(aggregate.modelTotals, aggregate.modelDays, aggregate.modelHours, aggregate.days),
|
|
433
|
+
sessions: [...aggregate.sessions].sort((a, b) => b.cost - a.cost).slice(0, SESSION_TOP_N),
|
|
434
|
+
sessionCount: aggregate.sessions.length
|
|
435
|
+
}));
|
|
359
436
|
return {
|
|
360
437
|
ok: true,
|
|
361
438
|
data: {
|
|
@@ -363,11 +440,13 @@ async function fetchUsage(persistence) {
|
|
|
363
440
|
hourly,
|
|
364
441
|
heatmap,
|
|
365
442
|
models,
|
|
366
|
-
summary: summarize(dayMap,
|
|
367
|
-
pricing: pricingInfo(
|
|
443
|
+
summary: summarize(dayMap, new Date(nowMs)),
|
|
444
|
+
pricing: pricingInfo(nowMs),
|
|
368
445
|
peakSplit,
|
|
369
446
|
sessions: [...sessions].sort((a, b) => b.cost - a.cost).slice(0, SESSION_TOP_N),
|
|
370
447
|
sessionCount: sessions.length,
|
|
448
|
+
coverage,
|
|
449
|
+
windows,
|
|
371
450
|
totals: {
|
|
372
451
|
input: overall.input,
|
|
373
452
|
output: overall.output,
|
|
@@ -381,6 +460,63 @@ async function fetchUsage(persistence) {
|
|
|
381
460
|
}
|
|
382
461
|
};
|
|
383
462
|
}
|
|
463
|
+
async function fetchSessionUsage(persistence, sessionId) {
|
|
464
|
+
if (persistence === void 0) return { ok: false, error: "\u4F1A\u8BDD\u6301\u4E45\u5316\u670D\u52A1\u4E0D\u53EF\u7528" };
|
|
465
|
+
if (typeof sessionId !== "string" || sessionId === "") return { ok: false, error: "\u7F3A\u5C11\u4F1A\u8BDD id" };
|
|
466
|
+
if (!isValidSessionId(sessionId)) return { ok: false, error: "\u4F1A\u8BDD id \u683C\u5F0F\u4E0D\u5408\u6CD5" };
|
|
467
|
+
let events;
|
|
468
|
+
try {
|
|
469
|
+
const result = await persistence.readFrom(sessionId, 0);
|
|
470
|
+
events = result.events;
|
|
471
|
+
} catch (err) {
|
|
472
|
+
return { ok: false, error: `\u8BFB\u53D6\u4F1A\u8BDD\u65E5\u5FD7\u5931\u8D25\uFF1A${errorMessage(err)}` };
|
|
473
|
+
}
|
|
474
|
+
const modelTotals = /* @__PURE__ */ new Map();
|
|
475
|
+
let total = 0;
|
|
476
|
+
let cost = 0;
|
|
477
|
+
let calls = 0;
|
|
478
|
+
let firstActive = null;
|
|
479
|
+
let lastActive = null;
|
|
480
|
+
addUsageEvent(events, (time, input, output, cache, _reasoning, provider, model) => {
|
|
481
|
+
const modelKey = `${provider}/${model}`;
|
|
482
|
+
const eventCost = costOf(time, model, input, cache, output);
|
|
483
|
+
const eventTotal = input + output + cache;
|
|
484
|
+
bump(modelTotals, modelKey, input, output, cache, eventCost);
|
|
485
|
+
total += eventTotal;
|
|
486
|
+
cost += eventCost;
|
|
487
|
+
calls += 1;
|
|
488
|
+
if (firstActive === null || time < firstActive) firstActive = time;
|
|
489
|
+
if (lastActive === null || time > lastActive) lastActive = time;
|
|
490
|
+
});
|
|
491
|
+
const models = [...modelTotals.entries()].map(([modelKey, bucket]) => {
|
|
492
|
+
const slash = modelKey.indexOf("/");
|
|
493
|
+
const provider = slash < 0 ? "" : modelKey.slice(0, slash);
|
|
494
|
+
const model = slash < 0 ? modelKey : modelKey.slice(slash + 1);
|
|
495
|
+
return {
|
|
496
|
+
provider,
|
|
497
|
+
model,
|
|
498
|
+
input: bucket.input,
|
|
499
|
+
output: bucket.output,
|
|
500
|
+
cache: bucket.cache,
|
|
501
|
+
total: bucket.total,
|
|
502
|
+
cost: bucket.cost,
|
|
503
|
+
calls: bucket.calls
|
|
504
|
+
};
|
|
505
|
+
}).sort((a, b) => b.cost - a.cost);
|
|
506
|
+
return {
|
|
507
|
+
ok: true,
|
|
508
|
+
data: {
|
|
509
|
+
sessionId,
|
|
510
|
+
title: titleOf(events, sessionId),
|
|
511
|
+
total,
|
|
512
|
+
cost,
|
|
513
|
+
calls,
|
|
514
|
+
firstActive,
|
|
515
|
+
lastActive,
|
|
516
|
+
models
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
384
520
|
|
|
385
521
|
// src/wire.ts
|
|
386
522
|
function writeJson(res, status, body, headers) {
|
|
@@ -419,6 +555,15 @@ function apply(ctx) {
|
|
|
419
555
|
const payload = await (force ? usageMemo.refresh() : usageMemo.get());
|
|
420
556
|
const headers = payload.ok ? { "cache-control": `private, max-age=${Math.round(USAGE_TTL_MS / 1e3)}` } : { "cache-control": "no-store" };
|
|
421
557
|
writeJson(res, 200, payload, headers);
|
|
558
|
+
} else if (pathname === "/api/dsh-usage-dashboard/session") {
|
|
559
|
+
const id = url.searchParams.get("id");
|
|
560
|
+
if (id === null || id.trim() === "" || !isValidSessionId(id)) {
|
|
561
|
+
const error = id === null || id.trim() === "" ? "\u7F3A\u5C11\u4F1A\u8BDD id" : "\u4F1A\u8BDD id \u683C\u5F0F\u4E0D\u5408\u6CD5";
|
|
562
|
+
writeJson(res, 400, { ok: false, error }, { "cache-control": "no-store" });
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const payload = await fetchSessionUsage(ctx.sessionPersistence, id);
|
|
566
|
+
writeJson(res, 200, payload, { "cache-control": "no-store" });
|
|
422
567
|
} else {
|
|
423
568
|
writeJson(res, 404, { ok: false, error: "not found" });
|
|
424
569
|
}
|