Skip to main content

mofa_plugins/tools/
filesystem.rs

1use super::*;
2use serde_json::json;
3use tokio::fs;
4
5/// 文件系统工具 - 读写文件、列出目录
6pub struct FileSystemTool {
7    definition: ToolDefinition,
8    allowed_paths: Vec<String>,
9}
10
11impl FileSystemTool {
12    pub fn new(allowed_paths: Vec<String>) -> Self {
13        Self {
14            definition: ToolDefinition {
15                name: "filesystem".to_string(),
16                description: "File system operations: read files, write files, list directories, check if file exists.".to_string(),
17                parameters: json!({
18                    "type": "object",
19                    "properties": {
20                        "operation": {
21                            "type": "string",
22                            "enum": ["read", "write", "list", "exists", "delete", "mkdir"],
23                            "description": "File operation to perform"
24                        },
25                        "path": {
26                            "type": "string",
27                            "description": "File or directory path"
28                        },
29                        "content": {
30                            "type": "string",
31                            "description": "Content to write (for write operation)"
32                        }
33                    },
34                    "required": ["operation", "path"]
35                }),
36                requires_confirmation: true,
37            },
38            allowed_paths,
39        }
40    }
41
42    /// Create with default allowed paths (temporary directory and current directory)
43    pub fn new_with_defaults() -> PluginResult<Self> {
44        Ok(Self::new(vec![
45            std::env::temp_dir().to_string_lossy().to_string(),
46            std::env::current_dir()?.to_string_lossy().to_string(),
47        ]))
48    }
49
50    fn is_path_allowed(&self, path: &str) -> bool {
51        if self.allowed_paths.is_empty() {
52            return true;
53        }
54        let path = std::path::Path::new(path);
55        self.allowed_paths.iter().any(|allowed| {
56            let allowed_path = std::path::Path::new(allowed);
57            path.starts_with(allowed_path)
58        })
59    }
60}
61
62#[async_trait::async_trait]
63impl ToolExecutor for FileSystemTool {
64    fn definition(&self) -> &ToolDefinition {
65        &self.definition
66    }
67
68    async fn execute(&self, arguments: serde_json::Value) -> PluginResult<serde_json::Value> {
69        let operation = arguments["operation"]
70            .as_str()
71            .ok_or_else(|| anyhow::anyhow!("Operation is required"))?;
72        let path = arguments["path"]
73            .as_str()
74            .ok_or_else(|| anyhow::anyhow!("Path is required"))?;
75
76        if !self.is_path_allowed(path) {
77            return Err(anyhow::anyhow!(
78                "Access denied: path '{}' is not in allowed paths",
79                path
80            ));
81        }
82
83        match operation {
84            "read" => {
85                let content = fs::read_to_string(path).await?;
86                let truncated = if content.len() > 10000 {
87                    format!(
88                        "{}... [truncated, total {} bytes]",
89                        &content[..10000],
90                        content.len()
91                    )
92                } else {
93                    content
94                };
95                Ok(json!({
96                    "success": true,
97                    "content": truncated
98                }))
99            }
100            "write" => {
101                let content = arguments["content"]
102                    .as_str()
103                    .ok_or_else(|| anyhow::anyhow!("Content is required for write operation"))?;
104                fs::write(path, content).await?;
105                Ok(json!({
106                    "success": true,
107                    "message": format!("Written {} bytes to {}", content.len(), path)
108                }))
109            }
110            "list" => {
111                let mut entries = Vec::new();
112                let mut dir = fs::read_dir(path).await?;
113                while let Some(entry) = dir.next_entry().await? {
114                    let metadata = entry.metadata().await?;
115                    entries.push(json!({
116                        "name": entry.file_name().to_string_lossy(),
117                        "is_dir": metadata.is_dir(),
118                        "is_file": metadata.is_file(),
119                        "size": metadata.len()
120                    }));
121                }
122                Ok(json!({
123                    "success": true,
124                    "entries": entries
125                }))
126            }
127            "exists" => {
128                let exists = fs::try_exists(path).await?;
129                Ok(json!({
130                    "success": true,
131                    "exists": exists
132                }))
133            }
134            "delete" => {
135                let metadata = fs::metadata(path).await?;
136                if metadata.is_dir() {
137                    fs::remove_dir_all(path).await?;
138                } else {
139                    fs::remove_file(path).await?;
140                }
141                Ok(json!({
142                    "success": true,
143                    "message": format!("Deleted {}", path)
144                }))
145            }
146            "mkdir" => {
147                fs::create_dir_all(path).await?;
148                Ok(json!({
149                    "success": true,
150                    "message": format!("Created directory {}", path)
151                }))
152            }
153            _ => Err(anyhow::anyhow!("Unknown operation: {}", operation)),
154        }
155    }
156}