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