Skip to main content

matrixcode_core/tools/
write.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4
5use super::{Tool, ToolDefinition};
6use crate::approval::RiskLevel;
7use crate::path_validator::{validate_content_size, validate_path};
8
9pub struct WriteTool;
10
11#[async_trait]
12impl Tool for WriteTool {
13    fn definition(&self) -> ToolDefinition {
14        ToolDefinition {
15            name: "write".to_string(),
16            description: "向文件写入内容,若文件不存在则创建。
17
18【重要】写入现有文件前必须先读取:
19- 如果文件已存在,必须先用 read 工具读取当前内容
20- 如果没先读文件,此工具会失败
21- 了解现有内容可防止意外覆盖重要信息
22
23优先用 edit 工具修改现有文件(只发送 diff)
24只在以下情况使用此工具:
25- 创建新文件
26- 完整重写文件(用户明确要求)
27
28路径安全:自动验证路径安全性,阻止路径穿越和系统文件写入"
29                .to_string(),
30            parameters: json!({
31                "type": "object",
32                "properties": {
33                    "path": {
34                        "type": "string",
35                        "description": "要写入的文件路径(会自动验证安全性,阻止路径穿越和系统文件写入)"
36                    },
37                    "content": {
38                        "type": "string",
39                        "description": "要写入的内容(单次写入最大10MB,超大内容请分批写入)"
40                    }
41                },
42                "required": ["path", "content"]
43            }),
44            ..Default::default()
45        }
46    }
47
48    async fn execute(&self, params: Value) -> Result<String> {
49        let path_str = params["path"]
50            .as_str()
51            .ok_or_else(|| anyhow::anyhow!("missing 'path'"))?;
52        let content = params["content"]
53            .as_str()
54            .ok_or_else(|| anyhow::anyhow!("missing 'content'"))?;
55
56        // 1. Validate content size (prevent accidental huge writes)
57        validate_content_size(content)?;
58
59        // 2. Validate path security (prevent path traversal and system file writes)
60        // For writes, we use strict validation (is_write=true)
61        let validated_path = validate_path(path_str, None, true)?;
62
63        // 3. Create parent directories if needed
64        if let Some(parent) = validated_path.parent() {
65            tokio::fs::create_dir_all(parent).await?;
66        }
67
68        // 4. Write the file with validated path
69        let total_bytes = content.len();
70        let size_mb = total_bytes as f64 / 1_000_000.0;
71
72        // Write the file
73        tokio::fs::write(&validated_path, content).await?;
74
75        // 5. Provide helpful feedback based on file size
76        let size_feedback = if size_mb > 1.0 {
77            format!(
78                " ({:.2} MB - large file written successfully. \
79                Consider splitting if this causes performance issues)",
80                size_mb
81            )
82        } else if size_mb > 0.1 {
83            format!(" ({:.2} MB)", size_mb)
84        } else {
85            format!(" ({:.2} KB)", total_bytes as f64 / 1_000.0)
86        };
87
88        Ok(format!(
89            "Successfully wrote {} bytes{} to {}\nPath validated: {}",
90            total_bytes,
91            size_feedback,
92            path_str,
93            validated_path.display()
94        ))
95    }
96
97    fn risk_level(&self) -> RiskLevel {
98        RiskLevel::Mutating
99    }
100}