agent_code_lib/tools/
file_write.rs1use async_trait::async_trait;
4use serde_json::json;
5use std::path::PathBuf;
6
7use super::{Tool, ToolContext, ToolResult};
8use crate::error::ToolError;
9
10pub struct FileWriteTool;
11
12#[async_trait]
13impl Tool for FileWriteTool {
14 fn name(&self) -> &'static str {
15 "FileWrite"
16 }
17
18 fn description(&self) -> &'static str {
19 "Writes content to a file, creating it if needed or overwriting if it exists."
20 }
21
22 fn input_schema(&self) -> serde_json::Value {
23 json!({
24 "type": "object",
25 "required": ["file_path", "content"],
26 "properties": {
27 "file_path": {
28 "type": "string",
29 "description": "Absolute path to the file"
30 },
31 "content": {
32 "type": "string",
33 "description": "Content to write"
34 }
35 }
36 })
37 }
38
39 fn is_read_only(&self) -> bool {
40 false
41 }
42
43 fn get_path(&self, input: &serde_json::Value) -> Option<PathBuf> {
44 input
45 .get("file_path")
46 .and_then(|v| v.as_str())
47 .map(PathBuf::from)
48 }
49
50 async fn call(
51 &self,
52 input: serde_json::Value,
53 _ctx: &ToolContext,
54 ) -> Result<ToolResult, ToolError> {
55 let file_path = input
56 .get("file_path")
57 .and_then(|v| v.as_str())
58 .ok_or_else(|| ToolError::InvalidInput("'file_path' is required".into()))?;
59
60 let content = input
61 .get("content")
62 .and_then(|v| v.as_str())
63 .ok_or_else(|| ToolError::InvalidInput("'content' is required".into()))?;
64
65 let path = PathBuf::from(file_path);
66
67 if let Some(parent) = path.parent() {
69 tokio::fs::create_dir_all(parent).await.map_err(|e| {
70 ToolError::ExecutionFailed(format!(
71 "Failed to create directory {}: {e}",
72 parent.display()
73 ))
74 })?;
75 }
76
77 tokio::fs::write(&path, content)
78 .await
79 .map_err(|e| ToolError::ExecutionFailed(format!("Failed to write {file_path}: {e}")))?;
80
81 let lines = content.lines().count();
82 let bytes = content.len();
83 Ok(ToolResult::success(format!(
84 "Wrote {lines} lines ({bytes} bytes) to {file_path}"
85 )))
86 }
87}