pub mod file_ops;
pub mod shell;
pub mod search;
pub mod code_analysis;
pub mod git;
pub mod project_structure;
pub mod diagnostics;
pub mod completion;
pub mod hover;
pub mod goto_definition;
pub mod edit;
pub mod patch;
pub mod fetch;
pub mod glob;
pub mod router;
pub mod calculator;
use async_trait::async_trait;
use std::path::PathBuf;
use crate::integration::HostIntegration;
#[async_trait]
pub trait Tool: Send + Sync {
async fn execute(
&self,
parameters: serde_json::Value,
host: &dyn HostIntegration,
) -> Result<ToolResponse, ToolError>;
fn requires_permission(&self) -> Permission;
fn description(&self) -> &str;
fn name(&self) -> &str;
fn parameter_schema(&self) -> serde_json::Value;
fn clone_box(&self) -> Box<dyn Tool>;
}
#[derive(Debug, Clone)]
pub struct ToolResponse {
pub content: String,
pub success: bool,
pub metadata: serde_json::Value,
pub affected_files: Vec<PathBuf>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Permission {
ReadFile(PathBuf),
WriteFile(PathBuf),
ExecuteShell,
NetworkAccess,
SystemModification,
EnvironmentAccess,
None,
}
#[derive(Debug, thiserror::Error)]
pub enum ToolError {
#[error("Tool not found: {0}")]
NotFound(String),
#[error("Permission denied: {0}")]
PermissionDenied(String),
#[error("Execution failed: {0}")]
ExecutionFailed(String),
#[error("Invalid parameters: {0}")]
InvalidParameters(String),
#[error("Timeout: {0}")]
Timeout(String),
#[error("Tool unavailable: {0}")]
Unavailable(String),
#[error("Security violation: {0}")]
SecurityViolation(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Git error: {0}")]
Git(#[from] git2::Error),
}
pub struct ToolRegistry {
tools: std::collections::HashMap<String, Box<dyn Tool>>,
enhanced_router: router::ToolRouter,
}
impl ToolRegistry {
pub fn new() -> Self {
Self {
tools: std::collections::HashMap::new(),
enhanced_router: router::ToolRouter::new(),
}
}
pub fn register<T: Tool + 'static>(&mut self, tool: T) {
self.tools.insert(tool.name().to_string(), Box::new(tool));
}
pub fn register_enhanced<T: router::EnhancedTool + Clone + 'static>(&mut self, tool: T) {
let name = tool.name().to_string();
let adapter = router::EnhancedToolAdapter::new(tool.clone());
self.enhanced_router.register(tool);
self.tools.insert(name, Box::new(adapter));
}
pub fn get(&self, name: &str) -> Option<&dyn Tool> {
self.tools.get(name).map(|t| t.as_ref())
}
pub fn get_enhanced(&self, name: &str) -> Option<&dyn router::EnhancedTool> {
self.enhanced_router.get(name)
}
pub fn enhanced_router(&self) -> &router::ToolRouter {
&self.enhanced_router
}
pub fn list_tools(&self) -> Vec<&str> {
let mut tools: Vec<&str> = self.tools.keys().map(|s| s.as_str()).collect();
tools.sort();
tools.dedup();
tools
}
pub fn get_tool_schemas(&self) -> std::collections::HashMap<String, router::ToolSchema> {
self.enhanced_router.get_tool_schemas()
}
pub fn tools_requiring_permission(&self, permission: &Permission) -> Vec<&str> {
self.tools
.iter()
.filter(|(_, tool)| tool.requires_permission() == *permission)
.map(|(name, _)| name.as_str())
.collect()
}
pub fn with_default_tools() -> Self {
let mut registry = Self::new();
registry.register(file_ops::FileReadTool::new());
registry.register(file_ops::FileWriteTool::new());
registry.register(file_ops::FileListTool::new());
registry.register(shell::ShellCommandTool::new());
registry.register(search::FileSearchTool::new());
registry.register(search::ContentSearchTool::new());
registry.register(search::GrepTool::new());
if let Ok(code_analysis) = code_analysis::CodeAnalysisTool::new() {
registry.register(code_analysis);
}
registry.register(git::GitTool::new());
registry.register(project_structure::ProjectStructureTool::new());
registry.register(diagnostics::DiagnosticsTool::new());
registry.register(completion::CompletionTool::new());
registry.register(hover::HoverTool::new());
registry.register(goto_definition::GotoDefinitionTool::new());
registry.register(edit::EditTool::new());
registry.register(patch::PatchTool::new());
if let Ok(fetch_tool) = fetch::FetchTool::new() {
registry.register(fetch_tool);
}
registry.register(glob::GlobTool::new());
registry.register_enhanced(calculator::CalculatorTool::new());
registry
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}
impl ToolResponse {
pub fn success(content: String) -> Self {
Self {
content,
success: true,
metadata: serde_json::Value::Null,
affected_files: Vec::new(),
}
}
pub fn failure(error: String) -> Self {
Self {
content: error,
success: false,
metadata: serde_json::Value::Null,
affected_files: Vec::new(),
}
}
pub fn with_metadata(content: String, metadata: serde_json::Value) -> Self {
Self {
content,
success: true,
metadata,
affected_files: Vec::new(),
}
}
pub fn with_files(content: String, files: Vec<PathBuf>) -> Self {
Self {
content,
success: true,
metadata: serde_json::Value::Null,
affected_files: files,
}
}
}
impl Permission {
pub fn allows_path(&self, path: &PathBuf) -> bool {
match self {
Permission::ReadFile(allowed_path) | Permission::WriteFile(allowed_path) => {
path.starts_with(allowed_path) || allowed_path.starts_with(path)
}
Permission::None => true,
_ => false,
}
}
pub fn is_more_restrictive_than(&self, other: &Permission) -> bool {
match (self, other) {
(Permission::None, _) => false,
(_, Permission::None) => true,
(Permission::ReadFile(_), Permission::WriteFile(_)) => true,
(Permission::ReadFile(_), Permission::ExecuteShell) => true,
(Permission::ReadFile(_), Permission::NetworkAccess) => true,
(Permission::ReadFile(_), Permission::SystemModification) => true,
_ => false,
}
}
pub fn description(&self) -> String {
match self {
Permission::ReadFile(path) => format!("Read file: {}", path.display()),
Permission::WriteFile(path) => format!("Write file: {}", path.display()),
Permission::ExecuteShell => "Execute shell commands".to_string(),
Permission::NetworkAccess => "Access network resources".to_string(),
Permission::SystemModification => "Modify system settings".to_string(),
Permission::EnvironmentAccess => "Access environment variables".to_string(),
Permission::None => "No special permission required".to_string(),
}
}
}
pub mod validation {
use super::*;
pub fn require_string(params: &serde_json::Value, key: &str) -> Result<String, ToolError> {
params
.get(key)
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.ok_or_else(|| ToolError::InvalidParameters(format!("Missing or invalid parameter: {}", key)))
}
pub fn require_path(params: &serde_json::Value, key: &str) -> Result<PathBuf, ToolError> {
let path_str = require_string(params, key)?;
Ok(PathBuf::from(path_str))
}
pub fn require_bool(params: &serde_json::Value, key: &str) -> Result<bool, ToolError> {
params
.get(key)
.and_then(|v| v.as_bool())
.ok_or_else(|| ToolError::InvalidParameters(format!("Missing or invalid parameter: {}", key)))
}
pub fn optional_string(params: &serde_json::Value, key: &str) -> Option<String> {
params.get(key).and_then(|v| v.as_str()).map(|s| s.to_string())
}
pub fn optional_path(params: &serde_json::Value, key: &str) -> Option<PathBuf> {
optional_string(params, key).map(PathBuf::from)
}
pub fn validate_safe_path(path: &PathBuf) -> Result<(), ToolError> {
if path.to_string_lossy().contains("..") {
return Err(ToolError::SecurityViolation(
"Path traversal not allowed".to_string()
));
}
let sensitive_dirs = [
"/etc", "/sys", "/proc", "/dev",
"C:\\Windows", "C:\\System32", "C:\\Program Files",
];
for sensitive in &sensitive_dirs {
if path.starts_with(sensitive) {
return Err(ToolError::SecurityViolation(
format!("Access to {} is not allowed", sensitive)
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_response_creation() {
let success = ToolResponse::success("Operation completed".to_string());
assert!(success.success);
assert_eq!(success.content, "Operation completed");
let failure = ToolResponse::failure("Operation failed".to_string());
assert!(!failure.success);
assert_eq!(failure.content, "Operation failed");
}
#[test]
fn test_permission_path_checking() {
let read_permission = Permission::ReadFile(PathBuf::from("/home/user"));
assert!(read_permission.allows_path(&PathBuf::from("/home/user/file.txt")));
assert!(!read_permission.allows_path(&PathBuf::from("/etc/passwd")));
}
#[test]
fn test_permission_description() {
let permission = Permission::ExecuteShell;
assert_eq!(permission.description(), "Execute shell commands");
let file_permission = Permission::ReadFile(PathBuf::from("/test"));
assert!(file_permission.description().contains("/test"));
}
#[test]
fn test_tool_registry() {
let registry = ToolRegistry::new();
assert_eq!(registry.list_tools().len(), 0);
}
#[test]
fn test_validation_functions() {
use validation::*;
let params = serde_json::json!({
"name": "test",
"path": "/home/user/file.txt",
"enabled": true
});
assert_eq!(require_string(¶ms, "name").unwrap(), "test");
assert_eq!(require_path(¶ms, "path").unwrap(), PathBuf::from("/home/user/file.txt"));
assert_eq!(require_bool(¶ms, "enabled").unwrap(), true);
assert!(require_string(¶ms, "missing").is_err());
assert!(optional_string(¶ms, "missing").is_none());
}
#[test]
fn test_path_validation() {
use validation::validate_safe_path;
assert!(validate_safe_path(&PathBuf::from("safe/path")).is_ok());
assert!(validate_safe_path(&PathBuf::from("../dangerous")).is_err());
assert!(validate_safe_path(&PathBuf::from("/etc/passwd")).is_err());
}
}