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