llm_coding_tools_rig/absolute/
write.rs1use llm_coding_tools_core::operations::write_file;
4use llm_coding_tools_core::path::AbsolutePathResolver;
5use llm_coding_tools_core::tool_names;
6use llm_coding_tools_core::{ToolContext, ToolError};
7use rig::completion::ToolDefinition;
8use rig::tool::Tool;
9use schemars::{schema_for, JsonSchema};
10use serde::Deserialize;
11
12#[derive(Debug, Clone, Deserialize, JsonSchema)]
14pub struct WriteToolArgs {
15 pub file_path: String,
17 pub content: String,
19}
20
21#[derive(Debug, Clone, Default)]
23pub struct WriteTool;
24
25impl WriteTool {
26 #[inline]
28 pub fn new() -> Self {
29 Self
30 }
31}
32
33impl Tool for WriteTool {
34 const NAME: &'static str = tool_names::WRITE;
35
36 type Error = ToolError;
37 type Args = WriteToolArgs;
38 type Output = String;
39
40 async fn definition(&self, _prompt: String) -> ToolDefinition {
41 ToolDefinition {
42 name: <Self as Tool>::NAME.to_string(),
43 description: "Write content to a file, creating parent directories if needed. \
44 Overwrites existing files."
45 .to_string(),
46 parameters: serde_json::to_value(schema_for!(WriteToolArgs))
47 .expect("schema generation should not fail"),
48 }
49 }
50
51 async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
52 let resolver = AbsolutePathResolver;
53 write_file(&resolver, &args.file_path, &args.content).await
54 }
55}
56
57impl ToolContext for WriteTool {
58 const NAME: &'static str = tool_names::WRITE;
59
60 fn context(&self) -> &'static str {
61 llm_coding_tools_core::context::WRITE_ABSOLUTE
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use tempfile::TempDir;
69
70 #[tokio::test]
71 async fn writes_new_file() {
72 let temp = TempDir::new().unwrap();
73 let file_path = temp.path().join("new.txt");
74 let tool = WriteTool::new();
75 let result = tool
76 .call(WriteToolArgs {
77 file_path: file_path.to_string_lossy().to_string(),
78 content: "hello".to_string(),
79 })
80 .await
81 .unwrap();
82 assert!(result.contains("5 bytes"));
83 }
84
85 #[tokio::test]
86 async fn rejects_relative_path() {
87 let tool = WriteTool::new();
88 let result = tool
89 .call(WriteToolArgs {
90 file_path: "relative/path.txt".to_string(),
91 content: "content".to_string(),
92 })
93 .await;
94 assert!(matches!(result, Err(ToolError::InvalidPath(_))));
95 }
96}