rucora_tools/file/
read.rs1use 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
15pub struct FileReadTool {
29 config: FileToolConfig,
30}
31
32impl FileReadTool {
33 pub fn new() -> Self {
35 Self {
36 config: FileToolConfig::new(),
37 }
38 }
39
40 pub fn with_allowed_dirs(self, dirs: Vec<PathBuf>) -> Self {
42 Self {
43 config: self.config.with_allowed_dirs(dirs),
44 }
45 }
46
47 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 fn name(&self) -> &str {
65 "file_read"
66 }
67
68 fn description(&self) -> Option<&str> {
70 Some("读取文件内容(有安全限制:仅允许特定扩展名,禁止系统路径)")
71 }
72
73 fn categories(&self) -> &'static [ToolCategory] {
75 &[ToolCategory::File]
76 }
77
78 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 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 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}