sidekick-shared 0.28.3 → 0.29.1

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 (151) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +188 -27
  3. package/dist/aggregation/EventAggregator.d.ts +19 -0
  4. package/dist/aggregation/EventAggregator.js +174 -35
  5. package/dist/aggregation/snapshot.d.ts +37 -8
  6. package/dist/aggregation/snapshot.js +31 -9
  7. package/dist/aggregation/types.d.ts +13 -0
  8. package/dist/authStatus.d.ts +54 -0
  9. package/dist/authStatus.js +91 -0
  10. package/dist/browser.d.ts +16 -6
  11. package/dist/browser.js +24 -2
  12. package/dist/codex.d.ts +23 -0
  13. package/dist/codex.js +44 -0
  14. package/dist/codexProfiles.d.ts +1 -0
  15. package/dist/codexProfiles.js +8 -11
  16. package/dist/context/sessionContext.js +4 -0
  17. package/dist/esm/aggregation/EventAggregator.js +174 -35
  18. package/dist/esm/aggregation/snapshot.js +31 -9
  19. package/dist/esm/authStatus.js +87 -0
  20. package/dist/esm/browser.js +5 -2
  21. package/dist/esm/codex.js +18 -0
  22. package/dist/esm/codexProfiles.js +8 -11
  23. package/dist/esm/context/sessionContext.js +4 -0
  24. package/dist/esm/formatters/toolSummary.js +4 -1
  25. package/dist/esm/formatting.js +10 -1
  26. package/dist/esm/index.js +11 -6
  27. package/dist/esm/modelCatalog.js +7 -5
  28. package/dist/esm/modelContext.js +4 -3
  29. package/dist/esm/modelInfo.js +425 -47
  30. package/dist/esm/node.js +9 -3
  31. package/dist/esm/observedContextWindows.js +2 -1
  32. package/dist/esm/ownKey.js +26 -0
  33. package/dist/esm/parsers/codexParser.js +351 -54
  34. package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
  35. package/dist/esm/parsers/openCodeParser.js +85 -22
  36. package/dist/esm/parsers/subagentScanner.js +33 -12
  37. package/dist/esm/parsers/subagentTraceParser.js +97 -37
  38. package/dist/esm/pricing.js +18 -0
  39. package/dist/esm/pricingCatalog.js +17 -9
  40. package/dist/esm/promptHistory.js +66 -4
  41. package/dist/esm/providerFailure.js +124 -3
  42. package/dist/esm/providers/claudeCode.js +10 -2
  43. package/dist/esm/providers/codex.js +84 -62
  44. package/dist/esm/providers/codexDatabase.js +3 -19
  45. package/dist/esm/providers/codexRollouts.js +24 -1
  46. package/dist/esm/providers/openCode.js +9 -17
  47. package/dist/esm/report/htmlReportGenerator.js +42 -5
  48. package/dist/esm/report/transcriptParser.js +13 -11
  49. package/dist/esm/schemas/providerFailure.js +3 -1
  50. package/dist/esm/schemas/sessionEvent.js +35 -14
  51. package/dist/esm/schemas/transcript.js +4 -0
  52. package/dist/esm/sessionPromptHistory.js +2 -2
  53. package/dist/esm/statusline/payload.js +9 -0
  54. package/dist/esm/subagentSpawn.js +163 -0
  55. package/dist/esm/testing/credentialGuard.js +87 -5
  56. package/dist/esm/toolCallOrigins.js +310 -0
  57. package/dist/esm/transcript.js +46 -3
  58. package/dist/esm/turns/assistantTurn.js +3 -2
  59. package/dist/esm/usage/claudeUsageDedupe.js +68 -0
  60. package/dist/esm/usage/usageEvents.js +146 -17
  61. package/dist/esm/usage/usageReports.js +39 -6
  62. package/dist/esm/watchers/eventBridge.js +15 -0
  63. package/dist/esm/watchers/factory.js +5 -3
  64. package/dist/esm/watchers/jsonlTail.js +442 -58
  65. package/dist/esm/watchers/jsonlWatcher.js +51 -4
  66. package/dist/esm/watchers/providerReaderWatcher.js +13 -0
  67. package/dist/esm/zaiQuota.js +2 -1
  68. package/dist/formatters/toolSummary.js +4 -1
  69. package/dist/formatting.d.ts +26 -1
  70. package/dist/formatting.js +11 -1
  71. package/dist/index.d.ts +22 -11
  72. package/dist/index.js +44 -12
  73. package/dist/modelCatalog.js +7 -5
  74. package/dist/modelContext.js +4 -3
  75. package/dist/modelInfo.d.ts +185 -16
  76. package/dist/modelInfo.js +434 -49
  77. package/dist/node.d.ts +16 -5
  78. package/dist/node.js +33 -2
  79. package/dist/observedContextWindows.js +2 -1
  80. package/dist/ownKey.d.ts +16 -0
  81. package/dist/ownKey.js +30 -0
  82. package/dist/parsers/codexParser.d.ts +118 -0
  83. package/dist/parsers/codexParser.js +352 -54
  84. package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
  85. package/dist/parsers/codexRolloutOrigin.js +111 -0
  86. package/dist/parsers/openCodeParser.d.ts +38 -0
  87. package/dist/parsers/openCodeParser.js +88 -22
  88. package/dist/parsers/subagentScanner.d.ts +19 -6
  89. package/dist/parsers/subagentScanner.js +33 -12
  90. package/dist/parsers/subagentTraceParser.d.ts +25 -5
  91. package/dist/parsers/subagentTraceParser.js +97 -37
  92. package/dist/pricing.d.ts +22 -0
  93. package/dist/pricing.js +50 -0
  94. package/dist/pricingCatalog.d.ts +10 -0
  95. package/dist/pricingCatalog.js +16 -8
  96. package/dist/promptHistory.d.ts +65 -4
  97. package/dist/promptHistory.js +68 -5
  98. package/dist/providerFailure.d.ts +44 -0
  99. package/dist/providerFailure.js +126 -4
  100. package/dist/providers/claudeCode.js +9 -1
  101. package/dist/providers/codex.d.ts +3 -22
  102. package/dist/providers/codex.js +85 -64
  103. package/dist/providers/codexDatabase.d.ts +3 -2
  104. package/dist/providers/codexDatabase.js +4 -22
  105. package/dist/providers/codexRollouts.d.ts +22 -0
  106. package/dist/providers/codexRollouts.js +24 -0
  107. package/dist/providers/openCode.js +8 -16
  108. package/dist/providers/types.d.ts +14 -0
  109. package/dist/report/htmlReportGenerator.js +42 -5
  110. package/dist/report/transcriptParser.d.ts +10 -1
  111. package/dist/report/transcriptParser.js +13 -11
  112. package/dist/schemas/index.d.ts +1 -0
  113. package/dist/schemas/providerFailure.d.ts +39 -0
  114. package/dist/schemas/providerFailure.js +2 -0
  115. package/dist/schemas/sessionEvent.d.ts +27 -10
  116. package/dist/schemas/sessionEvent.js +35 -14
  117. package/dist/schemas/transcript.d.ts +2 -0
  118. package/dist/schemas/transcript.js +4 -0
  119. package/dist/sessionPromptHistory.d.ts +7 -1
  120. package/dist/sessionPromptHistory.js +1 -1
  121. package/dist/statusline/payload.d.ts +10 -0
  122. package/dist/statusline/payload.js +14 -0
  123. package/dist/subagentSpawn.d.ts +80 -0
  124. package/dist/subagentSpawn.js +174 -0
  125. package/dist/testing/credentialGuard.d.ts +14 -0
  126. package/dist/testing/credentialGuard.js +87 -5
  127. package/dist/toolCallOrigins.d.ts +70 -0
  128. package/dist/toolCallOrigins.js +346 -0
  129. package/dist/transcript.d.ts +12 -0
  130. package/dist/transcript.js +46 -3
  131. package/dist/turns/assistantTurn.d.ts +1 -0
  132. package/dist/turns/assistantTurn.js +3 -2
  133. package/dist/types/opencode.d.ts +2 -0
  134. package/dist/usage/claudeUsageDedupe.d.ts +31 -0
  135. package/dist/usage/claudeUsageDedupe.js +68 -0
  136. package/dist/usage/usageEvents.d.ts +54 -6
  137. package/dist/usage/usageEvents.js +146 -17
  138. package/dist/usage/usageReports.d.ts +35 -4
  139. package/dist/usage/usageReports.js +39 -6
  140. package/dist/watchers/eventBridge.js +15 -0
  141. package/dist/watchers/factory.d.ts +6 -0
  142. package/dist/watchers/factory.js +5 -3
  143. package/dist/watchers/jsonlTail.d.ts +124 -4
  144. package/dist/watchers/jsonlTail.js +446 -59
  145. package/dist/watchers/jsonlWatcher.d.ts +18 -1
  146. package/dist/watchers/jsonlWatcher.js +50 -3
  147. package/dist/watchers/providerReaderWatcher.d.ts +7 -0
  148. package/dist/watchers/providerReaderWatcher.js +13 -0
  149. package/dist/watchers/types.d.ts +12 -0
  150. package/dist/zaiQuota.js +2 -1
  151. package/package.json +41 -1
@@ -18,6 +18,7 @@ exports.resolveCodexCallUsage = resolveCodexCallUsage;
18
18
  exports.normalizeCodexToolName = normalizeCodexToolName;
19
19
  exports.normalizeCodexToolInput = normalizeCodexToolInput;
20
20
  exports.extractPatchFilePaths = extractPatchFilePaths;
21
+ exports.codexTokenUsageFrom = codexTokenUsageFrom;
21
22
  const codex_1 = require("../types/codex");
22
23
  const openCodeParser_1 = require("./openCodeParser");
23
24
  const usageNormalization_1 = require("../usageNormalization");
@@ -178,6 +179,65 @@ function extractPatchFilePaths(input) {
178
179
  }
179
180
  /** Default cap on each piece of per-call correlation state the parser keeps. */
180
181
  exports.DEFAULT_MAX_TRACKED_TOOL_CALLS = 4096;
182
+ /** Entries per table {@link CodexRolloutParser.exportResumeState} keeps by default. */
183
+ const DEFAULT_RESUME_STATE_LIMIT = 256;
184
+ function isPlainObject(value) {
185
+ return !!value && typeof value === 'object' && !Array.isArray(value);
186
+ }
187
+ function cloneJson(value) {
188
+ return JSON.parse(JSON.stringify(value));
189
+ }
190
+ function isOptionalString(value) {
191
+ return value === undefined || typeof value === 'string';
192
+ }
193
+ function isStringArray(value) {
194
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
195
+ }
196
+ function isTokenCount(value) {
197
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
198
+ }
199
+ /** A valid `CodexTokenUsage` (copied), `null` for `null`, or `undefined` when invalid. Internal. */
200
+ function codexTokenUsageFrom(value) {
201
+ if (value === null)
202
+ return null;
203
+ if (!isPlainObject(value))
204
+ return undefined;
205
+ if (!isTokenCount(value.input_tokens) || !isTokenCount(value.output_tokens))
206
+ return undefined;
207
+ const usage = {
208
+ input_tokens: value.input_tokens,
209
+ output_tokens: value.output_tokens,
210
+ };
211
+ for (const field of ['cached_input_tokens', 'reasoning_output_tokens', 'total_tokens']) {
212
+ const count = value[field];
213
+ if (count === undefined)
214
+ continue;
215
+ if (!isTokenCount(count))
216
+ return undefined;
217
+ usage[field] = count;
218
+ }
219
+ return usage;
220
+ }
221
+ /** Last `limit` items of an insertion-ordered collection, oldest first. */
222
+ function newest(items, size, limit) {
223
+ const count = limit >= 0 ? Math.min(size, Math.floor(limit)) : 0;
224
+ const kept = [];
225
+ let skip = size - count;
226
+ for (const item of items) {
227
+ if (kept.length >= count)
228
+ break;
229
+ if (skip > 0) {
230
+ skip -= 1;
231
+ continue;
232
+ }
233
+ kept.push(item);
234
+ }
235
+ return kept;
236
+ }
237
+ /** Fixed per-entry cost added to each pending call's byte estimate. */
238
+ const PENDING_ENTRY_OVERHEAD_BYTES = 64;
239
+ /** Prefix of the bookkeeping keys the parser stashes inside tool inputs. */
240
+ const SIDEKICK_INPUT_KEY_PREFIX = '_sidekick';
181
241
  /** Adds `key`, refreshing its position, and evicts the oldest past `max`. */
182
242
  function rememberBounded(set, key, max) {
183
243
  set.delete(key);
@@ -185,12 +245,31 @@ function rememberBounded(set, key, max) {
185
245
  while (set.size > max)
186
246
  set.delete(set.values().next().value);
187
247
  }
188
- /** Sets `key`, refreshing its position, and evicts the oldest past `max`. */
189
- function setBounded(map, key, value, max) {
190
- map.delete(key);
191
- map.set(key, value);
192
- while (map.size > max)
193
- map.delete(map.keys().next().value);
248
+ function stringBytes(...values) {
249
+ let total = 0;
250
+ for (const value of values)
251
+ if (typeof value === 'string')
252
+ total += value.length;
253
+ return total;
254
+ }
255
+ function approxJsonBytes(value) {
256
+ if (value === undefined)
257
+ return 0;
258
+ try {
259
+ return JSON.stringify(value)?.length ?? 0;
260
+ }
261
+ catch {
262
+ return 0;
263
+ }
264
+ }
265
+ /** Only the `_sidekick*` bookkeeping keys of a tool input. */
266
+ function sidekickKeysOnly(input) {
267
+ const kept = {};
268
+ for (const key of Object.keys(input)) {
269
+ if (key.startsWith(SIDEKICK_INPUT_KEY_PREFIX))
270
+ kept[key] = input[key];
271
+ }
272
+ return kept;
194
273
  }
195
274
  /**
196
275
  * Stateful parser for Codex JSONL rollout lines.
@@ -216,10 +295,35 @@ class CodexRolloutParser {
216
295
  emittedToolUseIds = new Set();
217
296
  patchExpandedToolUseIds = new Map();
218
297
  maxTrackedToolCalls;
298
+ /** Byte budget for pending calls; `null` = unlimited (no accounting). */
299
+ maxPendingBytes;
300
+ metadataOnly;
301
+ /** Pending calls in insertion order, for the byte budget. */
302
+ pendingLedger = new Map();
303
+ pendingBytes = 0;
304
+ evictedByCount = 0;
305
+ evictedByBytes = 0;
219
306
  constructor(options = {}) {
220
307
  const max = options.maxTrackedToolCalls;
221
308
  this.maxTrackedToolCalls =
222
309
  typeof max === 'number' && max >= 1 ? Math.floor(max) : exports.DEFAULT_MAX_TRACKED_TOOL_CALLS;
310
+ const maxBytes = options.maxPendingBytes;
311
+ this.maxPendingBytes =
312
+ typeof maxBytes === 'number' && Number.isFinite(maxBytes) && maxBytes >= 1
313
+ ? Math.floor(maxBytes)
314
+ : null;
315
+ this.metadataOnly = options.metadataOnly === true;
316
+ }
317
+ /** Counters for the pending tool-call state and what the caps evicted. */
318
+ getPendingStats() {
319
+ return {
320
+ pendingCalls: this.pendingExecCommands.size +
321
+ this.pendingMcpToolCalls.size +
322
+ this.patchExpandedToolUseIds.size,
323
+ pendingBytes: this.maxPendingBytes === null ? null : this.pendingBytes,
324
+ evictedByCount: this.evictedByCount,
325
+ evictedByBytes: this.evictedByBytes,
326
+ };
223
327
  }
224
328
  /** Get stored session metadata. */
225
329
  getSessionMeta() {
@@ -241,6 +345,118 @@ class CodexRolloutParser {
241
345
  getLastRateLimits() {
242
346
  return this.lastRateLimits;
243
347
  }
348
+ /**
349
+ * The state later lines depend on — session metadata, current model,
350
+ * previous cumulative token total, context window, rate limits, plan mode,
351
+ * and the tool-call correlation tables (newest `limit` entries each) — as
352
+ * JSON-serializable data. Eviction counters are not included.
353
+ */
354
+ exportResumeState(limit = DEFAULT_RESUME_STATE_LIMIT) {
355
+ return cloneJson({
356
+ version: 1,
357
+ sessionMeta: this.sessionMeta,
358
+ currentModel: this.currentModel,
359
+ prevTotalUsage: this.prevTotalUsage,
360
+ lastTokenUsage: this.lastTokenUsage,
361
+ modelContextWindow: this.modelContextWindow,
362
+ lastRateLimits: this.lastRateLimits,
363
+ hasAggregateRateLimits: this.hasAggregateRateLimits,
364
+ inPlanMode: this.inPlanMode,
365
+ emittedToolUseIds: newest(this.emittedToolUseIds, this.emittedToolUseIds.size, limit),
366
+ patchExpandedToolUseIds: newest(this.patchExpandedToolUseIds, this.patchExpandedToolUseIds.size, limit),
367
+ pendingExecCommands: newest(this.pendingExecCommands.values(), this.pendingExecCommands.size, limit),
368
+ pendingMcpToolCalls: newest(this.pendingMcpToolCalls.values(), this.pendingMcpToolCalls.size, limit),
369
+ });
370
+ }
371
+ /**
372
+ * Replaces this parser's state with one from {@link exportResumeState}, as
373
+ * if it had parsed the rollout up to where that state was taken. Returns
374
+ * false, changing nothing, when `state` is not a valid version-1 state.
375
+ * This parser's own `maxTrackedToolCalls`, `maxPendingBytes`, and
376
+ * `metadataOnly` options apply to the imported tables.
377
+ */
378
+ importResumeState(state) {
379
+ if (!isPlainObject(state) || state.version !== 1)
380
+ return false;
381
+ const s = state;
382
+ const prevTotalUsage = codexTokenUsageFrom(s.prevTotalUsage);
383
+ const lastTokenUsage = codexTokenUsageFrom(s.lastTokenUsage);
384
+ if (prevTotalUsage === undefined || lastTokenUsage === undefined)
385
+ return false;
386
+ if (s.sessionMeta !== null && !isPlainObject(s.sessionMeta))
387
+ return false;
388
+ if (s.lastRateLimits !== null && !isPlainObject(s.lastRateLimits))
389
+ return false;
390
+ if (s.currentModel !== null && typeof s.currentModel !== 'string')
391
+ return false;
392
+ const contextWindow = s.modelContextWindow;
393
+ if (contextWindow !== null &&
394
+ !(typeof contextWindow === 'number' && Number.isFinite(contextWindow) && contextWindow > 0)) {
395
+ return false;
396
+ }
397
+ if (typeof s.hasAggregateRateLimits !== 'boolean' || typeof s.inPlanMode !== 'boolean') {
398
+ return false;
399
+ }
400
+ if (!isStringArray(s.emittedToolUseIds))
401
+ return false;
402
+ const patches = s.patchExpandedToolUseIds;
403
+ if (!Array.isArray(patches) ||
404
+ !patches.every((entry) => Array.isArray(entry) &&
405
+ entry.length === 2 &&
406
+ typeof entry[0] === 'string' &&
407
+ isStringArray(entry[1]))) {
408
+ return false;
409
+ }
410
+ const execs = s.pendingExecCommands;
411
+ if (!Array.isArray(execs) ||
412
+ !execs.every((entry) => isPlainObject(entry) &&
413
+ typeof entry.call_id === 'string' &&
414
+ isStringArray(entry.command) &&
415
+ isOptionalString(entry.workdir) &&
416
+ typeof entry.timestamp === 'string')) {
417
+ return false;
418
+ }
419
+ const mcps = s.pendingMcpToolCalls;
420
+ if (!Array.isArray(mcps) ||
421
+ !mcps.every((entry) => isPlainObject(entry) &&
422
+ typeof entry.call_id === 'string' &&
423
+ typeof entry.tool_name === 'string' &&
424
+ isOptionalString(entry.server_name) &&
425
+ (entry.arguments === undefined || isPlainObject(entry.arguments)) &&
426
+ typeof entry.timestamp === 'string')) {
427
+ return false;
428
+ }
429
+ const copy = cloneJson({
430
+ sessionMeta: s.sessionMeta,
431
+ lastRateLimits: s.lastRateLimits,
432
+ patches: patches,
433
+ execs: execs,
434
+ mcps: mcps,
435
+ });
436
+ this.reset();
437
+ this.sessionMeta = copy.sessionMeta;
438
+ this.currentModel = s.currentModel;
439
+ this.prevTotalUsage = prevTotalUsage;
440
+ this.lastTokenUsage = lastTokenUsage;
441
+ this.modelContextWindow = contextWindow;
442
+ this.lastRateLimits = copy.lastRateLimits;
443
+ this.hasAggregateRateLimits = s.hasAggregateRateLimits;
444
+ this.inPlanMode = s.inPlanMode;
445
+ for (const id of s.emittedToolUseIds) {
446
+ rememberBounded(this.emittedToolUseIds, id, this.maxTrackedToolCalls);
447
+ }
448
+ for (const [callId, toolUseIds] of copy.patches)
449
+ this.putPendingPatch(callId, toolUseIds);
450
+ for (const pending of copy.execs) {
451
+ this.putPendingExec(this.metadataOnly
452
+ ? { call_id: pending.call_id, command: [], timestamp: pending.timestamp }
453
+ : pending);
454
+ }
455
+ for (const pending of copy.mcps) {
456
+ this.putPendingMcp(this.metadataOnly ? { ...pending, arguments: undefined } : pending);
457
+ }
458
+ return true;
459
+ }
244
460
  /**
245
461
  * Converts a single rollout line to zero or more session events.
246
462
  */
@@ -274,6 +490,82 @@ class CodexRolloutParser {
274
490
  this.inPlanMode = false;
275
491
  this.emittedToolUseIds.clear();
276
492
  this.patchExpandedToolUseIds.clear();
493
+ this.pendingLedger.clear();
494
+ this.pendingBytes = 0;
495
+ this.evictedByCount = 0;
496
+ this.evictedByBytes = 0;
497
+ }
498
+ // --- Pending call state ---
499
+ /**
500
+ * Stores a pending call, refreshing its position, then enforces the count
501
+ * cap on its table and the byte budget across all pending tables, oldest
502
+ * first.
503
+ */
504
+ putPending(table, callId, value, estimateBytes) {
505
+ this.forgetPendingBytes(table, callId);
506
+ table.delete(callId);
507
+ table.set(callId, value);
508
+ while (table.size > this.maxTrackedToolCalls) {
509
+ const oldest = table.keys().next().value;
510
+ table.delete(oldest);
511
+ this.forgetPendingBytes(table, oldest);
512
+ this.evictedByCount += 1;
513
+ }
514
+ if (this.maxPendingBytes === null)
515
+ return;
516
+ const bytes = PENDING_ENTRY_OVERHEAD_BYTES + estimateBytes();
517
+ this.pendingLedger.set(this.ledgerKey(table, callId), {
518
+ table: table,
519
+ callId,
520
+ bytes,
521
+ });
522
+ this.pendingBytes += bytes;
523
+ while (this.pendingBytes > this.maxPendingBytes && this.pendingLedger.size > 0) {
524
+ const [key, oldest] = this.pendingLedger.entries().next().value;
525
+ this.pendingLedger.delete(key);
526
+ this.pendingBytes -= oldest.bytes;
527
+ oldest.table.delete(oldest.callId);
528
+ this.evictedByBytes += 1;
529
+ }
530
+ }
531
+ putPendingExec(pending) {
532
+ this.putPending(this.pendingExecCommands, pending.call_id, pending, () => stringBytes(pending.call_id, pending.workdir, pending.timestamp, ...(Array.isArray(pending.command) ? pending.command : [])));
533
+ }
534
+ putPendingMcp(pending) {
535
+ this.putPending(this.pendingMcpToolCalls, pending.call_id, pending, () => stringBytes(pending.call_id, pending.server_name, pending.tool_name, pending.timestamp) +
536
+ approxJsonBytes(pending.arguments));
537
+ }
538
+ putPendingPatch(callId, toolUseIds) {
539
+ this.putPending(this.patchExpandedToolUseIds, callId, toolUseIds, () => stringBytes(callId, ...toolUseIds));
540
+ }
541
+ /** Removes and returns a pending call. */
542
+ takePending(table, callId) {
543
+ const value = table.get(callId);
544
+ table.delete(callId);
545
+ this.forgetPendingBytes(table, callId);
546
+ return value;
547
+ }
548
+ forgetPendingBytes(table, callId) {
549
+ if (this.maxPendingBytes === null)
550
+ return;
551
+ const key = this.ledgerKey(table, callId);
552
+ const entry = this.pendingLedger.get(key);
553
+ if (!entry)
554
+ return;
555
+ this.pendingLedger.delete(key);
556
+ this.pendingBytes -= entry.bytes;
557
+ }
558
+ ledgerKey(table, callId) {
559
+ const kind = table === this.pendingExecCommands ? 'e' : table === this.pendingMcpToolCalls ? 'm' : 'p';
560
+ return `${kind}\u0000${callId}`;
561
+ }
562
+ /** The emitted tool input: unchanged, or only its `_sidekick*` keys in metadata-only mode. */
563
+ toolInput(input) {
564
+ return this.metadataOnly ? sidekickKeysOnly(input) : input;
565
+ }
566
+ /** The emitted tool-result content: unchanged, or empty in metadata-only mode. */
567
+ toolOutput(content) {
568
+ return this.metadataOnly ? '' : content;
277
569
  }
278
570
  // --- Handlers ---
279
571
  handleSessionMeta(timestamp, payload) {
@@ -380,7 +672,9 @@ class CodexRolloutParser {
380
672
  role: 'assistant',
381
673
  id: item.id,
382
674
  model: this.currentModel || undefined,
383
- content: [{ type: 'thinking', thinking: summaryTexts.join('\n') }],
675
+ content: [
676
+ { type: 'thinking', thinking: this.metadataOnly ? '' : summaryTexts.join('\n') },
677
+ ],
384
678
  },
385
679
  timestamp,
386
680
  },
@@ -388,11 +682,13 @@ class CodexRolloutParser {
388
682
  }
389
683
  handleFunctionCall(timestamp, item) {
390
684
  let parsedArgs = {};
391
- try {
392
- parsedArgs = JSON.parse(item.arguments);
393
- }
394
- catch {
395
- parsedArgs = { raw: item.arguments };
685
+ if (!this.metadataOnly) {
686
+ try {
687
+ parsedArgs = JSON.parse(item.arguments);
688
+ }
689
+ catch {
690
+ parsedArgs = { raw: item.arguments };
691
+ }
396
692
  }
397
693
  const canonicalName = normalizeCodexToolName(item.name);
398
694
  const normalizedInput = normalizeCodexToolInput(canonicalName, item.name, parsedArgs);
@@ -409,7 +705,7 @@ class CodexRolloutParser {
409
705
  type: 'tool_use',
410
706
  id: item.call_id,
411
707
  name: canonicalName,
412
- input: normalizedInput,
708
+ input: this.toolInput(normalizedInput),
413
709
  },
414
710
  ],
415
711
  },
@@ -429,7 +725,7 @@ class CodexRolloutParser {
429
725
  {
430
726
  type: 'tool_result',
431
727
  tool_use_id: item.call_id,
432
- content: item.output,
728
+ content: this.toolOutput(item.output),
433
729
  is_error: isError,
434
730
  },
435
731
  ],
@@ -453,11 +749,11 @@ class CodexRolloutParser {
453
749
  type: 'tool_use',
454
750
  id: item.call_id,
455
751
  name: 'Bash',
456
- input: {
752
+ input: this.toolInput({
457
753
  command,
458
754
  workdir: item.action?.workdir,
459
755
  _sidekickRawToolName: 'local_shell_call',
460
- },
756
+ }),
461
757
  },
462
758
  ],
463
759
  },
@@ -471,7 +767,7 @@ class CodexRolloutParser {
471
767
  if (filePaths.length === 0)
472
768
  return [];
473
769
  const toolUseIds = filePaths.map((fp) => `${item.call_id}-${fp}`);
474
- setBounded(this.patchExpandedToolUseIds, item.call_id, toolUseIds, this.maxTrackedToolCalls);
770
+ this.putPendingPatch(item.call_id, toolUseIds);
475
771
  for (const id of toolUseIds) {
476
772
  rememberBounded(this.emittedToolUseIds, id, this.maxTrackedToolCalls);
477
773
  }
@@ -486,10 +782,10 @@ class CodexRolloutParser {
486
782
  type: 'tool_use',
487
783
  id: `${item.call_id}-${fp}`,
488
784
  name: 'Edit',
489
- input: {
785
+ input: this.toolInput({
490
786
  file_path: fp,
491
787
  _sidekickRawToolName: 'apply_patch',
492
- },
788
+ }),
493
789
  },
494
790
  ],
495
791
  },
@@ -497,12 +793,14 @@ class CodexRolloutParser {
497
793
  }));
498
794
  }
499
795
  // Generic custom tool
500
- let parsedInput;
501
- try {
502
- parsedInput = JSON.parse(item.input);
503
- }
504
- catch {
505
- parsedInput = { raw: item.input };
796
+ let parsedInput = {};
797
+ if (!this.metadataOnly) {
798
+ try {
799
+ parsedInput = JSON.parse(item.input);
800
+ }
801
+ catch {
802
+ parsedInput = { raw: item.input };
803
+ }
506
804
  }
507
805
  const canonicalName = normalizeCodexToolName(item.name);
508
806
  const normalizedInput = normalizeCodexToolInput(canonicalName, item.name, parsedInput);
@@ -519,7 +817,7 @@ class CodexRolloutParser {
519
817
  type: 'tool_use',
520
818
  id: item.call_id,
521
819
  name: canonicalName,
522
- input: normalizedInput,
820
+ input: this.toolInput(normalizedInput),
523
821
  },
524
822
  ],
525
823
  },
@@ -540,8 +838,9 @@ class CodexRolloutParser {
540
838
  catch {
541
839
  isError = parsePlainTextFailure(item.output);
542
840
  }
543
- const toolUseIds = this.patchExpandedToolUseIds.get(item.call_id) ?? [item.call_id];
544
- this.patchExpandedToolUseIds.delete(item.call_id);
841
+ const toolUseIds = this.takePending(this.patchExpandedToolUseIds, item.call_id) ?? [
842
+ item.call_id,
843
+ ];
545
844
  return toolUseIds.map((toolUseId) => ({
546
845
  type: 'user',
547
846
  message: {
@@ -551,7 +850,7 @@ class CodexRolloutParser {
551
850
  {
552
851
  type: 'tool_result',
553
852
  tool_use_id: toolUseId,
554
- content: item.output,
853
+ content: this.toolOutput(item.output),
555
854
  is_error: isError,
556
855
  duration,
557
856
  },
@@ -615,18 +914,15 @@ class CodexRolloutParser {
615
914
  // response_item/message events which carry richer metadata (id, content parts, role).
616
915
  case 'exec_command_begin': {
617
916
  const e = event;
618
- setBounded(this.pendingExecCommands, e.call_id, {
619
- call_id: e.call_id,
620
- command: e.command,
621
- workdir: e.workdir,
622
- timestamp,
623
- }, this.maxTrackedToolCalls);
917
+ const pending = this.metadataOnly
918
+ ? { call_id: e.call_id, command: [], timestamp }
919
+ : { call_id: e.call_id, command: e.command, workdir: e.workdir, timestamp };
920
+ this.putPendingExec(pending);
624
921
  return [];
625
922
  }
626
923
  case 'exec_command_end': {
627
924
  const e = event;
628
- const pending = this.pendingExecCommands.get(e.call_id);
629
- this.pendingExecCommands.delete(e.call_id);
925
+ const pending = this.takePending(this.pendingExecCommands, e.call_id);
630
926
  const command = pending?.command?.join(' ') || '';
631
927
  const events = [];
632
928
  if (!this.emittedToolUseIds.has(e.call_id)) {
@@ -642,18 +938,20 @@ class CodexRolloutParser {
642
938
  type: 'tool_use',
643
939
  id: e.call_id,
644
940
  name: 'Bash',
645
- input: {
941
+ input: this.toolInput({
646
942
  command,
647
943
  workdir: pending?.workdir,
648
944
  _sidekickRawToolName: 'exec_command',
649
- },
945
+ }),
650
946
  },
651
947
  ],
652
948
  },
653
949
  timestamp: pending?.timestamp || timestamp,
654
950
  });
655
951
  }
656
- const output = [e.stdout, e.stderr].filter(Boolean).join('\n') || '';
952
+ const output = this.metadataOnly
953
+ ? ''
954
+ : [e.stdout, e.stderr].filter(Boolean).join('\n') || '';
657
955
  events.push({
658
956
  type: 'user',
659
957
  message: {
@@ -675,19 +973,19 @@ class CodexRolloutParser {
675
973
  }
676
974
  case 'mcp_tool_call_begin': {
677
975
  const e = event;
678
- setBounded(this.pendingMcpToolCalls, e.call_id, {
976
+ const pending = {
679
977
  call_id: e.call_id,
680
978
  server_name: e.server_name,
681
979
  tool_name: e.tool_name,
682
- arguments: e.arguments,
980
+ arguments: this.metadataOnly ? undefined : e.arguments,
683
981
  timestamp,
684
- }, this.maxTrackedToolCalls);
982
+ };
983
+ this.putPendingMcp(pending);
685
984
  return [];
686
985
  }
687
986
  case 'mcp_tool_call_end': {
688
987
  const e = event;
689
- const pendingMcp = this.pendingMcpToolCalls.get(e.call_id);
690
- this.pendingMcpToolCalls.delete(e.call_id);
988
+ const pendingMcp = this.takePending(this.pendingMcpToolCalls, e.call_id);
691
989
  const toolName = pendingMcp?.tool_name || 'McpTool';
692
990
  const events = [];
693
991
  if (!this.emittedToolUseIds.has(e.call_id)) {
@@ -703,11 +1001,11 @@ class CodexRolloutParser {
703
1001
  type: 'tool_use',
704
1002
  id: e.call_id,
705
1003
  name: normalizeCodexToolName(toolName),
706
- input: {
1004
+ input: this.toolInput({
707
1005
  ...(pendingMcp?.arguments || {}),
708
1006
  _sidekickRawToolName: toolName,
709
1007
  _sidekickMcpServerName: pendingMcp?.server_name,
710
- },
1008
+ }),
711
1009
  },
712
1010
  ],
713
1011
  },
@@ -723,7 +1021,7 @@ class CodexRolloutParser {
723
1021
  {
724
1022
  type: 'tool_result',
725
1023
  tool_use_id: e.call_id,
726
- content: e.result || '',
1024
+ content: this.toolOutput(e.result || ''),
727
1025
  is_error: e.is_error || false,
728
1026
  duration: e.duration_ms,
729
1027
  },
@@ -782,12 +1080,12 @@ class CodexRolloutParser {
782
1080
  type: 'tool_use',
783
1081
  id: patchId,
784
1082
  name: 'Edit',
785
- input: {
1083
+ input: this.toolInput({
786
1084
  file_path: e.file_path,
787
1085
  additions: e.additions ?? 0,
788
1086
  deletions: e.deletions ?? 0,
789
1087
  _sidekickRawToolName: 'patch_applied',
790
- },
1088
+ }),
791
1089
  },
792
1090
  ],
793
1091
  },
@@ -814,10 +1112,10 @@ class CodexRolloutParser {
814
1112
  type: 'tool_use',
815
1113
  id: `plan-enter-${timestamp}`,
816
1114
  name: 'EnterPlanMode',
817
- input: {
1115
+ input: this.toolInput({
818
1116
  source: 'codex_task_started',
819
1117
  _sidekickRawToolName: 'task_started',
820
- },
1118
+ }),
821
1119
  },
822
1120
  ],
823
1121
  },
@@ -841,10 +1139,10 @@ class CodexRolloutParser {
841
1139
  type: 'tool_use',
842
1140
  id: `plan-exit-${timestamp}`,
843
1141
  name: 'ExitPlanMode',
844
- input: {
1142
+ input: this.toolInput({
845
1143
  source: 'codex_task_complete',
846
1144
  _sidekickRawToolName: 'task_complete',
847
- },
1145
+ }),
848
1146
  },
849
1147
  ],
850
1148
  },
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Where a Codex rollout came from, read from its `session_meta` record:
3
+ * a session a person started, a fork of another session, or a subagent.
4
+ *
5
+ * Codex writes `session_meta.source` as a plain string for sessions started
6
+ * from a surface (`cli`, `vscode`, `exec`, …) and as an object for subagents:
7
+ * `{ subagent: { thread_spawn: { parent_thread_id, … } } }` for a spawned
8
+ * agent, `{ subagent: { other: 'guardian' } }` for internal reviewers, or
9
+ * `{ subagent: 'review' }`. The same JSON appears as a string in the state
10
+ * database's `threads.source`. A forked rollout records `forked_from_id`, and
11
+ * embeds its parent's `session_meta` after its own, so only the first counts.
12
+ *
13
+ * Pure: no filesystem access. Browser-safe.
14
+ *
15
+ * @module parsers/codexRolloutOrigin
16
+ */
17
+ export interface CodexRolloutOrigin {
18
+ /** `session_meta.forked_from_id`: the session this rollout was forked from. */
19
+ forkedFromId?: string;
20
+ /**
21
+ * `session_meta.source` when it is a string (`cli`, `vscode`, `exec`, …),
22
+ * or `'subagent'` when it is a subagent object.
23
+ */
24
+ source?: string;
25
+ /** The rollout was written by a subagent (`source: { subagent: … }`), not a person. */
26
+ isSubagent: boolean;
27
+ /**
28
+ * The parent session: `forkedFromId`, else the spawn's `parent_thread_id`,
29
+ * else (subagents only) the top-level `parent_thread_id`.
30
+ */
31
+ parentId?: string;
32
+ /**
33
+ * Subagent variant: `thread_spawn`, `review`, `compact`, or the name of an
34
+ * `other` subagent (for example `guardian`).
35
+ */
36
+ subagentKind?: string;
37
+ }
38
+ /**
39
+ * Parent thread id of a spawned subagent's `source` (object, or JSON text as
40
+ * stored in `threads.source`): `subagent.thread_spawn.parent_thread_id`.
41
+ */
42
+ export declare function codexSpawnParentFromSource(source: unknown): string | undefined;
43
+ /**
44
+ * Origin of a Codex rollout from its `session_meta`. Accepts the parsed
45
+ * `session_meta` record, its payload, or the rollout's records in order (the
46
+ * first `session_meta` wins: a fork embeds its parent's after its own).
47
+ * Anything else yields `{ isSubagent: false }`.
48
+ */
49
+ export declare function codexRolloutOrigin(input: unknown | readonly unknown[]): CodexRolloutOrigin;