UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

522 lines 20.8 kB
/** * Comprehensive Error Handling System * * Provides structured error handling, logging, recovery mechanisms, * and user-friendly error reporting across the entire application. */ import { logger } from '../logger.js'; import chalk from 'chalk'; // Error severity levels export var ErrorSeverity; (function (ErrorSeverity) { ErrorSeverity["LOW"] = "low"; ErrorSeverity["MEDIUM"] = "medium"; ErrorSeverity["HIGH"] = "high"; ErrorSeverity["CRITICAL"] = "critical"; })(ErrorSeverity || (ErrorSeverity = {})); // Error categories export var ErrorCategory; (function (ErrorCategory) { ErrorCategory["VALIDATION"] = "validation"; ErrorCategory["AUTHENTICATION"] = "authentication"; ErrorCategory["AUTHORIZATION"] = "authorization"; ErrorCategory["NETWORK"] = "network"; ErrorCategory["FILE_SYSTEM"] = "file_system"; ErrorCategory["MODEL"] = "model"; ErrorCategory["TOOL_EXECUTION"] = "tool_execution"; ErrorCategory["CONFIGURATION"] = "configuration"; ErrorCategory["SYSTEM"] = "system"; ErrorCategory["USER_INPUT"] = "user_input"; ErrorCategory["EXTERNAL_API"] = "external_api"; ErrorCategory["MCP_SERVICE"] = "mcp_service"; ErrorCategory["DATABASE"] = "database"; ErrorCategory["TIMEOUT"] = "timeout"; ErrorCategory["RATE_LIMIT"] = "rate_limit"; ErrorCategory["NOT_FOUND"] = "not_found"; ErrorCategory["CONFLICT"] = "conflict"; ErrorCategory["SECURITY"] = "security"; ErrorCategory["INFRASTRUCTURE"] = "infrastructure"; ErrorCategory["APPLICATION"] = "application"; })(ErrorCategory || (ErrorCategory = {})); /** * Error factory for creating structured errors */ export class ErrorFactory { static errorCounter = 0; static createError(message, category, severity = ErrorSeverity.MEDIUM, options = {}) { const id = `ERR_${Date.now()}_${++this.errorCounter}`; return { id, message, category, severity, timestamp: Date.now(), context: options.context, stackTrace: options.originalError?.stack, userMessage: options.userMessage || this.generateUserMessage(message, category), suggestedActions: options.suggestedActions || this.generateSuggestedActions(category), recoverable: options.recoverable ?? this.isRecoverable(category, severity), retryable: options.retryable ?? this.isRetryable(category), metadata: options.metadata, }; } static generateUserMessage(message, category) { const categoryMessages = { [ErrorCategory.VALIDATION]: 'Invalid input provided', [ErrorCategory.AUTHENTICATION]: 'Authentication failed', [ErrorCategory.AUTHORIZATION]: 'Access denied', [ErrorCategory.NETWORK]: 'Network connection issue', [ErrorCategory.FILE_SYSTEM]: 'File operation failed', [ErrorCategory.MODEL]: 'AI model processing error', [ErrorCategory.TOOL_EXECUTION]: 'Tool execution failed', [ErrorCategory.CONFIGURATION]: 'Configuration error', [ErrorCategory.SYSTEM]: 'System error occurred', [ErrorCategory.USER_INPUT]: 'Invalid user input', [ErrorCategory.EXTERNAL_API]: 'External service unavailable', [ErrorCategory.MCP_SERVICE]: 'MCP service error', [ErrorCategory.DATABASE]: 'Database operation failed', [ErrorCategory.TIMEOUT]: 'Operation timed out', [ErrorCategory.RATE_LIMIT]: 'Rate limit exceeded', [ErrorCategory.NOT_FOUND]: 'Resource not found', [ErrorCategory.CONFLICT]: 'Resource conflict detected', [ErrorCategory.SECURITY]: 'Security violation detected', [ErrorCategory.INFRASTRUCTURE]: 'Infrastructure error', [ErrorCategory.APPLICATION]: 'Application error', }; return categoryMessages[category] || 'An error occurred'; } static generateSuggestedActions(category) { const categoryActions = { [ErrorCategory.VALIDATION]: [ 'Check input format and required fields', 'Review parameter types and constraints', ], [ErrorCategory.AUTHENTICATION]: [ 'Verify API keys and credentials', 'Check authentication configuration', ], [ErrorCategory.AUTHORIZATION]: ['Verify user permissions', 'Check access rights and roles'], [ErrorCategory.NETWORK]: [ 'Check internet connection', 'Verify endpoint availability', 'Try again in a moment', ], [ErrorCategory.FILE_SYSTEM]: [ 'Check file permissions', 'Verify file path exists', 'Ensure sufficient disk space', ], [ErrorCategory.MODEL]: [ 'Check if AI model is running', 'Verify model configuration', 'Try with a different model', ], [ErrorCategory.TOOL_EXECUTION]: [ 'Review tool parameters', 'Check tool availability', 'Try alternative tools', ], [ErrorCategory.CONFIGURATION]: [ 'Review configuration settings', 'Check environment variables', 'Reset to default configuration', ], [ErrorCategory.SYSTEM]: [ 'Restart the application', 'Check system resources', 'Contact support if issue persists', ], [ErrorCategory.USER_INPUT]: [ 'Review input format', 'Check for special characters', 'Try simplified input', ], [ErrorCategory.EXTERNAL_API]: [ 'Check service status', 'Verify API credentials', 'Try again later', ], [ErrorCategory.MCP_SERVICE]: [ 'Check MCP server status', 'Verify service configuration', 'Try fallback options', ], [ErrorCategory.DATABASE]: [ 'Check database connection', 'Verify database credentials', 'Try reconnecting to database', ], [ErrorCategory.TIMEOUT]: [ 'Increase timeout limit', 'Check network connectivity', 'Try again later', ], [ErrorCategory.RATE_LIMIT]: [ 'Wait before retrying', 'Reduce request frequency', 'Check rate limit settings', ], [ErrorCategory.NOT_FOUND]: [ 'Check resource path', 'Verify resource exists', 'Update resource references', ], [ErrorCategory.CONFLICT]: [ 'Resolve resource conflicts', 'Check for duplicate entries', 'Update resource state', ], [ErrorCategory.SECURITY]: [ 'Review security permissions', 'Check authentication status', 'Contact security administrator', ], [ErrorCategory.INFRASTRUCTURE]: [ 'Check system health', 'Verify infrastructure status', 'Contact system administrator', ], [ErrorCategory.APPLICATION]: [ 'Restart application', 'Check application logs', 'Update application configuration', ], }; return categoryActions[category] || ['Try again', 'Contact support if issue persists']; } static isRecoverable(category, severity) { if (severity === ErrorSeverity.CRITICAL) return false; const recoverableCategories = [ ErrorCategory.NETWORK, ErrorCategory.EXTERNAL_API, ErrorCategory.MCP_SERVICE, ErrorCategory.TOOL_EXECUTION, ErrorCategory.MODEL, ]; return recoverableCategories.includes(category); } static isRetryable(category) { const retryableCategories = [ ErrorCategory.NETWORK, ErrorCategory.EXTERNAL_API, ErrorCategory.MCP_SERVICE, ErrorCategory.MODEL, ]; return retryableCategories.includes(category); } } /** * Error handler with recovery mechanisms */ export class ErrorHandler { static errorHistory = []; static maxHistorySize = 100; /** * Handle error with logging and potential recovery */ static async handleError(error, context) { const structuredError = this.ensureStructuredError(error, context); // Add to history this.errorHistory.push(structuredError); if (this.errorHistory.length > this.maxHistorySize) { this.errorHistory.shift(); } // Log error this.logError(structuredError); // Attempt recovery if possible if (structuredError.recoverable) { await this.attemptRecovery(structuredError); } return structuredError; } /** * Create error response for APIs and tools */ static createErrorResponse(error, requestId, service) { const structuredError = this.ensureStructuredError(error); return { success: false, error: structuredError, request_id: requestId, service, recovery_suggestions: structuredError.suggestedActions, }; } /** * Create success response */ static createSuccessResponse(data, requestId, service, metadata) { return { success: true, data, request_id: requestId, service, metadata, }; } /** * Wrap function with error handling */ static wrapWithErrorHandling(fn, context) { return async (...args) => { try { const result = await fn(...args); return this.createSuccessResponse(result); } catch (error) { const structuredError = await this.handleError(error, context); return this.createErrorResponse(structuredError); } }; } /** * Retry function with exponential backoff */ static async retryWithBackoff(fn, maxRetries = 3, baseDelay = 1000, context) { let lastError = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const result = await fn(); return this.createSuccessResponse(result); } catch (error) { lastError = await this.handleError(error, { ...context, attempt, maxRetries, }); if (attempt < maxRetries && lastError.retryable) { const delay = baseDelay * Math.pow(2, attempt - 1); logger.info(`Retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, delay)); } else { break; } } } return this.createErrorResponse(lastError); } /** * Get error statistics */ static getErrorStatistics() { const byCategoryCount = {}; const bySeverityCount = {}; // Initialize counts Object.values(ErrorCategory).forEach(cat => (byCategoryCount[cat] = 0)); Object.values(ErrorSeverity).forEach(sev => (bySeverityCount[sev] = 0)); // Count errors this.errorHistory.forEach(error => { byCategoryCount[error.category]++; bySeverityCount[error.severity]++; }); return { total: this.errorHistory.length, by_category: byCategoryCount, by_severity: bySeverityCount, recent_errors: this.errorHistory.slice(-10), }; } /** * Clear error history */ static clearErrorHistory() { this.errorHistory = []; } static ensureStructuredError(error, context) { if ('id' in error && 'category' in error) { return error; } const originalError = error; return ErrorFactory.createError(originalError.message || 'Unknown error', ErrorCategory.SYSTEM, ErrorSeverity.MEDIUM, { context, originalError, metadata: { error_name: originalError.name, error_type: typeof originalError, }, }); } static logError(error) { const colorMap = { [ErrorSeverity.LOW]: chalk.yellow, [ErrorSeverity.MEDIUM]: chalk.yellowBright, [ErrorSeverity.HIGH]: chalk.red, [ErrorSeverity.CRITICAL]: chalk.redBright, }; const color = colorMap[error.severity] || chalk.red; logger.error(`${color(`[${error.severity.toUpperCase()}]`)} ${error.category}: ${error.message}`, { errorId: error.id, category: error.category, severity: error.severity, context: error.context, stackTrace: error.stackTrace, timestamp: error.timestamp, }); // Also log to console for immediate visibility console.error(color(`❌ ${error.userMessage || error.message}`)); if (error.suggestedActions && error.suggestedActions.length > 0) { console.error(chalk.cyan('💡 Suggested actions:')); error.suggestedActions.forEach(action => { console.error(chalk.cyan(` • ${action}`)); }); } } static async attemptRecovery(error) { logger.info(`Attempting recovery for error: ${error.id}`); try { switch (error.category) { case ErrorCategory.NETWORK: // Wait and retry network connections await new Promise(resolve => setTimeout(resolve, 2000)); break; case ErrorCategory.MODEL: // Try to reinitialize model connection logger.info('Attempting model recovery...'); break; case ErrorCategory.MCP_SERVICE: // Try to reconnect to MCP services logger.info('Attempting MCP service recovery...'); break; default: logger.debug(`No recovery mechanism for category: ${error.category}`); } } catch (recoveryError) { logger.warn('Recovery attempt failed:', recoveryError); } } } /** * Input validation with structured error responses */ export class InputValidator { /** * Validate required string field */ static validateString(value, fieldName, options = {}) { if (value === undefined || value === null) { return ErrorHandler.createErrorResponse(ErrorFactory.createError(`${fieldName} is required`, ErrorCategory.VALIDATION, ErrorSeverity.MEDIUM, { userMessage: `Please provide ${fieldName}`, suggestedActions: [`Provide a valid ${fieldName} value`], })); } const stringValue = String(value); if (!options.allowEmpty && stringValue.trim().length === 0) { return ErrorHandler.createErrorResponse(ErrorFactory.createError(`${fieldName} cannot be empty`, ErrorCategory.VALIDATION, ErrorSeverity.MEDIUM)); } if (options.minLength && stringValue.length < options.minLength) { return ErrorHandler.createErrorResponse(ErrorFactory.createError(`${fieldName} must be at least ${options.minLength} characters`, ErrorCategory.VALIDATION, ErrorSeverity.MEDIUM)); } if (options.maxLength && stringValue.length > options.maxLength) { return ErrorHandler.createErrorResponse(ErrorFactory.createError(`${fieldName} must not exceed ${options.maxLength} characters`, ErrorCategory.VALIDATION, ErrorSeverity.MEDIUM)); } if (options.pattern && !options.pattern.test(stringValue)) { return ErrorHandler.createErrorResponse(ErrorFactory.createError(`${fieldName} format is invalid`, ErrorCategory.VALIDATION, ErrorSeverity.MEDIUM)); } return ErrorHandler.createSuccessResponse(stringValue); } /** * Validate and sanitize file path */ static validateFilePath(path) { const stringResult = this.validateString(path, 'file path'); if (!stringResult.success) return stringResult; const filePath = stringResult.data; // Check for directory traversal attempts if (filePath.includes('..') || filePath.includes('~')) { return ErrorHandler.createErrorResponse(ErrorFactory.createError('Invalid file path: directory traversal not allowed', ErrorCategory.VALIDATION, ErrorSeverity.HIGH, { context: { attempted_path: filePath }, userMessage: 'File path contains invalid characters', suggestedActions: ['Use relative paths within the project directory'], })); } return ErrorHandler.createSuccessResponse(filePath); } /** * Sanitize user input to prevent injection attacks */ static sanitizeInput(input) { if (!input || typeof input !== 'string') { return ''; } let sanitized = input; // SQL Injection protection - remove/escape SQL keywords and operators const sqlKeywords = [ 'drop', 'delete', 'insert', 'update', 'union', 'select', 'create', 'alter', 'truncate', 'exec', 'execute', 'sp_', 'xp_', 'cmd', 'shell', 'script', ]; sqlKeywords.forEach(keyword => { const regex = new RegExp(`\\b${keyword}\\b`, 'gi'); sanitized = sanitized.replace(regex, ''); }); // Remove SQL operators and comment markers sanitized = sanitized .replace(/--/g, '') // SQL comments .replace(/\/\*/g, '') // SQL block comment start .replace(/\*\//g, '') // SQL block comment end .replace(/;/g, '') // SQL statement terminator .replace(/'/g, '') // Single quotes .replace(/"/g, '') // Double quotes .replace(/`/g, '') // Backticks .replace(/\|/g, '') // Pipe operators .replace(/&/g, 'and'); // Ampersand // XSS protection - remove script tags and event handlers const xssPatterns = [ /<script[^>]*>.*?<\/script>/gi, /<iframe[^>]*>.*?<\/iframe>/gi, /<object[^>]*>.*?<\/object>/gi, /<embed[^>]*>.*?<\/embed>/gi, /<svg[^>]*>.*?<\/svg>/gi, /javascript:/gi, /vbscript:/gi, /data:/gi, /on\w+\s*=/gi, // Event handlers like onclick, onerror, etc. /<[^>]*on\w+[^>]*>/gi, // Any tag with event handlers ]; xssPatterns.forEach(pattern => { sanitized = sanitized.replace(pattern, ''); }); // Remove HTML tags completely sanitized = sanitized.replace(/<[^>]*>/g, ''); // Directory traversal protection sanitized = sanitized .replace(/\.\./g, '') // Remove .. sequences .replace(/~/g, '') // Remove home directory references .replace(/\\/g, '/') // Normalize path separators .replace(/\/\//g, '/'); // Remove double slashes // Encoding-based attack protection sanitized = sanitized .replace(/%2e/gi, '') // Encoded dots .replace(/%2f/gi, '') // Encoded slashes .replace(/%5c/gi, '') // Encoded backslashes .replace(/\\u[0-9a-f]{4}/gi, '') // Unicode escape sequences .replace(/\\x[0-9a-f]{2}/gi, ''); // Hex escape sequences // Normalize and limit whitespace sanitized = sanitized .replace(/\s+/g, ' ') // Normalize whitespace .trim() .substring(0, 10000); // Limit length to prevent buffer overflow return sanitized; } } // Export default export default ErrorHandler; //# sourceMappingURL=structured-error-system.js.map