ParseLineCodex parses a single line of Codex exec --json JSONL output and returns an Event. If the line cannot be parsed or is not relevant, it returns nil.
(line string)
| 32 | // ParseLineCodex parses a single line of Codex exec --json JSONL output and returns an Event. |
| 33 | // If the line cannot be parsed or is not relevant, it returns nil. |
| 34 | func ParseLineCodex(line string) *Event { |
| 35 | line = strings.TrimSpace(line) |
| 36 | if line == "" { |
| 37 | return nil |
| 38 | } |
| 39 | |
| 40 | var ev codexEvent |
| 41 | if err := json.Unmarshal([]byte(line), &ev); err != nil { |
| 42 | return nil |
| 43 | } |
| 44 | |
| 45 | switch ev.Type { |
| 46 | case "thread.started", "turn.started": |
| 47 | return &Event{Type: EventIterationStart} |
| 48 | |
| 49 | case "turn.failed": |
| 50 | msg := "" |
| 51 | if ev.Error != nil { |
| 52 | msg = ev.Error.Message |
| 53 | } |
| 54 | return &Event{Type: EventError, Err: errors.New(msg)} |
| 55 | |
| 56 | case "error": |
| 57 | msg := ev.Message |
| 58 | if msg == "" && ev.Error != nil { |
| 59 | msg = ev.Error.Message |
| 60 | } |
| 61 | if msg == "" { |
| 62 | msg = "unknown error" |
| 63 | } |
| 64 | return &Event{Type: EventError, Err: errors.New(msg)} |
| 65 | |
| 66 | case "item.started": |
| 67 | if ev.Item == nil { |
| 68 | return nil |
| 69 | } |
| 70 | switch ev.Item.Type { |
| 71 | case "command_execution": |
| 72 | return &Event{ |
| 73 | Type: EventToolStart, |
| 74 | Tool: ev.Item.Command, |
| 75 | } |
| 76 | case "mcp_tool_call": |
| 77 | toolName := ev.Item.Tool |
| 78 | if ev.Item.Server != "" { |
| 79 | toolName = ev.Item.Server + "/" + ev.Item.Tool |
| 80 | } |
| 81 | return &Event{ |
| 82 | Type: EventToolStart, |
| 83 | Tool: toolName, |
| 84 | } |
| 85 | } |
| 86 | return nil |
| 87 | |
| 88 | case "item.completed": |
| 89 | if ev.Item == nil { |
| 90 | return nil |
| 91 | } |
no outgoing calls