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
@@ -43,8 +43,7 @@ var __importStar = (this && this.__importStar) || (function () {
43
43
  };
44
44
  })();
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.CodexProvider = void 0;
47
- exports.findCodexRolloutFile = findCodexRolloutFile;
46
+ exports.CodexProvider = exports.findCodexRolloutFile = void 0;
48
47
  const fs = __importStar(require("fs"));
49
48
  const path = __importStar(require("path"));
50
49
  const string_decoder_1 = require("string_decoder");
@@ -56,6 +55,7 @@ const subagentStatsCache_1 = require("../parsers/subagentStatsCache");
56
55
  const rolloutWalker_1 = require("./rolloutWalker");
57
56
  const codexPaths_1 = require("../codexPaths");
58
57
  const codexRollouts_1 = require("./codexRollouts");
58
+ Object.defineProperty(exports, "findCodexRolloutFile", { enumerable: true, get: function () { return codexRollouts_1.findCodexRolloutFile; } });
59
59
  const directoryListingCache_1 = require("./directoryListingCache");
60
60
  const diagnostics_1 = require("./diagnostics");
61
61
  const modelContext_1 = require("../modelContext");
@@ -84,29 +84,6 @@ function hasRolloutFile(dir) {
84
84
  function findRolloutFilesInConfiguredHomes(options = {}) {
85
85
  return (0, rolloutWalker_1.walkRolloutFiles)((0, codexRollouts_1.getSessionsDirs)(), options);
86
86
  }
87
- /**
88
- * Locates the rollout file for a Codex session id by walking
89
- * `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`, then
90
- * `<home>/archived_sessions/`, where Codex moves the rollouts of archived
91
- * threads.
92
- *
93
- * Filename-only match, case-insensitive on the id — no file contents are read
94
- * and files are only stat'ed once their name matches, so the walk stays cheap
95
- * even over a large history. When the same id exists in more than one home,
96
- * the most recently modified copy wins. Returns `null` for an unknown, empty,
97
- * or whitespace id. Consumers who want the database's answer instead can query
98
- * the already-public `CodexDatabase` (requires the external `sqlite3` binary).
99
- */
100
- function findCodexRolloutFile(sessionId, options = {}) {
101
- const normalizedId = sessionId.trim().toLowerCase();
102
- if (!normalizedId)
103
- return null;
104
- const sessionsDirs = options.codexHome
105
- ? (0, codexPaths_1.getCodexSessionsDirs)([options.codexHome])
106
- : (0, codexRollouts_1.getSessionsDirs)();
107
- // Name-only filter, one walk across the homes, newest modification first.
108
- return (0, rolloutWalker_1.walkRolloutFiles)(sessionsDirs, { sessionId: normalizedId })[0]?.path ?? null;
109
- }
110
87
  /** Pick the best sessions directory for UI/search entrypoints. */
111
88
  function getSessionsDir() {
112
89
  const dirs = (0, codexPaths_1.getCodexSessionsDirs)(undefined, { includeArchived: false });
@@ -329,6 +306,12 @@ class CodexReader {
329
306
  lineBuffer = '';
330
307
  _wasTruncated = false;
331
308
  decoder = new string_decoder_1.StringDecoder('utf8');
309
+ /**
310
+ * Position from `seekTo()` whose prefix still has to be parsed for model,
311
+ * turn, and cumulative-token context; `restoreResumeState()` supplies that
312
+ * context instead and clears it.
313
+ */
314
+ pendingSeek = null;
332
315
  constructor(rolloutPath, onContextWindowLimit, onRateLimits, onTokenUsage) {
333
316
  this.rolloutPath = rolloutPath;
334
317
  this.onContextWindowLimit = onContextWindowLimit;
@@ -338,9 +321,11 @@ class CodexReader {
338
321
  }
339
322
  /** Get the parser's session metadata. */
340
323
  getSessionMeta() {
324
+ this.settleSeek();
341
325
  return this.parser.getSessionMeta();
342
326
  }
343
327
  readNew(endPosition) {
328
+ this.settleSeek();
344
329
  this._wasTruncated = false;
345
330
  const events = [];
346
331
  try {
@@ -394,47 +379,51 @@ class CodexReader {
394
379
  // Skip malformed JSON lines
395
380
  }
396
381
  }
397
- // Propagate model_context_window from token_count events to the provider.
398
- // The model comes along so the value can be persisted per model — the
399
- // window Codex reports is tier-specific, not the published maximum.
400
- const mcw = this.parser.getModelContextWindow();
401
- if (mcw && this.onContextWindowLimit) {
402
- this.onContextWindowLimit(mcw, this.parser.getCurrentModel() ?? undefined);
403
- }
404
- // Propagate rate_limits from token_count events to the provider
405
- const rateLimits = this.parser.getLastRateLimits();
406
- if (rateLimits && this.onRateLimits) {
407
- this.onRateLimits(rateLimits);
408
- }
409
- // Propagate last token usage snapshot to the provider
410
- const lastUsage = this.parser.getLastTokenUsage();
411
- if (lastUsage && this.onTokenUsage) {
412
- const normalized = (0, usageNormalization_1.normalizeProviderUsage)({
413
- semantics: 'openai',
414
- provider: 'openai',
415
- source: 'codex-reader-snapshot',
416
- model: this.parser.getCurrentModel() || undefined,
417
- inputTokens: lastUsage.input_tokens,
418
- outputTokens: lastUsage.output_tokens,
419
- cacheReadTokens: lastUsage.cached_input_tokens,
420
- reasoningTokens: lastUsage.reasoning_output_tokens,
421
- });
422
- this.onTokenUsage({
423
- inputTokens: normalized.uncachedInputTokens,
424
- outputTokens: normalized.outputTokens,
425
- cacheWriteTokens: normalized.cacheWriteTokens,
426
- cacheReadTokens: normalized.cacheReadTokens,
427
- reasoningTokens: normalized.reasoningTokens,
428
- model: this.parser.getCurrentModel() || 'unknown',
429
- timestamp: new Date(),
430
- });
431
- }
382
+ this.propagateParserState();
432
383
  }
433
384
  catch {
434
385
  // Skip read errors
435
386
  }
436
387
  return events;
437
388
  }
389
+ /** Hands the parser's context window, rate limits, and last usage to the provider. */
390
+ propagateParserState() {
391
+ // Propagate model_context_window from token_count events to the provider.
392
+ // The model comes along so the value can be persisted per model — the
393
+ // window Codex reports is tier-specific, not the published maximum.
394
+ const mcw = this.parser.getModelContextWindow();
395
+ if (mcw && this.onContextWindowLimit) {
396
+ this.onContextWindowLimit(mcw, this.parser.getCurrentModel() ?? undefined);
397
+ }
398
+ // Propagate rate_limits from token_count events to the provider
399
+ const rateLimits = this.parser.getLastRateLimits();
400
+ if (rateLimits && this.onRateLimits) {
401
+ this.onRateLimits(rateLimits);
402
+ }
403
+ // Propagate last token usage snapshot to the provider
404
+ const lastUsage = this.parser.getLastTokenUsage();
405
+ if (lastUsage && this.onTokenUsage) {
406
+ const normalized = (0, usageNormalization_1.normalizeProviderUsage)({
407
+ semantics: 'openai',
408
+ provider: 'openai',
409
+ source: 'codex-reader-snapshot',
410
+ model: this.parser.getCurrentModel() || undefined,
411
+ inputTokens: lastUsage.input_tokens,
412
+ outputTokens: lastUsage.output_tokens,
413
+ cacheReadTokens: lastUsage.cached_input_tokens,
414
+ reasoningTokens: lastUsage.reasoning_output_tokens,
415
+ });
416
+ this.onTokenUsage({
417
+ inputTokens: normalized.uncachedInputTokens,
418
+ outputTokens: normalized.outputTokens,
419
+ cacheWriteTokens: normalized.cacheWriteTokens,
420
+ cacheReadTokens: normalized.cacheReadTokens,
421
+ reasoningTokens: normalized.reasoningTokens,
422
+ model: this.parser.getCurrentModel() || 'unknown',
423
+ timestamp: new Date(),
424
+ });
425
+ }
426
+ }
438
427
  readAll() {
439
428
  this.reset();
440
429
  return this.readNew();
@@ -446,11 +435,13 @@ class CodexReader {
446
435
  this.parser.reset();
447
436
  this.decoder = new string_decoder_1.StringDecoder('utf8');
448
437
  this._wasTruncated = false;
438
+ this.pendingSeek = null;
449
439
  }
450
440
  exists() {
451
441
  return fs.existsSync(this.rolloutPath);
452
442
  }
453
443
  flush() {
444
+ this.settleSeek();
454
445
  this.lineBuffer += this.decoder.end();
455
446
  // Process any remaining data in the line buffer
456
447
  if (this.lineBuffer.trim()) {
@@ -468,15 +459,45 @@ class CodexReader {
468
459
  return this.committedPosition;
469
460
  }
470
461
  seekTo(position) {
471
- // Rebuild model, turn, and cumulative-token context before resuming.
472
- // The prefix is parsed only; its events are already in the consumer snapshot.
473
462
  this.reset();
463
+ this.filePosition = position;
464
+ this.committedPosition = position;
465
+ // The prefix's context is rebuilt on the next read, unless
466
+ // restoreResumeState() supplies it first.
474
467
  if (position > 0)
475
- this.readNew(position);
468
+ this.pendingSeek = position;
476
469
  }
477
470
  wasTruncated() {
478
471
  return this._wasTruncated;
479
472
  }
473
+ /** The parser's continuation state (see `CodexRolloutParser.exportResumeState`). */
474
+ getResumeState() {
475
+ this.settleSeek();
476
+ return this.parser.exportResumeState();
477
+ }
478
+ /**
479
+ * Restores parser state from `getResumeState()`; after `seekTo()` this
480
+ * replaces re-parsing the rollout up to that position.
481
+ */
482
+ restoreResumeState(state) {
483
+ if (!this.parser.importResumeState(state))
484
+ return false;
485
+ this.pendingSeek = null;
486
+ this.propagateParserState();
487
+ return true;
488
+ }
489
+ /**
490
+ * Rebuilds model, turn, and cumulative-token context for a pending
491
+ * `seekTo()` position. The prefix is parsed only; its events are already
492
+ * in the consumer snapshot.
493
+ */
494
+ settleSeek() {
495
+ const position = this.pendingSeek;
496
+ if (position === null)
497
+ return;
498
+ this.reset();
499
+ this.readNew(position);
500
+ }
480
501
  }
481
502
  // ---------------------------------------------------------------------------
482
503
  // CodexProvider — full SessionProviderBase implementation
@@ -634,7 +655,7 @@ class CodexProvider {
634
655
  }
635
656
  findSessionById(_workspacePath, sessionId) {
636
657
  try {
637
- const found = findCodexRolloutFile(sessionId);
658
+ const found = (0, codexRollouts_1.findCodexRolloutFile)(sessionId);
638
659
  this.recordDatabaseFallback('findSessionById', true);
639
660
  return found;
640
661
  }
@@ -4,8 +4,9 @@
4
4
  */
5
5
  import type { CodexDbThread } from '../types/codex';
6
6
  import type { ProviderRuntimeStatus } from './types';
7
- /** Parent thread id recorded in a Codex `threads.source` JSON value, if any. */
8
- export declare function spawnParentFromSource(source: unknown): string | undefined;
7
+ import { codexSpawnParentFromSource as spawnParentFromSource } from '../parsers/codexRolloutOrigin';
8
+ /** Parent thread id of a spawned subagent's `source`; one rule shared with rollout parsing. */
9
+ export { spawnParentFromSource };
9
10
  export declare class CodexDatabase {
10
11
  private readonly dbPath;
11
12
  private sqlite3Available;
@@ -37,35 +37,17 @@ var __importStar = (this && this.__importStar) || (function () {
37
37
  };
38
38
  })();
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.CodexDatabase = void 0;
41
- exports.spawnParentFromSource = spawnParentFromSource;
40
+ exports.CodexDatabase = exports.spawnParentFromSource = void 0;
42
41
  const fs = __importStar(require("fs"));
43
42
  const path = __importStar(require("path"));
44
43
  const child_process_1 = require("child_process");
44
+ const codexRolloutOrigin_1 = require("../parsers/codexRolloutOrigin");
45
+ Object.defineProperty(exports, "spawnParentFromSource", { enumerable: true, get: function () { return codexRolloutOrigin_1.codexSpawnParentFromSource; } });
45
46
  /**
46
47
  * Ids per inlined `IN (…)` query. Values are passed inside a single argv
47
48
  * element to sqlite3, so an unbounded list would hit E2BIG on large stores.
48
49
  */
49
50
  const SQL_ID_CHUNK_SIZE = 400;
50
- /** Parent thread id recorded in a Codex `threads.source` JSON value, if any. */
51
- function spawnParentFromSource(source) {
52
- let parsed = source;
53
- if (typeof source === 'string') {
54
- if (!source.startsWith('{'))
55
- return undefined;
56
- try {
57
- parsed = JSON.parse(source);
58
- }
59
- catch {
60
- return undefined;
61
- }
62
- }
63
- if (!parsed || typeof parsed !== 'object')
64
- return undefined;
65
- const parent = parsed
66
- .subagent?.thread_spawn?.parent_thread_id;
67
- return typeof parent === 'string' ? parent : undefined;
68
- }
69
51
  class CodexDatabase {
70
52
  dbPath;
71
53
  sqlite3Available = null;
@@ -246,7 +228,7 @@ class CodexDatabase {
246
228
  if (!this.getThreadColumns().has('source'))
247
229
  return [];
248
230
  const rows = this.query('SELECT * FROM threads WHERE source LIKE ? ORDER BY created_at ASC', [`%${parentId}%`]);
249
- return rows.filter((row) => spawnParentFromSource(row.source) === parentId);
231
+ return rows.filter((row) => (0, codexRolloutOrigin_1.codexSpawnParentFromSource)(row.source) === parentId);
250
232
  }
251
233
  /** Get the database file's mtime (ms epoch). Returns 0 if unavailable. */
252
234
  getDbMtime() {
@@ -42,3 +42,25 @@ export declare function normalizePath(input: string): string;
42
42
  * sits inside it.
43
43
  */
44
44
  export declare function listCodexSessionFilesAsync(workspacePath?: string, options?: ListSessionFilesOptions, cache?: DirectoryListingCache): Promise<SessionFileInfo[]>;
45
+ export interface FindCodexRolloutFileOptions {
46
+ /**
47
+ * Codex home whose `sessions/` subtree to search. Defaults to every
48
+ * monitored home: `$CODEX_HOME` if set, else `~/.codex`, plus managed
49
+ * profile homes that recorded sessions.
50
+ */
51
+ codexHome?: string;
52
+ }
53
+ /**
54
+ * Locates the rollout file for a Codex session id by walking
55
+ * `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`, then
56
+ * `<home>/archived_sessions/`, where Codex moves the rollouts of archived
57
+ * threads.
58
+ *
59
+ * Filename-only match, case-insensitive on the id — no file contents are read
60
+ * and files are only stat'ed once their name matches, so the walk stays cheap
61
+ * even over a large history. When the same id exists in more than one home,
62
+ * the most recently modified copy wins. Returns `null` for an unknown, empty,
63
+ * or whitespace id. Consumers who want the database's answer instead can query
64
+ * the already-public `CodexDatabase` (requires the external `sqlite3` binary).
65
+ */
66
+ export declare function findCodexRolloutFile(sessionId: string, options?: FindCodexRolloutFileOptions): string | null;
@@ -50,6 +50,7 @@ exports.initializeCounters = initializeCounters;
50
50
  exports.cwdMatches = cwdMatches;
51
51
  exports.normalizePath = normalizePath;
52
52
  exports.listCodexSessionFilesAsync = listCodexSessionFilesAsync;
53
+ exports.findCodexRolloutFile = findCodexRolloutFile;
53
54
  const fs = __importStar(require("fs"));
54
55
  const path = __importStar(require("path"));
55
56
  const codexPaths_1 = require("../codexPaths");
@@ -259,3 +260,26 @@ async function listCodexSessionFilesAsync(workspacePath, options = {}, cache = n
259
260
  }
260
261
  return results;
261
262
  }
263
+ /**
264
+ * Locates the rollout file for a Codex session id by walking
265
+ * `<home>/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`, then
266
+ * `<home>/archived_sessions/`, where Codex moves the rollouts of archived
267
+ * threads.
268
+ *
269
+ * Filename-only match, case-insensitive on the id — no file contents are read
270
+ * and files are only stat'ed once their name matches, so the walk stays cheap
271
+ * even over a large history. When the same id exists in more than one home,
272
+ * the most recently modified copy wins. Returns `null` for an unknown, empty,
273
+ * or whitespace id. Consumers who want the database's answer instead can query
274
+ * the already-public `CodexDatabase` (requires the external `sqlite3` binary).
275
+ */
276
+ function findCodexRolloutFile(sessionId, options = {}) {
277
+ const normalizedId = sessionId.trim().toLowerCase();
278
+ if (!normalizedId)
279
+ return null;
280
+ const sessionsDirs = options.codexHome
281
+ ? (0, codexPaths_1.getCodexSessionsDirs)([options.codexHome])
282
+ : getSessionsDirs();
283
+ // Name-only filter, one walk across the homes, newest modification first.
284
+ return (0, rolloutWalker_1.walkRolloutFiles)(sessionsDirs, { sessionId: normalizedId })[0]?.path ?? null;
285
+ }
@@ -60,7 +60,6 @@ const sessionContext_1 = require("../context/sessionContext");
60
60
  const sessionStats_1 = require("../sessionStats");
61
61
  const openCodeParser_1 = require("../parsers/openCodeParser");
62
62
  const openCodeDatabase_1 = require("./openCodeDatabase");
63
- const usageNormalization_1 = require("../usageNormalization");
64
63
  const subagentUsage_1 = require("../usage/subagentUsage");
65
64
  const zaiQuota_1 = require("../zaiQuota");
66
65
  const diagnostics_1 = require("./diagnostics");
@@ -1493,21 +1492,14 @@ class OpenCodeProvider {
1493
1492
  if (tokens) {
1494
1493
  // Same semantics as the main-session parser (openCodeParser).
1495
1494
  const cache = tokens.cache;
1496
- const providerID = msgData.providerID;
1497
- (0, subagentUsage_1.addUsageToSubagent)(tokenTotals, (0, usageNormalization_1.normalizeProviderUsage)({
1498
- semantics: providerID === 'openai'
1499
- ? 'openai'
1500
- : providerID === 'anthropic'
1501
- ? 'anthropic'
1502
- : 'sidekick',
1503
- provider: providerID ?? 'opencode',
1504
- inputTokens: tokens.input,
1505
- outputTokens: tokens.output,
1506
- cacheReadTokens: cache?.read,
1507
- cacheWriteTokens: cache?.write,
1508
- reasoningTokens: tokens.reasoning,
1509
- reasoningIncludedInOutput: providerID === 'openai',
1510
- }));
1495
+ (0, subagentUsage_1.addUsageToSubagent)(tokenTotals, (0, openCodeParser_1.normalizeOpenCodeUsage)({
1496
+ input: tokens.input,
1497
+ output: tokens.output,
1498
+ cacheRead: cache?.read,
1499
+ cacheWrite: cache?.write,
1500
+ reasoning: tokens.reasoning,
1501
+ total: tokens.total,
1502
+ }, msgData.providerID));
1511
1503
  }
1512
1504
  }
1513
1505
  catch {
@@ -186,6 +186,20 @@ export interface SessionReader {
186
186
  seekTo(position: number): void;
187
187
  /** Check if file was truncated (size < position). */
188
188
  wasTruncated(): boolean;
189
+ /**
190
+ * Parse state the reader needs, beyond `getPosition()`, to continue
191
+ * correctly after a restart: JSON-serializable data to persist together
192
+ * with that position (for example which Claude responses were already
193
+ * counted). `undefined` when the reader keeps none. Optional.
194
+ */
195
+ getResumeState?(): unknown;
196
+ /**
197
+ * Restores state from `getResumeState()`. Call it right after `seekTo()`
198
+ * the position that state was taken at. Returns false, changing nothing,
199
+ * when `state` is missing or not valid for this reader; the caller should
200
+ * then replay from the start instead. Optional.
201
+ */
202
+ restoreResumeState?(state: unknown): boolean;
189
203
  }
190
204
  /**
191
205
  * Expanded session provider interface (no VS Code dependencies).
@@ -599,14 +599,51 @@ function renderContentBlock(block, includeThinking, includeToolDetail) {
599
599
  return '';
600
600
  }
601
601
  }
602
- function renderToolUse(block, includeDetail) {
602
+ /** Prefix of the bookkeeping keys session parsers stash inside tool inputs. */
603
+ const SIDEKICK_INPUT_KEY_PREFIX = '_sidekick';
604
+ /**
605
+ * The tool's display identity and user-facing input. Entries built outside
606
+ * the canonical projection may still carry the parsers' `_sidekick*` keys in
607
+ * `toolInput`; those are hidden here and read as the raw identity.
608
+ */
609
+ function toolDisplay(block) {
603
610
  const name = block.toolName || 'unknown';
611
+ const raw = block.toolInput;
612
+ let input = raw;
613
+ let rawToolName = block.rawToolName;
614
+ let mcpServerName = block.mcpServerName;
615
+ if (raw && Object.keys(raw).some((key) => key.startsWith(SIDEKICK_INPUT_KEY_PREFIX))) {
616
+ const clean = {};
617
+ for (const [key, value] of Object.entries(raw)) {
618
+ if (!key.startsWith(SIDEKICK_INPUT_KEY_PREFIX))
619
+ clean[key] = value;
620
+ }
621
+ input = clean;
622
+ if (!rawToolName && typeof raw._sidekickRawToolName === 'string') {
623
+ rawToolName = raw._sidekickRawToolName;
624
+ }
625
+ if (!mcpServerName && typeof raw._sidekickMcpServerName === 'string') {
626
+ mcpServerName = raw._sidekickMcpServerName;
627
+ }
628
+ }
629
+ const parts = [];
630
+ if (rawToolName && rawToolName.trim() && rawToolName !== name)
631
+ parts.push(rawToolName);
632
+ if (mcpServerName && mcpServerName.trim())
633
+ parts.push(`MCP: ${mcpServerName}`);
634
+ const hint = parts.length
635
+ ? ` <span class="tool-raw-name" style="color:var(--text-muted);font-size:11px">${(0, htmlHelpers_1.escapeHtml)(parts.join(' · '))}</span>`
636
+ : '';
637
+ return { name, hint, input };
638
+ }
639
+ function renderToolUse(block, includeDetail) {
640
+ const { name, hint, input } = toolDisplay(block);
604
641
  if (!includeDetail) {
605
- return `<div class="tool-block" data-block-type="tool"><div style="padding:8px 12px;background:rgba(245,166,35,0.08);color:var(--accent-orange);font-family:monospace;font-size:13px;">${(0, htmlHelpers_1.escapeHtml)(name)}</div></div>`;
642
+ return `<div class="tool-block" data-block-type="tool"><div style="padding:8px 12px;background:rgba(245,166,35,0.08);color:var(--accent-orange);font-family:monospace;font-size:13px;">${(0, htmlHelpers_1.escapeHtml)(name)}${hint}</div></div>`;
606
643
  }
607
644
  let inputHtml = '';
608
- if (block.toolInput && Object.keys(block.toolInput).length > 0) {
609
- const inputJson = JSON.stringify(block.toolInput, null, 2);
645
+ if (input && Object.keys(input).length > 0) {
646
+ const inputJson = JSON.stringify(input, null, 2);
610
647
  const highlighted = (0, htmlHelpers_1.highlightCodeBlock)(inputJson, 'json');
611
648
  inputHtml = `<div class="tool-io">
612
649
  <div class="tool-label">Input</div>
@@ -614,7 +651,7 @@ function renderToolUse(block, includeDetail) {
614
651
  </div>`;
615
652
  }
616
653
  return `<details class="tool-block" data-block-type="tool">
617
- <summary>${(0, htmlHelpers_1.escapeHtml)(name)}${block.toolUseId ? ` <span style="color:var(--text-muted);font-size:11px">${(0, htmlHelpers_1.escapeHtml)(block.toolUseId)}</span>` : ''}</summary>
654
+ <summary>${(0, htmlHelpers_1.escapeHtml)(name)}${hint}${block.toolUseId ? ` <span style="color:var(--text-muted);font-size:11px">${(0, htmlHelpers_1.escapeHtml)(block.toolUseId)}</span>` : ''}</summary>
618
655
  <div class="tool-content">
619
656
  ${inputHtml}
620
657
  </div>
@@ -1,6 +1,15 @@
1
1
  /** Report compatibility adapters over the canonical transcript projection. */
2
2
  import type { SessionEvent } from '../types/sessionEvent';
3
- import type { TranscriptEntry } from './types';
3
+ import type { TranscriptEntry, TranscriptContentBlock } from './types';
4
+ /**
5
+ * A report tool block that also names the provider's raw tool and MCP
6
+ * server. Internal: the extra fields are optional and read only by the HTML
7
+ * renderer.
8
+ */
9
+ export type ReportToolUseBlock = TranscriptContentBlock & {
10
+ rawToolName?: string;
11
+ mcpServerName?: string;
12
+ };
4
13
  /** Parse a Claude Code JSONL session through the canonical provider reader. */
5
14
  export declare function parseTranscript(sessionPath: string): TranscriptEntry[];
6
15
  /** Project canonical provider events into the legacy report transcript shape. */
@@ -79,17 +79,19 @@ function toReportBlock(block) {
79
79
  return block.text ? [{ type: 'text', text: block.text }] : [];
80
80
  case 'thinking':
81
81
  return block.text ? [{ type: 'thinking', text: block.text }] : [];
82
- case 'tool_use':
83
- return [
84
- {
85
- type: 'tool_use',
86
- toolName: block.toolName ?? 'unknown',
87
- toolInput: block.input && typeof block.input === 'object'
88
- ? block.input
89
- : {},
90
- toolUseId: block.toolUseId,
91
- },
92
- ];
82
+ case 'tool_use': {
83
+ const toolBlock = {
84
+ type: 'tool_use',
85
+ toolName: block.toolName ?? 'unknown',
86
+ toolInput: block.input && typeof block.input === 'object'
87
+ ? block.input
88
+ : {},
89
+ toolUseId: block.toolUseId,
90
+ ...(block.rawToolName ? { rawToolName: block.rawToolName } : {}),
91
+ ...(block.mcpServerName ? { mcpServerName: block.mcpServerName } : {}),
92
+ };
93
+ return [toolBlock];
94
+ }
93
95
  case 'tool_result':
94
96
  return [
95
97
  {
@@ -12,6 +12,7 @@
12
12
  */
13
13
  export { ACCOUNT_PROVIDER_IDS, QUOTA_PROVIDER_IDS, RUNTIME_QUOTA_PROVIDER_IDS, SESSION_PROVIDER_IDS, } from '../providerIds';
14
14
  export { messageUsageSchema, sessionMessageSchema, sessionEventSchema, permissionModeSchema, extractSessionEvents, userSessionEventSchema, assistantSessionEventSchema, summarySessionEventSchema, systemSessionEventSchema, toolUseSessionEventSchema, toolResultSessionEventSchema, } from './sessionEvent';
15
+ export type { ExtractSessionEventsOptions } from './sessionEvent';
15
16
  export type { MessageUsage, SessionMessage, SessionEvent, PermissionMode, } from '../types/sessionEvent';
16
17
  export { normalizedUsageSchema, normalizedUsageCostSchema } from './usageNormalization';
17
18
  export type { NormalizedUsage, NormalizedUsageCost } from '../usageNormalization';
@@ -52,6 +52,25 @@ export declare const providerFailureEvidenceSchema: z.ZodObject<{
52
52
  context_overflow: "context_overflow";
53
53
  }>;
54
54
  checkedAt: z.ZodOptional<z.ZodString>;
55
+ codexErrorInfo: z.ZodOptional<z.ZodEnum<{
56
+ other: "other";
57
+ contextWindowExceeded: "contextWindowExceeded";
58
+ sessionBudgetExceeded: "sessionBudgetExceeded";
59
+ usageLimitExceeded: "usageLimitExceeded";
60
+ rateLimitExceeded: "rateLimitExceeded";
61
+ serverOverloaded: "serverOverloaded";
62
+ cyberPolicy: "cyberPolicy";
63
+ httpConnectionFailed: "httpConnectionFailed";
64
+ responseStreamConnectionFailed: "responseStreamConnectionFailed";
65
+ internalServerError: "internalServerError";
66
+ unauthorized: "unauthorized";
67
+ badRequest: "badRequest";
68
+ threadRollbackFailed: "threadRollbackFailed";
69
+ sandboxError: "sandboxError";
70
+ responseStreamDisconnected: "responseStreamDisconnected";
71
+ responseTooManyFailedAttempts: "responseTooManyFailedAttempts";
72
+ activeTurnNotSteerable: "activeTurnNotSteerable";
73
+ }>>;
55
74
  }, z.core.$strip>;
56
75
  export declare const providerRetryAfterSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
57
76
  kind: z.ZodLiteral<"delay">;
@@ -98,6 +117,7 @@ export declare const providerFailureDiagnosisSchema: z.ZodObject<{
98
117
  reduce_context: "reduce_context";
99
118
  inspect_error: "inspect_error";
100
119
  }>;
120
+ retryable: z.ZodOptional<z.ZodBoolean>;
101
121
  evidence: z.ZodArray<z.ZodObject<{
102
122
  source: z.ZodEnum<{
103
123
  message: "message";
@@ -122,6 +142,25 @@ export declare const providerFailureDiagnosisSchema: z.ZodObject<{
122
142
  context_overflow: "context_overflow";
123
143
  }>;
124
144
  checkedAt: z.ZodOptional<z.ZodString>;
145
+ codexErrorInfo: z.ZodOptional<z.ZodEnum<{
146
+ other: "other";
147
+ contextWindowExceeded: "contextWindowExceeded";
148
+ sessionBudgetExceeded: "sessionBudgetExceeded";
149
+ usageLimitExceeded: "usageLimitExceeded";
150
+ rateLimitExceeded: "rateLimitExceeded";
151
+ serverOverloaded: "serverOverloaded";
152
+ cyberPolicy: "cyberPolicy";
153
+ httpConnectionFailed: "httpConnectionFailed";
154
+ responseStreamConnectionFailed: "responseStreamConnectionFailed";
155
+ internalServerError: "internalServerError";
156
+ unauthorized: "unauthorized";
157
+ badRequest: "badRequest";
158
+ threadRollbackFailed: "threadRollbackFailed";
159
+ sandboxError: "sandboxError";
160
+ responseStreamDisconnected: "responseStreamDisconnected";
161
+ responseTooManyFailedAttempts: "responseTooManyFailedAttempts";
162
+ activeTurnNotSteerable: "activeTurnNotSteerable";
163
+ }>>;
125
164
  }, z.core.$strip>>;
126
165
  authentication: z.ZodOptional<z.ZodObject<{
127
166
  credentialKind: z.ZodEnum<{
@@ -23,6 +23,7 @@ exports.providerFailureEvidenceSchema = zod_1.z.object({
23
23
  source: zod_1.z.enum(providerFailure_1.PROVIDER_FAILURE_EVIDENCE_SOURCES),
24
24
  rule: zod_1.z.enum(providerFailure_1.PROVIDER_FAILURE_CODES),
25
25
  checkedAt: zod_1.z.string().optional(),
26
+ codexErrorInfo: zod_1.z.enum(providerFailure_1.CODEX_ERROR_INFO_KINDS).optional(),
26
27
  });
27
28
  exports.providerRetryAfterSchema = zod_1.z.discriminatedUnion('kind', [
28
29
  zod_1.z.object({ kind: zod_1.z.literal('delay'), delayMs: zod_1.z.number() }),
@@ -33,6 +34,7 @@ exports.providerFailureDiagnosisSchema = zod_1.z.object({
33
34
  credentialKind: zod_1.z.enum(providerFailure_1.PROVIDER_CREDENTIAL_KINDS),
34
35
  diagnosis: zod_1.z.enum(providerFailure_1.PROVIDER_FAILURE_CODES),
35
36
  recovery: zod_1.z.enum(providerFailure_1.PROVIDER_RECOVERY_ACTIONS),
37
+ retryable: zod_1.z.boolean().optional(),
36
38
  evidence: zod_1.z.array(exports.providerFailureEvidenceSchema),
37
39
  authentication: exports.providerAuthenticationEvidenceSchema.optional(),
38
40
  httpStatus: zod_1.z.number().optional(),