* Execute a single batch of steps concurrently. * * Each step is a mini agent loop: * 1. Build isolated message context (system + step instruction) * 2. Call chatCompletion with that context * 3. Execute any tool calls returned * 4. Return { stepIndex, toolResults, finalContent } * *
(batch, planSteps, systemMessages, chatCompletionFn, executeToolFn, config, ctx)
| 71 | * @returns {Promise<Array<{stepIndex: number, content: string, toolResults: object[]}>>} |
| 72 | */ |
| 73 | async function executeBatch(batch, planSteps, systemMessages, chatCompletionFn, executeToolFn, config, ctx) { |
| 74 | const batchPromises = batch.map(async (stepIdx) => { |
| 75 | const stepText = planSteps[stepIdx] || `Step ${stepIdx + 1}`; |
| 76 | const messages = buildStepMessages(systemMessages, stepText); |
| 77 | |
| 78 | const toolResults = []; |
| 79 | let finalContent = ''; |
| 80 | let iterations = 0; |
| 81 | const MAX_ITERATIONS = 20; // per-step tool call limit |
| 82 | |
| 83 | try { |
| 84 | let currentMessages = [...messages]; |
| 85 | |
| 86 | while (iterations < MAX_ITERATIONS) { |
| 87 | iterations++; |
| 88 | const response = await chatCompletionFn(config, currentMessages); |
| 89 | if (!response) break; |
| 90 | |
| 91 | const choice = response?.choices?.[0]?.message; |
| 92 | if (!choice) break; |
| 93 | |
| 94 | // Recover tool calls embedded in text content (qwen2.5-coder etc.). |
| 95 | // Issue #36 — the agent loop in bin/smallcode.js does the same. |
| 96 | try { |
| 97 | const { extractFromMessage } = require('../tools/tool_call_extractor'); |
| 98 | const tools = (typeof ctx?.getAllTools === 'function' ? ctx.getAllTools(config) : null) || []; |
| 99 | extractFromMessage(choice, tools); |
| 100 | } catch {} |
| 101 | |
| 102 | finalContent = choice.content || ''; |
| 103 | |
| 104 | const toolCalls = choice.tool_calls || []; |
| 105 | if (toolCalls.length === 0) break; // no more tool calls → step done |
| 106 | |
| 107 | // Execute tool calls sequentially within this step |
| 108 | for (const tc of toolCalls) { |
| 109 | const name = tc?.function?.name; |
| 110 | const argsStr = tc?.function?.arguments || '{}'; |
| 111 | let args = {}; |
| 112 | try { args = JSON.parse(argsStr); } catch {} |
| 113 | |
| 114 | const result = await executeToolFn(name, args, ctx); |
| 115 | const resultText = result?.result || result?.error || JSON.stringify(result); |
| 116 | |
| 117 | toolResults.push({ toolName: name, args, result: resultText }); |
| 118 | |
| 119 | // Add tool call + result to this step's message history |
| 120 | currentMessages.push({ |
| 121 | role: 'assistant', |
| 122 | content: choice.content || '', |
| 123 | tool_calls: toolCalls, |
| 124 | }); |
| 125 | currentMessages.push({ |
| 126 | role: 'tool', |
| 127 | tool_call_id: tc.id || String(Date.now()), |
| 128 | content: resultText, |
| 129 | }); |
| 130 | } |
no test coverage detected