(raw: unknown)
| 116 | |
| 117 | /** Normalizes a raw Pi/SDK event object into a {@link PiEvent}. */ |
| 118 | export function normalizePiEvent(raw: unknown): PiEvent | null { |
| 119 | const ev = asRecord(raw) |
| 120 | if (!ev) return null |
| 121 | |
| 122 | switch (asString(ev.type)) { |
| 123 | case 'message_update': { |
| 124 | const assistantEvent = asRecord(ev.assistantMessageEvent) |
| 125 | const deltaType = assistantEvent ? asString(assistantEvent.type) : '' |
| 126 | const delta = assistantEvent ? asString(assistantEvent.delta) : '' |
| 127 | if (deltaType === 'text_delta') return { type: 'text', text: delta } |
| 128 | if (deltaType === 'thinking_delta') return { type: 'thinking', text: delta } |
| 129 | return { type: 'other' } |
| 130 | } |
| 131 | case 'tool_execution_start': |
| 132 | return { type: 'tool_start', toolName: asString(ev.toolName) } |
| 133 | case 'tool_execution_end': |
| 134 | return { type: 'tool_end', toolName: asString(ev.toolName), isError: ev.isError === true } |
| 135 | case 'turn_end': { |
| 136 | const usage = extractUsage(ev) |
| 137 | return usage ? { type: 'usage', ...usage } : { type: 'other' } |
| 138 | } |
| 139 | case 'agent_end': |
| 140 | return { type: 'final' } |
| 141 | case 'error': |
| 142 | return { |
| 143 | type: 'error', |
| 144 | message: asString(ev.error) || asString(ev.message) || 'Pi run failed', |
| 145 | } |
| 146 | default: |
| 147 | return { type: 'other' } |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | /** Parses one `pi --mode json` stdout line into a {@link PiEvent}. */ |
| 152 | export function parseJsonLine(line: string): PiEvent | null { |
no test coverage detected