sidekick-shared 0.29.0 → 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 +22 -0
- package/README.md +56 -25
- 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/browser.d.ts +5 -1
- package/dist/browser.js +9 -3
- 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/browser.js +1 -0
- package/dist/esm/context/sessionContext.js +4 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/modelCatalog.js +5 -4
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +4 -3
- package/dist/esm/node.js +1 -1
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +180 -4
- package/dist/esm/parsers/openCodeParser.js +2 -1
- package/dist/esm/parsers/subagentScanner.js +33 -12
- package/dist/esm/parsers/subagentTraceParser.js +97 -37
- package/dist/esm/pricingCatalog.js +3 -2
- package/dist/esm/providers/claudeCode.js +10 -2
- package/dist/esm/providers/codex.js +82 -38
- package/dist/esm/schemas/sessionEvent.js +35 -14
- package/dist/esm/subagentSpawn.js +163 -0
- package/dist/esm/transcript.js +4 -0
- package/dist/esm/turns/assistantTurn.js +3 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/watchers/eventBridge.js +15 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +73 -8
- 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/index.d.ts +8 -3
- package/dist/index.js +18 -9
- package/dist/modelCatalog.js +5 -4
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.js +4 -3
- package/dist/node.d.ts +1 -1
- package/dist/node.js +3 -1
- 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 +63 -0
- package/dist/parsers/codexParser.js +181 -4
- package/dist/parsers/openCodeParser.js +2 -1
- 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/pricingCatalog.js +3 -2
- package/dist/providers/claudeCode.js +9 -1
- package/dist/providers/codex.js +82 -38
- package/dist/providers/types.d.ts +14 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/sessionEvent.d.ts +27 -10
- package/dist/schemas/sessionEvent.js +35 -14
- package/dist/subagentSpawn.d.ts +80 -0
- package/dist/subagentSpawn.js +174 -0
- package/dist/transcript.js +4 -0
- package/dist/turns/assistantTurn.d.ts +1 -0
- package/dist/turns/assistantTurn.js +3 -2
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- 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 +27 -4
- package/dist/watchers/jsonlTail.js +75 -8
- 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 +1 -1
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
* (not just stats) for drill-down trace visualization. Applies noise classification
|
|
7
7
|
* and tool summary formatting from the formatters module.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* 1.
|
|
11
|
-
* 2.
|
|
12
|
-
*
|
|
9
|
+
* Parent-child linking, exact links first:
|
|
10
|
+
* 1. Meta `parentAgentId` (Claude Code writes it for nested spawns)
|
|
11
|
+
* 2. The child's spawning tool_use id (meta `toolUseId`) found in another
|
|
12
|
+
* trace's `Agent` / `Task` calls
|
|
13
|
+
* Only traces without that spawn metadata (older Claude Code) fall back to:
|
|
14
|
+
* 3. Team summary: parse <teammate-message> XML blocks
|
|
15
|
+
* 4. Positional fallback: timestamp ordering + proximity
|
|
13
16
|
*
|
|
14
17
|
* @module parsers/subagentTraceParser
|
|
15
18
|
*/
|
|
@@ -55,6 +58,8 @@ const noiseClassifier_1 = require("../formatters/noiseClassifier");
|
|
|
55
58
|
const claudeUsageDedupe_1 = require("../usage/claudeUsageDedupe");
|
|
56
59
|
const subagentUsage_1 = require("../usage/subagentUsage");
|
|
57
60
|
const subagentScanner_1 = require("./subagentScanner");
|
|
61
|
+
const subagentSpawn_1 = require("../subagentSpawn");
|
|
62
|
+
const ownKey_1 = require("../ownKey");
|
|
58
63
|
// ── Constants ──
|
|
59
64
|
const AGENT_FILE_PATTERN = /^agent-(.+)\.jsonl$/;
|
|
60
65
|
// ── Public API ──
|
|
@@ -86,10 +91,8 @@ function scanSubagentTraces(sessionDir, sessionId) {
|
|
|
86
91
|
if (trace)
|
|
87
92
|
traces.push(trace);
|
|
88
93
|
}
|
|
89
|
-
// Link parent-child relationships
|
|
90
|
-
linkTraces(traces);
|
|
91
|
-
// Return only top-level traces (those without parents)
|
|
92
|
-
return traces.filter((t) => !t.parentToolUseId);
|
|
94
|
+
// Link parent-child relationships; returns the traces with no parent agent.
|
|
95
|
+
return linkTraces(traces);
|
|
93
96
|
}
|
|
94
97
|
catch {
|
|
95
98
|
return [];
|
|
@@ -169,8 +172,8 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
169
172
|
input: block.input || {},
|
|
170
173
|
timestamp,
|
|
171
174
|
});
|
|
172
|
-
// Extract agent info from Task
|
|
173
|
-
if (block.name
|
|
175
|
+
// Extract agent info from an Agent/Task spawn
|
|
176
|
+
if ((0, subagentSpawn_1.isSubagentSpawnTool)(block.name) && block.input) {
|
|
174
177
|
if (block.input.subagent_type && !agentType) {
|
|
175
178
|
agentType = String(block.input.subagent_type);
|
|
176
179
|
}
|
|
@@ -207,6 +210,14 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
207
210
|
agentId,
|
|
208
211
|
agentType,
|
|
209
212
|
description,
|
|
213
|
+
filePath,
|
|
214
|
+
...(meta.toolUseId
|
|
215
|
+
? { spawnToolUseId: meta.toolUseId, parentToolUseId: meta.toolUseId }
|
|
216
|
+
: {}),
|
|
217
|
+
...(meta.parentAgentId ? { parentAgentId: meta.parentAgentId } : {}),
|
|
218
|
+
...(meta.requestShape ? { background: meta.requestShape === 'background' } : {}),
|
|
219
|
+
...(meta.finished !== undefined ? { finished: meta.finished } : {}),
|
|
220
|
+
...(meta.stoppedByUser !== undefined ? { stoppedByUser: meta.stoppedByUser } : {}),
|
|
210
221
|
events,
|
|
211
222
|
children: [],
|
|
212
223
|
stats: {
|
|
@@ -231,23 +242,61 @@ function parseAgentTrace(filePath, agentId) {
|
|
|
231
242
|
}
|
|
232
243
|
}
|
|
233
244
|
// ── Parent-Child Linking ──
|
|
245
|
+
/**
|
|
246
|
+
* Nests child traces under their parents and returns the traces with no
|
|
247
|
+
* parent agent. Exact metadata links come first; the heuristics only place
|
|
248
|
+
* traces Claude Code recorded no spawn metadata for.
|
|
249
|
+
*/
|
|
234
250
|
function linkTraces(traces) {
|
|
235
251
|
const traceById = new Map();
|
|
236
252
|
for (const trace of traces) {
|
|
237
253
|
traceById.set(trace.agentId, trace);
|
|
238
254
|
}
|
|
239
|
-
|
|
240
|
-
|
|
255
|
+
const parentOf = new Map();
|
|
256
|
+
const attach = (parent, child) => {
|
|
257
|
+
if (parent === child || parentOf.has(child))
|
|
258
|
+
return false;
|
|
259
|
+
// Never nest an agent under its own descendant (malformed metadata).
|
|
260
|
+
for (let up = parent; up; up = parentOf.get(up)) {
|
|
261
|
+
if (up === child)
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
parentOf.set(child, parent);
|
|
265
|
+
parent.children.push(child);
|
|
266
|
+
return true;
|
|
267
|
+
};
|
|
268
|
+
// Phase 1: the meta file names the parent agent
|
|
269
|
+
for (const child of traces) {
|
|
270
|
+
const parent = child.parentAgentId ? traceById.get(child.parentAgentId) : undefined;
|
|
271
|
+
if (parent)
|
|
272
|
+
attach(parent, child);
|
|
273
|
+
}
|
|
274
|
+
// Phase 2: the child's spawning call is in another trace
|
|
275
|
+
const spawnOwner = new Map();
|
|
241
276
|
for (const trace of traces) {
|
|
242
|
-
for (const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// The tool_use_id from this call should appear as a result in the parent
|
|
246
|
-
// but we can link via timestamp proximity to child traces
|
|
247
|
-
}
|
|
277
|
+
for (const id of spawnToolUseIdsOf(trace)) {
|
|
278
|
+
if (!spawnOwner.has(id))
|
|
279
|
+
spawnOwner.set(id, trace);
|
|
248
280
|
}
|
|
249
281
|
}
|
|
250
|
-
|
|
282
|
+
for (const child of traces) {
|
|
283
|
+
if (parentOf.has(child) || !child.spawnToolUseId)
|
|
284
|
+
continue;
|
|
285
|
+
const parent = spawnOwner.get(child.spawnToolUseId);
|
|
286
|
+
if (parent && attach(parent, child))
|
|
287
|
+
child.parentAgentId ??= parent.agentId;
|
|
288
|
+
}
|
|
289
|
+
// Heuristic links set parentAgentId and, as before, parentToolUseId to the
|
|
290
|
+
// parent's agent id.
|
|
291
|
+
const unresolved = (trace) => !parentOf.has(trace) && !trace.spawnToolUseId && !trace.parentAgentId;
|
|
292
|
+
const linkHeuristically = (parent, child) => {
|
|
293
|
+
if (!attach(parent, child))
|
|
294
|
+
return false;
|
|
295
|
+
child.parentAgentId = parent.agentId;
|
|
296
|
+
child.parentToolUseId = parent.agentId;
|
|
297
|
+
return true;
|
|
298
|
+
};
|
|
299
|
+
// Phase 3: Team summary linking
|
|
251
300
|
for (const trace of traces) {
|
|
252
301
|
for (const traceEvent of trace.events) {
|
|
253
302
|
const content = traceEvent.event.message?.content;
|
|
@@ -255,45 +304,56 @@ function linkTraces(traces) {
|
|
|
255
304
|
// Parse teammate agent IDs from XML blocks
|
|
256
305
|
const matches = content.matchAll(/agent[_-]id['":\s]+([a-f0-9]+)/gi);
|
|
257
306
|
for (const match of matches) {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
child.parentToolUseId = trace.agentId;
|
|
262
|
-
trace.children.push(child);
|
|
263
|
-
}
|
|
307
|
+
const child = traceById.get(match[1]);
|
|
308
|
+
if (child && unresolved(child))
|
|
309
|
+
linkHeuristically(trace, child);
|
|
264
310
|
}
|
|
265
311
|
}
|
|
266
312
|
}
|
|
267
313
|
}
|
|
268
|
-
// Phase
|
|
314
|
+
// Phase 4: Positional fallback — timestamp proximity
|
|
269
315
|
// Traces without parents that fall within another trace's time window
|
|
270
|
-
const
|
|
271
|
-
for (const child of parentless) {
|
|
316
|
+
for (const child of traces.filter(unresolved)) {
|
|
272
317
|
if (!child.stats.startTime)
|
|
273
318
|
continue;
|
|
274
319
|
const childStart = child.stats.startTime.getTime();
|
|
275
|
-
// Find a trace that
|
|
320
|
+
// Find a top-level trace that spawned an agent right before this child started
|
|
276
321
|
for (const potential of traces) {
|
|
277
|
-
if (potential === child)
|
|
322
|
+
if (potential === child || parentOf.has(potential))
|
|
278
323
|
continue;
|
|
279
|
-
if (potential.parentToolUseId)
|
|
280
|
-
continue; // Already linked
|
|
281
324
|
for (const traceEvent of potential.events) {
|
|
282
325
|
const evt = traceEvent.event;
|
|
283
|
-
if (evt.type === 'assistant' && evt.tool?.name
|
|
326
|
+
if (evt.type === 'assistant' && (0, subagentSpawn_1.isSubagentSpawnTool)(evt.tool?.name)) {
|
|
284
327
|
const taskTime = new Date(evt.timestamp).getTime();
|
|
285
328
|
// Within 2 seconds before child start
|
|
286
329
|
if (taskTime <= childStart && childStart - taskTime < 2000) {
|
|
287
|
-
child
|
|
288
|
-
potential.children.push(child);
|
|
330
|
+
linkHeuristically(potential, child);
|
|
289
331
|
break;
|
|
290
332
|
}
|
|
291
333
|
}
|
|
292
334
|
}
|
|
293
|
-
if (child
|
|
335
|
+
if (parentOf.has(child))
|
|
294
336
|
break;
|
|
295
337
|
}
|
|
296
338
|
}
|
|
339
|
+
return traces.filter((trace) => !parentOf.has(trace));
|
|
340
|
+
}
|
|
341
|
+
/** Ids of the `Agent` / `Task` calls a trace made. */
|
|
342
|
+
function spawnToolUseIdsOf(trace) {
|
|
343
|
+
const ids = [];
|
|
344
|
+
for (const traceEvent of trace.events) {
|
|
345
|
+
const content = traceEvent.event.message?.content;
|
|
346
|
+
if (!Array.isArray(content))
|
|
347
|
+
continue;
|
|
348
|
+
for (const block of content) {
|
|
349
|
+
if (block?.type === 'tool_use' &&
|
|
350
|
+
typeof block.id === 'string' &&
|
|
351
|
+
(0, subagentSpawn_1.isSubagentSpawnTool)(block.name)) {
|
|
352
|
+
ids.push(block.id);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return ids;
|
|
297
357
|
}
|
|
298
358
|
// ── Helpers ──
|
|
299
359
|
function rawToSessionEvent(raw) {
|
|
@@ -308,7 +368,7 @@ function rawToSessionEvent(raw) {
|
|
|
308
368
|
tool_result: 'tool_result',
|
|
309
369
|
summary: 'summary',
|
|
310
370
|
};
|
|
311
|
-
const mappedType = typeMap
|
|
371
|
+
const mappedType = (0, ownKey_1.ownValue)(typeMap, type);
|
|
312
372
|
if (!mappedType)
|
|
313
373
|
return null;
|
|
314
374
|
const message = raw.message;
|
package/dist/pricingCatalog.js
CHANGED
|
@@ -59,6 +59,7 @@ const node_fs_1 = require("node:fs");
|
|
|
59
59
|
const path = __importStar(require("node:path"));
|
|
60
60
|
const modelContext_1 = require("./modelContext");
|
|
61
61
|
const modelInfo_1 = require("./modelInfo");
|
|
62
|
+
const ownKey_1 = require("./ownKey");
|
|
62
63
|
const paths_1 = require("./paths");
|
|
63
64
|
const atomic_1 = require("./writers/atomic");
|
|
64
65
|
// ── Constants ──
|
|
@@ -156,7 +157,7 @@ function normalizeLiteLlmCatalog(raw) {
|
|
|
156
157
|
continue;
|
|
157
158
|
// Catalog keys use both `provider/model` and bare `model` forms. Record both
|
|
158
159
|
// the raw key and the bare-model form so lookups work regardless of caller.
|
|
159
|
-
out
|
|
160
|
+
(0, ownKey_1.setOwnValue)(out, key, pricing);
|
|
160
161
|
const bare = stripProviderPrefix(key);
|
|
161
162
|
if (bare && bare !== key && !out[bare]) {
|
|
162
163
|
out[bare] = pricing;
|
|
@@ -193,7 +194,7 @@ function normalizeLiteLlmContextWindows(raw) {
|
|
|
193
194
|
const window = entry.max_input_tokens;
|
|
194
195
|
if (typeof window !== 'number' || !Number.isFinite(window) || window <= 0)
|
|
195
196
|
continue;
|
|
196
|
-
out
|
|
197
|
+
(0, ownKey_1.setOwnValue)(out, key, window);
|
|
197
198
|
const bare = stripProviderPrefix(key);
|
|
198
199
|
if (bare && bare !== key) {
|
|
199
200
|
const seen = aliasWindows.get(bare) ?? new Set();
|
|
@@ -155,7 +155,8 @@ class ClaudeCodeReader {
|
|
|
155
155
|
this.usageDeduper = usageDeduper ?? new claudeUsageDedupe_1.ClaudeUsageDeduper();
|
|
156
156
|
this.parser = new jsonl_1.JsonlParser({
|
|
157
157
|
onEvent: (raw) => {
|
|
158
|
-
|
|
158
|
+
// Mid-turn task notifications finish background subagents.
|
|
159
|
+
for (const event of (0, sessionEvent_1.extractSessionEvents)(raw, { includeTaskNotifications: true })) {
|
|
159
160
|
this.events.push(this.usageDeduper.apply((0, claudeUsageDedupe_1.normalizeClaudeUsage)(event)));
|
|
160
161
|
}
|
|
161
162
|
},
|
|
@@ -244,6 +245,13 @@ class ClaudeCodeReader {
|
|
|
244
245
|
wasTruncated() {
|
|
245
246
|
return this._wasTruncated;
|
|
246
247
|
}
|
|
248
|
+
/** The usage deduper's recent message ids, so a resumed reader does not recount a split response. */
|
|
249
|
+
getResumeState() {
|
|
250
|
+
return this.usageDeduper.exportState();
|
|
251
|
+
}
|
|
252
|
+
restoreResumeState(state) {
|
|
253
|
+
return this.usageDeduper.importState(state);
|
|
254
|
+
}
|
|
247
255
|
}
|
|
248
256
|
/**
|
|
249
257
|
* Session provider for Claude Code CLI.
|
package/dist/providers/codex.js
CHANGED
|
@@ -306,6 +306,12 @@ class CodexReader {
|
|
|
306
306
|
lineBuffer = '';
|
|
307
307
|
_wasTruncated = false;
|
|
308
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;
|
|
309
315
|
constructor(rolloutPath, onContextWindowLimit, onRateLimits, onTokenUsage) {
|
|
310
316
|
this.rolloutPath = rolloutPath;
|
|
311
317
|
this.onContextWindowLimit = onContextWindowLimit;
|
|
@@ -315,9 +321,11 @@ class CodexReader {
|
|
|
315
321
|
}
|
|
316
322
|
/** Get the parser's session metadata. */
|
|
317
323
|
getSessionMeta() {
|
|
324
|
+
this.settleSeek();
|
|
318
325
|
return this.parser.getSessionMeta();
|
|
319
326
|
}
|
|
320
327
|
readNew(endPosition) {
|
|
328
|
+
this.settleSeek();
|
|
321
329
|
this._wasTruncated = false;
|
|
322
330
|
const events = [];
|
|
323
331
|
try {
|
|
@@ -371,47 +379,51 @@ class CodexReader {
|
|
|
371
379
|
// Skip malformed JSON lines
|
|
372
380
|
}
|
|
373
381
|
}
|
|
374
|
-
|
|
375
|
-
// The model comes along so the value can be persisted per model — the
|
|
376
|
-
// window Codex reports is tier-specific, not the published maximum.
|
|
377
|
-
const mcw = this.parser.getModelContextWindow();
|
|
378
|
-
if (mcw && this.onContextWindowLimit) {
|
|
379
|
-
this.onContextWindowLimit(mcw, this.parser.getCurrentModel() ?? undefined);
|
|
380
|
-
}
|
|
381
|
-
// Propagate rate_limits from token_count events to the provider
|
|
382
|
-
const rateLimits = this.parser.getLastRateLimits();
|
|
383
|
-
if (rateLimits && this.onRateLimits) {
|
|
384
|
-
this.onRateLimits(rateLimits);
|
|
385
|
-
}
|
|
386
|
-
// Propagate last token usage snapshot to the provider
|
|
387
|
-
const lastUsage = this.parser.getLastTokenUsage();
|
|
388
|
-
if (lastUsage && this.onTokenUsage) {
|
|
389
|
-
const normalized = (0, usageNormalization_1.normalizeProviderUsage)({
|
|
390
|
-
semantics: 'openai',
|
|
391
|
-
provider: 'openai',
|
|
392
|
-
source: 'codex-reader-snapshot',
|
|
393
|
-
model: this.parser.getCurrentModel() || undefined,
|
|
394
|
-
inputTokens: lastUsage.input_tokens,
|
|
395
|
-
outputTokens: lastUsage.output_tokens,
|
|
396
|
-
cacheReadTokens: lastUsage.cached_input_tokens,
|
|
397
|
-
reasoningTokens: lastUsage.reasoning_output_tokens,
|
|
398
|
-
});
|
|
399
|
-
this.onTokenUsage({
|
|
400
|
-
inputTokens: normalized.uncachedInputTokens,
|
|
401
|
-
outputTokens: normalized.outputTokens,
|
|
402
|
-
cacheWriteTokens: normalized.cacheWriteTokens,
|
|
403
|
-
cacheReadTokens: normalized.cacheReadTokens,
|
|
404
|
-
reasoningTokens: normalized.reasoningTokens,
|
|
405
|
-
model: this.parser.getCurrentModel() || 'unknown',
|
|
406
|
-
timestamp: new Date(),
|
|
407
|
-
});
|
|
408
|
-
}
|
|
382
|
+
this.propagateParserState();
|
|
409
383
|
}
|
|
410
384
|
catch {
|
|
411
385
|
// Skip read errors
|
|
412
386
|
}
|
|
413
387
|
return events;
|
|
414
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
|
+
}
|
|
415
427
|
readAll() {
|
|
416
428
|
this.reset();
|
|
417
429
|
return this.readNew();
|
|
@@ -423,11 +435,13 @@ class CodexReader {
|
|
|
423
435
|
this.parser.reset();
|
|
424
436
|
this.decoder = new string_decoder_1.StringDecoder('utf8');
|
|
425
437
|
this._wasTruncated = false;
|
|
438
|
+
this.pendingSeek = null;
|
|
426
439
|
}
|
|
427
440
|
exists() {
|
|
428
441
|
return fs.existsSync(this.rolloutPath);
|
|
429
442
|
}
|
|
430
443
|
flush() {
|
|
444
|
+
this.settleSeek();
|
|
431
445
|
this.lineBuffer += this.decoder.end();
|
|
432
446
|
// Process any remaining data in the line buffer
|
|
433
447
|
if (this.lineBuffer.trim()) {
|
|
@@ -445,15 +459,45 @@ class CodexReader {
|
|
|
445
459
|
return this.committedPosition;
|
|
446
460
|
}
|
|
447
461
|
seekTo(position) {
|
|
448
|
-
// Rebuild model, turn, and cumulative-token context before resuming.
|
|
449
|
-
// The prefix is parsed only; its events are already in the consumer snapshot.
|
|
450
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.
|
|
451
467
|
if (position > 0)
|
|
452
|
-
this.
|
|
468
|
+
this.pendingSeek = position;
|
|
453
469
|
}
|
|
454
470
|
wasTruncated() {
|
|
455
471
|
return this._wasTruncated;
|
|
456
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
|
+
}
|
|
457
501
|
}
|
|
458
502
|
// ---------------------------------------------------------------------------
|
|
459
503
|
// CodexProvider — full SessionProviderBase implementation
|
|
@@ -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).
|
package/dist/schemas/index.d.ts
CHANGED
|
@@ -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';
|
|
@@ -76,12 +76,12 @@ export declare const userSessionEventSchema: z.ZodObject<{
|
|
|
76
76
|
promptSource: z.ZodOptional<z.ZodString>;
|
|
77
77
|
cwd: z.ZodOptional<z.ZodString>;
|
|
78
78
|
gitBranch: z.ZodOptional<z.ZodString>;
|
|
79
|
-
permissionMode: z.ZodOptional<z.ZodEnum<{
|
|
79
|
+
permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
|
|
80
80
|
default: "default";
|
|
81
81
|
acceptEdits: "acceptEdits";
|
|
82
82
|
bypassPermissions: "bypassPermissions";
|
|
83
83
|
plan: "plan";
|
|
84
|
-
}
|
|
84
|
+
}>>>;
|
|
85
85
|
rateLimits: z.ZodOptional<z.ZodObject<{
|
|
86
86
|
primary: z.ZodOptional<z.ZodObject<{
|
|
87
87
|
usedPercent: z.ZodNumber;
|
|
@@ -165,12 +165,12 @@ export declare const assistantSessionEventSchema: z.ZodObject<{
|
|
|
165
165
|
promptSource: z.ZodOptional<z.ZodString>;
|
|
166
166
|
cwd: z.ZodOptional<z.ZodString>;
|
|
167
167
|
gitBranch: z.ZodOptional<z.ZodString>;
|
|
168
|
-
permissionMode: z.ZodOptional<z.ZodEnum<{
|
|
168
|
+
permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
|
|
169
169
|
default: "default";
|
|
170
170
|
acceptEdits: "acceptEdits";
|
|
171
171
|
bypassPermissions: "bypassPermissions";
|
|
172
172
|
plan: "plan";
|
|
173
|
-
}
|
|
173
|
+
}>>>;
|
|
174
174
|
rateLimits: z.ZodOptional<z.ZodObject<{
|
|
175
175
|
primary: z.ZodOptional<z.ZodObject<{
|
|
176
176
|
usedPercent: z.ZodNumber;
|
|
@@ -255,12 +255,12 @@ export declare const systemSessionEventSchema: z.ZodObject<{
|
|
|
255
255
|
promptSource: z.ZodOptional<z.ZodString>;
|
|
256
256
|
cwd: z.ZodOptional<z.ZodString>;
|
|
257
257
|
gitBranch: z.ZodOptional<z.ZodString>;
|
|
258
|
-
permissionMode: z.ZodOptional<z.ZodEnum<{
|
|
258
|
+
permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
|
|
259
259
|
default: "default";
|
|
260
260
|
acceptEdits: "acceptEdits";
|
|
261
261
|
bypassPermissions: "bypassPermissions";
|
|
262
262
|
plan: "plan";
|
|
263
|
-
}
|
|
263
|
+
}>>>;
|
|
264
264
|
rateLimits: z.ZodOptional<z.ZodObject<{
|
|
265
265
|
primary: z.ZodOptional<z.ZodObject<{
|
|
266
266
|
usedPercent: z.ZodNumber;
|
|
@@ -348,12 +348,12 @@ export declare const toolUseSessionEventSchema: z.ZodObject<{
|
|
|
348
348
|
promptSource: z.ZodOptional<z.ZodString>;
|
|
349
349
|
cwd: z.ZodOptional<z.ZodString>;
|
|
350
350
|
gitBranch: z.ZodOptional<z.ZodString>;
|
|
351
|
-
permissionMode: z.ZodOptional<z.ZodEnum<{
|
|
351
|
+
permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
|
|
352
352
|
default: "default";
|
|
353
353
|
acceptEdits: "acceptEdits";
|
|
354
354
|
bypassPermissions: "bypassPermissions";
|
|
355
355
|
plan: "plan";
|
|
356
|
-
}
|
|
356
|
+
}>>>;
|
|
357
357
|
rateLimits: z.ZodOptional<z.ZodObject<{
|
|
358
358
|
primary: z.ZodOptional<z.ZodObject<{
|
|
359
359
|
usedPercent: z.ZodNumber;
|
|
@@ -442,12 +442,12 @@ export declare const toolResultSessionEventSchema: z.ZodObject<{
|
|
|
442
442
|
promptSource: z.ZodOptional<z.ZodString>;
|
|
443
443
|
cwd: z.ZodOptional<z.ZodString>;
|
|
444
444
|
gitBranch: z.ZodOptional<z.ZodString>;
|
|
445
|
-
permissionMode: z.ZodOptional<z.ZodEnum<{
|
|
445
|
+
permissionMode: z.ZodCatch<z.ZodOptional<z.ZodEnum<{
|
|
446
446
|
default: "default";
|
|
447
447
|
acceptEdits: "acceptEdits";
|
|
448
448
|
bypassPermissions: "bypassPermissions";
|
|
449
449
|
plan: "plan";
|
|
450
|
-
}
|
|
450
|
+
}>>>;
|
|
451
451
|
rateLimits: z.ZodOptional<z.ZodObject<{
|
|
452
452
|
primary: z.ZodOptional<z.ZodObject<{
|
|
453
453
|
usedPercent: z.ZodNumber;
|
|
@@ -520,6 +520,19 @@ export declare const toolResultSessionEventSchema: z.ZodObject<{
|
|
|
520
520
|
}, z.core.$strip>>;
|
|
521
521
|
}, z.core.$strip>;
|
|
522
522
|
export declare const sessionEventSchema: z.ZodType<SessionEvent>;
|
|
523
|
+
/** Options for {@link extractSessionEvents}. */
|
|
524
|
+
export interface ExtractSessionEventsOptions {
|
|
525
|
+
/**
|
|
526
|
+
* Also turn Claude Code's mid-turn task-notification attachments
|
|
527
|
+
* (`{ type: 'attachment', attachment: { type: 'queued_command',
|
|
528
|
+
* commandMode: 'task-notification', prompt } }`) into one `system` event
|
|
529
|
+
* with `origin: { kind: 'task-notification' }`, `message: { role: 'user',
|
|
530
|
+
* sourceLabel: 'task notification', content: <prompt> }`, and the line's
|
|
531
|
+
* timestamp. They report background agents finishing. Default `false`:
|
|
532
|
+
* attachment lines yield no events.
|
|
533
|
+
*/
|
|
534
|
+
includeTaskNotifications?: boolean;
|
|
535
|
+
}
|
|
523
536
|
/**
|
|
524
537
|
* Extracts canonical SessionEvents from a raw JSONL value.
|
|
525
538
|
*
|
|
@@ -528,5 +541,9 @@ export declare const sessionEventSchema: z.ZodType<SessionEvent>;
|
|
|
528
541
|
* `sessionEventSchema` alone rejects. This helper tries a direct parse,
|
|
529
542
|
* then unwraps progress envelopes (recursively, in case of nesting).
|
|
530
543
|
* Returns zero events for unrecognized input — never throws.
|
|
544
|
+
*
|
|
545
|
+
* The second argument is either the starting progress depth (the original
|
|
546
|
+
* signature) or an options object.
|
|
531
547
|
*/
|
|
532
548
|
export declare function extractSessionEvents(raw: unknown, depth?: number): SessionEvent[];
|
|
549
|
+
export declare function extractSessionEvents(raw: unknown, options?: ExtractSessionEventsOptions): SessionEvent[];
|
|
@@ -12,6 +12,7 @@ exports.sessionEventSchema = exports.toolResultSessionEventSchema = exports.tool
|
|
|
12
12
|
exports.extractSessionEvents = extractSessionEvents;
|
|
13
13
|
const zod_1 = require("zod");
|
|
14
14
|
const usageNormalization_1 = require("./usageNormalization");
|
|
15
|
+
const subagentSpawn_1 = require("../subagentSpawn");
|
|
15
16
|
// ── MessageUsage ──
|
|
16
17
|
exports.messageUsageSchema = zod_1.z.object({
|
|
17
18
|
input_tokens: zod_1.z.number(),
|
|
@@ -56,7 +57,9 @@ const eventBaseShape = {
|
|
|
56
57
|
promptSource: zod_1.z.string().optional(),
|
|
57
58
|
cwd: zod_1.z.string().optional(),
|
|
58
59
|
gitBranch: zod_1.z.string().optional(),
|
|
59
|
-
|
|
60
|
+
// A mode this release doesn't know (Claude Code added `auto`) is dropped
|
|
61
|
+
// rather than rejecting the whole line with its prompt or tool results.
|
|
62
|
+
permissionMode: exports.permissionModeSchema.optional().catch(undefined),
|
|
60
63
|
rateLimits: zod_1.z
|
|
61
64
|
.object({
|
|
62
65
|
primary: zod_1.z
|
|
@@ -173,22 +176,21 @@ exports.sessionEventSchema = zod_1.z.union([
|
|
|
173
176
|
// ── Progress unwrapping ──
|
|
174
177
|
/** Recursion bound for nested progress envelopes; real data nests 1 level. */
|
|
175
178
|
const MAX_PROGRESS_DEPTH = 8;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
* `sessionEventSchema` alone rejects. This helper tries a direct parse,
|
|
182
|
-
* then unwraps progress envelopes (recursively, in case of nesting).
|
|
183
|
-
* Returns zero events for unrecognized input — never throws.
|
|
184
|
-
*/
|
|
185
|
-
function extractSessionEvents(raw, depth = 0) {
|
|
186
|
-
return extractSessionEventsWithProvenance(raw, depth, {});
|
|
179
|
+
function extractSessionEvents(raw, depthOrOptions = 0) {
|
|
180
|
+
if (typeof depthOrOptions === 'number') {
|
|
181
|
+
return extractSessionEventsWithProvenance(raw, depthOrOptions, {}, {});
|
|
182
|
+
}
|
|
183
|
+
return extractSessionEventsWithProvenance(raw, 0, {}, depthOrOptions ?? {});
|
|
187
184
|
}
|
|
188
|
-
function extractSessionEventsWithProvenance(raw, depth, inherited) {
|
|
185
|
+
function extractSessionEventsWithProvenance(raw, depth, inherited, options) {
|
|
189
186
|
const direct = exports.sessionEventSchema.safeParse(raw);
|
|
190
187
|
if (direct.success)
|
|
191
188
|
return [{ ...inherited, ...direct.data }];
|
|
189
|
+
if (options.includeTaskNotifications) {
|
|
190
|
+
const notification = taskNotificationAttachmentEvent(raw);
|
|
191
|
+
if (notification)
|
|
192
|
+
return [{ ...inherited, ...notification }];
|
|
193
|
+
}
|
|
192
194
|
if (depth >= MAX_PROGRESS_DEPTH)
|
|
193
195
|
return [];
|
|
194
196
|
if (typeof raw === 'object' && raw !== null && raw.type === 'progress') {
|
|
@@ -197,12 +199,31 @@ function extractSessionEventsWithProvenance(raw, depth, inherited) {
|
|
|
197
199
|
if (typeof data === 'object' && data !== null) {
|
|
198
200
|
const message = data.message;
|
|
199
201
|
if (typeof message === 'object' && message !== null) {
|
|
200
|
-
return extractSessionEventsWithProvenance(message, depth + 1, provenance);
|
|
202
|
+
return extractSessionEventsWithProvenance(message, depth + 1, provenance, options);
|
|
201
203
|
}
|
|
202
204
|
}
|
|
203
205
|
}
|
|
204
206
|
return [];
|
|
205
207
|
}
|
|
208
|
+
/** The `system` event for a task-notification attachment line, or `null`. */
|
|
209
|
+
function taskNotificationAttachmentEvent(raw) {
|
|
210
|
+
const prompt = (0, subagentSpawn_1.taskNotificationPromptFromAttachment)(raw);
|
|
211
|
+
if (prompt === null)
|
|
212
|
+
return null;
|
|
213
|
+
const line = raw;
|
|
214
|
+
const candidate = {
|
|
215
|
+
type: 'system',
|
|
216
|
+
timestamp: line.timestamp,
|
|
217
|
+
origin: { kind: 'task-notification' },
|
|
218
|
+
message: { role: 'user', sourceLabel: 'task notification', content: prompt },
|
|
219
|
+
};
|
|
220
|
+
for (const key of ['entrypoint', 'isSidechain', 'cwd', 'gitBranch']) {
|
|
221
|
+
if (line[key] !== undefined)
|
|
222
|
+
candidate[key] = line[key];
|
|
223
|
+
}
|
|
224
|
+
const parsed = exports.systemSessionEventSchema.safeParse(candidate);
|
|
225
|
+
return parsed.success ? parsed.data : null;
|
|
226
|
+
}
|
|
206
227
|
function progressProvenance(raw) {
|
|
207
228
|
const value = raw;
|
|
208
229
|
const provenance = {};
|