capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
168 lines • 6.11 kB
JavaScript
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