Skip to main content

adk_devtools/tools/
write.rs

1//! `write_file` — create or overwrite a file in the workspace.
2
3use std::sync::Arc;
4
5use adk_core::{Result, Tool, ToolContext};
6use async_trait::async_trait;
7use serde_json::{Value, json};
8
9use crate::error::DevToolError;
10use crate::tools::read::require_str;
11use crate::workspace::Workspace;
12
13/// Creates or overwrites a file (creating parent directories as needed).
14pub struct WriteFileTool {
15    workspace: Workspace,
16}
17
18impl WriteFileTool {
19    /// Create a `write_file` tool bound to `workspace`.
20    pub fn new(workspace: Workspace) -> Self {
21        Self { workspace }
22    }
23}
24
25#[async_trait]
26impl Tool for WriteFileTool {
27    fn name(&self) -> &str {
28        "write_file"
29    }
30
31    fn description(&self) -> &str {
32        "Create a new file or overwrite an existing one with the given content. \
33         Parent directories are created automatically."
34    }
35
36    fn parameters_schema(&self) -> Option<Value> {
37        Some(json!({
38            "type": "object",
39            "properties": {
40                "path": { "type": "string", "description": "Path relative to the workspace root." },
41                "content": { "type": "string", "description": "The full file content to write." }
42            },
43            "required": ["path", "content"]
44        }))
45    }
46
47    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
48        if !self.workspace.is_writable() {
49            return Err(DevToolError::ReadOnly("write_file").into());
50        }
51        let path = require_str(&args, "path")?;
52        let content = require_str(&args, "content")?;
53        let resolved = self.workspace.resolve(&path)?;
54
55        if let Some(parent) = resolved.parent() {
56            tokio::fs::create_dir_all(parent).await.map_err(DevToolError::from)?;
57        }
58        self.workspace.write_contained(&resolved, content.as_bytes()).await?;
59        // A freshly-written file is considered "read" (its content is known).
60        self.workspace.mark_read(&resolved);
61
62        Ok(json!({
63            "path": self.workspace.display(&resolved),
64            "bytes_written": content.len(),
65            "message": format!("Wrote {} bytes to {}", content.len(), self.workspace.display(&resolved)),
66        }))
67    }
68}