diff --git a/src/servers/api/pi/pi-bridge.ts b/src/servers/api/pi/pi-bridge.ts index 9d9fbe20..ca2bcc9a 100644 --- a/src/servers/api/pi/pi-bridge.ts +++ b/src/servers/api/pi/pi-bridge.ts @@ -346,8 +346,8 @@ export async function spawnPi( if (!line.trim()) continue; try { const event = JSON.parse(line) as Record; - const piEvent = parsePiEvent(event, streamBuffer); - if (piEvent) { + const piEvents = parsePiEvent(event, streamBuffer); + for (const piEvent of piEvents) { if (piEvent.type === 'delta') { streamBuffer += piEvent.text; } else if (piEvent.type === 'text' || piEvent.type === 'tool:start') { @@ -404,37 +404,60 @@ export async function spawnPi( return proc; } -function parsePiEvent(event: Record, currentStreamBuffer: string): PiEvent | null { +function parseErrorMessage(raw: string): string { + try { + const parsed = JSON.parse(raw.replace(/^\d+\s*/, '')); + const inner = parsed?.error; + if (inner?.message) return inner.message; + } catch { + // not JSON + } + return raw; +} + +function extractMessageError(msg: Record): string | null { + if (msg.stopReason !== 'error') return null; + const raw = msg.errorMessage as string | undefined; + if (!raw) return null; + return parseErrorMessage(raw); +} + +function parsePiEvent(event: Record, currentStreamBuffer: string): PiEvent[] { const type = event.type as string; // Handle response (success/failure for commands) if (type === 'response') { if (event.command === 'prompt' && !event.success) { const errorMsg = (event.error as string) ?? 'Prompt failed'; - return { type: 'error', message: errorMsg }; + return [{ type: 'error', message: errorMsg }]; } - return null; + return []; } switch (type) { case 'agent_start': - // No event to emit, just resets state - return null; + return []; case 'message_update': { const ame = event.assistantMessageEvent as Record | undefined; if (ame?.type === 'text_delta') { const delta = ame.delta as string; - return { type: 'delta', text: delta }; + return [{ type: 'delta', text: delta }]; } - return null; + return []; } case 'message_end': { + const events: PiEvent[] = []; if (currentStreamBuffer) { - return { type: 'text', text: currentStreamBuffer }; + events.push({ type: 'text', text: currentStreamBuffer }); } - return null; + const msg = event.message as Record | undefined; + if (msg) { + const errorText = extractMessageError(msg); + if (errorText) events.push({ type: 'error', message: errorText }); + } + return events; } case 'tool_execution_start': { @@ -442,12 +465,12 @@ function parsePiEvent(event: Record, currentStreamBuffer: strin const toolName = (event.toolName as string) ?? 'unknown'; const args = (event.args as Record) ?? {}; - return { + return [{ type: 'tool:start', toolCallId, toolName, toolInput: args, - }; + }]; } case 'tool_execution_end': { @@ -466,37 +489,40 @@ function parsePiEvent(event: Record, currentStreamBuffer: strin const isError = (event.isError as boolean) ?? (resultObj?.isError as boolean) ?? false; const output = result != null ? (typeof result === 'string' ? result : JSON.stringify(result)) : ''; - return { + return [{ type: 'tool:result', toolCallId, output, isError, - }; + }]; } case 'agent_end': { const cost: MessageCost = { inputTokens: 0, outputTokens: 0, totalUSD: 0 }; + const events: PiEvent[] = []; const messages = event.messages as Array> | undefined; if (messages) { for (const msg of messages) { const usage = msg.usage as Record | undefined; - if (!usage) continue; - cost.inputTokens += (usage.input as number) ?? 0; - cost.outputTokens += (usage.output as number) ?? 0; - const usageCost = usage.cost as Record | undefined; - if (usageCost) cost.totalUSD += (usageCost.total as number) ?? 0; + if (usage) { + cost.inputTokens += (usage.input as number) ?? 0; + cost.outputTokens += (usage.output as number) ?? 0; + const usageCost = usage.cost as Record | undefined; + if (usageCost) cost.totalUSD += (usageCost.total as number) ?? 0; + } + const errorText = extractMessageError(msg); + if (errorText) events.push({ type: 'error', message: errorText }); } } - return { type: 'result', cost }; + events.push({ type: 'result', cost }); + return events; } - case 'extension_ui_request': { - // Will be handled separately - return null; - } + case 'extension_ui_request': + return []; default: - return null; + return []; } }