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