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