UNPKG

@vfarcic/dot-ai

Version:

AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance

411 lines (410 loc) 16 kB
"use strict"; /** * Comprehensive Error Handling System for DevOps AI Toolkit * * Provides centralized error handling, logging, and context management * with support for MCP protocol, CLI operations, and core functionality. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorHandler = exports.ConsoleLogger = exports.LogLevel = exports.AppError = exports.ErrorSeverity = exports.ErrorCategory = void 0; const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); /** * Error categories for systematic error classification */ var ErrorCategory; (function (ErrorCategory) { // Infrastructure errors ErrorCategory["KUBERNETES"] = "kubernetes"; ErrorCategory["NETWORK"] = "network"; ErrorCategory["AUTHENTICATION"] = "authentication"; ErrorCategory["AUTHORIZATION"] = "authorization"; // Application errors ErrorCategory["VALIDATION"] = "validation"; ErrorCategory["CONFIGURATION"] = "configuration"; ErrorCategory["OPERATION"] = "operation"; // External service errors ErrorCategory["AI_SERVICE"] = "ai_service"; ErrorCategory["STORAGE"] = "storage"; // Protocol errors ErrorCategory["MCP_PROTOCOL"] = "mcp_protocol"; ErrorCategory["CLI_INTERFACE"] = "cli_interface"; // System errors ErrorCategory["INTERNAL"] = "internal"; ErrorCategory["UNKNOWN"] = "unknown"; })(ErrorCategory || (exports.ErrorCategory = ErrorCategory = {})); /** * Error severity levels */ var ErrorSeverity; (function (ErrorSeverity) { ErrorSeverity["LOW"] = "low"; ErrorSeverity["MEDIUM"] = "medium"; ErrorSeverity["HIGH"] = "high"; ErrorSeverity["CRITICAL"] = "critical"; // System-threatening, immediate action required })(ErrorSeverity || (exports.ErrorSeverity = ErrorSeverity = {})); /** * Structured error class that extends native Error */ class AppError extends Error { // Core identification id; code; category; severity; // User-facing information userMessage; technicalDetails; // Context and debugging context; // Timing timestamp; // Recovery guidance suggestedActions; isRetryable; // Chaining cause; constructor(id, code, category, severity, message, context, timestamp, suggestedActions, isRetryable, userMessage, technicalDetails, cause) { super(message); this.name = 'AppError'; this.id = id; this.code = code; this.category = category; this.severity = severity; this.context = context; this.timestamp = timestamp; this.suggestedActions = suggestedActions; this.isRetryable = isRetryable; this.userMessage = userMessage; this.technicalDetails = technicalDetails; this.cause = cause; } } exports.AppError = AppError; /** * Log levels for structured logging */ var LogLevel; (function (LogLevel) { LogLevel["DEBUG"] = "debug"; LogLevel["INFO"] = "info"; LogLevel["WARN"] = "warn"; LogLevel["ERROR"] = "error"; LogLevel["FATAL"] = "fatal"; })(LogLevel || (exports.LogLevel = LogLevel = {})); /** * Default console logger implementation */ class ConsoleLogger { component; minLevel; constructor(component, minLevel = LogLevel.INFO) { this.component = component; this.minLevel = minLevel; } shouldLog(level) { const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]; return levels.indexOf(level) >= levels.indexOf(this.minLevel); } formatMessage(level, message, data) { const timestamp = new Date().toISOString(); const baseMessage = `[${timestamp}] ${level.toUpperCase()} [${this.component}] ${message}`; if (data) { return `${baseMessage} ${JSON.stringify(data)}`; } return baseMessage; } debug(message, data) { if (this.shouldLog(LogLevel.DEBUG)) { console.debug(this.formatMessage(LogLevel.DEBUG, message, data)); } } info(message, data) { if (this.shouldLog(LogLevel.INFO)) { console.info(this.formatMessage(LogLevel.INFO, message, data)); } } warn(message, data) { if (this.shouldLog(LogLevel.WARN)) { console.warn(this.formatMessage(LogLevel.WARN, message, data)); } } error(message, error, data) { if (this.shouldLog(LogLevel.ERROR)) { const errorData = error ? { error: this.serializeError(error), ...data } : data; console.error(this.formatMessage(LogLevel.ERROR, message, errorData)); } } fatal(message, error, data) { if (this.shouldLog(LogLevel.FATAL)) { const errorData = error ? { error: this.serializeError(error), ...data } : data; console.error(this.formatMessage(LogLevel.FATAL, message, errorData)); } } serializeError(error) { if ('category' in error) { // AppError return { id: error.id, code: error.code, category: error.category, severity: error.severity, message: error.message, context: error.context }; } else { // Native Error return { name: error.name, message: error.message, stack: error.stack }; } } } exports.ConsoleLogger = ConsoleLogger; /** * Error handler factory and utilities */ class ErrorHandler { static requestIdCounter = 0; static logger = new ConsoleLogger('ErrorHandler'); /** * Set custom logger implementation */ static setLogger(logger) { this.logger = logger; } /** * Generate unique request ID */ static generateRequestId() { return `req_${Date.now()}_${++this.requestIdCounter}`; } /** * Create comprehensive AppError from various error sources */ static createError(category, severity, message, context, originalError) { const errorId = `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const timestamp = new Date(); const fullContext = { operation: context.operation || 'unknown', component: context.component || 'unknown', timestamp, version: process.env.npm_package_version || '0.1.0', originalError, stackTrace: originalError?.stack || new Error().stack, isRetryable: context.isRetryable || false, retryCount: context.retryCount || 0, ...context }; const suggestedActions = context.suggestedActions || this.getDefaultSuggestedActions(category); const appError = new AppError(errorId, this.generateErrorCode(category, severity), category, severity, message, fullContext, timestamp, suggestedActions, fullContext.isRetryable || false, this.getUserFriendlyMessage(category), originalError?.message, undefined // Don't wrap the original error to prevent circular references ); // Log the error this.logger.error(`Error created: ${message}`, appError, { category, severity, operation: fullContext.operation, component: fullContext.component }); return appError; } /** * Convert AppError to McpError for MCP protocol */ static toMcpError(appError) { const errorCode = this.mapToMcpErrorCode(appError.category); const message = `${appError.message}${appError.technicalDetails ? ` - ${appError.technicalDetails}` : ''}`; return new types_js_1.McpError(errorCode, message); } /** * Handle error with automatic logging and context enhancement */ static handleError(error, context, options = {}) { let appError; if ('category' in error) { // Already an AppError appError = error; } else { // Convert native Error to AppError appError = this.createError(this.categorizeError(error), this.assessSeverity(error), error.message, context, error); } // Log the handled error - always use error() method for proper error typing this.logger.error(`Error handled in ${context.component || 'unknown'}`, appError); if (options.convertToMcp) { const mcpError = this.toMcpError(appError); if (options.rethrow) { throw mcpError; } return mcpError; } if (options.rethrow) { throw appError; } return appError; } /** * Wrap operation with error handling */ static async withErrorHandling(operation, context, options = {}) { const maxRetries = options.retryCount || 0; let lastError; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { this.logger.debug(`Executing operation: ${context.operation}`, { attempt: attempt + 1, maxRetries: maxRetries + 1 }); return await operation(); } catch (error) { lastError = error; const enhancedContext = { ...context, retryCount: attempt, isRetryable: attempt < maxRetries }; const appError = this.handleError(lastError, enhancedContext, { logLevel: attempt < maxRetries ? LogLevel.WARN : LogLevel.ERROR }); // Retry if we haven't exceeded max retries and the error is retryable // For retry logic, we consider errors retryable by default unless explicitly marked as not retryable const shouldRetry = attempt < maxRetries && (appError.isRetryable || enhancedContext.isRetryable); if (shouldRetry) { this.logger.info(`Retrying operation: ${context.operation}`, { attempt: attempt + 1, maxRetries: maxRetries + 1, reason: appError.message }); continue; } // Final attempt failed or not retryable if (options.convertToMcp) { throw this.toMcpError(appError); } throw appError; } } // This should never be reached, but TypeScript requires it throw lastError; } static generateErrorCode(category, severity) { const categoryCode = category.toUpperCase().replace('_', ''); const severityCode = severity.charAt(0).toUpperCase(); const timestamp = Date.now().toString().slice(-6); const random = Math.random().toString(36).substring(2, 5); return `${categoryCode}_${severityCode}_${timestamp}_${random}`; } static mapToMcpErrorCode(category) { switch (category) { case ErrorCategory.VALIDATION: return types_js_1.ErrorCode.InvalidParams; case ErrorCategory.AUTHENTICATION: case ErrorCategory.AUTHORIZATION: return types_js_1.ErrorCode.InvalidParams; case ErrorCategory.MCP_PROTOCOL: return types_js_1.ErrorCode.MethodNotFound; case ErrorCategory.OPERATION: case ErrorCategory.CLI_INTERFACE: return types_js_1.ErrorCode.InvalidRequest; default: return types_js_1.ErrorCode.InternalError; } } static categorizeError(error) { const message = error.message.toLowerCase(); if (message.includes('kubeconfig') || message.includes('kubernetes')) { return ErrorCategory.KUBERNETES; } if (message.includes('network') || message.includes('connection')) { return ErrorCategory.NETWORK; } if (message.includes('authentication') || message.includes('unauthorized')) { return ErrorCategory.AUTHENTICATION; } if (message.includes('ai') || message.includes('api key invalid') || message.includes('model')) { return ErrorCategory.AI_SERVICE; } if (message.includes('validation') || message.includes('invalid')) { return ErrorCategory.VALIDATION; } return ErrorCategory.UNKNOWN; } static assessSeverity(error) { const message = error.message.toLowerCase(); if (message.includes('critical') || message.includes('fatal')) { return ErrorSeverity.CRITICAL; } if (message.includes('authentication') || message.includes('authorization')) { return ErrorSeverity.HIGH; } if (message.includes('validation') || message.includes('invalid')) { return ErrorSeverity.MEDIUM; } return ErrorSeverity.LOW; } static getUserFriendlyMessage(category) { switch (category) { case ErrorCategory.KUBERNETES: return 'Unable to connect to Kubernetes cluster. Please check your kubeconfig and cluster connectivity.'; case ErrorCategory.AUTHENTICATION: return 'Authentication failed. Please verify your credentials.'; case ErrorCategory.VALIDATION: return 'Input validation failed. Please check your parameters and try again.'; case ErrorCategory.AI_SERVICE: return 'AI service is temporarily unavailable. Please try again later.'; default: return 'An unexpected error occurred. Please try again or contact support.'; } } static getDefaultSuggestedActions(category) { switch (category) { case ErrorCategory.KUBERNETES: return [ 'Verify kubeconfig file exists and is valid', 'Check cluster connectivity with kubectl cluster-info', 'Ensure proper authentication credentials' ]; case ErrorCategory.VALIDATION: return [ 'Review input parameters for correct format', 'Check required fields are provided', 'Verify data types match expected schema' ]; case ErrorCategory.AI_SERVICE: return [ 'Check AI provider API key environment variable is set', 'Verify API key is valid and has sufficient credits', 'Try again after a short delay' ]; default: return [ 'Try the operation again', 'Check system logs for more details', 'Contact support if problem persists' ]; } } static wrapNativeError(error) { const errorId = `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const timestamp = new Date(); const category = this.categorizeError(error); const severity = this.assessSeverity(error); const context = { operation: 'error_wrapping', component: 'ErrorHandler', timestamp, version: process.env.npm_package_version || '0.1.0', originalError: error, stackTrace: error.stack, isRetryable: false, retryCount: 0 }; const appError = new AppError(errorId, this.generateErrorCode(category, severity), category, severity, error.message, context, timestamp, this.getDefaultSuggestedActions(category), false, this.getUserFriendlyMessage(category), error.message, undefined // No cause to prevent circular reference ); return appError; } } exports.ErrorHandler = ErrorHandler;