Skip to main content

rucora_tools/file/
write.rs

1//! 文件写入工具
2//!
3//! 提供安全的文件写入功能,带有扩展名白名单和路径限制
4
5use async_trait::async_trait;
6use rucora_core::{
7    error::ToolError,
8    tool::{Tool, ToolCategory},
9};
10use serde_json::{Value, json};
11use std::path::PathBuf;
12
13use super::FileToolConfig;
14
15/// 文件写入工具:写入文件内容。
16///
17/// 安全限制:
18/// - 仅允许写入白名单扩展名的文件
19/// - 禁止访问系统敏感路径
20/// - 支持配置允许的工作目录
21///
22/// 输入格式:
23/// ```json
24/// {
25///   "path": "/path/to/file",
26///   "content": "文件内容"
27/// }
28/// ```
29pub struct FileWriteTool {
30    config: FileToolConfig,
31}
32
33impl FileWriteTool {
34    /// 创建一个新的 FileWriteTool 实例。
35    pub fn new() -> Self {
36        Self {
37            config: FileToolConfig::new(),
38        }
39    }
40
41    /// 设置允许的工作目录
42    pub fn with_allowed_dirs(self, dirs: Vec<PathBuf>) -> Self {
43        Self {
44            config: self.config.with_allowed_dirs(dirs),
45        }
46    }
47
48    /// 设置最大文件大小
49    pub fn with_max_file_size(self, size: u64) -> Self {
50        Self {
51            config: self.config.with_max_file_size(size),
52        }
53    }
54}
55
56impl Default for FileWriteTool {
57    fn default() -> Self {
58        Self::new()
59    }
60}
61
62#[async_trait]
63impl Tool for FileWriteTool {
64    /// 返回工具名称。
65    fn name(&self) -> &str {
66        "file_write"
67    }
68
69    /// 返回工具描述。
70    fn description(&self) -> Option<&str> {
71        Some("写入文件内容(有安全限制:仅允许特定扩展名,禁止系统路径)")
72    }
73
74    /// 返回工具分类。
75    fn categories(&self) -> &'static [ToolCategory] {
76        &[ToolCategory::File]
77    }
78
79    /// 返回输入参数的 JSON Schema。
80    fn input_schema(&self) -> Value {
81        json!({
82            "type": "object",
83            "properties": {
84                "path": {
85                    "type": "string",
86                    "description": "文件路径"
87                },
88                "content": {
89                    "type": "string",
90                    "description": "文件内容"
91                }
92            },
93            "required": ["path", "content"]
94        })
95    }
96
97    /// 执行工具的核心逻辑。
98    async fn call(&self, input: Value) -> Result<Value, ToolError> {
99        let path_str = input
100            .get("path")
101            .and_then(|v| v.as_str())
102            .ok_or_else(|| ToolError::Message("缺少必需的 'path' 字段".to_string()))?;
103
104        let content = input
105            .get("content")
106            .and_then(|v| v.as_str())
107            .ok_or_else(|| ToolError::Message("缺少必需的 'content' 字段".to_string()))?;
108
109        // 检查内容大小
110        self.config.check_file_size(content.len() as u64, "内容")?;
111
112        let path = self.config.validate_path_for_write(path_str)?;
113
114        tokio::fs::write(&path, content)
115            .await
116            .map_err(|e| ToolError::Message(format!("写入文件失败:{e}")))?;
117
118        Ok(json!({
119            "path": path_str,
120            "success": true,
121            "bytes_written": content.len()
122        }))
123    }
124}