* Parse codex log content and format as markdown * @param {string} logContent - The raw log content to parse * @returns {{markdown: string, logEntries: Array, mcpFailures: Array , maxTurnsHit: boolean}} Parsed log data
(logContent)
| 483 | * @returns {{markdown: string, logEntries: Array, mcpFailures: Array<string>, maxTurnsHit: boolean}} Parsed log data |
| 484 | */ |
| 485 | function parseCodexLog(logContent) { |
| 486 | // Newer Codex CLI versions emit a structured JSONL event stream rather than the |
| 487 | // legacy pretty-printed format. Route those to the dedicated JSONL parser. |
| 488 | if (logContent && isCodexJsonlFormat(logContent.split("\n"))) { |
| 489 | return parseCodexJsonl(logContent); |
| 490 | } |
| 491 | if (!logContent) { |
| 492 | return { |
| 493 | markdown: "## 🤖 Commands and Tools\n\nNo log content provided.\n\n## 🤖 Reasoning\n\nUnable to parse reasoning from log.\n\n", |
| 494 | logEntries: [], |
| 495 | mcpFailures: [], |
| 496 | maxTurnsHit: false, |
| 497 | }; |
| 498 | } |
| 499 | |
| 500 | const lines = logContent.split("\n"); |
| 501 | const parsedData = []; // Array to collect structured data for logEntries conversion |
| 502 | |
| 503 | // Look-ahead window size for finding tool results |
| 504 | // New format has verbose debug logs, so requires larger window |
| 505 | const LOOKAHEAD_WINDOW = 50; |
| 506 | |
| 507 | let markdown = ""; |
| 508 | |
| 509 | // Extract MCP initialization information |
| 510 | const mcpInfo = extractMCPInitialization(lines); |
| 511 | if (mcpInfo.hasInfo) { |
| 512 | markdown += "## 🚀 Initialization\n\n"; |
| 513 | markdown += mcpInfo.markdown; |
| 514 | } |
| 515 | |
| 516 | // Extract error messages (e.g., model access blocked, cyber_policy_violation) |
| 517 | const errorInfo = extractCodexErrorMessages(lines); |
| 518 | if (errorInfo.hasErrors) { |
| 519 | markdown += "## ⚠️ Errors\n\n"; |
| 520 | for (const message of errorInfo.messages) { |
| 521 | markdown += `> ${message}\n\n`; |
| 522 | } |
| 523 | if (errorInfo.reconnectCount > 0) { |
| 524 | markdown += `> Reconnect attempts: ${errorInfo.reconnectCount}/${errorInfo.maxReconnects}\n\n`; |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | markdown += "## 🤖 Reasoning\n\n"; |
| 529 | |
| 530 | // Second pass: process full conversation flow with interleaved reasoning and tools |
| 531 | let inThinkingSection = false; |
| 532 | let thinkingContent = []; // Collect thinking content in chunks |
| 533 | |
| 534 | for (let i = 0; i < lines.length; i++) { |
| 535 | const line = lines[i]; |
| 536 | |
| 537 | // Skip metadata lines (including Rust debug lines) |
| 538 | if ( |
| 539 | line.includes("OpenAI Codex") || |
| 540 | line.startsWith("--------") || |
| 541 | line.includes("workdir:") || |
| 542 | line.includes("model:") || |
no test coverage detected