(userMessage, { label = '', verbose = false, extraTools, toolChoice } = {})
| 395 | |
| 396 | // ─── Agentic 循环驱动器 ───────────────────────────────────────────────── |
| 397 | async function runAgentLoop(userMessage, { label = '', verbose = false, extraTools, toolChoice } = {}) { |
| 398 | const messages = [{ role: 'user', content: userMessage }]; |
| 399 | // 更强的 system prompt:明确要求 tool-first,禁止不调工具就回答 |
| 400 | const systemPrompt = [ |
| 401 | 'You are an AI coding assistant with full file system access.', |
| 402 | 'CRITICAL RULES:', |
| 403 | '1. You MUST use tools to read files before discussing their content. Never guess file contents.', |
| 404 | '2. You MUST use Write or Edit tools to actually modify files. Never just show code in text.', |
| 405 | '3. You MUST use Bash to run commands. Never pretend to run them.', |
| 406 | '4. Always use LS or Glob first to discover files if you are not sure about paths.', |
| 407 | '5. Use attempt_completion when the task is fully done.', |
| 408 | '6. Working directory is /project. All files are accessible via the Read tool.', |
| 409 | ].join('\n'); |
| 410 | |
| 411 | let turnCount = 0; |
| 412 | const toolCallLog = []; |
| 413 | let finalResult = null; |
| 414 | |
| 415 | while (turnCount < MAX_TURNS) { |
| 416 | turnCount++; |
| 417 | |
| 418 | // 发送请求 |
| 419 | const resp = await fetch(`${BASE_URL}/v1/messages`, { |
| 420 | method: 'POST', |
| 421 | headers: { 'Content-Type': 'application/json', 'x-api-key': 'dummy' }, |
| 422 | body: JSON.stringify({ |
| 423 | model: MODEL, |
| 424 | max_tokens: 8096, |
| 425 | system: systemPrompt, |
| 426 | tools: extraTools ? CLAUDE_CODE_TOOLS.filter(t => extraTools.includes(t.name)) : CLAUDE_CODE_TOOLS, |
| 427 | ...(toolChoice ? { tool_choice: toolChoice } : {}), |
| 428 | messages, |
| 429 | }), |
| 430 | }); |
| 431 | |
| 432 | if (!resp.ok) { |
| 433 | const text = await resp.text(); |
| 434 | throw new Error(`HTTP ${resp.status}: ${text.substring(0, 200)}`); |
| 435 | } |
| 436 | |
| 437 | const data = await resp.json(); |
| 438 | |
| 439 | if (verbose) { |
| 440 | const textBlock = data.content?.find(b => b.type === 'text'); |
| 441 | if (textBlock?.text) { |
| 442 | console.log(info(` [Turn ${turnCount}] 模型文本: "${textBlock.text.substring(0, 100)}..."`)); |
| 443 | } |
| 444 | } |
| 445 | |
| 446 | // 收集本轮工具调用 |
| 447 | const toolUseBlocks = data.content?.filter(b => b.type === 'tool_use') || []; |
| 448 | |
| 449 | if (data.stop_reason === 'end_turn' || toolUseBlocks.length === 0) { |
| 450 | // 任务自然结束 |
| 451 | const textBlock = data.content?.find(b => b.type === 'text'); |
| 452 | finalResult = textBlock?.text || '(no text response)'; |
| 453 | break; |
| 454 | } |
no test coverage detected