1use futures_util::future::BoxFuture;
4use serde_json::{Value, json};
5use tokio_util::sync::CancellationToken;
6use yapi_agent::{Tool, UpdateSink};
7use yapi_types::event::ToolResult;
8use yapi_types::message::ToolDeclaration;
9
10use super::mutation::with_file_lock;
11use super::{ToolEnv, check_abort, declaration, node_error, string_arg, text_result};
12
13pub struct Write {
15 env: ToolEnv,
16 declaration: ToolDeclaration,
17}
18
19impl Write {
20 pub fn new(env: ToolEnv) -> Write {
22 Write {
23 env,
24 declaration: declaration(
25 "write",
26 "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.".into(),
27 json!({"type":"object","required":["path","content"],"properties":{
28 "path":{"type":"string","description":"Path to the file to write (relative or absolute)"},
29 "content":{"type":"string","description":"Content to write to the file"}}}),
30 ),
31 }
32 }
33}
34
35impl Tool for Write {
36 fn declaration(&self) -> &ToolDeclaration {
37 &self.declaration
38 }
39
40 fn execute(
41 &self,
42 _call_id: String,
43 args: Value,
44 cancel: CancellationToken,
45 _updates: UpdateSink,
46 ) -> BoxFuture<'_, Result<ToolResult, String>> {
47 Box::pin(async move {
48 let path = string_arg(&args, "path")?.to_owned();
49 let content = string_arg(&args, "content")?.to_owned();
50 let absolute = super::path::resolve_to_cwd(&path, &self.env.cwd);
51 with_file_lock(&absolute, async {
52 check_abort(&cancel)?;
53 if let Some(dir) = absolute.parent() {
54 tokio::fs::create_dir_all(dir)
55 .await
56 .map_err(|err| node_error(&err, "mkdir", dir))?;
57 }
58 check_abort(&cancel)?;
59 tokio::fs::write(&absolute, content)
60 .await
61 .map_err(|err| node_error(&err, "open", &absolute))?;
62 check_abort(&cancel)?;
63 Ok(text_result(format!("Successfully wrote to {path}"), None))
64 })
65 .await
66 })
67 }
68}