Skip to main content

codetether_agent/tool/
mod.rs

1//! Tool system
2//!
3//! Tools are the executable capabilities available to agents.
4
5pub mod avatar;
6pub mod bash;
7pub mod batch;
8pub mod codesearch;
9pub mod confirm_edit;
10pub mod confirm_multiedit;
11pub mod edit;
12pub mod file;
13pub mod invalid;
14pub mod lsp;
15pub mod mcp_bridge;
16pub mod multiedit;
17pub mod patch;
18pub mod plan;
19pub mod podcast;
20pub mod prd;
21pub mod question;
22pub mod ralph;
23pub mod rlm;
24pub mod sandbox;
25pub mod search;
26pub mod skill;
27pub mod task;
28pub mod todo;
29pub mod undo;
30pub mod voice;
31pub mod webfetch;
32pub mod websearch;
33pub mod youtube;
34
35use anyhow::Result;
36use async_trait::async_trait;
37use serde::{Deserialize, Serialize};
38use serde_json::Value;
39use std::collections::HashMap;
40use std::sync::Arc;
41
42use crate::provider::Provider;
43
44/// A tool that can be executed by an agent
45#[async_trait]
46pub trait Tool: Send + Sync {
47    /// Tool identifier
48    fn id(&self) -> &str;
49
50    /// Human-readable name
51    fn name(&self) -> &str;
52
53    /// Description for the LLM
54    fn description(&self) -> &str;
55
56    /// JSON Schema for parameters
57    fn parameters(&self) -> Value;
58
59    /// Execute the tool with given arguments
60    async fn execute(&self, args: Value) -> Result<ToolResult>;
61}
62
63/// Result from tool execution
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct ToolResult {
66    pub output: String,
67    pub success: bool,
68    #[serde(default)]
69    pub metadata: HashMap<String, Value>,
70}
71
72impl ToolResult {
73    pub fn success(output: impl Into<String>) -> Self {
74        Self {
75            output: output.into(),
76            success: true,
77            metadata: HashMap::new(),
78        }
79    }
80
81    pub fn error(message: impl Into<String>) -> Self {
82        Self {
83            output: message.into(),
84            success: false,
85            metadata: HashMap::new(),
86        }
87    }
88
89    /// Create a structured error with code, tool name, missing fields, and example
90    ///
91    /// This helps LLMs self-correct by providing actionable information about what went wrong.
92    pub fn structured_error(
93        code: &str,
94        tool: &str,
95        message: &str,
96        missing_fields: Option<Vec<&str>>,
97        example: Option<Value>,
98    ) -> Self {
99        let mut error_obj = serde_json::json!({
100            "code": code,
101            "tool": tool,
102            "message": message,
103        });
104
105        if let Some(fields) = missing_fields {
106            error_obj["missing_fields"] = serde_json::json!(fields);
107        }
108
109        if let Some(ex) = example {
110            error_obj["example"] = ex;
111        }
112
113        let output = serde_json::to_string_pretty(&serde_json::json!({
114            "error": error_obj
115        }))
116        .unwrap_or_else(|_| format!("Error: {}", message));
117
118        let mut metadata = HashMap::new();
119        metadata.insert("error_code".to_string(), serde_json::json!(code));
120        metadata.insert("tool".to_string(), serde_json::json!(tool));
121
122        Self {
123            output,
124            success: false,
125            metadata,
126        }
127    }
128
129    pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
130        self.metadata.insert(key.into(), value);
131        self
132    }
133}
134
135/// Registry of available tools
136pub struct ToolRegistry {
137    tools: HashMap<String, Arc<dyn Tool>>,
138}
139
140impl std::fmt::Debug for ToolRegistry {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        f.debug_struct("ToolRegistry")
143            .field("tools", &self.tools.keys().collect::<Vec<_>>())
144            .finish()
145    }
146}
147
148impl ToolRegistry {
149    pub fn new() -> Self {
150        Self {
151            tools: HashMap::new(),
152        }
153    }
154
155    /// Register a tool
156    pub fn register(&mut self, tool: Arc<dyn Tool>) {
157        self.tools.insert(tool.id().to_string(), tool);
158    }
159
160    /// Get a tool by ID
161    pub fn get(&self, id: &str) -> Option<Arc<dyn Tool>> {
162        self.tools.get(id).cloned()
163    }
164
165    /// List all tool IDs
166    pub fn list(&self) -> Vec<&str> {
167        self.tools.keys().map(|s| s.as_str()).collect()
168    }
169
170    /// Get tool definitions for LLM
171    pub fn definitions(&self) -> Vec<crate::provider::ToolDefinition> {
172        self.tools
173            .values()
174            .map(|t| crate::provider::ToolDefinition {
175                name: t.id().to_string(),
176                description: t.description().to_string(),
177                parameters: t.parameters(),
178            })
179            .collect()
180    }
181
182    /// Create registry with all default tools (without batch)
183    pub fn with_defaults() -> Self {
184        let mut registry = Self::new();
185
186        registry.register(Arc::new(file::ReadTool::new()));
187        registry.register(Arc::new(file::WriteTool::new()));
188        registry.register(Arc::new(file::ListTool::new()));
189        registry.register(Arc::new(file::GlobTool::new()));
190        registry.register(Arc::new(search::GrepTool::new()));
191        registry.register(Arc::new(edit::EditTool::new()));
192        registry.register(Arc::new(bash::BashTool::new()));
193        registry.register(Arc::new(lsp::LspTool::new()));
194        registry.register(Arc::new(webfetch::WebFetchTool::new()));
195        registry.register(Arc::new(multiedit::MultiEditTool::new()));
196        registry.register(Arc::new(websearch::WebSearchTool::new()));
197        registry.register(Arc::new(codesearch::CodeSearchTool::new()));
198        registry.register(Arc::new(patch::ApplyPatchTool::new()));
199        registry.register(Arc::new(todo::TodoReadTool::new()));
200        registry.register(Arc::new(todo::TodoWriteTool::new()));
201        registry.register(Arc::new(question::QuestionTool::new()));
202        registry.register(Arc::new(task::TaskTool::new()));
203        registry.register(Arc::new(plan::PlanEnterTool::new()));
204        registry.register(Arc::new(plan::PlanExitTool::new()));
205        registry.register(Arc::new(skill::SkillTool::new()));
206        registry.register(Arc::new(rlm::RlmTool::new()));
207        registry.register(Arc::new(ralph::RalphTool::new()));
208        registry.register(Arc::new(prd::PrdTool::new()));
209        registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
210        registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
211        registry.register(Arc::new(undo::UndoTool));
212        registry.register(Arc::new(voice::VoiceTool::new()));
213        registry.register(Arc::new(podcast::PodcastTool::new()));
214        registry.register(Arc::new(youtube::YouTubeTool::new()));
215        registry.register(Arc::new(avatar::AvatarTool::new()));
216        registry.register(Arc::new(mcp_bridge::McpBridgeTool::new()));
217        // Register the invalid tool handler for graceful error handling
218        registry.register(Arc::new(invalid::InvalidTool::new()));
219
220        registry
221    }
222
223    /// Create registry with provider for tools that need it (like RalphTool)
224    pub fn with_provider(provider: Arc<dyn Provider>, model: String) -> Self {
225        let mut registry = Self::new();
226
227        registry.register(Arc::new(file::ReadTool::new()));
228        registry.register(Arc::new(file::WriteTool::new()));
229        registry.register(Arc::new(file::ListTool::new()));
230        registry.register(Arc::new(file::GlobTool::new()));
231        registry.register(Arc::new(search::GrepTool::new()));
232        registry.register(Arc::new(edit::EditTool::new()));
233        registry.register(Arc::new(bash::BashTool::new()));
234        registry.register(Arc::new(lsp::LspTool::new()));
235        registry.register(Arc::new(webfetch::WebFetchTool::new()));
236        registry.register(Arc::new(multiedit::MultiEditTool::new()));
237        registry.register(Arc::new(websearch::WebSearchTool::new()));
238        registry.register(Arc::new(codesearch::CodeSearchTool::new()));
239        registry.register(Arc::new(patch::ApplyPatchTool::new()));
240        registry.register(Arc::new(todo::TodoReadTool::new()));
241        registry.register(Arc::new(todo::TodoWriteTool::new()));
242        registry.register(Arc::new(question::QuestionTool::new()));
243        registry.register(Arc::new(task::TaskTool::new()));
244        registry.register(Arc::new(plan::PlanEnterTool::new()));
245        registry.register(Arc::new(plan::PlanExitTool::new()));
246        registry.register(Arc::new(skill::SkillTool::new()));
247        registry.register(Arc::new(rlm::RlmTool::new()));
248        // RalphTool with provider for autonomous execution
249        registry.register(Arc::new(ralph::RalphTool::with_provider(provider, model)));
250        registry.register(Arc::new(prd::PrdTool::new()));
251        registry.register(Arc::new(undo::UndoTool));
252        registry.register(Arc::new(voice::VoiceTool::new()));
253        registry.register(Arc::new(podcast::PodcastTool::new()));
254        registry.register(Arc::new(youtube::YouTubeTool::new()));
255        registry.register(Arc::new(avatar::AvatarTool::new()));
256        registry.register(Arc::new(mcp_bridge::McpBridgeTool::new()));
257        // Register the invalid tool handler for graceful error handling
258        registry.register(Arc::new(invalid::InvalidTool::new()));
259
260        registry
261    }
262
263    /// Create Arc-wrapped registry with batch tool properly initialized.
264    /// The batch tool needs a weak reference to the registry, so we use
265    /// a two-phase initialization pattern.
266    pub fn with_defaults_arc() -> Arc<Self> {
267        let mut registry = Self::with_defaults();
268
269        // Create batch tool without registry reference
270        let batch_tool = Arc::new(batch::BatchTool::new());
271        registry.register(batch_tool.clone());
272
273        // Wrap registry in Arc
274        let registry = Arc::new(registry);
275
276        // Now give batch tool a weak reference to the registry
277        batch_tool.set_registry(Arc::downgrade(&registry));
278
279        registry
280    }
281
282    /// Create Arc-wrapped registry with provider and batch tool properly initialized.
283    /// The batch tool needs a weak reference to the registry, so we use
284    /// a two-phase initialization pattern.
285    #[allow(dead_code)]
286    pub fn with_provider_arc(provider: Arc<dyn Provider>, model: String) -> Arc<Self> {
287        let mut registry = Self::with_provider(provider, model);
288
289        // Create batch tool without registry reference
290        let batch_tool = Arc::new(batch::BatchTool::new());
291        registry.register(batch_tool.clone());
292
293        // Wrap registry in Arc
294        let registry = Arc::new(registry);
295
296        // Now give batch tool a weak reference to the registry
297        batch_tool.set_registry(Arc::downgrade(&registry));
298
299        registry
300    }
301}
302
303impl Default for ToolRegistry {
304    fn default() -> Self {
305        Self::with_defaults()
306    }
307}