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