surface pi process errors to web ui
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user