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.
- package/CHANGELOG.md +86 -0
- package/README.md +188 -27
- package/dist/aggregation/EventAggregator.d.ts +19 -0
- package/dist/aggregation/EventAggregator.js +174 -35
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +13 -0
- package/dist/authStatus.d.ts +54 -0
- package/dist/authStatus.js +91 -0
- package/dist/browser.d.ts +16 -6
- package/dist/browser.js +24 -2
- package/dist/codex.d.ts +23 -0
- package/dist/codex.js +44 -0
- package/dist/codexProfiles.d.ts +1 -0
- package/dist/codexProfiles.js +8 -11
- package/dist/context/sessionContext.js +4 -0
- package/dist/esm/aggregation/EventAggregator.js +174 -35
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/authStatus.js +87 -0
- package/dist/esm/browser.js +5 -2
- package/dist/esm/codex.js +18 -0
- package/dist/esm/codexProfiles.js +8 -11
- package/dist/esm/context/sessionContext.js +4 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/formatting.js +10 -1
- package/dist/esm/index.js +11 -6
- package/dist/esm/modelCatalog.js +7 -5
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +425 -47
- package/dist/esm/node.js +9 -3
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +351 -54
- package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
- package/dist/esm/parsers/openCodeParser.js +85 -22
- package/dist/esm/parsers/subagentScanner.js +33 -12
- package/dist/esm/parsers/subagentTraceParser.js +97 -37
- package/dist/esm/pricing.js +18 -0
- package/dist/esm/pricingCatalog.js +17 -9
- package/dist/esm/promptHistory.js +66 -4
- package/dist/esm/providerFailure.js +124 -3
- package/dist/esm/providers/claudeCode.js +10 -2
- package/dist/esm/providers/codex.js +84 -62
- package/dist/esm/providers/codexDatabase.js +3 -19
- package/dist/esm/providers/codexRollouts.js +24 -1
- package/dist/esm/providers/openCode.js +9 -17
- package/dist/esm/report/htmlReportGenerator.js +42 -5
- package/dist/esm/report/transcriptParser.js +13 -11
- package/dist/esm/schemas/providerFailure.js +3 -1
- package/dist/esm/schemas/sessionEvent.js +35 -14
- package/dist/esm/schemas/transcript.js +4 -0
- package/dist/esm/sessionPromptHistory.js +2 -2
- package/dist/esm/statusline/payload.js +9 -0
- package/dist/esm/subagentSpawn.js +163 -0
- package/dist/esm/testing/credentialGuard.js +87 -5
- package/dist/esm/toolCallOrigins.js +310 -0
- package/dist/esm/transcript.js +46 -3
- package/dist/esm/turns/assistantTurn.js +3 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +146 -17
- package/dist/esm/usage/usageReports.js +39 -6
- package/dist/esm/watchers/eventBridge.js +15 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +442 -58
- package/dist/esm/watchers/jsonlWatcher.js +51 -4
- package/dist/esm/watchers/providerReaderWatcher.js +13 -0
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/formatting.d.ts +26 -1
- package/dist/formatting.js +11 -1
- package/dist/index.d.ts +22 -11
- package/dist/index.js +44 -12
- package/dist/modelCatalog.js +7 -5
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.d.ts +185 -16
- package/dist/modelInfo.js +434 -49
- package/dist/node.d.ts +16 -5
- package/dist/node.js +33 -2
- package/dist/observedContextWindows.js +2 -1
- package/dist/ownKey.d.ts +16 -0
- package/dist/ownKey.js +30 -0
- package/dist/parsers/codexParser.d.ts +118 -0
- package/dist/parsers/codexParser.js +352 -54
- package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
- package/dist/parsers/codexRolloutOrigin.js +111 -0
- package/dist/parsers/openCodeParser.d.ts +38 -0
- package/dist/parsers/openCodeParser.js +88 -22
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +33 -12
- package/dist/parsers/subagentTraceParser.d.ts +25 -5
- package/dist/parsers/subagentTraceParser.js +97 -37
- package/dist/pricing.d.ts +22 -0
- package/dist/pricing.js +50 -0
- package/dist/pricingCatalog.d.ts +10 -0
- package/dist/pricingCatalog.js +16 -8
- package/dist/promptHistory.d.ts +65 -4
- package/dist/promptHistory.js +68 -5
- package/dist/providerFailure.d.ts +44 -0
- package/dist/providerFailure.js +126 -4
- package/dist/providers/claudeCode.js +9 -1
- package/dist/providers/codex.d.ts +3 -22
- package/dist/providers/codex.js +85 -64
- package/dist/providers/codexDatabase.d.ts +3 -2
- package/dist/providers/codexDatabase.js +4 -22
- package/dist/providers/codexRollouts.d.ts +22 -0
- package/dist/providers/codexRollouts.js +24 -0
- package/dist/providers/openCode.js +8 -16
- package/dist/providers/types.d.ts +14 -0
- package/dist/report/htmlReportGenerator.js +42 -5
- package/dist/report/transcriptParser.d.ts +10 -1
- package/dist/report/transcriptParser.js +13 -11
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/providerFailure.d.ts +39 -0
- package/dist/schemas/providerFailure.js +2 -0
- package/dist/schemas/sessionEvent.d.ts +27 -10
- package/dist/schemas/sessionEvent.js +35 -14
- package/dist/schemas/transcript.d.ts +2 -0
- package/dist/schemas/transcript.js +4 -0
- package/dist/sessionPromptHistory.d.ts +7 -1
- package/dist/sessionPromptHistory.js +1 -1
- package/dist/statusline/payload.d.ts +10 -0
- package/dist/statusline/payload.js +14 -0
- package/dist/subagentSpawn.d.ts +80 -0
- package/dist/subagentSpawn.js +174 -0
- package/dist/testing/credentialGuard.d.ts +14 -0
- package/dist/testing/credentialGuard.js +87 -5
- package/dist/toolCallOrigins.d.ts +70 -0
- package/dist/toolCallOrigins.js +346 -0
- package/dist/transcript.d.ts +12 -0
- package/dist/transcript.js +46 -3
- package/dist/turns/assistantTurn.d.ts +1 -0
- package/dist/turns/assistantTurn.js +3 -2
- package/dist/types/opencode.d.ts +2 -0
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +54 -6
- package/dist/usage/usageEvents.js +146 -17
- package/dist/usage/usageReports.d.ts +35 -4
- package/dist/usage/usageReports.js +39 -6
- package/dist/watchers/eventBridge.js +15 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +124 -4
- package/dist/watchers/jsonlTail.js +446 -59
- package/dist/watchers/jsonlWatcher.d.ts +18 -1
- package/dist/watchers/jsonlWatcher.js +50 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +13 -0
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- 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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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: [
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
|
544
|
-
|
|
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
|
-
|
|
619
|
-
call_id: e.call_id,
|
|
620
|
-
command: e.command,
|
|
621
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
|
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;
|