llm_coding_tools_serdesai/absolute/
write.rs1use async_trait::async_trait;
4use llm_coding_tools_core::operations::write_file;
5use llm_coding_tools_core::path::AbsolutePathResolver;
6use llm_coding_tools_core::tool_names;
7use llm_coding_tools_core::{ToolContext, ToolOutput};
8use serde::Deserialize;
9use serdes_ai::tools::{RunContext, SchemaBuilder, Tool, ToolDefinition, ToolError, ToolResult};
10
11use crate::convert::to_serdes_result;
12
13#[derive(Debug, Deserialize)]
15struct WriteArgs {
16 file_path: String,
18 content: String,
20}
21
22#[derive(Debug, Clone, Default)]
26pub struct WriteTool;
27
28impl WriteTool {
29 #[inline]
31 pub fn new() -> Self {
32 Self
33 }
34}
35
36#[async_trait]
37impl<Deps: Send + Sync> Tool<Deps> for WriteTool {
38 fn definition(&self) -> ToolDefinition {
39 let schema = SchemaBuilder::new()
40 .string("file_path", "Absolute path to the file", true)
41 .string("content", "Content to write to the file", true)
42 .build()
43 .expect("schema build should not fail");
44
45 ToolDefinition::new(
46 tool_names::WRITE,
47 "Write content to a file, creating parent directories if needed. Overwrites existing files.",
48 )
49 .with_parameters(schema)
50 }
51
52 async fn call(&self, _ctx: &RunContext<Deps>, args: serde_json::Value) -> ToolResult {
53 let args: WriteArgs = serde_json::from_value(args)
54 .map_err(|e| ToolError::validation_error(tool_names::WRITE, None, e.to_string()))?;
55
56 let resolver = AbsolutePathResolver;
57 let result = write_file(&resolver, &args.file_path, &args.content).await;
58
59 to_serdes_result(tool_names::WRITE, result.map(ToolOutput::new))
61 }
62}
63
64impl ToolContext for WriteTool {
65 const NAME: &'static str = tool_names::WRITE;
66
67 fn context(&self) -> &'static str {
68 llm_coding_tools_core::context::WRITE_ABSOLUTE
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use serde_json::json;
76 use serdes_ai::tools::RunContext;
77 use tempfile::TempDir;
78
79 fn mock_ctx() -> RunContext<()> {
80 RunContext::new((), "test-model")
81 }
82
83 #[tokio::test]
84 async fn writes_file() {
85 let temp = TempDir::new().unwrap();
86 let file_path = temp.path().join("new.txt");
87 let tool = WriteTool::new();
88
89 let result = tool
90 .call(
91 &mock_ctx(),
92 json!({
93 "file_path": file_path.to_string_lossy(),
94 "content": "hello world"
95 }),
96 )
97 .await
98 .unwrap();
99
100 let text = result.as_text().unwrap();
101 assert!(text.contains("11 bytes"));
102 assert_eq!(std::fs::read_to_string(&file_path).unwrap(), "hello world");
103 }
104}