Skip to main content

codetether_agent/agent/
mod.rs

1//! Agent system
2//!
3//! Agents are the core execution units that orchestrate tools and LLM interactions.
4
5pub mod builtin;
6
7use crate::config::PermissionAction;
8use crate::provider::{CompletionRequest, Message, Provider, Role, ContentPart};
9use crate::session::Session;
10use crate::swarm::{Actor, ActorStatus, Handler, SwarmMessage};
11use crate::tool::{Tool, ToolRegistry, ToolResult};
12use anyhow::Result;
13use async_trait::async_trait;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::sync::Arc;
17
18/// Agent information
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct AgentInfo {
21    pub name: String,
22    pub description: Option<String>,
23    pub mode: AgentMode,
24    pub native: bool,
25    pub hidden: bool,
26    pub model: Option<String>,
27    pub temperature: Option<f32>,
28    pub top_p: Option<f32>,
29    pub max_steps: Option<usize>,
30}
31
32#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
33#[serde(rename_all = "lowercase")]
34pub enum AgentMode {
35    Primary,
36    Subagent,
37    All,
38}
39
40/// The main agent execution context
41pub struct Agent {
42    pub info: AgentInfo,
43    pub provider: Arc<dyn Provider>,
44    pub tools: ToolRegistry,
45    pub permissions: HashMap<String, PermissionAction>,
46    system_prompt: String,
47}
48
49impl Agent {
50    /// Create a new agent
51    pub fn new(
52        info: AgentInfo,
53        provider: Arc<dyn Provider>,
54        tools: ToolRegistry,
55        system_prompt: String,
56    ) -> Self {
57        Self {
58            info,
59            provider,
60            tools,
61            permissions: HashMap::new(),
62            system_prompt,
63        }
64    }
65
66    /// Execute a prompt and return the response
67    pub async fn execute(
68        &self,
69        session: &mut Session,
70        prompt: &str,
71    ) -> Result<AgentResponse> {
72        // Add user message to session
73        session.add_message(Message {
74            role: Role::User,
75            content: vec![ContentPart::Text { text: prompt.to_string() }],
76        });
77
78        let mut steps = 0;
79        let max_steps = self.info.max_steps.unwrap_or(100);
80
81        loop {
82            steps += 1;
83            if steps > max_steps {
84                anyhow::bail!("Exceeded maximum steps ({})", max_steps);
85            }
86
87            // Build the completion request
88            let request = CompletionRequest {
89                messages: self.build_messages(session),
90                tools: self.tools.definitions(),
91                model: self.info.model.clone().unwrap_or_else(|| "gpt-4o".to_string()),
92                temperature: self.info.temperature,
93                top_p: self.info.top_p,
94                max_tokens: None,
95                stop: vec![],
96            };
97
98            // Get completion from provider
99            let response = self.provider.complete(request).await?;
100            session.add_message(response.message.clone());
101
102            // Check for tool calls
103            let tool_calls: Vec<_> = response
104                .message
105                .content
106                .iter()
107                .filter_map(|p| match p {
108                    ContentPart::ToolCall { id, name, arguments } => {
109                        Some((id.clone(), name.clone(), arguments.clone()))
110                    }
111                    _ => None,
112                })
113                .collect();
114
115            if tool_calls.is_empty() {
116                // No tool calls, we're done
117                let text = response
118                    .message
119                    .content
120                    .iter()
121                    .filter_map(|p| match p {
122                        ContentPart::Text { text } => Some(text.clone()),
123                        _ => None,
124                    })
125                    .collect::<Vec<_>>()
126                    .join("\n");
127
128                return Ok(AgentResponse {
129                    text,
130                    tool_uses: session.tool_uses.clone(),
131                    usage: session.usage.clone(),
132                });
133            }
134
135            // Execute tool calls
136            for (id, name, arguments) in tool_calls {
137                let result = self.execute_tool(&name, &arguments).await;
138                
139                session.tool_uses.push(ToolUse {
140                    id: id.clone(),
141                    name: name.clone(),
142                    input: arguments.clone(),
143                    output: result.output.clone(),
144                    success: result.success,
145                });
146
147                session.add_message(Message {
148                    role: Role::Tool,
149                    content: vec![ContentPart::ToolResult {
150                        tool_call_id: id,
151                        content: result.output,
152                    }],
153                });
154            }
155        }
156    }
157
158    /// Build the full message list including system prompt
159    fn build_messages(&self, session: &Session) -> Vec<Message> {
160        let mut messages = vec![Message {
161            role: Role::System,
162            content: vec![ContentPart::Text {
163                text: self.system_prompt.clone(),
164            }],
165        }];
166        messages.extend(session.messages.clone());
167        messages
168    }
169
170    /// Execute a single tool
171    async fn execute_tool(&self, name: &str, arguments: &str) -> ToolResult {
172        // Check permissions for this tool
173        if let Some(permission) = self.permissions.get(name) {
174            tracing::debug!(tool = name, permission = ?permission, "Checking tool permission");
175            // Permission validation could be extended here
176            // For now, we just log that a permission check occurred
177        }
178        
179        match self.tools.get(name) {
180            Some(tool) => {
181                let args: serde_json::Value = match serde_json::from_str(arguments) {
182                    Ok(v) => v,
183                    Err(e) => {
184                        return ToolResult {
185                            output: format!("Failed to parse arguments: {}", e),
186                            success: false,
187                            metadata: HashMap::new(),
188                        }
189                    }
190                };
191                
192                match tool.execute(args).await {
193                    Ok(result) => result,
194                    Err(e) => ToolResult {
195                        output: format!("Tool execution failed: {}", e),
196                        success: false,
197                        metadata: HashMap::new(),
198                    },
199                }
200            }
201            None => {
202                // Use the invalid tool handler for better error messages
203                let available_tools = self.tools.list().iter().map(|s| s.to_string()).collect();
204                let invalid_tool = crate::tool::invalid::InvalidTool::with_context(name.to_string(), available_tools);
205                let args = serde_json::json!({
206                    "requested_tool": name,
207                    "args": serde_json::from_str::<serde_json::Value>(arguments).unwrap_or(serde_json::json!({}))
208                });
209                match invalid_tool.execute(args).await {
210                    Ok(result) => result,
211                    Err(e) => ToolResult {
212                        output: format!("Unknown tool: {}. Error: {}", name, e),
213                        success: false,
214                        metadata: HashMap::new(),
215                    },
216                }
217            }
218        }
219    }
220
221    /// Get a tool from the registry by name
222    pub fn get_tool(&self, name: &str) -> Option<Arc<dyn Tool>> {
223        self.tools.get(name)
224    }
225
226    /// Register a tool with the agent's tool registry
227    pub fn register_tool(&mut self, tool: Arc<dyn Tool>) {
228        self.tools.register(tool);
229    }
230
231    /// List all available tool IDs
232    pub fn list_tools(&self) -> Vec<&str> {
233        self.tools.list()
234    }
235
236    /// Check if a tool is available
237    pub fn has_tool(&self, name: &str) -> bool {
238        self.tools.get(name).is_some()
239    }
240}
241
242/// Actor implementation for Agent - enables swarm participation
243#[async_trait]
244impl Actor for Agent {
245    fn actor_id(&self) -> &str {
246        &self.info.name
247    }
248
249    fn actor_status(&self) -> ActorStatus {
250        // Agent is always ready to process messages
251        ActorStatus::Ready
252    }
253
254    async fn initialize(&mut self) -> Result<()> {
255        // Agent initialization is handled during construction
256        // Additional async initialization can be added here
257        tracing::info!("Agent '{}' initialized for swarm participation", self.info.name);
258        Ok(())
259    }
260
261    async fn shutdown(&mut self) -> Result<()> {
262        tracing::info!("Agent '{}' shutting down", self.info.name);
263        Ok(())
264    }
265}
266
267/// Handler implementation for SwarmMessage - enables message processing
268#[async_trait]
269impl Handler<SwarmMessage> for Agent {
270    type Response = SwarmMessage;
271
272    async fn handle(&mut self, message: SwarmMessage) -> Result<Self::Response> {
273        match message {
274            SwarmMessage::ExecuteTask { task_id, instruction } => {
275                // Create a new session for this task
276                let mut session = Session::new().await?;
277                
278                // Execute the task
279                match self.execute(&mut session, &instruction).await {
280                    Ok(response) => {
281                        Ok(SwarmMessage::TaskCompleted {
282                            task_id,
283                            result: response.text,
284                        })
285                    }
286                    Err(e) => {
287                        Ok(SwarmMessage::TaskFailed {
288                            task_id,
289                            error: e.to_string(),
290                        })
291                    }
292                }
293            }
294            SwarmMessage::ToolRequest { tool_id, arguments } => {
295                // Execute the requested tool
296                let result = if let Some(tool) = self.get_tool(&tool_id) {
297                    match tool.execute(arguments).await {
298                        Ok(r) => r,
299                        Err(e) => ToolResult::error(format!("Tool execution failed: {}", e)),
300                    }
301                } else {
302                    // Use the invalid tool handler for better error messages
303                    let available_tools = self.tools.list().iter().map(|s| s.to_string()).collect();
304                    let invalid_tool = crate::tool::invalid::InvalidTool::with_context(tool_id.clone(), available_tools);
305                    let args = serde_json::json!({
306                        "requested_tool": tool_id,
307                        "args": arguments
308                    });
309                    match invalid_tool.execute(args).await {
310                        Ok(r) => r,
311                        Err(e) => ToolResult::error(format!("Tool '{}' not found: {}", tool_id, e)),
312                    }
313                };
314                
315                Ok(SwarmMessage::ToolResponse {
316                    tool_id,
317                    result,
318                })
319            }
320            _ => {
321                // Other message types are not handled directly by the agent
322                Ok(SwarmMessage::TaskFailed {
323                    task_id: "unknown".to_string(),
324                    error: "Unsupported message type".to_string(),
325                })
326            }
327        }
328    }
329}
330
331/// Response from agent execution
332#[derive(Debug, Clone, Serialize, Deserialize)]
333pub struct AgentResponse {
334    pub text: String,
335    pub tool_uses: Vec<ToolUse>,
336    pub usage: crate::provider::Usage,
337}
338
339/// Record of a tool use
340#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct ToolUse {
342    pub id: String,
343    pub name: String,
344    pub input: String,
345    pub output: String,
346    pub success: bool,
347}
348
349/// Registry of available agents
350pub struct AgentRegistry {
351    agents: HashMap<String, AgentInfo>,
352}
353
354impl AgentRegistry {
355    #[allow(dead_code)]
356    pub fn new() -> Self {
357        Self {
358            agents: HashMap::new(),
359        }
360    }
361
362    /// Register a new agent
363    pub fn register(&mut self, info: AgentInfo) {
364        self.agents.insert(info.name.clone(), info);
365    }
366
367    /// Get agent info by name
368    #[allow(dead_code)]
369    pub fn get(&self, name: &str) -> Option<&AgentInfo> {
370        self.agents.get(name)
371    }
372
373    /// List all agents
374    pub fn list(&self) -> Vec<&AgentInfo> {
375        self.agents.values().collect()
376    }
377
378    /// List primary agents (visible in UI)
379    #[allow(dead_code)]
380    pub fn list_primary(&self) -> Vec<&AgentInfo> {
381        self.agents
382            .values()
383            .filter(|a| a.mode == AgentMode::Primary && !a.hidden)
384            .collect()
385    }
386
387    /// Initialize with builtin agents
388    pub fn with_builtins() -> Self {
389        let mut registry = Self::new();
390        
391        registry.register(builtin::build_agent());
392        registry.register(builtin::plan_agent());
393        registry.register(builtin::explore_agent());
394        
395        registry
396    }
397}
398
399impl Default for AgentRegistry {
400    fn default() -> Self {
401        Self::with_builtins()
402    }
403}