@jingyi0605/codingns4dsh 0.2.1-rc.3 → 0.2.1-rc.4
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.en.md +3 -3
- package/README.md +3 -3
- package/data/build/dist/client/bundle.js +168 -47
- package/data/build/dist/client/bundle.js.map +1 -1
- package/data/build/dist/client/client.native-workspace-store.js +1 -1
- package/data/build/dist/client/client.native-workspace-store.js.map +1 -1
- package/data/build/dist/client/client.transport.js +32 -10
- package/data/build/dist/client/client.transport.js.map +1 -1
- package/data/build/dist/client/desktop-assistant.js +32 -10
- package/data/build/dist/client/features/magpie-management-controls.d.ts.map +1 -1
- package/data/build/dist/client/features/magpie-management-controls.js +45 -10
- package/data/build/dist/client/features/magpie-management-controls.js.map +1 -1
- package/data/build/dist/client/features/magpie-management-page.d.ts +6 -1
- package/data/build/dist/client/features/magpie-management-page.d.ts.map +1 -1
- package/data/build/dist/client/features/magpie-management-page.js +29 -6
- package/data/build/dist/client/features/magpie-management-page.js.map +1 -1
- package/data/build/dist/client/locales/magpie.d.ts.map +1 -1
- package/data/build/dist/client/locales/magpie.js +32 -10
- package/data/build/dist/client/locales/magpie.js.map +1 -1
- package/data/build/dist/client/subscription-slot.d.ts +3 -2
- package/data/build/dist/client/subscription-slot.d.ts.map +1 -1
- package/data/build/dist/client/subscription-slot.js +68 -22
- package/data/build/dist/client/subscription-slot.js.map +1 -1
- package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/feature.js +56 -8
- package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-detection.d.ts +3 -3
- package/data/build/dist/host/cli-adapters/magpie-detection.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-detection.js +18 -4
- package/data/build/dist/host/cli-adapters/magpie-detection.js.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-management.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-management.js +95 -20
- package/data/build/dist/host/cli-adapters/magpie-management.js.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-usage.d.ts +5 -1
- package/data/build/dist/host/cli-adapters/magpie-usage.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-usage.js +285 -59
- package/data/build/dist/host/cli-adapters/magpie-usage.js.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-web.d.ts +15 -0
- package/data/build/dist/host/cli-adapters/magpie-web.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-web.js +100 -22
- package/data/build/dist/host/cli-adapters/magpie-web.js.map +1 -1
- package/data/build/dist/host/cli-adapters/process-utils.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/process-utils.js +4 -0
- package/data/build/dist/host/cli-adapters/process-utils.js.map +1 -1
- package/data/build/dist/host/cli-adapters/session-performance-projection.d.ts +6 -0
- package/data/build/dist/host/cli-adapters/session-performance-projection.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/session-performance-projection.js +25 -0
- package/data/build/dist/host/cli-adapters/session-performance-projection.js.map +1 -1
- package/data/build/dist/shared/contracts/magpie-management.d.ts +19 -0
- package/data/build/dist/shared/contracts/magpie-management.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/subscription.d.ts +2 -0
- package/data/build/dist/shared/contracts/subscription.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/subscription.js.map +1 -1
- package/data/build/dist/shared/contracts/version.d.ts +1 -1
- package/data/build/dist/shared/contracts/version.js +1 -1
- package/data/build/dist/shared/index.d.ts +2 -1
- package/data/build/dist/shared/index.d.ts.map +1 -1
- package/data/build/dist/shared/index.js +1 -0
- package/data/build/dist/shared/index.js.map +1 -1
- package/data/build/dist/shared/magpie-provider-id.d.ts +12 -0
- package/data/build/dist/shared/magpie-provider-id.d.ts.map +1 -0
- package/data/build/dist/shared/magpie-provider-id.js +16 -0
- package/data/build/dist/shared/magpie-provider-id.js.map +1 -0
- package/package.json +1 -1
- package/profile/README.md +2 -2
- package/profile/package.json +2 -2
- package/profile/version.json +1 -1
- package/version.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { commandEnvironment, resolveCommandPath, runAsyncCommand, WINDOWS, withCommandSignal } from './process-utils.js';
|
|
3
|
+
import { parseMagpieWebProviders } from './magpie-management.js';
|
|
3
4
|
const MAGPIE_CATALOG_TTL_MS = 60_000;
|
|
4
5
|
const MAGPIE_MODEL_METADATA_TTL_MS = 60_000;
|
|
5
6
|
const MAGPIE_FAILURE_CACHE_TTL_MS = 5_000;
|
|
@@ -8,10 +9,13 @@ const MAGPIE_MINIMUM_CLI_VERSION = [0, 1, 1295];
|
|
|
8
9
|
const MAGPIE_CLI_TIMEOUT_MS = 10_000;
|
|
9
10
|
const MAGPIE_HTTP_TIMEOUT_MS = 3_000;
|
|
10
11
|
const MAGPIE_DASHBOARD_CACHE_TTL_MS = 15_000;
|
|
12
|
+
const MAGPIE_PROVIDER_CATALOG_TTL_MS = 15_000;
|
|
11
13
|
const MAX_MAGPIE_CLI_OUTPUT_BYTES = 512 * 1024;
|
|
12
14
|
const MAX_MAGPIE_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
13
15
|
const MAX_MAGPIE_ICON_FETCHES = 64;
|
|
14
16
|
const MAX_MAGPIE_MODELS = 10_000;
|
|
17
|
+
const MAX_MAGPIE_SESSION_SCAN_ROWS = 10_000;
|
|
18
|
+
const MAX_MAGPIE_SESSION_IDENTITIES = 16;
|
|
15
19
|
const MAX_MODEL_TOKEN_LIMIT = 1_000_000_000;
|
|
16
20
|
const MAGPIE_MODEL_ID_PATTERN = '[A-Za-z0-9][A-Za-z0-9._-]*(?:/[A-Za-z0-9][A-Za-z0-9._:/-]*)+';
|
|
17
21
|
const MAGPIE_AGENT_BY_ADAPTER = {
|
|
@@ -62,6 +66,7 @@ export class MagpieUsageService {
|
|
|
62
66
|
dashboardCache = new Map();
|
|
63
67
|
dashboardRefreshes = new Map();
|
|
64
68
|
quotaLogoCache = new Map();
|
|
69
|
+
providerCatalog;
|
|
65
70
|
constructor(options = {}) {
|
|
66
71
|
this.readModels = options.readModels ?? readMagpieModels;
|
|
67
72
|
// An injected catalog reader represents the whole fake CLI boundary in
|
|
@@ -305,45 +310,119 @@ export class MagpieUsageService {
|
|
|
305
310
|
|| !Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > 100)
|
|
306
311
|
return null;
|
|
307
312
|
const sessionId = options.sessionId === undefined ? null : cleanText(options.sessionId);
|
|
313
|
+
const sessionIds = options.sessionIds === undefined
|
|
314
|
+
? []
|
|
315
|
+
: options.sessionIds.map(cleanText);
|
|
308
316
|
const sessionAgent = options.sessionAgent === undefined ? null : cleanText(options.sessionAgent);
|
|
309
317
|
if ((options.sessionId !== undefined && sessionId === null)
|
|
318
|
+
|| sessionIds.some((value) => value === null)
|
|
319
|
+
|| sessionIds.length > MAX_MAGPIE_SESSION_IDENTITIES
|
|
310
320
|
|| (options.sessionAgent !== undefined && (sessionAgent === null || !isMagpieAgentId(sessionAgent)))
|
|
311
|
-
|| ((sessionId === null) !== (sessionAgent === null))
|
|
312
|
-
|
|
313
|
-
const query = new URLSearchParams({ period: magpieWebPeriod(options.period), limit: String(options.limit), offset: String(options.offset) });
|
|
314
|
-
if (sessionId !== null)
|
|
315
|
-
query.set('q', sessionId);
|
|
316
|
-
const response = await this.readWebJson(`/api/usage/requests?${query.toString()}`, signal);
|
|
317
|
-
if (!response.ok)
|
|
321
|
+
|| ((sessionId === null) !== (sessionAgent === null))
|
|
322
|
+
|| (sessionIds.length > 0 && sessionId === null))
|
|
318
323
|
return null;
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
324
|
+
const requestedSessionIds = sessionId === null
|
|
325
|
+
? []
|
|
326
|
+
: [...new Set([sessionId, ...sessionIds.filter((value) => value !== null)])];
|
|
327
|
+
if (requestedSessionIds.length > MAX_MAGPIE_SESSION_IDENTITIES)
|
|
322
328
|
return null;
|
|
323
|
-
const
|
|
329
|
+
const readCandidatePage = async (offset, limit, querySessionId) => {
|
|
330
|
+
const query = new URLSearchParams({ period: magpieWebPeriod(options.period), limit: String(limit), offset: String(offset) });
|
|
331
|
+
if (querySessionId !== null)
|
|
332
|
+
query.set('q', querySessionId);
|
|
333
|
+
const response = await this.readWebJson(`/api/usage/requests?${query.toString()}`, signal);
|
|
334
|
+
if (!response.ok)
|
|
335
|
+
return null;
|
|
336
|
+
const root = asRecord(response.value);
|
|
337
|
+
const rows = root?.rows;
|
|
338
|
+
if (!Array.isArray(rows) || rows.length > 500)
|
|
339
|
+
return null;
|
|
340
|
+
return { rows, total: safeCount(root?.total) };
|
|
341
|
+
};
|
|
342
|
+
if (sessionId === null) {
|
|
343
|
+
const page = await readCandidatePage(options.offset, options.limit, null);
|
|
344
|
+
if (page === null)
|
|
345
|
+
return null;
|
|
346
|
+
const providerRecords = page.rows.some((row) => routeText(asRecord(row)?.agent) === 'codex')
|
|
347
|
+
? await this.readProviderCatalog(signal)
|
|
348
|
+
: [];
|
|
349
|
+
const items = parseMagpieRequestRows(page.rows, null, null, providerRecords);
|
|
350
|
+
return {
|
|
351
|
+
period: options.period,
|
|
352
|
+
offset: options.offset,
|
|
353
|
+
limit: options.limit,
|
|
354
|
+
total: page.total,
|
|
355
|
+
hasMore: page.total === null ? page.rows.length >= options.limit : options.offset + page.rows.length < page.total,
|
|
356
|
+
sessionId,
|
|
357
|
+
sessionAgent,
|
|
358
|
+
items: items.slice(0, options.limit),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
// Magpie's `q` is a free-text candidate search, not an exact session filter.
|
|
362
|
+
// Fetch candidates from the beginning and apply the exact filter before paging;
|
|
363
|
+
// otherwise unrelated rows can consume the requested page and hide valid rows.
|
|
364
|
+
//
|
|
365
|
+
// Scanning stops one match past the requested page: enough to fill it and to prove
|
|
366
|
+
// whether another page exists. `total` therefore stays `null` while the identity's
|
|
367
|
+
// candidate window is not exhausted; the page that reads the window's end reports
|
|
368
|
+
// the exact count. Aggregated DSH sessions scan every contributing identity so one
|
|
369
|
+
// provider conversation cannot hide another.
|
|
370
|
+
const candidateLimit = 100;
|
|
371
|
+
const requestedEnd = options.offset + options.limit;
|
|
372
|
+
const rawRowsByKey = new Map();
|
|
373
|
+
let fullyScanned = true;
|
|
374
|
+
const perIdentityScanLimit = Math.floor(MAX_MAGPIE_SESSION_SCAN_ROWS / requestedSessionIds.length);
|
|
375
|
+
const acceptedAgents = MAGPIE_USAGE_AGENT_ALIASES[sessionAgent] ?? [sessionAgent];
|
|
376
|
+
const acceptedSessionIds = new Set(requestedSessionIds);
|
|
377
|
+
const matchesIn = () => [...rawRowsByKey.values()].reduce((count, row) => {
|
|
324
378
|
const record = asRecord(row);
|
|
325
379
|
if (record === null)
|
|
326
|
-
return
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
380
|
+
return count;
|
|
381
|
+
if (!acceptedSessionIds.has(cleanText(record.session) ?? '') && !acceptedSessionIds.has(cleanText(record.native_session) ?? ''))
|
|
382
|
+
return count;
|
|
383
|
+
return acceptedAgents.includes(cleanText(record.agent) ?? '') ? count + 1 : count;
|
|
384
|
+
}, 0);
|
|
385
|
+
for (const querySessionId of requestedSessionIds) {
|
|
386
|
+
let candidateOffset = 0;
|
|
387
|
+
let reachedEnd = false;
|
|
388
|
+
while (!reachedEnd && candidateOffset < perIdentityScanLimit) {
|
|
389
|
+
const pageLimit = Math.min(candidateLimit, perIdentityScanLimit - candidateOffset);
|
|
390
|
+
const page = await readCandidatePage(candidateOffset, pageLimit, querySessionId);
|
|
391
|
+
if (page === null)
|
|
392
|
+
return null;
|
|
393
|
+
for (const row of page.rows) {
|
|
394
|
+
const key = magpieRequestRowKey(row);
|
|
395
|
+
if (!rawRowsByKey.has(key))
|
|
396
|
+
rawRowsByKey.set(key, row);
|
|
397
|
+
}
|
|
398
|
+
const pageLength = page.rows.length;
|
|
399
|
+
const nextOffset = candidateOffset + pageLength;
|
|
400
|
+
reachedEnd = pageLength === 0
|
|
401
|
+
|| nextOffset <= candidateOffset
|
|
402
|
+
|| (page.total === null ? pageLength < pageLimit : nextOffset >= page.total);
|
|
403
|
+
candidateOffset = nextOffset;
|
|
404
|
+
// Enough rows for this page plus one more: no need to read the rest of the window.
|
|
405
|
+
if (!reachedEnd && matchesIn() > requestedEnd)
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
if (!reachedEnd)
|
|
409
|
+
fullyScanned = false;
|
|
410
|
+
}
|
|
411
|
+
const rawRows = [...rawRowsByKey.values()];
|
|
412
|
+
const providerRecords = rawRows.some((row) => routeText(asRecord(row)?.agent) === 'codex')
|
|
413
|
+
? await this.readProviderCatalog(signal)
|
|
414
|
+
: [];
|
|
415
|
+
const matching = parseMagpieRequestRows(rawRows, sessionId, sessionAgent, providerRecords, requestedSessionIds);
|
|
416
|
+
const total = fullyScanned ? matching.length : null;
|
|
338
417
|
return {
|
|
339
418
|
period: options.period,
|
|
340
419
|
offset: options.offset,
|
|
341
420
|
limit: options.limit,
|
|
342
421
|
total,
|
|
343
|
-
hasMore:
|
|
422
|
+
hasMore: !fullyScanned || matching.length > requestedEnd,
|
|
344
423
|
sessionId,
|
|
345
424
|
sessionAgent,
|
|
346
|
-
items:
|
|
425
|
+
items: matching.slice(options.offset, requestedEnd),
|
|
347
426
|
};
|
|
348
427
|
}
|
|
349
428
|
async readFromWeb(agent, modelId, sessionId, magpieSessionId, fallbackSessionId, signal, includeRoute) {
|
|
@@ -369,8 +448,12 @@ export class MagpieUsageService {
|
|
|
369
448
|
: { ok: false };
|
|
370
449
|
const routeRows = routeHistoryResult.ok ? asRecord(routeHistoryResult.value)?.rows : null;
|
|
371
450
|
const routeHistoryValid = Array.isArray(routeRows) && routeRows.length <= 500;
|
|
451
|
+
const providerRecords = agent === 'codex' && routeHistoryValid
|
|
452
|
+
&& routeRows.some((row) => routeText(asRecord(row)?.agent) === 'codex')
|
|
453
|
+
? await this.readProviderCatalog(signal)
|
|
454
|
+
: [];
|
|
372
455
|
const routeHistory = routeHistoryResult.ok && routeHistoryValid && resolvedSessionId !== null
|
|
373
|
-
? readSessionRouteHistory(routeHistoryResult.value, agent, resolvedSessionId)
|
|
456
|
+
? readSessionRouteHistory(routeHistoryResult.value, agent, resolvedSessionId, providerRecords)
|
|
374
457
|
: [];
|
|
375
458
|
const route = routeHistory[0]?.route ?? null;
|
|
376
459
|
const routeStatus = route !== null
|
|
@@ -407,6 +490,7 @@ export class MagpieUsageService {
|
|
|
407
490
|
usageStatus: aggregate === null ? 'unavailable' : 'available',
|
|
408
491
|
aggregate,
|
|
409
492
|
sessionId,
|
|
493
|
+
...(resolvedSessionId === null ? {} : { requestSessionId: resolvedSessionId }),
|
|
410
494
|
session: sessionUsage,
|
|
411
495
|
routeStatus,
|
|
412
496
|
route,
|
|
@@ -591,6 +675,18 @@ export class MagpieUsageService {
|
|
|
591
675
|
return { ok: false };
|
|
592
676
|
}
|
|
593
677
|
}
|
|
678
|
+
/** Provider management data is the canonical source when a usage row mislabels its upstream. */
|
|
679
|
+
async readProviderCatalog(signal) {
|
|
680
|
+
const cached = this.providerCatalog;
|
|
681
|
+
if (cached !== undefined && this.now() - cached.loadedAt < MAGPIE_PROVIDER_CATALOG_TTL_MS)
|
|
682
|
+
return cached.records;
|
|
683
|
+
const response = await this.readWebJson('/api/providers', signal);
|
|
684
|
+
const parsed = response.ok ? parseMagpieWebProviders(response.value) : null;
|
|
685
|
+
const records = parsed?.records ?? [];
|
|
686
|
+
if (parsed !== null)
|
|
687
|
+
this.providerCatalog = { records, loadedAt: this.now() };
|
|
688
|
+
return records;
|
|
689
|
+
}
|
|
594
690
|
}
|
|
595
691
|
function awaitWithSignal(task, signal) {
|
|
596
692
|
if (signal === undefined)
|
|
@@ -864,53 +960,124 @@ export function magpieUsageAgentAliases(adapterId) {
|
|
|
864
960
|
* native DSH use different identities across Magpie versions.
|
|
865
961
|
*/
|
|
866
962
|
export function attachMagpieSessionNames(dashboard, bindings) {
|
|
867
|
-
if (!Array.isArray(dashboard.sessions)
|
|
963
|
+
if (!Array.isArray(dashboard.sessions))
|
|
868
964
|
return dashboard;
|
|
869
|
-
const
|
|
965
|
+
const owners = new Map();
|
|
870
966
|
const ambiguous = new Set();
|
|
967
|
+
const ownerByGroup = new Map();
|
|
871
968
|
for (const binding of bindings) {
|
|
872
|
-
const name = cleanSessionName(binding.name);
|
|
873
969
|
const dshSessionId = cleanText(binding.dshSessionId);
|
|
874
|
-
|
|
970
|
+
const name = cleanSessionName(binding.name);
|
|
971
|
+
if (dshSessionId === null)
|
|
875
972
|
continue;
|
|
876
|
-
//
|
|
877
|
-
// DSH ID
|
|
878
|
-
// adapters use only their Provider session ID.
|
|
973
|
+
// Magpie versions may record either the provider conversation ID or the
|
|
974
|
+
// DSH ID; both exact identities represent the same DSH session.
|
|
879
975
|
const providerSessionId = cleanText(binding.providerSessionId);
|
|
880
|
-
const ids = new Set((
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
976
|
+
const ids = new Set([dshSessionId, providerSessionId].filter((value) => value !== null));
|
|
977
|
+
const groupKey = `dsh\u0000${dshSessionId}`;
|
|
978
|
+
const owner = ownerByGroup.get(groupKey) ?? {
|
|
979
|
+
groupKey,
|
|
980
|
+
dshSessionId,
|
|
981
|
+
agent: magpieAgentForAdapter(binding.adapterId) ?? binding.adapterId,
|
|
982
|
+
name,
|
|
983
|
+
ids: [...new Set(ids)],
|
|
984
|
+
};
|
|
985
|
+
if (ownerByGroup.has(groupKey)) {
|
|
986
|
+
ownerByGroup.set(groupKey, {
|
|
987
|
+
...owner,
|
|
988
|
+
name: owner.name === null || name === null || owner.name === name ? owner.name ?? name : null,
|
|
989
|
+
ids: [...new Set([...owner.ids, ...ids])],
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
else {
|
|
993
|
+
ownerByGroup.set(groupKey, owner);
|
|
994
|
+
}
|
|
995
|
+
const currentOwner = ownerByGroup.get(groupKey);
|
|
885
996
|
for (const agent of magpieUsageAgentAliases(binding.adapterId)) {
|
|
886
997
|
for (const id of ids) {
|
|
887
998
|
const key = `${agent}\u0000${id}`;
|
|
888
999
|
if (ambiguous.has(key))
|
|
889
1000
|
continue;
|
|
890
|
-
const previous =
|
|
891
|
-
if (previous !== undefined && previous !==
|
|
892
|
-
|
|
1001
|
+
const previous = owners.get(key);
|
|
1002
|
+
if (previous !== undefined && previous.groupKey !== groupKey) {
|
|
1003
|
+
owners.delete(key);
|
|
893
1004
|
ambiguous.add(key);
|
|
894
1005
|
}
|
|
895
1006
|
else {
|
|
896
|
-
|
|
1007
|
+
owners.set(key, currentOwner);
|
|
897
1008
|
}
|
|
898
1009
|
}
|
|
899
1010
|
}
|
|
900
1011
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1012
|
+
for (const [key, owner] of owners) {
|
|
1013
|
+
const currentOwner = ownerByGroup.get(owner.groupKey);
|
|
1014
|
+
if (currentOwner !== undefined)
|
|
1015
|
+
owners.set(key, currentOwner);
|
|
1016
|
+
}
|
|
1017
|
+
const grouped = new Map();
|
|
1018
|
+
for (const row of dashboard.sessions) {
|
|
1019
|
+
const aliasKey = `${row.agent}\u0000${row.id}`;
|
|
1020
|
+
const owner = ambiguous.has(aliasKey) ? undefined : owners.get(aliasKey);
|
|
1021
|
+
const groupKey = owner?.groupKey ?? `raw\u0000${row.agent}\u0000${row.id}`;
|
|
1022
|
+
const current = grouped.get(groupKey);
|
|
1023
|
+
if (current === undefined) {
|
|
1024
|
+
const ids = new Set(owner?.ids ?? [row.id]);
|
|
1025
|
+
ids.add(row.id);
|
|
1026
|
+
const session = owner === undefined
|
|
1027
|
+
? row
|
|
1028
|
+
: {
|
|
1029
|
+
...row,
|
|
1030
|
+
id: owner.dshSessionId,
|
|
1031
|
+
agent: owner.agent,
|
|
1032
|
+
...(owner.name === null ? {} : { dshSessionName: owner.name }),
|
|
1033
|
+
sessionIds: [...ids],
|
|
1034
|
+
};
|
|
1035
|
+
grouped.set(groupKey, { session, ids });
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
for (const id of owner?.ids ?? [row.id])
|
|
1039
|
+
current.ids.add(id);
|
|
1040
|
+
current.ids.add(row.id);
|
|
1041
|
+
current.session = mergeMagpieUsageSessions(current.session, row, owner);
|
|
1042
|
+
}
|
|
1043
|
+
const sessions = [...grouped.values()].map(({ session, ids }) => {
|
|
1044
|
+
if (ids.size <= 1 && session.sessionIds === undefined)
|
|
909
1045
|
return session;
|
|
910
|
-
|
|
911
|
-
return { ...session, dshSessionName: name };
|
|
1046
|
+
return { ...session, sessionIds: [...ids] };
|
|
912
1047
|
});
|
|
913
|
-
|
|
1048
|
+
if (sessions.length === dashboard.sessions.length && sessions.every((session, index) => session === dashboard.sessions[index]))
|
|
1049
|
+
return dashboard;
|
|
1050
|
+
return { ...dashboard, sessions };
|
|
1051
|
+
}
|
|
1052
|
+
function mergeMagpieUsageSessions(current, row, owner) {
|
|
1053
|
+
return {
|
|
1054
|
+
...current,
|
|
1055
|
+
id: owner?.dshSessionId ?? current.id,
|
|
1056
|
+
agent: owner?.agent ?? current.agent,
|
|
1057
|
+
calls: sumMagpieUsageMetric(current.calls, row.calls),
|
|
1058
|
+
errors: sumMagpieUsageMetric(current.errors, row.errors),
|
|
1059
|
+
retried: sumMagpieUsageMetric(current.retried, row.retried),
|
|
1060
|
+
inputTokens: sumMagpieUsageMetric(current.inputTokens, row.inputTokens),
|
|
1061
|
+
outputTokens: sumMagpieUsageMetric(current.outputTokens, row.outputTokens),
|
|
1062
|
+
cacheReadTokens: sumMagpieUsageMetric(current.cacheReadTokens, row.cacheReadTokens),
|
|
1063
|
+
cacheWriteTokens: sumMagpieUsageMetric(current.cacheWriteTokens, row.cacheWriteTokens),
|
|
1064
|
+
reasoningTokens: sumMagpieUsageMetric(current.reasoningTokens, row.reasoningTokens),
|
|
1065
|
+
cost: sumMagpieUsageMetric(current.cost, row.cost),
|
|
1066
|
+
unpriced: sumMagpieUsageMetric(current.unpriced, row.unpriced),
|
|
1067
|
+
credits: sumMagpieUsageMetric(current.credits, row.credits),
|
|
1068
|
+
credited: sumMagpieUsageMetric(current.credited, row.credited),
|
|
1069
|
+
timed: sumMagpieUsageMetric(current.timed, row.timed),
|
|
1070
|
+
ttftMs: sumMagpieUsageMetric(current.ttftMs, row.ttftMs),
|
|
1071
|
+
decodeMs: sumMagpieUsageMetric(current.decodeMs, row.decodeMs),
|
|
1072
|
+
decodeTokens: sumMagpieUsageMetric(current.decodeTokens, row.decodeTokens),
|
|
1073
|
+
decodeCount: sumMagpieUsageMetric(current.decodeCount, row.decodeCount),
|
|
1074
|
+
...(current.dshSessionName === undefined && owner?.name !== null && owner?.name !== undefined
|
|
1075
|
+
? { dshSessionName: owner.name }
|
|
1076
|
+
: {}),
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
function sumMagpieUsageMetric(left, right) {
|
|
1080
|
+
return left === null && right === null ? null : (left ?? 0) + (right ?? 0);
|
|
914
1081
|
}
|
|
915
1082
|
function isMagpieAgentId(value) {
|
|
916
1083
|
return value.length <= 64 && /^[a-z0-9][a-z0-9-]*$/u.test(value);
|
|
@@ -999,7 +1166,7 @@ function readSession(value, agent, sessionId) {
|
|
|
999
1166
|
return record !== null && usageAgent !== null && totals !== null ? { id: sessionId, agent: usageAgent, ...totals } : null;
|
|
1000
1167
|
}
|
|
1001
1168
|
/** Read the durable request ledger when Magpie's short in-memory trace has expired. */
|
|
1002
|
-
function readSessionRouteHistory(value, agent, sessionId) {
|
|
1169
|
+
function readSessionRouteHistory(value, agent, sessionId, providerRecords = []) {
|
|
1003
1170
|
const rows = asRecord(value)?.rows;
|
|
1004
1171
|
if (!Array.isArray(rows) || rows.length > 500)
|
|
1005
1172
|
return [];
|
|
@@ -1009,9 +1176,11 @@ function readSessionRouteHistory(value, agent, sessionId) {
|
|
|
1009
1176
|
const rowAgent = cleanText(record?.agent);
|
|
1010
1177
|
const session = cleanText(record?.session);
|
|
1011
1178
|
const nativeSession = cleanText(record?.native_session);
|
|
1012
|
-
const provider = routeText(record?.provider);
|
|
1013
1179
|
if (record === null || rowAgent === null || !usageAgents.includes(rowAgent)
|
|
1014
|
-
|| (session !== sessionId && nativeSession !== sessionId)
|
|
1180
|
+
|| (session !== sessionId && nativeSession !== sessionId))
|
|
1181
|
+
return [];
|
|
1182
|
+
const provider = normalizeMagpieRequestProvider(record, providerRecords);
|
|
1183
|
+
if (provider === null)
|
|
1015
1184
|
return [];
|
|
1016
1185
|
const requested = routeText(record.req);
|
|
1017
1186
|
const model = routeText(record.model);
|
|
@@ -1393,9 +1562,32 @@ function readMagpieUsageSessions(value) {
|
|
|
1393
1562
|
return record === null || id === null || agent === null ? [] : [{ id, agent, ...readMagpieUsageMetrics(record) }];
|
|
1394
1563
|
});
|
|
1395
1564
|
}
|
|
1396
|
-
function
|
|
1565
|
+
function parseMagpieRequestRows(rows, sessionId, sessionAgent, providerRecords = [], sessionIds = sessionId === null ? [] : [sessionId]) {
|
|
1566
|
+
const acceptedAgents = sessionAgent === null ? null : MAGPIE_USAGE_AGENT_ALIASES[sessionAgent] ?? [sessionAgent];
|
|
1567
|
+
const acceptedSessionIds = new Set(sessionIds);
|
|
1568
|
+
return rows.flatMap((row) => {
|
|
1569
|
+
const record = asRecord(row);
|
|
1570
|
+
if (record === null)
|
|
1571
|
+
return [];
|
|
1572
|
+
const candidateSession = cleanText(record.session);
|
|
1573
|
+
const nativeSession = cleanText(record.native_session);
|
|
1574
|
+
const agent = cleanText(record.agent);
|
|
1575
|
+
if (sessionId !== null
|
|
1576
|
+
&& (!acceptedSessionIds.has(candidateSession ?? '') && !acceptedSessionIds.has(nativeSession ?? '')
|
|
1577
|
+
|| agent === null || acceptedAgents === null || !acceptedAgents.includes(agent)))
|
|
1578
|
+
return [];
|
|
1579
|
+
const item = parseMagpieRequest(record, providerRecords);
|
|
1580
|
+
return item === null ? [] : [sessionId === null ? item : { ...item, sessionId }];
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
function magpieRequestRowKey(value) {
|
|
1584
|
+
const record = asRecord(value);
|
|
1585
|
+
const routeId = safeRouteId(record?.route_id);
|
|
1586
|
+
return routeId === null ? JSON.stringify(record) : `route:${routeId}`;
|
|
1587
|
+
}
|
|
1588
|
+
function parseMagpieRequest(record, providerRecords = []) {
|
|
1397
1589
|
const agent = routeText(record.agent);
|
|
1398
|
-
const provider =
|
|
1590
|
+
const provider = normalizeMagpieRequestProvider(record, providerRecords);
|
|
1399
1591
|
const requestedModel = routeText(record.req);
|
|
1400
1592
|
const servedModel = routeText(record.served);
|
|
1401
1593
|
const group = requestedModel?.startsWith('group/') === true ? requestedModel.slice('group/'.length) : null;
|
|
@@ -1404,7 +1596,7 @@ function parseMagpieRequest(record) {
|
|
|
1404
1596
|
time: routeText(record.t),
|
|
1405
1597
|
agent,
|
|
1406
1598
|
provider,
|
|
1407
|
-
account:
|
|
1599
|
+
account: normalizeMagpieRequestAccount(record, provider, providerRecords),
|
|
1408
1600
|
requestedModel,
|
|
1409
1601
|
servedModel,
|
|
1410
1602
|
group,
|
|
@@ -1425,6 +1617,40 @@ function parseMagpieRequest(record) {
|
|
|
1425
1617
|
sessionId: cleanText(record.session) ?? cleanText(record.native_session),
|
|
1426
1618
|
};
|
|
1427
1619
|
}
|
|
1620
|
+
/** Session-filtered rows can omit the account label; use only the matching provider's safe catalog label. */
|
|
1621
|
+
function normalizeMagpieRequestAccount(record, provider, providerRecords) {
|
|
1622
|
+
const account = routeText(record.providerAccount);
|
|
1623
|
+
if (account !== null || provider === null)
|
|
1624
|
+
return account;
|
|
1625
|
+
const matches = providerRecords.filter((item) => item.id === provider);
|
|
1626
|
+
return matches.length === 1 ? matches[0].account : null;
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Magpie's usage ledger can retain a stale provider id after an Agent provider
|
|
1630
|
+
* is renamed. Reconcile only Codex rows against the read-only provider catalog;
|
|
1631
|
+
* arbitrary provider ids must remain untouched when the evidence is ambiguous.
|
|
1632
|
+
*/
|
|
1633
|
+
function normalizeMagpieRequestProvider(record, providerRecords) {
|
|
1634
|
+
const provider = routeText(record.provider);
|
|
1635
|
+
if (provider === null || routeText(record.agent)?.toLocaleLowerCase() !== 'codex')
|
|
1636
|
+
return provider;
|
|
1637
|
+
const codexProviders = providerRecords.filter((item) => item.accountAgent?.toLocaleLowerCase() === 'codex');
|
|
1638
|
+
if (codexProviders.length === 0)
|
|
1639
|
+
return provider;
|
|
1640
|
+
const modelValues = [record.model, record.served, record.req]
|
|
1641
|
+
.map(routeText)
|
|
1642
|
+
.filter((value) => value !== null);
|
|
1643
|
+
const matches = codexProviders.filter((candidate) => candidate.models.some((model) => modelValues.some((value) => magpieModelMatchesProvider(value, model.id, candidate.id))));
|
|
1644
|
+
if (matches.length === 1)
|
|
1645
|
+
return matches[0].id;
|
|
1646
|
+
return codexProviders.length === 1 ? codexProviders[0].id : provider;
|
|
1647
|
+
}
|
|
1648
|
+
function magpieModelMatchesProvider(value, modelId, providerId) {
|
|
1649
|
+
if (value === modelId)
|
|
1650
|
+
return true;
|
|
1651
|
+
const prefix = `${providerId}/`;
|
|
1652
|
+
return modelId.startsWith(prefix) && value === modelId.slice(prefix.length);
|
|
1653
|
+
}
|
|
1428
1654
|
function firstRouteText(record, keys) {
|
|
1429
1655
|
for (const key of keys) {
|
|
1430
1656
|
const value = routeText(record[key]);
|