1use std::path::Path;
2
3use async_trait::async_trait;
4use serde_json::{Value, json};
5
6use crate::error::ToolError;
7use crate::tool::{Tool, ToolContext, ToolOutput};
8
9pub struct Write;
11
12#[async_trait]
13impl Tool for Write {
14 fn name(&self) -> &str {
15 "write"
16 }
17
18 fn description(&self) -> &str {
19 "Write content to a file. Creates the file if it doesn't exist, \
20 overwrites if it does. Creates parent directories as needed."
21 }
22
23 fn input_schema(&self) -> Value {
24 json!({
25 "type": "object",
26 "properties": {
27 "file_path": {
28 "type": "string",
29 "description": "Absolute or relative path to the file"
30 },
31 "content": {
32 "type": "string",
33 "description": "The content to write to the file"
34 }
35 },
36 "required": ["file_path", "content"]
37 })
38 }
39
40 async fn execute(&self, input: Value, ctx: &ToolContext) -> Result<ToolOutput, ToolError> {
41 let file_path = input["file_path"]
42 .as_str()
43 .ok_or_else(|| ToolError::InvalidInput("file_path is required".into()))?;
44 let content = input["content"]
45 .as_str()
46 .ok_or_else(|| ToolError::InvalidInput("content is required".into()))?;
47
48 let path = resolve_path(&ctx.working_dir, file_path);
49
50 if let Some(parent) = path.parent() {
51 tokio::fs::create_dir_all(parent)
52 .await
53 .map_err(ToolError::Io)?;
54 }
55
56 tokio::fs::write(&path, content)
57 .await
58 .map_err(ToolError::Io)?;
59
60 let lines = content.lines().count();
61 Ok(ToolOutput::text(format!(
62 "Wrote {lines} lines to {}",
63 path.display()
64 )))
65 }
66}
67
68fn resolve_path(working_dir: &Path, file_path: &str) -> std::path::PathBuf {
69 let p = Path::new(file_path);
70 if p.is_absolute() {
71 p.to_path_buf()
72 } else {
73 working_dir.join(p)
74 }
75}