use crate::core::{Tool, ToolArgs, ToolError, ToolResult};
use crate::tools::old::state::ToolState;
use anyhow::Result;
use std::fs;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
pub struct DeletePathTool {
name: String,
}
impl DeletePathTool {
pub fn new() -> Self {
Self {
name: "delete_path".to_string(),
}
}
fn parse_params(&self, args: &ToolArgs) -> Result<serde_json::Value, ToolError> {
if let Some(json_str) = args.get_named_arg("json") {
return serde_json::from_str(json_str).map_err(|e| ToolError::Json(e));
}
if !args.named_args.is_empty() {
return Ok(serde_json::to_value(&args.named_args).map_err(|e| ToolError::Json(e))?);
}
if args.len() >= 1 {
let mut params = serde_json::Map::new();
params.insert(
"path".to_string(),
serde_json::Value::String(args.get_arg(0).unwrap().clone()),
);
if args.len() >= 2 {
if let Ok(recursive) = args.get_arg(1).unwrap().parse::<bool>() {
params.insert("recursive".to_string(), serde_json::Value::Bool(recursive));
}
}
return Ok(serde_json::Value::Object(params));
}
Err(ToolError::InvalidArgs {
message: "Insufficient parameters".to_string(),
})
}
}
impl Tool for DeletePathTool {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Delete a file or directory"
}
fn signature(&self) -> &str {
"delete_path(path: str, recursive?: bool)"
}
fn validate_args(&self, args: &ToolArgs) -> Result<(), ToolError> {
let params = self.parse_params(args)?;
let obj = params.as_object().ok_or_else(|| ToolError::InvalidArgs {
message: "Parameters must be an object".to_string(),
})?;
if !obj.contains_key("path") {
return Err(ToolError::InvalidArgs {
message: "Missing required parameter: path".to_string(),
});
}
Ok(())
}
fn execute(&mut self, args: &ToolArgs, state: &Arc<Mutex<ToolState>>) -> Result<ToolResult> {
let params = self.parse_params(args)?;
let obj = params
.as_object()
.ok_or_else(|| anyhow::anyhow!("Invalid parameters"))?;
let path_str = obj
.get("path")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Invalid path parameter"))?;
let recursive = obj
.get("recursive")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let path = PathBuf::from(path_str);
if !path.exists() {
return Ok(ToolResult::error(format!(
"Path not found: {}",
path.display()
)));
}
let is_dir = path.is_dir();
let is_file = path.is_file();
let path_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
let filter = crate::tools::old::search::ConfigurableFilter::new(None);
let mut protected = false;
if path_name == "src" {
protected = true;
}
if !protected {
if let Some(ex_dirs) = &filter.config.exclude_dirs {
if ex_dirs.iter().any(|d| d == path_name) {
protected = true;
}
}
}
if is_dir && protected {
return Ok(ToolResult::error(format!(
"Safety check: Refusing to delete important directory '{}'. Use recursive=true explicitly if needed.",
path_name
)));
}
if is_file {
fs::remove_file(&path).map_err(|e| anyhow::anyhow!("Failed to delete file: {}", e))?;
} else if is_dir {
if recursive {
fs::remove_dir_all(&path)
.map_err(|e| anyhow::anyhow!("Failed to delete directory: {}", e))?;
} else {
fs::remove_dir(&path)
.map_err(|e| anyhow::anyhow!("Failed to delete directory (not empty?): {}. Use recursive=true for non-empty directories.", e))?;
}
}
{
let mut state_guard = state
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock state: {}", e))?;
if state_guard.current_file.as_ref() == Some(&path) {
state_guard.current_file = None;
}
state_guard.push_history(format!(
"Deleted {}: {}",
if is_dir { "directory" } else { "file" },
path.display()
));
}
Ok(ToolResult::success_with_data(
format!(
"Successfully deleted {}: {}",
if is_dir { "directory" } else { "file" },
path.display()
),
serde_json::json!({
"path": path.to_string_lossy(),
"type": if is_dir { "directory" } else { "file" },
"recursive": recursive,
"deleted": true
}),
))
}
fn get_parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Full path to the file or directory to delete"
},
"recursive": {
"type": "boolean",
"description": "Whether to delete directories and their contents recursively",
"default": false
}
},
"required": ["path"]
})
}
}
pub struct MovePathTool {
name: String,
}
impl MovePathTool {
pub fn new() -> Self {
Self {
name: "move_path".to_string(),
}
}
fn parse_params(&self, args: &ToolArgs) -> Result<serde_json::Value, ToolError> {
if let Some(json_str) = args.get_named_arg("json") {
return serde_json::from_str(json_str).map_err(|e| ToolError::Json(e));
}
if !args.named_args.is_empty() {
return Ok(serde_json::to_value(&args.named_args).map_err(|e| ToolError::Json(e))?);
}
if args.len() >= 2 {
let mut params = serde_json::Map::new();
params.insert(
"source".to_string(),
serde_json::Value::String(args.get_arg(0).unwrap().clone()),
);
params.insert(
"destination".to_string(),
serde_json::Value::String(args.get_arg(1).unwrap().clone()),
);
return Ok(serde_json::Value::Object(params));
}
Err(ToolError::InvalidArgs {
message: "Insufficient parameters".to_string(),
})
}
}
impl Tool for MovePathTool {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Move or rename a file or directory"
}
fn signature(&self) -> &str {
"move_path(source: str, destination: str)"
}
fn validate_args(&self, args: &ToolArgs) -> Result<(), ToolError> {
let params = self.parse_params(args)?;
let obj = params.as_object().ok_or_else(|| ToolError::InvalidArgs {
message: "Parameters must be an object".to_string(),
})?;
if !obj.contains_key("source") {
return Err(ToolError::InvalidArgs {
message: "Missing required parameter: source".to_string(),
});
}
if !obj.contains_key("destination") {
return Err(ToolError::InvalidArgs {
message: "Missing required parameter: destination".to_string(),
});
}
Ok(())
}
fn execute(&mut self, args: &ToolArgs, state: &Arc<Mutex<ToolState>>) -> Result<ToolResult> {
let params = self.parse_params(args)?;
let obj = params
.as_object()
.ok_or_else(|| anyhow::anyhow!("Invalid parameters"))?;
let source_str = obj
.get("source")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Invalid source parameter"))?;
let dest_str = obj
.get("destination")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Invalid destination parameter"))?;
let source = PathBuf::from(source_str);
let destination = PathBuf::from(dest_str);
if !source.exists() {
return Ok(ToolResult::error(format!(
"Source path not found: {}",
source.display()
)));
}
if destination.exists() {
return Ok(ToolResult::error(format!(
"Destination already exists: {}",
destination.display()
)));
}
if let Some(parent) = destination.parent() {
fs::create_dir_all(parent).map_err(|e| {
anyhow::anyhow!("Failed to create destination parent directories: {}", e)
})?;
}
let is_dir = source.is_dir();
fs::rename(&source, &destination)
.map_err(|e| anyhow::anyhow!("Failed to move path: {}", e))?;
{
let mut state_guard = state
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock state: {}", e))?;
if state_guard.current_file.as_ref() == Some(&source) {
state_guard.current_file = Some(destination.clone());
}
state_guard.push_history(format!(
"Moved {} from {} to {}",
if is_dir { "directory" } else { "file" },
source.display(),
destination.display()
));
}
Ok(ToolResult::success_with_data(
format!(
"Successfully moved {} from {} to {}",
if is_dir { "directory" } else { "file" },
source.display(),
destination.display()
),
serde_json::json!({
"source": source.to_string_lossy(),
"destination": destination.to_string_lossy(),
"type": if is_dir { "directory" } else { "file" },
"moved": true
}),
))
}
fn get_parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"source": {
"type": "string",
"description": "Full path to the source file or directory"
},
"destination": {
"type": "string",
"description": "Full path to the destination"
}
},
"required": ["source", "destination"]
})
}
}
pub struct CopyPathTool {
name: String,
}
impl CopyPathTool {
pub fn new() -> Self {
Self {
name: "copy_path".to_string(),
}
}
fn parse_params(&self, args: &ToolArgs) -> Result<serde_json::Value, ToolError> {
if let Some(json_str) = args.get_named_arg("json") {
return serde_json::from_str(json_str).map_err(|e| ToolError::Json(e));
}
if !args.named_args.is_empty() {
return Ok(serde_json::to_value(&args.named_args).map_err(|e| ToolError::Json(e))?);
}
if args.len() >= 2 {
let mut params = serde_json::Map::new();
params.insert(
"source".to_string(),
serde_json::Value::String(args.get_arg(0).unwrap().clone()),
);
params.insert(
"destination".to_string(),
serde_json::Value::String(args.get_arg(1).unwrap().clone()),
);
if args.len() >= 3 {
if let Ok(recursive) = args.get_arg(2).unwrap().parse::<bool>() {
params.insert("recursive".to_string(), serde_json::Value::Bool(recursive));
}
}
return Ok(serde_json::Value::Object(params));
}
Err(ToolError::InvalidArgs {
message: "Insufficient parameters".to_string(),
})
}
}
impl Tool for CopyPathTool {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Copy a file or directory"
}
fn signature(&self) -> &str {
"copy_path(source: str, destination: str, recursive?: bool)"
}
fn validate_args(&self, args: &ToolArgs) -> Result<(), ToolError> {
let params = self.parse_params(args)?;
let obj = params.as_object().ok_or_else(|| ToolError::InvalidArgs {
message: "Parameters must be an object".to_string(),
})?;
if !obj.contains_key("source") {
return Err(ToolError::InvalidArgs {
message: "Missing required parameter: source".to_string(),
});
}
if !obj.contains_key("destination") {
return Err(ToolError::InvalidArgs {
message: "Missing required parameter: destination".to_string(),
});
}
Ok(())
}
fn execute(&mut self, args: &ToolArgs, state: &Arc<Mutex<ToolState>>) -> Result<ToolResult> {
let params = self.parse_params(args)?;
let obj = params
.as_object()
.ok_or_else(|| anyhow::anyhow!("Invalid parameters"))?;
let source_str = obj
.get("source")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Invalid source parameter"))?;
let dest_str = obj
.get("destination")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Invalid destination parameter"))?;
let recursive = obj
.get("recursive")
.and_then(|v| v.as_bool())
.unwrap_or(true);
let source = PathBuf::from(source_str);
let destination = PathBuf::from(dest_str);
if !source.exists() {
return Ok(ToolResult::error(format!(
"Source path not found: {}",
source.display()
)));
}
if destination.exists() {
return Ok(ToolResult::error(format!(
"Destination already exists: {}",
destination.display()
)));
}
let is_dir = source.is_dir();
let is_file = source.is_file();
if let Some(parent) = destination.parent() {
fs::create_dir_all(parent).map_err(|e| {
anyhow::anyhow!("Failed to create destination parent directories: {}", e)
})?;
}
if is_file {
fs::copy(&source, &destination)
.map_err(|e| anyhow::anyhow!("Failed to copy file: {}", e))?;
} else if is_dir {
if !recursive {
return Ok(ToolResult::error(
"Cannot copy directory without recursive=true".to_string(),
));
}
self.copy_dir_recursive(&source, &destination)
.map_err(|e| anyhow::anyhow!("Failed to copy directory: {}", e))?;
}
{
let mut state_guard = state
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock state: {}", e))?;
state_guard.push_history(format!(
"Copied {} from {} to {}",
if is_dir { "directory" } else { "file" },
source.display(),
destination.display()
));
}
Ok(ToolResult::success_with_data(
format!(
"Successfully copied {} from {} to {}",
if is_dir { "directory" } else { "file" },
source.display(),
destination.display()
),
serde_json::json!({
"source": source.to_string_lossy(),
"destination": destination.to_string_lossy(),
"type": if is_dir { "directory" } else { "file" },
"recursive": recursive,
"copied": true
}),
))
}
fn get_parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"source": {
"type": "string",
"description": "Full path to the source file or directory"
},
"destination": {
"type": "string",
"description": "Full path to the destination"
},
"recursive": {
"type": "boolean",
"description": "Whether to copy directories recursively",
"default": true
}
},
"required": ["source", "destination"]
})
}
}
impl CopyPathTool {
fn copy_dir_recursive(&self, source: &PathBuf, destination: &PathBuf) -> Result<()> {
fs::create_dir_all(destination)?;
for entry in fs::read_dir(source)? {
let entry = entry?;
let entry_path = entry.path();
let entry_name = entry.file_name();
let dest_path = destination.join(entry_name);
if entry_path.is_dir() {
self.copy_dir_recursive(&entry_path, &dest_path)?;
} else {
fs::copy(&entry_path, &dest_path)?;
}
}
Ok(())
}
}
pub struct CreateDirectoryTool {
name: String,
}
impl CreateDirectoryTool {
pub fn new() -> Self {
Self {
name: "create_directory".to_string(),
}
}
fn parse_params(&self, args: &ToolArgs) -> Result<serde_json::Value, ToolError> {
if let Some(json_str) = args.get_named_arg("json") {
return serde_json::from_str(json_str).map_err(|e| ToolError::Json(e));
}
if !args.named_args.is_empty() {
return Ok(serde_json::to_value(&args.named_args).map_err(|e| ToolError::Json(e))?);
}
if args.len() >= 1 {
let mut params = serde_json::Map::new();
params.insert(
"path".to_string(),
serde_json::Value::String(args.get_arg(0).unwrap().clone()),
);
return Ok(serde_json::Value::Object(params));
}
Err(ToolError::InvalidArgs {
message: "Insufficient parameters".to_string(),
})
}
}
impl Tool for CreateDirectoryTool {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Create a new directory (and parent directories if needed)"
}
fn signature(&self) -> &str {
"create_directory(path: str)"
}
fn validate_args(&self, args: &ToolArgs) -> Result<(), ToolError> {
let params = self.parse_params(args)?;
let obj = params.as_object().ok_or_else(|| ToolError::InvalidArgs {
message: "Parameters must be an object".to_string(),
})?;
if !obj.contains_key("path") {
return Err(ToolError::InvalidArgs {
message: "Missing required parameter: path".to_string(),
});
}
Ok(())
}
fn execute(&mut self, args: &ToolArgs, state: &Arc<Mutex<ToolState>>) -> Result<ToolResult> {
let params = self.parse_params(args)?;
let obj = params
.as_object()
.ok_or_else(|| anyhow::anyhow!("Invalid parameters"))?;
let path_str = obj
.get("path")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Invalid path parameter"))?;
let path = PathBuf::from(path_str);
if path.exists() {
return Ok(ToolResult::error(format!(
"Directory already exists: {}",
path.display()
)));
}
fs::create_dir_all(&path)
.map_err(|e| anyhow::anyhow!("Failed to create directory: {}", e))?;
{
let mut state_guard = state
.lock()
.map_err(|e| anyhow::anyhow!("Failed to lock state: {}", e))?;
state_guard.push_history(format!("Created directory: {}", path.display()));
}
Ok(ToolResult::success_with_data(
format!("Successfully created directory: {}", path.display()),
serde_json::json!({
"path": path.to_string_lossy(),
"type": "directory",
"created": true
}),
))
}
fn get_parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Full path to the directory to create"
}
},
"required": ["path"]
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_delete_path_tool_file() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("test_delete.txt");
fs::write(&test_file, "test content").unwrap();
let mut tool = DeletePathTool::new();
let state = Arc::new(Mutex::new(ToolState::new()));
let args = ToolArgs::with_named_args(
vec![],
vec![("path".to_string(), test_file.to_string_lossy().to_string())]
.into_iter()
.collect(),
);
let result = tool.execute(&args, &state).unwrap();
assert!(result.success);
assert!(!test_file.exists());
}
#[test]
fn test_move_path_tool() {
let temp_dir = TempDir::new().unwrap();
let source = temp_dir.path().join("source.txt");
fs::write(&source, "test content").unwrap();
let destination = temp_dir.path().join("destination.txt");
let mut tool = MovePathTool::new();
let state = Arc::new(Mutex::new(ToolState::new()));
let args = ToolArgs::with_named_args(
vec![],
vec![
("source".to_string(), source.to_string_lossy().to_string()),
(
"destination".to_string(),
destination.to_string_lossy().to_string(),
),
]
.into_iter()
.collect(),
);
let result = tool.execute(&args, &state).unwrap();
assert!(result.success);
assert!(!source.exists());
assert!(destination.exists());
let content = fs::read_to_string(&destination).unwrap();
assert_eq!(content, "test content");
}
#[test]
fn test_copy_path_tool() {
let temp_dir = TempDir::new().unwrap();
let source = temp_dir.path().join("source.txt");
fs::write(&source, "test content").unwrap();
let destination = temp_dir.path().join("copy.txt");
let mut tool = CopyPathTool::new();
let state = Arc::new(Mutex::new(ToolState::new()));
let args = ToolArgs::with_named_args(
vec![],
vec![
("source".to_string(), source.to_string_lossy().to_string()),
(
"destination".to_string(),
destination.to_string_lossy().to_string(),
),
]
.into_iter()
.collect(),
);
let result = tool.execute(&args, &state).unwrap();
assert!(result.success);
assert!(source.exists());
assert!(destination.exists());
let content = fs::read_to_string(&destination).unwrap();
assert_eq!(content, "test content");
}
#[test]
fn test_create_directory_tool() {
let temp_dir = TempDir::new().unwrap();
let mut tool = CreateDirectoryTool::new();
let state = Arc::new(Mutex::new(ToolState::new()));
let dir_path = temp_dir.path().join("new_dir/sub_dir");
let args = ToolArgs::with_named_args(
vec![],
vec![("path".to_string(), dir_path.to_string_lossy().to_string())]
.into_iter()
.collect(),
);
let result = tool.execute(&args, &state).unwrap();
assert!(result.success);
assert!(dir_path.exists());
assert!(dir_path.is_dir());
}
}