tiny-trae 0.1.0

An AI coding assistant with tool integration
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();
        
        // Register all tools
        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>;
}

// Helper functions for file operations
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)
}