(userMessage, { tools = TOOLS, systemPrompt = '', toolChoice, maxTurns = MAX_TURNS } = {})
| 177 | |
| 178 | // ─── 多轮 Agentic 循环(用于完整任务分析) ───────────────────────────── |
| 179 | async function runMultiTurn(userMessage, { tools = TOOLS, systemPrompt = '', toolChoice, maxTurns = MAX_TURNS } = {}) { |
| 180 | const messages = [{ role: 'user', content: userMessage }]; |
| 181 | const system = systemPrompt || 'You are an AI coding assistant. Working directory: /project.'; |
| 182 | |
| 183 | let totalToolCalls = 0; |
| 184 | let totalTextChars = 0; |
| 185 | let turns = 0; |
| 186 | let firstTurnHasToolCall = false; |
| 187 | const toolCallLog = []; |
| 188 | |
| 189 | while (turns < maxTurns) { |
| 190 | turns++; |
| 191 | const resp = await fetch(`${BASE_URL}/v1/messages`, { |
| 192 | method: 'POST', |
| 193 | headers: { 'Content-Type': 'application/json', 'x-api-key': 'dummy' }, |
| 194 | body: JSON.stringify({ |
| 195 | model: MODEL, |
| 196 | max_tokens: 4096, |
| 197 | system, |
| 198 | tools, |
| 199 | ...(toolChoice ? { tool_choice: toolChoice } : {}), |
| 200 | messages, |
| 201 | }), |
| 202 | }); |
| 203 | |
| 204 | if (!resp.ok) throw new Error(`HTTP ${resp.status}`); |
| 205 | const data = await resp.json(); |
| 206 | |
| 207 | const textBlocks = data.content?.filter(b => b.type === 'text') || []; |
| 208 | const toolUseBlocks = data.content?.filter(b => b.type === 'tool_use') || []; |
| 209 | |
| 210 | totalTextChars += textBlocks.reduce((s, b) => s + (b.text?.length || 0), 0); |
| 211 | totalToolCalls += toolUseBlocks.length; |
| 212 | |
| 213 | if (turns === 1 && toolUseBlocks.length > 0) firstTurnHasToolCall = true; |
| 214 | |
| 215 | for (const tb of toolUseBlocks) { |
| 216 | toolCallLog.push({ turn: turns, tool: tb.name, input: tb.input }); |
| 217 | } |
| 218 | |
| 219 | if (data.stop_reason === 'end_turn' || toolUseBlocks.length === 0) break; |
| 220 | |
| 221 | messages.push({ role: 'assistant', content: data.content }); |
| 222 | |
| 223 | const toolResults = toolUseBlocks.map(tb => ({ |
| 224 | type: 'tool_result', |
| 225 | tool_use_id: tb.id, |
| 226 | content: mockExecute(tb.name, tb.input), |
| 227 | })); |
| 228 | messages.push({ role: 'user', content: toolResults }); |
| 229 | |
| 230 | // Check for completion signal |
| 231 | if (toolResults.some(r => r.content.startsWith('__DONE__'))) break; |
| 232 | } |
| 233 | |
| 234 | return { totalToolCalls, totalTextChars, turns, firstTurnHasToolCall, toolCallLog }; |
| 235 | } |
| 236 |
no test coverage detected