* Parse Pi CLI JSONL streaming log output and format as markdown. * Pi CLI emits one JSON object per line (JSONL) with typed events: * - type "init": session initialization with model and session_id * - type "assistant": agent message content (delta:true for streaming chunks) * - type "
(logContent)
| 21 | * @returns {{markdown: string, logEntries: Array, mcpFailures: Array<string>, maxTurnsHit: boolean}} Parsed log data |
| 22 | */ |
| 23 | function parsePiLog(logContent) { |
| 24 | if (!logContent) { |
| 25 | return { |
| 26 | markdown: "## 🤖 Pi\n\nNo log content provided.\n\n", |
| 27 | logEntries: [], |
| 28 | mcpFailures: [], |
| 29 | maxTurnsHit: false, |
| 30 | }; |
| 31 | } |
| 32 | |
| 33 | /** @type {Array<any>} */ |
| 34 | const rawEntries = []; |
| 35 | for (const line of logContent.split("\n")) { |
| 36 | const trimmed = line.trim(); |
| 37 | if (!trimmed || !trimmed.startsWith("{")) { |
| 38 | continue; |
| 39 | } |
| 40 | try { |
| 41 | rawEntries.push(JSON.parse(trimmed)); |
| 42 | } catch (_e) { |
| 43 | // Skip non-JSON lines |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | if (rawEntries.length === 0) { |
| 48 | return { |
| 49 | markdown: "## 🤖 Pi\n\nLog format not recognized as Pi JSONL.\n\n", |
| 50 | logEntries: [], |
| 51 | mcpFailures: [], |
| 52 | maxTurnsHit: false, |
| 53 | }; |
| 54 | } |
| 55 | |
| 56 | const logEntries = transformPiEntries(rawEntries); |
| 57 | |
| 58 | const resultEntry = rawEntries.find(e => e.type === "result"); |
| 59 | |
| 60 | const canonicalLogEntries = convertLegacyLogEntriesToCopilotEvents(logEntries, { sourceEngine: "pi" }); |
| 61 | const conversationResult = generateConversationMarkdown(canonicalLogEntries, { |
| 62 | formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: false }), |
| 63 | formatInitCallback: initEntry => formatInitializationSummary(initEntry, { includeSlashCommands: false }), |
| 64 | }); |
| 65 | |
| 66 | let markdown = conversationResult.markdown; |
| 67 | |
| 68 | if (resultEntry && resultEntry.stats) { |
| 69 | const stats = resultEntry.stats; |
| 70 | const syntheticEntry = { |
| 71 | usage: { |
| 72 | input_tokens: stats.input_tokens || 0, |
| 73 | output_tokens: stats.output_tokens || 0, |
| 74 | }, |
| 75 | duration_ms: stats.duration_ms || 0, |
| 76 | num_turns: stats.turns || 0, |
| 77 | }; |
| 78 | markdown += generateInformationSection(syntheticEntry); |
| 79 | |
| 80 | // Append a normalized result entry so log_parser_bootstrap.cjs can write it |
no test coverage detected