UNPKG

@mrtkrcm/acp-claude-code

Version:

ACP (Agent Client Protocol) bridge for Claude Code

224 lines 8.92 kB
import { globalMcpManager } from './mcp-connection-manager.js'; import { createLogger } from './logger.js'; export class McpToolBatcher { pendingBatches = new Map(); executingBatches = new Set(); logger; constructor() { this.logger = createLogger('MCP-Batcher'); } /** * Add tool calls to a batch for optimized execution */ addToBatch(batchId, toolCalls) { const existing = this.pendingBatches.get(batchId) || []; existing.push(...toolCalls); this.pendingBatches.set(batchId, existing); this.logger.debug(`Added ${toolCalls.length} tools to batch ${batchId}. Total: ${existing.length}`); } /** * Execute a batch of MCP tool calls with optimization strategies */ async executeBatch(batchId, options = { strategy: 'parallel' }) { const toolCalls = this.pendingBatches.get(batchId); if (!toolCalls || toolCalls.length === 0) { return []; } if (this.executingBatches.has(batchId)) { throw new Error(`Batch ${batchId} is already executing`); } this.executingBatches.add(batchId); this.pendingBatches.delete(batchId); try { const startTime = Date.now(); this.logger.info(`Executing batch ${batchId} with ${toolCalls.length} tools using ${options.strategy} strategy`); let results; switch (options.strategy) { case 'parallel': results = await this.executeParallel(toolCalls, options); break; case 'sequential': results = await this.executeSequential(toolCalls); break; case 'dependency-aware': results = await this.executeDependencyAware(toolCalls, options); break; default: throw new Error(`Unknown batch strategy: ${options.strategy}`); } const totalTime = Date.now() - startTime; this.logger.info(`Batch ${batchId} completed in ${totalTime}ms. Success rate: ${results.filter(r => r.success).length}/${results.length}`); return results; } finally { this.executingBatches.delete(batchId); } } async executeParallel(toolCalls, options) { const { maxConcurrency = 5 } = options; const results = []; // Group by server to optimize connection usage const serverGroups = new Map(); for (const call of toolCalls) { const group = serverGroups.get(call.serverName) || []; group.push(call); serverGroups.set(call.serverName, group); } const concurrencyLimiter = new Map(); const executeCall = async (call) => { const startTime = Date.now(); try { // Limit concurrency per server const current = concurrencyLimiter.get(call.serverName) || 0; const serverLimit = Math.ceil(maxConcurrency / serverGroups.size); if (current >= serverLimit) { await new Promise(resolve => setTimeout(resolve, 100)); } concurrencyLimiter.set(call.serverName, current + 1); const result = await globalMcpManager.executeToolWithPooling(call.serverName, call.toolName, call.args, { timeout: call.timeout }); return { id: call.id, success: true, result, executionTime: Date.now() - startTime, }; } catch (error) { return { id: call.id, success: false, error: error instanceof Error ? error.message : String(error), executionTime: Date.now() - startTime, }; } finally { const current = concurrencyLimiter.get(call.serverName) || 1; concurrencyLimiter.set(call.serverName, current - 1); } }; // Execute with controlled concurrency const promises = []; const executing = new Set(); for (const call of toolCalls) { const promise = executeCall(call); promises.push(promise); executing.add(promise); if (executing.size >= maxConcurrency) { const completed = await Promise.race(executing); executing.delete(Promise.resolve(completed)); } } const allResults = await Promise.all(promises); return allResults; } async executeSequential(toolCalls) { const results = []; for (const call of toolCalls) { const startTime = Date.now(); try { const result = await globalMcpManager.executeToolWithPooling(call.serverName, call.toolName, call.args, { timeout: call.timeout }); results.push({ id: call.id, success: true, result, executionTime: Date.now() - startTime, }); } catch (error) { results.push({ id: call.id, success: false, error: error instanceof Error ? error.message : String(error), executionTime: Date.now() - startTime, }); } } return results; } async executeDependencyAware(toolCalls, options) { const results = new Map(); const completed = new Set(); const executing = new Set(); // Build dependency graph const dependents = new Map(); for (const call of toolCalls) { if (call.dependencies) { for (const dep of call.dependencies) { const deps = dependents.get(dep) || []; deps.push(call.id); dependents.set(dep, deps); } } } const canExecute = (call) => { if (executing.has(call.id) || completed.has(call.id)) { return false; } if (!call.dependencies) { return true; } return call.dependencies.every(dep => completed.has(dep)); }; const executeWave = async () => { const readyCalls = toolCalls.filter(canExecute); if (readyCalls.length === 0) { return; } const promises = readyCalls.slice(0, options.maxConcurrency || 3).map(async (call) => { executing.add(call.id); const startTime = Date.now(); try { const result = await globalMcpManager.executeToolWithPooling(call.serverName, call.toolName, call.args, { timeout: call.timeout }); const batchResult = { id: call.id, success: true, result, executionTime: Date.now() - startTime, }; results.set(call.id, batchResult); completed.add(call.id); } catch (error) { const batchResult = { id: call.id, success: false, error: error instanceof Error ? error.message : String(error), executionTime: Date.now() - startTime, }; results.set(call.id, batchResult); completed.add(call.id); // Still mark as completed to unblock dependents } finally { executing.delete(call.id); } }); await Promise.all(promises); }; // Execute waves until all tools are processed while (completed.size < toolCalls.length && executing.size === 0) { await executeWave(); } return Array.from(results.values()); } /** * Get statistics about batch execution */ getBatchStats() { const totalPendingTools = Array.from(this.pendingBatches.values()) .reduce((sum, batch) => sum + batch.length, 0); return { pendingBatches: this.pendingBatches.size, executingBatches: this.executingBatches.size, totalPendingTools, }; } /** * Clear all pending batches */ clearPendingBatches() { this.pendingBatches.clear(); this.logger.info('Cleared all pending batches'); } } export const globalToolBatcher = new McpToolBatcher(); //# sourceMappingURL=mcp-tool-batcher.js.map