UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and

317 lines (316 loc) 10 kB
/** * Robust Error Handling Utilities for NeuroLink * Provides structured error management for tool execution and system operations */ import { logger } from "./logger.js"; // Error categories for proper handling export var ErrorCategory; (function (ErrorCategory) { ErrorCategory["VALIDATION"] = "validation"; ErrorCategory["TIMEOUT"] = "timeout"; ErrorCategory["NETWORK"] = "network"; ErrorCategory["RESOURCE"] = "resource"; ErrorCategory["PERMISSION"] = "permission"; ErrorCategory["CONFIGURATION"] = "configuration"; ErrorCategory["EXECUTION"] = "execution"; ErrorCategory["SYSTEM"] = "system"; })(ErrorCategory || (ErrorCategory = {})); // Error severity levels export var ErrorSeverity; (function (ErrorSeverity) { ErrorSeverity["LOW"] = "low"; ErrorSeverity["MEDIUM"] = "medium"; ErrorSeverity["HIGH"] = "high"; ErrorSeverity["CRITICAL"] = "critical"; })(ErrorSeverity || (ErrorSeverity = {})); // Error codes for different scenarios export const ERROR_CODES = { // Tool errors TOOL_NOT_FOUND: "TOOL_NOT_FOUND", TOOL_EXECUTION_FAILED: "TOOL_EXECUTION_FAILED", TOOL_TIMEOUT: "TOOL_TIMEOUT", TOOL_VALIDATION_FAILED: "TOOL_VALIDATION_FAILED", // Parameter errors INVALID_PARAMETERS: "INVALID_PARAMETERS", MISSING_REQUIRED_PARAM: "MISSING_REQUIRED_PARAM", // System errors MEMORY_EXHAUSTED: "MEMORY_EXHAUSTED", NETWORK_ERROR: "NETWORK_ERROR", PERMISSION_DENIED: "PERMISSION_DENIED", // Provider errors PROVIDER_NOT_AVAILABLE: "PROVIDER_NOT_AVAILABLE", PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED", PROVIDER_QUOTA_EXCEEDED: "PROVIDER_QUOTA_EXCEEDED", // Configuration errors INVALID_CONFIGURATION: "INVALID_CONFIGURATION", MISSING_CONFIGURATION: "MISSING_CONFIGURATION", }; /** * Enhanced error class with structured information */ export class NeuroLinkError extends Error { code; category; severity; retriable; context; timestamp; toolName; serverId; constructor(options) { super(options.message); this.name = "NeuroLinkError"; this.code = options.code; this.category = options.category; this.severity = options.severity; this.retriable = options.retriable; this.context = options.context || {}; this.timestamp = new Date(); this.toolName = options.toolName; this.serverId = options.serverId; // Preserve original error stack if provided if (options.originalError) { this.stack = options.originalError.stack; this.context.originalMessage = options.originalError.message; } } /** * Convert to JSON for logging and serialization */ toJSON() { return { code: this.code, message: this.message, category: this.category, severity: this.severity, retriable: this.retriable, context: this.context, timestamp: this.timestamp, toolName: this.toolName, serverId: this.serverId, }; } } /** * Error factory for common error scenarios */ export class ErrorFactory { /** * Create a tool not found error */ static toolNotFound(toolName, availableTools) { return new NeuroLinkError({ code: ERROR_CODES.TOOL_NOT_FOUND, message: `Tool '${toolName}' not found`, category: ErrorCategory.VALIDATION, severity: ErrorSeverity.MEDIUM, retriable: false, context: { toolName, availableTools }, toolName, }); } /** * Create a tool execution failed error */ static toolExecutionFailed(toolName, originalError, serverId) { return new NeuroLinkError({ code: ERROR_CODES.TOOL_EXECUTION_FAILED, message: `Tool '${toolName}' execution failed: ${originalError.message}`, category: ErrorCategory.EXECUTION, severity: ErrorSeverity.HIGH, retriable: true, originalError, toolName, serverId, }); } /** * Create a tool timeout error */ static toolTimeout(toolName, timeoutMs, serverId) { return new NeuroLinkError({ code: ERROR_CODES.TOOL_TIMEOUT, message: `Tool '${toolName}' timed out after ${timeoutMs}ms`, category: ErrorCategory.TIMEOUT, severity: ErrorSeverity.HIGH, retriable: true, context: { timeoutMs }, toolName, serverId, }); } /** * Create a parameter validation error */ static invalidParameters(toolName, validationError, providedParams) { return new NeuroLinkError({ code: ERROR_CODES.INVALID_PARAMETERS, message: `Invalid parameters for tool '${toolName}': ${validationError.message}`, category: ErrorCategory.VALIDATION, severity: ErrorSeverity.MEDIUM, retriable: false, context: { providedParams }, originalError: validationError, toolName, }); } /** * Create a network error */ static networkError(toolName, originalError, serverId) { return new NeuroLinkError({ code: ERROR_CODES.NETWORK_ERROR, message: `Network error in tool '${toolName}': ${originalError.message}`, category: ErrorCategory.NETWORK, severity: ErrorSeverity.HIGH, retriable: true, originalError, toolName, serverId, }); } /** * Create a memory exhaustion error */ static memoryExhausted(toolName, memoryUsageMB) { return new NeuroLinkError({ code: ERROR_CODES.MEMORY_EXHAUSTED, message: `Memory exhausted during tool '${toolName}' execution (${memoryUsageMB}MB used)`, category: ErrorCategory.RESOURCE, severity: ErrorSeverity.CRITICAL, retriable: false, context: { memoryUsageMB }, toolName, }); } } /** * Timeout wrapper for async operations */ export async function withTimeout(promise, timeoutMs, timeoutError) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(timeoutError || new Error(`Operation timed out after ${timeoutMs}ms`)); }, timeoutMs); }); return Promise.race([promise, timeoutPromise]); } /** * Retry mechanism for retriable operations */ export async function withRetry(operation, options) { const { maxAttempts, delayMs, isRetriable = () => true, onRetry } = options; let lastError; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await operation(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); // Don't retry on the last attempt or if error is not retriable if (attempt === maxAttempts || !isRetriable(lastError)) { throw lastError; } if (onRetry) { onRetry(attempt, lastError); } // Wait before retry await new Promise((resolve) => setTimeout(resolve, delayMs)); } } throw lastError; } /** * Circuit breaker for preventing cascading failures */ export class CircuitBreaker { failureThreshold; resetTimeoutMs; failures = 0; lastFailureTime = 0; state = "closed"; constructor(failureThreshold = 5, resetTimeoutMs = 60000) { this.failureThreshold = failureThreshold; this.resetTimeoutMs = resetTimeoutMs; } async execute(operation) { if (this.state === "open") { if (Date.now() - this.lastFailureTime > this.resetTimeoutMs) { this.state = "half-open"; } else { throw new Error("Circuit breaker is open - operation not executed"); } } try { const result = await operation(); this.onSuccess(); return result; } catch (error) { this.onFailure(); throw error; } } onSuccess() { this.failures = 0; this.state = "closed"; } onFailure() { this.failures++; this.lastFailureTime = Date.now(); if (this.failures >= this.failureThreshold) { this.state = "open"; } } getState() { return this.state; } getFailureCount() { return this.failures; } } /** * Error handler that decides whether to retry based on error type */ export function isRetriableError(error) { if (error instanceof NeuroLinkError) { return error.retriable; } // Check for common retriable error patterns const retriablePatterns = [ /timeout/i, /network/i, /connection/i, /temporary/i, /rate limit/i, /quota/i, /503/i, // Service unavailable /502/i, // Bad gateway /504/i, // Gateway timeout ]; return retriablePatterns.some((pattern) => pattern.test(error.message)); } /** * Enhanced error logger that provides structured logging */ export function logStructuredError(error, context) { const logData = { ...error.toJSON(), ...context, }; switch (error.severity) { case ErrorSeverity.CRITICAL: logger.error(`[CRITICAL] ${error.message}`, logData); break; case ErrorSeverity.HIGH: logger.error(`[HIGH] ${error.message}`, logData); break; case ErrorSeverity.MEDIUM: logger.warn(`[MEDIUM] ${error.message}`, logData); break; case ErrorSeverity.LOW: logger.info(`[LOW] ${error.message}`, logData); break; } }