1pub 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#[async_trait]
86pub trait Tool: Send + Sync {
87 fn id(&self) -> &str;
89
90 fn name(&self) -> &str;
92
93 fn description(&self) -> &str;
95
96 fn parameters(&self) -> Value;
98
99 async fn execute(&self, args: Value) -> Result<ToolResult>;
101}
102
103#[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 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 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
208pub const DEFAULT_TOOL_OUTPUT_MAX_BYTES: usize = 64 * 1024;
213
214pub 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
223pub 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 pub fn plugins(&self) -> &PluginRegistry {
247 &self.plugin_registry
248 }
249
250 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 pub fn get(&self, id: &str) -> Option<Arc<dyn Tool>> {
272 self.tools.get(id).cloned()
273 }
274
275 pub fn list(&self) -> Vec<&str> {
277 self.tools.keys().map(|s| s.as_str()).collect()
278 }
279
280 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 pub fn register_all(&mut self, tools: Vec<Arc<dyn Tool>>) {
294 for tool in tools {
295 self.register(tool);
296 }
297 }
298
299 pub fn unregister(&mut self, id: &str) -> Option<Arc<dyn Tool>> {
301 self.tools.remove(id)
302 }
303
304 pub fn contains(&self, id: &str) -> bool {
306 self.tools.contains_key(id)
307 }
308
309 pub fn len(&self) -> usize {
311 self.tools.len()
312 }
313
314 pub fn is_empty(&self) -> bool {
316 self.tools.is_empty()
317 }
318
319 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 registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
376 registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
377 registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
379 registry.register(Arc::new(invalid::InvalidTool::new()));
381 registry.register(Arc::new(agent::AgentTool::new()));
383 registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
385 registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
387 registry.register(Arc::new(go::GoTool::new()));
389 registry.register(Arc::new(k8s_tool::K8sTool::new()));
391 #[cfg(feature = "tetherscript")]
393 tetherscript::register(&mut registry);
394 registry.register_compat_aliases();
395
396 registry
397 }
398
399 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 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 registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
468 registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
469 registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
471 registry.register(Arc::new(invalid::InvalidTool::new()));
473 registry.register(Arc::new(agent::AgentTool::new()));
475 registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
477 registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
479 registry.register(Arc::new(go::GoTool::new()));
481 registry.register(Arc::new(k8s_tool::K8sTool::new()));
483 #[cfg(feature = "tetherscript")]
485 tetherscript::register(&mut registry);
486 registry.register_compat_aliases();
487
488 registry
489 }
490
491 pub fn with_defaults_arc() -> Arc<Self> {
495 let mut registry = Self::with_defaults();
496
497 let batch_tool = Arc::new(batch::BatchTool::new());
499 registry.register(batch_tool.clone());
500
501 let registry = Arc::new(registry);
503
504 batch_tool.set_registry(Arc::downgrade(®istry));
506
507 registry
508 }
509
510 #[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 let batch_tool = Arc::new(batch::BatchTool::new());
519 registry.register(batch_tool.clone());
520
521 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 let registry = Arc::new(registry);
532
533 batch_tool.set_registry(Arc::downgrade(®istry));
535
536 registry
537 }
538}
539
540impl Default for ToolRegistry {
541 fn default() -> Self {
542 Self::with_defaults()
543 }
544}
545
546impl ToolRegistry {
547 pub fn register_search(&mut self, providers: Arc<crate::provider::ProviderRegistry>) {
567 self.register(Arc::new(search_router::SearchTool::new(providers)));
568 }
569}