1use anyhow::Result;
7use serde_json::{json, Value};
8use std::fs;
9use std::process::Command;
10
11pub fn get_tool_definitions() -> Vec<Value> {
14 vec![
15 json!({
16 "type": "function",
17 "function": {
18 "name": "read_file",
19 "description": "Read the contents of a file at the given path",
20 "parameters": {
21 "type": "object",
22 "properties": {
23 "path": {
24 "type": "string",
25 "description": "File path to read"
26 }
27 },
28 "required": ["path"]
29 }
30 }
31 }),
32 json!({
33 "type": "function",
34 "function": {
35 "name": "write_file",
36 "description": "Write content to a file, creating or overwriting it",
37 "parameters": {
38 "type": "object",
39 "properties": {
40 "path": {
41 "type": "string",
42 "description": "File path to write to"
43 },
44 "content": {
45 "type": "string",
46 "description": "Content to write to the file"
47 }
48 },
49 "required": ["path", "content"]
50 }
51 }
52 }),
53 json!({
54 "type": "function",
55 "function": {
56 "name": "run_command",
57 "description": "Run a shell command and return its output",
58 "parameters": {
59 "type": "object",
60 "properties": {
61 "command": {
62 "type": "string",
63 "description": "Shell command to execute"
64 }
65 },
66 "required": ["command"]
67 }
68 }
69 }),
70 json!({
71 "type": "function",
72 "function": {
73 "name": "list_files",
74 "description": "List files matching a glob pattern",
75 "parameters": {
76 "type": "object",
77 "properties": {
78 "pattern": {
79 "type": "string",
80 "description": "Glob pattern like 'src/**/*.rs' or '*.txt'"
81 }
82 },
83 "required": ["pattern"]
84 }
85 }
86 }),
87 json!({
88 "type": "function",
89 "function": {
90 "name": "task_complete",
91 "description": "Signal that the task has been completed successfully",
92 "parameters": {
93 "type": "object",
94 "properties": {
95 "summary": {
96 "type": "string",
97 "description": "Brief summary of what was accomplished"
98 }
99 },
100 "required": ["summary"]
101 }
102 }
103 }),
104 ]
105}
106
107pub fn execute_tool(name: &str, args: &Value) -> Result<String, String> {
110 match name {
111 "read_file" => {
112 let path = args["path"]
113 .as_str()
114 .ok_or_else(|| "missing or invalid 'path' parameter".to_string())?;
115 read_file(path.to_string()).map_err(|e| format!("read_file failed: {}", e))
116 }
117 "write_file" => {
118 let path = args["path"]
119 .as_str()
120 .ok_or_else(|| "missing or invalid 'path' parameter".to_string())?;
121 let content = args["content"]
122 .as_str()
123 .ok_or_else(|| "missing or invalid 'content' parameter".to_string())?;
124 write_file(path.to_string(), content.to_string())
125 .map_err(|e| format!("write_file failed: {}", e))
126 }
127 "run_command" => {
128 let command = args["command"]
129 .as_str()
130 .ok_or_else(|| "missing or invalid 'command' parameter".to_string())?;
131 run_command(command.to_string()).map_err(|e| format!("run_command failed: {}", e))
132 }
133 "list_files" => {
134 let pattern = args["pattern"]
135 .as_str()
136 .ok_or_else(|| "missing or invalid 'pattern' parameter".to_string())?;
137 list_files(pattern.to_string()).map_err(|e| format!("list_files failed: {}", e))
138 }
139 "task_complete" => {
140 let summary = args["summary"].as_str().unwrap_or("Task completed");
141 Ok(format!("TASK_COMPLETE: {}", summary))
142 }
143 _ => Err(format!("Unknown tool: {}", name)),
144 }
145}
146
147pub fn read_file(path: String) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
150 Ok(fs::read_to_string(&path)?)
151}
152
153pub fn write_file(
156 path: String,
157 content: String,
158) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
159 fs::write(&path, &content)?;
160 Ok(format!("Wrote {} bytes to {}", content.len(), path))
161}
162
163pub fn run_command(command: String) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
166 let output = Command::new("sh").arg("-c").arg(&command).output()?;
167
168 let stdout = String::from_utf8_lossy(&output.stdout);
169 let stderr = String::from_utf8_lossy(&output.stderr);
170
171 if output.status.success() {
172 Ok(stdout.to_string())
173 } else {
174 Ok(format!(
175 "Command failed:\nstdout: {}\nstderr: {}",
176 stdout, stderr
177 ))
178 }
179}
180
181pub fn list_files(pattern: String) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
184 use glob::glob;
185
186 let paths: Vec<_> = glob(&pattern)?
187 .filter_map(Result::ok)
188 .map(|p| p.display().to_string())
189 .collect();
190 Ok(paths.join("\n"))
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use std::fs;
197 use tempfile::tempdir;
198
199 #[test]
200 fn test_write_file() {
201 let dir = tempdir().unwrap();
202 let file_path = dir.path().join("test.txt");
203 let path_str = file_path.to_string_lossy().to_string();
204
205 let result = write_file(path_str.clone(), "test content".to_string()).unwrap();
206 assert!(result.contains("12 bytes"));
207
208 let content = fs::read_to_string(&file_path).unwrap();
209 assert_eq!(content, "test content");
210 }
211
212 #[test]
213 fn test_read_file() {
214 let dir = tempdir().unwrap();
215 let file_path = dir.path().join("test.txt");
216 fs::write(&file_path, "test content").unwrap();
217
218 let path_str = file_path.to_string_lossy().to_string();
219 let content = read_file(path_str).unwrap();
220 assert_eq!(content, "test content");
221 }
222
223 #[test]
224 fn test_run_command() {
225 let result = run_command("echo 'test output'".to_string()).unwrap();
226 assert!(result.contains("test output"));
227 }
228
229 #[test]
230 fn test_list_files() {
231 let dir = tempdir().unwrap();
232 let _file1 = fs::File::create(dir.path().join("file1.txt")).unwrap();
233 let _file2 = fs::File::create(dir.path().join("file2.txt")).unwrap();
234
235 let pattern = format!("{}/*.txt", dir.path().display());
236 let result = list_files(pattern).unwrap();
237
238 assert!(result.contains("file1.txt"));
239 assert!(result.contains("file2.txt"));
240 }
241}