//! Multi-step OpenAI-compatible tool-calling ReAct loop. use crate::config::Config; use crate::model::{ChatMessage, chat_completion}; use crate::tools::{ToolContext, execute_tool, tool_definitions}; use anyhow::Result; pub const DEFAULT_MAX_ROUNDS: usize = 12; pub const AGENT_SYSTEM: &str = "\ You are GrokBoy, a concise local coding assistant with tools. Use tools when they help solve the task; otherwise answer directly. Prefer short, clear answers. Traditional Chinese is welcome when the user writes in Chinese. Available tools: shell, list_dir, read_file, write_file. Do not invent tool results — call the tools. Stop when you can give a final answer."; /// Run the agent loop until the model returns text without tool_calls or max rounds. /// Appends all intermediate messages (assistant tool_calls + tool results + final) to `messages`. /// Returns the final assistant text (may be empty if stopped on round limit with only tools). pub async fn run_agent( config: &Config, messages: &mut Vec, tool_ctx: &ToolContext, max_rounds: usize, ) -> Result { let tools = tool_definitions(); let mut last_text = String::new(); for _round in 0..max_rounds { let reply = chat_completion(config, messages, Some(&tools)).await?; let tool_calls = reply.tool_calls.clone().unwrap_or_default(); if tool_calls.is_empty() { last_text = reply.text().to_string(); messages.push(reply); return Ok(last_text); } // Keep any content the model sent alongside tool_calls. if let Some(c) = reply.content.as_ref().filter(|s| !s.is_empty()) { last_text = c.clone(); } messages.push(reply); for call in &tool_calls { let result = execute_tool(tool_ctx, &call.function.name, &call.function.arguments).await; messages.push(ChatMessage::tool(&call.id, result)); } } Ok(last_text) } #[cfg(test)] mod tests { use super::*; #[test] fn system_prompt_mentions_tools() { assert!(AGENT_SYSTEM.contains("shell")); assert!(AGENT_SYSTEM.contains("Traditional Chinese") || AGENT_SYSTEM.contains("Chinese")); } #[test] fn default_max_rounds_is_12() { assert_eq!(DEFAULT_MAX_ROUNDS, 12); } }