1use crate::types::*;
2use std::fs;
3
4pub struct FileReadTool;
5
6impl FileReadTool {
7 pub fn new() -> Self {
8 Self
9 }
10
11 pub fn name(&self) -> &str {
12 "FileRead"
13 }
14
15 pub fn description(&self) -> &str {
16 "Read files from filesystem"
17 }
18
19 pub fn input_schema(&self) -> ToolInputSchema {
20 ToolInputSchema {
21 schema_type: "object".to_string(),
22 properties: serde_json::json!({
23 "path": {
24 "type": "string",
25 "description": "The file path to read"
26 }
27 }),
28 required: Some(vec!["path".to_string()]),
29 }
30 }
31
32 pub async fn execute(
33 &self,
34 input: serde_json::Value,
35 context: &ToolContext,
36 ) -> Result<ToolResult, crate::error::AgentError> {
37 let path = input["path"]
38 .as_str()
39 .ok_or_else(|| crate::error::AgentError::Tool("path is required".to_string()))?;
40
41 let path_buf = std::path::PathBuf::from(path);
43
44 let final_path = if path_buf.is_absolute() && !path_buf.exists() {
46 if let Some(filename) = path_buf.file_name() {
48 std::path::Path::new(&context.cwd).join(filename)
49 } else {
50 std::path::Path::new(&context.cwd).join(path)
52 }
53 } else if path_buf.is_relative() {
54 std::path::Path::new(&context.cwd).join(path)
55 } else {
56 path_buf
57 };
58
59 let content =
60 fs::read_to_string(&final_path).map_err(|e| crate::error::AgentError::Io(e))?;
61
62 Ok(ToolResult {
63 result_type: "text".to_string(),
64 tool_use_id: "".to_string(),
65 content,
66 is_error: None,
67 })
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn test_file_read_tool_name() {
77 let tool = FileReadTool::new();
78 assert_eq!(tool.name(), "FileRead");
79 }
80
81 #[test]
82 fn test_file_read_tool_description_contains_read() {
83 let tool = FileReadTool::new();
84 assert!(tool.description().to_lowercase().contains("read"));
85 }
86
87 #[test]
88 fn test_file_read_tool_has_path_in_schema() {
89 let tool = FileReadTool::new();
90 let schema = tool.input_schema();
91 assert!(schema.properties.get("path").is_some());
92 }
93
94 #[tokio::test]
95 async fn test_file_read_tool_execute_reads_file() {
96 let temp_dir = std::env::temp_dir();
98 let temp_file = temp_dir.join("test_read_file.txt");
99 std::fs::write(&temp_file, "Hello, World!").unwrap();
100
101 let tool = FileReadTool::new();
102 let input = serde_json::json!({
103 "path": temp_file.to_str().unwrap()
104 });
105 let context = ToolContext::default();
106
107 let result = tool.execute(input, &context).await;
108 assert!(result.is_ok());
109 let tool_result = result.unwrap();
110 assert!(tool_result.content.contains("Hello, World!"));
111
112 std::fs::remove_file(temp_file).ok();
114 }
115
116 #[tokio::test]
117 async fn test_file_read_tool_returns_error_for_nonexistent_file() {
118 let tool = FileReadTool::new();
119 let input = serde_json::json!({
120 "path": "/nonexistent/file/that/does/not/exist.txt"
121 });
122 let context = ToolContext::default();
123
124 let result = tool.execute(input, &context).await;
125 assert!(result.is_err());
126 }
127}