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
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Own-property lookups for plain-object tables indexed by external strings.
3
+ *
4
+ * `table[key]` also finds inherited members, so a model id, tool name, or
5
+ * error code such as `constructor`, `toString`, or `__proto__` would return an
6
+ * `Object.prototype` member instead of `undefined`.
7
+ *
8
+ * @module ownKey
9
+ */
10
+ const hasOwn = Object.prototype.hasOwnProperty;
11
+ /** The value stored under `key` in `table` itself, or `undefined`. */
12
+ export function ownValue(table, key) {
13
+ return hasOwn.call(table, key) ? table[key] : undefined;
14
+ }
15
+ /**
16
+ * Store `value` as an own, enumerable property, even for `__proto__`, which a
17
+ * plain assignment would treat as a prototype change.
18
+ */
19
+ export function setOwnValue(table, key, value) {
20
+ Object.defineProperty(table, key, {
21
+ value,
22
+ enumerable: true,
23
+ writable: true,
24
+ configurable: true,
25
+ });
26
+ }
@@ -171,6 +171,65 @@ export function extractPatchFilePaths(input) {
171
171
  }
172
172
  /** Default cap on each piece of per-call correlation state the parser keeps. */
173
173
  export const DEFAULT_MAX_TRACKED_TOOL_CALLS = 4096;
174
+ /** Entries per table {@link CodexRolloutParser.exportResumeState} keeps by default. */
175
+ const DEFAULT_RESUME_STATE_LIMIT = 256;
176
+ function isPlainObject(value) {
177
+ return !!value && typeof value === 'object' && !Array.isArray(value);
178
+ }
179
+ function cloneJson(value) {
180
+ return JSON.parse(JSON.stringify(value));
181
+ }
182
+ function isOptionalString(value) {
183
+ return value === undefined || typeof value === 'string';
184
+ }
185
+ function isStringArray(value) {
186
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
187
+ }
188
+ function isTokenCount(value) {
189
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
190
+ }
191
+ /** A valid `CodexTokenUsage` (copied), `null` for `null`, or `undefined` when invalid. Internal. */
192
+ export function codexTokenUsageFrom(value) {
193
+ if (value === null)
194
+ return null;
195
+ if (!isPlainObject(value))
196
+ return undefined;
197
+ if (!isTokenCount(value.input_tokens) || !isTokenCount(value.output_tokens))
198
+ return undefined;
199
+ const usage = {
200
+ input_tokens: value.input_tokens,
201
+ output_tokens: value.output_tokens,
202
+ };
203
+ for (const field of ['cached_input_tokens', 'reasoning_output_tokens', 'total_tokens']) {
204
+ const count = value[field];
205
+ if (count === undefined)
206
+ continue;
207
+ if (!isTokenCount(count))
208
+ return undefined;
209
+ usage[field] = count;
210
+ }
211
+ return usage;
212
+ }
213
+ /** Last `limit` items of an insertion-ordered collection, oldest first. */
214
+ function newest(items, size, limit) {
215
+ const count = limit >= 0 ? Math.min(size, Math.floor(limit)) : 0;
216
+ const kept = [];
217
+ let skip = size - count;
218
+ for (const item of items) {
219
+ if (kept.length >= count)
220
+ break;
221
+ if (skip > 0) {
222
+ skip -= 1;
223
+ continue;
224
+ }
225
+ kept.push(item);
226
+ }
227
+ return kept;
228
+ }
229
+ /** Fixed per-entry cost added to each pending call's byte estimate. */
230
+ const PENDING_ENTRY_OVERHEAD_BYTES = 64;
231
+ /** Prefix of the bookkeeping keys the parser stashes inside tool inputs. */
232
+ const SIDEKICK_INPUT_KEY_PREFIX = '_sidekick';
174
233
  /** Adds `key`, refreshing its position, and evicts the oldest past `max`. */
175
234
  function rememberBounded(set, key, max) {
176
235
  set.delete(key);
@@ -178,12 +237,31 @@ function rememberBounded(set, key, max) {
178
237
  while (set.size > max)
179
238
  set.delete(set.values().next().value);
180
239
  }
181
- /** Sets `key`, refreshing its position, and evicts the oldest past `max`. */
182
- function setBounded(map, key, value, max) {
183
- map.delete(key);
184
- map.set(key, value);
185
- while (map.size > max)
186
- map.delete(map.keys().next().value);
240
+ function stringBytes(...values) {
241
+ let total = 0;
242
+ for (const value of values)
243
+ if (typeof value === 'string')
244
+ total += value.length;
245
+ return total;
246
+ }
247
+ function approxJsonBytes(value) {
248
+ if (value === undefined)
249
+ return 0;
250
+ try {
251
+ return JSON.stringify(value)?.length ?? 0;
252
+ }
253
+ catch {
254
+ return 0;
255
+ }
256
+ }
257
+ /** Only the `_sidekick*` bookkeeping keys of a tool input. */
258
+ function sidekickKeysOnly(input) {
259
+ const kept = {};
260
+ for (const key of Object.keys(input)) {
261
+ if (key.startsWith(SIDEKICK_INPUT_KEY_PREFIX))
262
+ kept[key] = input[key];
263
+ }
264
+ return kept;
187
265
  }
188
266
  /**
189
267
  * Stateful parser for Codex JSONL rollout lines.
@@ -209,10 +287,35 @@ export class CodexRolloutParser {
209
287
  emittedToolUseIds = new Set();
210
288
  patchExpandedToolUseIds = new Map();
211
289
  maxTrackedToolCalls;
290
+ /** Byte budget for pending calls; `null` = unlimited (no accounting). */
291
+ maxPendingBytes;
292
+ metadataOnly;
293
+ /** Pending calls in insertion order, for the byte budget. */
294
+ pendingLedger = new Map();
295
+ pendingBytes = 0;
296
+ evictedByCount = 0;
297
+ evictedByBytes = 0;
212
298
  constructor(options = {}) {
213
299
  const max = options.maxTrackedToolCalls;
214
300
  this.maxTrackedToolCalls =
215
301
  typeof max === 'number' && max >= 1 ? Math.floor(max) : DEFAULT_MAX_TRACKED_TOOL_CALLS;
302
+ const maxBytes = options.maxPendingBytes;
303
+ this.maxPendingBytes =
304
+ typeof maxBytes === 'number' && Number.isFinite(maxBytes) && maxBytes >= 1
305
+ ? Math.floor(maxBytes)
306
+ : null;
307
+ this.metadataOnly = options.metadataOnly === true;
308
+ }
309
+ /** Counters for the pending tool-call state and what the caps evicted. */
310
+ getPendingStats() {
311
+ return {
312
+ pendingCalls: this.pendingExecCommands.size +
313
+ this.pendingMcpToolCalls.size +
314
+ this.patchExpandedToolUseIds.size,
315
+ pendingBytes: this.maxPendingBytes === null ? null : this.pendingBytes,
316
+ evictedByCount: this.evictedByCount,
317
+ evictedByBytes: this.evictedByBytes,
318
+ };
216
319
  }
217
320
  /** Get stored session metadata. */
218
321
  getSessionMeta() {
@@ -234,6 +337,118 @@ export class CodexRolloutParser {
234
337
  getLastRateLimits() {
235
338
  return this.lastRateLimits;
236
339
  }
340
+ /**
341
+ * The state later lines depend on — session metadata, current model,
342
+ * previous cumulative token total, context window, rate limits, plan mode,
343
+ * and the tool-call correlation tables (newest `limit` entries each) — as
344
+ * JSON-serializable data. Eviction counters are not included.
345
+ */
346
+ exportResumeState(limit = DEFAULT_RESUME_STATE_LIMIT) {
347
+ return cloneJson({
348
+ version: 1,
349
+ sessionMeta: this.sessionMeta,
350
+ currentModel: this.currentModel,
351
+ prevTotalUsage: this.prevTotalUsage,
352
+ lastTokenUsage: this.lastTokenUsage,
353
+ modelContextWindow: this.modelContextWindow,
354
+ lastRateLimits: this.lastRateLimits,
355
+ hasAggregateRateLimits: this.hasAggregateRateLimits,
356
+ inPlanMode: this.inPlanMode,
357
+ emittedToolUseIds: newest(this.emittedToolUseIds, this.emittedToolUseIds.size, limit),
358
+ patchExpandedToolUseIds: newest(this.patchExpandedToolUseIds, this.patchExpandedToolUseIds.size, limit),
359
+ pendingExecCommands: newest(this.pendingExecCommands.values(), this.pendingExecCommands.size, limit),
360
+ pendingMcpToolCalls: newest(this.pendingMcpToolCalls.values(), this.pendingMcpToolCalls.size, limit),
361
+ });
362
+ }
363
+ /**
364
+ * Replaces this parser's state with one from {@link exportResumeState}, as
365
+ * if it had parsed the rollout up to where that state was taken. Returns
366
+ * false, changing nothing, when `state` is not a valid version-1 state.
367
+ * This parser's own `maxTrackedToolCalls`, `maxPendingBytes`, and
368
+ * `metadataOnly` options apply to the imported tables.
369
+ */
370
+ importResumeState(state) {
371
+ if (!isPlainObject(state) || state.version !== 1)
372
+ return false;
373
+ const s = state;
374
+ const prevTotalUsage = codexTokenUsageFrom(s.prevTotalUsage);
375
+ const lastTokenUsage = codexTokenUsageFrom(s.lastTokenUsage);
376
+ if (prevTotalUsage === undefined || lastTokenUsage === undefined)
377
+ return false;
378
+ if (s.sessionMeta !== null && !isPlainObject(s.sessionMeta))
379
+ return false;
380
+ if (s.lastRateLimits !== null && !isPlainObject(s.lastRateLimits))
381
+ return false;
382
+ if (s.currentModel !== null && typeof s.currentModel !== 'string')
383
+ return false;
384
+ const contextWindow = s.modelContextWindow;
385
+ if (contextWindow !== null &&
386
+ !(typeof contextWindow === 'number' && Number.isFinite(contextWindow) && contextWindow > 0)) {
387
+ return false;
388
+ }
389
+ if (typeof s.hasAggregateRateLimits !== 'boolean' || typeof s.inPlanMode !== 'boolean') {
390
+ return false;
391
+ }
392
+ if (!isStringArray(s.emittedToolUseIds))
393
+ return false;
394
+ const patches = s.patchExpandedToolUseIds;
395
+ if (!Array.isArray(patches) ||
396
+ !patches.every((entry) => Array.isArray(entry) &&
397
+ entry.length === 2 &&
398
+ typeof entry[0] === 'string' &&
399
+ isStringArray(entry[1]))) {
400
+ return false;
401
+ }
402
+ const execs = s.pendingExecCommands;
403
+ if (!Array.isArray(execs) ||
404
+ !execs.every((entry) => isPlainObject(entry) &&
405
+ typeof entry.call_id === 'string' &&
406
+ isStringArray(entry.command) &&
407
+ isOptionalString(entry.workdir) &&
408
+ typeof entry.timestamp === 'string')) {
409
+ return false;
410
+ }
411
+ const mcps = s.pendingMcpToolCalls;
412
+ if (!Array.isArray(mcps) ||
413
+ !mcps.every((entry) => isPlainObject(entry) &&
414
+ typeof entry.call_id === 'string' &&
415
+ typeof entry.tool_name === 'string' &&
416
+ isOptionalString(entry.server_name) &&
417
+ (entry.arguments === undefined || isPlainObject(entry.arguments)) &&
418
+ typeof entry.timestamp === 'string')) {
419
+ return false;
420
+ }
421
+ const copy = cloneJson({
422
+ sessionMeta: s.sessionMeta,
423
+ lastRateLimits: s.lastRateLimits,
424
+ patches: patches,
425
+ execs: execs,
426
+ mcps: mcps,
427
+ });
428
+ this.reset();
429
+ this.sessionMeta = copy.sessionMeta;
430
+ this.currentModel = s.currentModel;
431
+ this.prevTotalUsage = prevTotalUsage;
432
+ this.lastTokenUsage = lastTokenUsage;
433
+ this.modelContextWindow = contextWindow;
434
+ this.lastRateLimits = copy.lastRateLimits;
435
+ this.hasAggregateRateLimits = s.hasAggregateRateLimits;
436
+ this.inPlanMode = s.inPlanMode;
437
+ for (const id of s.emittedToolUseIds) {
438
+ rememberBounded(this.emittedToolUseIds, id, this.maxTrackedToolCalls);
439
+ }
440
+ for (const [callId, toolUseIds] of copy.patches)
441
+ this.putPendingPatch(callId, toolUseIds);
442
+ for (const pending of copy.execs) {
443
+ this.putPendingExec(this.metadataOnly
444
+ ? { call_id: pending.call_id, command: [], timestamp: pending.timestamp }
445
+ : pending);
446
+ }
447
+ for (const pending of copy.mcps) {
448
+ this.putPendingMcp(this.metadataOnly ? { ...pending, arguments: undefined } : pending);
449
+ }
450
+ return true;
451
+ }
237
452
  /**
238
453
  * Converts a single rollout line to zero or more session events.
239
454
  */
@@ -267,6 +482,82 @@ export class CodexRolloutParser {
267
482
  this.inPlanMode = false;
268
483
  this.emittedToolUseIds.clear();
269
484
  this.patchExpandedToolUseIds.clear();
485
+ this.pendingLedger.clear();
486
+ this.pendingBytes = 0;
487
+ this.evictedByCount = 0;
488
+ this.evictedByBytes = 0;
489
+ }
490
+ // --- Pending call state ---
491
+ /**
492
+ * Stores a pending call, refreshing its position, then enforces the count
493
+ * cap on its table and the byte budget across all pending tables, oldest
494
+ * first.
495
+ */
496
+ putPending(table, callId, value, estimateBytes) {
497
+ this.forgetPendingBytes(table, callId);
498
+ table.delete(callId);
499
+ table.set(callId, value);
500
+ while (table.size > this.maxTrackedToolCalls) {
501
+ const oldest = table.keys().next().value;
502
+ table.delete(oldest);
503
+ this.forgetPendingBytes(table, oldest);
504
+ this.evictedByCount += 1;
505
+ }
506
+ if (this.maxPendingBytes === null)
507
+ return;
508
+ const bytes = PENDING_ENTRY_OVERHEAD_BYTES + estimateBytes();
509
+ this.pendingLedger.set(this.ledgerKey(table, callId), {
510
+ table: table,
511
+ callId,
512
+ bytes,
513
+ });
514
+ this.pendingBytes += bytes;
515
+ while (this.pendingBytes > this.maxPendingBytes && this.pendingLedger.size > 0) {
516
+ const [key, oldest] = this.pendingLedger.entries().next().value;
517
+ this.pendingLedger.delete(key);
518
+ this.pendingBytes -= oldest.bytes;
519
+ oldest.table.delete(oldest.callId);
520
+ this.evictedByBytes += 1;
521
+ }
522
+ }
523
+ putPendingExec(pending) {
524
+ this.putPending(this.pendingExecCommands, pending.call_id, pending, () => stringBytes(pending.call_id, pending.workdir, pending.timestamp, ...(Array.isArray(pending.command) ? pending.command : [])));
525
+ }
526
+ putPendingMcp(pending) {
527
+ this.putPending(this.pendingMcpToolCalls, pending.call_id, pending, () => stringBytes(pending.call_id, pending.server_name, pending.tool_name, pending.timestamp) +
528
+ approxJsonBytes(pending.arguments));
529
+ }
530
+ putPendingPatch(callId, toolUseIds) {
531
+ this.putPending(this.patchExpandedToolUseIds, callId, toolUseIds, () => stringBytes(callId, ...toolUseIds));
532
+ }
533
+ /** Removes and returns a pending call. */
534
+ takePending(table, callId) {
535
+ const value = table.get(callId);
536
+ table.delete(callId);
537
+ this.forgetPendingBytes(table, callId);
538
+ return value;
539
+ }
540
+ forgetPendingBytes(table, callId) {
541
+ if (this.maxPendingBytes === null)
542
+ return;
543
+ const key = this.ledgerKey(table, callId);
544
+ const entry = this.pendingLedger.get(key);
545
+ if (!entry)
546
+ return;
547
+ this.pendingLedger.delete(key);
548
+ this.pendingBytes -= entry.bytes;
549
+ }
550
+ ledgerKey(table, callId) {
551
+ const kind = table === this.pendingExecCommands ? 'e' : table === this.pendingMcpToolCalls ? 'm' : 'p';
552
+ return `${kind}\u0000${callId}`;
553
+ }
554
+ /** The emitted tool input: unchanged, or only its `_sidekick*` keys in metadata-only mode. */
555
+ toolInput(input) {
556
+ return this.metadataOnly ? sidekickKeysOnly(input) : input;
557
+ }
558
+ /** The emitted tool-result content: unchanged, or empty in metadata-only mode. */
559
+ toolOutput(content) {
560
+ return this.metadataOnly ? '' : content;
270
561
  }
271
562
  // --- Handlers ---
272
563
  handleSessionMeta(timestamp, payload) {
@@ -373,7 +664,9 @@ export class CodexRolloutParser {
373
664
  role: 'assistant',
374
665
  id: item.id,
375
666
  model: this.currentModel || undefined,
376
- content: [{ type: 'thinking', thinking: summaryTexts.join('\n') }],
667
+ content: [
668
+ { type: 'thinking', thinking: this.metadataOnly ? '' : summaryTexts.join('\n') },
669
+ ],
377
670
  },
378
671
  timestamp,
379
672
  },
@@ -381,11 +674,13 @@ export class CodexRolloutParser {
381
674
  }
382
675
  handleFunctionCall(timestamp, item) {
383
676
  let parsedArgs = {};
384
- try {
385
- parsedArgs = JSON.parse(item.arguments);
386
- }
387
- catch {
388
- parsedArgs = { raw: item.arguments };
677
+ if (!this.metadataOnly) {
678
+ try {
679
+ parsedArgs = JSON.parse(item.arguments);
680
+ }
681
+ catch {
682
+ parsedArgs = { raw: item.arguments };
683
+ }
389
684
  }
390
685
  const canonicalName = normalizeCodexToolName(item.name);
391
686
  const normalizedInput = normalizeCodexToolInput(canonicalName, item.name, parsedArgs);
@@ -402,7 +697,7 @@ export class CodexRolloutParser {
402
697
  type: 'tool_use',
403
698
  id: item.call_id,
404
699
  name: canonicalName,
405
- input: normalizedInput,
700
+ input: this.toolInput(normalizedInput),
406
701
  },
407
702
  ],
408
703
  },
@@ -422,7 +717,7 @@ export class CodexRolloutParser {
422
717
  {
423
718
  type: 'tool_result',
424
719
  tool_use_id: item.call_id,
425
- content: item.output,
720
+ content: this.toolOutput(item.output),
426
721
  is_error: isError,
427
722
  },
428
723
  ],
@@ -446,11 +741,11 @@ export class CodexRolloutParser {
446
741
  type: 'tool_use',
447
742
  id: item.call_id,
448
743
  name: 'Bash',
449
- input: {
744
+ input: this.toolInput({
450
745
  command,
451
746
  workdir: item.action?.workdir,
452
747
  _sidekickRawToolName: 'local_shell_call',
453
- },
748
+ }),
454
749
  },
455
750
  ],
456
751
  },
@@ -464,7 +759,7 @@ export class CodexRolloutParser {
464
759
  if (filePaths.length === 0)
465
760
  return [];
466
761
  const toolUseIds = filePaths.map((fp) => `${item.call_id}-${fp}`);
467
- setBounded(this.patchExpandedToolUseIds, item.call_id, toolUseIds, this.maxTrackedToolCalls);
762
+ this.putPendingPatch(item.call_id, toolUseIds);
468
763
  for (const id of toolUseIds) {
469
764
  rememberBounded(this.emittedToolUseIds, id, this.maxTrackedToolCalls);
470
765
  }
@@ -479,10 +774,10 @@ export class CodexRolloutParser {
479
774
  type: 'tool_use',
480
775
  id: `${item.call_id}-${fp}`,
481
776
  name: 'Edit',
482
- input: {
777
+ input: this.toolInput({
483
778
  file_path: fp,
484
779
  _sidekickRawToolName: 'apply_patch',
485
- },
780
+ }),
486
781
  },
487
782
  ],
488
783
  },
@@ -490,12 +785,14 @@ export class CodexRolloutParser {
490
785
  }));
491
786
  }
492
787
  // Generic custom tool
493
- let parsedInput;
494
- try {
495
- parsedInput = JSON.parse(item.input);
496
- }
497
- catch {
498
- parsedInput = { raw: item.input };
788
+ let parsedInput = {};
789
+ if (!this.metadataOnly) {
790
+ try {
791
+ parsedInput = JSON.parse(item.input);
792
+ }
793
+ catch {
794
+ parsedInput = { raw: item.input };
795
+ }
499
796
  }
500
797
  const canonicalName = normalizeCodexToolName(item.name);
501
798
  const normalizedInput = normalizeCodexToolInput(canonicalName, item.name, parsedInput);
@@ -512,7 +809,7 @@ export class CodexRolloutParser {
512
809
  type: 'tool_use',
513
810
  id: item.call_id,
514
811
  name: canonicalName,
515
- input: normalizedInput,
812
+ input: this.toolInput(normalizedInput),
516
813
  },
517
814
  ],
518
815
  },
@@ -533,8 +830,9 @@ export class CodexRolloutParser {
533
830
  catch {
534
831
  isError = parsePlainTextFailure(item.output);
535
832
  }
536
- const toolUseIds = this.patchExpandedToolUseIds.get(item.call_id) ?? [item.call_id];
537
- this.patchExpandedToolUseIds.delete(item.call_id);
833
+ const toolUseIds = this.takePending(this.patchExpandedToolUseIds, item.call_id) ?? [
834
+ item.call_id,
835
+ ];
538
836
  return toolUseIds.map((toolUseId) => ({
539
837
  type: 'user',
540
838
  message: {
@@ -544,7 +842,7 @@ export class CodexRolloutParser {
544
842
  {
545
843
  type: 'tool_result',
546
844
  tool_use_id: toolUseId,
547
- content: item.output,
845
+ content: this.toolOutput(item.output),
548
846
  is_error: isError,
549
847
  duration,
550
848
  },
@@ -608,18 +906,15 @@ export class CodexRolloutParser {
608
906
  // response_item/message events which carry richer metadata (id, content parts, role).
609
907
  case 'exec_command_begin': {
610
908
  const e = event;
611
- setBounded(this.pendingExecCommands, e.call_id, {
612
- call_id: e.call_id,
613
- command: e.command,
614
- workdir: e.workdir,
615
- timestamp,
616
- }, this.maxTrackedToolCalls);
909
+ const pending = this.metadataOnly
910
+ ? { call_id: e.call_id, command: [], timestamp }
911
+ : { call_id: e.call_id, command: e.command, workdir: e.workdir, timestamp };
912
+ this.putPendingExec(pending);
617
913
  return [];
618
914
  }
619
915
  case 'exec_command_end': {
620
916
  const e = event;
621
- const pending = this.pendingExecCommands.get(e.call_id);
622
- this.pendingExecCommands.delete(e.call_id);
917
+ const pending = this.takePending(this.pendingExecCommands, e.call_id);
623
918
  const command = pending?.command?.join(' ') || '';
624
919
  const events = [];
625
920
  if (!this.emittedToolUseIds.has(e.call_id)) {
@@ -635,18 +930,20 @@ export class CodexRolloutParser {
635
930
  type: 'tool_use',
636
931
  id: e.call_id,
637
932
  name: 'Bash',
638
- input: {
933
+ input: this.toolInput({
639
934
  command,
640
935
  workdir: pending?.workdir,
641
936
  _sidekickRawToolName: 'exec_command',
642
- },
937
+ }),
643
938
  },
644
939
  ],
645
940
  },
646
941
  timestamp: pending?.timestamp || timestamp,
647
942
  });
648
943
  }
649
- const output = [e.stdout, e.stderr].filter(Boolean).join('\n') || '';
944
+ const output = this.metadataOnly
945
+ ? ''
946
+ : [e.stdout, e.stderr].filter(Boolean).join('\n') || '';
650
947
  events.push({
651
948
  type: 'user',
652
949
  message: {
@@ -668,19 +965,19 @@ export class CodexRolloutParser {
668
965
  }
669
966
  case 'mcp_tool_call_begin': {
670
967
  const e = event;
671
- setBounded(this.pendingMcpToolCalls, e.call_id, {
968
+ const pending = {
672
969
  call_id: e.call_id,
673
970
  server_name: e.server_name,
674
971
  tool_name: e.tool_name,
675
- arguments: e.arguments,
972
+ arguments: this.metadataOnly ? undefined : e.arguments,
676
973
  timestamp,
677
- }, this.maxTrackedToolCalls);
974
+ };
975
+ this.putPendingMcp(pending);
678
976
  return [];
679
977
  }
680
978
  case 'mcp_tool_call_end': {
681
979
  const e = event;
682
- const pendingMcp = this.pendingMcpToolCalls.get(e.call_id);
683
- this.pendingMcpToolCalls.delete(e.call_id);
980
+ const pendingMcp = this.takePending(this.pendingMcpToolCalls, e.call_id);
684
981
  const toolName = pendingMcp?.tool_name || 'McpTool';
685
982
  const events = [];
686
983
  if (!this.emittedToolUseIds.has(e.call_id)) {
@@ -696,11 +993,11 @@ export class CodexRolloutParser {
696
993
  type: 'tool_use',
697
994
  id: e.call_id,
698
995
  name: normalizeCodexToolName(toolName),
699
- input: {
996
+ input: this.toolInput({
700
997
  ...(pendingMcp?.arguments || {}),
701
998
  _sidekickRawToolName: toolName,
702
999
  _sidekickMcpServerName: pendingMcp?.server_name,
703
- },
1000
+ }),
704
1001
  },
705
1002
  ],
706
1003
  },
@@ -716,7 +1013,7 @@ export class CodexRolloutParser {
716
1013
  {
717
1014
  type: 'tool_result',
718
1015
  tool_use_id: e.call_id,
719
- content: e.result || '',
1016
+ content: this.toolOutput(e.result || ''),
720
1017
  is_error: e.is_error || false,
721
1018
  duration: e.duration_ms,
722
1019
  },
@@ -775,12 +1072,12 @@ export class CodexRolloutParser {
775
1072
  type: 'tool_use',
776
1073
  id: patchId,
777
1074
  name: 'Edit',
778
- input: {
1075
+ input: this.toolInput({
779
1076
  file_path: e.file_path,
780
1077
  additions: e.additions ?? 0,
781
1078
  deletions: e.deletions ?? 0,
782
1079
  _sidekickRawToolName: 'patch_applied',
783
- },
1080
+ }),
784
1081
  },
785
1082
  ],
786
1083
  },
@@ -807,10 +1104,10 @@ export class CodexRolloutParser {
807
1104
  type: 'tool_use',
808
1105
  id: `plan-enter-${timestamp}`,
809
1106
  name: 'EnterPlanMode',
810
- input: {
1107
+ input: this.toolInput({
811
1108
  source: 'codex_task_started',
812
1109
  _sidekickRawToolName: 'task_started',
813
- },
1110
+ }),
814
1111
  },
815
1112
  ],
816
1113
  },
@@ -834,10 +1131,10 @@ export class CodexRolloutParser {
834
1131
  type: 'tool_use',
835
1132
  id: `plan-exit-${timestamp}`,
836
1133
  name: 'ExitPlanMode',
837
- input: {
1134
+ input: this.toolInput({
838
1135
  source: 'codex_task_complete',
839
1136
  _sidekickRawToolName: 'task_complete',
840
- },
1137
+ }),
841
1138
  },
842
1139
  ],
843
1140
  },