UNPKG

crewai-ts

Version:

TypeScript port of crewAI for agent-based workflows

311 lines (310 loc) 8.83 kB
/** * Error handling system implementation * Optimized for efficient error management with contextual information */ /** * Base error class for all CrewAI errors * Provides consistent error structure and handling */ export class CrewAIError extends Error { /** * Error code for programmatic handling */ code; /** * HTTP status code (if applicable) */ status; /** * Additional contextual information */ context; /** * Whether this error is retryable */ retryable; /** * Original error if this is a wrapped error */ cause; /** * Create a new CrewAIError instance */ constructor(message, options) { super(message); // Set name to the class name for better debugging this.name = this.constructor.name; // Set error properties this.code = options?.code ?? 'CREW_ERROR'; this.status = options?.status; this.context = options?.context ?? {}; this.retryable = options?.retryable ?? false; this.cause = options?.cause; // Capture stack trace if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } // Handle cause for better stack traces if (this.cause && this.cause.stack) { this.stack = `${this.stack}\nCaused by: ${this.cause.stack}`; } } /** * Convert error to JSON for logging */ toJSON() { return { name: this.name, message: this.message, code: this.code, status: this.status, context: this.context, retryable: this.retryable, stack: this.stack, cause: this.cause ? ( // Check if cause has a toJSON method that's a function 'toJSON' in this.cause && typeof this.cause.toJSON === 'function' ? this.cause.toJSON() : { message: this.cause.message, stack: this.cause.stack }) : undefined }; } /** * Get a string representation of the error * @override */ toString() { let result = `${this.name}: ${this.message}`; if (Object.keys(this.context).length > 0) { result += ` (${JSON.stringify(this.context)})`; } return result; } } /** * Validation error - thrown when input validation fails */ export class ValidationError extends CrewAIError { constructor(message, options) { super(message, { code: 'VALIDATION_ERROR', status: 400, retryable: false, ...options }); } } /** * Configuration error - thrown when there's an issue with configuration */ export class ConfigurationError extends CrewAIError { constructor(message, options) { super(message, { code: 'CONFIGURATION_ERROR', status: 500, retryable: false, ...options }); } } /** * LLM error - thrown when there's an issue with LLM interaction */ export class LLMError extends CrewAIError { constructor(message, options) { super(message, { code: 'LLM_ERROR', status: 502, retryable: options?.retryable ?? true, // LLM errors are often retryable ...options }); } } /** * Rate limit error - thrown when rate limits are exceeded */ export class RateLimitError extends CrewAIError { constructor(message, options) { super(message, { code: 'RATE_LIMIT_ERROR', status: 429, retryable: true, context: { retryAfterMs: options?.retryAfterMs, ...options?.context }, cause: options?.cause }); } /** * Get the recommended retry delay */ getRetryAfterMs() { return this.context.retryAfterMs ?? 1000; } } /** * Memory error - thrown when there's an issue with memory operations */ export class MemoryError extends CrewAIError { constructor(message, options) { super(message, { code: 'MEMORY_ERROR', status: 500, retryable: false, ...options }); } } /** * Task execution error - thrown when task execution fails */ export class TaskExecutionError extends CrewAIError { constructor(message, options) { super(message, { code: 'TASK_EXECUTION_ERROR', status: 500, retryable: false, context: { taskId: options?.taskId, agentId: options?.agentId, ...options?.context }, cause: options?.cause }); } } /** * Tool execution error - thrown when tool execution fails */ export class ToolExecutionError extends CrewAIError { constructor(message, options) { super(message, { code: 'TOOL_EXECUTION_ERROR', status: 500, retryable: false, context: { toolName: options?.toolName, ...options?.context }, cause: options?.cause }); } } /** * Timeout error - thrown when an operation times out */ export class TimeoutError extends CrewAIError { constructor(message, options) { super(message, { code: 'TIMEOUT_ERROR', status: 408, retryable: true, context: { operationName: options?.operationName, timeoutMs: options?.timeoutMs, ...options?.context }, cause: options?.cause }); } } /** * Authentication error - thrown when authentication fails */ export class AuthenticationError extends CrewAIError { constructor(message, options) { super(message, { code: 'AUTHENTICATION_ERROR', status: 401, retryable: false, ...options }); } } /** * Authorization error - thrown when authorization fails */ export class AuthorizationError extends CrewAIError { constructor(message, options) { super(message, { code: 'AUTHORIZATION_ERROR', status: 403, retryable: false, ...options }); } } /** * Network error - thrown when network operations fail */ export class NetworkError extends CrewAIError { constructor(message, options) { super(message, { code: 'NETWORK_ERROR', status: 503, retryable: true, ...options }); } } /** * Retry a function with exponential backoff */ export async function withRetry(fn, options = {}) { const maxRetries = options.maxRetries ?? 3; const initialDelayMs = options.initialDelayMs ?? 1000; const backoffFactor = options.backoffFactor ?? 2; const maxDelayMs = options.maxDelayMs ?? 30000; const jitter = options.jitter ?? 0.2; const retryPredicate = options.retryPredicate ?? ((error) => { return error?.retryable === true; }); const onRetry = options.onRetry; let attempt = 0; while (true) { try { return await fn(); } catch (error) { attempt++; // If we've reached max retries or the error is not retryable, throw if (attempt >= maxRetries || !retryPredicate(error)) { throw error; } // Calculate backoff delay with jitter const backoffMs = Math.min(maxDelayMs, initialDelayMs * Math.pow(backoffFactor, attempt - 1)); // Add jitter (random variance) to prevent thundering herd const jitterMs = backoffMs * jitter * (Math.random() * 2 - 1); const delayMs = Math.max(0, Math.floor(backoffMs + jitterMs)); // Call retry callback if provided if (onRetry) { await Promise.resolve(onRetry(error, attempt, delayMs)); } // Wait before retrying await new Promise(resolve => setTimeout(resolve, delayMs)); } } } /** * Error boundary for handling errors in async contexts */ export async function errorBoundary(fn, options) { try { return await fn(); } catch (error) { // Call error handler if provided if (options?.onError) { await Promise.resolve(options.onError(error)); } // Return fallback value if provided if (options?.fallback !== undefined) { if (typeof options.fallback === 'function') { return await Promise.resolve(options.fallback()); } return options.fallback; } // Re-throw the error throw error; } }