* Parse Gemini CLI JSONL log output and format as markdown. * Gemini CLI outputs one JSON object per line (JSONL) with typed entries: * - type "init": session initialization with model and session_id * - type "message": user/assistant messages, assistant uses delta:true for streaming chunks * -
(logContent)
| 21 | * @returns {{markdown: string, logEntries: Array, mcpFailures: Array<string>, maxTurnsHit: boolean}} Parsed log data |
| 22 | */ |
| 23 | function parseGeminiLog(logContent) { |
| 24 | if (!logContent) { |
| 25 | return { |
| 26 | markdown: "## 🤖 Gemini\n\nNo log content provided.\n\n", |
| 27 | logEntries: [], |
| 28 | mcpFailures: [], |
| 29 | maxTurnsHit: false, |
| 30 | }; |
| 31 | } |
| 32 | |
| 33 | // Parse JSONL lines |
| 34 | /** @type {Array<any>} */ |
| 35 | const rawEntries = []; |
| 36 | for (const line of logContent.split("\n")) { |
| 37 | const trimmed = line.trim(); |
| 38 | if (!trimmed || !trimmed.startsWith("{")) { |
| 39 | continue; |
| 40 | } |
| 41 | try { |
| 42 | rawEntries.push(JSON.parse(trimmed)); |
| 43 | } catch (_e) { |
| 44 | // Skip non-JSON lines |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | if (rawEntries.length === 0) { |
| 49 | return { |
| 50 | markdown: "## 🤖 Gemini\n\nLog format not recognized as Gemini JSONL.\n\n", |
| 51 | logEntries: [], |
| 52 | mcpFailures: [], |
| 53 | maxTurnsHit: false, |
| 54 | }; |
| 55 | } |
| 56 | |
| 57 | // Transform Gemini JSONL entries into canonical logEntries format |
| 58 | const logEntries = transformGeminiEntries(rawEntries); |
| 59 | |
| 60 | // Extract the final result entry for stats |
| 61 | const resultEntry = rawEntries.find(e => e.type === "result"); |
| 62 | |
| 63 | // Generate conversation markdown using shared function |
| 64 | const canonicalLogEntries = convertLegacyLogEntriesToCopilotEvents(logEntries, { sourceEngine: "gemini" }); |
| 65 | const conversationResult = generateConversationMarkdown(canonicalLogEntries, { |
| 66 | formatToolCallback: (toolUse, toolResult) => formatToolUse(toolUse, toolResult, { includeDetailedParameters: false }), |
| 67 | formatInitCallback: initEntry => formatInitializationSummary(initEntry, { includeSlashCommands: false }), |
| 68 | }); |
| 69 | |
| 70 | let markdown = conversationResult.markdown; |
| 71 | |
| 72 | // Add Information section using Gemini-specific stats from the result entry |
| 73 | if (resultEntry && resultEntry.stats) { |
| 74 | const stats = resultEntry.stats; |
| 75 | const syntheticEntry = { |
| 76 | usage: { |
| 77 | input_tokens: stats.input_tokens || 0, |
| 78 | output_tokens: stats.output_tokens || 0, |
| 79 | cache_read_input_tokens: stats.cached || 0, |
| 80 | }, |
no test coverage detected