1use anyhow::Result;
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11pub mod bash;
12pub mod editor;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ToolInfo {
17 pub name: String,
18 pub description: String,
19 pub input_schema: serde_json::Value,
20}
21
22#[async_trait]
24pub trait Tool: Send + Sync {
25 fn info(&self) -> ToolInfo;
27
28 async fn execute(&self, params: serde_json::Value) -> Result<String>;
30}
31
32pub struct ToolRegistry {
34 tools: HashMap<String, Box<dyn Tool>>,
35}
36
37impl Default for ToolRegistry {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl ToolRegistry {
44 pub fn new() -> Self {
46 let mut tools: HashMap<String, Box<dyn Tool>> = HashMap::new();
47
48 let bash_tool = bash::BashTool::new();
50 tools.insert(bash_tool.info().name.clone(), Box::new(bash_tool));
51
52 let editor_tool = editor::EditorTool::new();
54 tools.insert(editor_tool.info().name.clone(), Box::new(editor_tool));
55
56 Self { tools }
57 }
58
59 pub fn get(&self, name: &str) -> Option<&dyn Tool> {
61 self.tools.get(name).map(|tool| tool.as_ref())
62 }
63
64 pub fn all_tools(&self) -> Vec<ToolInfo> {
66 self.tools.values().map(|t| t.info()).collect()
67 }
68
69 pub async fn execute(&self, name: &str, params: serde_json::Value) -> Result<String> {
71 match self.tools.get(name) {
72 Some(tool) => tool.execute(params).await,
73 None => Err(anyhow::anyhow!("Tool '{}' not found", name)),
74 }
75 }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct ToolCall {
81 pub tool: String,
82 pub params: serde_json::Value,
83}
84
85pub fn parse_tool_calls(response: &str) -> Vec<ToolCall> {
87 let mut tool_calls = Vec::new();
88
89 let tool_use_re = regex::Regex::new(r"(?s)<tool_use>\s*(\{.*?\})\s*</tool_use>").unwrap();
95
96 for cap in tool_use_re.captures_iter(response) {
97 if let Ok(tool_call) = serde_json::from_str::<ToolCall>(&cap[1]) {
98 tool_calls.push(tool_call);
99 }
100 }
101
102 let (reads, execs) = crate::utils::extract_commands(response);
104
105 for path in reads {
107 tool_calls.push(ToolCall {
108 tool: "editor".to_string(),
109 params: serde_json::json!({
110 "command": "view",
111 "path": path
112 }),
113 });
114 }
115
116 for cmd in execs {
118 tool_calls.push(ToolCall {
119 tool: "bash".to_string(),
120 params: serde_json::json!({
121 "command": cmd
122 }),
123 });
124 }
125
126 tool_calls
127}
128
129pub fn format_tool_output(tool_name: &str, output: &str) -> String {
131 let colored_header = match tool_name {
133 "read_file" => format!("\x1b[34m[{}]\x1b[0m", tool_name.to_uppercase()),
134 "bash" | "exec" => format!("\x1b[38;5;208m[{}]\x1b[0m", tool_name.to_uppercase()),
135 _ => format!("[{}]", tool_name.to_uppercase()),
136 };
137
138 format!("\n{}\n{}", colored_header, output)
140}