Skip to main content

rucora_tools/file/
read.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/// }
27/// ```
28pub struct FileReadTool {
29    config: FileToolConfig,
30}
31
32impl FileReadTool {
33    /// 创建一个新的 FileReadTool 实例。
34    pub fn new() -> Self {
35        Self {
36            config: FileToolConfig::new(),
37        }
38    }
39
40    /// 设置允许的工作目录
41    pub fn with_allowed_dirs(self, dirs: Vec<PathBuf>) -> Self {
42        Self {
43            config: self.config.with_allowed_dirs(dirs),
44        }
45    }
46
47    /// 设置最大文件大小
48    pub fn with_max_file_size(self, size: u64) -> Self {
49        Self {
50            config: self.config.with_max_file_size(size),
51        }
52    }
53}
54
55impl Default for FileReadTool {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61#[async_trait]
62impl Tool for FileReadTool {
63    /// 返回工具名称。
64    fn name(&self) -> &str {
65        "file_read"
66    }
67
68    /// 返回工具描述。
69    fn description(&self) -> Option<&str> {
70        Some("读取文件内容(有安全限制:仅允许特定扩展名,禁止系统路径)")
71    }
72
73    /// 返回工具分类。
74    fn categories(&self) -> &'static [ToolCategory] {
75        &[ToolCategory::File]
76    }
77
78    /// 返回输入参数的 JSON Schema。
79    fn input_schema(&self) -> Value {
80        json!({
81            "type": "object",
82            "properties": {
83                "path": {
84                    "type": "string",
85                    "description": "文件路径(相对路径或绝对路径)"
86                }
87            },
88            "required": ["path"]
89        })
90    }
91
92    /// 执行工具的核心逻辑。
93    async fn call(&self, input: Value) -> Result<Value, ToolError> {
94        let path_str = input
95            .get("path")
96            .and_then(|v| v.as_str())
97            .ok_or_else(|| ToolError::Message("缺少必需的 'path' 字段".to_string()))?;
98
99        let path = self.config.validate_path_for_read(path_str)?;
100
101        // 检查文件大小
102        let metadata = tokio::fs::metadata(&path)
103            .await
104            .map_err(|e| ToolError::Message(format!("无法获取文件信息:{e}")))?;
105
106        self.config.check_file_size(metadata.len(), "文件")?;
107
108        let content = tokio::fs::read_to_string(&path)
109            .await
110            .map_err(|e| ToolError::Message(format!("读取文件失败:{e}")))?;
111
112        Ok(json!({
113            "path": path_str,
114            "content": content,
115            "size": content.len()
116        }))
117    }
118}