1use async_trait::async_trait;
4use cortexai_core::{errors::ToolError, ExecutionContext, Tool, ToolSchema};
5use serde_json::json;
6
7pub struct ReadFileTool;
9
10impl Default for ReadFileTool {
11 fn default() -> Self {
12 Self::new()
13 }
14}
15
16impl ReadFileTool {
17 pub fn new() -> Self {
18 Self
19 }
20}
21
22#[async_trait]
23impl Tool for ReadFileTool {
24 fn schema(&self) -> ToolSchema {
25 ToolSchema::new("read_file", "Read contents of a file").with_parameters(json!({
26 "type": "object",
27 "properties": {
28 "path": {
29 "type": "string",
30 "description": "Path to the file to read"
31 }
32 },
33 "required": ["path"]
34 }))
35 }
36
37 async fn execute(
38 &self,
39 _context: &ExecutionContext,
40 arguments: serde_json::Value,
41 ) -> Result<serde_json::Value, ToolError> {
42 let path = arguments["path"]
43 .as_str()
44 .ok_or_else(|| ToolError::InvalidArguments("Missing 'path' field".to_string()))?;
45
46 match tokio::fs::read_to_string(path).await {
47 Ok(content) => Ok(json!({
48 "path": path,
49 "content": content,
50 "size": content.len()
51 })),
52 Err(e) => Err(ToolError::ExecutionFailed(e.to_string())),
53 }
54 }
55}
56
57pub struct WriteFileTool;
59
60impl Default for WriteFileTool {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl WriteFileTool {
67 pub fn new() -> Self {
68 Self
69 }
70}
71
72#[async_trait]
73impl Tool for WriteFileTool {
74 fn schema(&self) -> ToolSchema {
75 ToolSchema::new("write_file", "Write content to a file")
76 .with_parameters(json!({
77 "type": "object",
78 "properties": {
79 "path": {
80 "type": "string",
81 "description": "Path to the file to write"
82 },
83 "content": {
84 "type": "string",
85 "description": "Content to write to the file"
86 }
87 },
88 "required": ["path", "content"]
89 }))
90 .with_dangerous(true)
91 }
92
93 async fn execute(
94 &self,
95 _context: &ExecutionContext,
96 arguments: serde_json::Value,
97 ) -> Result<serde_json::Value, ToolError> {
98 let path = arguments["path"]
99 .as_str()
100 .ok_or_else(|| ToolError::InvalidArguments("Missing 'path' field".to_string()))?;
101 let content = arguments["content"]
102 .as_str()
103 .ok_or_else(|| ToolError::InvalidArguments("Missing 'content' field".to_string()))?;
104
105 match tokio::fs::write(path, content).await {
106 Ok(_) => Ok(json!({
107 "path": path,
108 "bytes_written": content.len(),
109 "success": true
110 })),
111 Err(e) => Err(ToolError::ExecutionFailed(e.to_string())),
112 }
113 }
114}
115
116pub struct ListDirectoryTool;
118
119impl Default for ListDirectoryTool {
120 fn default() -> Self {
121 Self::new()
122 }
123}
124
125impl ListDirectoryTool {
126 pub fn new() -> Self {
127 Self
128 }
129}
130
131#[async_trait]
132impl Tool for ListDirectoryTool {
133 fn schema(&self) -> ToolSchema {
134 ToolSchema::new("list_directory", "List contents of a directory").with_parameters(json!({
135 "type": "object",
136 "properties": {
137 "path": {
138 "type": "string",
139 "description": "Path to the directory to list"
140 }
141 },
142 "required": ["path"]
143 }))
144 }
145
146 async fn execute(
147 &self,
148 _context: &ExecutionContext,
149 arguments: serde_json::Value,
150 ) -> Result<serde_json::Value, ToolError> {
151 let path = arguments["path"]
152 .as_str()
153 .ok_or_else(|| ToolError::InvalidArguments("Missing 'path' field".to_string()))?;
154
155 let mut entries = Vec::new();
156 let mut dir = tokio::fs::read_dir(path)
157 .await
158 .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
159
160 while let Ok(Some(entry)) = dir.next_entry().await {
161 if let Ok(file_type) = entry.file_type().await {
162 entries.push(json!({
163 "name": entry.file_name().to_string_lossy().to_string(),
164 "is_file": file_type.is_file(),
165 "is_dir": file_type.is_dir(),
166 }));
167 }
168 }
169
170 Ok(json!({
171 "path": path,
172 "entries": entries,
173 "count": entries.len()
174 }))
175 }
176}