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;
|
if (!line.trim()) continue;
|
||||||
try {
|
try {
|
||||||
const event = JSON.parse(line) as Record<string, unknown>;
|
const event = JSON.parse(line) as Record<string, unknown>;
|
||||||
const piEvent = parsePiEvent(event, streamBuffer);
|
const piEvents = parsePiEvent(event, streamBuffer);
|
||||||
if (piEvent) {
|
for (const piEvent of piEvents) {
|
||||||
if (piEvent.type === 'delta') {
|
if (piEvent.type === 'delta') {
|
||||||
streamBuffer += piEvent.text;
|
streamBuffer += piEvent.text;
|
||||||
} else if (piEvent.type === 'text' || piEvent.type === 'tool:start') {
|
} else if (piEvent.type === 'text' || piEvent.type === 'tool:start') {
|
||||||
@@ -404,37 +404,60 @@ export async function spawnPi(
|
|||||||
return proc;
|
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;
|
const type = event.type as string;
|
||||||
|
|
||||||
// Handle response (success/failure for commands)
|
// Handle response (success/failure for commands)
|
||||||
if (type === 'response') {
|
if (type === 'response') {
|
||||||
if (event.command === 'prompt' && !event.success) {
|
if (event.command === 'prompt' && !event.success) {
|
||||||
const errorMsg = (event.error as string) ?? 'Prompt failed';
|
const errorMsg = (event.error as string) ?? 'Prompt failed';
|
||||||
return { type: 'error', message: errorMsg };
|
return [{ type: 'error', message: errorMsg }];
|
||||||
}
|
}
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'agent_start':
|
case 'agent_start':
|
||||||
// No event to emit, just resets state
|
return [];
|
||||||
return null;
|
|
||||||
|
|
||||||
case 'message_update': {
|
case 'message_update': {
|
||||||
const ame = event.assistantMessageEvent as Record<string, unknown> | undefined;
|
const ame = event.assistantMessageEvent as Record<string, unknown> | undefined;
|
||||||
if (ame?.type === 'text_delta') {
|
if (ame?.type === 'text_delta') {
|
||||||
const delta = ame.delta as string;
|
const delta = ame.delta as string;
|
||||||
return { type: 'delta', text: delta };
|
return [{ type: 'delta', text: delta }];
|
||||||
}
|
}
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'message_end': {
|
case 'message_end': {
|
||||||
|
const events: PiEvent[] = [];
|
||||||
if (currentStreamBuffer) {
|
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': {
|
case 'tool_execution_start': {
|
||||||
@@ -442,12 +465,12 @@ function parsePiEvent(event: Record<string, unknown>, currentStreamBuffer: strin
|
|||||||
const toolName = (event.toolName as string) ?? 'unknown';
|
const toolName = (event.toolName as string) ?? 'unknown';
|
||||||
const args = (event.args as Record<string, unknown>) ?? {};
|
const args = (event.args as Record<string, unknown>) ?? {};
|
||||||
|
|
||||||
return {
|
return [{
|
||||||
type: 'tool:start',
|
type: 'tool:start',
|
||||||
toolCallId,
|
toolCallId,
|
||||||
toolName,
|
toolName,
|
||||||
toolInput: args,
|
toolInput: args,
|
||||||
};
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'tool_execution_end': {
|
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 isError = (event.isError as boolean) ?? (resultObj?.isError as boolean) ?? false;
|
||||||
const output = result != null ? (typeof result === 'string' ? result : JSON.stringify(result)) : '';
|
const output = result != null ? (typeof result === 'string' ? result : JSON.stringify(result)) : '';
|
||||||
|
|
||||||
return {
|
return [{
|
||||||
type: 'tool:result',
|
type: 'tool:result',
|
||||||
toolCallId,
|
toolCallId,
|
||||||
output,
|
output,
|
||||||
isError,
|
isError,
|
||||||
};
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'agent_end': {
|
case 'agent_end': {
|
||||||
const cost: MessageCost = { inputTokens: 0, outputTokens: 0, totalUSD: 0 };
|
const cost: MessageCost = { inputTokens: 0, outputTokens: 0, totalUSD: 0 };
|
||||||
|
const events: PiEvent[] = [];
|
||||||
const messages = event.messages as Array<Record<string, unknown>> | undefined;
|
const messages = event.messages as Array<Record<string, unknown>> | undefined;
|
||||||
if (messages) {
|
if (messages) {
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const usage = msg.usage as Record<string, unknown> | undefined;
|
const usage = msg.usage as Record<string, unknown> | undefined;
|
||||||
if (!usage) continue;
|
if (usage) {
|
||||||
cost.inputTokens += (usage.input as number) ?? 0;
|
cost.inputTokens += (usage.input as number) ?? 0;
|
||||||
cost.outputTokens += (usage.output as number) ?? 0;
|
cost.outputTokens += (usage.output as number) ?? 0;
|
||||||
const usageCost = usage.cost as Record<string, unknown> | undefined;
|
const usageCost = usage.cost as Record<string, unknown> | undefined;
|
||||||
if (usageCost) cost.totalUSD += (usageCost.total as number) ?? 0;
|
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': {
|
case 'extension_ui_request':
|
||||||
// Will be handled separately
|
return [];
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user