rucora_tools/file/
write.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 FileWriteTool {
30 config: FileToolConfig,
31}
32
33impl FileWriteTool {
34 pub fn new() -> Self {
36 Self {
37 config: FileToolConfig::new(),
38 }
39 }
40
41 pub fn with_allowed_dirs(self, dirs: Vec<PathBuf>) -> Self {
43 Self {
44 config: self.config.with_allowed_dirs(dirs),
45 }
46 }
47
48 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 fn name(&self) -> &str {
66 "file_write"
67 }
68
69 fn description(&self) -> Option<&str> {
71 Some("写入文件内容(有安全限制:仅允许特定扩展名,禁止系统路径)")
72 }
73
74 fn categories(&self) -> &'static [ToolCategory] {
76 &[ToolCategory::File]
77 }
78
79 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 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 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}