use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub mod bash;
pub mod editor;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolInfo {
pub name: String,
pub description: String,
pub input_schema: serde_json::Value,
}
#[async_trait]
pub trait Tool: Send + Sync {
fn info(&self) -> ToolInfo;
async fn execute(&self, params: serde_json::Value) -> Result<String>;
}
pub struct ToolRegistry {
tools: HashMap<String, Box<dyn Tool>>,
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
impl ToolRegistry {
pub fn new() -> Self {
let mut tools: HashMap<String, Box<dyn Tool>> = HashMap::new();
let bash_tool = bash::BashTool::new();
tools.insert(bash_tool.info().name.clone(), Box::new(bash_tool));
let editor_tool = editor::EditorTool::new();
tools.insert(editor_tool.info().name.clone(), Box::new(editor_tool));
Self { tools }
}
pub fn get(&self, name: &str) -> Option<&dyn Tool> {
self.tools.get(name).map(|tool| tool.as_ref())
}
pub fn all_tools(&self) -> Vec<ToolInfo> {
self.tools.values().map(|t| t.info()).collect()
}
pub async fn execute(&self, name: &str, params: serde_json::Value) -> Result<String> {
match self.tools.get(name) {
Some(tool) => tool.execute(params).await,
None => Err(anyhow::anyhow!("Tool '{}' not found", name)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub tool: String,
pub params: serde_json::Value,
}
pub fn parse_tool_calls(response: &str) -> Vec<ToolCall> {
let mut tool_calls = Vec::new();
let tool_use_re = regex::Regex::new(r"(?s)<tool_use>\s*(\{.*?\})\s*</tool_use>").unwrap();
for cap in tool_use_re.captures_iter(response) {
if let Ok(tool_call) = serde_json::from_str::<ToolCall>(&cap[1]) {
tool_calls.push(tool_call);
}
}
let (reads, execs) = crate::utils::extract_commands(response);
for path in reads {
tool_calls.push(ToolCall {
tool: "editor".to_string(),
params: serde_json::json!({
"command": "view",
"path": path
}),
});
}
for cmd in execs {
tool_calls.push(ToolCall {
tool: "bash".to_string(),
params: serde_json::json!({
"command": cmd
}),
});
}
tool_calls
}
pub fn format_tool_output(tool_name: &str, output: &str) -> String {
let colored_header = match tool_name {
"read_file" => format!("\x1b[34m[{}]\x1b[0m", tool_name.to_uppercase()),
"bash" | "exec" => format!("\x1b[38;5;208m[{}]\x1b[0m", tool_name.to_uppercase()),
_ => format!("[{}]", tool_name.to_uppercase()),
};
format!("\n{}\n{}", colored_header, output)
}