export function handleStreamError(error: unknown): void { if (!(error instanceof Error)) { throw error; } // Abort errors are expected during cancellation - don't re-throw. // "Operation aborted" comes from the SDK's internal write() when the // subprocess is killed during handleControlRequest. if (error.name === 'AbortError' || error.message.includes('aborted') || error.message.includes('abort') || error.message === 'Operation aborted') { return; } if (error.message.includes('error_max_turns') || error.message.includes('max_turns')) { throw new Error('Unexpected conversation limit reached. This should not happen with unlimited turns enabled.'); } if (error.message.includes('ENOENT') || error.message.includes('spawn claude-code')) { throw new Error('Claude Agent SDK not found. Please install it with: npm install @anthropic-ai/claude-agent-sdk'); } if (error.message.includes('API key')) { throw new Error('Claude Agent SDK requires an Anthropic API key. Please set ANTHROPIC_API_KEY environment variable.'); } // Extract detailed error info from API errors (Anthropic SDK APIError has .status, .error) const enriched = extractDetailedError(error); throw new Error(enriched); } /** * Extract detailed error info from an Error object. * Handles Anthropic SDK's APIError which has .status and .error.message fields. * * Does NOT prepend error class names (e.g. "APIError", "UnknownError") — * those are SDK implementation details, not useful for the end user. * The stream-manager's normalizeErrorText() also strips them as a safety net. */ function extractDetailedError(error: Error): string { const err = error as Record; // Use the message directly, stripping any redundant leading "Error: " prefix let message = (err.message || '').replace(/^Error:\s*/, ''); if (!message) { message = err.name && err.name !== 'Error' ? String(err.name) : 'Unknown error'; } // Append status code if available (Anthropic APIError has .status) if (err.status && !message.includes(String(err.status))) { message += ` (status ${err.status})`; } // Append nested error body (Anthropic APIError has .error.message) if (err.error?.message && !message.includes(err.error.message)) { message += ` - ${err.error.message}`; } return message || error.message || 'Unknown error'; }