@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.
Files changed (220) hide show
  1. package/README.en.md +3 -3
  2. package/README.md +3 -3
  3. package/data/build/dist/client/bundle.js +1838 -223
  4. package/data/build/dist/client/bundle.js.map +1 -1
  5. package/data/build/dist/client/cli-session-config-cache.d.ts.map +1 -1
  6. package/data/build/dist/client/cli-session-config-cache.js +9 -3
  7. package/data/build/dist/client/cli-session-config-cache.js.map +1 -1
  8. package/data/build/dist/client/client.native-workspace-store.js +7 -1
  9. package/data/build/dist/client/client.native-workspace-store.js.map +1 -1
  10. package/data/build/dist/client/client.transport.js +276 -60
  11. package/data/build/dist/client/client.transport.js.map +1 -1
  12. package/data/build/dist/client/desktop-assistant.js +274 -58
  13. package/data/build/dist/client/features/cli-adapters.d.ts.map +1 -1
  14. package/data/build/dist/client/features/cli-adapters.js +13 -1
  15. package/data/build/dist/client/features/cli-adapters.js.map +1 -1
  16. package/data/build/dist/client/features/index.d.ts.map +1 -1
  17. package/data/build/dist/client/features/index.js +2 -0
  18. package/data/build/dist/client/features/index.js.map +1 -1
  19. package/data/build/dist/client/features/magpie-management-controls.d.ts.map +1 -1
  20. package/data/build/dist/client/features/magpie-management-controls.js +45 -10
  21. package/data/build/dist/client/features/magpie-management-controls.js.map +1 -1
  22. package/data/build/dist/client/features/magpie-management-page.d.ts +6 -1
  23. package/data/build/dist/client/features/magpie-management-page.d.ts.map +1 -1
  24. package/data/build/dist/client/features/magpie-management-page.js +109 -37
  25. package/data/build/dist/client/features/magpie-management-page.js.map +1 -1
  26. package/data/build/dist/client/features/magpie-management.d.ts.map +1 -1
  27. package/data/build/dist/client/features/magpie-management.js +3 -2
  28. package/data/build/dist/client/features/magpie-management.js.map +1 -1
  29. package/data/build/dist/client/features/plugin-update.d.ts +3 -0
  30. package/data/build/dist/client/features/plugin-update.d.ts.map +1 -0
  31. package/data/build/dist/client/features/plugin-update.js +8 -0
  32. package/data/build/dist/client/features/plugin-update.js.map +1 -0
  33. package/data/build/dist/client/features/types.d.ts +3 -0
  34. package/data/build/dist/client/features/types.d.ts.map +1 -1
  35. package/data/build/dist/client/locale.d.ts.map +1 -1
  36. package/data/build/dist/client/locale.js +3 -0
  37. package/data/build/dist/client/locale.js.map +1 -1
  38. package/data/build/dist/client/locales/magpie.d.ts.map +1 -1
  39. package/data/build/dist/client/locales/magpie.js +92 -12
  40. package/data/build/dist/client/locales/magpie.js.map +1 -1
  41. package/data/build/dist/client/locales/pluginUpdate.d.ts +3 -0
  42. package/data/build/dist/client/locales/pluginUpdate.d.ts.map +1 -0
  43. package/data/build/dist/client/locales/pluginUpdate.js +131 -0
  44. package/data/build/dist/client/locales/pluginUpdate.js.map +1 -0
  45. package/data/build/dist/client/locales/usage.d.ts.map +1 -1
  46. package/data/build/dist/client/locales/usage.js +2 -0
  47. package/data/build/dist/client/locales/usage.js.map +1 -1
  48. package/data/build/dist/client/magpie-context-meter.d.ts +18 -0
  49. package/data/build/dist/client/magpie-context-meter.d.ts.map +1 -0
  50. package/data/build/dist/client/magpie-context-meter.js +274 -0
  51. package/data/build/dist/client/magpie-context-meter.js.map +1 -0
  52. package/data/build/dist/client/magpie-context-store.d.ts +32 -0
  53. package/data/build/dist/client/magpie-context-store.d.ts.map +1 -0
  54. package/data/build/dist/client/magpie-context-store.js +165 -0
  55. package/data/build/dist/client/magpie-context-store.js.map +1 -0
  56. package/data/build/dist/client/settings-promo-styles.d.ts +5 -0
  57. package/data/build/dist/client/settings-promo-styles.d.ts.map +1 -1
  58. package/data/build/dist/client/settings-promo-styles.js +15 -2
  59. package/data/build/dist/client/settings-promo-styles.js.map +1 -1
  60. package/data/build/dist/client/settings-promo.d.ts +4 -2
  61. package/data/build/dist/client/settings-promo.d.ts.map +1 -1
  62. package/data/build/dist/client/settings-promo.js +4 -4
  63. package/data/build/dist/client/settings-promo.js.map +1 -1
  64. package/data/build/dist/client/settings-section.d.ts +2 -3
  65. package/data/build/dist/client/settings-section.d.ts.map +1 -1
  66. package/data/build/dist/client/settings-section.js +7 -7
  67. package/data/build/dist/client/settings-section.js.map +1 -1
  68. package/data/build/dist/client/settings-update.d.ts +8 -0
  69. package/data/build/dist/client/settings-update.d.ts.map +1 -0
  70. package/data/build/dist/client/settings-update.js +57 -0
  71. package/data/build/dist/client/settings-update.js.map +1 -0
  72. package/data/build/dist/client/subscription-slot.d.ts +3 -2
  73. package/data/build/dist/client/subscription-slot.d.ts.map +1 -1
  74. package/data/build/dist/client/subscription-slot.js +115 -43
  75. package/data/build/dist/client/subscription-slot.js.map +1 -1
  76. package/data/build/dist/client/update-panel-styles.d.ts +29 -0
  77. package/data/build/dist/client/update-panel-styles.d.ts.map +1 -0
  78. package/data/build/dist/client/update-panel-styles.js +69 -0
  79. package/data/build/dist/client/update-panel-styles.js.map +1 -0
  80. package/data/build/dist/client/update-panel.d.ts +7 -0
  81. package/data/build/dist/client/update-panel.d.ts.map +1 -0
  82. package/data/build/dist/client/update-panel.js +52 -0
  83. package/data/build/dist/client/update-panel.js.map +1 -0
  84. package/data/build/dist/client/update-store.d.ts +43 -0
  85. package/data/build/dist/client/update-store.d.ts.map +1 -0
  86. package/data/build/dist/client/update-store.js +207 -0
  87. package/data/build/dist/client/update-store.js.map +1 -0
  88. package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.d.ts +4 -0
  89. package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.d.ts.map +1 -0
  90. package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.js +6 -0
  91. package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.js.map +1 -0
  92. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.d.ts +15 -1
  93. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.d.ts.map +1 -1
  94. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js +53 -21
  95. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js.map +1 -1
  96. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts +22 -0
  97. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts.map +1 -0
  98. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js +71 -0
  99. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js.map +1 -0
  100. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts +4 -0
  101. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts.map +1 -1
  102. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js +16 -1
  103. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js.map +1 -1
  104. package/data/build/dist/dsh-capabilities/matrix.d.ts.map +1 -1
  105. package/data/build/dist/dsh-capabilities/matrix.js +4 -0
  106. package/data/build/dist/dsh-capabilities/matrix.js.map +1 -1
  107. package/data/build/dist/dsh-capabilities/routes.d.ts.map +1 -1
  108. package/data/build/dist/dsh-capabilities/routes.js +10 -0
  109. package/data/build/dist/dsh-capabilities/routes.js.map +1 -1
  110. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  111. package/data/build/dist/dsh-capabilities/types.d.ts.map +1 -1
  112. package/data/build/dist/dsh-capabilities/types.js.map +1 -1
  113. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  114. package/data/build/dist/host/cli-adapters/feature.js +230 -34
  115. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  116. package/data/build/dist/host/cli-adapters/magpie-context.d.ts +9 -0
  117. package/data/build/dist/host/cli-adapters/magpie-context.d.ts.map +1 -0
  118. package/data/build/dist/host/cli-adapters/magpie-context.js +128 -0
  119. package/data/build/dist/host/cli-adapters/magpie-context.js.map +1 -0
  120. package/data/build/dist/host/cli-adapters/magpie-detection.d.ts +3 -3
  121. package/data/build/dist/host/cli-adapters/magpie-detection.d.ts.map +1 -1
  122. package/data/build/dist/host/cli-adapters/magpie-detection.js +18 -4
  123. package/data/build/dist/host/cli-adapters/magpie-detection.js.map +1 -1
  124. package/data/build/dist/host/cli-adapters/magpie-management.d.ts.map +1 -1
  125. package/data/build/dist/host/cli-adapters/magpie-management.js +95 -20
  126. package/data/build/dist/host/cli-adapters/magpie-management.js.map +1 -1
  127. package/data/build/dist/host/cli-adapters/magpie-update.d.ts.map +1 -1
  128. package/data/build/dist/host/cli-adapters/magpie-update.js +14 -4
  129. package/data/build/dist/host/cli-adapters/magpie-update.js.map +1 -1
  130. package/data/build/dist/host/cli-adapters/magpie-usage.d.ts +17 -3
  131. package/data/build/dist/host/cli-adapters/magpie-usage.d.ts.map +1 -1
  132. package/data/build/dist/host/cli-adapters/magpie-usage.js +382 -92
  133. package/data/build/dist/host/cli-adapters/magpie-usage.js.map +1 -1
  134. package/data/build/dist/host/cli-adapters/magpie-web.d.ts +15 -0
  135. package/data/build/dist/host/cli-adapters/magpie-web.d.ts.map +1 -1
  136. package/data/build/dist/host/cli-adapters/magpie-web.js +100 -22
  137. package/data/build/dist/host/cli-adapters/magpie-web.js.map +1 -1
  138. package/data/build/dist/host/cli-adapters/process-utils.d.ts.map +1 -1
  139. package/data/build/dist/host/cli-adapters/process-utils.js +4 -0
  140. package/data/build/dist/host/cli-adapters/process-utils.js.map +1 -1
  141. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  142. package/data/build/dist/host/cli-adapters/registry.js +6 -5
  143. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  144. package/data/build/dist/host/cli-adapters/session-performance-projection.d.ts +6 -0
  145. package/data/build/dist/host/cli-adapters/session-performance-projection.d.ts.map +1 -1
  146. package/data/build/dist/host/cli-adapters/session-performance-projection.js +25 -0
  147. package/data/build/dist/host/cli-adapters/session-performance-projection.js.map +1 -1
  148. package/data/build/dist/host/features/index.d.ts.map +1 -1
  149. package/data/build/dist/host/features/index.js +2 -0
  150. package/data/build/dist/host/features/index.js.map +1 -1
  151. package/data/build/dist/host/features/plugin-update.d.ts +6 -0
  152. package/data/build/dist/host/features/plugin-update.d.ts.map +1 -0
  153. package/data/build/dist/host/features/plugin-update.js +45 -0
  154. package/data/build/dist/host/features/plugin-update.js.map +1 -0
  155. package/data/build/dist/host/index.d.ts.map +1 -1
  156. package/data/build/dist/host/index.js +31 -1
  157. package/data/build/dist/host/index.js.map +1 -1
  158. package/data/build/dist/host/native-session-bridge.d.ts +8 -0
  159. package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
  160. package/data/build/dist/host/native-session-bridge.js +43 -0
  161. package/data/build/dist/host/native-session-bridge.js.map +1 -1
  162. package/data/build/dist/host/rpc.d.ts +4 -1
  163. package/data/build/dist/host/rpc.d.ts.map +1 -1
  164. package/data/build/dist/host/rpc.js +33 -7
  165. package/data/build/dist/host/rpc.js.map +1 -1
  166. package/data/build/dist/host/session-handle-eviction.d.ts +29 -0
  167. package/data/build/dist/host/session-handle-eviction.d.ts.map +1 -0
  168. package/data/build/dist/host/session-handle-eviction.js +413 -0
  169. package/data/build/dist/host/session-handle-eviction.js.map +1 -0
  170. package/data/build/dist/host/settings.d.ts.map +1 -1
  171. package/data/build/dist/host/settings.js +12 -1
  172. package/data/build/dist/host/settings.js.map +1 -1
  173. package/data/build/dist/host/update/application-result.d.ts +3 -0
  174. package/data/build/dist/host/update/application-result.d.ts.map +1 -0
  175. package/data/build/dist/host/update/application-result.js +13 -0
  176. package/data/build/dist/host/update/application-result.js.map +1 -0
  177. package/data/build/dist/host/update/http.d.ts +7 -0
  178. package/data/build/dist/host/update/http.d.ts.map +1 -0
  179. package/data/build/dist/host/update/http.js +55 -0
  180. package/data/build/dist/host/update/http.js.map +1 -0
  181. package/data/build/dist/host/update/metadata.d.ts +14 -0
  182. package/data/build/dist/host/update/metadata.d.ts.map +1 -0
  183. package/data/build/dist/host/update/metadata.js +75 -0
  184. package/data/build/dist/host/update/metadata.js.map +1 -0
  185. package/data/build/dist/host/update/release-notes.d.ts +6 -0
  186. package/data/build/dist/host/update/release-notes.d.ts.map +1 -0
  187. package/data/build/dist/host/update/release-notes.js +52 -0
  188. package/data/build/dist/host/update/release-notes.js.map +1 -0
  189. package/data/build/dist/host/update/service.d.ts +25 -0
  190. package/data/build/dist/host/update/service.d.ts.map +1 -0
  191. package/data/build/dist/host/update/service.js +209 -0
  192. package/data/build/dist/host/update/service.js.map +1 -0
  193. package/data/build/dist/shared/contracts/config.d.ts +17 -0
  194. package/data/build/dist/shared/contracts/config.d.ts.map +1 -1
  195. package/data/build/dist/shared/contracts/config.js +8 -0
  196. package/data/build/dist/shared/contracts/config.js.map +1 -1
  197. package/data/build/dist/shared/contracts/magpie-management.d.ts +59 -0
  198. package/data/build/dist/shared/contracts/magpie-management.d.ts.map +1 -1
  199. package/data/build/dist/shared/contracts/plugin-update.d.ts +93 -0
  200. package/data/build/dist/shared/contracts/plugin-update.d.ts.map +1 -0
  201. package/data/build/dist/shared/contracts/plugin-update.js +128 -0
  202. package/data/build/dist/shared/contracts/plugin-update.js.map +1 -0
  203. package/data/build/dist/shared/contracts/subscription.d.ts +5 -0
  204. package/data/build/dist/shared/contracts/subscription.d.ts.map +1 -1
  205. package/data/build/dist/shared/contracts/subscription.js.map +1 -1
  206. package/data/build/dist/shared/contracts/version.d.ts +1 -1
  207. package/data/build/dist/shared/contracts/version.js +1 -1
  208. package/data/build/dist/shared/index.d.ts +2 -1
  209. package/data/build/dist/shared/index.d.ts.map +1 -1
  210. package/data/build/dist/shared/index.js +1 -0
  211. package/data/build/dist/shared/index.js.map +1 -1
  212. package/data/build/dist/shared/magpie-provider-id.d.ts +12 -0
  213. package/data/build/dist/shared/magpie-provider-id.d.ts.map +1 -0
  214. package/data/build/dist/shared/magpie-provider-id.js +16 -0
  215. package/data/build/dist/shared/magpie-provider-id.js.map +1 -0
  216. package/package.json +2 -1
  217. package/profile/README.md +2 -2
  218. package/profile/package.json +2 -2
  219. package/profile/version.json +1 -1
  220. 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 use the DSH session ID as their Magpie session
88
- // identity. Other drivers must never treat that fallback as a provider ID.
89
- const sessionId = providerSessionId !== null && providerSessionId !== nativeSessionId
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 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)
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
- const candidates = rawRows.flatMap((row) => {
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
- const candidateSession = cleanText(record.session);
328
- const nativeSession = cleanText(record.native_session);
329
- const agent = cleanText(record.agent);
330
- const acceptedAgents = sessionAgent === null ? [] : MAGPIE_USAGE_AGENT_ALIASES[sessionAgent] ?? [sessionAgent];
331
- if (sessionId !== null
332
- && (candidateSession !== sessionId && nativeSession !== sessionId || agent === null || !acceptedAgents.includes(agent)))
333
- return [];
334
- const item = parseMagpieRequest(record);
335
- return item === null ? [] : [sessionId === null ? item : { ...item, sessionId }];
336
- });
337
- const total = sessionId === null ? safeCount(root?.total) : null;
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: total === null ? rawRows.length >= options.limit : options.offset + rawRows.length < total,
437
+ hasMore: !fullyScanned || matching.length > requestedEnd,
344
438
  sessionId,
345
439
  sessionAgent,
346
- items: candidates.slice(0, options.limit),
440
+ aggregate: await aggregatePromise,
441
+ items: matching.slice(options.offset, requestedEnd),
347
442
  };
348
443
  }
349
- async readFromWeb(agent, modelId, sessionId, magpieSessionId, fallbackSessionId, signal, includeRoute) {
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 = usageResult.ok && magpieSessionId !== null
357
- ? readSession(usageResult.value, agent, magpieSessionId)
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
- const fallbackSession = primarySession === null && usageResult.ok && fallbackSessionId !== null
360
- ? readSession(usageResult.value, agent, fallbackSessionId)
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 = primarySession !== null ? magpieSessionId : fallbackSession !== null ? fallbackSessionId : magpieSessionId;
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=today&offset=0&limit=100&q=${encodeURIComponent(resolvedSessionId)}`, signal)
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) || bindings.length === 0)
1013
+ if (!Array.isArray(dashboard.sessions))
868
1014
  return dashboard;
869
- const names = new Map();
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
- if (name === null || dshSessionId === null)
1020
+ const name = cleanSessionName(binding.name);
1021
+ if (dshSessionId === null)
875
1022
  continue;
876
- // Match the identity strategy used by compact usage: native DSH uses its
877
- // DSH ID, Command Code may use either injected DSH or ACP ID, and other
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((binding.adapterId === 'dsh'
881
- ? [dshSessionId]
882
- : binding.adapterId === 'command-code'
883
- ? [dshSessionId, providerSessionId]
884
- : [providerSessionId]).filter((value) => value !== null));
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 = names.get(key);
891
- if (previous !== undefined && previous !== name) {
892
- names.delete(key);
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
- names.set(key, name);
1057
+ owners.set(key, currentOwner);
897
1058
  }
898
1059
  }
899
1060
  }
900
1061
  }
901
- if (names.size === 0)
902
- return dashboard;
903
- let changed = false;
904
- const sessions = dashboard.sessions.map((session) => {
905
- if (session.dshSessionName !== undefined)
906
- return session;
907
- const name = names.get(`${session.agent}\u0000${session.id}`);
908
- if (name === undefined)
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
- changed = true;
911
- return { ...session, dshSessionName: name };
1096
+ return { ...session, sessionIds: [...ids] };
912
1097
  });
913
- return changed ? { ...dashboard, sessions } : dashboard;
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 = asRecord(value);
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 = asRecord(value)?.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 cleanText(record?.id) === sessionId && usageAgent !== null && usageAgents.includes(usageAgent);
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) || provider === null)
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 parseMagpieRequest(record) {
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 = routeText(record.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: routeText(record.providerAccount),
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]);