use async_trait::async_trait;
use std::path::PathBuf;
use crate::integration::HostIntegration;
use crate::tools::{Tool, ToolResponse, ToolError, Permission, validation};
pub struct FileReadTool;
impl FileReadTool {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Tool for FileReadTool {
async fn execute(
&self,
parameters: serde_json::Value,
host: &dyn HostIntegration,
) -> Result<ToolResponse, ToolError> {
let path = validation::require_path(¶meters, "path")?;
validation::validate_safe_path(&path)?;
let content = host.get_file_content(&path).await
.map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
Ok(ToolResponse::success(content))
}
fn requires_permission(&self) -> Permission {
Permission::ReadFile(PathBuf::new())
}
fn description(&self) -> &str {
"Read the contents of a file"
}
fn name(&self) -> &str {
"file_read"
}
fn parameter_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the file to read"
}
},
"required": ["path"]
})
}
fn clone_box(&self) -> Box<dyn Tool> {
Box::new(FileReadTool::new())
}
}
pub struct FileWriteTool;
impl FileWriteTool {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Tool for FileWriteTool {
async fn execute(
&self,
parameters: serde_json::Value,
host: &dyn HostIntegration,
) -> Result<ToolResponse, ToolError> {
let path = validation::require_path(¶meters, "path")?;
let content = validation::require_string(¶meters, "content")?;
validation::validate_safe_path(&path)?;
host.update_file_content(&path, &content).await
.map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
Ok(ToolResponse::with_files(
format!("Successfully wrote {} bytes to {}", content.len(), path.display()),
vec![path]
))
}
fn requires_permission(&self) -> Permission {
Permission::WriteFile(PathBuf::new())
}
fn description(&self) -> &str {
"Write content to a file"
}
fn name(&self) -> &str {
"file_write"
}
fn parameter_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the file to write"
},
"content": {
"type": "string",
"description": "Content to write to the file"
}
},
"required": ["path", "content"]
})
}
fn clone_box(&self) -> Box<dyn Tool> {
Box::new(FileWriteTool::new())
}
}
pub struct FileListTool;
impl FileListTool {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl Tool for FileListTool {
async fn execute(
&self,
parameters: serde_json::Value,
_host: &dyn HostIntegration,
) -> Result<ToolResponse, ToolError> {
let path = validation::require_path(¶meters, "path")?;
validation::validate_safe_path(&path)?;
let entries = std::fs::read_dir(&path)
.map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
let mut files = Vec::new();
let mut dirs = Vec::new();
for entry in entries {
let entry = entry.map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
let path = entry.path();
let name = path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("?")
.to_string();
if path.is_dir() {
dirs.push(name);
} else {
files.push(name);
}
}
dirs.sort();
files.sort();
let mut result = String::new();
if !dirs.is_empty() {
result.push_str("Directories:\n");
for dir in dirs {
result.push_str(&format!(" {}/\n", dir));
}
}
if !files.is_empty() {
if !result.is_empty() {
result.push('\n');
}
result.push_str("Files:\n");
for file in files {
result.push_str(&format!(" {}\n", file));
}
}
if result.is_empty() {
result = "Directory is empty".to_string();
}
Ok(ToolResponse::success(result))
}
fn requires_permission(&self) -> Permission {
Permission::ReadFile(PathBuf::new())
}
fn description(&self) -> &str {
"List files and directories in a directory"
}
fn name(&self) -> &str {
"file_list"
}
fn parameter_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the directory to list"
}
},
"required": ["path"]
})
}
fn clone_box(&self) -> Box<dyn Tool> {
Box::new(FileListTool::new())
}
}