use anyhow::{Context, Result};
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
use std::process::Command;
use walkdir::WalkDir;
use log::{debug, info, warn};
use crate::config::Config;
use crate::claude::ToolDefinition;
pub mod file_ops;
pub mod search;
pub mod shell;
pub use file_ops::*;
pub use search::*;
pub use shell::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
pub success: bool,
pub output: String,
pub error: Option<String>,
}
impl ToolResult {
pub fn success(output: String) -> Self {
Self {
success: true,
output,
error: None,
}
}
pub fn error(error: String) -> Self {
Self {
success: false,
output: String::new(),
error: Some(error),
}
}
}
pub struct ToolSystem {
config: Config,
tools: HashMap<String, Box<dyn Tool>>,
}
impl ToolSystem {
pub fn new(config: Config) -> Self {
let mut tools: HashMap<String, Box<dyn Tool>> = HashMap::new();
tools.insert("read_file".to_string(), Box::new(ReadFileTool));
tools.insert("list_files".to_string(), Box::new(ListFilesTool));
tools.insert("edit_file".to_string(), Box::new(EditFileTool));
tools.insert("search_text".to_string(), Box::new(SearchTextTool));
tools.insert("ripgrep".to_string(), Box::new(RipgrepTool));
tools.insert("bash".to_string(), Box::new(BashTool));
Self { config, tools }
}
pub fn get_tool_definitions(&self) -> Vec<ToolDefinition> {
self.tools
.iter()
.map(|(name, tool)| tool.get_definition(name))
.collect()
}
pub async fn execute_tool(&self, name: &str, input: Value) -> Result<ToolResult> {
debug!("Executing tool: {} with input: {:?}", name, input);
match self.tools.get(name) {
Some(tool) => {
let result = tool.execute(input, &self.config).await?;
debug!("Tool {} executed successfully", name);
Ok(result)
}
None => {
warn!("Unknown tool: {}", name);
Ok(ToolResult::error(format!("Unknown tool: {}", name)))
}
}
}
}
#[async_trait::async_trait]
pub trait Tool: Send + Sync {
fn get_definition(&self, name: &str) -> ToolDefinition;
async fn execute(&self, input: Value, config: &Config) -> Result<ToolResult>;
}
pub fn is_ignored_path(path: &Path, ignore_patterns: &[String]) -> bool {
let path_str = path.to_string_lossy();
ignore_patterns.iter().any(|pattern| {
path_str.contains(pattern) ||
path.components().any(|comp| comp.as_os_str().to_string_lossy() == **pattern)
})
}
pub fn get_file_content(path: &Path) -> Result<String> {
std::fs::read_to_string(path)
.with_context(|| format!("Failed to read file: {}", path.display()))
}
pub fn write_file_content(path: &Path, content: &str) -> Result<()> {
std::fs::write(path, content)
.with_context(|| format!("Failed to write file: {}", path.display()))
}
pub fn list_directory(path: &Path, ignore_patterns: &[String]) -> Result<Vec<String>> {
let mut files = Vec::new();
for entry in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
if is_ignored_path(path, ignore_patterns) {
continue;
}
if path.is_file() {
files.push(path.to_string_lossy().to_string());
}
}
files.sort();
Ok(files)
}