robit_agent/tool/
write.rs1use async_trait::async_trait;
4use serde::Deserialize;
5use serde_json::Value;
6
7use super::{resolve_path, Tool, ToolContext, ToolResult};
8use crate::error::Result;
9
10#[derive(Debug, Deserialize)]
11struct WriteArgs {
12 file_path: String,
13 content: String,
14}
15
16pub struct WriteTool;
17
18impl WriteTool {
19 pub fn new() -> Self {
20 Self
21 }
22}
23
24impl Default for WriteTool {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30#[async_trait]
31impl Tool for WriteTool {
32 fn name(&self) -> &str {
33 "write"
34 }
35
36 fn description(&self) -> &str {
37 "Create or overwrite a file. Automatically creates parent directories. Overwrites if the file already exists."
38 }
39
40 fn parameters_schema(&self) -> Value {
41 serde_json::json!({
42 "type": "object",
43 "properties": {
44 "file_path": {
45 "type": "string",
46 "description": "Target file path (relative or absolute)"
47 },
48 "content": {
49 "type": "string",
50 "description": "File content to write"
51 }
52 },
53 "required": ["file_path", "content"]
54 })
55 }
56
57 fn requires_confirmation(&self) -> bool {
58 true
59 }
60
61 async fn execute(&self, args: Value, ctx: &ToolContext) -> Result<ToolResult> {
62 let parsed: WriteArgs = match serde_json::from_value(args) {
63 Ok(a) => a,
64 Err(e) => return Ok(ToolResult::error(format!("Argument parsing failed: {}", e))),
65 };
66
67 if parsed.file_path.trim().is_empty() {
69 return Ok(ToolResult::error("File path cannot be empty".to_string()));
70 }
71
72 let path = resolve_path(&parsed.file_path, &ctx.working_dir);
73
74 if path.is_dir() {
76 return Ok(ToolResult::error(format!(
77 "Path is a directory: {}",
78 path.display()
79 )));
80 }
81
82 let existed = path.exists();
84
85 if let Some(parent) = path.parent() {
87 if !parent.exists() {
88 if let Err(e) = tokio::fs::create_dir_all(parent).await {
89 return Ok(ToolResult::error(format!(
90 "Failed to create directory '{}': {}",
91 parent.display(),
92 e
93 )));
94 }
95 }
96 }
97
98 let content_bytes = parsed.content.as_bytes();
100 match tokio::fs::write(&path, &parsed.content).await {
101 Ok(()) => {
102 let msg = if existed {
103 format!(
104 "Overwritten file: {} ({} bytes)",
105 path.display(),
106 content_bytes.len()
107 )
108 } else {
109 format!(
110 "Created file: {} ({} bytes)",
111 path.display(),
112 content_bytes.len()
113 )
114 };
115 Ok(ToolResult::success(msg))
116 }
117 Err(e) => Ok(ToolResult::error(format!(
118 "Failed to write file '{}': {}",
119 path.display(),
120 e
121 ))),
122 }
123 }
124}