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
JavaScript
/**
* 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