@jingyi0605/codingns4dsh 0.2.1-rc.3 → 0.2.1-rc.5
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 +1838 -223
- package/data/build/dist/client/bundle.js.map +1 -1
- package/data/build/dist/client/cli-session-config-cache.d.ts.map +1 -1
- package/data/build/dist/client/cli-session-config-cache.js +9 -3
- package/data/build/dist/client/cli-session-config-cache.js.map +1 -1
- package/data/build/dist/client/client.native-workspace-store.js +7 -1
- package/data/build/dist/client/client.native-workspace-store.js.map +1 -1
- package/data/build/dist/client/client.transport.js +276 -60
- package/data/build/dist/client/client.transport.js.map +1 -1
- package/data/build/dist/client/desktop-assistant.js +274 -58
- package/data/build/dist/client/features/cli-adapters.d.ts.map +1 -1
- package/data/build/dist/client/features/cli-adapters.js +13 -1
- package/data/build/dist/client/features/cli-adapters.js.map +1 -1
- package/data/build/dist/client/features/index.d.ts.map +1 -1
- package/data/build/dist/client/features/index.js +2 -0
- package/data/build/dist/client/features/index.js.map +1 -1
- 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 +109 -37
- package/data/build/dist/client/features/magpie-management-page.js.map +1 -1
- package/data/build/dist/client/features/magpie-management.d.ts.map +1 -1
- package/data/build/dist/client/features/magpie-management.js +3 -2
- package/data/build/dist/client/features/magpie-management.js.map +1 -1
- package/data/build/dist/client/features/plugin-update.d.ts +3 -0
- package/data/build/dist/client/features/plugin-update.d.ts.map +1 -0
- package/data/build/dist/client/features/plugin-update.js +8 -0
- package/data/build/dist/client/features/plugin-update.js.map +1 -0
- package/data/build/dist/client/features/types.d.ts +3 -0
- package/data/build/dist/client/features/types.d.ts.map +1 -1
- package/data/build/dist/client/locale.d.ts.map +1 -1
- package/data/build/dist/client/locale.js +3 -0
- package/data/build/dist/client/locale.js.map +1 -1
- package/data/build/dist/client/locales/magpie.d.ts.map +1 -1
- package/data/build/dist/client/locales/magpie.js +92 -12
- package/data/build/dist/client/locales/magpie.js.map +1 -1
- package/data/build/dist/client/locales/pluginUpdate.d.ts +3 -0
- package/data/build/dist/client/locales/pluginUpdate.d.ts.map +1 -0
- package/data/build/dist/client/locales/pluginUpdate.js +131 -0
- package/data/build/dist/client/locales/pluginUpdate.js.map +1 -0
- package/data/build/dist/client/locales/usage.d.ts.map +1 -1
- package/data/build/dist/client/locales/usage.js +2 -0
- package/data/build/dist/client/locales/usage.js.map +1 -1
- package/data/build/dist/client/magpie-context-meter.d.ts +18 -0
- package/data/build/dist/client/magpie-context-meter.d.ts.map +1 -0
- package/data/build/dist/client/magpie-context-meter.js +274 -0
- package/data/build/dist/client/magpie-context-meter.js.map +1 -0
- package/data/build/dist/client/magpie-context-store.d.ts +32 -0
- package/data/build/dist/client/magpie-context-store.d.ts.map +1 -0
- package/data/build/dist/client/magpie-context-store.js +165 -0
- package/data/build/dist/client/magpie-context-store.js.map +1 -0
- package/data/build/dist/client/settings-promo-styles.d.ts +5 -0
- package/data/build/dist/client/settings-promo-styles.d.ts.map +1 -1
- package/data/build/dist/client/settings-promo-styles.js +15 -2
- package/data/build/dist/client/settings-promo-styles.js.map +1 -1
- package/data/build/dist/client/settings-promo.d.ts +4 -2
- package/data/build/dist/client/settings-promo.d.ts.map +1 -1
- package/data/build/dist/client/settings-promo.js +4 -4
- package/data/build/dist/client/settings-promo.js.map +1 -1
- package/data/build/dist/client/settings-section.d.ts +2 -3
- package/data/build/dist/client/settings-section.d.ts.map +1 -1
- package/data/build/dist/client/settings-section.js +7 -7
- package/data/build/dist/client/settings-section.js.map +1 -1
- package/data/build/dist/client/settings-update.d.ts +8 -0
- package/data/build/dist/client/settings-update.d.ts.map +1 -0
- package/data/build/dist/client/settings-update.js +57 -0
- package/data/build/dist/client/settings-update.js.map +1 -0
- 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 +115 -43
- package/data/build/dist/client/subscription-slot.js.map +1 -1
- package/data/build/dist/client/update-panel-styles.d.ts +29 -0
- package/data/build/dist/client/update-panel-styles.d.ts.map +1 -0
- package/data/build/dist/client/update-panel-styles.js +69 -0
- package/data/build/dist/client/update-panel-styles.js.map +1 -0
- package/data/build/dist/client/update-panel.d.ts +7 -0
- package/data/build/dist/client/update-panel.d.ts.map +1 -0
- package/data/build/dist/client/update-panel.js +52 -0
- package/data/build/dist/client/update-panel.js.map +1 -0
- package/data/build/dist/client/update-store.d.ts +43 -0
- package/data/build/dist/client/update-store.d.ts.map +1 -0
- package/data/build/dist/client/update-store.js +207 -0
- package/data/build/dist/client/update-store.js.map +1 -0
- package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.d.ts +4 -0
- package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.d.ts.map +1 -0
- package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.js +6 -0
- package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.js.map +1 -0
- package/data/build/dist/dsh-capabilities/client/session-performance-adapter.d.ts +15 -1
- package/data/build/dist/dsh-capabilities/client/session-performance-adapter.d.ts.map +1 -1
- package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js +53 -21
- package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js.map +1 -1
- package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts +22 -0
- package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts.map +1 -0
- package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js +71 -0
- package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js.map +1 -0
- package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts +4 -0
- package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts.map +1 -1
- package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js +16 -1
- package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js.map +1 -1
- package/data/build/dist/dsh-capabilities/matrix.d.ts.map +1 -1
- package/data/build/dist/dsh-capabilities/matrix.js +4 -0
- package/data/build/dist/dsh-capabilities/matrix.js.map +1 -1
- package/data/build/dist/dsh-capabilities/routes.d.ts.map +1 -1
- package/data/build/dist/dsh-capabilities/routes.js +10 -0
- package/data/build/dist/dsh-capabilities/routes.js.map +1 -1
- package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
- package/data/build/dist/dsh-capabilities/types.d.ts.map +1 -1
- package/data/build/dist/dsh-capabilities/types.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 +230 -34
- package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-context.d.ts +9 -0
- package/data/build/dist/host/cli-adapters/magpie-context.d.ts.map +1 -0
- package/data/build/dist/host/cli-adapters/magpie-context.js +128 -0
- package/data/build/dist/host/cli-adapters/magpie-context.js.map +1 -0
- 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-update.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-update.js +14 -4
- package/data/build/dist/host/cli-adapters/magpie-update.js.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-usage.d.ts +17 -3
- package/data/build/dist/host/cli-adapters/magpie-usage.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/magpie-usage.js +382 -92
- 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/registry.d.ts.map +1 -1
- package/data/build/dist/host/cli-adapters/registry.js +6 -5
- package/data/build/dist/host/cli-adapters/registry.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/host/features/index.d.ts.map +1 -1
- package/data/build/dist/host/features/index.js +2 -0
- package/data/build/dist/host/features/index.js.map +1 -1
- package/data/build/dist/host/features/plugin-update.d.ts +6 -0
- package/data/build/dist/host/features/plugin-update.d.ts.map +1 -0
- package/data/build/dist/host/features/plugin-update.js +45 -0
- package/data/build/dist/host/features/plugin-update.js.map +1 -0
- package/data/build/dist/host/index.d.ts.map +1 -1
- package/data/build/dist/host/index.js +31 -1
- package/data/build/dist/host/index.js.map +1 -1
- package/data/build/dist/host/native-session-bridge.d.ts +8 -0
- package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
- package/data/build/dist/host/native-session-bridge.js +43 -0
- package/data/build/dist/host/native-session-bridge.js.map +1 -1
- package/data/build/dist/host/rpc.d.ts +4 -1
- package/data/build/dist/host/rpc.d.ts.map +1 -1
- package/data/build/dist/host/rpc.js +33 -7
- package/data/build/dist/host/rpc.js.map +1 -1
- package/data/build/dist/host/session-handle-eviction.d.ts +29 -0
- package/data/build/dist/host/session-handle-eviction.d.ts.map +1 -0
- package/data/build/dist/host/session-handle-eviction.js +413 -0
- package/data/build/dist/host/session-handle-eviction.js.map +1 -0
- package/data/build/dist/host/settings.d.ts.map +1 -1
- package/data/build/dist/host/settings.js +12 -1
- package/data/build/dist/host/settings.js.map +1 -1
- package/data/build/dist/host/update/application-result.d.ts +3 -0
- package/data/build/dist/host/update/application-result.d.ts.map +1 -0
- package/data/build/dist/host/update/application-result.js +13 -0
- package/data/build/dist/host/update/application-result.js.map +1 -0
- package/data/build/dist/host/update/http.d.ts +7 -0
- package/data/build/dist/host/update/http.d.ts.map +1 -0
- package/data/build/dist/host/update/http.js +55 -0
- package/data/build/dist/host/update/http.js.map +1 -0
- package/data/build/dist/host/update/metadata.d.ts +14 -0
- package/data/build/dist/host/update/metadata.d.ts.map +1 -0
- package/data/build/dist/host/update/metadata.js +75 -0
- package/data/build/dist/host/update/metadata.js.map +1 -0
- package/data/build/dist/host/update/release-notes.d.ts +6 -0
- package/data/build/dist/host/update/release-notes.d.ts.map +1 -0
- package/data/build/dist/host/update/release-notes.js +52 -0
- package/data/build/dist/host/update/release-notes.js.map +1 -0
- package/data/build/dist/host/update/service.d.ts +25 -0
- package/data/build/dist/host/update/service.d.ts.map +1 -0
- package/data/build/dist/host/update/service.js +209 -0
- package/data/build/dist/host/update/service.js.map +1 -0
- package/data/build/dist/shared/contracts/config.d.ts +17 -0
- package/data/build/dist/shared/contracts/config.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/config.js +8 -0
- package/data/build/dist/shared/contracts/config.js.map +1 -1
- package/data/build/dist/shared/contracts/magpie-management.d.ts +59 -0
- package/data/build/dist/shared/contracts/magpie-management.d.ts.map +1 -1
- package/data/build/dist/shared/contracts/plugin-update.d.ts +93 -0
- package/data/build/dist/shared/contracts/plugin-update.d.ts.map +1 -0
- package/data/build/dist/shared/contracts/plugin-update.js +128 -0
- package/data/build/dist/shared/contracts/plugin-update.js.map +1 -0
- package/data/build/dist/shared/contracts/subscription.d.ts +5 -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 +2 -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,7 @@
|
|
|
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';
|
|
4
|
+
import { parseMagpieContextResponse } from './magpie-context.js';
|
|
3
5
|
const MAGPIE_CATALOG_TTL_MS = 60_000;
|
|
4
6
|
const MAGPIE_MODEL_METADATA_TTL_MS = 60_000;
|
|
5
7
|
const MAGPIE_FAILURE_CACHE_TTL_MS = 5_000;
|
|
@@ -8,10 +10,13 @@ const MAGPIE_MINIMUM_CLI_VERSION = [0, 1, 1295];
|
|
|
8
10
|
const MAGPIE_CLI_TIMEOUT_MS = 10_000;
|
|
9
11
|
const MAGPIE_HTTP_TIMEOUT_MS = 3_000;
|
|
10
12
|
const MAGPIE_DASHBOARD_CACHE_TTL_MS = 15_000;
|
|
13
|
+
const MAGPIE_PROVIDER_CATALOG_TTL_MS = 15_000;
|
|
11
14
|
const MAX_MAGPIE_CLI_OUTPUT_BYTES = 512 * 1024;
|
|
12
15
|
const MAX_MAGPIE_RESPONSE_BYTES = 2 * 1024 * 1024;
|
|
13
16
|
const MAX_MAGPIE_ICON_FETCHES = 64;
|
|
14
17
|
const MAX_MAGPIE_MODELS = 10_000;
|
|
18
|
+
const MAX_MAGPIE_SESSION_SCAN_ROWS = 10_000;
|
|
19
|
+
const MAX_MAGPIE_SESSION_IDENTITIES = 16;
|
|
15
20
|
const MAX_MODEL_TOKEN_LIMIT = 1_000_000_000;
|
|
16
21
|
const MAGPIE_MODEL_ID_PATTERN = '[A-Za-z0-9][A-Za-z0-9._-]*(?:/[A-Za-z0-9][A-Za-z0-9._:/-]*)+';
|
|
17
22
|
const MAGPIE_AGENT_BY_ADAPTER = {
|
|
@@ -62,6 +67,7 @@ export class MagpieUsageService {
|
|
|
62
67
|
dashboardCache = new Map();
|
|
63
68
|
dashboardRefreshes = new Map();
|
|
64
69
|
quotaLogoCache = new Map();
|
|
70
|
+
providerCatalog;
|
|
65
71
|
constructor(options = {}) {
|
|
66
72
|
this.readModels = options.readModels ?? readMagpieModels;
|
|
67
73
|
// An injected catalog reader represents the whole fake CLI boundary in
|
|
@@ -76,7 +82,7 @@ export class MagpieUsageService {
|
|
|
76
82
|
this.webApi = options.webApi;
|
|
77
83
|
this.onDashboardTiming = options.onDashboardTiming;
|
|
78
84
|
}
|
|
79
|
-
async read(session, signal, includeRoute = false, dshSessionId) {
|
|
85
|
+
async read(session, signal, includeRoute = false, dshSessionId, options = {}) {
|
|
80
86
|
signal?.throwIfAborted();
|
|
81
87
|
const agent = magpieAgentForAdapter(session.adapterId);
|
|
82
88
|
const requestedModelId = cleanText(session.modelId);
|
|
@@ -84,24 +90,25 @@ export class MagpieUsageService {
|
|
|
84
90
|
return null;
|
|
85
91
|
const providerSessionId = cleanText(session.providerSessionId);
|
|
86
92
|
const nativeSessionId = cleanText(dshSessionId);
|
|
87
|
-
// Native DSH requests
|
|
88
|
-
//
|
|
89
|
-
|
|
93
|
+
// Native DSH requests are tagged with the DSH session ID. Keep the older
|
|
94
|
+
// provider-session fallback for callers that do not have the native ID yet,
|
|
95
|
+
// and for Magpie ledgers written before native_session tagging was added.
|
|
96
|
+
const sessionId = agent === 'dsh'
|
|
97
|
+
? nativeSessionId ?? providerSessionId
|
|
98
|
+
: providerSessionId !== null && providerSessionId !== nativeSessionId ? providerSessionId : null;
|
|
99
|
+
// Current Codex and Command Code gateway requests carry the Host's native
|
|
100
|
+
// session tag. Codex may use it only after the caller confirms Magpie.
|
|
101
|
+
const nativeTagged = agent === 'dsh' || agent === 'commandcode'
|
|
102
|
+
|| agent === 'codex' && options.allowPendingSession === true;
|
|
103
|
+
const magpieSessionId = nativeTagged ? nativeSessionId ?? sessionId : sessionId;
|
|
104
|
+
// Older gateways can still index the same session by its provider ID.
|
|
105
|
+
const fallbackSessionId = nativeTagged
|
|
106
|
+
&& nativeSessionId !== null && providerSessionId !== null && nativeSessionId !== providerSessionId
|
|
90
107
|
? providerSessionId
|
|
91
|
-
: agent === 'dsh' ? nativeSessionId : null;
|
|
92
|
-
// Command Code's ACP child tags loopback Magpie requests with the DSH
|
|
93
|
-
// session ID injected by the Host, so the ledger key differs from the ACP
|
|
94
|
-
// session ID shown in the panel. Older Hosts without that ID keep the
|
|
95
|
-
// provider ID so the panel still reports the same "no usage" state.
|
|
96
|
-
const magpieSessionId = agent === 'commandcode' ? nativeSessionId ?? sessionId : sessionId;
|
|
97
|
-
// During a rolling update the ACP child may still tag requests with its ACP
|
|
98
|
-
// session ID; fall back to it when the DSH-ID ledger row is absent.
|
|
99
|
-
const fallbackSessionId = agent === 'commandcode' && nativeSessionId !== null && sessionId !== null && nativeSessionId !== sessionId
|
|
100
|
-
? sessionId
|
|
101
108
|
: null;
|
|
102
|
-
if (magpieSessionId === null)
|
|
109
|
+
if (magpieSessionId === null && !options.allowPendingSession)
|
|
103
110
|
return null;
|
|
104
|
-
return this.readFromWeb(agent, requestedModelId, sessionId, magpieSessionId, fallbackSessionId, signal, includeRoute);
|
|
111
|
+
return this.readFromWeb(agent, requestedModelId, sessionId, magpieSessionId, fallbackSessionId, signal, includeRoute, options.allowPendingSession === true);
|
|
105
112
|
}
|
|
106
113
|
/**
|
|
107
114
|
* Mark Magpie models and enrich their public metadata. The marker is
|
|
@@ -187,6 +194,15 @@ export class MagpieUsageService {
|
|
|
187
194
|
const metadata = await this.loadModelMetadata(catalog, signal);
|
|
188
195
|
return metadata?.get(id) ?? null;
|
|
189
196
|
}
|
|
197
|
+
/** Read one exact, sanitized context session from Magpie's internal Web API. */
|
|
198
|
+
async readContext(options, signal) {
|
|
199
|
+
signal?.throwIfAborted();
|
|
200
|
+
const days = options.days === undefined ? 7 : Math.max(1, Math.min(30, Math.trunc(options.days)));
|
|
201
|
+
const response = await this.readWebJson(`/api/context?days=${days}`, signal);
|
|
202
|
+
if (!response.ok)
|
|
203
|
+
return null;
|
|
204
|
+
return parseMagpieContextResponse(response.value, options);
|
|
205
|
+
}
|
|
190
206
|
/** Read bounded, graph-ready usage and quota data through authenticated Magpie Web. */
|
|
191
207
|
async readDashboard(period, signal) {
|
|
192
208
|
signal?.throwIfAborted();
|
|
@@ -305,77 +321,193 @@ export class MagpieUsageService {
|
|
|
305
321
|
|| !Number.isSafeInteger(options.limit) || options.limit < 1 || options.limit > 100)
|
|
306
322
|
return null;
|
|
307
323
|
const sessionId = options.sessionId === undefined ? null : cleanText(options.sessionId);
|
|
324
|
+
const sessionIds = options.sessionIds === undefined
|
|
325
|
+
? []
|
|
326
|
+
: options.sessionIds.map(cleanText);
|
|
308
327
|
const sessionAgent = options.sessionAgent === undefined ? null : cleanText(options.sessionAgent);
|
|
309
328
|
if ((options.sessionId !== undefined && sessionId === null)
|
|
329
|
+
|| sessionIds.some((value) => value === null)
|
|
330
|
+
|| sessionIds.length > MAX_MAGPIE_SESSION_IDENTITIES
|
|
310
331
|
|| (options.sessionAgent !== undefined && (sessionAgent === null || !isMagpieAgentId(sessionAgent)))
|
|
311
|
-
|| ((sessionId === null) !== (sessionAgent === null))
|
|
332
|
+
|| ((sessionId === null) !== (sessionAgent === null))
|
|
333
|
+
|| (sessionIds.length > 0 && sessionId === null))
|
|
312
334
|
return null;
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if (!response.ok)
|
|
318
|
-
return null;
|
|
319
|
-
const root = asRecord(response.value);
|
|
320
|
-
const rawRows = root?.rows;
|
|
321
|
-
if (!Array.isArray(rawRows) || rawRows.length > 500)
|
|
335
|
+
const requestedSessionIds = sessionId === null
|
|
336
|
+
? []
|
|
337
|
+
: [...new Set([sessionId, ...sessionIds.filter((value) => value !== null)])];
|
|
338
|
+
if (requestedSessionIds.length > MAX_MAGPIE_SESSION_IDENTITIES)
|
|
322
339
|
return null;
|
|
323
|
-
|
|
340
|
+
// Usage summaries are returned separately from the request ledger. This keeps
|
|
341
|
+
// the cards complete without preloading rows that belong to later pages.
|
|
342
|
+
const aggregatePromise = this.readRequestAggregate(options.period, sessionAgent, requestedSessionIds, signal);
|
|
343
|
+
const readCandidatePage = async (offset, limit, querySessionId) => {
|
|
344
|
+
const query = new URLSearchParams({ period: magpieWebPeriod(options.period), limit: String(limit), offset: String(offset) });
|
|
345
|
+
if (querySessionId !== null)
|
|
346
|
+
query.set('q', querySessionId);
|
|
347
|
+
const response = await this.readWebJson(`/api/usage/requests?${query.toString()}`, signal);
|
|
348
|
+
if (!response.ok)
|
|
349
|
+
return null;
|
|
350
|
+
const root = asRecord(response.value);
|
|
351
|
+
const rows = root?.rows;
|
|
352
|
+
if (!Array.isArray(rows) || rows.length > 500)
|
|
353
|
+
return null;
|
|
354
|
+
return { rows, total: safeCount(root?.total) };
|
|
355
|
+
};
|
|
356
|
+
if (sessionId === null) {
|
|
357
|
+
const page = await readCandidatePage(options.offset, options.limit, null);
|
|
358
|
+
if (page === null)
|
|
359
|
+
return null;
|
|
360
|
+
const providerRecords = page.rows.some((row) => routeText(asRecord(row)?.agent) === 'codex')
|
|
361
|
+
? await this.readProviderCatalog(signal)
|
|
362
|
+
: [];
|
|
363
|
+
const items = parseMagpieRequestRows(page.rows, null, null, providerRecords);
|
|
364
|
+
return {
|
|
365
|
+
period: options.period,
|
|
366
|
+
offset: options.offset,
|
|
367
|
+
limit: options.limit,
|
|
368
|
+
total: page.total,
|
|
369
|
+
hasMore: page.total === null ? page.rows.length >= options.limit : options.offset + page.rows.length < page.total,
|
|
370
|
+
sessionId,
|
|
371
|
+
sessionAgent,
|
|
372
|
+
aggregate: await aggregatePromise,
|
|
373
|
+
items: items.slice(0, options.limit),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
// Magpie's `q` is a free-text candidate search, not an exact session filter.
|
|
377
|
+
// Fetch candidates from the beginning and apply the exact filter before paging;
|
|
378
|
+
// otherwise unrelated rows can consume the requested page and hide valid rows.
|
|
379
|
+
//
|
|
380
|
+
// Scanning stops one match past the requested page: enough to fill it and to prove
|
|
381
|
+
// whether another page exists. `total` therefore stays `null` while the identity's
|
|
382
|
+
// candidate window is not exhausted; the page that reads the window's end reports
|
|
383
|
+
// the exact count. Aggregated DSH sessions scan every contributing identity so one
|
|
384
|
+
// provider conversation cannot hide another.
|
|
385
|
+
const candidateLimit = 100;
|
|
386
|
+
const requestedEnd = options.offset + options.limit;
|
|
387
|
+
const rawRowsByKey = new Map();
|
|
388
|
+
let fullyScanned = true;
|
|
389
|
+
const perIdentityScanLimit = Math.floor(MAX_MAGPIE_SESSION_SCAN_ROWS / requestedSessionIds.length);
|
|
390
|
+
const acceptedAgents = MAGPIE_USAGE_AGENT_ALIASES[sessionAgent] ?? [sessionAgent];
|
|
391
|
+
const acceptedSessionIds = new Set(requestedSessionIds);
|
|
392
|
+
const matchesIn = () => [...rawRowsByKey.values()].reduce((count, row) => {
|
|
324
393
|
const record = asRecord(row);
|
|
325
394
|
if (record === null)
|
|
326
|
-
return
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
395
|
+
return count;
|
|
396
|
+
if (!acceptedSessionIds.has(cleanText(record.session) ?? '') && !acceptedSessionIds.has(cleanText(record.native_session) ?? ''))
|
|
397
|
+
return count;
|
|
398
|
+
return acceptedAgents.includes(cleanText(record.agent) ?? '') ? count + 1 : count;
|
|
399
|
+
}, 0);
|
|
400
|
+
for (const querySessionId of requestedSessionIds) {
|
|
401
|
+
let candidateOffset = 0;
|
|
402
|
+
let reachedEnd = false;
|
|
403
|
+
while (!reachedEnd && candidateOffset < perIdentityScanLimit) {
|
|
404
|
+
const pageLimit = Math.min(candidateLimit, perIdentityScanLimit - candidateOffset);
|
|
405
|
+
const page = await readCandidatePage(candidateOffset, pageLimit, querySessionId);
|
|
406
|
+
if (page === null)
|
|
407
|
+
return null;
|
|
408
|
+
for (const row of page.rows) {
|
|
409
|
+
const key = magpieRequestRowKey(row);
|
|
410
|
+
if (!rawRowsByKey.has(key))
|
|
411
|
+
rawRowsByKey.set(key, row);
|
|
412
|
+
}
|
|
413
|
+
const pageLength = page.rows.length;
|
|
414
|
+
const nextOffset = candidateOffset + pageLength;
|
|
415
|
+
reachedEnd = pageLength === 0
|
|
416
|
+
|| nextOffset <= candidateOffset
|
|
417
|
+
|| (page.total === null ? pageLength < pageLimit : nextOffset >= page.total);
|
|
418
|
+
candidateOffset = nextOffset;
|
|
419
|
+
// Enough rows for this page plus one more: no need to read the rest of the window.
|
|
420
|
+
if (!reachedEnd && matchesIn() > requestedEnd)
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
if (!reachedEnd)
|
|
424
|
+
fullyScanned = false;
|
|
425
|
+
}
|
|
426
|
+
const rawRows = [...rawRowsByKey.values()];
|
|
427
|
+
const providerRecords = rawRows.some((row) => routeText(asRecord(row)?.agent) === 'codex')
|
|
428
|
+
? await this.readProviderCatalog(signal)
|
|
429
|
+
: [];
|
|
430
|
+
const matching = parseMagpieRequestRows(rawRows, sessionId, sessionAgent, providerRecords, requestedSessionIds);
|
|
431
|
+
const total = fullyScanned ? matching.length : null;
|
|
338
432
|
return {
|
|
339
433
|
period: options.period,
|
|
340
434
|
offset: options.offset,
|
|
341
435
|
limit: options.limit,
|
|
342
436
|
total,
|
|
343
|
-
hasMore:
|
|
437
|
+
hasMore: !fullyScanned || matching.length > requestedEnd,
|
|
344
438
|
sessionId,
|
|
345
439
|
sessionAgent,
|
|
346
|
-
|
|
440
|
+
aggregate: await aggregatePromise,
|
|
441
|
+
items: matching.slice(options.offset, requestedEnd),
|
|
347
442
|
};
|
|
348
443
|
}
|
|
349
|
-
async
|
|
444
|
+
async readRequestAggregate(period, sessionAgent, sessionIds, signal) {
|
|
445
|
+
const response = await this.readWebJson(`/api/usage?period=${magpieWebPeriod(period)}`, signal);
|
|
446
|
+
if (!response.ok)
|
|
447
|
+
return null;
|
|
448
|
+
const root = usageRecord(response.value);
|
|
449
|
+
if (root === null)
|
|
450
|
+
return null;
|
|
451
|
+
if (sessionIds.length === 0)
|
|
452
|
+
return readMagpieUsageMetrics(root);
|
|
453
|
+
const sessions = root.sessions;
|
|
454
|
+
if (!Array.isArray(sessions) || sessionAgent === null)
|
|
455
|
+
return null;
|
|
456
|
+
const acceptedAgents = MAGPIE_USAGE_AGENT_ALIASES[sessionAgent] ?? [sessionAgent];
|
|
457
|
+
const acceptedIds = new Set(sessionIds);
|
|
458
|
+
let aggregate = null;
|
|
459
|
+
for (const item of sessions) {
|
|
460
|
+
const record = asRecord(item);
|
|
461
|
+
if (record === null || !acceptedIds.has(usageSessionId(record) ?? '')
|
|
462
|
+
|| !acceptedAgents.includes(cleanText(record.agent) ?? ''))
|
|
463
|
+
continue;
|
|
464
|
+
aggregate = aggregate === null
|
|
465
|
+
? readMagpieUsageMetrics(record)
|
|
466
|
+
: sumMagpieUsageMetrics(aggregate, readMagpieUsageMetrics(record));
|
|
467
|
+
}
|
|
468
|
+
return aggregate;
|
|
469
|
+
}
|
|
470
|
+
async readFromWeb(agent, modelId, sessionId, magpieSessionId, fallbackSessionId, signal, includeRoute, allowPendingSession) {
|
|
350
471
|
const capturedAt = new Date(this.now()).toISOString();
|
|
351
|
-
const [usageResult, quotaResult] = await Promise.all([
|
|
472
|
+
const [usageResult, sessionResult, quotaResult] = await Promise.all([
|
|
352
473
|
this.readWebJson('/api/usage?period=today', signal),
|
|
474
|
+
this.readWebJson('/api/usage?period=all', signal),
|
|
353
475
|
this.readWebJson('/api/usage/quotas', signal),
|
|
354
476
|
]);
|
|
355
477
|
const aggregate = usageResult.ok ? readTotals(usageResult.value) : null;
|
|
356
|
-
const primarySession =
|
|
357
|
-
? readSession(
|
|
478
|
+
const primarySession = sessionResult.ok && magpieSessionId !== null
|
|
479
|
+
? readSession(sessionResult.value, agent, magpieSessionId)
|
|
480
|
+
: null;
|
|
481
|
+
const fallbackSession = primarySession === null && sessionResult.ok && fallbackSessionId !== null
|
|
482
|
+
? readSession(sessionResult.value, agent, fallbackSessionId)
|
|
358
483
|
: null;
|
|
359
|
-
|
|
360
|
-
|
|
484
|
+
// Magpie's all-time summary can omit a session already present today.
|
|
485
|
+
// Preserve that exact daily match with its scope, rather than hiding it.
|
|
486
|
+
const todaySession = primarySession === null && fallbackSession === null && allowPendingSession && usageResult.ok
|
|
487
|
+
? (magpieSessionId === null ? null : readSession(usageResult.value, agent, magpieSessionId))
|
|
488
|
+
?? (fallbackSessionId === null ? null : readSession(usageResult.value, agent, fallbackSessionId))
|
|
361
489
|
: null;
|
|
362
|
-
const sessionUsage = primarySession ?? fallbackSession;
|
|
363
|
-
const resolvedSessionId =
|
|
364
|
-
if (sessionUsage === null)
|
|
490
|
+
const sessionUsage = primarySession ?? fallbackSession ?? todaySession;
|
|
491
|
+
const resolvedSessionId = sessionUsage?.id ?? magpieSessionId;
|
|
492
|
+
if (sessionUsage === null && (!allowPendingSession || aggregate === null))
|
|
365
493
|
return null;
|
|
366
|
-
const canReadRoute = includeRoute && resolvedSessionId !== null && resolvedSessionId.length <= 128;
|
|
494
|
+
const canReadRoute = sessionUsage !== null && includeRoute && resolvedSessionId !== null && resolvedSessionId.length <= 128;
|
|
367
495
|
const routeHistoryResult = canReadRoute
|
|
368
|
-
? await this.readWebJson(`/api/usage/requests?period=
|
|
496
|
+
? await this.readWebJson(`/api/usage/requests?period=all&offset=0&limit=100&q=${encodeURIComponent(resolvedSessionId)}`, signal)
|
|
369
497
|
: { ok: false };
|
|
370
498
|
const routeRows = routeHistoryResult.ok ? asRecord(routeHistoryResult.value)?.rows : null;
|
|
371
499
|
const routeHistoryValid = Array.isArray(routeRows) && routeRows.length <= 500;
|
|
500
|
+
const providerRecords = agent === 'codex' && routeHistoryValid
|
|
501
|
+
&& routeRows.some((row) => routeText(asRecord(row)?.agent) === 'codex')
|
|
502
|
+
? await this.readProviderCatalog(signal)
|
|
503
|
+
: [];
|
|
372
504
|
const routeHistory = routeHistoryResult.ok && routeHistoryValid && resolvedSessionId !== null
|
|
373
|
-
? readSessionRouteHistory(routeHistoryResult.value, agent, resolvedSessionId)
|
|
505
|
+
? readSessionRouteHistory(routeHistoryResult.value, agent, resolvedSessionId, providerRecords)
|
|
374
506
|
: [];
|
|
375
507
|
const route = routeHistory[0]?.route ?? null;
|
|
376
508
|
const routeStatus = route !== null
|
|
377
509
|
? 'available'
|
|
378
|
-
: resolvedSessionId === null
|
|
510
|
+
: sessionUsage === null || resolvedSessionId === null
|
|
379
511
|
? 'session-unavailable'
|
|
380
512
|
: !includeRoute
|
|
381
513
|
? 'not-requested'
|
|
@@ -387,7 +519,7 @@ export class MagpieUsageService {
|
|
|
387
519
|
providerFromRoute(route),
|
|
388
520
|
...routeHistory.map((record) => record.provider),
|
|
389
521
|
].filter((provider) => provider !== null))];
|
|
390
|
-
const upstreamProviders = routeProviders.length > 0
|
|
522
|
+
const upstreamProviders = sessionUsage === null ? [] : routeProviders.length > 0
|
|
391
523
|
? routeProviders
|
|
392
524
|
: [providerFromModelId(modelId, agent)].filter((provider) => provider !== null);
|
|
393
525
|
const upstreamProvider = upstreamProviders[0] ?? null;
|
|
@@ -406,8 +538,10 @@ export class MagpieUsageService {
|
|
|
406
538
|
capturedAt,
|
|
407
539
|
usageStatus: aggregate === null ? 'unavailable' : 'available',
|
|
408
540
|
aggregate,
|
|
409
|
-
sessionId,
|
|
541
|
+
sessionId: sessionId ?? (sessionUsage === null ? null : resolvedSessionId),
|
|
542
|
+
...(resolvedSessionId === null ? {} : { requestSessionId: resolvedSessionId }),
|
|
410
543
|
session: sessionUsage,
|
|
544
|
+
...(todaySession === null ? {} : { sessionPeriod: 'today' }),
|
|
411
545
|
routeStatus,
|
|
412
546
|
route,
|
|
413
547
|
upstreamProvider,
|
|
@@ -591,6 +725,18 @@ export class MagpieUsageService {
|
|
|
591
725
|
return { ok: false };
|
|
592
726
|
}
|
|
593
727
|
}
|
|
728
|
+
/** Provider management data is the canonical source when a usage row mislabels its upstream. */
|
|
729
|
+
async readProviderCatalog(signal) {
|
|
730
|
+
const cached = this.providerCatalog;
|
|
731
|
+
if (cached !== undefined && this.now() - cached.loadedAt < MAGPIE_PROVIDER_CATALOG_TTL_MS)
|
|
732
|
+
return cached.records;
|
|
733
|
+
const response = await this.readWebJson('/api/providers', signal);
|
|
734
|
+
const parsed = response.ok ? parseMagpieWebProviders(response.value) : null;
|
|
735
|
+
const records = parsed?.records ?? [];
|
|
736
|
+
if (parsed !== null)
|
|
737
|
+
this.providerCatalog = { records, loadedAt: this.now() };
|
|
738
|
+
return records;
|
|
739
|
+
}
|
|
594
740
|
}
|
|
595
741
|
function awaitWithSignal(task, signal) {
|
|
596
742
|
if (signal === undefined)
|
|
@@ -864,53 +1010,129 @@ export function magpieUsageAgentAliases(adapterId) {
|
|
|
864
1010
|
* native DSH use different identities across Magpie versions.
|
|
865
1011
|
*/
|
|
866
1012
|
export function attachMagpieSessionNames(dashboard, bindings) {
|
|
867
|
-
if (!Array.isArray(dashboard.sessions)
|
|
1013
|
+
if (!Array.isArray(dashboard.sessions))
|
|
868
1014
|
return dashboard;
|
|
869
|
-
const
|
|
1015
|
+
const owners = new Map();
|
|
870
1016
|
const ambiguous = new Set();
|
|
1017
|
+
const ownerByGroup = new Map();
|
|
871
1018
|
for (const binding of bindings) {
|
|
872
|
-
const name = cleanSessionName(binding.name);
|
|
873
1019
|
const dshSessionId = cleanText(binding.dshSessionId);
|
|
874
|
-
|
|
1020
|
+
const name = cleanSessionName(binding.name);
|
|
1021
|
+
if (dshSessionId === null)
|
|
875
1022
|
continue;
|
|
876
|
-
//
|
|
877
|
-
// DSH ID
|
|
878
|
-
// adapters use only their Provider session ID.
|
|
1023
|
+
// Magpie versions may record either the provider conversation ID or the
|
|
1024
|
+
// DSH ID; both exact identities represent the same DSH session.
|
|
879
1025
|
const providerSessionId = cleanText(binding.providerSessionId);
|
|
880
|
-
const ids = new Set((
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1026
|
+
const ids = new Set([dshSessionId, providerSessionId].filter((value) => value !== null));
|
|
1027
|
+
const groupKey = `dsh\u0000${dshSessionId}`;
|
|
1028
|
+
const owner = ownerByGroup.get(groupKey) ?? {
|
|
1029
|
+
groupKey,
|
|
1030
|
+
dshSessionId,
|
|
1031
|
+
agent: magpieAgentForAdapter(binding.adapterId) ?? binding.adapterId,
|
|
1032
|
+
name,
|
|
1033
|
+
ids: [...new Set(ids)],
|
|
1034
|
+
};
|
|
1035
|
+
if (ownerByGroup.has(groupKey)) {
|
|
1036
|
+
ownerByGroup.set(groupKey, {
|
|
1037
|
+
...owner,
|
|
1038
|
+
name: owner.name === null || name === null || owner.name === name ? owner.name ?? name : null,
|
|
1039
|
+
ids: [...new Set([...owner.ids, ...ids])],
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
ownerByGroup.set(groupKey, owner);
|
|
1044
|
+
}
|
|
1045
|
+
const currentOwner = ownerByGroup.get(groupKey);
|
|
885
1046
|
for (const agent of magpieUsageAgentAliases(binding.adapterId)) {
|
|
886
1047
|
for (const id of ids) {
|
|
887
1048
|
const key = `${agent}\u0000${id}`;
|
|
888
1049
|
if (ambiguous.has(key))
|
|
889
1050
|
continue;
|
|
890
|
-
const previous =
|
|
891
|
-
if (previous !== undefined && previous !==
|
|
892
|
-
|
|
1051
|
+
const previous = owners.get(key);
|
|
1052
|
+
if (previous !== undefined && previous.groupKey !== groupKey) {
|
|
1053
|
+
owners.delete(key);
|
|
893
1054
|
ambiguous.add(key);
|
|
894
1055
|
}
|
|
895
1056
|
else {
|
|
896
|
-
|
|
1057
|
+
owners.set(key, currentOwner);
|
|
897
1058
|
}
|
|
898
1059
|
}
|
|
899
1060
|
}
|
|
900
1061
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1062
|
+
for (const [key, owner] of owners) {
|
|
1063
|
+
const currentOwner = ownerByGroup.get(owner.groupKey);
|
|
1064
|
+
if (currentOwner !== undefined)
|
|
1065
|
+
owners.set(key, currentOwner);
|
|
1066
|
+
}
|
|
1067
|
+
const grouped = new Map();
|
|
1068
|
+
for (const row of dashboard.sessions) {
|
|
1069
|
+
const aliasKey = `${row.agent}\u0000${row.id}`;
|
|
1070
|
+
const owner = ambiguous.has(aliasKey) ? undefined : owners.get(aliasKey);
|
|
1071
|
+
const groupKey = owner?.groupKey ?? `raw\u0000${row.agent}\u0000${row.id}`;
|
|
1072
|
+
const current = grouped.get(groupKey);
|
|
1073
|
+
if (current === undefined) {
|
|
1074
|
+
const ids = new Set(owner?.ids ?? [row.id]);
|
|
1075
|
+
ids.add(row.id);
|
|
1076
|
+
const session = owner === undefined
|
|
1077
|
+
? row
|
|
1078
|
+
: {
|
|
1079
|
+
...row,
|
|
1080
|
+
id: owner.dshSessionId,
|
|
1081
|
+
agent: owner.agent,
|
|
1082
|
+
...(owner.name === null ? {} : { dshSessionName: owner.name }),
|
|
1083
|
+
sessionIds: [...ids],
|
|
1084
|
+
};
|
|
1085
|
+
grouped.set(groupKey, { session, ids });
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
for (const id of owner?.ids ?? [row.id])
|
|
1089
|
+
current.ids.add(id);
|
|
1090
|
+
current.ids.add(row.id);
|
|
1091
|
+
current.session = mergeMagpieUsageSessions(current.session, row, owner);
|
|
1092
|
+
}
|
|
1093
|
+
const sessions = [...grouped.values()].map(({ session, ids }) => {
|
|
1094
|
+
if (ids.size <= 1 && session.sessionIds === undefined)
|
|
909
1095
|
return session;
|
|
910
|
-
|
|
911
|
-
return { ...session, dshSessionName: name };
|
|
1096
|
+
return { ...session, sessionIds: [...ids] };
|
|
912
1097
|
});
|
|
913
|
-
|
|
1098
|
+
if (sessions.length === dashboard.sessions.length && sessions.every((session, index) => session === dashboard.sessions[index]))
|
|
1099
|
+
return dashboard;
|
|
1100
|
+
return { ...dashboard, sessions };
|
|
1101
|
+
}
|
|
1102
|
+
function mergeMagpieUsageSessions(current, row, owner) {
|
|
1103
|
+
return {
|
|
1104
|
+
...current,
|
|
1105
|
+
...sumMagpieUsageMetrics(current, row),
|
|
1106
|
+
id: owner?.dshSessionId ?? current.id,
|
|
1107
|
+
agent: owner?.agent ?? current.agent,
|
|
1108
|
+
...(current.dshSessionName === undefined && owner?.name !== null && owner?.name !== undefined
|
|
1109
|
+
? { dshSessionName: owner.name }
|
|
1110
|
+
: {}),
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function sumMagpieUsageMetrics(left, right) {
|
|
1114
|
+
return {
|
|
1115
|
+
calls: sumMagpieUsageMetric(left.calls, right.calls),
|
|
1116
|
+
errors: sumMagpieUsageMetric(left.errors, right.errors),
|
|
1117
|
+
retried: sumMagpieUsageMetric(left.retried, right.retried),
|
|
1118
|
+
inputTokens: sumMagpieUsageMetric(left.inputTokens, right.inputTokens),
|
|
1119
|
+
outputTokens: sumMagpieUsageMetric(left.outputTokens, right.outputTokens),
|
|
1120
|
+
cacheReadTokens: sumMagpieUsageMetric(left.cacheReadTokens, right.cacheReadTokens),
|
|
1121
|
+
cacheWriteTokens: sumMagpieUsageMetric(left.cacheWriteTokens, right.cacheWriteTokens),
|
|
1122
|
+
reasoningTokens: sumMagpieUsageMetric(left.reasoningTokens, right.reasoningTokens),
|
|
1123
|
+
cost: sumMagpieUsageMetric(left.cost, right.cost),
|
|
1124
|
+
unpriced: sumMagpieUsageMetric(left.unpriced, right.unpriced),
|
|
1125
|
+
credits: sumMagpieUsageMetric(left.credits, right.credits),
|
|
1126
|
+
credited: sumMagpieUsageMetric(left.credited, right.credited),
|
|
1127
|
+
timed: sumMagpieUsageMetric(left.timed, right.timed),
|
|
1128
|
+
ttftMs: sumMagpieUsageMetric(left.ttftMs, right.ttftMs),
|
|
1129
|
+
decodeMs: sumMagpieUsageMetric(left.decodeMs, right.decodeMs),
|
|
1130
|
+
decodeTokens: sumMagpieUsageMetric(left.decodeTokens, right.decodeTokens),
|
|
1131
|
+
decodeCount: sumMagpieUsageMetric(left.decodeCount, right.decodeCount),
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
function sumMagpieUsageMetric(left, right) {
|
|
1135
|
+
return left === null && right === null ? null : (left ?? 0) + (right ?? 0);
|
|
914
1136
|
}
|
|
915
1137
|
function isMagpieAgentId(value) {
|
|
916
1138
|
return value.length <= 64 && /^[a-z0-9][a-z0-9-]*$/u.test(value);
|
|
@@ -969,7 +1191,7 @@ async function readBoundedJson(response) {
|
|
|
969
1191
|
}
|
|
970
1192
|
}
|
|
971
1193
|
function readTotals(value) {
|
|
972
|
-
const record =
|
|
1194
|
+
const record = usageRecord(value);
|
|
973
1195
|
if (record === null)
|
|
974
1196
|
return null;
|
|
975
1197
|
const calls = safeCount(record.calls);
|
|
@@ -984,22 +1206,31 @@ function readTotals(value) {
|
|
|
984
1206
|
return { calls, errors, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, costUsd };
|
|
985
1207
|
}
|
|
986
1208
|
function readSession(value, agent, sessionId) {
|
|
987
|
-
const sessions =
|
|
1209
|
+
const sessions = usageRecord(value)?.sessions;
|
|
988
1210
|
if (!Array.isArray(sessions))
|
|
989
1211
|
return null;
|
|
990
1212
|
const usageAgents = MAGPIE_USAGE_AGENT_ALIASES[agent] ?? [agent];
|
|
991
1213
|
const row = sessions.find((item) => {
|
|
992
1214
|
const record = asRecord(item);
|
|
993
1215
|
const usageAgent = cleanText(record?.agent);
|
|
994
|
-
return
|
|
1216
|
+
return usageSessionId(record) === sessionId && usageAgent !== null && usageAgents.includes(usageAgent);
|
|
995
1217
|
});
|
|
996
1218
|
const record = asRecord(row);
|
|
997
1219
|
const totals = readTotals(record);
|
|
998
1220
|
const usageAgent = cleanText(record?.agent);
|
|
999
1221
|
return record !== null && usageAgent !== null && totals !== null ? { id: sessionId, agent: usageAgent, ...totals } : null;
|
|
1000
1222
|
}
|
|
1223
|
+
/** Magpie 0.1.14xx can wrap usage payloads in `data`; older versions do not. */
|
|
1224
|
+
function usageRecord(value) {
|
|
1225
|
+
const record = asRecord(value);
|
|
1226
|
+
const data = asRecord(record?.data);
|
|
1227
|
+
return data ?? record;
|
|
1228
|
+
}
|
|
1229
|
+
function usageSessionId(record) {
|
|
1230
|
+
return cleanText(record?.id) ?? cleanText(record?.session) ?? cleanText(record?.native_session);
|
|
1231
|
+
}
|
|
1001
1232
|
/** Read the durable request ledger when Magpie's short in-memory trace has expired. */
|
|
1002
|
-
function readSessionRouteHistory(value, agent, sessionId) {
|
|
1233
|
+
function readSessionRouteHistory(value, agent, sessionId, providerRecords = []) {
|
|
1003
1234
|
const rows = asRecord(value)?.rows;
|
|
1004
1235
|
if (!Array.isArray(rows) || rows.length > 500)
|
|
1005
1236
|
return [];
|
|
@@ -1009,9 +1240,11 @@ function readSessionRouteHistory(value, agent, sessionId) {
|
|
|
1009
1240
|
const rowAgent = cleanText(record?.agent);
|
|
1010
1241
|
const session = cleanText(record?.session);
|
|
1011
1242
|
const nativeSession = cleanText(record?.native_session);
|
|
1012
|
-
const provider = routeText(record?.provider);
|
|
1013
1243
|
if (record === null || rowAgent === null || !usageAgents.includes(rowAgent)
|
|
1014
|
-
|| (session !== sessionId && nativeSession !== sessionId)
|
|
1244
|
+
|| (session !== sessionId && nativeSession !== sessionId))
|
|
1245
|
+
return [];
|
|
1246
|
+
const provider = normalizeMagpieRequestProvider(record, providerRecords);
|
|
1247
|
+
if (provider === null)
|
|
1015
1248
|
return [];
|
|
1016
1249
|
const requested = routeText(record.req);
|
|
1017
1250
|
const model = routeText(record.model);
|
|
@@ -1393,9 +1626,32 @@ function readMagpieUsageSessions(value) {
|
|
|
1393
1626
|
return record === null || id === null || agent === null ? [] : [{ id, agent, ...readMagpieUsageMetrics(record) }];
|
|
1394
1627
|
});
|
|
1395
1628
|
}
|
|
1396
|
-
function
|
|
1629
|
+
function parseMagpieRequestRows(rows, sessionId, sessionAgent, providerRecords = [], sessionIds = sessionId === null ? [] : [sessionId]) {
|
|
1630
|
+
const acceptedAgents = sessionAgent === null ? null : MAGPIE_USAGE_AGENT_ALIASES[sessionAgent] ?? [sessionAgent];
|
|
1631
|
+
const acceptedSessionIds = new Set(sessionIds);
|
|
1632
|
+
return rows.flatMap((row) => {
|
|
1633
|
+
const record = asRecord(row);
|
|
1634
|
+
if (record === null)
|
|
1635
|
+
return [];
|
|
1636
|
+
const candidateSession = cleanText(record.session);
|
|
1637
|
+
const nativeSession = cleanText(record.native_session);
|
|
1638
|
+
const agent = cleanText(record.agent);
|
|
1639
|
+
if (sessionId !== null
|
|
1640
|
+
&& (!acceptedSessionIds.has(candidateSession ?? '') && !acceptedSessionIds.has(nativeSession ?? '')
|
|
1641
|
+
|| agent === null || acceptedAgents === null || !acceptedAgents.includes(agent)))
|
|
1642
|
+
return [];
|
|
1643
|
+
const item = parseMagpieRequest(record, providerRecords);
|
|
1644
|
+
return item === null ? [] : [sessionId === null ? item : { ...item, sessionId }];
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
function magpieRequestRowKey(value) {
|
|
1648
|
+
const record = asRecord(value);
|
|
1649
|
+
const routeId = safeRouteId(record?.route_id);
|
|
1650
|
+
return routeId === null ? JSON.stringify(record) : `route:${routeId}`;
|
|
1651
|
+
}
|
|
1652
|
+
function parseMagpieRequest(record, providerRecords = []) {
|
|
1397
1653
|
const agent = routeText(record.agent);
|
|
1398
|
-
const provider =
|
|
1654
|
+
const provider = normalizeMagpieRequestProvider(record, providerRecords);
|
|
1399
1655
|
const requestedModel = routeText(record.req);
|
|
1400
1656
|
const servedModel = routeText(record.served);
|
|
1401
1657
|
const group = requestedModel?.startsWith('group/') === true ? requestedModel.slice('group/'.length) : null;
|
|
@@ -1404,7 +1660,7 @@ function parseMagpieRequest(record) {
|
|
|
1404
1660
|
time: routeText(record.t),
|
|
1405
1661
|
agent,
|
|
1406
1662
|
provider,
|
|
1407
|
-
account:
|
|
1663
|
+
account: normalizeMagpieRequestAccount(record, provider, providerRecords),
|
|
1408
1664
|
requestedModel,
|
|
1409
1665
|
servedModel,
|
|
1410
1666
|
group,
|
|
@@ -1425,6 +1681,40 @@ function parseMagpieRequest(record) {
|
|
|
1425
1681
|
sessionId: cleanText(record.session) ?? cleanText(record.native_session),
|
|
1426
1682
|
};
|
|
1427
1683
|
}
|
|
1684
|
+
/** Session-filtered rows can omit the account label; use only the matching provider's safe catalog label. */
|
|
1685
|
+
function normalizeMagpieRequestAccount(record, provider, providerRecords) {
|
|
1686
|
+
const account = routeText(record.providerAccount);
|
|
1687
|
+
if (account !== null || provider === null)
|
|
1688
|
+
return account;
|
|
1689
|
+
const matches = providerRecords.filter((item) => item.id === provider);
|
|
1690
|
+
return matches.length === 1 ? matches[0].account : null;
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Magpie's usage ledger can retain a stale provider id after an Agent provider
|
|
1694
|
+
* is renamed. Reconcile only Codex rows against the read-only provider catalog;
|
|
1695
|
+
* arbitrary provider ids must remain untouched when the evidence is ambiguous.
|
|
1696
|
+
*/
|
|
1697
|
+
function normalizeMagpieRequestProvider(record, providerRecords) {
|
|
1698
|
+
const provider = routeText(record.provider);
|
|
1699
|
+
if (provider === null || routeText(record.agent)?.toLocaleLowerCase() !== 'codex')
|
|
1700
|
+
return provider;
|
|
1701
|
+
const codexProviders = providerRecords.filter((item) => item.accountAgent?.toLocaleLowerCase() === 'codex');
|
|
1702
|
+
if (codexProviders.length === 0)
|
|
1703
|
+
return provider;
|
|
1704
|
+
const modelValues = [record.model, record.served, record.req]
|
|
1705
|
+
.map(routeText)
|
|
1706
|
+
.filter((value) => value !== null);
|
|
1707
|
+
const matches = codexProviders.filter((candidate) => candidate.models.some((model) => modelValues.some((value) => magpieModelMatchesProvider(value, model.id, candidate.id))));
|
|
1708
|
+
if (matches.length === 1)
|
|
1709
|
+
return matches[0].id;
|
|
1710
|
+
return codexProviders.length === 1 ? codexProviders[0].id : provider;
|
|
1711
|
+
}
|
|
1712
|
+
function magpieModelMatchesProvider(value, modelId, providerId) {
|
|
1713
|
+
if (value === modelId)
|
|
1714
|
+
return true;
|
|
1715
|
+
const prefix = `${providerId}/`;
|
|
1716
|
+
return modelId.startsWith(prefix) && value === modelId.slice(prefix.length);
|
|
1717
|
+
}
|
|
1428
1718
|
function firstRouteText(record, keys) {
|
|
1429
1719
|
for (const key of keys) {
|
|
1430
1720
|
const value = routeText(record[key]);
|