@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
JavaScript
"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;