Spaces:
Paused
Paused
| /** | |
| * Error handling utilities for standardized error classification | |
| * | |
| * Provides utilities for: | |
| * - Detecting abort/cancellation errors | |
| * - Detecting authentication errors | |
| * - Detecting rate limit and quota exhaustion errors | |
| * - Classifying errors by type | |
| * - Generating user-friendly error messages | |
| */ | |
| import type { ErrorType, ErrorInfo } from '@automaker/types'; | |
| /** | |
| * Check if an error is an abort/cancellation error | |
| * | |
| * @param error - The error to check | |
| * @returns True if the error is an abort error | |
| */ | |
| export function isAbortError(error: unknown): boolean { | |
| return error instanceof Error && (error.name === 'AbortError' || error.message.includes('abort')); | |
| } | |
| /** | |
| * Check if an error is a user-initiated cancellation | |
| * | |
| * @param errorMessage - The error message to check | |
| * @returns True if the error is a user-initiated cancellation | |
| */ | |
| export function isCancellationError(errorMessage: string): boolean { | |
| const lowerMessage = errorMessage.toLowerCase(); | |
| return ( | |
| lowerMessage.includes('cancelled') || | |
| lowerMessage.includes('canceled') || | |
| lowerMessage.includes('stopped') || | |
| lowerMessage.includes('aborted') | |
| ); | |
| } | |
| /** | |
| * Check if an error is an authentication/API key error | |
| * | |
| * @param errorMessage - The error message to check | |
| * @returns True if the error is authentication-related | |
| */ | |
| export function isAuthenticationError(errorMessage: string): boolean { | |
| return ( | |
| errorMessage.includes('Authentication failed') || | |
| errorMessage.includes('Invalid API key') || | |
| errorMessage.includes('authentication_failed') || | |
| errorMessage.includes('Fix external API key') | |
| ); | |
| } | |
| /** | |
| * Check if an error is a rate limit error (429 Too Many Requests) | |
| * | |
| * @param error - The error to check | |
| * @returns True if the error is a rate limit error | |
| */ | |
| export function isRateLimitError(error: unknown): boolean { | |
| const message = error instanceof Error ? error.message : String(error || ''); | |
| return message.includes('429') || message.includes('rate_limit'); | |
| } | |
| /** | |
| * Check if an error indicates quota/usage exhaustion | |
| * This includes session limits, weekly limits, credit/billing issues, and overloaded errors | |
| * | |
| * @param error - The error to check | |
| * @returns True if the error indicates quota exhaustion | |
| */ | |
| export function isQuotaExhaustedError(error: unknown): boolean { | |
| const message = error instanceof Error ? error.message : String(error || ''); | |
| const lowerMessage = message.toLowerCase(); | |
| // Check for overloaded/capacity errors | |
| if ( | |
| lowerMessage.includes('overloaded') || | |
| lowerMessage.includes('overloaded_error') || | |
| lowerMessage.includes('capacity') | |
| ) { | |
| return true; | |
| } | |
| // Check for usage/quota limit patterns | |
| if ( | |
| lowerMessage.includes('limit reached') || | |
| lowerMessage.includes('usage limit') || | |
| lowerMessage.includes('quota exceeded') || | |
| lowerMessage.includes('quota_exceeded') || | |
| lowerMessage.includes('session limit') || | |
| lowerMessage.includes('weekly limit') || | |
| lowerMessage.includes('monthly limit') | |
| ) { | |
| return true; | |
| } | |
| // Check for billing/credit issues | |
| if ( | |
| lowerMessage.includes('credit balance') || | |
| lowerMessage.includes('insufficient credits') || | |
| lowerMessage.includes('insufficient balance') || | |
| lowerMessage.includes('no credits') || | |
| lowerMessage.includes('out of credits') || | |
| lowerMessage.includes('billing') || | |
| lowerMessage.includes('payment required') | |
| ) { | |
| return true; | |
| } | |
| // Check for upgrade prompts (often indicates limit reached) | |
| if (lowerMessage.includes('/upgrade') || lowerMessage.includes('extra-usage')) { | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Check if an error indicates a model-not-found or model access issue | |
| * | |
| * @param error - The error to check | |
| * @returns True if the error indicates the model doesn't exist or user lacks access | |
| */ | |
| export function isModelNotFoundError(error: unknown): boolean { | |
| const message = error instanceof Error ? error.message : String(error || ''); | |
| const lowerMessage = message.toLowerCase(); | |
| return ( | |
| lowerMessage.includes('does not exist or you do not have access') || | |
| lowerMessage.includes('model_not_found') || | |
| lowerMessage.includes('invalid_model') || | |
| (lowerMessage.includes('model') && | |
| (lowerMessage.includes('does not exist') || lowerMessage.includes('not found'))) | |
| ); | |
| } | |
| /** | |
| * Check if an error indicates a stream disconnection | |
| * | |
| * @param error - The error to check | |
| * @returns True if the error indicates the stream was disconnected | |
| */ | |
| export function isStreamDisconnectedError(error: unknown): boolean { | |
| const message = error instanceof Error ? error.message : String(error || ''); | |
| const lowerMessage = message.toLowerCase(); | |
| return ( | |
| lowerMessage.includes('stream disconnected') || | |
| lowerMessage.includes('stream ended') || | |
| lowerMessage.includes('connection reset') || | |
| lowerMessage.includes('socket hang up') || | |
| lowerMessage.includes('econnreset') | |
| ); | |
| } | |
| /** | |
| * Extract retry-after duration from rate limit error | |
| * | |
| * @param error - The error to extract retry-after from | |
| * @returns Number of seconds to wait, or undefined if not found | |
| */ | |
| export function extractRetryAfter(error: unknown): number | undefined { | |
| const message = error instanceof Error ? error.message : String(error || ''); | |
| // Try to extract from Retry-After header format | |
| const retryMatch = message.match(/retry[_-]?after[:\s]+(\d+)/i); | |
| if (retryMatch) { | |
| return parseInt(retryMatch[1], 10); | |
| } | |
| // Try to extract from error message patterns | |
| const waitMatch = message.match(/wait[:\s]+(\d+)\s*(?:second|sec|s)/i); | |
| if (waitMatch) { | |
| return parseInt(waitMatch[1], 10); | |
| } | |
| return undefined; | |
| } | |
| /** | |
| * Classify an error into a specific type | |
| * | |
| * @param error - The error to classify | |
| * @returns Classified error information | |
| */ | |
| export function classifyError(error: unknown): ErrorInfo { | |
| const message = error instanceof Error ? error.message : String(error || 'Unknown error'); | |
| const isAbort = isAbortError(error); | |
| const isAuth = isAuthenticationError(message); | |
| const isCancellation = isCancellationError(message); | |
| const isRateLimit = isRateLimitError(error); | |
| const isQuotaExhausted = isQuotaExhaustedError(error); | |
| const isModelNotFound = isModelNotFoundError(error); | |
| const isStreamDisconnected = isStreamDisconnectedError(error); | |
| const retryAfter = isRateLimit ? (extractRetryAfter(error) ?? 60) : undefined; | |
| let type: ErrorType; | |
| if (isAuth) { | |
| type = 'authentication'; | |
| } else if (isModelNotFound) { | |
| type = 'model_not_found'; | |
| } else if (isStreamDisconnected) { | |
| type = 'stream_disconnected'; | |
| } else if (isQuotaExhausted) { | |
| // Quota exhaustion takes priority over rate limit since it's more specific | |
| type = 'quota_exhausted'; | |
| } else if (isRateLimit) { | |
| type = 'rate_limit'; | |
| } else if (isAbort) { | |
| type = 'abort'; | |
| } else if (isCancellation) { | |
| type = 'cancellation'; | |
| } else if (error instanceof Error) { | |
| type = 'execution'; | |
| } else { | |
| type = 'unknown'; | |
| } | |
| return { | |
| type, | |
| message, | |
| isAbort, | |
| isAuth, | |
| isCancellation, | |
| isRateLimit, | |
| isQuotaExhausted, | |
| isModelNotFound, | |
| isStreamDisconnected, | |
| retryAfter, | |
| originalError: error, | |
| }; | |
| } | |
| /** | |
| * Get a user-friendly error message | |
| * | |
| * @param error - The error to convert | |
| * @returns User-friendly error message | |
| */ | |
| export function getUserFriendlyErrorMessage(error: unknown): string { | |
| const info = classifyError(error); | |
| if (info.isAbort) { | |
| return 'Operation was cancelled'; | |
| } | |
| if (info.isAuth) { | |
| return 'Authentication failed. Please check your API key.'; | |
| } | |
| if (info.isModelNotFound) { | |
| return `Model not available: ${info.message}\n\nSome models require specific subscription plans or authentication methods. Try authenticating with 'codex login' or switch to a different model.`; | |
| } | |
| if (info.isStreamDisconnected) { | |
| return `Connection interrupted: ${info.message}\n\nThe stream was disconnected before the response could complete. This may be caused by network issues, model access restrictions, or server timeouts. Try again or switch to a different model.`; | |
| } | |
| if (info.isQuotaExhausted) { | |
| return 'Usage limit reached. Auto Mode has been paused. Please wait for your quota to reset or upgrade your plan.'; | |
| } | |
| if (info.isRateLimit) { | |
| const retryMsg = info.retryAfter | |
| ? ` Please wait ${info.retryAfter} seconds before retrying.` | |
| : ' Please reduce concurrency or wait before retrying.'; | |
| return `Rate limit exceeded (429).${retryMsg}`; | |
| } | |
| return info.message; | |
| } | |
| /** | |
| * Extract error message from an unknown error value | |
| * | |
| * Simple utility for getting a string error message from any error type. | |
| * Returns the error's message property if it's an Error, otherwise | |
| * converts to string. Used throughout the codebase for consistent | |
| * error message extraction. | |
| * | |
| * @param error - The error value (Error object, string, or unknown) | |
| * @returns Error message string | |
| * | |
| * @example | |
| * ```typescript | |
| * try { | |
| * throw new Error("Something went wrong"); | |
| * } catch (error) { | |
| * const message = getErrorMessage(error); // "Something went wrong" | |
| * } | |
| * ``` | |
| */ | |
| export function getErrorMessage(error: unknown): string { | |
| return error instanceof Error ? error.message : 'Unknown error'; | |
| } | |
| /** | |
| * Log an error with a context message to stderr. | |
| * | |
| * Convenience utility for consistent error logging throughout the codebase. | |
| * Outputs a formatted error line to stderr with an ❌ prefix and the context. | |
| * | |
| * @param error - The error value to log | |
| * @param context - Descriptive context message indicating where/why the error occurred | |
| * | |
| * @example | |
| * ```typescript | |
| * try { | |
| * await someOperation(); | |
| * } catch (error) { | |
| * logError(error, 'Failed to perform some operation'); | |
| * } | |
| * ``` | |
| */ | |
| export function logError(error: unknown, context: string): void { | |
| console.error(`❌ ${context}:`, error); | |
| } | |