Skip to main content

cortexai_tools/
file.rs

1//! File system tools
2
3use async_trait::async_trait;
4use cortexai_core::{errors::ToolError, ExecutionContext, Tool, ToolSchema};
5use serde_json::json;
6
7/// Read file tool
8pub 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
57/// Write file tool
58pub 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
116/// List directory tool
117pub 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}