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_shell;
14pub mod batch;
15pub mod browserctl;
16pub mod codesearch;
17pub mod confirm_edit;
18pub mod confirm_multiedit;
19pub mod context_browse;
20pub mod context_reset;
21pub mod edit;
22pub mod file;
23pub mod file_extras;
24pub mod go;
25pub mod image;
26pub mod invalid;
27pub mod k8s_tool;
28pub mod lsp;
29pub mod mcp_bridge;
30pub mod mcp_tools;
31pub mod memory;
32pub mod morph_backend;
33pub mod multiedit;
34pub mod okr;
35pub mod patch;
36pub mod plan;
37pub mod podcast;
38pub mod prd;
39pub mod question;
40pub mod ralph;
41pub mod readonly;
42pub mod relay_autochat;
43pub mod rlm;
44pub mod sandbox;
45pub mod search;
46pub mod search_router;
47pub mod session_recall;
48pub mod session_task;
49pub mod skill;
50pub mod swarm_execute;
51pub mod swarm_share;
52pub mod task;
53pub mod todo;
54pub mod undo;
55pub mod voice;
56pub mod webfetch;
57pub mod websearch;
58pub mod youtube;
59
60use anyhow::Result;
61use async_trait::async_trait;
62use serde::{Deserialize, Serialize};
63use serde_json::Value;
64use std::collections::HashMap;
65use std::sync::Arc;
66
67use crate::provider::Provider;
68pub use mcp_tools::{McpToolManager, McpToolWrapper};
69pub use sandbox::{PluginManifest, PluginRegistry, SigningKey, hash_bytes, hash_file};
70
71#[async_trait]
73pub trait Tool: Send + Sync {
74 fn id(&self) -> &str;
76
77 fn name(&self) -> &str;
79
80 fn description(&self) -> &str;
82
83 fn parameters(&self) -> Value;
85
86 async fn execute(&self, args: Value) -> Result<ToolResult>;
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ToolResult {
93 pub output: String,
94 pub success: bool,
95 #[serde(default)]
96 pub metadata: HashMap<String, Value>,
97}
98
99impl ToolResult {
100 pub fn success(output: impl Into<String>) -> Self {
101 Self {
102 output: output.into(),
103 success: true,
104 metadata: HashMap::new(),
105 }
106 }
107
108 pub fn error(message: impl Into<String>) -> Self {
109 Self {
110 output: message.into(),
111 success: false,
112 metadata: HashMap::new(),
113 }
114 }
115
116 pub fn structured_error(
120 code: &str,
121 tool: &str,
122 message: &str,
123 missing_fields: Option<Vec<&str>>,
124 example: Option<Value>,
125 ) -> Self {
126 let mut error_obj = serde_json::json!({
127 "code": code,
128 "tool": tool,
129 "message": message,
130 });
131
132 if let Some(fields) = missing_fields {
133 error_obj["missing_fields"] = serde_json::json!(fields);
134 }
135
136 if let Some(ex) = example {
137 error_obj["example"] = ex;
138 }
139
140 let output = serde_json::to_string_pretty(&serde_json::json!({
141 "error": error_obj
142 }))
143 .unwrap_or_else(|_| format!("Error: {}", message));
144
145 let mut metadata = HashMap::new();
146 metadata.insert("error_code".to_string(), serde_json::json!(code));
147 metadata.insert("tool".to_string(), serde_json::json!(tool));
148
149 Self {
150 output,
151 success: false,
152 metadata,
153 }
154 }
155
156 pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
157 self.metadata.insert(key.into(), value);
158 self
159 }
160
161 pub fn truncate_to(mut self, max_bytes: usize) -> Self {
177 if self.output.len() <= max_bytes {
178 return self;
179 }
180 let original_len = self.output.len();
181 let head = crate::util::truncate_bytes_safe(&self.output, max_bytes);
182 self.output =
183 format!("{head}\n…[truncated: {original_len} bytes, showing first {max_bytes}]");
184 self.metadata.insert(
185 "truncated".to_string(),
186 serde_json::json!({
187 "original_bytes": original_len,
188 "shown_bytes": max_bytes,
189 }),
190 );
191 self
192 }
193}
194
195pub const DEFAULT_TOOL_OUTPUT_MAX_BYTES: usize = 64 * 1024;
200
201pub fn tool_output_budget() -> usize {
204 std::env::var("CODETETHER_TOOL_OUTPUT_MAX_BYTES")
205 .ok()
206 .and_then(|v| v.parse().ok())
207 .unwrap_or(DEFAULT_TOOL_OUTPUT_MAX_BYTES)
208}
209
210pub struct ToolRegistry {
212 tools: HashMap<String, Arc<dyn Tool>>,
213 plugin_registry: PluginRegistry,
214}
215
216impl std::fmt::Debug for ToolRegistry {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 f.debug_struct("ToolRegistry")
219 .field("tools", &self.tools.keys().collect::<Vec<_>>())
220 .finish()
221 }
222}
223
224impl ToolRegistry {
225 pub fn new() -> Self {
226 let _ = std::any::TypeId::of::<McpToolManager>();
227 let _ = std::any::TypeId::of::<McpToolWrapper>();
228 Self {
229 tools: HashMap::new(),
230 plugin_registry: PluginRegistry::from_env(),
231 }
232 }
233
234 pub fn plugins(&self) -> &PluginRegistry {
236 &self.plugin_registry
237 }
238
239 pub fn register(&mut self, tool: Arc<dyn Tool>) {
241 self.tools.insert(tool.id().to_string(), tool);
242 }
243
244 fn register_compat_alias(&mut self, id: &str, inner: Arc<dyn Tool>) {
245 self.register(Arc::new(alias::AliasTool::new(id, inner)));
246 }
247
248 fn register_compat_aliases(&mut self) {
249 self.register_compat_alias("patch", Arc::new(patch::ApplyPatchTool::new()));
250 self.register_compat_alias("file_info", Arc::new(file_extras::FileInfoTool::new()));
251 self.register_compat_alias("head_tail", Arc::new(file_extras::HeadTailTool::new()));
252 self.register_compat_alias("todo_read", Arc::new(todo::TodoReadTool::new()));
253 self.register_compat_alias("todo_write", Arc::new(todo::TodoWriteTool::new()));
254 self.register_compat_alias("mcp_bridge", Arc::new(mcp_bridge::McpBridgeTool::new()));
255 self.register_compat_alias("k8s_tool", Arc::new(k8s_tool::K8sTool::new()));
256 }
257
258 pub fn get(&self, id: &str) -> Option<Arc<dyn Tool>> {
260 self.tools.get(id).cloned()
261 }
262
263 pub fn list(&self) -> Vec<&str> {
265 self.tools.keys().map(|s| s.as_str()).collect()
266 }
267
268 pub fn definitions(&self) -> Vec<crate::provider::ToolDefinition> {
270 self.tools
271 .values()
272 .map(|t| crate::provider::ToolDefinition {
273 name: t.id().to_string(),
274 description: t.description().to_string(),
275 parameters: t.parameters(),
276 })
277 .collect()
278 }
279
280 pub fn register_all(&mut self, tools: Vec<Arc<dyn Tool>>) {
282 for tool in tools {
283 self.register(tool);
284 }
285 }
286
287 pub fn unregister(&mut self, id: &str) -> Option<Arc<dyn Tool>> {
289 self.tools.remove(id)
290 }
291
292 pub fn contains(&self, id: &str) -> bool {
294 self.tools.contains_key(id)
295 }
296
297 pub fn len(&self) -> usize {
299 self.tools.len()
300 }
301
302 pub fn is_empty(&self) -> bool {
304 self.tools.is_empty()
305 }
306
307 pub fn with_defaults() -> Self {
309 let mut registry = Self::new();
310
311 registry.register(Arc::new(file::ReadTool::new()));
312 registry.register(Arc::new(file::WriteTool::new()));
313 registry.register(Arc::new(file::ListTool::new()));
314 registry.register(Arc::new(file::GlobTool::new()));
315 registry.register(Arc::new(file_extras::TreeTool::new()));
316 registry.register(Arc::new(file_extras::FileInfoTool::new()));
317 registry.register(Arc::new(file_extras::HeadTailTool::new()));
318 registry.register(Arc::new(file_extras::DiffTool::new()));
319 registry.register(Arc::new(search::GrepTool::new()));
320 registry.register(Arc::new(advanced_edit::AdvancedEditTool::new()));
321 registry.register(Arc::new(edit::EditTool::new()));
322 registry.register(Arc::new(bash::BashTool::new()));
323 registry.register(Arc::new(lsp::LspTool::with_root(
324 std::env::current_dir()
325 .map(|p| format!("file://{}", p.display()))
326 .unwrap_or_default(),
327 )));
328 registry.register(Arc::new(webfetch::WebFetchTool::new()));
329 registry.register(Arc::new(multiedit::MultiEditTool::new()));
330 registry.register(Arc::new(websearch::WebSearchTool::new()));
331 registry.register(Arc::new(browserctl::BrowserCtlTool::new()));
332 registry.register(Arc::new(codesearch::CodeSearchTool::new()));
333 registry.register(Arc::new(patch::ApplyPatchTool::new()));
334 registry.register(Arc::new(todo::TodoReadTool::new()));
335 registry.register(Arc::new(todo::TodoWriteTool::new()));
336 registry.register(Arc::new(session_task::SessionTaskTool::new()));
337 registry.register(Arc::new(context_reset::ContextResetTool));
338 registry.register(Arc::new(context_browse::ContextBrowseTool));
339 registry.register(Arc::new(question::QuestionTool::new()));
340 registry.register(Arc::new(task::TaskTool::new()));
341 registry.register(Arc::new(plan::PlanEnterTool::new()));
342 registry.register(Arc::new(plan::PlanExitTool::new()));
343 registry.register(Arc::new(skill::SkillTool::new()));
344 registry.register(Arc::new(memory::MemoryTool::new()));
345 registry.register(Arc::new(ralph::RalphTool::new()));
346 registry.register(Arc::new(prd::PrdTool::new()));
347 registry.register(Arc::new(undo::UndoTool));
348 registry.register(Arc::new(voice::VoiceTool::new()));
349 registry.register(Arc::new(podcast::PodcastTool::new()));
350 registry.register(Arc::new(youtube::YouTubeTool::new()));
351 registry.register(Arc::new(avatar::AvatarTool::new()));
352 registry.register(Arc::new(image::ImageTool::new()));
353 registry.register(Arc::new(mcp_bridge::McpBridgeTool::new()));
354 registry.register(Arc::new(okr::OkrTool::new()));
355 registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
357 registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
358 registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
360 registry.register(Arc::new(invalid::InvalidTool::new()));
362 registry.register(Arc::new(agent::AgentTool::new()));
364 registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
366 registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
368 registry.register(Arc::new(go::GoTool::new()));
370 registry.register(Arc::new(k8s_tool::K8sTool::new()));
372 registry.register_compat_aliases();
373
374 registry
375 }
376
377 pub fn with_provider(provider: Arc<dyn Provider>, model: String) -> Self {
379 let mut registry = Self::new();
380
381 registry.register(Arc::new(file::ReadTool::new()));
382 registry.register(Arc::new(file::WriteTool::new()));
383 registry.register(Arc::new(file::ListTool::new()));
384 registry.register(Arc::new(file::GlobTool::new()));
385 registry.register(Arc::new(file_extras::TreeTool::new()));
386 registry.register(Arc::new(file_extras::FileInfoTool::new()));
387 registry.register(Arc::new(file_extras::HeadTailTool::new()));
388 registry.register(Arc::new(file_extras::DiffTool::new()));
389 registry.register(Arc::new(search::GrepTool::new()));
390 registry.register(Arc::new(advanced_edit::AdvancedEditTool::new()));
391 registry.register(Arc::new(edit::EditTool::new()));
392 registry.register(Arc::new(bash::BashTool::new()));
393 registry.register(Arc::new(lsp::LspTool::with_root(
394 std::env::current_dir()
395 .map(|p| format!("file://{}", p.display()))
396 .unwrap_or_default(),
397 )));
398 registry.register(Arc::new(webfetch::WebFetchTool::new()));
399 registry.register(Arc::new(multiedit::MultiEditTool::new()));
400 registry.register(Arc::new(websearch::WebSearchTool::new()));
401 registry.register(Arc::new(browserctl::BrowserCtlTool::new()));
402 registry.register(Arc::new(codesearch::CodeSearchTool::new()));
403 registry.register(Arc::new(patch::ApplyPatchTool::new()));
404 registry.register(Arc::new(todo::TodoReadTool::new()));
405 registry.register(Arc::new(todo::TodoWriteTool::new()));
406 registry.register(Arc::new(session_task::SessionTaskTool::new()));
407 registry.register(Arc::new(context_reset::ContextResetTool));
408 registry.register(Arc::new(context_browse::ContextBrowseTool));
409 registry.register(Arc::new(question::QuestionTool::new()));
410 registry.register(Arc::new(task::TaskTool::new()));
411 registry.register(Arc::new(plan::PlanEnterTool::new()));
412 registry.register(Arc::new(plan::PlanExitTool::new()));
413 registry.register(Arc::new(skill::SkillTool::new()));
414 registry.register(Arc::new(memory::MemoryTool::new()));
415 registry.register(Arc::new(rlm::RlmTool::new(
416 Arc::clone(&provider),
417 model.clone(),
418 crate::rlm::RlmConfig::default(),
419 )));
420 registry.register(Arc::new(session_recall::SessionRecallTool::new(
421 Arc::clone(&provider),
422 model.clone(),
423 crate::rlm::RlmConfig::default(),
424 )));
425 registry.register(Arc::new(ralph::RalphTool::with_provider(provider, model)));
427 registry.register(Arc::new(prd::PrdTool::new()));
428 registry.register(Arc::new(undo::UndoTool));
429 registry.register(Arc::new(voice::VoiceTool::new()));
430 registry.register(Arc::new(podcast::PodcastTool::new()));
431 registry.register(Arc::new(youtube::YouTubeTool::new()));
432 registry.register(Arc::new(avatar::AvatarTool::new()));
433 registry.register(Arc::new(image::ImageTool::new()));
434 registry.register(Arc::new(mcp_bridge::McpBridgeTool::new()));
435 registry.register(Arc::new(okr::OkrTool::new()));
436 registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
438 registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
439 registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
441 registry.register(Arc::new(invalid::InvalidTool::new()));
443 registry.register(Arc::new(agent::AgentTool::new()));
445 registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
447 registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
449 registry.register(Arc::new(go::GoTool::new()));
451 registry.register(Arc::new(k8s_tool::K8sTool::new()));
453 registry.register_compat_aliases();
454
455 registry
456 }
457
458 pub fn with_defaults_arc() -> Arc<Self> {
462 let mut registry = Self::with_defaults();
463
464 let batch_tool = Arc::new(batch::BatchTool::new());
466 registry.register(batch_tool.clone());
467
468 let registry = Arc::new(registry);
470
471 batch_tool.set_registry(Arc::downgrade(®istry));
473
474 registry
475 }
476
477 #[allow(dead_code)]
481 pub fn with_provider_arc(provider: Arc<dyn Provider>, model: String) -> Arc<Self> {
482 let mut registry = Self::with_provider(Arc::clone(&provider), model);
483
484 let batch_tool = Arc::new(batch::BatchTool::new());
486 registry.register(batch_tool.clone());
487
488 let mut providers = crate::provider::ProviderRegistry::new();
492 providers.register(Arc::clone(&provider));
493 registry.register(Arc::new(search_router::SearchTool::new(Arc::new(
494 providers,
495 ))));
496
497 let registry = Arc::new(registry);
499
500 batch_tool.set_registry(Arc::downgrade(®istry));
502
503 registry
504 }
505}
506
507impl Default for ToolRegistry {
508 fn default() -> Self {
509 Self::with_defaults()
510 }
511}
512
513impl ToolRegistry {
514 pub fn register_search(&mut self, providers: Arc<crate::provider::ProviderRegistry>) {
534 self.register(Arc::new(search_router::SearchTool::new(providers)));
535 }
536}