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;
9#[path = "bash_github/mod.rs"]
10mod bash_github;
11mod bash_identity;
12pub mod batch;
13pub mod browserctl;
14pub mod codesearch;
15pub mod confirm_edit;
16pub mod confirm_multiedit;
17pub mod edit;
18pub mod file;
19pub mod file_extras;
20pub mod go;
21pub mod image;
22pub mod invalid;
23pub mod k8s_tool;
24pub mod lsp;
25pub mod mcp_bridge;
26pub mod mcp_tools;
27pub mod memory;
28pub mod morph_backend;
29pub mod multiedit;
30pub mod okr;
31pub mod patch;
32pub mod plan;
33pub mod podcast;
34pub mod prd;
35pub mod question;
36pub mod ralph;
37pub mod readonly;
38pub mod relay_autochat;
39pub mod rlm;
40pub mod sandbox;
41pub mod search;
42pub mod skill;
43pub mod swarm_execute;
44pub mod swarm_share;
45pub mod task;
46pub mod todo;
47pub mod undo;
48pub mod voice;
49pub mod webfetch;
50pub mod websearch;
51pub mod youtube;
52
53use anyhow::Result;
54use async_trait::async_trait;
55use serde::{Deserialize, Serialize};
56use serde_json::Value;
57use std::collections::HashMap;
58use std::sync::Arc;
59
60use crate::provider::Provider;
61pub use mcp_tools::{McpToolManager, McpToolWrapper};
62pub use sandbox::{PluginManifest, PluginRegistry, SigningKey, hash_bytes, hash_file};
63
64/// A tool that can be executed by an agent
65#[async_trait]
66pub trait Tool: Send + Sync {
67    /// Tool identifier
68    fn id(&self) -> &str;
69
70    /// Human-readable name
71    fn name(&self) -> &str;
72
73    /// Description for the LLM
74    fn description(&self) -> &str;
75
76    /// JSON Schema for parameters
77    fn parameters(&self) -> Value;
78
79    /// Execute the tool with given arguments
80    async fn execute(&self, args: Value) -> Result<ToolResult>;
81}
82
83/// Result from tool execution
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct ToolResult {
86    pub output: String,
87    pub success: bool,
88    #[serde(default)]
89    pub metadata: HashMap<String, Value>,
90}
91
92impl ToolResult {
93    pub fn success(output: impl Into<String>) -> Self {
94        Self {
95            output: output.into(),
96            success: true,
97            metadata: HashMap::new(),
98        }
99    }
100
101    pub fn error(message: impl Into<String>) -> Self {
102        Self {
103            output: message.into(),
104            success: false,
105            metadata: HashMap::new(),
106        }
107    }
108
109    /// Create a structured error with code, tool name, missing fields, and example
110    ///
111    /// This helps LLMs self-correct by providing actionable information about what went wrong.
112    pub fn structured_error(
113        code: &str,
114        tool: &str,
115        message: &str,
116        missing_fields: Option<Vec<&str>>,
117        example: Option<Value>,
118    ) -> Self {
119        let mut error_obj = serde_json::json!({
120            "code": code,
121            "tool": tool,
122            "message": message,
123        });
124
125        if let Some(fields) = missing_fields {
126            error_obj["missing_fields"] = serde_json::json!(fields);
127        }
128
129        if let Some(ex) = example {
130            error_obj["example"] = ex;
131        }
132
133        let output = serde_json::to_string_pretty(&serde_json::json!({
134            "error": error_obj
135        }))
136        .unwrap_or_else(|_| format!("Error: {}", message));
137
138        let mut metadata = HashMap::new();
139        metadata.insert("error_code".to_string(), serde_json::json!(code));
140        metadata.insert("tool".to_string(), serde_json::json!(tool));
141
142        Self {
143            output,
144            success: false,
145            metadata,
146        }
147    }
148
149    pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
150        self.metadata.insert(key.into(), value);
151        self
152    }
153
154    /// Truncate `output` to at most `max_bytes`, tagging `metadata.truncated`
155    /// with the original length when truncation occurs.
156    ///
157    /// Uses UTF-8-safe truncation. When the output fits, returns `self`
158    /// unchanged.
159    ///
160    /// # Examples
161    ///
162    /// ```rust
163    /// use codetether_agent::tool::ToolResult;
164    ///
165    /// let r = ToolResult::success("x".repeat(1_000)).truncate_to(100);
166    /// assert!(r.output.len() <= 100 + 32); // + marker
167    /// assert!(r.metadata.contains_key("truncated"));
168    /// ```
169    pub fn truncate_to(mut self, max_bytes: usize) -> Self {
170        if self.output.len() <= max_bytes {
171            return self;
172        }
173        let original_len = self.output.len();
174        let head = crate::util::truncate_bytes_safe(&self.output, max_bytes);
175        self.output =
176            format!("{head}\n…[truncated: {original_len} bytes, showing first {max_bytes}]");
177        self.metadata.insert(
178            "truncated".to_string(),
179            serde_json::json!({
180                "original_bytes": original_len,
181                "shown_bytes": max_bytes,
182            }),
183        );
184        self
185    }
186}
187
188/// Default per-tool-output byte budget. Tunable at runtime via the
189/// `CODETETHER_TOOL_OUTPUT_MAX_BYTES` environment variable. Chosen to keep a
190/// single tool result well under typical provider context windows even after
191/// JSON re-encoding overhead.
192pub const DEFAULT_TOOL_OUTPUT_MAX_BYTES: usize = 64 * 1024;
193
194/// Resolve the current tool-output byte budget from env, falling back to
195/// [`DEFAULT_TOOL_OUTPUT_MAX_BYTES`]. Invalid values fall back to the default.
196pub fn tool_output_budget() -> usize {
197    std::env::var("CODETETHER_TOOL_OUTPUT_MAX_BYTES")
198        .ok()
199        .and_then(|v| v.parse().ok())
200        .unwrap_or(DEFAULT_TOOL_OUTPUT_MAX_BYTES)
201}
202
203/// Registry of available tools
204pub struct ToolRegistry {
205    tools: HashMap<String, Arc<dyn Tool>>,
206    plugin_registry: PluginRegistry,
207}
208
209impl std::fmt::Debug for ToolRegistry {
210    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211        f.debug_struct("ToolRegistry")
212            .field("tools", &self.tools.keys().collect::<Vec<_>>())
213            .finish()
214    }
215}
216
217impl ToolRegistry {
218    pub fn new() -> Self {
219        let _ = std::any::TypeId::of::<McpToolManager>();
220        let _ = std::any::TypeId::of::<McpToolWrapper>();
221        Self {
222            tools: HashMap::new(),
223            plugin_registry: PluginRegistry::from_env(),
224        }
225    }
226
227    /// Get a reference to the plugin registry for managing signed plugins.
228    pub fn plugins(&self) -> &PluginRegistry {
229        &self.plugin_registry
230    }
231
232    /// Register a tool
233    pub fn register(&mut self, tool: Arc<dyn Tool>) {
234        self.tools.insert(tool.id().to_string(), tool);
235    }
236
237    /// Get a tool by ID
238    pub fn get(&self, id: &str) -> Option<Arc<dyn Tool>> {
239        self.tools.get(id).cloned()
240    }
241
242    /// List all tool IDs
243    pub fn list(&self) -> Vec<&str> {
244        self.tools.keys().map(|s| s.as_str()).collect()
245    }
246
247    /// Get tool definitions for LLM
248    pub fn definitions(&self) -> Vec<crate::provider::ToolDefinition> {
249        self.tools
250            .values()
251            .map(|t| crate::provider::ToolDefinition {
252                name: t.id().to_string(),
253                description: t.description().to_string(),
254                parameters: t.parameters(),
255            })
256            .collect()
257    }
258
259    /// Register multiple tools at once
260    pub fn register_all(&mut self, tools: Vec<Arc<dyn Tool>>) {
261        for tool in tools {
262            self.register(tool);
263        }
264    }
265
266    /// Remove a tool by ID
267    pub fn unregister(&mut self, id: &str) -> Option<Arc<dyn Tool>> {
268        self.tools.remove(id)
269    }
270
271    /// Check if a tool exists
272    pub fn contains(&self, id: &str) -> bool {
273        self.tools.contains_key(id)
274    }
275
276    /// Get the number of registered tools
277    pub fn len(&self) -> usize {
278        self.tools.len()
279    }
280
281    /// Check if the registry is empty
282    pub fn is_empty(&self) -> bool {
283        self.tools.is_empty()
284    }
285
286    /// Create registry with all default tools (without batch)
287    pub fn with_defaults() -> Self {
288        let mut registry = Self::new();
289
290        registry.register(Arc::new(file::ReadTool::new()));
291        registry.register(Arc::new(file::WriteTool::new()));
292        registry.register(Arc::new(file::ListTool::new()));
293        registry.register(Arc::new(file::GlobTool::new()));
294        registry.register(Arc::new(file_extras::TreeTool::new()));
295        registry.register(Arc::new(file_extras::FileInfoTool::new()));
296        registry.register(Arc::new(file_extras::HeadTailTool::new()));
297        registry.register(Arc::new(file_extras::DiffTool::new()));
298        registry.register(Arc::new(search::GrepTool::new()));
299        registry.register(Arc::new(advanced_edit::AdvancedEditTool::new()));
300        registry.register(Arc::new(edit::EditTool::new()));
301        registry.register(Arc::new(bash::BashTool::new()));
302        registry.register(Arc::new(lsp::LspTool::with_root(
303            std::env::current_dir()
304                .map(|p| format!("file://{}", p.display()))
305                .unwrap_or_default(),
306        )));
307        registry.register(Arc::new(webfetch::WebFetchTool::new()));
308        registry.register(Arc::new(multiedit::MultiEditTool::new()));
309        registry.register(Arc::new(websearch::WebSearchTool::new()));
310        registry.register(Arc::new(browserctl::BrowserCtlTool::new()));
311        registry.register(Arc::new(codesearch::CodeSearchTool::new()));
312        registry.register(Arc::new(patch::ApplyPatchTool::new()));
313        registry.register(Arc::new(todo::TodoReadTool::new()));
314        registry.register(Arc::new(todo::TodoWriteTool::new()));
315        registry.register(Arc::new(question::QuestionTool::new()));
316        registry.register(Arc::new(task::TaskTool::new()));
317        registry.register(Arc::new(plan::PlanEnterTool::new()));
318        registry.register(Arc::new(plan::PlanExitTool::new()));
319        registry.register(Arc::new(skill::SkillTool::new()));
320        registry.register(Arc::new(memory::MemoryTool::new()));
321        registry.register(Arc::new(ralph::RalphTool::new()));
322        registry.register(Arc::new(prd::PrdTool::new()));
323        registry.register(Arc::new(undo::UndoTool));
324        registry.register(Arc::new(voice::VoiceTool::new()));
325        registry.register(Arc::new(podcast::PodcastTool::new()));
326        registry.register(Arc::new(youtube::YouTubeTool::new()));
327        registry.register(Arc::new(avatar::AvatarTool::new()));
328        registry.register(Arc::new(image::ImageTool::new()));
329        registry.register(Arc::new(mcp_bridge::McpBridgeTool::new()));
330        registry.register(Arc::new(okr::OkrTool::new()));
331        // Edit tools with confirmation (diff display before applying)
332        registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
333        registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
334        // Swarm result sharing between sub-agents
335        registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
336        // Register the invalid tool handler for graceful error handling
337        registry.register(Arc::new(invalid::InvalidTool::new()));
338        // Agent orchestration tool
339        registry.register(Arc::new(agent::AgentTool::new()));
340        // Swarm execution tool for parallel task execution
341        registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
342        // Relay autochat tool for autonomous agent communication
343        registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
344        // Go tool for autonomous OKR→PRD→Ralph pipeline
345        registry.register(Arc::new(go::GoTool::new()));
346        // Kubernetes management tool
347        registry.register(Arc::new(k8s_tool::K8sTool::new()));
348
349        registry
350    }
351
352    /// Create registry with provider for tools that need it (like RalphTool)
353    pub fn with_provider(provider: Arc<dyn Provider>, model: String) -> Self {
354        let mut registry = Self::new();
355
356        registry.register(Arc::new(file::ReadTool::new()));
357        registry.register(Arc::new(file::WriteTool::new()));
358        registry.register(Arc::new(file::ListTool::new()));
359        registry.register(Arc::new(file::GlobTool::new()));
360        registry.register(Arc::new(file_extras::TreeTool::new()));
361        registry.register(Arc::new(file_extras::FileInfoTool::new()));
362        registry.register(Arc::new(file_extras::HeadTailTool::new()));
363        registry.register(Arc::new(file_extras::DiffTool::new()));
364        registry.register(Arc::new(search::GrepTool::new()));
365        registry.register(Arc::new(advanced_edit::AdvancedEditTool::new()));
366        registry.register(Arc::new(edit::EditTool::new()));
367        registry.register(Arc::new(bash::BashTool::new()));
368        registry.register(Arc::new(lsp::LspTool::with_root(
369            std::env::current_dir()
370                .map(|p| format!("file://{}", p.display()))
371                .unwrap_or_default(),
372        )));
373        registry.register(Arc::new(webfetch::WebFetchTool::new()));
374        registry.register(Arc::new(multiedit::MultiEditTool::new()));
375        registry.register(Arc::new(websearch::WebSearchTool::new()));
376        registry.register(Arc::new(browserctl::BrowserCtlTool::new()));
377        registry.register(Arc::new(codesearch::CodeSearchTool::new()));
378        registry.register(Arc::new(patch::ApplyPatchTool::new()));
379        registry.register(Arc::new(todo::TodoReadTool::new()));
380        registry.register(Arc::new(todo::TodoWriteTool::new()));
381        registry.register(Arc::new(question::QuestionTool::new()));
382        registry.register(Arc::new(task::TaskTool::new()));
383        registry.register(Arc::new(plan::PlanEnterTool::new()));
384        registry.register(Arc::new(plan::PlanExitTool::new()));
385        registry.register(Arc::new(skill::SkillTool::new()));
386        registry.register(Arc::new(memory::MemoryTool::new()));
387        registry.register(Arc::new(rlm::RlmTool::new(
388            Arc::clone(&provider),
389            model.clone(),
390            crate::rlm::RlmConfig::default(),
391        )));
392        // RalphTool with provider for autonomous execution
393        registry.register(Arc::new(ralph::RalphTool::with_provider(provider, model)));
394        registry.register(Arc::new(prd::PrdTool::new()));
395        registry.register(Arc::new(undo::UndoTool));
396        registry.register(Arc::new(voice::VoiceTool::new()));
397        registry.register(Arc::new(podcast::PodcastTool::new()));
398        registry.register(Arc::new(youtube::YouTubeTool::new()));
399        registry.register(Arc::new(avatar::AvatarTool::new()));
400        registry.register(Arc::new(image::ImageTool::new()));
401        registry.register(Arc::new(mcp_bridge::McpBridgeTool::new()));
402        registry.register(Arc::new(okr::OkrTool::new()));
403        // Edit tools with confirmation (diff display before applying)
404        registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
405        registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
406        // Swarm result sharing between sub-agents
407        registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
408        // Register the invalid tool handler for graceful error handling
409        registry.register(Arc::new(invalid::InvalidTool::new()));
410        // Agent orchestration tool
411        registry.register(Arc::new(agent::AgentTool::new()));
412        // Swarm execution tool for parallel task execution
413        registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
414        // Relay autochat tool for autonomous agent communication
415        registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
416        // Go tool for autonomous OKR→PRD→Ralph pipeline
417        registry.register(Arc::new(go::GoTool::new()));
418        // Kubernetes management tool
419        registry.register(Arc::new(k8s_tool::K8sTool::new()));
420
421        registry
422    }
423
424    /// Create Arc-wrapped registry with batch tool properly initialized.
425    /// The batch tool needs a weak reference to the registry, so we use
426    /// a two-phase initialization pattern.
427    pub fn with_defaults_arc() -> Arc<Self> {
428        let mut registry = Self::with_defaults();
429
430        // Create batch tool without registry reference
431        let batch_tool = Arc::new(batch::BatchTool::new());
432        registry.register(batch_tool.clone());
433
434        // Wrap registry in Arc
435        let registry = Arc::new(registry);
436
437        // Now give batch tool a weak reference to the registry
438        batch_tool.set_registry(Arc::downgrade(&registry));
439
440        registry
441    }
442
443    /// Create Arc-wrapped registry with provider and batch tool properly initialized.
444    /// The batch tool needs a weak reference to the registry, so we use
445    /// a two-phase initialization pattern.
446    #[allow(dead_code)]
447    pub fn with_provider_arc(provider: Arc<dyn Provider>, model: String) -> Arc<Self> {
448        let mut registry = Self::with_provider(provider, model);
449
450        // Create batch tool without registry reference
451        let batch_tool = Arc::new(batch::BatchTool::new());
452        registry.register(batch_tool.clone());
453
454        // Wrap registry in Arc
455        let registry = Arc::new(registry);
456
457        // Now give batch tool a weak reference to the registry
458        batch_tool.set_registry(Arc::downgrade(&registry));
459
460        registry
461    }
462}
463
464impl Default for ToolRegistry {
465    fn default() -> Self {
466        Self::with_defaults()
467    }
468}