- 1
use async_trait::async_trait; - 2
use serde_json::Value; - 3
- 4
use crate::{Tool, ToolContext, ToolOutput}; - 5
- 6
pub struct WriteTool; - 7
- 8
#[async_trait] - 9
impl Tool for WriteTool { - 10
fn name(&self) -> &str { - 11
"write" - 12
} - 13
- 14
fn serves(&self) -> &'static [&'static str] { - 15
&["documents"] - 16
} - 17
- 18
fn description(&self) -> &str { - 19
"Write text content to a file, creating parent directories as needed. Overwrites the file if it exists. Not for Word, Excel or PowerPoint files: use office_apply." - 20
} - 21
- 22
fn schema(&self) -> Value { - 23
serde_json::json!({ - 24
"type": "object", - 25
"properties": { - 26
"path": {"type": "string", "description": "File path (relative to cwd or absolute)"}, - 27
"content": {"type": "string", "description": "Full file content to write"} - 28
}, - 29
"required": ["path", "content"] - 30
}) - 31
} - 32
- 33
fn refusal(&self, args: &Value) -> Option<String> { - 34
let path = args.get("path").and_then(Value::as_str)?; - 35
crate::office_apply::text_tool_refusal(std::path::Path::new(path), "write") - 36
} - 37
- 38
fn claims(&self, args: &Value) -> crate::ResourceClaims { - 39
crate::ResourceClaims { - 40
exclusive: false, - 41
read_only: false, - 42
paths: args - 43
.get("path") - 44
.and_then(|p| p.as_str()) - 45
.map(|p| vec![p.to_string()]) - 46
.unwrap_or_default(), - 47
} - 48
} - 49
- 50
async fn execute(&self, args: &Value, ctx: &ToolContext) -> ToolOutput { - 51
let Some(path_str) = args.get("path").and_then(|p| p.as_str()) else { - 52
return ToolOutput::error("missing required parameter: path"); - 53
}; - 54
let Some(content) = args.get("content").and_then(|c| c.as_str()) else { - 55
return ToolOutput::error("missing required parameter: content"); - 56
}; - 57
let path = ctx.resolve(std::path::Path::new(path_str)); - 58
if let Some(refusal) = crate::office_apply::text_tool_refusal(&path, "write") { - 59
return ToolOutput::error(refusal); - 60
} - 61
- 62
if let Some(parent) = path.parent() - 63
&& let Err(e) = tokio::fs::create_dir_all(parent).await - 64
{ - 65
return ToolOutput::error(format!("cannot create directory {}: {e}", parent.display())); - 66
} - 67
// Temp file + rename: a crash mid-write cannot truncate the target. - 68
let tmp = path.with_extension(format!( - 69
"{}vak-tmp", - 70
path.extension() - 71
.map(|e| format!("{}.", e.to_string_lossy())) - 72
.unwrap_or_default() - 73
)); - 74
let write_res = tokio::fs::write(&tmp, content).await; - 75
let rename_res = match write_res { - 76
Ok(()) => { - 77
let r = std::fs::rename(&tmp, &path); - 78
if r.is_err() { - 79
let _ = std::fs::remove_file(&tmp); - 80
} - 81
r - 82
} - 83
Err(e) => Err(e), - 84
}; - 85
match rename_res { - 86
Ok(()) => { - 87
crate::artifact::emit_file(ctx.sandbox_sink.as_ref(), &path, &ctx.cwd); - 88
ToolOutput::ok(format!( - 89
"wrote {} bytes to {}", - 90
content.len(), - 91
path.display() - 92
)) - 93
} - 94
Err(e) => ToolOutput::error(format!("cannot write {}: {e}", path.display())), - 95
} - 96
} - 97
} - 98
- 99
#[cfg(test)] - 100
mod tests { - 101
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 102
- 103
use super::*; - 104
use crate::{SandboxEvent, SandboxEventSink}; - 105
- 106
#[tokio::test] - 107
async fn write_reports_any_file_as_an_artifact() { - 108
let workspace = tempfile::tempdir().unwrap(); - 109
let (sink, mut events) = SandboxEventSink::new_with_id("write-artifact".into()); - 110
let ctx = ToolContext::new(workspace.path().to_path_buf()).with_sandbox_sink(sink); - 111
- 112
let output = WriteTool - 113
.execute( - 114
&serde_json::json!({ - 115
"path": "reports/market-dashboard.html", - 116
"content": "<!doctype html><title>Market week</title>" - 117
}), - 118
&ctx, - 119
) - 120
.await; - 121
- 122
assert!(!output.is_error, "{}", output.content); - 123
let event = events.try_recv().unwrap(); - 124
assert!(matches!( - 125
event, - 126
SandboxEvent::ArtifactGenerated { path, mime_type, .. } - 127
if path == "reports/market-dashboard.html" && mime_type == "text/html" - 128
)); - 129
} - 130
} - 131
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.