ccakashic 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/ccakashic.js +248 -0
- package/dist/discover.js +230 -0
- package/dist/html-generator.js +554 -0
- package/{lib → dist}/pages.js +65 -80
- package/dist/parser.js +465 -0
- package/{lib → dist}/template-assets.js +6 -7
- package/package.json +14 -5
- package/bin/ccakashic.js +0 -225
- package/lib/discover.js +0 -221
- package/lib/html-generator.js +0 -586
- package/lib/parser.js +0 -496
package/lib/parser.js
DELETED
|
@@ -1,496 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const readline = require('readline');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
|
|
7
|
-
async function parseSession(sessionPath) {
|
|
8
|
-
const lines = await readJsonlLines(sessionPath);
|
|
9
|
-
const messages = buildConversation(lines);
|
|
10
|
-
|
|
11
|
-
// Check for subagent conversations
|
|
12
|
-
const sessionId = path.basename(sessionPath, '.jsonl');
|
|
13
|
-
const subagentsDir = path.join(path.dirname(sessionPath), sessionId, 'subagents');
|
|
14
|
-
const subagents = {};
|
|
15
|
-
|
|
16
|
-
if (fs.existsSync(subagentsDir)) {
|
|
17
|
-
const agentFiles = fs.readdirSync(subagentsDir).filter(f => f.endsWith('.jsonl'));
|
|
18
|
-
for (const f of agentFiles) {
|
|
19
|
-
const agentLines = await readJsonlLines(path.join(subagentsDir, f));
|
|
20
|
-
const agentMessages = buildConversation(agentLines);
|
|
21
|
-
// Extract agent ID from filename: agent-a1234.jsonl → a1234
|
|
22
|
-
const agentId = f.replace(/^agent-/, '').replace(/\.jsonl$/, '');
|
|
23
|
-
subagents[agentId] = agentMessages;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Inline subagent conversations into the main message list
|
|
28
|
-
inlineSubagents(messages, subagents);
|
|
29
|
-
|
|
30
|
-
// Aggregate token usage stats
|
|
31
|
-
const stats = aggregateUsage(lines);
|
|
32
|
-
|
|
33
|
-
return { messages, subagents: {}, sessionPath, stats };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function aggregateUsage(lines) {
|
|
37
|
-
let inputTokens = 0;
|
|
38
|
-
let outputTokens = 0;
|
|
39
|
-
let cacheCreation = 0;
|
|
40
|
-
let cacheRead = 0;
|
|
41
|
-
let turns = 0;
|
|
42
|
-
let firstTimestamp = null;
|
|
43
|
-
let lastTimestamp = null;
|
|
44
|
-
|
|
45
|
-
for (const line of lines) {
|
|
46
|
-
if (line.timestamp) {
|
|
47
|
-
if (!firstTimestamp) firstTimestamp = line.timestamp;
|
|
48
|
-
lastTimestamp = line.timestamp;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (line.type === 'assistant') {
|
|
52
|
-
const usage = line.message?.usage;
|
|
53
|
-
if (usage) {
|
|
54
|
-
turns++;
|
|
55
|
-
inputTokens += usage.input_tokens || 0;
|
|
56
|
-
outputTokens += usage.output_tokens || 0;
|
|
57
|
-
cacheCreation += usage.cache_creation_input_tokens || 0;
|
|
58
|
-
cacheRead += usage.cache_read_input_tokens || 0;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const totalInput = inputTokens + cacheCreation + cacheRead;
|
|
64
|
-
const totalTokens = totalInput + outputTokens;
|
|
65
|
-
const cacheTotal = cacheCreation + cacheRead;
|
|
66
|
-
const cacheHitRate = cacheTotal > 0 ? cacheRead / cacheTotal : 0;
|
|
67
|
-
|
|
68
|
-
let durationMs = 0;
|
|
69
|
-
if (firstTimestamp && lastTimestamp) {
|
|
70
|
-
durationMs = new Date(lastTimestamp) - new Date(firstTimestamp);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
turns,
|
|
75
|
-
inputTokens,
|
|
76
|
-
outputTokens,
|
|
77
|
-
cacheCreation,
|
|
78
|
-
cacheRead,
|
|
79
|
-
totalTokens,
|
|
80
|
-
cacheHitRate,
|
|
81
|
-
durationMs,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function readJsonlLines(filePath) {
|
|
86
|
-
return new Promise((resolve) => {
|
|
87
|
-
const lines = [];
|
|
88
|
-
const rl = readline.createInterface({
|
|
89
|
-
input: fs.createReadStream(filePath, { encoding: 'utf-8' }),
|
|
90
|
-
crlfDelay: Infinity,
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
rl.on('line', (line) => {
|
|
94
|
-
try {
|
|
95
|
-
lines.push(JSON.parse(line));
|
|
96
|
-
} catch {
|
|
97
|
-
// skip
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
rl.on('close', () => resolve(lines));
|
|
102
|
-
rl.on('error', () => resolve(lines));
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function buildConversation(lines) {
|
|
107
|
-
const messages = [];
|
|
108
|
-
|
|
109
|
-
// Deduplicate assistant messages by message.id (streaming: keep last)
|
|
110
|
-
const assistantById = new Map();
|
|
111
|
-
for (const line of lines) {
|
|
112
|
-
if (line.type === 'assistant' && line.message?.id) {
|
|
113
|
-
assistantById.set(line.message.id, line);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const seenAssistantIds = new Set();
|
|
117
|
-
|
|
118
|
-
for (const line of lines) {
|
|
119
|
-
// Skip metadata-only types
|
|
120
|
-
if (['file-history-snapshot', 'last-prompt', 'permission-mode'].includes(line.type)) {
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (line.type === 'user') {
|
|
125
|
-
processUserMessage(line, messages);
|
|
126
|
-
} else if (line.type === 'assistant') {
|
|
127
|
-
// Deduplicate: skip if we've already processed this message.id
|
|
128
|
-
const msgId = line.message?.id;
|
|
129
|
-
if (msgId) {
|
|
130
|
-
if (seenAssistantIds.has(msgId)) continue;
|
|
131
|
-
// Only process the final version
|
|
132
|
-
if (assistantById.get(msgId) !== line) continue;
|
|
133
|
-
seenAssistantIds.add(msgId);
|
|
134
|
-
}
|
|
135
|
-
processAssistantMessage(line, messages);
|
|
136
|
-
} else if (line.type === 'system') {
|
|
137
|
-
processSystemMessage(line, messages);
|
|
138
|
-
}
|
|
139
|
-
// Skip progress, queue-operation, etc.
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Pair tool_use with tool_result
|
|
143
|
-
pairToolMessages(messages);
|
|
144
|
-
|
|
145
|
-
// Calculate per-turn token usage (tokens between each user input)
|
|
146
|
-
calculateTurnUsage(messages);
|
|
147
|
-
|
|
148
|
-
// Calculate elapsed time for each message (time since previous message)
|
|
149
|
-
calculateElapsed(messages);
|
|
150
|
-
|
|
151
|
-
return messages;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function parseLocalCommand(text) {
|
|
155
|
-
// Parse <bash-input>, <bash-stdout>, <bash-stderr> tags
|
|
156
|
-
const inputMatch = text.match(/<bash-input>([\s\S]*?)<\/bash-input>/);
|
|
157
|
-
const stdoutMatch = text.match(/<bash-stdout>([\s\S]*?)<\/bash-stdout>/);
|
|
158
|
-
const stderrMatch = text.match(/<bash-stderr>([\s\S]*?)<\/bash-stderr>/);
|
|
159
|
-
|
|
160
|
-
if (inputMatch) {
|
|
161
|
-
return { type: 'local_command', subtype: 'input', command: inputMatch[1] };
|
|
162
|
-
}
|
|
163
|
-
if (stdoutMatch || stderrMatch) {
|
|
164
|
-
return {
|
|
165
|
-
type: 'local_command',
|
|
166
|
-
subtype: 'output',
|
|
167
|
-
stdout: stdoutMatch ? stdoutMatch[1] : '',
|
|
168
|
-
stderr: stderrMatch ? stderrMatch[1] : '',
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function processUserText(text, line, messages) {
|
|
175
|
-
// Skip local-command-caveat (system instruction, not user input)
|
|
176
|
-
if (text.match(/^<local-command-caveat>/)) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Parse local command input/output
|
|
181
|
-
const localCmd = parseLocalCommand(text);
|
|
182
|
-
if (localCmd) {
|
|
183
|
-
if (localCmd.subtype === 'input') {
|
|
184
|
-
messages.push({
|
|
185
|
-
type: 'local_command',
|
|
186
|
-
command: localCmd.command,
|
|
187
|
-
timestamp: line.timestamp,
|
|
188
|
-
uuid: line.uuid,
|
|
189
|
-
});
|
|
190
|
-
} else if (localCmd.subtype === 'output') {
|
|
191
|
-
// Attach to previous local_command if exists
|
|
192
|
-
const prev = messages.length > 0 ? messages[messages.length - 1] : null;
|
|
193
|
-
if (prev && prev.type === 'local_command' && !prev.stdout) {
|
|
194
|
-
prev.stdout = localCmd.stdout;
|
|
195
|
-
prev.stderr = localCmd.stderr;
|
|
196
|
-
} else {
|
|
197
|
-
messages.push({
|
|
198
|
-
type: 'local_command',
|
|
199
|
-
command: '',
|
|
200
|
-
stdout: localCmd.stdout,
|
|
201
|
-
stderr: localCmd.stderr,
|
|
202
|
-
timestamp: line.timestamp,
|
|
203
|
-
uuid: line.uuid,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Strip system-reminder tags but keep surrounding user text
|
|
211
|
-
const stripped = text
|
|
212
|
-
.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g, '')
|
|
213
|
-
.replace(/<command-name>[\s\S]*?<\/command-name>/g, '')
|
|
214
|
-
.replace(/<command-message>[\s\S]*?<\/command-message>/g, '')
|
|
215
|
-
.replace(/<command-args>[\s\S]*?<\/command-args>/g, '')
|
|
216
|
-
.replace(/<local-command-stdout>[\s\S]*?<\/local-command-stdout>/g, '')
|
|
217
|
-
.trim();
|
|
218
|
-
|
|
219
|
-
if (stripped) {
|
|
220
|
-
messages.push({
|
|
221
|
-
type: 'user',
|
|
222
|
-
text: stripped,
|
|
223
|
-
timestamp: line.timestamp,
|
|
224
|
-
uuid: line.uuid,
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function processUserMessage(line, messages) {
|
|
230
|
-
const content = line.message?.content;
|
|
231
|
-
if (!content) return;
|
|
232
|
-
|
|
233
|
-
if (typeof content === 'string') {
|
|
234
|
-
if (content.trim()) {
|
|
235
|
-
processUserText(content, line, messages);
|
|
236
|
-
}
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (Array.isArray(content)) {
|
|
241
|
-
for (const block of content) {
|
|
242
|
-
if (block.type === 'text' && block.text?.trim()) {
|
|
243
|
-
processUserText(block.text, line, messages);
|
|
244
|
-
} else if (block.type === 'tool_result') {
|
|
245
|
-
const toolResult = {
|
|
246
|
-
type: 'tool_result',
|
|
247
|
-
toolUseId: block.tool_use_id,
|
|
248
|
-
timestamp: line.timestamp,
|
|
249
|
-
uuid: line.uuid,
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
// Extract structured result from toolUseResult
|
|
253
|
-
if (line.toolUseResult) {
|
|
254
|
-
toolResult.richResult = line.toolUseResult;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Raw content
|
|
258
|
-
if (typeof block.content === 'string') {
|
|
259
|
-
toolResult.content = block.content;
|
|
260
|
-
} else if (Array.isArray(block.content)) {
|
|
261
|
-
const texts = block.content
|
|
262
|
-
.filter(b => b.type === 'text')
|
|
263
|
-
.map(b => b.text);
|
|
264
|
-
toolResult.content = texts.join('\n');
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Try to read persisted output
|
|
268
|
-
if (toolResult.content && toolResult.content.includes('<persisted-output>')) {
|
|
269
|
-
const match = toolResult.content.match(/Full output saved to: ([^\n<]+)/);
|
|
270
|
-
if (match) {
|
|
271
|
-
const persistedPath = match[1].trim();
|
|
272
|
-
try {
|
|
273
|
-
if (fs.existsSync(persistedPath)) {
|
|
274
|
-
toolResult.fullContent = fs.readFileSync(persistedPath, 'utf-8');
|
|
275
|
-
}
|
|
276
|
-
} catch {
|
|
277
|
-
// ignore
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
messages.push(toolResult);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function processAssistantMessage(line, messages) {
|
|
289
|
-
const content = line.message?.content;
|
|
290
|
-
if (!content || !Array.isArray(content)) return;
|
|
291
|
-
|
|
292
|
-
const usage = line.message?.usage || null;
|
|
293
|
-
let usageAssigned = false;
|
|
294
|
-
|
|
295
|
-
for (const block of content) {
|
|
296
|
-
if (block.type === 'thinking') {
|
|
297
|
-
messages.push({
|
|
298
|
-
type: 'thinking',
|
|
299
|
-
timestamp: line.timestamp,
|
|
300
|
-
uuid: line.uuid,
|
|
301
|
-
});
|
|
302
|
-
} else if (block.type === 'text' && block.text?.trim()) {
|
|
303
|
-
messages.push({
|
|
304
|
-
type: 'assistant',
|
|
305
|
-
text: block.text,
|
|
306
|
-
model: line.message.model,
|
|
307
|
-
timestamp: line.timestamp,
|
|
308
|
-
uuid: line.uuid,
|
|
309
|
-
usage: !usageAssigned ? usage : null,
|
|
310
|
-
});
|
|
311
|
-
usageAssigned = true;
|
|
312
|
-
} else if (block.type === 'tool_use') {
|
|
313
|
-
messages.push({
|
|
314
|
-
type: 'tool_use',
|
|
315
|
-
toolName: block.name,
|
|
316
|
-
toolUseId: block.id,
|
|
317
|
-
input: block.input,
|
|
318
|
-
timestamp: line.timestamp,
|
|
319
|
-
uuid: line.uuid,
|
|
320
|
-
usage: !usageAssigned ? usage : null,
|
|
321
|
-
});
|
|
322
|
-
usageAssigned = true;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function calculateTurnUsage(messages) {
|
|
328
|
-
// Walk backwards from each user message, summing usage of preceding assistant messages
|
|
329
|
-
// A "turn" = user input → all assistant responses until next user input
|
|
330
|
-
for (let i = 0; i < messages.length; i++) {
|
|
331
|
-
if (messages[i].type !== 'user' && messages[i].type !== 'local_command') continue;
|
|
332
|
-
|
|
333
|
-
// Sum usage of all following assistant/tool messages until next user message
|
|
334
|
-
let totalInput = 0;
|
|
335
|
-
let totalOutput = 0;
|
|
336
|
-
let totalCacheRead = 0;
|
|
337
|
-
let totalCacheCreate = 0;
|
|
338
|
-
const seenUsageIds = new Set(); // Dedupe (multiple blocks from same API response)
|
|
339
|
-
|
|
340
|
-
for (let j = i + 1; j < messages.length; j++) {
|
|
341
|
-
const m = messages[j];
|
|
342
|
-
if (m.type === 'user' || m.type === 'local_command') break;
|
|
343
|
-
if (m.usage && !seenUsageIds.has(m.uuid + (m.usage.input_tokens || 0))) {
|
|
344
|
-
seenUsageIds.add(m.uuid + (m.usage.input_tokens || 0));
|
|
345
|
-
totalInput += m.usage.input_tokens || 0;
|
|
346
|
-
totalOutput += m.usage.output_tokens || 0;
|
|
347
|
-
totalCacheRead += m.usage.cache_read_input_tokens || 0;
|
|
348
|
-
totalCacheCreate += m.usage.cache_creation_input_tokens || 0;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Find turn_duration in the following messages
|
|
353
|
-
let turnDurationMs = 0;
|
|
354
|
-
for (let j = i + 1; j < messages.length; j++) {
|
|
355
|
-
const m = messages[j];
|
|
356
|
-
if (m.type === 'user' || m.type === 'local_command') break;
|
|
357
|
-
if (m.type === 'system' && m.subtype === 'turn_duration') {
|
|
358
|
-
turnDurationMs = m.durationMs || 0;
|
|
359
|
-
break;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const total = totalInput + totalOutput + totalCacheRead + totalCacheCreate;
|
|
364
|
-
if (total > 0 || turnDurationMs > 0) {
|
|
365
|
-
messages[i].turnUsage = {
|
|
366
|
-
input: totalInput,
|
|
367
|
-
output: totalOutput,
|
|
368
|
-
cacheRead: totalCacheRead,
|
|
369
|
-
cacheCreate: totalCacheCreate,
|
|
370
|
-
total,
|
|
371
|
-
durationMs: turnDurationMs,
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
function calculateElapsed(messages) {
|
|
378
|
-
for (let i = 1; i < messages.length; i++) {
|
|
379
|
-
const prev = messages[i - 1];
|
|
380
|
-
const curr = messages[i];
|
|
381
|
-
if (prev.timestamp && curr.timestamp) {
|
|
382
|
-
const elapsed = new Date(curr.timestamp) - new Date(prev.timestamp);
|
|
383
|
-
if (elapsed > 0) {
|
|
384
|
-
curr.elapsedMs = elapsed;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
// For tool_use with paired result, calculate execution time
|
|
388
|
-
if (curr.type === 'tool_use' && curr.result?.timestamp && curr.timestamp) {
|
|
389
|
-
const execTime = new Date(curr.result.timestamp) - new Date(curr.timestamp);
|
|
390
|
-
if (execTime > 0) {
|
|
391
|
-
curr.execMs = execTime;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function processSystemMessage(line, messages) {
|
|
398
|
-
if (line.subtype === 'turn_duration') {
|
|
399
|
-
messages.push({
|
|
400
|
-
type: 'system',
|
|
401
|
-
subtype: 'turn_duration',
|
|
402
|
-
durationMs: line.durationMs,
|
|
403
|
-
timestamp: line.timestamp,
|
|
404
|
-
});
|
|
405
|
-
} else if (line.subtype === 'bridge_status') {
|
|
406
|
-
// skip
|
|
407
|
-
} else if (line.content) {
|
|
408
|
-
messages.push({
|
|
409
|
-
type: 'system',
|
|
410
|
-
subtype: line.subtype || 'info',
|
|
411
|
-
content: typeof line.content === 'string' ? line.content : JSON.stringify(line.content),
|
|
412
|
-
timestamp: line.timestamp,
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function inlineSubagents(messages, subagents) {
|
|
418
|
-
if (!Object.keys(subagents).length) return;
|
|
419
|
-
|
|
420
|
-
// For each Agent tool_use that has a paired result, extract agentId from the result content
|
|
421
|
-
for (const msg of messages) {
|
|
422
|
-
if (msg.type !== 'tool_use' || msg.toolName !== 'Agent') continue;
|
|
423
|
-
if (!msg.result) continue;
|
|
424
|
-
|
|
425
|
-
const content = msg.result.content || msg.result.fullContent || '';
|
|
426
|
-
const match = content.match(/agentId:\s*([a-f0-9]+)/);
|
|
427
|
-
if (match) {
|
|
428
|
-
const agentId = match[1];
|
|
429
|
-
if (subagents[agentId]) {
|
|
430
|
-
msg.subagentMessages = subagents[agentId];
|
|
431
|
-
msg.subagentId = agentId;
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Fallback: match remaining subagents by description in meta files
|
|
437
|
-
const usedIds = new Set(
|
|
438
|
-
messages.filter(m => m.subagentId).map(m => m.subagentId)
|
|
439
|
-
);
|
|
440
|
-
const unmatchedAgents = Object.keys(subagents).filter(id => !usedIds.has(id));
|
|
441
|
-
|
|
442
|
-
if (unmatchedAgents.length) {
|
|
443
|
-
for (const msg of messages) {
|
|
444
|
-
if (msg.type !== 'tool_use' || msg.toolName !== 'Agent') continue;
|
|
445
|
-
if (msg.subagentMessages) continue;
|
|
446
|
-
|
|
447
|
-
const desc = (msg.input?.description || '').toLowerCase();
|
|
448
|
-
if (!desc) continue;
|
|
449
|
-
|
|
450
|
-
for (let i = unmatchedAgents.length - 1; i >= 0; i--) {
|
|
451
|
-
const agentId = unmatchedAgents[i];
|
|
452
|
-
// Check first user message of subagent for matching description
|
|
453
|
-
const firstMsg = subagents[agentId]?.[0];
|
|
454
|
-
if (firstMsg?.type === 'user') {
|
|
455
|
-
const agentText = (firstMsg.text || '').toLowerCase();
|
|
456
|
-
if (agentText.includes(desc) || desc.includes(agentText.slice(0, 30))) {
|
|
457
|
-
msg.subagentMessages = subagents[agentId];
|
|
458
|
-
msg.subagentId = agentId;
|
|
459
|
-
unmatchedAgents.splice(i, 1);
|
|
460
|
-
break;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function pairToolMessages(messages) {
|
|
469
|
-
// Build map of tool_use id → index
|
|
470
|
-
const toolUseMap = new Map();
|
|
471
|
-
for (let i = 0; i < messages.length; i++) {
|
|
472
|
-
if (messages[i].type === 'tool_use') {
|
|
473
|
-
toolUseMap.set(messages[i].toolUseId, i);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Attach tool_result to corresponding tool_use
|
|
478
|
-
for (let i = 0; i < messages.length; i++) {
|
|
479
|
-
if (messages[i].type === 'tool_result' && messages[i].toolUseId) {
|
|
480
|
-
const useIdx = toolUseMap.get(messages[i].toolUseId);
|
|
481
|
-
if (useIdx !== undefined) {
|
|
482
|
-
messages[useIdx].result = messages[i];
|
|
483
|
-
messages[i]._paired = true;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Remove paired tool_results (they're now nested in tool_use)
|
|
489
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
490
|
-
if (messages[i]._paired) {
|
|
491
|
-
messages.splice(i, 1);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
module.exports = { parseSession };
|