* Categorizes errors for better error handling (used for event emission)
(error: unknown)
| 3449 | * Categorizes errors for better error handling (used for event emission) |
| 3450 | */ |
| 3451 | private categorizeError(error: unknown): StreamErrorType { |
| 3452 | if (error instanceof StreamTruncatedError) { |
| 3453 | return "stream_truncated"; |
| 3454 | } |
| 3455 | |
| 3456 | // Use AI SDK error type guards first |
| 3457 | if (LoadAPIKeyError.isInstance(error)) { |
| 3458 | return "authentication"; |
| 3459 | } |
| 3460 | if (APICallError.isInstance(error)) { |
| 3461 | if (error.statusCode === 401) return "authentication"; |
| 3462 | // 402 (Payment Required) is used by mux gateway for billing/credits issues |
| 3463 | // (e.g. "Insufficient balance. Please add credits to continue."). |
| 3464 | // Treat as non-retryable quota. Some providers also encode quota failures as |
| 3465 | // 429, so classify 429 by payload intent instead of status code alone. |
| 3466 | if (error.statusCode === 402) return "quota"; |
| 3467 | if (error.statusCode === 429) { |
| 3468 | return classify429Capacity({ |
| 3469 | message: error.message, |
| 3470 | data: error.data, |
| 3471 | responseBody: error.responseBody, |
| 3472 | }); |
| 3473 | } |
| 3474 | if (error.statusCode && error.statusCode >= 500) return "server_error"; |
| 3475 | |
| 3476 | // Check for model_not_found errors (OpenAI and Anthropic) |
| 3477 | // Type guard for error data structure |
| 3478 | const hasErrorProperty = ( |
| 3479 | data: unknown |
| 3480 | ): data is { error: { code?: string; type?: string } } => { |
| 3481 | return ( |
| 3482 | typeof data === "object" && |
| 3483 | data !== null && |
| 3484 | "error" in data && |
| 3485 | typeof data.error === "object" && |
| 3486 | data.error !== null |
| 3487 | ); |
| 3488 | }; |
| 3489 | |
| 3490 | // OpenAI: 400 with error.code === 'model_not_found' |
| 3491 | const isOpenAIModelError = |
| 3492 | error.statusCode === 400 && |
| 3493 | hasErrorProperty(error.data) && |
| 3494 | error.data.error.code === "model_not_found"; |
| 3495 | |
| 3496 | // Anthropic: 404 with error.type === 'not_found_error' |
| 3497 | const isAnthropicModelError = |
| 3498 | error.statusCode === 404 && |
| 3499 | hasErrorProperty(error.data) && |
| 3500 | error.data.error.type === "not_found_error"; |
| 3501 | |
| 3502 | if (isOpenAIModelError || isAnthropicModelError) { |
| 3503 | return "model_not_found"; |
| 3504 | } |
| 3505 | |
| 3506 | // Check for context exceeded errors (Anthropic + OpenAI-compatible / Copilot) |
| 3507 | const msgLower = error.message.toLowerCase(); |
| 3508 |
no test coverage detected