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