UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

168 lines 6.11 kB
import { v4 as uuidv4 } from 'uuid'; import { toolRegistry } from './registry.js'; import chalk from 'chalk'; import { EventEmitter } from 'events'; export class ToolExecutor extends EventEmitter { executions = new Map(); abortControllers = new Map(); constructor() { super(); } emit(event, ...args) { return super.emit(event, ...args); } on(event, listener) { return super.on(event, listener); } async execute(toolCall, options) { const tool = toolRegistry.get(toolCall.name); if (!tool) { throw new Error(`Tool not found: ${toolCall.name}`); } if (!toolRegistry.isAllowed(toolCall.name)) { throw new Error(`Tool not allowed by configuration: ${toolCall.name}`); } if (options?.requireApproval && tool.ui?.dangerous) { const approved = await options.onApproval?.() ?? false; if (!approved) { throw new Error('Tool execution cancelled by user'); } } const execution = { id: uuidv4(), toolName: tool.name, displayName: tool.displayName, parameters: toolCall.arguments, state: 'pending', startTime: new Date() }; this.executions.set(execution.id, execution); this.emit('execution:start', execution); const abortController = new AbortController(); this.abortControllers.set(execution.id, abortController); const context = { signal: abortController.signal, workingDirectory: options?.workingDirectory || process.cwd(), onProgress: (progress) => { execution.progress = progress; this.emit('execution:progress', execution.id, progress); } }; execution.state = 'running'; try { const result = await tool.execute(toolCall.arguments, context); execution.state = 'completed'; execution.endTime = new Date(); execution.result = result; this.emit('execution:complete', execution); return execution; } catch (error) { if (error.name === 'AbortError') { execution.state = 'cancelled'; } else { execution.state = 'error'; } execution.endTime = new Date(); execution.result = { success: false, error: error.message || 'Unknown error', executionTime: Date.now() - execution.startTime.getTime() }; this.emit('execution:error', execution); return execution; } finally { this.abortControllers.delete(execution.id); } } async executeSequence(toolCalls, options) { const executions = []; for (const toolCall of toolCalls) { try { const execution = await this.execute(toolCall, options); executions.push(execution); if (execution.state === 'error' || execution.state === 'cancelled') { break; } } catch (error) { console.error(chalk.red(`Tool execution failed: ${error}`)); break; } } return executions; } cancel(executionId) { const controller = this.abortControllers.get(executionId); const execution = this.executions.get(executionId); if (controller && execution && execution.state === 'running') { controller.abort(); execution.state = 'cancelled'; execution.endTime = new Date(); return true; } return false; } getExecution(executionId) { return this.executions.get(executionId); } getAllExecutions() { return Array.from(this.executions.values()); } clearCompleted() { for (const [id, execution] of this.executions) { if (execution.state === 'completed' || execution.state === 'error') { this.executions.delete(id); } } } formatExecution(execution) { const tool = toolRegistry.get(execution.toolName); const icon = tool?.icon || '🔧'; const duration = execution.endTime ? `${execution.endTime.getTime() - execution.startTime.getTime()}ms` : 'running...'; let status = ''; switch (execution.state) { case 'pending': status = chalk.gray('⏳ Pending'); break; case 'running': status = chalk.blue('🔄 Running'); break; case 'completed': status = chalk.green('✓ Completed'); break; case 'error': status = chalk.red('✗ Error'); break; case 'cancelled': status = chalk.yellow('⚠ Cancelled'); break; } const lines = [ `${icon} ${chalk.bold(execution.displayName)} ${status} ${chalk.dim(`(${duration})`)}`, chalk.dim(` Parameters: ${JSON.stringify(execution.parameters, null, 2).split('\n').join('\n ')}`) ]; if (execution.progress) { lines.push(chalk.blue(` → ${execution.progress.message}`)); } if (execution.result) { if (execution.result.success) { const output = JSON.stringify(execution.result.output, null, 2); const preview = output.length > 200 ? output.substring(0, 200) + '...' : output; lines.push(chalk.green(` Result: ${preview}`)); } else { lines.push(chalk.red(` Error: ${execution.result.error}`)); } } return lines.join('\n'); } } export const toolExecutor = new ToolExecutor(); //# sourceMappingURL=executor.js.map