UNPKG

capsule-ai-cli

Version:

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

306 lines 13.3 kB
import chalk from 'chalk'; import { configManager } from '../core/config.js'; import { authService } from './auth.js'; import { stateService } from './state.js'; import { contextManager } from './context.js'; import { chatService } from './chat.js'; import { toolExecutor } from '../tools/executor.js'; import { toolResultsService } from './tool-results.js'; import { ModelResolver } from './model-resolver.js'; import { openRouterModelsService } from './openrouter-models.js'; import { ProviderFactory } from '../providers/factory.js'; import { providerRegistry } from '../providers/base.js'; import { registerBuiltinToolsForTaskMode } from '../tools/builtin/index.js'; export async function executeTaskMode(options) { const unhandledRejectionHandler = (reason, promise) => { console.error(chalk.red('\n[Unhandled Promise Rejection]'), reason); }; const uncaughtExceptionHandler = (error) => { console.error(chalk.red('\n[Uncaught Exception]'), error); console.error(chalk.red('[Stack trace]'), error.stack); }; const signalHandler = (signal) => { console.error(chalk.red(`\n[Process received ${signal}]`)); }; process.on('SIGTERM', () => signalHandler('SIGTERM')); process.on('SIGINT', () => signalHandler('SIGINT')); process.on('unhandledRejection', unhandledRejectionHandler); process.on('uncaughtException', uncaughtExceptionHandler); try { await initializeServices(); const authStatus = await authService.getStatus(); if (!authStatus.isAuthenticated) { const error = 'Capsule must be activated to use task mode.\n' + 'Purchase at: https://capsulecli.dev\n' + 'Then run: capsule and use /activate'; if (options.json) { console.log(JSON.stringify({ success: false, error }, null, 2)); } else { console.error(chalk.red('Error: ') + error); } return { success: false, error }; } const config = configManager.getConfig(); if (!config.providers.openrouter?.apiKey) { const error = 'OpenRouter API key required.\n' + 'Get your key at: https://openrouter.ai/keys\n' + 'Then run: capsule and use /keys'; if (options.json) { console.log(JSON.stringify({ success: false, error }, null, 2)); } else { console.error(chalk.red('Error: ') + error); } return { success: false, error }; } let provider = options.provider || stateService.getProvider(); let model = options.model || stateService.getModel(); if (options.provider) { const resolvedProvider = ModelResolver.resolveProvider(options.provider); if (!resolvedProvider) { const error = `Provider "${options.provider}" not found`; if (options.json) { console.log(JSON.stringify({ success: false, error }, null, 2)); } else { console.error(chalk.red('Error: ') + error); } return { success: false, error }; } provider = resolvedProvider; } if (options.model) { const resolvedModel = ModelResolver.resolveModel(options.model, provider); if (!resolvedModel) { const error = `Model "${options.model}" not found for provider ${provider}`; if (options.json) { console.log(JSON.stringify({ success: false, error }, null, 2)); } else { console.error(chalk.red('Error: ') + error); } return { success: false, error }; } model = resolvedModel; } const validation = ModelResolver.validateModel(model); if (!validation.valid) { const error = `Model "${model}" cannot be used: ${validation.reason}`; if (options.json) { console.log(JSON.stringify({ success: false, error }, null, 2)); } else { console.error(chalk.red('Error: ') + error); } return { success: false, error }; } if (!options.json) { console.log(chalk.cyan('╭' + '─'.repeat(58) + '╮')); console.log(chalk.cyan('│') + chalk.bold(' 🚀 Executing Task'.padEnd(58)) + chalk.cyan('│')); console.log(chalk.cyan('├' + '─'.repeat(58) + '┤')); console.log(chalk.cyan('│') + chalk.gray(' Provider: ') + provider.padEnd(48) + chalk.cyan('│')); console.log(chalk.cyan('│') + chalk.gray(' Model: ') + model.padEnd(51) + chalk.cyan('│')); console.log(chalk.cyan('╰' + '─'.repeat(58) + '╯')); console.log(); } const taskContext = contextManager.createNewContext(); const previousContext = contextManager.getCurrentContext(); contextManager.setCurrentContext(taskContext.id); const originalProvider = stateService.getProvider(); const originalModel = stateService.getModel(); stateService.setProvider(provider); stateService.setModel(model); try { const result = await executeTask(options.task, options); stateService.setProvider(originalProvider); stateService.setModel(originalModel); contextManager.setCurrentContext(previousContext.id); if (options.json) { const output = { success: result.success, task: options.task, provider: provider, model: model, duration: result.duration, tokensUsed: result.tokensUsed || 0, toolsExecuted: result.toolsExecuted || [], output: result.output || '', error: result.error }; console.log(JSON.stringify(output, null, 2)); } else { if (result.success) { console.log('\n' + chalk.green('═'.repeat(60))); console.log(chalk.green('✓ Task completed successfully')); console.log(chalk.green('═'.repeat(60))); console.log(chalk.gray('Duration: ') + result.duration); console.log(chalk.gray('Tokens: ') + (result.tokensUsed || 0)); if (result.toolsExecuted && result.toolsExecuted.length > 0) { console.log(chalk.gray('Tools used: ') + result.toolsExecuted.join(', ')); } } else { console.error('\n' + chalk.red('═'.repeat(60))); console.error(chalk.red('✗ Task failed')); console.error(chalk.red('═'.repeat(60))); console.error(chalk.red('Error: ') + (result.error || 'Unknown error')); } } return { success: result.success, output: result.output, error: result.error }; } catch (error) { stateService.setProvider(originalProvider); stateService.setModel(originalModel); contextManager.setCurrentContext(previousContext.id); throw error; } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; if (options.json) { console.log(JSON.stringify({ success: false, error: errorMessage }, null, 2)); } else { console.error(chalk.red('Error: ') + errorMessage); } return { success: false, error: errorMessage }; } finally { process.removeListener('unhandledRejection', unhandledRejectionHandler); process.removeListener('uncaughtException', uncaughtExceptionHandler); process.removeAllListeners('SIGTERM'); process.removeAllListeners('SIGINT'); } } async function initializeServices() { await configManager.loadConfig(); const config = configManager.getConfig(); if (config.providers?.openrouter?.apiKey) { await openRouterModelsService.fetchModels(false, true); const supportedProviders = openRouterModelsService.getAvailableProviders(); for (const providerName of supportedProviders) { try { const provider = await ProviderFactory.create(providerName); providerRegistry.register(provider); } catch { } } } registerBuiltinToolsForTaskMode(); } async function executeTask(task, options) { const startTime = Date.now(); try { let fullOutput = ''; let tokensUsed = 0; const toolsExecuted = []; let isFirstOutput = true; if (!options.json) { process.stdout.write(chalk.yellow('⠋ ') + chalk.gray('Thinking...')); } const stream = chatService.stream(task, { mode: 'agent' }); let timeoutId; if (options.timeout) { timeoutId = setTimeout(() => { chatService.abort(); }, options.timeout * 1000); } const pendingToolCalls = new Set(); for await (const chunk of stream) { if (chunk.delta) { if (!options.json && isFirstOutput) { process.stdout.write('\r' + ' '.repeat(20) + '\r'); isFirstOutput = false; } fullOutput += chunk.delta; if (!options.json) { process.stdout.write(chunk.delta); } } if (chunk.usage) { tokensUsed = chunk.usage.totalTokens; } if (chunk.toolCall) { const toolName = chunk.toolCall.name; pendingToolCalls.add(chunk.toolCall.id); if (!toolsExecuted.includes(toolName)) { toolsExecuted.push(toolName); } if (!options.json) { console.log(chalk.yellow(`\n\n🔧 Executing tool: ${chalk.bold(toolName)}`)); process.stdout.write(chalk.gray(' ')); } try { const execution = await toolExecutor.execute(chunk.toolCall, { requireApproval: false, workingDirectory: process.cwd() }); const toolResult = { call_id: chunk.toolCall.id, output: execution.result?.output || execution.result?.error || 'Unknown error', success: execution.state === 'completed', name: chunk.toolCall.name }; toolResultsService.setToolResult(chunk.toolCall.id, toolResult); pendingToolCalls.delete(chunk.toolCall.id); if (execution.result?.error) { if (!options.json) { console.log(chalk.red(`✗ Failed: ${execution.result.error}`)); } } else if (!options.json) { console.log(chalk.green(`✓ Completed`)); } } catch (toolError) { const errorResult = { call_id: chunk.toolCall.id, output: { error: toolError instanceof Error ? toolError.message : String(toolError) }, success: false, name: chunk.toolCall.name }; toolResultsService.setToolResult(chunk.toolCall.id, errorResult); pendingToolCalls.delete(chunk.toolCall.id); if (!options.json) { console.log(chalk.red(`✗ Error: ${toolError}`)); } } if (!options.json) { console.log(); } } } if (timeoutId) { clearTimeout(timeoutId); } const duration = ((Date.now() - startTime) / 1000).toFixed(1) + 's'; return { success: true, output: fullOutput.trim(), duration, tokensUsed, toolsExecuted }; } catch (error) { const duration = ((Date.now() - startTime) / 1000).toFixed(1) + 's'; if (options.timeout && error instanceof Error && error.message.includes('aborted')) { return { success: false, error: `Task timed out after ${options.timeout} seconds`, duration }; } return { success: false, error: error instanceof Error ? error.message : 'Unknown error', duration }; } } //# sourceMappingURL=task-executor.js.map