agent-trajectory 0.2.3__py3-none-any.whl

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.
@@ -0,0 +1,3505 @@
1
+ // src/python-cli.ts
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+
4
+ // src/types.ts
5
+ class NormalizationError extends Error {
6
+ code;
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.name = "NormalizationError";
10
+ this.code = code;
11
+ }
12
+ }
13
+
14
+ // src/adapters/shared.ts
15
+ function parseJsonLines(transcript, diagnostics) {
16
+ const parsed = [];
17
+ const lines = transcript.split(`
18
+ `);
19
+ let byteOffset = 0;
20
+ for (let index = 0;index < lines.length; index += 1) {
21
+ const raw = lines[index];
22
+ if (raw === undefined)
23
+ continue;
24
+ const lineByteOffset = byteOffset;
25
+ byteOffset += utf8ByteLength(raw) + 1;
26
+ if (!raw.trim())
27
+ continue;
28
+ const line = index + 1;
29
+ let value;
30
+ try {
31
+ value = JSON.parse(raw);
32
+ } catch {
33
+ diagnostics.push({
34
+ code: "invalid_json_line",
35
+ message: `Skipped invalid JSON on line ${line}.`,
36
+ inputLine: line
37
+ });
38
+ continue;
39
+ }
40
+ if (!isObject(value)) {
41
+ diagnostics.push({
42
+ code: "non_object_json_line",
43
+ message: `Skipped non-object JSON on line ${line}.`,
44
+ inputLine: line
45
+ });
46
+ continue;
47
+ }
48
+ parsed.push({ value, line, byteOffset: lineByteOffset });
49
+ }
50
+ return parsed;
51
+ }
52
+ function utf8ByteLength(text) {
53
+ return new TextEncoder().encode(text).length;
54
+ }
55
+ function isObject(value) {
56
+ return value !== null && typeof value === "object" && !Array.isArray(value);
57
+ }
58
+ function parseTimestamp(value) {
59
+ if (value instanceof Date && !Number.isNaN(value.getTime()))
60
+ return value;
61
+ if (typeof value === "number" && value > 100000000000) {
62
+ const date2 = new Date(value);
63
+ return Number.isNaN(date2.getTime()) ? undefined : date2;
64
+ }
65
+ if (typeof value !== "string" || value.length === 0)
66
+ return;
67
+ const withZone = /(Z|[+-]\d{2}:?\d{2})$/.test(value) ? value : `${value}Z`;
68
+ const date = new Date(withZone);
69
+ return Number.isNaN(date.getTime()) ? undefined : date;
70
+ }
71
+ function blocksText(content) {
72
+ if (typeof content === "string")
73
+ return content;
74
+ if (!Array.isArray(content))
75
+ return "";
76
+ const parts = [];
77
+ for (const item of content) {
78
+ if (!isObject(item))
79
+ continue;
80
+ const type = item.type;
81
+ if (type === "text" || type === "input_text" || type === "output_text" || type === undefined && "text" in item) {
82
+ if (typeof item.text === "string" && item.text.length > 0) {
83
+ parts.push(item.text);
84
+ }
85
+ } else if (type === "image") {
86
+ parts.push("[image]");
87
+ }
88
+ }
89
+ return parts.join(`
90
+ `);
91
+ }
92
+ function jsonString(value) {
93
+ const serialized = JSON.stringify(value ?? {});
94
+ return serialized === undefined ? "{}" : serialized;
95
+ }
96
+
97
+ // src/adapters/claude-code/index.ts
98
+ var TRANSPORT_TYPES = new Set([
99
+ "progress",
100
+ "queue-operation",
101
+ "file-history-snapshot",
102
+ "summary",
103
+ "system",
104
+ "pr-link",
105
+ "last-prompt",
106
+ "custom-title",
107
+ "ai-title",
108
+ "agent-name",
109
+ "permission-mode",
110
+ "attachment",
111
+ "mode"
112
+ ]);
113
+ var claudeCodeAdapter = {
114
+ source: "claude-code",
115
+ decode(transcript) {
116
+ const diagnostics = [];
117
+ const events = [];
118
+ const rows = [...parseJsonLines(transcript, diagnostics)];
119
+ const standaloneSidechain = rows.some(({ value }) => isConversationalRecord(value) && value.isSidechain === true) && !rows.some(({ value }) => isConversationalRecord(value) && value.isSidechain !== true);
120
+ let cwdCandidate;
121
+ let branchCandidate;
122
+ const sessionIds = new Set;
123
+ const agentIds = new Set;
124
+ for (const { value: record, line, byteOffset } of rows) {
125
+ const recordType = record.type;
126
+ if (record.isSidechain === true && !standaloneSidechain) {
127
+ diagnostics.push({
128
+ code: "sidechain_record_dropped",
129
+ message: `Dropped a Claude Code sidechain record on line ${line}.`,
130
+ inputLine: line
131
+ });
132
+ continue;
133
+ }
134
+ if (typeof recordType === "string" && TRANSPORT_TYPES.has(recordType)) {
135
+ continue;
136
+ }
137
+ const contextKey = {
138
+ ts: parseTimestamp(record.timestamp)?.getTime() ?? Number.POSITIVE_INFINITY,
139
+ tie: typeof record.uuid === "string" && record.uuid ? record.uuid : `@${byteOffset}`
140
+ };
141
+ if (typeof record.cwd === "string" && record.cwd) {
142
+ cwdCandidate = earlier(cwdCandidate, { ...contextKey, value: record.cwd });
143
+ }
144
+ if (typeof record.gitBranch === "string" && record.gitBranch) {
145
+ branchCandidate = earlier(branchCandidate, {
146
+ ...contextKey,
147
+ value: record.gitBranch
148
+ });
149
+ }
150
+ if (typeof record.sessionId === "string" && record.sessionId) {
151
+ sessionIds.add(record.sessionId);
152
+ }
153
+ if (standaloneSidechain && typeof record.agentId === "string" && record.agentId) {
154
+ agentIds.add(record.agentId);
155
+ }
156
+ if (recordType !== "user" && recordType !== "assistant")
157
+ continue;
158
+ if (!isObject(record.message))
159
+ continue;
160
+ const message = record.message;
161
+ const timestamp = parseTimestamp(record.timestamp);
162
+ const model = typeof message.model === "string" ? message.model : undefined;
163
+ const content = message.content;
164
+ const uuid = typeof record.uuid === "string" && record.uuid ? record.uuid : undefined;
165
+ let componentIndex = 0;
166
+ const emit = (event) => {
167
+ events.push({
168
+ ...event,
169
+ ...uuid !== undefined ? { sourceRecordId: uuid } : {},
170
+ sourceOffset: byteOffset,
171
+ sourceAnchorKind: "byte",
172
+ componentIndex: componentIndex++
173
+ });
174
+ };
175
+ if (recordType === "user") {
176
+ if (typeof content === "string") {
177
+ emit(messageEvent("user", content, line, timestamp));
178
+ continue;
179
+ }
180
+ const textParts = [];
181
+ for (const block of Array.isArray(content) ? content : []) {
182
+ if (!isObject(block))
183
+ continue;
184
+ if (block.type === "tool_result") {
185
+ emit(toolResultEvent(blocksText(block.content), typeof block.tool_use_id === "string" ? block.tool_use_id : undefined, typeof block.is_error === "boolean" ? !block.is_error : undefined, line, timestamp));
186
+ } else if (block.type === "text" && typeof block.text === "string") {
187
+ textParts.push(block.text);
188
+ } else if (block.type === "image") {
189
+ textParts.push("[image]");
190
+ }
191
+ }
192
+ if (textParts.length > 0) {
193
+ emit(messageEvent("user", textParts.join(`
194
+ `), line, timestamp));
195
+ }
196
+ continue;
197
+ }
198
+ if (typeof content === "string") {
199
+ if (content.trim()) {
200
+ emit(messageEvent("assistant", content, line, timestamp, model));
201
+ }
202
+ continue;
203
+ }
204
+ for (const block of Array.isArray(content) ? content : []) {
205
+ if (!isObject(block))
206
+ continue;
207
+ if (block.type === "thinking") {
208
+ emit(reasoningEvent(typeof block.thinking === "string" ? block.thinking : "", line, timestamp, model));
209
+ } else if (block.type === "text") {
210
+ emit(messageEvent("assistant", typeof block.text === "string" ? block.text : "", line, timestamp, model));
211
+ } else if (block.type === "tool_use") {
212
+ emit(toolCallEvent(typeof block.id === "string" ? block.id : undefined, typeof block.name === "string" ? block.name : undefined, jsonString(block.input), line, timestamp, model));
213
+ }
214
+ }
215
+ }
216
+ if (agentIds.size > 1) {
217
+ throw new NormalizationError("source_group_conflict", `Claude Code subagent transcript contains multiple agent ids: ${[...agentIds].map((id) => JSON.stringify(id)).sort().join(", ")}.`);
218
+ }
219
+ if (sessionIds.size > 1 && (!standaloneSidechain || agentIds.size === 0)) {
220
+ throw new NormalizationError("source_group_conflict", `Claude Code transcript contains multiple session ids: ${[...sessionIds].map((id) => JSON.stringify(id)).sort().join(", ")}.`);
221
+ }
222
+ const [sessionId] = sessionIds;
223
+ const [agentId] = agentIds;
224
+ const cwd = cwdCandidate?.value;
225
+ const gitBranch = branchCandidate?.value;
226
+ return {
227
+ events,
228
+ context: {
229
+ source: "claude-code",
230
+ ...cwd ? { cwd } : {},
231
+ ...gitBranch ? { gitBranch } : {},
232
+ ...agentId || sessionId ? { sourceGroupId: agentId || sessionId } : {}
233
+ },
234
+ diagnostics
235
+ };
236
+ }
237
+ };
238
+ function isConversationalRecord(record) {
239
+ return (record.type === "user" || record.type === "assistant") && isObject(record.message);
240
+ }
241
+ function earlier(current, next) {
242
+ if (current === undefined)
243
+ return next;
244
+ if (next.ts < current.ts)
245
+ return next;
246
+ if (next.ts > current.ts)
247
+ return current;
248
+ return next.tie < current.tie ? next : current;
249
+ }
250
+ function messageEvent(role, content, inputLine, timestamp, model) {
251
+ return {
252
+ type: "message",
253
+ role,
254
+ content,
255
+ inputLine,
256
+ ...timestamp ? { timestamp } : {},
257
+ ...model ? { model } : {}
258
+ };
259
+ }
260
+ function reasoningEvent(content, inputLine, timestamp, model) {
261
+ return {
262
+ type: "reasoning",
263
+ content,
264
+ inputLine,
265
+ ...timestamp ? { timestamp } : {},
266
+ ...model ? { model } : {}
267
+ };
268
+ }
269
+ function toolCallEvent(id, name, args, inputLine, timestamp, model) {
270
+ return {
271
+ type: "tool_call",
272
+ args,
273
+ inputLine,
274
+ ...id ? { id } : {},
275
+ ...name ? { name } : {},
276
+ ...timestamp ? { timestamp } : {},
277
+ ...model ? { model } : {}
278
+ };
279
+ }
280
+ function toolResultEvent(content, callId, ok, inputLine, timestamp) {
281
+ return {
282
+ type: "tool_result",
283
+ content,
284
+ ...typeof ok === "boolean" ? { ok } : {},
285
+ inputLine,
286
+ ...callId ? { callId } : {},
287
+ ...timestamp ? { timestamp } : {}
288
+ };
289
+ }
290
+
291
+ // src/adapters/codex/index.ts
292
+ var INJECTED_PREFIXES = [
293
+ "<environment_context>",
294
+ "<user_instructions>",
295
+ "<permissions instructions>",
296
+ "<turn_context>"
297
+ ];
298
+ var codexAdapter = {
299
+ source: "codex",
300
+ decode(transcript) {
301
+ const diagnostics = [];
302
+ const events = [];
303
+ let cwd;
304
+ let gitBranch;
305
+ let model;
306
+ let createdAt;
307
+ let sessionId;
308
+ for (const { value: record, line, byteOffset } of parseJsonLines(transcript, diagnostics)) {
309
+ const recordType = record.type;
310
+ const payload = isObject(record.payload) ? record.payload : {};
311
+ const timestamp = parseTimestamp(record.timestamp);
312
+ const payloadType = payload.type;
313
+ const emit = (event) => {
314
+ events.push({
315
+ ...event,
316
+ sourceOffset: byteOffset,
317
+ sourceAnchorKind: "byte",
318
+ componentIndex: 0
319
+ });
320
+ };
321
+ if (recordType === "session_meta") {
322
+ if (!cwd && typeof payload.cwd === "string" && payload.cwd)
323
+ cwd = payload.cwd;
324
+ createdAt ??= parseTimestamp(payload.timestamp) ?? timestamp;
325
+ if (!gitBranch && isObject(payload.git) && typeof payload.git.branch === "string") {
326
+ gitBranch = payload.git.branch;
327
+ }
328
+ if (!sessionId && typeof payload.id === "string" && payload.id) {
329
+ sessionId = payload.id;
330
+ }
331
+ continue;
332
+ }
333
+ if (recordType === "turn_context") {
334
+ if (!cwd && typeof payload.cwd === "string" && payload.cwd)
335
+ cwd = payload.cwd;
336
+ if (!model && typeof payload.model === "string" && payload.model) {
337
+ model = payload.model;
338
+ }
339
+ continue;
340
+ }
341
+ if (recordType === "event_msg") {
342
+ if (payloadType === "agent_reasoning" && typeof payload.text === "string" && payload.text.trim()) {
343
+ emit({
344
+ type: "reasoning",
345
+ content: payload.text,
346
+ inputLine: line,
347
+ ...timestamp ? { timestamp } : {}
348
+ });
349
+ }
350
+ continue;
351
+ }
352
+ if (recordType !== "response_item")
353
+ continue;
354
+ if (payloadType === "message") {
355
+ const role = payload.role;
356
+ const content = blocksText(payload.content);
357
+ if (role === "user") {
358
+ const head = content.trimStart();
359
+ if (INJECTED_PREFIXES.some((prefix) => head.startsWith(prefix))) {
360
+ diagnostics.push({
361
+ code: "injected_context_dropped",
362
+ message: `Dropped Codex system-injected user content on line ${line}.`,
363
+ inputLine: line
364
+ });
365
+ } else {
366
+ emit({
367
+ type: "message",
368
+ role: "user",
369
+ content,
370
+ inputLine: line,
371
+ ...timestamp ? { timestamp } : {}
372
+ });
373
+ }
374
+ } else if (role === "assistant") {
375
+ emit({
376
+ type: "message",
377
+ role: "assistant",
378
+ content,
379
+ inputLine: line,
380
+ ...timestamp ? { timestamp } : {}
381
+ });
382
+ }
383
+ continue;
384
+ }
385
+ if (payloadType === "function_call") {
386
+ emit({
387
+ type: "tool_call",
388
+ args: typeof payload.arguments === "string" && payload.arguments ? payload.arguments : "{}",
389
+ inputLine: line,
390
+ ...typeof payload.call_id === "string" ? { id: payload.call_id } : {},
391
+ ...typeof payload.name === "string" ? { name: payload.name } : {},
392
+ ...timestamp ? { timestamp } : {}
393
+ });
394
+ continue;
395
+ }
396
+ if (payloadType === "custom_tool_call") {
397
+ emit({
398
+ type: "tool_call",
399
+ args: jsonString({ input: payload.input ?? "" }),
400
+ inputLine: line,
401
+ ...typeof payload.call_id === "string" ? { id: payload.call_id } : {},
402
+ ...typeof payload.name === "string" ? { name: payload.name } : {},
403
+ ...timestamp ? { timestamp } : {}
404
+ });
405
+ continue;
406
+ }
407
+ if (payloadType === "web_search_call") {
408
+ const args = {};
409
+ for (const [key, value] of Object.entries(payload)) {
410
+ if (key !== "type" && key !== "call_id" && key !== "status") {
411
+ args[key] = value;
412
+ }
413
+ }
414
+ emit({
415
+ type: "tool_call",
416
+ name: "web_search",
417
+ args: jsonString(args),
418
+ inputLine: line,
419
+ ...typeof payload.call_id === "string" ? { id: payload.call_id } : {},
420
+ ...timestamp ? { timestamp } : {}
421
+ });
422
+ continue;
423
+ }
424
+ if (payloadType === "tool_search_call") {
425
+ emit({
426
+ type: "tool_call",
427
+ name: "tool_search",
428
+ args: typeof payload.arguments === "string" && payload.arguments ? payload.arguments : jsonString(payload.arguments),
429
+ inputLine: line,
430
+ ...typeof payload.call_id === "string" ? { id: payload.call_id } : {},
431
+ ...timestamp ? { timestamp } : {}
432
+ });
433
+ continue;
434
+ }
435
+ if (payloadType === "function_call_output" || payloadType === "custom_tool_call_output" || payloadType === "tool_search_output") {
436
+ emit({
437
+ type: "tool_result",
438
+ content: payloadType === "tool_search_output" ? jsonString(payload.tools ?? []) : outputText(payload.output),
439
+ inputLine: line,
440
+ ...typeof payload.call_id === "string" ? { callId: payload.call_id } : {},
441
+ ...timestamp ? { timestamp } : {}
442
+ });
443
+ }
444
+ }
445
+ return {
446
+ events,
447
+ context: {
448
+ source: "codex",
449
+ ...cwd ? { cwd } : {},
450
+ ...gitBranch ? { gitBranch } : {},
451
+ ...model ? { model } : {},
452
+ ...createdAt ? { createdAt } : {},
453
+ ...sessionId ? { sourceGroupId: sessionId } : {}
454
+ },
455
+ diagnostics
456
+ };
457
+ }
458
+ };
459
+ function outputText(output) {
460
+ if (typeof output === "string")
461
+ return output;
462
+ if (Array.isArray(output))
463
+ return blocksText(output) || jsonString(output);
464
+ if (isObject(output)) {
465
+ return typeof output.content === "string" && output.content ? output.content : jsonString(output);
466
+ }
467
+ return output == null ? "" : String(output);
468
+ }
469
+
470
+ // src/adapters/copilot-cli/index.ts
471
+ var KNOWN_EVENT_TYPES = new Set([
472
+ "abort",
473
+ "assistant.message",
474
+ "assistant.turn_end",
475
+ "assistant.turn_start",
476
+ "hook.end",
477
+ "hook.start",
478
+ "session.compaction_complete",
479
+ "session.compaction_start",
480
+ "session.info",
481
+ "session.mode_changed",
482
+ "session.model_change",
483
+ "session.plan_changed",
484
+ "session.resume",
485
+ "session.shutdown",
486
+ "session.start",
487
+ "session.task_complete",
488
+ "system.notification",
489
+ "tool.execution_complete",
490
+ "tool.execution_start",
491
+ "user.message"
492
+ ]);
493
+ var copilotCliAdapter = {
494
+ source: "copilot-cli",
495
+ decode(transcript) {
496
+ const diagnostics = [];
497
+ const rows = parseJsonLines(transcript, diagnostics);
498
+ const events = [];
499
+ let recognizedRows = 0;
500
+ let cwd;
501
+ let gitBranch;
502
+ let model;
503
+ let sourceGroupId;
504
+ let createdAt;
505
+ for (const { value: row } of rows) {
506
+ if (typeof row.type !== "string" || !KNOWN_EVENT_TYPES.has(row.type))
507
+ continue;
508
+ const data = isObject(row.data) ? row.data : {};
509
+ if (row.type === "session.start") {
510
+ sourceGroupId ??= nonemptyString(data.sessionId);
511
+ createdAt ??= parseTimestamp(data.startTime);
512
+ const context = isObject(data.context) ? data.context : {};
513
+ cwd ??= nonemptyString(context.cwd);
514
+ gitBranch ??= nonemptyString(context.branch);
515
+ } else if (row.type === "hook.start" && data.hookType === "userPromptSubmitted") {
516
+ const input = isObject(data.input) ? data.input : {};
517
+ sourceGroupId ??= nonemptyString(input.sessionId);
518
+ cwd ??= nonemptyString(input.cwd);
519
+ } else if (row.type === "tool.execution_complete") {
520
+ model ??= nonemptyString(data.model);
521
+ } else if (row.type === "session.model_change") {
522
+ model ??= nonemptyString(data.newModel);
523
+ } else if (row.type === "session.shutdown") {
524
+ model ??= nonemptyString(data.currentModel);
525
+ }
526
+ }
527
+ for (const { value: row, line, byteOffset } of rows) {
528
+ const rowType = row.type;
529
+ if (typeof rowType !== "string" || !KNOWN_EVENT_TYPES.has(rowType)) {
530
+ diagnostics.push({
531
+ code: "noise_record_dropped",
532
+ message: `Skipped unsupported Copilot CLI event on line ${line}.`,
533
+ inputLine: line
534
+ });
535
+ continue;
536
+ }
537
+ recognizedRows += 1;
538
+ const data = isObject(row.data) ? row.data : {};
539
+ const timestamp = parseTimestamp(row.timestamp);
540
+ const sourceRecordId = nonemptyString(row.id);
541
+ let componentIndex = 0;
542
+ const emit = (event) => {
543
+ events.push({
544
+ ...event,
545
+ ...sourceRecordId ? { sourceRecordId } : { sourceOffset: byteOffset, sourceAnchorKind: "byte" },
546
+ sourceSequence: line - 1,
547
+ componentIndex: componentIndex++,
548
+ inputLine: line
549
+ });
550
+ };
551
+ if (rowType === "hook.start" && data.hookType === "userPromptSubmitted") {
552
+ const input = isObject(data.input) ? data.input : {};
553
+ const promptTimestamp = parseTimestamp(input.timestamp) ?? timestamp;
554
+ emit({
555
+ type: "message",
556
+ role: "user",
557
+ content: typeof input.prompt === "string" ? input.prompt : "",
558
+ ...promptTimestamp ? { timestamp: promptTimestamp } : {}
559
+ });
560
+ continue;
561
+ }
562
+ if (rowType === "assistant.message") {
563
+ const eventModel = nonemptyString(data.model);
564
+ if (typeof data.reasoningText === "string" && data.reasoningText.trim()) {
565
+ emit({
566
+ type: "reasoning",
567
+ content: data.reasoningText,
568
+ ...timestamp ? { timestamp } : {},
569
+ ...eventModel ? { model: eventModel } : {}
570
+ });
571
+ }
572
+ if (typeof data.content === "string" && data.content.trim()) {
573
+ emit({
574
+ type: "message",
575
+ role: "assistant",
576
+ content: data.content,
577
+ ...timestamp ? { timestamp } : {},
578
+ ...eventModel ? { model: eventModel } : {}
579
+ });
580
+ }
581
+ if (Array.isArray(data.toolRequests)) {
582
+ for (const request of data.toolRequests) {
583
+ if (!isObject(request))
584
+ continue;
585
+ const callId = nonemptyString(request.toolCallId);
586
+ const name = nonemptyString(request.name);
587
+ emit({
588
+ type: "tool_call",
589
+ args: typeof request.arguments === "string" ? request.arguments : jsonString(request.arguments),
590
+ ...callId ? { id: callId } : {},
591
+ ...name ? { name } : {},
592
+ ...timestamp ? { timestamp } : {},
593
+ ...eventModel ? { model: eventModel } : {}
594
+ });
595
+ }
596
+ }
597
+ continue;
598
+ }
599
+ if (rowType === "tool.execution_complete") {
600
+ const callId = nonemptyString(data.toolCallId);
601
+ const eventModel = nonemptyString(data.model);
602
+ emit({
603
+ type: "tool_result",
604
+ content: copilotResultContent(data),
605
+ ...callId ? { callId } : {},
606
+ ...typeof data.success === "boolean" ? { ok: data.success } : {},
607
+ ...timestamp ? { timestamp } : {},
608
+ ...eventModel ? { model: eventModel } : {}
609
+ });
610
+ }
611
+ }
612
+ if (recognizedRows === 0)
613
+ throw invalidCopilotTranscript();
614
+ return {
615
+ events,
616
+ context: {
617
+ source: "copilot-cli",
618
+ ...cwd ? { cwd } : {},
619
+ ...gitBranch ? { gitBranch } : {},
620
+ ...model ? { model } : {},
621
+ ...sourceGroupId ? { sourceGroupId } : {},
622
+ ...createdAt ? { createdAt } : {}
623
+ },
624
+ diagnostics
625
+ };
626
+ }
627
+ };
628
+ function nonemptyString(value) {
629
+ return typeof value === "string" && value.length > 0 ? value : undefined;
630
+ }
631
+ function copilotResultContent(data) {
632
+ if (isObject(data.result)) {
633
+ const content = data.result.content;
634
+ if (typeof content === "string")
635
+ return content;
636
+ if (content !== undefined)
637
+ return stringifyContent(content);
638
+ }
639
+ if (isObject(data.error) && typeof data.error.message === "string") {
640
+ return data.error.message;
641
+ }
642
+ if (data.error !== undefined)
643
+ return stringifyContent(data.error);
644
+ return "";
645
+ }
646
+ function stringifyContent(value) {
647
+ if (typeof value === "string")
648
+ return value;
649
+ if (value === null || value === undefined)
650
+ return "";
651
+ return isObject(value) || Array.isArray(value) ? jsonString(value) : String(value);
652
+ }
653
+ function invalidCopilotTranscript() {
654
+ return new NormalizationError("invalid_input", "Copilot CLI transcript must be native event JSONL with recognized type and data records.");
655
+ }
656
+
657
+ // src/adapters/cursor/index.ts
658
+ var cursorAdapter = {
659
+ source: "cursor",
660
+ decode(transcript) {
661
+ const diagnostics = [];
662
+ const events = [];
663
+ let recognizedRows = 0;
664
+ for (const { value: row, line, byteOffset } of parseJsonLines(transcript, diagnostics)) {
665
+ if (row.role !== "user" && row.role !== "assistant" || !isObject(row.message)) {
666
+ diagnostics.push({
667
+ code: "noise_record_dropped",
668
+ message: `Skipped unsupported Cursor row on line ${line}.`,
669
+ inputLine: line
670
+ });
671
+ continue;
672
+ }
673
+ recognizedRows += 1;
674
+ const role = row.role;
675
+ const model = typeof row.message.model === "string" && row.message.model ? row.message.model : undefined;
676
+ const content = row.message.content;
677
+ const blocks = Array.isArray(content) ? content : [{ type: "text", text: typeof content === "string" ? content : "" }];
678
+ const sourceRecordId = typeof row.id === "string" && row.id ? row.id : undefined;
679
+ let componentIndex = 0;
680
+ const emit = (event) => {
681
+ events.push({
682
+ ...event,
683
+ ...sourceRecordId ? { sourceRecordId } : { sourceOffset: byteOffset, sourceAnchorKind: "byte" },
684
+ sourceSequence: line - 1,
685
+ componentIndex: componentIndex++,
686
+ inputLine: line
687
+ });
688
+ };
689
+ for (const block of blocks) {
690
+ if (!isObject(block))
691
+ continue;
692
+ if (block.type === "text") {
693
+ emit({
694
+ type: "message",
695
+ role,
696
+ content: typeof block.text === "string" ? block.text : "",
697
+ ...model ? { model } : {}
698
+ });
699
+ } else if (block.type === "thinking") {
700
+ emit({
701
+ type: "reasoning",
702
+ content: typeof block.thinking === "string" ? block.thinking : "",
703
+ ...model ? { model } : {}
704
+ });
705
+ } else if (block.type === "tool_use") {
706
+ emit({
707
+ type: "tool_call",
708
+ args: jsonString(block.input),
709
+ ...typeof block.id === "string" && block.id ? { id: block.id } : {},
710
+ ...typeof block.name === "string" && block.name ? { name: block.name } : {},
711
+ ...model ? { model } : {}
712
+ });
713
+ } else if (block.type === "tool_result") {
714
+ emit({
715
+ type: "tool_result",
716
+ content: resultContent(block.content),
717
+ ...typeof block.tool_use_id === "string" && block.tool_use_id ? { callId: block.tool_use_id } : {},
718
+ ...typeof block.is_error === "boolean" ? { ok: !block.is_error } : {},
719
+ ...model ? { model } : {}
720
+ });
721
+ } else {
722
+ diagnostics.push({
723
+ code: "noise_record_dropped",
724
+ message: `Skipped unsupported Cursor content block ${JSON.stringify(block.type)} ` + `on line ${line}.`,
725
+ inputLine: line
726
+ });
727
+ }
728
+ }
729
+ }
730
+ if (recognizedRows === 0)
731
+ throw invalidCursorTranscript();
732
+ return {
733
+ events,
734
+ context: { source: "cursor" },
735
+ diagnostics
736
+ };
737
+ }
738
+ };
739
+ function resultContent(value) {
740
+ if (typeof value === "string")
741
+ return value;
742
+ if (Array.isArray(value))
743
+ return blocksText(value);
744
+ if (value === null || value === undefined)
745
+ return "";
746
+ return isObject(value) ? jsonString(value) : String(value);
747
+ }
748
+ function invalidCursorTranscript() {
749
+ return new NormalizationError("invalid_input", "Cursor transcript must be JSONL with role and message.content records.");
750
+ }
751
+
752
+ // src/adapters/droid/index.ts
753
+ var TRANSPORT_TYPES2 = new Set([
754
+ "todo_state",
755
+ "session_end",
756
+ "compaction_state"
757
+ ]);
758
+ var droidAdapter = {
759
+ source: "droid",
760
+ decode(transcript) {
761
+ const diagnostics = [];
762
+ const events = [];
763
+ let cwd;
764
+ let sourceGroupId;
765
+ for (const { value: record, line, byteOffset } of parseJsonLines(transcript, diagnostics)) {
766
+ const recordType = record.type;
767
+ if (recordType === "session_start") {
768
+ if (sourceGroupId === undefined && typeof record.id === "string" && record.id) {
769
+ sourceGroupId = record.id;
770
+ }
771
+ if (cwd === undefined && typeof record.cwd === "string" && record.cwd) {
772
+ cwd = record.cwd;
773
+ }
774
+ continue;
775
+ }
776
+ if (typeof recordType === "string" && TRANSPORT_TYPES2.has(recordType)) {
777
+ continue;
778
+ }
779
+ if (recordType !== "message" || !isObject(record.message))
780
+ continue;
781
+ const role = record.message.role;
782
+ if (role !== "user" && role !== "assistant")
783
+ continue;
784
+ const blocks = record.message.content;
785
+ if (!Array.isArray(blocks))
786
+ continue;
787
+ let componentIndex = 0;
788
+ const emit = (event) => {
789
+ events.push({
790
+ ...event,
791
+ sourceOffset: byteOffset,
792
+ sourceAnchorKind: "byte",
793
+ componentIndex: componentIndex++
794
+ });
795
+ };
796
+ for (const block of blocks) {
797
+ if (!isObject(block))
798
+ continue;
799
+ if (block.type === "thinking") {
800
+ emit({
801
+ type: "reasoning",
802
+ content: typeof block.thinking === "string" ? block.thinking : "",
803
+ inputLine: line
804
+ });
805
+ } else if (block.type === "text") {
806
+ emit({
807
+ type: "message",
808
+ role,
809
+ content: typeof block.text === "string" ? block.text : "",
810
+ inputLine: line
811
+ });
812
+ } else if (block.type === "tool_use" && role === "assistant") {
813
+ emit({
814
+ type: "tool_call",
815
+ args: jsonString(block.input),
816
+ inputLine: line,
817
+ ...typeof block.id === "string" && block.id ? { id: block.id } : {},
818
+ ...typeof block.name === "string" && block.name ? { name: block.name } : {}
819
+ });
820
+ } else if (block.type === "tool_result" && role === "user") {
821
+ emit({
822
+ type: "tool_result",
823
+ content: toolResultContent(block.content, block.is_error === true),
824
+ inputLine: line,
825
+ ...typeof block.tool_use_id === "string" && block.tool_use_id ? { callId: block.tool_use_id } : {}
826
+ });
827
+ }
828
+ }
829
+ }
830
+ return {
831
+ events,
832
+ context: {
833
+ source: "droid",
834
+ ...cwd ? { cwd } : {},
835
+ ...sourceGroupId ? { sourceGroupId } : {}
836
+ },
837
+ diagnostics
838
+ };
839
+ }
840
+ };
841
+ function toolResultContent(content, isError) {
842
+ const text = typeof content === "string" ? content : blocksText(content);
843
+ return isError && !/^error/i.test(text) ? `Error: ${text}` : text;
844
+ }
845
+
846
+ // src/adapters/gemini-cli/index.ts
847
+ var TERMINAL_TOOL_STATUSES = new Set(["cancelled", "error", "success"]);
848
+ var geminiCliAdapter = {
849
+ source: "gemini-cli",
850
+ decode(transcript) {
851
+ const document = parseGeminiDocument(transcript);
852
+ const diagnostics = [];
853
+ const events = [];
854
+ for (let messageIndex = 0;messageIndex < document.messages.length; messageIndex += 1) {
855
+ const message = document.messages[messageIndex];
856
+ if (!isObject(message))
857
+ continue;
858
+ const messageType = message.type;
859
+ if (messageType === "info")
860
+ continue;
861
+ const timestamp = parseTimestamp(message.timestamp);
862
+ const model = nonemptyString2(message.model);
863
+ const sourceRecordId = nonemptyString2(message.id);
864
+ let componentIndex = 0;
865
+ const emit = (event) => {
866
+ events.push({
867
+ ...event,
868
+ ...sourceRecordId ? { sourceRecordId } : { sourceOffset: messageIndex, sourceAnchorKind: "ordinal" },
869
+ sourceSequence: messageIndex,
870
+ componentIndex: componentIndex++
871
+ });
872
+ };
873
+ if (messageType === "user") {
874
+ emit({
875
+ type: "message",
876
+ role: "user",
877
+ content: blocksText(message.content),
878
+ ...timestamp ? { timestamp } : {}
879
+ });
880
+ continue;
881
+ }
882
+ if (messageType !== "gemini") {
883
+ diagnostics.push({
884
+ code: "noise_record_dropped",
885
+ message: `Skipped unsupported Gemini CLI message type ${JSON.stringify(messageType)} ` + `at position ${messageIndex + 1}.`
886
+ });
887
+ continue;
888
+ }
889
+ if (Array.isArray(message.thoughts)) {
890
+ for (const thought of message.thoughts) {
891
+ const content2 = thoughtContent(thought);
892
+ if (!content2.trim())
893
+ continue;
894
+ emit({
895
+ type: "reasoning",
896
+ content: content2,
897
+ ...timestamp ? { timestamp } : {},
898
+ ...model ? { model } : {}
899
+ });
900
+ }
901
+ }
902
+ const content = blocksText(message.content);
903
+ if (content.trim()) {
904
+ emit({
905
+ type: "message",
906
+ role: "assistant",
907
+ content,
908
+ ...timestamp ? { timestamp } : {},
909
+ ...model ? { model } : {}
910
+ });
911
+ }
912
+ if (!Array.isArray(message.toolCalls))
913
+ continue;
914
+ for (const rawCall of message.toolCalls) {
915
+ if (!isObject(rawCall))
916
+ continue;
917
+ const callId = nonemptyString2(rawCall.id);
918
+ const name = nonemptyString2(rawCall.name);
919
+ const callTimestamp = parseTimestamp(rawCall.timestamp) ?? timestamp;
920
+ emit({
921
+ type: "tool_call",
922
+ args: jsonString(rawCall.args),
923
+ ...callId ? { id: callId } : {},
924
+ ...name ? { name } : {},
925
+ ...callTimestamp ? { timestamp: callTimestamp } : {},
926
+ ...model ? { model } : {}
927
+ });
928
+ const status = nonemptyString2(rawCall.status);
929
+ const outputs = toolOutputs(rawCall.result);
930
+ if (outputs.length === 0 && (!status || !TERMINAL_TOOL_STATUSES.has(status))) {
931
+ continue;
932
+ }
933
+ emit({
934
+ type: "tool_result",
935
+ content: outputs.join(`
936
+ `),
937
+ ...callId ? { callId } : {},
938
+ ...status === "success" ? { ok: true } : status === "error" || status === "cancelled" ? { ok: false } : {},
939
+ ...callTimestamp ? { timestamp: callTimestamp } : {},
940
+ ...model ? { model } : {}
941
+ });
942
+ }
943
+ }
944
+ const sourceGroupId = nonemptyString2(document.sessionId);
945
+ const createdAt = parseTimestamp(document.startTime);
946
+ return {
947
+ events,
948
+ context: {
949
+ source: "gemini-cli",
950
+ ...sourceGroupId ? { sourceGroupId } : {},
951
+ ...createdAt ? { createdAt } : {}
952
+ },
953
+ diagnostics
954
+ };
955
+ }
956
+ };
957
+ function parseGeminiDocument(transcript) {
958
+ let parsed;
959
+ try {
960
+ parsed = JSON.parse(transcript);
961
+ } catch {
962
+ throw invalidGeminiTranscript();
963
+ }
964
+ if (!isObject(parsed) || !Array.isArray(parsed.messages) || typeof parsed.sessionId !== "string" && typeof parsed.projectHash !== "string") {
965
+ throw invalidGeminiTranscript();
966
+ }
967
+ return parsed;
968
+ }
969
+ function nonemptyString2(value) {
970
+ return typeof value === "string" && value.length > 0 ? value : undefined;
971
+ }
972
+ function thoughtContent(value) {
973
+ if (!isObject(value))
974
+ return String(value ?? "");
975
+ return ["subject", "description"].flatMap((key) => typeof value[key] === "string" && value[key] ? [value[key]] : []).join(" — ");
976
+ }
977
+ function toolOutputs(value) {
978
+ if (!Array.isArray(value))
979
+ return [];
980
+ const outputs = [];
981
+ for (const item of value) {
982
+ if (!isObject(item) || !isObject(item.functionResponse))
983
+ continue;
984
+ const response = item.functionResponse.response;
985
+ if (!isObject(response))
986
+ continue;
987
+ if (response.output !== undefined) {
988
+ outputs.push(stringContent(response.output));
989
+ } else if (Object.keys(response).length > 0) {
990
+ outputs.push(jsonString(response));
991
+ }
992
+ }
993
+ return outputs;
994
+ }
995
+ function stringContent(value) {
996
+ if (typeof value === "string")
997
+ return value;
998
+ if (value === null || value === undefined)
999
+ return "";
1000
+ return isObject(value) || Array.isArray(value) ? jsonString(value) : String(value);
1001
+ }
1002
+ function invalidGeminiTranscript() {
1003
+ return new NormalizationError("invalid_input", "Gemini CLI transcript must be one native session JSON document with " + "session metadata and a messages array.");
1004
+ }
1005
+
1006
+ // src/adapters/hermes/index.ts
1007
+ var CONTENT_JSON_PREFIX = "\x00json:";
1008
+ var hermesAdapter = {
1009
+ source: "hermes",
1010
+ decode(transcript) {
1011
+ const diagnostics = [];
1012
+ const events = [];
1013
+ const parsed = parseTranscript(transcript);
1014
+ const rows = orderRows(parsed.messages.filter((row) => row.active !== 0 && row.active !== false));
1015
+ const callsByRow = planToolCalls(rows, diagnostics);
1016
+ for (let index = 0;index < rows.length; index += 1) {
1017
+ const row = rows[index];
1018
+ if (row === undefined)
1019
+ continue;
1020
+ const timestamp = hermesTimestamp(row.timestamp);
1021
+ const id = rowId(row);
1022
+ let componentIndex = 0;
1023
+ const emit = (event) => {
1024
+ events.push({
1025
+ ...event,
1026
+ ...id !== undefined ? { sourceRecordId: String(id) } : { sourceOffset: index, sourceAnchorKind: "ordinal" },
1027
+ ...typeof id === "number" ? { sourceSequence: id } : {},
1028
+ componentIndex: componentIndex++
1029
+ });
1030
+ };
1031
+ if (row.role === "user") {
1032
+ const content = contentText(row.content);
1033
+ if (content) {
1034
+ emit({
1035
+ type: "message",
1036
+ role: "user",
1037
+ content,
1038
+ ...timestamp ? { timestamp } : {}
1039
+ });
1040
+ }
1041
+ continue;
1042
+ }
1043
+ if (row.role === "assistant") {
1044
+ const reasoning = reasoningText(row);
1045
+ if (reasoning) {
1046
+ emit({
1047
+ type: "reasoning",
1048
+ content: reasoning,
1049
+ ...timestamp ? { timestamp } : {}
1050
+ });
1051
+ }
1052
+ const content = contentText(row.content);
1053
+ if (content) {
1054
+ emit({
1055
+ type: "message",
1056
+ role: "assistant",
1057
+ content,
1058
+ ...timestamp ? { timestamp } : {}
1059
+ });
1060
+ }
1061
+ for (const call of callsByRow.get(index) ?? []) {
1062
+ emit({
1063
+ type: "tool_call",
1064
+ args: call.args,
1065
+ ...call.id ? { id: call.id } : {},
1066
+ ...call.name ? { name: call.name } : {},
1067
+ ...timestamp ? { timestamp } : {}
1068
+ });
1069
+ }
1070
+ continue;
1071
+ }
1072
+ if (row.role === "tool") {
1073
+ emit({
1074
+ type: "tool_result",
1075
+ content: contentText(row.content),
1076
+ ...typeof row.tool_call_id === "string" && row.tool_call_id ? { callId: row.tool_call_id } : {},
1077
+ ...timestamp ? { timestamp } : {}
1078
+ });
1079
+ }
1080
+ }
1081
+ const session = parsed.session ?? {};
1082
+ const model = typeof session.model === "string" && session.model ? session.model : undefined;
1083
+ const cwd = typeof session.cwd === "string" && session.cwd ? session.cwd : undefined;
1084
+ const createdAt = hermesTimestamp(session.started_at);
1085
+ const sourceGroupId = resolveGroupId(session, parsed.messages);
1086
+ return {
1087
+ events,
1088
+ context: {
1089
+ source: "hermes",
1090
+ ...cwd ? { cwd } : {},
1091
+ ...model ? { model } : {},
1092
+ ...createdAt ? { createdAt } : {},
1093
+ ...sourceGroupId ? { sourceGroupId } : {}
1094
+ },
1095
+ diagnostics
1096
+ };
1097
+ }
1098
+ };
1099
+ function parseTranscript(transcript) {
1100
+ let parsed;
1101
+ try {
1102
+ parsed = JSON.parse(transcript);
1103
+ } catch {
1104
+ throw invalidHermesTranscript();
1105
+ }
1106
+ if (Array.isArray(parsed)) {
1107
+ if (!parsed.every(isObject))
1108
+ throw invalidHermesTranscript();
1109
+ return { messages: parsed };
1110
+ }
1111
+ if (isObject(parsed) && Array.isArray(parsed.messages)) {
1112
+ if (!parsed.messages.every(isObject))
1113
+ throw invalidHermesTranscript();
1114
+ return {
1115
+ messages: parsed.messages,
1116
+ ...isObject(parsed.session) ? { session: parsed.session } : {}
1117
+ };
1118
+ }
1119
+ throw invalidHermesTranscript();
1120
+ }
1121
+ function orderRows(rows) {
1122
+ if (!rows.every((row) => typeof row.id === "number"))
1123
+ return rows;
1124
+ return rows.map((row, index) => ({ row, index })).sort((left, right) => left.row.id - right.row.id || left.index - right.index).map(({ row }) => row);
1125
+ }
1126
+ function planToolCalls(rows, diagnostics) {
1127
+ const plan = new Map;
1128
+ for (let index = 0;index < rows.length; index += 1) {
1129
+ const row = rows[index];
1130
+ if (row === undefined || row.role !== "assistant")
1131
+ continue;
1132
+ const calls = rowToolCalls(row, index, diagnostics);
1133
+ if (calls.length === 0)
1134
+ continue;
1135
+ const idless = calls.filter((call) => !call.id);
1136
+ if (idless.length > 0) {
1137
+ const claimed = new Set(calls.flatMap((call) => call.id ? [call.id] : []));
1138
+ const available = [];
1139
+ for (let cursor = index + 1;cursor < rows.length; cursor += 1) {
1140
+ const next = rows[cursor];
1141
+ if (next === undefined)
1142
+ continue;
1143
+ if (next.role !== "tool")
1144
+ break;
1145
+ if (typeof next.tool_call_id === "string" && next.tool_call_id && !claimed.has(next.tool_call_id)) {
1146
+ available.push(next.tool_call_id);
1147
+ }
1148
+ }
1149
+ if (available.length === idless.length) {
1150
+ for (let position = 0;position < idless.length; position += 1) {
1151
+ const call = idless[position];
1152
+ const adopted = available[position];
1153
+ if (call && adopted !== undefined)
1154
+ call.id = adopted;
1155
+ }
1156
+ }
1157
+ }
1158
+ plan.set(index, calls);
1159
+ }
1160
+ return plan;
1161
+ }
1162
+ function rowToolCalls(row, index, diagnostics) {
1163
+ let raw = row.tool_calls;
1164
+ if (typeof raw === "string" && raw) {
1165
+ try {
1166
+ raw = JSON.parse(raw);
1167
+ } catch {
1168
+ diagnostics.push({
1169
+ code: "invalid_json_line",
1170
+ message: `Skipped undecodable tool_calls on message ${index + 1}.`,
1171
+ inputLine: index + 1
1172
+ });
1173
+ return [];
1174
+ }
1175
+ }
1176
+ if (!Array.isArray(raw))
1177
+ return [];
1178
+ const calls = [];
1179
+ for (const entry of raw) {
1180
+ if (!isObject(entry))
1181
+ continue;
1182
+ const fn = isObject(entry.function) ? entry.function : undefined;
1183
+ const name = firstString(fn?.name, entry.name);
1184
+ const id = firstString(entry.id, entry.call_id);
1185
+ const args = fn !== undefined ? fn.arguments : entry.arguments;
1186
+ calls.push({
1187
+ args: typeof args === "string" && args ? args : jsonString(args),
1188
+ ...id ? { id } : {},
1189
+ ...name ? { name } : {}
1190
+ });
1191
+ }
1192
+ return calls;
1193
+ }
1194
+ function contentText(content) {
1195
+ if (typeof content === "string") {
1196
+ if (content.startsWith(CONTENT_JSON_PREFIX)) {
1197
+ const encoded = content.slice(CONTENT_JSON_PREFIX.length);
1198
+ try {
1199
+ return contentText(JSON.parse(encoded));
1200
+ } catch {
1201
+ return encoded;
1202
+ }
1203
+ }
1204
+ return content;
1205
+ }
1206
+ if (Array.isArray(content))
1207
+ return blocksText(content);
1208
+ if (content === null || content === undefined)
1209
+ return "";
1210
+ if (isObject(content))
1211
+ return jsonString(content);
1212
+ return String(content);
1213
+ }
1214
+ function reasoningText(row) {
1215
+ if (typeof row.reasoning_content === "string" && row.reasoning_content.trim()) {
1216
+ return row.reasoning_content;
1217
+ }
1218
+ if (typeof row.reasoning === "string" && row.reasoning.trim()) {
1219
+ return row.reasoning;
1220
+ }
1221
+ return "";
1222
+ }
1223
+ function hermesTimestamp(value) {
1224
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
1225
+ const milliseconds = value > 100000000000 ? value : value * 1000;
1226
+ const date = new Date(Math.round(milliseconds));
1227
+ return Number.isNaN(date.getTime()) ? undefined : date;
1228
+ }
1229
+ return parseTimestamp(value);
1230
+ }
1231
+ function rowId(row) {
1232
+ if (typeof row.id === "number" && Number.isFinite(row.id))
1233
+ return row.id;
1234
+ if (typeof row.id === "string" && row.id)
1235
+ return row.id;
1236
+ return;
1237
+ }
1238
+ function resolveGroupId(session, messages) {
1239
+ if (typeof session.id === "string" && session.id)
1240
+ return session.id;
1241
+ for (const row of messages) {
1242
+ if (typeof row.session_id === "string" && row.session_id) {
1243
+ return row.session_id;
1244
+ }
1245
+ }
1246
+ return;
1247
+ }
1248
+ function firstString(...values) {
1249
+ for (const value of values) {
1250
+ if (typeof value === "string" && value)
1251
+ return value;
1252
+ }
1253
+ return;
1254
+ }
1255
+ function invalidHermesTranscript() {
1256
+ return new NormalizationError("invalid_input", "Hermes transcript must be a JSON array of session-store message rows or an object with a messages array.");
1257
+ }
1258
+
1259
+ // src/adapters/letta-code/index.ts
1260
+ var SUPPORTED_KINDS = new Set([
1261
+ "user",
1262
+ "assistant",
1263
+ "reasoning",
1264
+ "tool_call",
1265
+ "error"
1266
+ ]);
1267
+ var lettaCodeAdapter = {
1268
+ source: "letta-code",
1269
+ decode(transcript) {
1270
+ const diagnostics = [];
1271
+ const rows = parseJsonLines(transcript, diagnostics);
1272
+ const events = [];
1273
+ const reasoningRecordIds = new Set;
1274
+ let recognizedRows = 0;
1275
+ for (const { value: row } of rows) {
1276
+ if (row.kind !== "reasoning")
1277
+ continue;
1278
+ const sourceRecordId = nonemptyString3(row.source_message_id) ?? nonemptyString3(row.source_line_id);
1279
+ if (sourceRecordId)
1280
+ reasoningRecordIds.add(sourceRecordId);
1281
+ }
1282
+ for (const { value: row, line } of rows) {
1283
+ if (typeof row.kind !== "string" || !SUPPORTED_KINDS.has(row.kind)) {
1284
+ diagnostics.push({
1285
+ code: "noise_record_dropped",
1286
+ message: `Skipped unsupported Letta Code transcript row on line ${line}.`,
1287
+ inputLine: line
1288
+ });
1289
+ continue;
1290
+ }
1291
+ recognizedRows += 1;
1292
+ if (row.kind === "error") {
1293
+ diagnostics.push({
1294
+ code: "noise_record_dropped",
1295
+ message: `Skipped Letta Code runtime error row on line ${line}.`,
1296
+ inputLine: line
1297
+ });
1298
+ continue;
1299
+ }
1300
+ const timestamp = parseTimestamp(row.captured_at);
1301
+ const sourceMessageId = nonemptyString3(row.source_message_id);
1302
+ const sourceLineId = nonemptyString3(row.source_line_id);
1303
+ const sourceRecordId = sourceMessageId ?? sourceLineId;
1304
+ const sourceFields = sourceRecordId ? { sourceRecordId } : {
1305
+ sourceOffset: line - 1,
1306
+ sourceAnchorKind: "ordinal"
1307
+ };
1308
+ if (row.kind === "user" || row.kind === "assistant" || row.kind === "reasoning") {
1309
+ if (typeof row.text !== "string" || row.text.length === 0) {
1310
+ diagnostics.push({
1311
+ code: "noise_record_dropped",
1312
+ message: `Skipped empty Letta Code ${row.kind} row on line ${line}.`,
1313
+ inputLine: line
1314
+ });
1315
+ continue;
1316
+ }
1317
+ const componentIndex = row.kind === "assistant" && sourceRecordId !== undefined && reasoningRecordIds.has(sourceRecordId) ? 1 : 0;
1318
+ if (row.kind === "reasoning") {
1319
+ events.push({
1320
+ type: "reasoning",
1321
+ content: row.text,
1322
+ inputLine: line,
1323
+ ...sourceFields,
1324
+ componentIndex,
1325
+ ...timestamp ? { timestamp } : {}
1326
+ });
1327
+ } else {
1328
+ events.push({
1329
+ type: "message",
1330
+ role: row.kind,
1331
+ content: row.text,
1332
+ inputLine: line,
1333
+ ...sourceFields,
1334
+ componentIndex,
1335
+ ...timestamp ? { timestamp } : {}
1336
+ });
1337
+ }
1338
+ continue;
1339
+ }
1340
+ const callId = sourceLineId ?? sourceMessageId ?? `letta-code-tool-line-${line}`;
1341
+ const name = nonemptyString3(row.name);
1342
+ events.push({
1343
+ type: "tool_call",
1344
+ args: nonemptyString3(row.argsText) ?? "{}",
1345
+ inputLine: line,
1346
+ ...sourceFields,
1347
+ componentIndex: 0,
1348
+ id: callId,
1349
+ ...name ? { name } : {},
1350
+ ...timestamp ? { timestamp } : {}
1351
+ });
1352
+ if (typeof row.resultText === "string" || typeof row.resultOk === "boolean") {
1353
+ let content = typeof row.resultText === "string" ? row.resultText : "";
1354
+ if (row.resultOk === false && !/^error/i.test(content)) {
1355
+ content = `Error: ${content}`;
1356
+ }
1357
+ events.push({
1358
+ type: "tool_result",
1359
+ content,
1360
+ ...typeof row.resultOk === "boolean" ? { ok: row.resultOk } : {},
1361
+ inputLine: line,
1362
+ ...sourceFields,
1363
+ componentIndex: 1,
1364
+ callId,
1365
+ ...timestamp ? { timestamp } : {}
1366
+ });
1367
+ }
1368
+ }
1369
+ if (recognizedRows === 0) {
1370
+ throw invalidLettaCodeTranscript();
1371
+ }
1372
+ return {
1373
+ events,
1374
+ context: { source: "letta-code" },
1375
+ diagnostics
1376
+ };
1377
+ }
1378
+ };
1379
+ function nonemptyString3(value) {
1380
+ return typeof value === "string" && value.length > 0 ? value : undefined;
1381
+ }
1382
+ function invalidLettaCodeTranscript() {
1383
+ return new NormalizationError("invalid_input", "Letta Code transcript must be client-side transcript.jsonl with kind-tagged rows.");
1384
+ }
1385
+
1386
+ // src/adapters/pi-session-shared.ts
1387
+ function decodePiSessionTranscript(transcript, options) {
1388
+ const diagnostics = [];
1389
+ const events = [];
1390
+ const excludedModels = options.excludedModels ?? [];
1391
+ let cwd;
1392
+ let createdAt;
1393
+ let sessionId;
1394
+ let sawMessageRow = false;
1395
+ for (const { value: row, line, byteOffset } of parseJsonLines(transcript, diagnostics)) {
1396
+ if (row.type === "session") {
1397
+ if (!cwd && typeof row.cwd === "string" && row.cwd)
1398
+ cwd = row.cwd;
1399
+ createdAt ??= parseTimestamp(row.timestamp);
1400
+ if (!sessionId && typeof row.id === "string" && row.id) {
1401
+ sessionId = row.id;
1402
+ }
1403
+ continue;
1404
+ }
1405
+ if (row.type !== "message" || !isObject(row.message))
1406
+ continue;
1407
+ sawMessageRow = true;
1408
+ const message = row.message;
1409
+ const timestamp = parseTimestamp(row.timestamp) ?? messageTimestamp(message.timestamp);
1410
+ const id = typeof row.id === "string" && row.id ? row.id : undefined;
1411
+ const model = typeof message.model === "string" && message.model && !excludedModels.includes(message.model) ? message.model : undefined;
1412
+ let componentIndex = 0;
1413
+ const emit = (event) => {
1414
+ events.push({
1415
+ ...event,
1416
+ ...id !== undefined ? { sourceRecordId: id } : { sourceOffset: byteOffset, sourceAnchorKind: "byte" },
1417
+ componentIndex: componentIndex++
1418
+ });
1419
+ };
1420
+ if (message.role === "user") {
1421
+ const content = blocksText(message.content);
1422
+ if (content) {
1423
+ emit({
1424
+ type: "message",
1425
+ role: "user",
1426
+ content,
1427
+ inputLine: line,
1428
+ ...timestamp ? { timestamp } : {}
1429
+ });
1430
+ }
1431
+ continue;
1432
+ }
1433
+ if (message.role === "assistant") {
1434
+ if (typeof message.content === "string") {
1435
+ if (message.content) {
1436
+ emit({
1437
+ type: "message",
1438
+ role: "assistant",
1439
+ content: message.content,
1440
+ inputLine: line,
1441
+ ...timestamp ? { timestamp } : {},
1442
+ ...model ? { model } : {}
1443
+ });
1444
+ }
1445
+ continue;
1446
+ }
1447
+ for (const part of Array.isArray(message.content) ? message.content : []) {
1448
+ if (!isObject(part))
1449
+ continue;
1450
+ if (part.type === "thinking" && typeof part.thinking === "string") {
1451
+ emit({
1452
+ type: "reasoning",
1453
+ content: part.thinking,
1454
+ inputLine: line,
1455
+ ...timestamp ? { timestamp } : {},
1456
+ ...model ? { model } : {}
1457
+ });
1458
+ } else if (part.type === "text" && typeof part.text === "string") {
1459
+ emit({
1460
+ type: "message",
1461
+ role: "assistant",
1462
+ content: part.text,
1463
+ inputLine: line,
1464
+ ...timestamp ? { timestamp } : {},
1465
+ ...model ? { model } : {}
1466
+ });
1467
+ } else if (part.type === "toolCall") {
1468
+ emit({
1469
+ type: "tool_call",
1470
+ args: toolArguments(part.arguments),
1471
+ inputLine: line,
1472
+ ...typeof part.id === "string" && part.id ? { id: part.id } : {},
1473
+ ...typeof part.name === "string" && part.name ? { name: part.name } : {},
1474
+ ...timestamp ? { timestamp } : {},
1475
+ ...model ? { model } : {}
1476
+ });
1477
+ }
1478
+ }
1479
+ continue;
1480
+ }
1481
+ if (message.role === "toolResult" || message.role === "tool") {
1482
+ let content = blocksText(message.content);
1483
+ if (message.isError === true && !/^error/i.test(content)) {
1484
+ content = `Error: ${content}`;
1485
+ }
1486
+ emit({
1487
+ type: "tool_result",
1488
+ content,
1489
+ ...typeof message.isError === "boolean" ? { ok: !message.isError } : {},
1490
+ inputLine: line,
1491
+ ...typeof message.toolCallId === "string" && message.toolCallId ? { callId: message.toolCallId } : {},
1492
+ ...timestamp ? { timestamp } : {}
1493
+ });
1494
+ }
1495
+ }
1496
+ if (!sawMessageRow && sessionId === undefined) {
1497
+ throw new NormalizationError("invalid_input", `${options.sourceLabel} transcript must be session JSONL containing a session header or message entries.`);
1498
+ }
1499
+ return {
1500
+ events,
1501
+ context: {
1502
+ source: options.source,
1503
+ ...cwd ? { cwd } : {},
1504
+ ...createdAt ? { createdAt } : {},
1505
+ ...sessionId ? { sourceGroupId: sessionId } : {}
1506
+ },
1507
+ diagnostics
1508
+ };
1509
+ }
1510
+ function messageTimestamp(value) {
1511
+ return parseTimestamp(value);
1512
+ }
1513
+ function toolArguments(value) {
1514
+ if (typeof value === "string" && value)
1515
+ return value;
1516
+ return jsonString(value);
1517
+ }
1518
+
1519
+ // src/adapters/openclaw/index.ts
1520
+ var DELIVERY_MIRROR_MODEL = "delivery-mirror";
1521
+ var openClawAdapter = {
1522
+ source: "openclaw",
1523
+ decode(transcript) {
1524
+ return decodePiSessionTranscript(transcript, {
1525
+ source: "openclaw",
1526
+ sourceLabel: "OpenClaw",
1527
+ excludedModels: [DELIVERY_MIRROR_MODEL]
1528
+ });
1529
+ }
1530
+ };
1531
+
1532
+ // src/adapters/openhands/index.ts
1533
+ var openHandsAdapter = {
1534
+ source: "openhands",
1535
+ decode(transcript) {
1536
+ const diagnostics = [];
1537
+ const events = [];
1538
+ const rawEvents = parseEvents(transcript);
1539
+ const callIdByActionId = new Map;
1540
+ for (const event of rawEvents) {
1541
+ if (isObject(event) && event.kind === "ActionEvent" && typeof event.id === "string" && event.id) {
1542
+ callIdByActionId.set(event.id, actionCallId(event));
1543
+ }
1544
+ }
1545
+ for (const event of rawEvents) {
1546
+ if (!isObject(event) || typeof event.id !== "string" || !event.id) {
1547
+ continue;
1548
+ }
1549
+ const timestamp = parseTimestamp(event.timestamp);
1550
+ const sourceRecordId = event.id;
1551
+ let componentIndex = 0;
1552
+ const emit = (decoded) => {
1553
+ events.push({ ...decoded, sourceRecordId, componentIndex: componentIndex++ });
1554
+ };
1555
+ if (event.kind === "MessageEvent") {
1556
+ if (event.source !== "user" && event.source !== "agent")
1557
+ continue;
1558
+ const message = isObject(event.llm_message) ? event.llm_message : {};
1559
+ const content = joinTextContent(message.content);
1560
+ if (!content)
1561
+ continue;
1562
+ emit({
1563
+ type: "message",
1564
+ role: event.source === "user" ? "user" : "assistant",
1565
+ content,
1566
+ ...timestamp ? { timestamp } : {}
1567
+ });
1568
+ continue;
1569
+ }
1570
+ if (event.kind === "ActionEvent") {
1571
+ const thought = joinTextContent(event.thought);
1572
+ if (thought) {
1573
+ emit({
1574
+ type: "reasoning",
1575
+ content: thought,
1576
+ ...timestamp ? { timestamp } : {}
1577
+ });
1578
+ }
1579
+ const callId2 = callIdByActionId.get(event.id) ?? actionCallId(event);
1580
+ emit({
1581
+ type: "tool_call",
1582
+ id: callId2,
1583
+ args: actionArgsText(event),
1584
+ ...typeof event.tool_name === "string" && event.tool_name ? { name: event.tool_name } : {},
1585
+ ...timestamp ? { timestamp } : {}
1586
+ });
1587
+ continue;
1588
+ }
1589
+ const result = extractToolResultText(event);
1590
+ if (result === undefined)
1591
+ continue;
1592
+ const callId = typeof event.tool_call_id === "string" && event.tool_call_id ? event.tool_call_id : typeof event.action_id === "string" ? callIdByActionId.get(event.action_id) : undefined;
1593
+ emit({
1594
+ type: "tool_result",
1595
+ content: result,
1596
+ ...toolResultStatus(event),
1597
+ ...callId ? { callId } : {},
1598
+ ...timestamp ? { timestamp } : {}
1599
+ });
1600
+ }
1601
+ return {
1602
+ events,
1603
+ context: { source: "openhands" },
1604
+ diagnostics
1605
+ };
1606
+ }
1607
+ };
1608
+ function actionCallId(event) {
1609
+ return typeof event.tool_call_id === "string" && event.tool_call_id ? event.tool_call_id : `oh_${String(event.id)}`;
1610
+ }
1611
+ function parseEvents(transcript) {
1612
+ let parsed;
1613
+ try {
1614
+ parsed = JSON.parse(transcript);
1615
+ } catch {
1616
+ throw new NormalizationError("invalid_input", "OpenHands transcript must be a JSON event array or an object with an items array.");
1617
+ }
1618
+ if (Array.isArray(parsed))
1619
+ return parsed;
1620
+ if (isObject(parsed) && Array.isArray(parsed.items))
1621
+ return parsed.items;
1622
+ throw new NormalizationError("invalid_input", "OpenHands transcript must be a JSON event array or an object with an items array.");
1623
+ }
1624
+ function joinTextContent(content) {
1625
+ if (!Array.isArray(content))
1626
+ return "";
1627
+ const parts = [];
1628
+ for (const item of content) {
1629
+ if (isObject(item) && item.type === "text" && typeof item.text === "string") {
1630
+ parts.push(item.text);
1631
+ }
1632
+ }
1633
+ return parts.join("");
1634
+ }
1635
+ function actionArgsText(event) {
1636
+ if (isObject(event.tool_call)) {
1637
+ const raw = event.tool_call.arguments;
1638
+ if (typeof raw === "string" && raw)
1639
+ return raw;
1640
+ }
1641
+ if (isObject(event.action)) {
1642
+ const args = { ...event.action };
1643
+ delete args.kind;
1644
+ return jsonString(args);
1645
+ }
1646
+ return "{}";
1647
+ }
1648
+ function toolResultStatus(event) {
1649
+ if (event.kind !== "ObservationEvent" || !isObject(event.observation))
1650
+ return {};
1651
+ return typeof event.observation.is_error === "boolean" ? { ok: !event.observation.is_error } : {};
1652
+ }
1653
+ function extractToolResultText(event) {
1654
+ if (event.kind === "ObservationEvent") {
1655
+ const observation = isObject(event.observation) ? event.observation : {};
1656
+ return joinTextContent(observation.content);
1657
+ }
1658
+ if (event.kind === "AgentErrorEvent") {
1659
+ return typeof event.error === "string" ? event.error : "";
1660
+ }
1661
+ if (event.kind === "UserRejectObservation") {
1662
+ return typeof event.rejection_reason === "string" ? event.rejection_reason : "";
1663
+ }
1664
+ return;
1665
+ }
1666
+
1667
+ // src/adapters/opencode/index.ts
1668
+ var TRANSPORT_PART_TYPES = new Set([
1669
+ "file",
1670
+ "patch",
1671
+ "snapshot",
1672
+ "step-finish",
1673
+ "step-start",
1674
+ "subtask"
1675
+ ]);
1676
+ var openCodeAdapter = {
1677
+ source: "opencode",
1678
+ decode(transcript) {
1679
+ const document = parseOpenCodeDocument(transcript);
1680
+ const diagnostics = [];
1681
+ const events = [];
1682
+ const sessionInfo = isObject(document.info) ? document.info : {};
1683
+ let partOrdinal = 0;
1684
+ for (let messageIndex = 0;messageIndex < document.messages.length; messageIndex += 1) {
1685
+ const message = document.messages[messageIndex];
1686
+ if (!isObject(message))
1687
+ continue;
1688
+ const info = isObject(message.info) ? message.info : {};
1689
+ const role = info.role;
1690
+ const messageId = nonemptyString4(info.id);
1691
+ const timestamp = parseTimestamp(isObject(info.time) ? info.time.created : undefined);
1692
+ const model = nonemptyString4(info.modelID);
1693
+ const parts = Array.isArray(message.parts) ? message.parts : [];
1694
+ let messageComponentIndex = 0;
1695
+ let latestTimestamp;
1696
+ const orderedTimestamp = (candidate) => {
1697
+ if (!candidate)
1698
+ return latestTimestamp;
1699
+ if (latestTimestamp && candidate.getTime() < latestTimestamp.getTime()) {
1700
+ return latestTimestamp;
1701
+ }
1702
+ latestTimestamp = candidate;
1703
+ return candidate;
1704
+ };
1705
+ for (const part of parts) {
1706
+ const ordinal = partOrdinal++;
1707
+ if (!isObject(part))
1708
+ continue;
1709
+ const partId = nonemptyString4(part.id);
1710
+ const sourceRecordId = partId ?? messageId;
1711
+ let partComponentIndex = 0;
1712
+ const emit = (event) => {
1713
+ const componentIndex = partId ? partComponentIndex++ : messageId ? messageComponentIndex++ : partComponentIndex++;
1714
+ events.push({
1715
+ ...event,
1716
+ ...sourceRecordId ? { sourceRecordId } : { sourceOffset: ordinal, sourceAnchorKind: "ordinal" },
1717
+ sourceSequence: ordinal,
1718
+ componentIndex
1719
+ });
1720
+ };
1721
+ if (part.type === "text") {
1722
+ if (role !== "user" && role !== "assistant")
1723
+ continue;
1724
+ const partTime = isObject(part.time) ? part.time : {};
1725
+ const eventTimestamp = orderedTimestamp(parseTimestamp(partTime.start) ?? timestamp);
1726
+ emit({
1727
+ type: "message",
1728
+ role,
1729
+ content: stringContent2(part.text),
1730
+ ...eventTimestamp ? { timestamp: eventTimestamp } : {},
1731
+ ...model ? { model } : {}
1732
+ });
1733
+ continue;
1734
+ }
1735
+ if (part.type === "reasoning") {
1736
+ const partTime = isObject(part.time) ? part.time : {};
1737
+ const eventTimestamp = orderedTimestamp(parseTimestamp(partTime.start) ?? timestamp);
1738
+ emit({
1739
+ type: "reasoning",
1740
+ content: stringContent2(part.text),
1741
+ ...eventTimestamp ? { timestamp: eventTimestamp } : {},
1742
+ ...model ? { model } : {}
1743
+ });
1744
+ continue;
1745
+ }
1746
+ if (part.type === "tool") {
1747
+ const state = isObject(part.state) ? part.state : {};
1748
+ const stateTime = isObject(state.time) ? state.time : {};
1749
+ const callTimestamp = orderedTimestamp(parseTimestamp(stateTime.start) ?? timestamp);
1750
+ const resultTimestamp = orderedTimestamp(parseTimestamp(stateTime.end) ?? callTimestamp);
1751
+ const callId = nonemptyString4(part.callID);
1752
+ const name = nonemptyString4(part.tool);
1753
+ emit({
1754
+ type: "tool_call",
1755
+ args: jsonString(state.input),
1756
+ ...callId ? { id: callId } : {},
1757
+ ...name ? { name } : {},
1758
+ ...callTimestamp ? { timestamp: callTimestamp } : {},
1759
+ ...model ? { model } : {}
1760
+ });
1761
+ const status = nonemptyString4(state.status);
1762
+ const output = state.output !== undefined ? stringContent2(state.output) : status === "error" ? errorContent(state.error) : undefined;
1763
+ if (output !== undefined) {
1764
+ emit({
1765
+ type: "tool_result",
1766
+ content: output,
1767
+ ...callId ? { callId } : {},
1768
+ ...status === "completed" ? { ok: true } : status === "error" ? { ok: false } : {},
1769
+ ...resultTimestamp ? { timestamp: resultTimestamp } : {},
1770
+ ...model ? { model } : {}
1771
+ });
1772
+ }
1773
+ continue;
1774
+ }
1775
+ if (typeof part.type === "string" && !TRANSPORT_PART_TYPES.has(part.type)) {
1776
+ diagnostics.push({
1777
+ code: "noise_record_dropped",
1778
+ message: `Skipped unsupported OpenCode part type ${JSON.stringify(part.type)} ` + `in message ${messageIndex + 1}.`
1779
+ });
1780
+ }
1781
+ }
1782
+ }
1783
+ const cwd = nonemptyString4(sessionInfo.directory);
1784
+ const sourceGroupId = nonemptyString4(sessionInfo.id);
1785
+ const createdAt = parseTimestamp(isObject(sessionInfo.time) ? sessionInfo.time.created : undefined);
1786
+ return {
1787
+ events,
1788
+ context: {
1789
+ source: "opencode",
1790
+ ...cwd ? { cwd } : {},
1791
+ ...sourceGroupId ? { sourceGroupId } : {},
1792
+ ...createdAt ? { createdAt } : {}
1793
+ },
1794
+ diagnostics
1795
+ };
1796
+ }
1797
+ };
1798
+ function parseOpenCodeDocument(transcript) {
1799
+ let parsed;
1800
+ try {
1801
+ parsed = JSON.parse(transcript);
1802
+ } catch {
1803
+ throw invalidOpenCodeTranscript();
1804
+ }
1805
+ if (!isObject(parsed) || !isObject(parsed.info) || !Array.isArray(parsed.messages)) {
1806
+ throw invalidOpenCodeTranscript();
1807
+ }
1808
+ return parsed;
1809
+ }
1810
+ function nonemptyString4(value) {
1811
+ return typeof value === "string" && value.length > 0 ? value : undefined;
1812
+ }
1813
+ function stringContent2(value) {
1814
+ if (typeof value === "string")
1815
+ return value;
1816
+ if (value === null || value === undefined)
1817
+ return "";
1818
+ return isObject(value) || Array.isArray(value) ? jsonString(value) : String(value);
1819
+ }
1820
+ function errorContent(value) {
1821
+ if (isObject(value) && typeof value.message === "string")
1822
+ return value.message;
1823
+ return stringContent2(value);
1824
+ }
1825
+ function invalidOpenCodeTranscript() {
1826
+ return new NormalizationError("invalid_input", "OpenCode transcript must be one JSON document with info and messages arrays of message parts.");
1827
+ }
1828
+
1829
+ // src/adapters/omp/index.ts
1830
+ var ompAdapter = {
1831
+ source: "omp",
1832
+ decode(transcript) {
1833
+ return decodePiSessionTranscript(transcript, {
1834
+ source: "omp",
1835
+ sourceLabel: "omp"
1836
+ });
1837
+ }
1838
+ };
1839
+
1840
+ // src/adapters/pi/index.ts
1841
+ var piAdapter = {
1842
+ source: "pi",
1843
+ decode(transcript) {
1844
+ return decodePiSessionTranscript(transcript, {
1845
+ source: "pi",
1846
+ sourceLabel: "pi"
1847
+ });
1848
+ }
1849
+ };
1850
+
1851
+ // src/bounds.ts
1852
+ var DEFAULT_NORMALIZATION_BOUNDS = Object.freeze({
1853
+ toolArguments: Object.freeze({ maxCharacters: 20000 }),
1854
+ toolResults: Object.freeze({
1855
+ maxCharacters: 2500,
1856
+ strategy: "head-tail"
1857
+ })
1858
+ });
1859
+ function resolveBounds(bounds) {
1860
+ if (bounds === undefined)
1861
+ return copyDefaults();
1862
+ assertObject(bounds, "bounds");
1863
+ assertKnownKeys(bounds, ["toolArguments", "toolResults"], "bounds");
1864
+ const toolArguments2 = bounds.toolArguments;
1865
+ if (toolArguments2 !== undefined) {
1866
+ assertObject(toolArguments2, "bounds.toolArguments");
1867
+ assertKnownKeys(toolArguments2, ["maxCharacters"], "bounds.toolArguments");
1868
+ }
1869
+ const toolResults = bounds.toolResults;
1870
+ if (toolResults !== undefined) {
1871
+ assertObject(toolResults, "bounds.toolResults");
1872
+ assertKnownKeys(toolResults, ["maxCharacters", "strategy"], "bounds.toolResults");
1873
+ }
1874
+ const argumentLimit = resolveLimit(toolArguments2?.maxCharacters, DEFAULT_NORMALIZATION_BOUNDS.toolArguments.maxCharacters, "bounds.toolArguments.maxCharacters");
1875
+ if (argumentLimit !== null && argumentLimit < 2) {
1876
+ throw invalidBounds("bounds.toolArguments.maxCharacters must be at least 2 so arguments can remain a JSON object.");
1877
+ }
1878
+ const resultLimit = resolveLimit(toolResults?.maxCharacters, DEFAULT_NORMALIZATION_BOUNDS.toolResults.maxCharacters, "bounds.toolResults.maxCharacters");
1879
+ const strategy = toolResults?.strategy ?? DEFAULT_NORMALIZATION_BOUNDS.toolResults.strategy;
1880
+ if (strategy !== "head" && strategy !== "head-tail") {
1881
+ throw invalidBounds('bounds.toolResults.strategy must be either "head" or "head-tail".');
1882
+ }
1883
+ return {
1884
+ toolArguments: { maxCharacters: argumentLimit },
1885
+ toolResults: { maxCharacters: resultLimit, strategy }
1886
+ };
1887
+ }
1888
+ function copyDefaults() {
1889
+ return {
1890
+ toolArguments: {
1891
+ maxCharacters: DEFAULT_NORMALIZATION_BOUNDS.toolArguments.maxCharacters
1892
+ },
1893
+ toolResults: {
1894
+ maxCharacters: DEFAULT_NORMALIZATION_BOUNDS.toolResults.maxCharacters,
1895
+ strategy: DEFAULT_NORMALIZATION_BOUNDS.toolResults.strategy
1896
+ }
1897
+ };
1898
+ }
1899
+ function resolveLimit(value, fallback, path) {
1900
+ if (value === undefined)
1901
+ return fallback;
1902
+ if (value === null)
1903
+ return null;
1904
+ if (!Number.isSafeInteger(value) || value <= 0) {
1905
+ throw invalidBounds(`${path} must be a positive safe integer or null.`);
1906
+ }
1907
+ return value;
1908
+ }
1909
+ function assertObject(value, path) {
1910
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1911
+ throw invalidBounds(`${path} must be an object.`);
1912
+ }
1913
+ }
1914
+ function assertKnownKeys(value, knownKeys, path) {
1915
+ const unknown = Object.keys(value).find((key) => !knownKeys.includes(key));
1916
+ if (unknown !== undefined) {
1917
+ throw invalidBounds(`${path} contains unknown option ${JSON.stringify(unknown)}.`);
1918
+ }
1919
+ }
1920
+ function invalidBounds(message) {
1921
+ return new NormalizationError("invalid_input", message);
1922
+ }
1923
+
1924
+ // src/filters.ts
1925
+ var DEFAULT_NORMALIZATION_FILTERS = Object.freeze({ toolResults: "include" });
1926
+ function resolveFilters(filters) {
1927
+ if (filters === undefined)
1928
+ return { ...DEFAULT_NORMALIZATION_FILTERS };
1929
+ assertObject2(filters, "filters");
1930
+ const unknown = Object.keys(filters).find((key) => key !== "toolResults");
1931
+ if (unknown !== undefined) {
1932
+ throw invalidFilters(`filters contains unknown option ${JSON.stringify(unknown)}.`);
1933
+ }
1934
+ const toolResults = filters.toolResults ?? DEFAULT_NORMALIZATION_FILTERS.toolResults;
1935
+ if (toolResults !== "include" && toolResults !== "omit") {
1936
+ throw invalidFilters('filters.toolResults must be either "include" or "omit".');
1937
+ }
1938
+ return { toolResults };
1939
+ }
1940
+ function assertObject2(value, path) {
1941
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1942
+ throw invalidFilters(`${path} must be an object.`);
1943
+ }
1944
+ }
1945
+ function invalidFilters(message) {
1946
+ return new NormalizationError("invalid_input", message);
1947
+ }
1948
+
1949
+ // src/validate.ts
1950
+ var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/;
1951
+ var META_KEYS = new Set(["role", "source", "cwd", "git_branch", "model"]);
1952
+ var CONTENT_KEYS = new Set(["role", "content", "timestamp"]);
1953
+ var ASSISTANT_TOOL_KEYS = new Set(["role", "content", "timestamp", "tool_calls"]);
1954
+ var TOOL_RESULT_KEYS = new Set(["role", "tool_call_id", "content", "ok", "timestamp"]);
1955
+ var TOOL_CALL_KEYS = new Set(["id", "name", "args"]);
1956
+ function validateTranscript(value, options) {
1957
+ const partial = options?.partial ?? false;
1958
+ if (!Array.isArray(value) || value.length === 0)
1959
+ fail("Transcript must be a non-empty array.");
1960
+ const allCallIds = collectCallIds(value);
1961
+ const callIds = new Set;
1962
+ const roles = new Set;
1963
+ let metaSeen = false;
1964
+ for (let index = 0;index < value.length; index += 1) {
1965
+ const record = value[index];
1966
+ if (!isObject2(record) || typeof record.role !== "string") {
1967
+ fail(`Record ${index} must be an object with a role.`);
1968
+ }
1969
+ roles.add(record.role);
1970
+ if (record.role === "meta") {
1971
+ if (index !== 0 || metaSeen)
1972
+ fail(`Record ${index}: meta must appear once at index 0.`);
1973
+ metaSeen = true;
1974
+ exactKeys(record, META_KEYS, index);
1975
+ if (typeof record.source !== "string" || !record.source) {
1976
+ fail(`Record ${index}: meta.source must be a non-empty string.`);
1977
+ }
1978
+ optionalString(record, "cwd", index);
1979
+ optionalString(record, "git_branch", index);
1980
+ optionalString(record, "model", index);
1981
+ continue;
1982
+ }
1983
+ validateTimestamp(record.timestamp, index);
1984
+ if (record.role === "user" || record.role === "reasoning") {
1985
+ exactKeys(record, CONTENT_KEYS, index);
1986
+ if (typeof record.content !== "string") {
1987
+ fail(`Record ${index}: ${record.role} content must be a string.`);
1988
+ }
1989
+ continue;
1990
+ }
1991
+ if (record.role === "assistant") {
1992
+ if ("tool_calls" in record) {
1993
+ exactKeys(record, ASSISTANT_TOOL_KEYS, index);
1994
+ if (record.content !== null) {
1995
+ fail(`Record ${index}: assistant tool-call content must be null.`);
1996
+ }
1997
+ if (!Array.isArray(record.tool_calls) || record.tool_calls.length === 0) {
1998
+ fail(`Record ${index}: assistant tool_calls must be a non-empty array.`);
1999
+ }
2000
+ for (const call of record.tool_calls)
2001
+ validateToolCall(call, index, callIds);
2002
+ } else {
2003
+ exactKeys(record, CONTENT_KEYS, index);
2004
+ if (typeof record.content !== "string" || !record.content) {
2005
+ fail(`Record ${index}: assistant content must be a non-empty string.`);
2006
+ }
2007
+ }
2008
+ continue;
2009
+ }
2010
+ if (record.role === "tool") {
2011
+ exactKeys(record, TOOL_RESULT_KEYS, index);
2012
+ if (typeof record.tool_call_id !== "string" || !record.tool_call_id || !partial && !allCallIds.has(record.tool_call_id)) {
2013
+ fail(`Record ${index}: tool result must reference a tool call.`);
2014
+ }
2015
+ if (typeof record.content !== "string") {
2016
+ fail(`Record ${index}: tool content must be a string.`);
2017
+ }
2018
+ if ("ok" in record && typeof record.ok !== "boolean") {
2019
+ fail(`Record ${index}: tool ok must be boolean when present.`);
2020
+ }
2021
+ continue;
2022
+ }
2023
+ fail(`Record ${index}: unknown role ${JSON.stringify(record.role)}.`);
2024
+ }
2025
+ if (!partial) {
2026
+ if (!roles.has("user"))
2027
+ fail("Transcript must contain at least one user record.");
2028
+ if (!roles.has("assistant")) {
2029
+ fail("Transcript must contain at least one assistant record.");
2030
+ }
2031
+ }
2032
+ }
2033
+ function collectCallIds(records) {
2034
+ const ids = new Set;
2035
+ for (const record of records) {
2036
+ if (!isObject2(record) || record.role !== "assistant")
2037
+ continue;
2038
+ if (!Array.isArray(record.tool_calls))
2039
+ continue;
2040
+ for (const call of record.tool_calls) {
2041
+ if (isObject2(call) && typeof call.id === "string" && call.id) {
2042
+ ids.add(call.id);
2043
+ }
2044
+ }
2045
+ }
2046
+ return ids;
2047
+ }
2048
+ function validateToolCall(call, recordIndex, callIds) {
2049
+ if (!isObject2(call))
2050
+ fail(`Record ${recordIndex}: tool call must be an object.`);
2051
+ exactKeys(call, TOOL_CALL_KEYS, recordIndex, "tool call");
2052
+ if (typeof call.id !== "string" || !call.id) {
2053
+ fail(`Record ${recordIndex}: tool-call ID must be a non-empty string.`);
2054
+ }
2055
+ if (callIds.has(call.id))
2056
+ fail(`Record ${recordIndex}: duplicate tool-call ID ${call.id}.`);
2057
+ if (typeof call.name !== "string" || !call.name) {
2058
+ fail(`Record ${recordIndex}: tool-call name must be a non-empty string.`);
2059
+ }
2060
+ if (typeof call.args !== "string") {
2061
+ fail(`Record ${recordIndex}: tool-call args must be a string.`);
2062
+ }
2063
+ let args;
2064
+ try {
2065
+ args = JSON.parse(call.args);
2066
+ } catch {
2067
+ fail(`Record ${recordIndex}: tool-call args must contain valid JSON.`);
2068
+ }
2069
+ if (!isObject2(args)) {
2070
+ fail(`Record ${recordIndex}: tool-call args must encode a JSON object.`);
2071
+ }
2072
+ callIds.add(call.id);
2073
+ }
2074
+ function validateTimestamp(value, recordIndex) {
2075
+ if (typeof value !== "string" || !TIMESTAMP_PATTERN.test(value)) {
2076
+ fail(`Record ${recordIndex}: timestamp must be an ISO-8601 instant.`);
2077
+ }
2078
+ }
2079
+ function exactKeys(value, allowed, recordIndex, label = "record") {
2080
+ const extra = Object.keys(value).find((key) => !allowed.has(key));
2081
+ if (extra)
2082
+ fail(`Record ${recordIndex}: unexpected ${label} field ${JSON.stringify(extra)}.`);
2083
+ }
2084
+ function optionalString(value, key, recordIndex) {
2085
+ if (key in value && typeof value[key] !== "string") {
2086
+ fail(`Record ${recordIndex}: ${key} must be a string when present.`);
2087
+ }
2088
+ }
2089
+ function isObject2(value) {
2090
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2091
+ }
2092
+ function fail(message) {
2093
+ throw new NormalizationError("invalid_normalized_transcript", message);
2094
+ }
2095
+
2096
+ // src/core.ts
2097
+ var ARGS_LEAF_FLOOR = 2000;
2098
+ var SYNTH_BASE_MS = Date.UTC(2026, 0, 1);
2099
+ var SYNTH_STEP_SECONDS = 15;
2100
+ var NOISE_PREFIXES = [
2101
+ "<local-command-caveat>",
2102
+ "<command-name>",
2103
+ "<command-message>",
2104
+ "<local-command-stdout>",
2105
+ "<local-command-stderr>",
2106
+ "<task-notification"
2107
+ ];
2108
+ function semanticBucket(event) {
2109
+ switch (event.type) {
2110
+ case "message":
2111
+ return "message";
2112
+ case "reasoning":
2113
+ return "reasoning";
2114
+ case "tool_call":
2115
+ return "tool_call";
2116
+ case "tool_result":
2117
+ return "tool_result";
2118
+ }
2119
+ }
2120
+ function planEvents(events) {
2121
+ const calls = new Map;
2122
+ const openCalls = new Map;
2123
+ const usedIds = new Set;
2124
+ const occOf = [];
2125
+ const bucketOf = [];
2126
+ let occurrence = -1;
2127
+ for (let index = 0;index < events.length; index += 1) {
2128
+ const event = events[index];
2129
+ if (event === undefined) {
2130
+ occOf.push(occurrence);
2131
+ bucketOf.push("");
2132
+ continue;
2133
+ }
2134
+ if ((event.componentIndex ?? 0) === 0)
2135
+ occurrence += 1;
2136
+ const bucket = semanticBucket(event);
2137
+ occOf.push(occurrence);
2138
+ bucketOf.push(bucket);
2139
+ if (event.type === "tool_call") {
2140
+ const sourceId = event.id || `call_${index + 1}`;
2141
+ const synthesized = !event.id;
2142
+ let finalId = sourceId;
2143
+ let renamed = false;
2144
+ if (usedIds.has(finalId)) {
2145
+ let suffix = 2;
2146
+ while (usedIds.has(`${sourceId}__${suffix}`))
2147
+ suffix += 1;
2148
+ finalId = `${sourceId}__${suffix}`;
2149
+ renamed = true;
2150
+ }
2151
+ usedIds.add(finalId);
2152
+ const entries = openCalls.get(sourceId) ?? [];
2153
+ entries.push({ finalId, consumed: false });
2154
+ openCalls.set(sourceId, entries);
2155
+ calls.set(index, { finalId, renamed, synthesized, sourceId });
2156
+ }
2157
+ }
2158
+ const seen = new Map;
2159
+ const components = [];
2160
+ for (let index = 0;index < events.length; index += 1) {
2161
+ if (events[index] === undefined) {
2162
+ components.push(undefined);
2163
+ continue;
2164
+ }
2165
+ const key = `${occOf[index]}:${bucketOf[index]}`;
2166
+ const ordinal = seen.get(key) ?? 0;
2167
+ seen.set(key, ordinal + 1);
2168
+ components.push({ typeOrdinal: ordinal });
2169
+ }
2170
+ return { calls, openCalls, components };
2171
+ }
2172
+ function normalizeDecodedSession(decoded, bounds, options) {
2173
+ const internal = normalizeDecodedSessionInternal(decoded, bounds, options);
2174
+ return { records: internal.records, diagnostics: internal.diagnostics };
2175
+ }
2176
+ function normalizeDecodedSessionInternal(decoded, bounds, options) {
2177
+ const partial = options?.partial ?? false;
2178
+ const filters = options?.filters ?? DEFAULT_NORMALIZATION_FILTERS;
2179
+ const diagnostics = [...decoded.diagnostics];
2180
+ const body = [];
2181
+ const bodyBases = [];
2182
+ const anchors = new Map;
2183
+ const modelCounts = new Map;
2184
+ const plan = planEvents(decoded.events);
2185
+ for (let eventIndex = 0;eventIndex < decoded.events.length; eventIndex += 1) {
2186
+ const event = decoded.events[eventIndex];
2187
+ if (event === undefined)
2188
+ continue;
2189
+ if (event.model) {
2190
+ modelCounts.set(event.model, (modelCounts.get(event.model) ?? 0) + 1);
2191
+ }
2192
+ const record = normalizeEvent(event, eventIndex, body.length + 1, plan, diagnostics, bounds, filters, partial);
2193
+ if (!record)
2194
+ continue;
2195
+ const hasTimestamp = event.timestamp !== undefined && !Number.isNaN(event.timestamp.getTime());
2196
+ if (hasTimestamp && event.timestamp) {
2197
+ anchors.set(body.length, event.timestamp);
2198
+ }
2199
+ const component = plan.components[eventIndex] ?? { typeOrdinal: 0 };
2200
+ body.push(record);
2201
+ bodyBases.push({
2202
+ componentIndex: event.componentIndex ?? 0,
2203
+ componentTypeOrdinal: component.typeOrdinal,
2204
+ ...event.sourceRecordId !== undefined ? { sourceRecordId: event.sourceRecordId } : {},
2205
+ ...event.sourceSequence !== undefined ? { sourceSequence: event.sourceSequence } : {},
2206
+ ...event.sourceOffset !== undefined ? { sourceOffset: event.sourceOffset } : {},
2207
+ ...event.sourceAnchorKind !== undefined ? { sourceAnchorKind: event.sourceAnchorKind } : {},
2208
+ ...hasTimestamp && event.timestamp ? { sourceTimestamp: event.timestamp.toISOString() } : {}
2209
+ });
2210
+ }
2211
+ const roles = new Set(body.map((record) => record.role));
2212
+ if (!partial) {
2213
+ if (!roles.has("user")) {
2214
+ throw new NormalizationError("missing_user_records", "Transcript did not contain any normalizable user records.");
2215
+ }
2216
+ if (!roles.has("assistant")) {
2217
+ throw new NormalizationError("missing_assistant_records", "Transcript did not contain any normalizable assistant records.");
2218
+ }
2219
+ }
2220
+ const timestamps = fillTimestamps(body.length, anchors, decoded.context, diagnostics);
2221
+ const stampedBody = body.map((record, index) => {
2222
+ const timestamp = timestamps[index];
2223
+ if (timestamp === undefined) {
2224
+ throw new NormalizationError("invalid_normalized_transcript", `Could not assign a timestamp to normalized record ${index}.`);
2225
+ }
2226
+ return { ...record, timestamp };
2227
+ });
2228
+ const meta = buildMeta(decoded.context, modelCounts);
2229
+ const records = [meta, ...stampedBody];
2230
+ validateTranscript(records, { partial });
2231
+ const recordTimestamps = [
2232
+ null,
2233
+ ...stampedBody.map((record) => record.timestamp)
2234
+ ];
2235
+ const bases = [null, ...bodyBases];
2236
+ return {
2237
+ records,
2238
+ bases,
2239
+ recordTimestamps,
2240
+ context: decoded.context,
2241
+ diagnostics,
2242
+ bounds,
2243
+ filters
2244
+ };
2245
+ }
2246
+ function normalizeEvent(event, eventIndex, recordIndex, plan, diagnostics, bounds, filters, partial) {
2247
+ if (event.type === "message") {
2248
+ if (!event.content.trim()) {
2249
+ return;
2250
+ }
2251
+ if (event.role === "user" && NOISE_PREFIXES.some((prefix) => event.content.trimStart().startsWith(prefix))) {
2252
+ diagnostics.push({
2253
+ code: "noise_record_dropped",
2254
+ message: "Dropped a harness-noise user record.",
2255
+ recordIndex,
2256
+ ...event.inputLine ? { inputLine: event.inputLine } : {}
2257
+ });
2258
+ return;
2259
+ }
2260
+ if (event.role === "user") {
2261
+ const record3 = {
2262
+ role: "user",
2263
+ content: event.content
2264
+ };
2265
+ return record3;
2266
+ }
2267
+ const record2 = {
2268
+ role: "assistant",
2269
+ content: event.content
2270
+ };
2271
+ return record2;
2272
+ }
2273
+ if (event.type === "reasoning") {
2274
+ if (!event.content.trim()) {
2275
+ return;
2276
+ }
2277
+ const record2 = {
2278
+ role: "reasoning",
2279
+ content: event.content
2280
+ };
2281
+ return record2;
2282
+ }
2283
+ if (event.type === "tool_call") {
2284
+ const entry = plan.calls.get(eventIndex);
2285
+ const sourceId2 = entry?.sourceId ?? (event.id || `call_${eventIndex + 1}`);
2286
+ const finalId2 = entry?.finalId ?? sourceId2;
2287
+ if (entry?.synthesized ?? !event.id) {
2288
+ diagnostics.push({
2289
+ code: "tool_call_id_synthesized",
2290
+ message: `Synthesized tool-call ID ${JSON.stringify(sourceId2)}.`,
2291
+ recordIndex,
2292
+ ...event.inputLine ? { inputLine: event.inputLine } : {}
2293
+ });
2294
+ }
2295
+ if (entry?.renamed) {
2296
+ diagnostics.push({
2297
+ code: "duplicate_tool_call_id",
2298
+ message: `Renamed duplicate tool-call ID ${JSON.stringify(sourceId2)} to ${JSON.stringify(finalId2)}.`,
2299
+ recordIndex,
2300
+ ...event.inputLine ? { inputLine: event.inputLine } : {}
2301
+ });
2302
+ }
2303
+ const name = event.name || "unknown_tool";
2304
+ if (!event.name) {
2305
+ diagnostics.push({
2306
+ code: "unknown_tool_name",
2307
+ message: `Substituted ${JSON.stringify(name)} for a missing tool name.`,
2308
+ recordIndex,
2309
+ ...event.inputLine ? { inputLine: event.inputLine } : {}
2310
+ });
2311
+ }
2312
+ const args = shrinkArgs(event.args, bounds.toolArguments.maxCharacters);
2313
+ if (args.reshaped) {
2314
+ diagnostics.push({
2315
+ code: "tool_arguments_reshaped",
2316
+ message: `Reshaped arguments for tool call ${JSON.stringify(finalId2)} into a JSON object.`,
2317
+ recordIndex,
2318
+ ...event.inputLine ? { inputLine: event.inputLine } : {}
2319
+ });
2320
+ }
2321
+ if (args.truncated) {
2322
+ diagnostics.push({
2323
+ code: "tool_arguments_truncated",
2324
+ message: `Truncated arguments for tool call ${JSON.stringify(finalId2)} to at most ${bounds.toolArguments.maxCharacters} Unicode code points.`,
2325
+ recordIndex,
2326
+ ...event.inputLine ? { inputLine: event.inputLine } : {}
2327
+ });
2328
+ }
2329
+ const record2 = {
2330
+ role: "assistant",
2331
+ content: null,
2332
+ tool_calls: [{ id: finalId2, name, args: args.args }]
2333
+ };
2334
+ return record2;
2335
+ }
2336
+ const sourceId = event.callId || "";
2337
+ const entries = plan.openCalls.get(sourceId);
2338
+ const openEntry = entries?.find((entry) => !entry.consumed);
2339
+ const crossChunk = !openEntry && partial && sourceId !== "" && !(entries && entries.length > 0);
2340
+ if (!openEntry && !crossChunk) {
2341
+ const duplicate = Boolean(entries && entries.length > 0);
2342
+ diagnostics.push({
2343
+ code: duplicate ? "duplicate_tool_result" : "orphan_tool_result",
2344
+ message: duplicate ? `Dropped a duplicate result for tool call ${JSON.stringify(sourceId)}.` : `Dropped a tool result without a preceding call for ${JSON.stringify(sourceId)}.`,
2345
+ recordIndex,
2346
+ ...event.inputLine ? { inputLine: event.inputLine } : {}
2347
+ });
2348
+ return;
2349
+ }
2350
+ if (openEntry)
2351
+ openEntry.consumed = true;
2352
+ if (filters.toolResults === "omit")
2353
+ return;
2354
+ const finalId = openEntry ? openEntry.finalId : sourceId;
2355
+ const resultLimit = bounds.toolResults.maxCharacters;
2356
+ const content = resultLimit === null ? event.content : truncateText(event.content, resultLimit, bounds.toolResults.strategy);
2357
+ if (content !== event.content) {
2358
+ diagnostics.push({
2359
+ code: "tool_result_truncated",
2360
+ message: `Truncated the result for tool call ${JSON.stringify(finalId)} to at most ${resultLimit} Unicode code points using the ${JSON.stringify(bounds.toolResults.strategy)} strategy.`,
2361
+ recordIndex,
2362
+ ...event.inputLine ? { inputLine: event.inputLine } : {}
2363
+ });
2364
+ }
2365
+ const record = {
2366
+ role: "tool",
2367
+ tool_call_id: finalId,
2368
+ content,
2369
+ ...typeof event.ok === "boolean" ? { ok: event.ok } : {}
2370
+ };
2371
+ return record;
2372
+ }
2373
+ function buildMeta(context, modelCounts) {
2374
+ let model = context.model;
2375
+ if (!model) {
2376
+ let best;
2377
+ let highestCount = 0;
2378
+ for (const [candidate, count] of modelCounts) {
2379
+ if (count > highestCount || count === highestCount && best !== undefined && candidate < best) {
2380
+ best = candidate;
2381
+ highestCount = count;
2382
+ }
2383
+ }
2384
+ model = best;
2385
+ }
2386
+ return {
2387
+ role: "meta",
2388
+ source: context.source,
2389
+ ...context.cwd ? { cwd: context.cwd } : {},
2390
+ ...context.gitBranch ? { git_branch: context.gitBranch } : {},
2391
+ ...model ? { model } : {}
2392
+ };
2393
+ }
2394
+ function fillTimestamps(count, anchors, context, diagnostics) {
2395
+ if (count === 0)
2396
+ return [];
2397
+ if (anchors.size === 0) {
2398
+ const baseMs = (context.createdAt ?? new Date(SYNTH_BASE_MS)).getTime();
2399
+ const stepSeconds = context.durationSeconds && count > 1 ? context.durationSeconds / (count - 1) : SYNTH_STEP_SECONDS;
2400
+ diagnostics.push({
2401
+ code: "timestamps_synthesized",
2402
+ message: `Synthesized timestamps for ${count} normalized records.`,
2403
+ count
2404
+ });
2405
+ return Array.from({ length: count }, (_, index) => new Date(baseMs + stepSeconds * 1000 * index).toISOString());
2406
+ }
2407
+ const output = new Array(count);
2408
+ const indexes = [...anchors.keys()].sort((a, b) => a - b);
2409
+ const first = indexes[0];
2410
+ const last = indexes[indexes.length - 1];
2411
+ if (first === undefined || last === undefined)
2412
+ return output;
2413
+ const anchorMs = (index) => {
2414
+ const anchor = anchors.get(index);
2415
+ if (!anchor) {
2416
+ throw new NormalizationError("invalid_normalized_transcript", `Missing timestamp anchor at record ${index}.`);
2417
+ }
2418
+ return anchor.getTime();
2419
+ };
2420
+ for (let index = 0;index < first; index += 1) {
2421
+ output[index] = new Date(anchorMs(first) - (first - index) * 1000).toISOString();
2422
+ }
2423
+ for (let cursor = 0;cursor + 1 < indexes.length; cursor += 1) {
2424
+ const start = indexes[cursor];
2425
+ const end = indexes[cursor + 1];
2426
+ if (start === undefined || end === undefined)
2427
+ continue;
2428
+ output[start] = new Date(anchorMs(start)).toISOString();
2429
+ const spanMs = anchorMs(end) - anchorMs(start);
2430
+ const gap = end - start;
2431
+ for (let index = start + 1;index < end; index += 1) {
2432
+ output[index] = new Date(anchorMs(start) + spanMs * (index - start) / gap).toISOString();
2433
+ }
2434
+ }
2435
+ output[last] = new Date(anchorMs(last)).toISOString();
2436
+ for (let index = last + 1;index < count; index += 1) {
2437
+ output[index] = new Date(anchorMs(last) + (index - last) * 1000).toISOString();
2438
+ }
2439
+ const interpolatedCount = count - anchors.size;
2440
+ if (interpolatedCount > 0) {
2441
+ diagnostics.push({
2442
+ code: "timestamps_interpolated",
2443
+ message: `Interpolated timestamps for ${interpolatedCount} normalized records.`,
2444
+ count: interpolatedCount
2445
+ });
2446
+ }
2447
+ return output;
2448
+ }
2449
+ function shrinkArgs(rawInput, limit) {
2450
+ const raw = rawInput || "{}";
2451
+ let parsed;
2452
+ try {
2453
+ parsed = JSON.parse(raw);
2454
+ } catch {
2455
+ parsed = undefined;
2456
+ }
2457
+ if (!isPlainObject(parsed)) {
2458
+ const full = JSON.stringify({ _raw: raw });
2459
+ const wrapped = limit === null ? full : wrapRawArgs(raw, limit);
2460
+ return {
2461
+ args: wrapped,
2462
+ reshaped: true,
2463
+ truncated: wrapped !== full
2464
+ };
2465
+ }
2466
+ if (limit === null || codePointLength(raw) <= limit) {
2467
+ return { args: raw, reshaped: false, truncated: false };
2468
+ }
2469
+ const legacy = shrinkObjectArgsLegacy(parsed, limit);
2470
+ if (codePointLength(legacy) <= limit) {
2471
+ return { args: legacy, reshaped: false, truncated: true };
2472
+ }
2473
+ const fresh = JSON.parse(raw);
2474
+ const serialized = shrinkObjectArgsSafely(fresh, limit);
2475
+ if (codePointLength(serialized) > limit) {
2476
+ return {
2477
+ args: wrapRawArgs(raw, limit),
2478
+ reshaped: true,
2479
+ truncated: true
2480
+ };
2481
+ }
2482
+ return { args: serialized, reshaped: false, truncated: true };
2483
+ }
2484
+ function shrinkObjectArgsLegacy(parsed, limit) {
2485
+ const leaves = [];
2486
+ collectStringLeaves(parsed, leaves);
2487
+ let serialized = JSON.stringify(parsed);
2488
+ const seen = new Set;
2489
+ while (codePointLength(serialized) > limit && leaves.length > 0) {
2490
+ if (seen.has(serialized))
2491
+ break;
2492
+ seen.add(serialized);
2493
+ let largest = leaves[0];
2494
+ if (!largest)
2495
+ break;
2496
+ for (const leaf of leaves) {
2497
+ if (codePointLength(leafValue(leaf)) > codePointLength(leafValue(largest))) {
2498
+ largest = leaf;
2499
+ }
2500
+ }
2501
+ const value = leafValue(largest);
2502
+ const valueLength = codePointLength(value);
2503
+ if (valueLength <= ARGS_LEAF_FLOOR)
2504
+ break;
2505
+ const keep = Math.max(ARGS_LEAF_FLOOR, Math.floor(valueLength / 2));
2506
+ setLeafValue(largest, sliceCodePoints(value, 0, keep) + truncationMarker(valueLength - keep));
2507
+ serialized = JSON.stringify(parsed);
2508
+ }
2509
+ return serialized;
2510
+ }
2511
+ function shrinkObjectArgsSafely(parsed, limit) {
2512
+ const leaves = [];
2513
+ collectStringLeaves(parsed, leaves);
2514
+ let serialized = JSON.stringify(parsed);
2515
+ while (codePointLength(serialized) > limit && leaves.length > 0) {
2516
+ let largest = leaves.find((leaf) => leaf.currentLength > 0);
2517
+ if (!largest)
2518
+ break;
2519
+ for (const leaf of leaves) {
2520
+ if (leaf.currentLength > largest.currentLength)
2521
+ largest = leaf;
2522
+ }
2523
+ const previousLength = codePointLength(serialized);
2524
+ const overflow = previousLength - limit;
2525
+ let candidate = "";
2526
+ let nextKeep = 0;
2527
+ if (largest.keep > 0) {
2528
+ const preferredFloor = largest.keep > ARGS_LEAF_FLOOR ? ARGS_LEAF_FLOOR : 0;
2529
+ const markerBudget = codePointLength(truncationMarker(codePointLength(largest.original)));
2530
+ nextKeep = Math.max(preferredFloor, Math.min(Math.floor(largest.keep / 2), largest.keep - overflow - markerBudget - 1));
2531
+ nextKeep = Math.max(0, Math.min(nextKeep, largest.keep - 1));
2532
+ candidate = sliceCodePoints(largest.original, 0, nextKeep) + truncationMarker(codePointLength(largest.original) - nextKeep);
2533
+ if (codePointLength(candidate) >= largest.currentLength) {
2534
+ candidate = "";
2535
+ nextKeep = 0;
2536
+ }
2537
+ }
2538
+ setLeafValue(largest, candidate);
2539
+ largest.keep = nextKeep;
2540
+ largest.currentLength = codePointLength(candidate);
2541
+ serialized = JSON.stringify(parsed);
2542
+ if (codePointLength(serialized) >= previousLength && candidate) {
2543
+ setLeafValue(largest, "");
2544
+ largest.keep = 0;
2545
+ largest.currentLength = 0;
2546
+ serialized = JSON.stringify(parsed);
2547
+ }
2548
+ }
2549
+ return serialized;
2550
+ }
2551
+ function collectStringLeaves(value, leaves) {
2552
+ if (Array.isArray(value)) {
2553
+ for (let index = 0;index < value.length; index += 1) {
2554
+ const child = value[index];
2555
+ if (typeof child === "string") {
2556
+ leaves.push({
2557
+ parent: value,
2558
+ key: index,
2559
+ original: child,
2560
+ keep: codePointLength(child),
2561
+ currentLength: codePointLength(child)
2562
+ });
2563
+ } else if (child !== null && typeof child === "object") {
2564
+ collectStringLeaves(child, leaves);
2565
+ }
2566
+ }
2567
+ return;
2568
+ }
2569
+ if (!isPlainObject(value))
2570
+ return;
2571
+ for (const [key, child] of Object.entries(value)) {
2572
+ if (typeof child === "string") {
2573
+ leaves.push({
2574
+ parent: value,
2575
+ key,
2576
+ original: child,
2577
+ keep: codePointLength(child),
2578
+ currentLength: codePointLength(child)
2579
+ });
2580
+ } else if (child !== null && typeof child === "object") {
2581
+ collectStringLeaves(child, leaves);
2582
+ }
2583
+ }
2584
+ }
2585
+ function setLeafValue(leaf, value) {
2586
+ if (Array.isArray(leaf.parent))
2587
+ leaf.parent[leaf.key] = value;
2588
+ else
2589
+ leaf.parent[leaf.key] = value;
2590
+ }
2591
+ function leafValue(leaf) {
2592
+ const value = Array.isArray(leaf.parent) ? leaf.parent[leaf.key] : leaf.parent[leaf.key];
2593
+ return typeof value === "string" ? value : "";
2594
+ }
2595
+ function wrapRawArgs(raw, limit) {
2596
+ const full = JSON.stringify({ _raw: raw });
2597
+ if (codePointLength(full) <= limit)
2598
+ return full;
2599
+ let low = 0;
2600
+ const rawLength = codePointLength(raw);
2601
+ let high = Math.min(rawLength, limit);
2602
+ let best = "{}";
2603
+ while (low <= high) {
2604
+ const keep = Math.floor((low + high) / 2);
2605
+ const candidate = JSON.stringify({
2606
+ _raw: sliceCodePoints(raw, 0, keep) + truncationMarker(rawLength - keep)
2607
+ });
2608
+ if (codePointLength(candidate) <= limit) {
2609
+ best = candidate;
2610
+ low = keep + 1;
2611
+ } else {
2612
+ high = keep - 1;
2613
+ }
2614
+ }
2615
+ return best;
2616
+ }
2617
+ function isPlainObject(value) {
2618
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2619
+ }
2620
+ function truncateText(text, limit, strategy) {
2621
+ const textLength = codePointLength(text);
2622
+ if (textLength <= limit)
2623
+ return text;
2624
+ let low = 0;
2625
+ let high = Math.min(textLength - 1, limit);
2626
+ let keep = -1;
2627
+ let marker = "";
2628
+ while (low <= high) {
2629
+ const candidateKeep = Math.floor((low + high) / 2);
2630
+ const candidateMarker = truncationMarker(textLength - candidateKeep);
2631
+ if (candidateKeep + codePointLength(candidateMarker) <= limit) {
2632
+ keep = candidateKeep;
2633
+ marker = candidateMarker;
2634
+ low = candidateKeep + 1;
2635
+ } else {
2636
+ high = candidateKeep - 1;
2637
+ }
2638
+ }
2639
+ if (keep < 0) {
2640
+ marker = sliceCodePoints("…", 0, limit);
2641
+ keep = limit - codePointLength(marker);
2642
+ }
2643
+ if (strategy === "head") {
2644
+ return sliceCodePoints(text, 0, keep) + marker;
2645
+ }
2646
+ const headLength = Math.ceil(keep / 2);
2647
+ const tailLength = keep - headLength;
2648
+ return sliceCodePoints(text, 0, headLength) + marker + (tailLength > 0 ? sliceCodePoints(text, textLength - tailLength, textLength) : "");
2649
+ }
2650
+ function truncationMarker(remaining) {
2651
+ return `
2652
+ … [truncated, ${remaining} more chars]`;
2653
+ }
2654
+ function codePointLength(text) {
2655
+ let length = 0;
2656
+ for (const _character of text)
2657
+ length += 1;
2658
+ return length;
2659
+ }
2660
+ function sliceCodePoints(text, start, end) {
2661
+ const stop = end ?? Number.POSITIVE_INFINITY;
2662
+ let result = "";
2663
+ let index = 0;
2664
+ for (const character of text) {
2665
+ if (index >= stop)
2666
+ break;
2667
+ if (index >= start)
2668
+ result += character;
2669
+ index += 1;
2670
+ }
2671
+ return result;
2672
+ }
2673
+
2674
+ // src/adapters/deepagents/index.ts
2675
+ import { spawn } from "node:child_process";
2676
+ import { accessSync, constants } from "node:fs";
2677
+ import { homedir } from "node:os";
2678
+ import { join } from "node:path";
2679
+ import { fileURLToPath } from "node:url";
2680
+ var MAX_HELPER_OUTPUT_BYTES = 64 * 1024 * 1024;
2681
+ async function normalizeCheckpoint(input) {
2682
+ const { decoded, bounds, filters } = await decodeCheckpoint(input);
2683
+ return normalizeDecodedSession(decoded, bounds, { filters });
2684
+ }
2685
+ async function decodeCheckpoint(input) {
2686
+ if (!input || typeof input !== "object") {
2687
+ throw new NormalizationError("invalid_input", "Input must be an object.");
2688
+ }
2689
+ if (input.source !== "deepagents") {
2690
+ throw new NormalizationError("unknown_source", `Checkpoint source must be "deepagents"; received ${JSON.stringify(input.source)}.`);
2691
+ }
2692
+ const bounds = resolveBounds(input.bounds);
2693
+ const filters = resolveFilters(input.filters);
2694
+ const checkpoint = await loadDeepAgentsCheckpoint(input.checkpoint);
2695
+ return {
2696
+ decoded: decodeDeepAgentsCheckpoint(checkpoint),
2697
+ bounds,
2698
+ filters
2699
+ };
2700
+ }
2701
+ async function loadDeepAgentsCheckpoint(checkpoint) {
2702
+ validateLocation(checkpoint);
2703
+ const path = checkpoint.path ?? join(homedir(), ".deepagents", "sessions.db");
2704
+ const python = checkpoint.pythonExecutable ?? process.env.PYTHON ?? "python3";
2705
+ const helper = resolveHelperPath();
2706
+ return await new Promise((resolve, reject) => {
2707
+ const child = spawn(python, [helper], {
2708
+ stdio: ["pipe", "pipe", "pipe"],
2709
+ windowsHide: true
2710
+ });
2711
+ const stdout = [];
2712
+ const stderr = [];
2713
+ let outputBytes = 0;
2714
+ let settled = false;
2715
+ const fail2 = (error) => {
2716
+ if (settled)
2717
+ return;
2718
+ settled = true;
2719
+ reject(error);
2720
+ };
2721
+ child.on("error", (error) => {
2722
+ if (error.code === "ENOENT") {
2723
+ fail2(new NormalizationError("python_unavailable", `Could not execute Python interpreter ${JSON.stringify(python)}. ` + "Pass checkpoint.pythonExecutable or set PYTHON."));
2724
+ return;
2725
+ }
2726
+ fail2(new NormalizationError("checkpoint_read_failed", `Could not start the Deep Agents checkpoint helper: ${error.message}`));
2727
+ });
2728
+ child.stdout.on("data", (chunk) => {
2729
+ outputBytes += chunk.length;
2730
+ if (outputBytes > MAX_HELPER_OUTPUT_BYTES) {
2731
+ child.kill();
2732
+ fail2(new NormalizationError("checkpoint_read_failed", "Deep Agents checkpoint helper output exceeded 64 MiB."));
2733
+ return;
2734
+ }
2735
+ stdout.push(chunk);
2736
+ });
2737
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
2738
+ child.on("close", (code) => {
2739
+ if (settled)
2740
+ return;
2741
+ settled = true;
2742
+ const raw = Buffer.concat(stdout).toString("utf8");
2743
+ if (code !== 0) {
2744
+ const detail = Buffer.concat(stderr).toString("utf8").trim();
2745
+ reject(new NormalizationError("checkpoint_read_failed", `Deep Agents checkpoint helper failed${detail ? `: ${detail}` : ` with exit code ${code}`}.`));
2746
+ return;
2747
+ }
2748
+ let response;
2749
+ try {
2750
+ response = JSON.parse(raw);
2751
+ } catch {
2752
+ reject(new NormalizationError("checkpoint_read_failed", "Deep Agents checkpoint helper returned invalid JSON."));
2753
+ return;
2754
+ }
2755
+ if (isHelperFailure(response)) {
2756
+ reject(new NormalizationError(response.code, response.message));
2757
+ return;
2758
+ }
2759
+ if (!isHelperSuccess(response)) {
2760
+ reject(new NormalizationError("invalid_checkpoint_state", "Deep Agents checkpoint helper returned an invalid message envelope."));
2761
+ return;
2762
+ }
2763
+ resolve({ ...response.data, threadId: checkpoint.threadId });
2764
+ });
2765
+ child.stdin.on("error", () => {});
2766
+ child.stdin.end(JSON.stringify({
2767
+ path,
2768
+ threadId: checkpoint.threadId,
2769
+ checkpointNamespace: ""
2770
+ }));
2771
+ });
2772
+ }
2773
+ function decodeDeepAgentsCheckpoint(checkpoint) {
2774
+ const events = [];
2775
+ const checkpointTimestamp = parseTimestamp(checkpoint.checkpointTimestamp);
2776
+ checkpoint.messages.forEach((message, offset) => {
2777
+ const timestamp = parseTimestamp(message.timestamp) ?? checkpointTimestamp;
2778
+ let componentIndex = 0;
2779
+ const emit = (event) => {
2780
+ events.push({
2781
+ ...event,
2782
+ sourceOffset: offset,
2783
+ sourceAnchorKind: "ordinal",
2784
+ componentIndex: componentIndex++
2785
+ });
2786
+ };
2787
+ if (message.role === "human") {
2788
+ if (message.content) {
2789
+ emit({
2790
+ type: "message",
2791
+ role: "user",
2792
+ content: message.content,
2793
+ ...timestamp ? { timestamp } : {}
2794
+ });
2795
+ }
2796
+ return;
2797
+ }
2798
+ if (message.role === "ai") {
2799
+ for (const reasoning of message.reasoning) {
2800
+ if (!reasoning)
2801
+ continue;
2802
+ emit({
2803
+ type: "reasoning",
2804
+ content: reasoning,
2805
+ ...timestamp ? { timestamp } : {},
2806
+ ...message.model ? { model: message.model } : {}
2807
+ });
2808
+ }
2809
+ if (message.content) {
2810
+ emit({
2811
+ type: "message",
2812
+ role: "assistant",
2813
+ content: message.content,
2814
+ ...timestamp ? { timestamp } : {},
2815
+ ...message.model ? { model: message.model } : {}
2816
+ });
2817
+ }
2818
+ for (const call of message.toolCalls) {
2819
+ emit({
2820
+ type: "tool_call",
2821
+ args: jsonString(call.args),
2822
+ ...call.id ? { id: call.id } : {},
2823
+ ...call.name ? { name: call.name } : {},
2824
+ ...timestamp ? { timestamp } : {},
2825
+ ...message.model ? { model: message.model } : {}
2826
+ });
2827
+ }
2828
+ return;
2829
+ }
2830
+ emit({
2831
+ type: "tool_result",
2832
+ callId: message.toolCallId,
2833
+ content: message.content,
2834
+ ...timestamp ? { timestamp } : {}
2835
+ });
2836
+ });
2837
+ return {
2838
+ events,
2839
+ context: {
2840
+ source: "deepagents",
2841
+ ...checkpoint.cwd ? { cwd: checkpoint.cwd } : {},
2842
+ ...checkpoint.model ? { model: checkpoint.model } : {},
2843
+ ...checkpointTimestamp ? { createdAt: checkpointTimestamp } : {},
2844
+ sourceGroupId: deepAgentsGroupId(checkpoint.threadId, checkpoint.checkpointNamespace)
2845
+ },
2846
+ diagnostics: []
2847
+ };
2848
+ }
2849
+ function deepAgentsGroupId(threadId, checkpointNamespace) {
2850
+ return JSON.stringify([threadId, checkpointNamespace]);
2851
+ }
2852
+ function validateLocation(checkpoint) {
2853
+ if (!checkpoint || typeof checkpoint !== "object") {
2854
+ throw new NormalizationError("invalid_input", "Deep Agents checkpoint location must be an object.");
2855
+ }
2856
+ if (typeof checkpoint.threadId !== "string" || !checkpoint.threadId) {
2857
+ throw new NormalizationError("invalid_input", "Deep Agents checkpoint.threadId is required; the CLI session picker lists thread ids.");
2858
+ }
2859
+ if (checkpoint.path !== undefined && (typeof checkpoint.path !== "string" || !checkpoint.path)) {
2860
+ throw new NormalizationError("invalid_input", "Deep Agents checkpoint.path must be a non-empty string when provided.");
2861
+ }
2862
+ if (checkpoint.pythonExecutable !== undefined && (typeof checkpoint.pythonExecutable !== "string" || !checkpoint.pythonExecutable)) {
2863
+ throw new NormalizationError("invalid_input", "Deep Agents checkpoint.pythonExecutable must be a non-empty string.");
2864
+ }
2865
+ }
2866
+ function resolveHelperPath() {
2867
+ const candidates = [
2868
+ fileURLToPath(new URL("../../../helpers/deepagents_checkpoint.py", import.meta.url)),
2869
+ fileURLToPath(new URL("./deepagents_checkpoint.py", import.meta.url))
2870
+ ];
2871
+ for (const candidate of candidates) {
2872
+ try {
2873
+ accessSync(candidate, constants.R_OK);
2874
+ return candidate;
2875
+ } catch {}
2876
+ }
2877
+ throw new NormalizationError("checkpoint_read_failed", "The Deep Agents checkpoint helper is missing from this trajectory installation.");
2878
+ }
2879
+ function isObject3(value) {
2880
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2881
+ }
2882
+ function isHelperFailure(value) {
2883
+ return isObject3(value) && value.ok === false && typeof value.code === "string" && typeof value.message === "string";
2884
+ }
2885
+ function isHelperSuccess(value) {
2886
+ return isObject3(value) && value.ok === true && isCheckpointData(value.data);
2887
+ }
2888
+ function isCheckpointData(value) {
2889
+ return isObject3(value) && typeof value.checkpointId === "string" && typeof value.checkpointNamespace === "string" && typeof value.checkpointTimestamp === "string" && (value.cwd === undefined || typeof value.cwd === "string") && (value.model === undefined || typeof value.model === "string") && Array.isArray(value.messages) && value.messages.every(isMessageData);
2890
+ }
2891
+ function isMessageData(value) {
2892
+ if (!isObject3(value) || typeof value.content !== "string")
2893
+ return false;
2894
+ if (value.timestamp !== undefined && typeof value.timestamp !== "string") {
2895
+ return false;
2896
+ }
2897
+ if (value.role === "human")
2898
+ return true;
2899
+ if (value.role === "ai")
2900
+ return isAIData(value);
2901
+ if (value.role === "tool")
2902
+ return typeof value.toolCallId === "string";
2903
+ return false;
2904
+ }
2905
+ function isAIData(value) {
2906
+ return Array.isArray(value.reasoning) && value.reasoning.every((item) => typeof item === "string") && Array.isArray(value.toolCalls) && value.toolCalls.every(isToolCall) && (value.model === undefined || typeof value.model === "string");
2907
+ }
2908
+ function isToolCall(value) {
2909
+ return isObject3(value) && "args" in value && (value.id === undefined || typeof value.id === "string") && (value.name === undefined || typeof value.name === "string");
2910
+ }
2911
+ // src/adapters/claude-code/list.ts
2912
+ import { homedir as homedir2 } from "node:os";
2913
+ import { basename, join as join3 } from "node:path";
2914
+
2915
+ // src/adapters/listing-shared.ts
2916
+ import { readdirSync, statSync } from "node:fs";
2917
+ import { join as join2 } from "node:path";
2918
+ function sortListings(items) {
2919
+ return items.sort((left, right) => {
2920
+ const l = left.updatedAt ?? "";
2921
+ const r = right.updatedAt ?? "";
2922
+ if (l !== r)
2923
+ return l < r ? 1 : -1;
2924
+ return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
2925
+ });
2926
+ }
2927
+ function safeReadDir(path) {
2928
+ try {
2929
+ return readdirSync(path, { withFileTypes: true }).map((entry) => ({
2930
+ name: entry.name,
2931
+ isDirectory: entry.isDirectory(),
2932
+ isFile: entry.isFile()
2933
+ }));
2934
+ } catch {
2935
+ return [];
2936
+ }
2937
+ }
2938
+ function safeStat(path) {
2939
+ try {
2940
+ const stats = statSync(path);
2941
+ return { mtimeMs: stats.mtimeMs, sizeBytes: stats.size };
2942
+ } catch {
2943
+ return;
2944
+ }
2945
+ }
2946
+ function listingFromFile(id, path) {
2947
+ const facts = safeStat(path);
2948
+ if (!facts)
2949
+ return;
2950
+ return {
2951
+ id,
2952
+ path,
2953
+ updatedAt: new Date(facts.mtimeMs).toISOString(),
2954
+ sizeBytes: facts.sizeBytes
2955
+ };
2956
+ }
2957
+ function collectFiles(root, extension, depth) {
2958
+ if (depth < 0)
2959
+ return [];
2960
+ const collected = [];
2961
+ for (const entry of safeReadDir(root)) {
2962
+ const full = join2(root, entry.name);
2963
+ if (entry.isFile && entry.name.endsWith(extension)) {
2964
+ collected.push(full);
2965
+ } else if (entry.isDirectory) {
2966
+ collected.push(...collectFiles(full, extension, depth - 1));
2967
+ }
2968
+ }
2969
+ return collected;
2970
+ }
2971
+ var dynamicImport = (specifier) => import(specifier);
2972
+ async function openSqliteReadOnly(path) {
2973
+ let moduleError;
2974
+ try {
2975
+ const sqlite = await dynamicImport("node:sqlite");
2976
+ const DatabaseSync = sqlite.DatabaseSync;
2977
+ return openWithWalFallback((readOnly) => {
2978
+ const database = readOnly ? new DatabaseSync(path, { readOnly: true }) : new DatabaseSync(path);
2979
+ return {
2980
+ all: (sql, ...params) => database.prepare(sql).all(...params),
2981
+ close: () => database.close()
2982
+ };
2983
+ }, path);
2984
+ } catch (error) {
2985
+ if (error instanceof NormalizationError)
2986
+ throw error;
2987
+ if (!isModuleMissing(error))
2988
+ throw sqliteOpenFailure(path, error);
2989
+ moduleError = error;
2990
+ }
2991
+ try {
2992
+ const sqlite = await dynamicImport("bun:sqlite");
2993
+ const Database = sqlite.Database;
2994
+ return openWithWalFallback((readOnly) => {
2995
+ const database = readOnly ? new Database(path, { readonly: true }) : new Database(path);
2996
+ return {
2997
+ all: (sql, ...params) => database.query(sql).all(...params),
2998
+ close: () => database.close()
2999
+ };
3000
+ }, path);
3001
+ } catch (error) {
3002
+ if (error instanceof NormalizationError)
3003
+ throw error;
3004
+ if (isModuleMissing(error)) {
3005
+ throw new NormalizationError("listing_unavailable", `Listing this source requires a runtime with built-in SQLite (Node.js 22.5+ or Bun): ${String(moduleError instanceof Error ? moduleError.message : moduleError)}`);
3006
+ }
3007
+ throw sqliteOpenFailure(path, error);
3008
+ }
3009
+ }
3010
+ function openWithWalFallback(open, path) {
3011
+ let readOnlyError;
3012
+ for (const readOnly of [true, false]) {
3013
+ let handle;
3014
+ try {
3015
+ handle = open(readOnly);
3016
+ handle.all("SELECT 1");
3017
+ return handle;
3018
+ } catch (error) {
3019
+ try {
3020
+ handle?.close();
3021
+ } catch {}
3022
+ if (readOnly) {
3023
+ readOnlyError = error;
3024
+ continue;
3025
+ }
3026
+ throw sqliteOpenFailure(path, readOnlyError ?? error);
3027
+ }
3028
+ }
3029
+ throw sqliteOpenFailure(path, readOnlyError);
3030
+ }
3031
+ function sqliteOpenFailure(path, error) {
3032
+ return new NormalizationError("invalid_input", `Could not open SQLite store ${JSON.stringify(path)}: ${error instanceof Error ? error.message : String(error)}`);
3033
+ }
3034
+ function isModuleMissing(error) {
3035
+ if (error === null || typeof error !== "object")
3036
+ return false;
3037
+ const code = error.code;
3038
+ const message = error.message;
3039
+ return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || code === "ERR_UNKNOWN_BUILTIN_MODULE" || typeof message === "string" && /Cannot find (module|package)|No such built-in module/i.test(message);
3040
+ }
3041
+
3042
+ // src/adapters/claude-code/list.ts
3043
+ var AGENT_PREFIX = "agent-";
3044
+ var JSONL_SUFFIX = ".jsonl";
3045
+ async function listClaudeCodeTrajectories(root) {
3046
+ const base = root ?? join3(homedir2(), ".claude", "projects");
3047
+ const items = [];
3048
+ for (const project of safeReadDir(base)) {
3049
+ if (!project.isDirectory)
3050
+ continue;
3051
+ const projectPath = join3(base, project.name);
3052
+ for (const entry of safeReadDir(projectPath)) {
3053
+ if (entry.isFile && entry.name.endsWith(JSONL_SUFFIX)) {
3054
+ const path = join3(projectPath, entry.name);
3055
+ const listing = listingFromFile(agentIdFromFilename(entry.name) ?? basename(entry.name, JSONL_SUFFIX), path);
3056
+ if (listing)
3057
+ items.push(listing);
3058
+ continue;
3059
+ }
3060
+ if (!entry.isDirectory)
3061
+ continue;
3062
+ const subagentsRoot = join3(projectPath, entry.name, "subagents");
3063
+ for (const path of collectFiles(subagentsRoot, JSONL_SUFFIX, 2)) {
3064
+ const agentId = agentIdFromFilename(basename(path));
3065
+ if (!agentId)
3066
+ continue;
3067
+ const listing = listingFromFile(agentId, path);
3068
+ if (listing)
3069
+ items.push(listing);
3070
+ }
3071
+ }
3072
+ }
3073
+ return sortListings(items);
3074
+ }
3075
+ function agentIdFromFilename(filename) {
3076
+ if (!filename.startsWith(AGENT_PREFIX) || !filename.endsWith(JSONL_SUFFIX)) {
3077
+ return;
3078
+ }
3079
+ const agentId = filename.slice(AGENT_PREFIX.length, -JSONL_SUFFIX.length);
3080
+ return agentId || undefined;
3081
+ }
3082
+
3083
+ // src/adapters/codex/list.ts
3084
+ import { homedir as homedir3 } from "node:os";
3085
+ import { basename as basename2, join as join4 } from "node:path";
3086
+ async function listCodexTrajectories(root) {
3087
+ const base = root ?? join4(homedir3(), ".codex", "sessions");
3088
+ const items = [];
3089
+ for (const path of collectFiles(base, ".jsonl", 4)) {
3090
+ const listing = listingFromFile(basename2(path, ".jsonl"), path);
3091
+ if (listing)
3092
+ items.push(listing);
3093
+ }
3094
+ return sortListings(items);
3095
+ }
3096
+
3097
+ // src/adapters/droid/list.ts
3098
+ import { homedir as homedir4 } from "node:os";
3099
+ import { basename as basename3, join as join5 } from "node:path";
3100
+ async function listDroidTrajectories(root) {
3101
+ const base = root ?? join5(homedir4(), ".factory", "sessions");
3102
+ const items = [];
3103
+ for (const path of collectFiles(base, ".jsonl", 12)) {
3104
+ const listing = listingFromFile(basename3(path, ".jsonl"), path);
3105
+ if (listing)
3106
+ items.push(listing);
3107
+ }
3108
+ return sortListings(items);
3109
+ }
3110
+
3111
+ // src/adapters/deepagents/list.ts
3112
+ import { homedir as homedir5 } from "node:os";
3113
+ import { join as join6 } from "node:path";
3114
+ async function listDeepAgentsTrajectories(root) {
3115
+ const path = resolveStorePath(root);
3116
+ if (!safeStat(path))
3117
+ return [];
3118
+ const database = await openSqliteReadOnly(path);
3119
+ try {
3120
+ const rows = database.all("SELECT thread_id, MAX(checkpoint_id) AS latest FROM checkpoints " + "WHERE checkpoint_ns = '' GROUP BY thread_id ORDER BY latest DESC, thread_id");
3121
+ const items = [];
3122
+ for (const row of rows) {
3123
+ if (typeof row.thread_id !== "string" || !row.thread_id)
3124
+ continue;
3125
+ items.push({ id: row.thread_id, path });
3126
+ }
3127
+ return items;
3128
+ } finally {
3129
+ database.close();
3130
+ }
3131
+ }
3132
+ function resolveStorePath(root) {
3133
+ if (root === undefined)
3134
+ return join6(homedir5(), ".deepagents", "sessions.db");
3135
+ return root.endsWith(".db") ? root : join6(root, "sessions.db");
3136
+ }
3137
+
3138
+ // src/adapters/hermes/list.ts
3139
+ import { homedir as homedir6 } from "node:os";
3140
+ import { join as join7 } from "node:path";
3141
+ async function listHermesTrajectories(root) {
3142
+ const path = resolveStorePath2(root);
3143
+ if (!safeStat(path))
3144
+ return [];
3145
+ const database = await openSqliteReadOnly(path);
3146
+ try {
3147
+ const rows = database.all("SELECT id, title, started_at, ended_at FROM sessions");
3148
+ const items = [];
3149
+ for (const row of rows) {
3150
+ if (typeof row.id !== "string" || !row.id)
3151
+ continue;
3152
+ const updated = numeric(row.ended_at) ?? numeric(row.started_at);
3153
+ items.push({
3154
+ id: row.id,
3155
+ path,
3156
+ ...updated !== undefined ? { updatedAt: new Date(updated * 1000).toISOString() } : {},
3157
+ ...typeof row.title === "string" && row.title ? { title: row.title } : {}
3158
+ });
3159
+ }
3160
+ return sortListings(items);
3161
+ } finally {
3162
+ database.close();
3163
+ }
3164
+ }
3165
+ function resolveStorePath2(root) {
3166
+ if (root === undefined)
3167
+ return join7(homedir6(), ".hermes", "state.db");
3168
+ return root.endsWith(".db") ? root : join7(root, "state.db");
3169
+ }
3170
+ function numeric(value) {
3171
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
3172
+ }
3173
+
3174
+ // src/adapters/letta-code/list.ts
3175
+ import { homedir as homedir7 } from "node:os";
3176
+ import { join as join8 } from "node:path";
3177
+ async function listLettaCodeTrajectories(root) {
3178
+ const base = root ?? join8(homedir7(), ".letta", "transcripts");
3179
+ const items = [];
3180
+ for (const agent of safeReadDir(base)) {
3181
+ if (!agent.isDirectory)
3182
+ continue;
3183
+ const agentPath = join8(base, agent.name);
3184
+ for (const conversation of safeReadDir(agentPath)) {
3185
+ if (!conversation.isDirectory)
3186
+ continue;
3187
+ const path = join8(agentPath, conversation.name, "transcript.jsonl");
3188
+ const listing = listingFromFile(`${agent.name}/${conversation.name}`, path);
3189
+ if (listing && (listing.sizeBytes ?? 0) > 0)
3190
+ items.push(listing);
3191
+ }
3192
+ }
3193
+ return sortListings(items);
3194
+ }
3195
+
3196
+ // src/adapters/openclaw/list.ts
3197
+ import { existsSync } from "node:fs";
3198
+ import { homedir as homedir8 } from "node:os";
3199
+ import { basename as basename4, join as join9 } from "node:path";
3200
+ async function listOpenClawTrajectories(root) {
3201
+ const base = root ?? defaultStateDir();
3202
+ const items = [];
3203
+ const agentsPath = join9(base, "agents");
3204
+ for (const agent of safeReadDir(agentsPath)) {
3205
+ if (!agent.isDirectory)
3206
+ continue;
3207
+ const sessionsPath = join9(agentsPath, agent.name, "sessions");
3208
+ for (const entry of safeReadDir(sessionsPath)) {
3209
+ if (!entry.isFile || !entry.name.endsWith(".jsonl"))
3210
+ continue;
3211
+ const path = join9(sessionsPath, entry.name);
3212
+ const listing = listingFromFile(basename4(entry.name, ".jsonl"), path);
3213
+ if (listing)
3214
+ items.push(listing);
3215
+ }
3216
+ }
3217
+ return sortListings(items);
3218
+ }
3219
+ function defaultStateDir() {
3220
+ const override = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim();
3221
+ if (override)
3222
+ return override;
3223
+ const current = join9(homedir8(), ".openclaw");
3224
+ if (existsSync(current))
3225
+ return current;
3226
+ return join9(homedir8(), ".clawdbot");
3227
+ }
3228
+
3229
+ // src/adapters/openhands/list.ts
3230
+ import { homedir as homedir9 } from "node:os";
3231
+ import { join as join10 } from "node:path";
3232
+ async function listOpenHandsTrajectories(root) {
3233
+ const base = root ?? join10(homedir9(), ".openhands", "sessions");
3234
+ const items = [];
3235
+ for (const entry of safeReadDir(base)) {
3236
+ if (!entry.isDirectory)
3237
+ continue;
3238
+ const path = join10(base, entry.name);
3239
+ const facts = safeStat(path);
3240
+ items.push({
3241
+ id: entry.name,
3242
+ path,
3243
+ ...facts ? { updatedAt: new Date(facts.mtimeMs).toISOString() } : {}
3244
+ });
3245
+ }
3246
+ return sortListings(items);
3247
+ }
3248
+
3249
+ // src/adapters/pi/list.ts
3250
+ import { homedir as homedir10 } from "node:os";
3251
+ import { basename as basename5, join as join11 } from "node:path";
3252
+ async function listPiTrajectories(root) {
3253
+ const base = root ?? defaultAgentDir();
3254
+ const items = [];
3255
+ const sessionsPath = join11(base, "sessions");
3256
+ for (const project of safeReadDir(sessionsPath)) {
3257
+ if (!project.isDirectory)
3258
+ continue;
3259
+ const projectPath = join11(sessionsPath, project.name);
3260
+ for (const entry of safeReadDir(projectPath)) {
3261
+ if (!entry.isFile || !entry.name.endsWith(".jsonl"))
3262
+ continue;
3263
+ const path = join11(projectPath, entry.name);
3264
+ const listing = listingFromFile(basename5(entry.name, ".jsonl"), path);
3265
+ if (listing)
3266
+ items.push(listing);
3267
+ }
3268
+ }
3269
+ return sortListings(items);
3270
+ }
3271
+ function defaultAgentDir() {
3272
+ const override = process.env.PI_CODING_AGENT_DIR?.trim();
3273
+ if (override)
3274
+ return override;
3275
+ return join11(homedir10(), ".pi", "agent");
3276
+ }
3277
+
3278
+ // src/adapters/omp/list.ts
3279
+ import { existsSync as existsSync2 } from "node:fs";
3280
+ import { homedir as homedir11 } from "node:os";
3281
+ import { basename as basename6, join as join12 } from "node:path";
3282
+ async function listOmpTrajectories(root) {
3283
+ const items = [];
3284
+ const sessionsPath = root ? join12(root, "sessions") : resolveOmpSessionsPath({
3285
+ home: homedir11(),
3286
+ platform: process.platform,
3287
+ env: process.env,
3288
+ exists: existsSync2
3289
+ });
3290
+ for (const project of safeReadDir(sessionsPath)) {
3291
+ if (!project.isDirectory)
3292
+ continue;
3293
+ const projectPath = join12(sessionsPath, project.name);
3294
+ for (const entry of safeReadDir(projectPath)) {
3295
+ if (!entry.isFile || !entry.name.endsWith(".jsonl"))
3296
+ continue;
3297
+ const path = join12(projectPath, entry.name);
3298
+ const listing = listingFromFile(basename6(entry.name, ".jsonl"), path);
3299
+ if (listing)
3300
+ items.push(listing);
3301
+ }
3302
+ }
3303
+ return sortListings(items);
3304
+ }
3305
+ function resolveOmpSessionsPath(options) {
3306
+ const profile = resolveProfile(options.env.OMP_PROFILE, options.env.PI_PROFILE);
3307
+ const configRoot = join12(options.home, options.env.PI_CONFIG_DIR || ".omp", ...profile ? ["profiles", profile] : []);
3308
+ const agentOverride = profile ? undefined : options.env.PI_CODING_AGENT_DIR?.trim() || undefined;
3309
+ const agentDir = agentOverride ?? join12(configRoot, "agent");
3310
+ if (agentOverride === undefined && (options.platform === "linux" || options.platform === "darwin")) {
3311
+ const xdgData = options.env.XDG_DATA_HOME?.trim();
3312
+ if (xdgData) {
3313
+ const xdgRoot = join12(xdgData, "omp", ...profile ? ["profiles", profile] : []);
3314
+ if (options.exists(xdgRoot))
3315
+ return join12(xdgRoot, "sessions");
3316
+ }
3317
+ }
3318
+ return join12(agentDir, "sessions");
3319
+ }
3320
+ function resolveProfile(ompProfile, piProfile) {
3321
+ const value = (ompProfile !== undefined ? ompProfile : piProfile)?.trim();
3322
+ if (!value || value === "default")
3323
+ return;
3324
+ if (value === "." || value === ".." || value.endsWith(".") || !/^[a-z0-9][a-z0-9._-]{0,63}$/.test(value) || /^(?:CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])(?:\..*)?$/i.test(value)) {
3325
+ return;
3326
+ }
3327
+ return value;
3328
+ }
3329
+
3330
+ // src/listing.ts
3331
+ var DEFAULT_LIMIT = 50;
3332
+ var MAX_LIMIT = 1000;
3333
+ var LISTERS = {
3334
+ "claude-code": listClaudeCodeTrajectories,
3335
+ codex: listCodexTrajectories,
3336
+ droid: listDroidTrajectories,
3337
+ deepagents: listDeepAgentsTrajectories,
3338
+ hermes: listHermesTrajectories,
3339
+ "letta-code": listLettaCodeTrajectories,
3340
+ openclaw: listOpenClawTrajectories,
3341
+ openhands: listOpenHandsTrajectories,
3342
+ pi: listPiTrajectories,
3343
+ omp: listOmpTrajectories
3344
+ };
3345
+ async function listTrajectories(input) {
3346
+ if (!input || typeof input !== "object") {
3347
+ throw new NormalizationError("invalid_input", "Input must be an object.");
3348
+ }
3349
+ const lister = LISTERS[input.source];
3350
+ if (!lister) {
3351
+ throw new NormalizationError(isKnownNormalizationOnlySource(input.source) ? "listing_unavailable" : "unknown_source", isKnownNormalizationOnlySource(input.source) ? `Local trajectory listing is not available for ${JSON.stringify(input.source)}; supply an exported transcript directly to normalizeTranscript().` : `Unknown trajectory source ${JSON.stringify(input.source)}. Supported listing sources: ${Object.keys(LISTERS).join(", ")}.`);
3352
+ }
3353
+ if (input.root !== undefined && (typeof input.root !== "string" || !input.root)) {
3354
+ throw new NormalizationError("invalid_input", "root must be a non-empty string when provided.");
3355
+ }
3356
+ const limit = resolveLimit2(input.limit);
3357
+ const items = await lister(input.root);
3358
+ return paginate(items, input.cursor, limit);
3359
+ }
3360
+ function isKnownNormalizationOnlySource(source) {
3361
+ return source === "copilot-cli" || source === "cursor" || source === "gemini-cli" || source === "opencode";
3362
+ }
3363
+ function resolveLimit2(limit) {
3364
+ if (limit === undefined)
3365
+ return DEFAULT_LIMIT;
3366
+ if (!Number.isInteger(limit) || limit < 1 || limit > MAX_LIMIT) {
3367
+ throw new NormalizationError("invalid_input", `limit must be an integer between 1 and ${MAX_LIMIT}.`);
3368
+ }
3369
+ return limit;
3370
+ }
3371
+ function paginate(items, cursor, limit) {
3372
+ let start = 0;
3373
+ if (cursor !== undefined) {
3374
+ const state = decodeCursor(cursor);
3375
+ const index = items.findIndex((item) => item.id === state.id);
3376
+ start = index >= 0 ? index + 1 : Math.min(state.i + 1, items.length);
3377
+ }
3378
+ const page = items.slice(start, start + limit);
3379
+ const end = start + page.length;
3380
+ const last = page[page.length - 1];
3381
+ if (end >= items.length || last === undefined) {
3382
+ return { items: page };
3383
+ }
3384
+ return {
3385
+ items: page,
3386
+ nextCursor: encodeCursor({ v: 1, id: last.id, i: end - 1 })
3387
+ };
3388
+ }
3389
+ function encodeCursor(state) {
3390
+ return Buffer.from(JSON.stringify(state), "utf8").toString("base64url");
3391
+ }
3392
+ function decodeCursor(cursor) {
3393
+ let parsed;
3394
+ try {
3395
+ parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
3396
+ } catch {
3397
+ throw invalidCursor();
3398
+ }
3399
+ if (parsed === null || typeof parsed !== "object" || parsed.v !== 1 || typeof parsed.id !== "string" || !Number.isInteger(parsed.i) || parsed.i < 0) {
3400
+ throw invalidCursor();
3401
+ }
3402
+ return parsed;
3403
+ }
3404
+ function invalidCursor() {
3405
+ return new NormalizationError("invalid_input", "cursor is not a valid trajectory-listing cursor.");
3406
+ }
3407
+
3408
+ // src/index.ts
3409
+ var ADAPTERS = {
3410
+ "claude-code": claudeCodeAdapter,
3411
+ codex: codexAdapter,
3412
+ "copilot-cli": copilotCliAdapter,
3413
+ cursor: cursorAdapter,
3414
+ droid: droidAdapter,
3415
+ "gemini-cli": geminiCliAdapter,
3416
+ hermes: hermesAdapter,
3417
+ "letta-code": lettaCodeAdapter,
3418
+ openclaw: openClawAdapter,
3419
+ opencode: openCodeAdapter,
3420
+ openhands: openHandsAdapter,
3421
+ pi: piAdapter,
3422
+ omp: ompAdapter
3423
+ };
3424
+ function decodeTranscript(input) {
3425
+ if (!input || typeof input !== "object") {
3426
+ throw new NormalizationError("invalid_input", "Input must be an object.");
3427
+ }
3428
+ if (typeof input.transcript !== "string") {
3429
+ throw new NormalizationError("invalid_input", "Input transcript must be a string containing the source transcript.");
3430
+ }
3431
+ const adapter = ADAPTERS[input.source];
3432
+ if (!adapter) {
3433
+ throw new NormalizationError("unknown_source", `Unknown trajectory source ${JSON.stringify(input.source)}. Supported sources: ${Object.keys(ADAPTERS).join(", ")}.`);
3434
+ }
3435
+ return {
3436
+ decoded: adapter.decode(input.transcript),
3437
+ bounds: resolveBounds(input.bounds),
3438
+ filters: resolveFilters(input.filters)
3439
+ };
3440
+ }
3441
+ function normalizeTranscript(input) {
3442
+ const { decoded, bounds, filters } = decodeTranscript(input);
3443
+ return normalizeDecodedSession(decoded, bounds, {
3444
+ partial: isPartialTranscript(input),
3445
+ filters
3446
+ });
3447
+ }
3448
+ function isPartialTranscript(input) {
3449
+ return (input.sourceContext?.partial ?? false) || (input.sourceContext?.baseByteOffset ?? 0) > 0;
3450
+ }
3451
+
3452
+ // src/python-cli.ts
3453
+ var PROTOCOL_VERSION = 1;
3454
+ async function main() {
3455
+ const request = parseRequest(readFileSync(0, "utf8"));
3456
+ const results = [];
3457
+ for (const input of request.requests) {
3458
+ try {
3459
+ const result = input !== null && typeof input === "object" && "list" in input ? await listTrajectories(input.list) : input !== null && typeof input === "object" && ("source" in input) && input.source === "deepagents" ? await normalizeCheckpoint(input) : normalizeTranscript(input);
3460
+ results.push({
3461
+ ok: true,
3462
+ result
3463
+ });
3464
+ } catch (error) {
3465
+ if (error instanceof NormalizationError) {
3466
+ results.push({
3467
+ ok: false,
3468
+ error: {
3469
+ name: error.name,
3470
+ code: error.code,
3471
+ message: error.message
3472
+ }
3473
+ });
3474
+ continue;
3475
+ }
3476
+ results.push({
3477
+ ok: false,
3478
+ error: {
3479
+ name: error instanceof Error ? error.name : "Error",
3480
+ code: "internal_error",
3481
+ message: error instanceof Error ? error.message : String(error)
3482
+ }
3483
+ });
3484
+ }
3485
+ }
3486
+ writeFileSync(1, JSON.stringify({ version: PROTOCOL_VERSION, results }));
3487
+ }
3488
+ function parseRequest(raw) {
3489
+ let value;
3490
+ try {
3491
+ value = JSON.parse(raw);
3492
+ } catch {
3493
+ throw new Error("Trajectory bridge input must be valid JSON.");
3494
+ }
3495
+ if (!value || typeof value !== "object" || !("version" in value) || value.version !== PROTOCOL_VERSION || !("requests" in value) || !Array.isArray(value.requests)) {
3496
+ throw new Error(`Trajectory bridge input must contain version ${PROTOCOL_VERSION} and a requests array.`);
3497
+ }
3498
+ return value;
3499
+ }
3500
+ main().catch((error) => {
3501
+ const message = error instanceof Error ? error.message : String(error);
3502
+ process.stderr.write(`trajectory bridge: ${message}
3503
+ `);
3504
+ process.exitCode = 1;
3505
+ });