surface pi process errors to web ui

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 20:47:32 +00:00
co-authored by Claude Opus 4.6
parent 25e8aaaea6
commit c972553f22
+52 -26
View File
@@ -346,8 +346,8 @@ export async function spawnPi(
if (!line.trim()) continue;
try {
const event = JSON.parse(line) as Record<string, unknown>;
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<string, unknown>, 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, unknown>): 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<string, unknown>, 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<string, unknown> | 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<string, unknown> | 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<string, unknown>, currentStreamBuffer: strin
const toolName = (event.toolName as string) ?? 'unknown';
const args = (event.args as Record<string, unknown>) ?? {};
return {
return [{
type: 'tool:start',
toolCallId,
toolName,
toolInput: args,
};
}];
}
case 'tool_execution_end': {
@@ -466,37 +489,40 @@ function parsePiEvent(event: Record<string, unknown>, 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<Record<string, unknown>> | undefined;
if (messages) {
for (const msg of messages) {
const usage = msg.usage as Record<string, unknown> | undefined;
if (!usage) continue;
cost.inputTokens += (usage.input as number) ?? 0;
cost.outputTokens += (usage.output as number) ?? 0;
const usageCost = usage.cost as Record<string, unknown> | 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<string, unknown> | 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 [];
}
}