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;
12pub mod batch;
13pub mod browserctl;
14pub mod codesearch;
15pub mod confirm_edit;
16pub mod confirm_multiedit;
17pub mod edit;
18pub mod file;
19pub mod file_extras;
20pub mod go;
21pub mod image;
22pub mod invalid;
23pub mod k8s_tool;
24pub mod lsp;
25pub mod mcp_bridge;
26pub mod mcp_tools;
27pub mod memory;
28pub mod morph_backend;
29pub mod multiedit;
30pub mod okr;
31pub mod patch;
32pub mod plan;
33pub mod podcast;
34pub mod prd;
35pub mod question;
36pub mod ralph;
37pub mod readonly;
38pub mod relay_autochat;
39pub mod rlm;
40pub mod sandbox;
41pub mod search;
42pub mod skill;
43pub mod swarm_execute;
44pub mod swarm_share;
45pub mod task;
46pub mod todo;
47pub mod undo;
48pub mod voice;
49pub mod webfetch;
50pub mod websearch;
51pub mod youtube;
52
53use anyhow::Result;
54use async_trait::async_trait;
55use serde::{Deserialize, Serialize};
56use serde_json::Value;
57use std::collections::HashMap;
58use std::sync::Arc;
59
60use crate::provider::Provider;
61pub use mcp_tools::{McpToolManager, McpToolWrapper};
62pub use sandbox::{PluginManifest, PluginRegistry, SigningKey, hash_bytes, hash_file};
63
64#[async_trait]
66pub trait Tool: Send + Sync {
67 fn id(&self) -> &str;
69
70 fn name(&self) -> &str;
72
73 fn description(&self) -> &str;
75
76 fn parameters(&self) -> Value;
78
79 async fn execute(&self, args: Value) -> Result<ToolResult>;
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct ToolResult {
86 pub output: String,
87 pub success: bool,
88 #[serde(default)]
89 pub metadata: HashMap<String, Value>,
90}
91
92impl ToolResult {
93 pub fn success(output: impl Into<String>) -> Self {
94 Self {
95 output: output.into(),
96 success: true,
97 metadata: HashMap::new(),
98 }
99 }
100
101 pub fn error(message: impl Into<String>) -> Self {
102 Self {
103 output: message.into(),
104 success: false,
105 metadata: HashMap::new(),
106 }
107 }
108
109 pub fn structured_error(
113 code: &str,
114 tool: &str,
115 message: &str,
116 missing_fields: Option<Vec<&str>>,
117 example: Option<Value>,
118 ) -> Self {
119 let mut error_obj = serde_json::json!({
120 "code": code,
121 "tool": tool,
122 "message": message,
123 });
124
125 if let Some(fields) = missing_fields {
126 error_obj["missing_fields"] = serde_json::json!(fields);
127 }
128
129 if let Some(ex) = example {
130 error_obj["example"] = ex;
131 }
132
133 let output = serde_json::to_string_pretty(&serde_json::json!({
134 "error": error_obj
135 }))
136 .unwrap_or_else(|_| format!("Error: {}", message));
137
138 let mut metadata = HashMap::new();
139 metadata.insert("error_code".to_string(), serde_json::json!(code));
140 metadata.insert("tool".to_string(), serde_json::json!(tool));
141
142 Self {
143 output,
144 success: false,
145 metadata,
146 }
147 }
148
149 pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
150 self.metadata.insert(key.into(), value);
151 self
152 }
153
154 pub fn truncate_to(mut self, max_bytes: usize) -> Self {
170 if self.output.len() <= max_bytes {
171 return self;
172 }
173 let original_len = self.output.len();
174 let head = crate::util::truncate_bytes_safe(&self.output, max_bytes);
175 self.output =
176 format!("{head}\n…[truncated: {original_len} bytes, showing first {max_bytes}]");
177 self.metadata.insert(
178 "truncated".to_string(),
179 serde_json::json!({
180 "original_bytes": original_len,
181 "shown_bytes": max_bytes,
182 }),
183 );
184 self
185 }
186}
187
188pub const DEFAULT_TOOL_OUTPUT_MAX_BYTES: usize = 64 * 1024;
193
194pub fn tool_output_budget() -> usize {
197 std::env::var("CODETETHER_TOOL_OUTPUT_MAX_BYTES")
198 .ok()
199 .and_then(|v| v.parse().ok())
200 .unwrap_or(DEFAULT_TOOL_OUTPUT_MAX_BYTES)
201}
202
203pub struct ToolRegistry {
205 tools: HashMap<String, Arc<dyn Tool>>,
206 plugin_registry: PluginRegistry,
207}
208
209impl std::fmt::Debug for ToolRegistry {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("ToolRegistry")
212 .field("tools", &self.tools.keys().collect::<Vec<_>>())
213 .finish()
214 }
215}
216
217impl ToolRegistry {
218 pub fn new() -> Self {
219 let _ = std::any::TypeId::of::<McpToolManager>();
220 let _ = std::any::TypeId::of::<McpToolWrapper>();
221 Self {
222 tools: HashMap::new(),
223 plugin_registry: PluginRegistry::from_env(),
224 }
225 }
226
227 pub fn plugins(&self) -> &PluginRegistry {
229 &self.plugin_registry
230 }
231
232 pub fn register(&mut self, tool: Arc<dyn Tool>) {
234 self.tools.insert(tool.id().to_string(), tool);
235 }
236
237 pub fn get(&self, id: &str) -> Option<Arc<dyn Tool>> {
239 self.tools.get(id).cloned()
240 }
241
242 pub fn list(&self) -> Vec<&str> {
244 self.tools.keys().map(|s| s.as_str()).collect()
245 }
246
247 pub fn definitions(&self) -> Vec<crate::provider::ToolDefinition> {
249 self.tools
250 .values()
251 .map(|t| crate::provider::ToolDefinition {
252 name: t.id().to_string(),
253 description: t.description().to_string(),
254 parameters: t.parameters(),
255 })
256 .collect()
257 }
258
259 pub fn register_all(&mut self, tools: Vec<Arc<dyn Tool>>) {
261 for tool in tools {
262 self.register(tool);
263 }
264 }
265
266 pub fn unregister(&mut self, id: &str) -> Option<Arc<dyn Tool>> {
268 self.tools.remove(id)
269 }
270
271 pub fn contains(&self, id: &str) -> bool {
273 self.tools.contains_key(id)
274 }
275
276 pub fn len(&self) -> usize {
278 self.tools.len()
279 }
280
281 pub fn is_empty(&self) -> bool {
283 self.tools.is_empty()
284 }
285
286 pub fn with_defaults() -> Self {
288 let mut registry = Self::new();
289
290 registry.register(Arc::new(file::ReadTool::new()));
291 registry.register(Arc::new(file::WriteTool::new()));
292 registry.register(Arc::new(file::ListTool::new()));
293 registry.register(Arc::new(file::GlobTool::new()));
294 registry.register(Arc::new(file_extras::TreeTool::new()));
295 registry.register(Arc::new(file_extras::FileInfoTool::new()));
296 registry.register(Arc::new(file_extras::HeadTailTool::new()));
297 registry.register(Arc::new(file_extras::DiffTool::new()));
298 registry.register(Arc::new(search::GrepTool::new()));
299 registry.register(Arc::new(advanced_edit::AdvancedEditTool::new()));
300 registry.register(Arc::new(edit::EditTool::new()));
301 registry.register(Arc::new(bash::BashTool::new()));
302 registry.register(Arc::new(lsp::LspTool::with_root(
303 std::env::current_dir()
304 .map(|p| format!("file://{}", p.display()))
305 .unwrap_or_default(),
306 )));
307 registry.register(Arc::new(webfetch::WebFetchTool::new()));
308 registry.register(Arc::new(multiedit::MultiEditTool::new()));
309 registry.register(Arc::new(websearch::WebSearchTool::new()));
310 registry.register(Arc::new(browserctl::BrowserCtlTool::new()));
311 registry.register(Arc::new(codesearch::CodeSearchTool::new()));
312 registry.register(Arc::new(patch::ApplyPatchTool::new()));
313 registry.register(Arc::new(todo::TodoReadTool::new()));
314 registry.register(Arc::new(todo::TodoWriteTool::new()));
315 registry.register(Arc::new(question::QuestionTool::new()));
316 registry.register(Arc::new(task::TaskTool::new()));
317 registry.register(Arc::new(plan::PlanEnterTool::new()));
318 registry.register(Arc::new(plan::PlanExitTool::new()));
319 registry.register(Arc::new(skill::SkillTool::new()));
320 registry.register(Arc::new(memory::MemoryTool::new()));
321 registry.register(Arc::new(ralph::RalphTool::new()));
322 registry.register(Arc::new(prd::PrdTool::new()));
323 registry.register(Arc::new(undo::UndoTool));
324 registry.register(Arc::new(voice::VoiceTool::new()));
325 registry.register(Arc::new(podcast::PodcastTool::new()));
326 registry.register(Arc::new(youtube::YouTubeTool::new()));
327 registry.register(Arc::new(avatar::AvatarTool::new()));
328 registry.register(Arc::new(image::ImageTool::new()));
329 registry.register(Arc::new(mcp_bridge::McpBridgeTool::new()));
330 registry.register(Arc::new(okr::OkrTool::new()));
331 registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
333 registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
334 registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
336 registry.register(Arc::new(invalid::InvalidTool::new()));
338 registry.register(Arc::new(agent::AgentTool::new()));
340 registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
342 registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
344 registry.register(Arc::new(go::GoTool::new()));
346 registry.register(Arc::new(k8s_tool::K8sTool::new()));
348
349 registry
350 }
351
352 pub fn with_provider(provider: Arc<dyn Provider>, model: String) -> Self {
354 let mut registry = Self::new();
355
356 registry.register(Arc::new(file::ReadTool::new()));
357 registry.register(Arc::new(file::WriteTool::new()));
358 registry.register(Arc::new(file::ListTool::new()));
359 registry.register(Arc::new(file::GlobTool::new()));
360 registry.register(Arc::new(file_extras::TreeTool::new()));
361 registry.register(Arc::new(file_extras::FileInfoTool::new()));
362 registry.register(Arc::new(file_extras::HeadTailTool::new()));
363 registry.register(Arc::new(file_extras::DiffTool::new()));
364 registry.register(Arc::new(search::GrepTool::new()));
365 registry.register(Arc::new(advanced_edit::AdvancedEditTool::new()));
366 registry.register(Arc::new(edit::EditTool::new()));
367 registry.register(Arc::new(bash::BashTool::new()));
368 registry.register(Arc::new(lsp::LspTool::with_root(
369 std::env::current_dir()
370 .map(|p| format!("file://{}", p.display()))
371 .unwrap_or_default(),
372 )));
373 registry.register(Arc::new(webfetch::WebFetchTool::new()));
374 registry.register(Arc::new(multiedit::MultiEditTool::new()));
375 registry.register(Arc::new(websearch::WebSearchTool::new()));
376 registry.register(Arc::new(browserctl::BrowserCtlTool::new()));
377 registry.register(Arc::new(codesearch::CodeSearchTool::new()));
378 registry.register(Arc::new(patch::ApplyPatchTool::new()));
379 registry.register(Arc::new(todo::TodoReadTool::new()));
380 registry.register(Arc::new(todo::TodoWriteTool::new()));
381 registry.register(Arc::new(question::QuestionTool::new()));
382 registry.register(Arc::new(task::TaskTool::new()));
383 registry.register(Arc::new(plan::PlanEnterTool::new()));
384 registry.register(Arc::new(plan::PlanExitTool::new()));
385 registry.register(Arc::new(skill::SkillTool::new()));
386 registry.register(Arc::new(memory::MemoryTool::new()));
387 registry.register(Arc::new(rlm::RlmTool::new(
388 Arc::clone(&provider),
389 model.clone(),
390 crate::rlm::RlmConfig::default(),
391 )));
392 registry.register(Arc::new(ralph::RalphTool::with_provider(provider, model)));
394 registry.register(Arc::new(prd::PrdTool::new()));
395 registry.register(Arc::new(undo::UndoTool));
396 registry.register(Arc::new(voice::VoiceTool::new()));
397 registry.register(Arc::new(podcast::PodcastTool::new()));
398 registry.register(Arc::new(youtube::YouTubeTool::new()));
399 registry.register(Arc::new(avatar::AvatarTool::new()));
400 registry.register(Arc::new(image::ImageTool::new()));
401 registry.register(Arc::new(mcp_bridge::McpBridgeTool::new()));
402 registry.register(Arc::new(okr::OkrTool::new()));
403 registry.register(Arc::new(confirm_edit::ConfirmEditTool::new()));
405 registry.register(Arc::new(confirm_multiedit::ConfirmMultiEditTool::new()));
406 registry.register(Arc::new(swarm_share::SwarmShareTool::with_defaults()));
408 registry.register(Arc::new(invalid::InvalidTool::new()));
410 registry.register(Arc::new(agent::AgentTool::new()));
412 registry.register(Arc::new(swarm_execute::SwarmExecuteTool::new()));
414 registry.register(Arc::new(relay_autochat::RelayAutoChatTool::new()));
416 registry.register(Arc::new(go::GoTool::new()));
418 registry.register(Arc::new(k8s_tool::K8sTool::new()));
420
421 registry
422 }
423
424 pub fn with_defaults_arc() -> Arc<Self> {
428 let mut registry = Self::with_defaults();
429
430 let batch_tool = Arc::new(batch::BatchTool::new());
432 registry.register(batch_tool.clone());
433
434 let registry = Arc::new(registry);
436
437 batch_tool.set_registry(Arc::downgrade(®istry));
439
440 registry
441 }
442
443 #[allow(dead_code)]
447 pub fn with_provider_arc(provider: Arc<dyn Provider>, model: String) -> Arc<Self> {
448 let mut registry = Self::with_provider(provider, model);
449
450 let batch_tool = Arc::new(batch::BatchTool::new());
452 registry.register(batch_tool.clone());
453
454 let registry = Arc::new(registry);
456
457 batch_tool.set_registry(Arc::downgrade(®istry));
459
460 registry
461 }
462}
463
464impl Default for ToolRegistry {
465 fn default() -> Self {
466 Self::with_defaults()
467 }
468}