codetether_agent/tool/
mod.rs1pub 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#[async_trait]
67pub trait Tool: Send + Sync {
68 fn id(&self) -> &str;
70
71 fn name(&self) -> &str;
73
74 fn description(&self) -> &str;
76
77 fn parameters(&self) -> Value;
79
80 async fn execute(&self, args: Value) -> Result<ToolResult>;
82}
83
84#[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 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 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
189pub const DEFAULT_TOOL_OUTPUT_MAX_BYTES: usize = 64 * 1024;
194
195pub 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
204pub 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 pub fn plugins(&self) -> &PluginRegistry {
230 &self.plugin_registry
231 }
232
233 pub fn register(&mut self, tool: Arc<dyn Tool>) {
235 self.tools.insert(tool.id().to_string(), tool);
236 }
237
238 pub fn get(&self, id: &str) -> Option<Arc<dyn Tool>> {
240 self.tools.get(id).cloned()
241 }
242
243 pub fn list(&self) -> Vec<&str> {
245 self.tools.keys().map(|s| s.as_str()).collect()
246 }
247
248 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 pub fn register_all(&mut self, tools: Vec<Arc<dyn Tool>>) {
262 for tool in tools {
263 self.register(tool);
264 }
265 }
266
267 pub fn unregister(&mut self, id: &str) -> Option<Arc<dyn Tool>> {
269 self.tools.remove(id)
270 }
271
272 pub fn contains(&self, id: &str) -> bool {
274 self.tools.contains_key(id)
275 }
276
277 pub fn len(&self) -> usize {
279 self.tools.len()
280 }
281
282 pub fn is_empty(&self) -> bool {
284 self.tools.is_empty()
285 }
286
287 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 registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
334 registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
335 registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
337 registry.register(Arc::new(invalid::InvalidTool::new()));
339 registry.register(Arc::new(agent::AgentTool::new()));
341 registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
343 registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
345 registry.register(Arc::new(go::GoTool::new()));
347 registry.register(Arc::new(k8s_tool::K8sTool::new()));
349
350 registry
351 }
352
353 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 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 registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
406 registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
407 registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
409 registry.register(Arc::new(invalid::InvalidTool::new()));
411 registry.register(Arc::new(agent::AgentTool::new()));
413 registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
415 registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
417 registry.register(Arc::new(go::GoTool::new()));
419 registry.register(Arc::new(k8s_tool::K8sTool::new()));
421
422 registry
423 }
424
425 pub fn with_defaults_arc() -> Arc<Self> {
429 let mut registry = Self::with_defaults();
430
431 let batch_tool = Arc::new(batch::BatchTool::new());
433 registry.register(batch_tool.clone());
434
435 let registry = Arc::new(registry);
437
438 batch_tool.set_registry(Arc::downgrade(®istry));
440
441 registry
442 }
443
444 #[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 let batch_tool = Arc::new(batch::BatchTool::new());
453 registry.register(batch_tool.clone());
454
455 let registry = Arc::new(registry);
457
458 batch_tool.set_registry(Arc::downgrade(®istry));
460
461 registry
462 }
463}
464
465impl Default for ToolRegistry {
466 fn default() -> Self {
467 Self::with_defaults()
468 }
469}