Spaces:
Paused
Paused
| /** | |
| * Centralized Error Handling | |
| * Standardizes error handling across the codebase | |
| */ | |
| import pino from 'pino' | |
| const logger = pino({ level: process.env.LOG_LEVEL || 'info' }) | |
| /** | |
| * Error severity levels | |
| */ | |
| export type ErrorSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info' | |
| /** | |
| * Standardized error structure | |
| */ | |
| export interface PRIXError { | |
| message: string | |
| severity: ErrorSeverity | |
| code: string | |
| context?: Record<string, any> | |
| cause?: Error | |
| } | |
| /** | |
| * Error codes for different failure modes | |
| */ | |
| export const ErrorCodes = { | |
| // AI/LLM errors | |
| AI_RATE_LIMIT: 'AI_RATE_LIMIT', | |
| AI_CONTEXT_TOO_LARGE: 'AI_CONTEXT_TOO_LARGE', | |
| AI_TIMEOUT: 'AI_TIMEOUT', | |
| AI_AUTHENTICATION: 'AI_AUTHENTICATION', | |
| AI_SERVICE_UNAVAILABLE: 'AI_SERVICE_UNAVAILABLE', | |
| // GitHub API errors | |
| GITHUB_RATE_LIMIT: 'GITHUB_RATE_LIMIT', | |
| GITHUB_AUTHENTICATION: 'GITHUB_AUTHENTICATION', | |
| GITHUB_NOT_FOUND: 'GITHUB_NOT_FOUND', | |
| GITHUB_CONFLICT: 'GITHUB_CONFLICT', | |
| GITHUB_TIMEOUT: 'GITHUB_TIMEOUT', | |
| // File system errors | |
| FS_READ_ERROR: 'FS_READ_ERROR', | |
| FS_WRITE_ERROR: 'FS_WRITE_ERROR', | |
| FS_NOT_FOUND: 'FS_NOT_FOUND', | |
| // Parse/analysis errors | |
| PARSE_ERROR: 'PARSE_ERROR', | |
| AST_ANALYSIS_ERROR: 'AST_ANALYSIS_ERROR', | |
| PATCH_PARSE_ERROR: 'PATCH_PARSE_ERROR', | |
| // Context errors | |
| CONTEXT_MISSING: 'CONTEXT_MISSING', | |
| INVALID_PAYLOAD: 'INVALID_PAYLOAD', | |
| // Circuit breaker | |
| CIRCUIT_OPEN: 'CIRCUIT_OPEN', | |
| // Unknown | |
| UNKNOWN: 'UNKNOWN' | |
| } as const | |
| /** | |
| * Create a standardized error | |
| */ | |
| export const createError = ( | |
| message: string, | |
| severity: ErrorSeverity, | |
| code: string, | |
| context?: Record<string, any>, | |
| cause?: Error | |
| ): PRIXError => ({ | |
| message, | |
| severity, | |
| code, | |
| context, | |
| cause | |
| }) | |
| /** | |
| * Log error with appropriate level based on severity | |
| */ | |
| export const logError = (error: PRIXError): void => { | |
| const logData = { | |
| code: error.code, | |
| severity: error.severity, | |
| context: error.context, | |
| cause: error.cause?.message | |
| } | |
| switch (error.severity) { | |
| case 'critical': | |
| logger.fatal(logData, error.message) | |
| break | |
| case 'high': | |
| logger.error(logData, error.message) | |
| break | |
| case 'medium': | |
| logger.warn(logData, error.message) | |
| break | |
| case 'low': | |
| logger.info(logData, error.message) | |
| break | |
| case 'info': | |
| logger.debug(logData, error.message) | |
| break | |
| } | |
| } | |
| /** | |
| * Safe wrapper for async functions | |
| * Standardizes error handling | |
| */ | |
| export const safeExecute = async <T>( | |
| fn: () => Promise<T>, | |
| errorCode: string, | |
| severity: ErrorSeverity = 'medium', | |
| context?: Record<string, any>, | |
| fallback?: T | |
| ): Promise<T | undefined> => { | |
| try { | |
| return await fn() | |
| } catch (error) { | |
| const prixError = createError( | |
| error instanceof Error ? error.message : String(error), | |
| severity, | |
| errorCode, | |
| context, | |
| error instanceof Error ? error : undefined | |
| ) | |
| logError(prixError) | |
| return fallback | |
| } | |
| } | |
| /** | |
| * Safe wrapper for sync functions | |
| */ | |
| export const safeExecuteSync = <T>( | |
| fn: () => T, | |
| errorCode: string, | |
| severity: ErrorSeverity = 'medium', | |
| context?: Record<string, any>, | |
| fallback?: T | |
| ): T | undefined => { | |
| try { | |
| return fn() | |
| } catch (error) { | |
| const prixError = createError( | |
| error instanceof Error ? error.message : String(error), | |
| severity, | |
| errorCode, | |
| context, | |
| error instanceof Error ? error : undefined | |
| ) | |
| logError(prixError) | |
| return fallback | |
| } | |
| } | |
| /** | |
| * Classify error from external service | |
| */ | |
| export const classifyError = (error: any): PRIXError => { | |
| // AI API errors | |
| if (error?.status === 429) { | |
| return createError('AI rate limit exceeded', 'high', ErrorCodes.AI_RATE_LIMIT, { status: 429 }) | |
| } | |
| if (error?.status === 413) { | |
| return createError('AI context too large', 'high', ErrorCodes.AI_CONTEXT_TOO_LARGE, { status: 413 }) | |
| } | |
| if (error?.code === 'ETIMEDOUT' || error?.code === 'ECONNABORTED') { | |
| return createError('AI timeout', 'medium', ErrorCodes.AI_TIMEOUT, { code: error.code }) | |
| } | |
| if (error?.status === 401) { | |
| return createError('AI authentication failed', 'critical', ErrorCodes.AI_AUTHENTICATION, { status: 401 }) | |
| } | |
| if (error?.status === 503 || error?.status === 502) { | |
| return createError('AI service unavailable', 'high', ErrorCodes.AI_SERVICE_UNAVAILABLE, { status: error.status }) | |
| } | |
| // GitHub API errors | |
| if (error?.status === 403 && error?.message?.includes('rate limit')) { | |
| return createError('GitHub rate limit exceeded', 'high', ErrorCodes.GITHUB_RATE_LIMIT, { status: 403 }) | |
| } | |
| if (error?.status === 401) { | |
| return createError('GitHub authentication failed', 'critical', ErrorCodes.GITHUB_AUTHENTICATION, { status: 401 }) | |
| } | |
| if (error?.status === 404) { | |
| return createError('GitHub resource not found', 'medium', ErrorCodes.GITHUB_NOT_FOUND, { status: 404 }) | |
| } | |
| if (error?.status === 409) { | |
| return createError('GitHub conflict', 'medium', ErrorCodes.GITHUB_CONFLICT, { status: 409 }) | |
| } | |
| // Default | |
| return createError( | |
| error?.message || 'Unknown error', | |
| 'medium', | |
| ErrorCodes.UNKNOWN, | |
| { originalError: error } | |
| ) | |
| } | |
| /** | |
| * Retry configuration | |
| */ | |
| export interface RetryConfig { | |
| maxRetries: number | |
| baseDelay: number | |
| maxDelay: number | |
| retryableCodes: string[] | |
| } | |
| /** | |
| * Default retry configuration | |
| */ | |
| export const defaultRetryConfig: RetryConfig = { | |
| maxRetries: 3, | |
| baseDelay: 1000, | |
| maxDelay: 30000, | |
| retryableCodes: [ | |
| ErrorCodes.AI_RATE_LIMIT, | |
| ErrorCodes.AI_TIMEOUT, | |
| ErrorCodes.AI_SERVICE_UNAVAILABLE, | |
| ErrorCodes.GITHUB_RATE_LIMIT, | |
| ErrorCodes.GITHUB_TIMEOUT | |
| ] | |
| } | |
| /** | |
| * Check if error should be retried | |
| */ | |
| export const shouldRetry = (error: PRIXError, config: RetryConfig = defaultRetryConfig): boolean => { | |
| return config.retryableCodes.includes(error.code) | |
| } | |
| /** | |
| * Calculate delay for retry with exponential backoff | |
| */ | |
| export const calculateRetryDelay = (attempt: number, config: RetryConfig = defaultRetryConfig): number => { | |
| const delay = Math.min( | |
| config.baseDelay * Math.pow(2, attempt), | |
| config.maxDelay | |
| ) | |
| // Add jitter to prevent thundering herd | |
| return delay + Math.random() * 1000 | |
| } | |