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