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
11impl Default for WriteTool {
12    fn default() -> Self {
13        Self::new()
14    }
15}
16
17impl WriteTool {
18    /// Create a new WriteTool
19    pub fn new() -> Self {
20        Self
21    }
22
23    /// Create WriteTool with specific verification strategy (deprecated - verification now at Agent level)
24    pub fn with_verification_strategy(_strategy: crate::tools::code_quality_hook::VerificationStrategy) -> Self {
25        Self // Verification is handled at Agent level now
26    }
27}
28
29#[async_trait]
30impl Tool for WriteTool {
31    fn definition(&self) -> ToolDefinition {
32        ToolDefinition {
33            name: "write".to_string(),
34            description: "向文件写入内容,若文件不存在则创建。
35
36【重要】写入现有文件前必须先读取:
37- 如果文件已存在,必须先用 read 工具读取当前内容
38- 如果没先读文件,此工具会失败
39- 了解现有内容可防止意外覆盖重要信息
40
41【代码质量验证】写入代码文件后 Agent 会自动验证:
42- 根据 verify_strategy 配置决定验证时机
43- 'pre' 策略:写入前验证,失败则阻止写入并返回错误给 AI 纠正
44- 'post' 策略:写入后验证,结果附加在输出中
45- 支持的验证:cargo check / tsc / python -m py_compile / go vet
46
47优先用 edit 工具修改现有文件(只发送 diff)
48只在以下情况使用此工具:
49- 创建新文件
50- 完整重写文件(用户明确要求)
51
52路径安全:自动验证路径安全性,阻止路径穿越和系统文件写入"
53                .to_string(),
54            parameters: json!({
55                "type": "object",
56                "properties": {
57                    "path": {
58                        "type": "string",
59                        "description": "要写入的文件路径(会自动验证安全性,阻止路径穿越和系统文件写入)"
60                    },
61                    "content": {
62                        "type": "string",
63                        "description": "要写入的内容(单次写入最大10MB,超大内容请分批写入)"
64                    }
65                },
66                "required": ["path", "content"]
67            }),
68            ..Default::default()
69        }
70    }
71
72    async fn execute(&self, params: Value) -> Result<String> {
73        let path_str = params["path"]
74            .as_str()
75            .ok_or_else(|| anyhow::anyhow!("missing 'path'"))?;
76
77        // 1. Validate content size (prevent accidental huge writes)
78        let content = params["content"]
79            .as_str()
80            .ok_or_else(|| anyhow::anyhow!("missing 'content'"))?;
81        validate_content_size(content)?;
82
83        // 2. Validate path security (prevent path traversal and system file writes)
84        let validated_path = validate_path(path_str, None, true)?;
85
86        // 3. Create parent directories if needed
87        if let Some(parent) = validated_path.parent() {
88            tokio::fs::create_dir_all(parent).await?;
89        }
90
91        // 4. Write the file
92        let total_bytes = content.len();
93        tokio::fs::write(&validated_path, content).await?;
94
95        // 5. Provide helpful feedback based on file size
96        let size_feedback = if total_bytes > 1_000_000 {
97            format!(
98                " ({:.2} MB - large file written successfully. \
99                Consider splitting if this causes performance issues)",
100                total_bytes as f64 / 1_000_000.0
101            )
102        } else if total_bytes > 100_000 {
103            format!(" ({:.2} MB)", total_bytes as f64 / 1_000_000.0)
104        } else {
105            format!(" ({:.2} KB)", total_bytes as f64 / 1_000.0)
106        };
107
108        Ok(format!(
109            "Successfully wrote {} bytes{} to {}\nPath validated: {}",
110            total_bytes,
111            size_feedback,
112            path_str,
113            validated_path.display()
114        ))
115    }
116
117    fn risk_level(&self) -> RiskLevel {
118        RiskLevel::Mutating
119    }
120}