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