1use std::sync::Arc;
8
9use soul_core::executor::direct::DirectExecutor;
10use soul_core::executor::{ConfigTool, ExecutorRegistry, ToolExecutor};
11use soul_core::tool::ToolRegistry;
12use soul_core::vexec::VirtualExecutor;
13use soul_core::vfs::VirtualFs;
14
15use crate::tools::{
16 bash::BashTool, edit::EditTool, find::FindTool, grep::GrepTool, ls::LsTool, read::ReadTool,
17 write::WriteTool,
18};
19
20pub fn coding_tools(
23 fs: Arc<dyn VirtualFs>,
24 executor: Arc<dyn VirtualExecutor>,
25 cwd: impl Into<String>,
26) -> ToolRegistry {
27 let cwd = cwd.into();
28 let mut registry = ToolRegistry::new();
29 registry.register(Box::new(ReadTool::new(fs.clone(), &cwd)));
30 registry.register(Box::new(WriteTool::new(fs.clone(), &cwd)));
31 registry.register(Box::new(EditTool::new(fs, &cwd)));
32 registry.register(Box::new(BashTool::new(executor, &cwd)));
33 registry
34}
35
36pub fn read_only_tools(
39 fs: Arc<dyn VirtualFs>,
40 cwd: impl Into<String>,
41) -> ToolRegistry {
42 let cwd = cwd.into();
43 let mut registry = ToolRegistry::new();
44 registry.register(Box::new(ReadTool::new(fs.clone(), &cwd)));
45 registry.register(Box::new(GrepTool::new(fs.clone(), &cwd)));
46 registry.register(Box::new(FindTool::new(fs.clone(), &cwd)));
47 registry.register(Box::new(LsTool::new(fs, &cwd)));
48 registry
49}
50
51pub fn all_tools(
54 fs: Arc<dyn VirtualFs>,
55 executor: Arc<dyn VirtualExecutor>,
56 cwd: impl Into<String>,
57) -> ToolRegistry {
58 let cwd = cwd.into();
59 let mut registry = ToolRegistry::new();
60 registry.register(Box::new(ReadTool::new(fs.clone(), &cwd)));
61 registry.register(Box::new(WriteTool::new(fs.clone(), &cwd)));
62 registry.register(Box::new(EditTool::new(fs.clone(), &cwd)));
63 registry.register(Box::new(BashTool::new(executor, &cwd)));
64 registry.register(Box::new(GrepTool::new(fs.clone(), &cwd)));
65 registry.register(Box::new(FindTool::new(fs.clone(), &cwd)));
66 registry.register(Box::new(LsTool::new(fs, &cwd)));
67 registry
68}
69
70pub fn all_executor(
88 fs: Arc<dyn VirtualFs>,
89 executor: Arc<dyn VirtualExecutor>,
90 cwd: impl Into<String>,
91) -> ExecutorRegistry {
92 let tools = all_tools(fs, executor, cwd);
93 wrap_as_executor(tools)
94}
95
96pub fn coding_executor(
99 fs: Arc<dyn VirtualFs>,
100 executor: Arc<dyn VirtualExecutor>,
101 cwd: impl Into<String>,
102) -> ExecutorRegistry {
103 let tools = coding_tools(fs, executor, cwd);
104 wrap_as_executor(tools)
105}
106
107pub fn wrap_as_executor(tools: ToolRegistry) -> ExecutorRegistry {
110 let definitions = tools.definitions();
111 let direct = Arc::new(DirectExecutor::new(Arc::new(tools)));
112
113 let mut registry = ExecutorRegistry::new();
114 registry.register_executor(direct.clone() as Arc<dyn ToolExecutor>);
115
116 for def in definitions {
117 registry.register_config_tool(ConfigTool {
118 definition: def,
119 executor_name: "direct".into(),
120 executor_config: serde_json::json!({}),
121 });
122 }
123
124 registry
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use soul_core::vexec::NoopExecutor;
131 use soul_core::vfs::MemoryFs;
132
133 #[test]
134 fn coding_tools_has_four() {
135 let fs = Arc::new(MemoryFs::new());
136 let exec = Arc::new(NoopExecutor);
137 let registry = coding_tools(fs, exec, "/");
138 assert_eq!(registry.len(), 4);
139 assert!(registry.get("read").is_some());
140 assert!(registry.get("write").is_some());
141 assert!(registry.get("edit").is_some());
142 assert!(registry.get("bash").is_some());
143 }
144
145 #[test]
146 fn read_only_tools_has_four() {
147 let fs = Arc::new(MemoryFs::new());
148 let registry = read_only_tools(fs, "/");
149 assert_eq!(registry.len(), 4);
150 assert!(registry.get("read").is_some());
151 assert!(registry.get("grep").is_some());
152 assert!(registry.get("find").is_some());
153 assert!(registry.get("ls").is_some());
154 }
155
156 #[test]
157 fn all_tools_has_seven() {
158 let fs = Arc::new(MemoryFs::new());
159 let exec = Arc::new(NoopExecutor);
160 let registry = all_tools(fs, exec, "/");
161 assert_eq!(registry.len(), 7);
162 let names = registry.names();
163 assert!(names.contains(&"read"));
164 assert!(names.contains(&"write"));
165 assert!(names.contains(&"edit"));
166 assert!(names.contains(&"bash"));
167 assert!(names.contains(&"grep"));
168 assert!(names.contains(&"find"));
169 assert!(names.contains(&"ls"));
170 }
171
172 #[test]
173 fn definitions_all_have_schemas() {
174 let fs = Arc::new(MemoryFs::new());
175 let exec = Arc::new(NoopExecutor);
176 let registry = all_tools(fs, exec, "/");
177 for def in registry.definitions() {
178 assert!(!def.name.is_empty());
179 assert!(!def.description.is_empty());
180 assert!(def.input_schema.is_object());
181 }
182 }
183
184 #[test]
185 fn all_executor_has_tools() {
186 let fs = Arc::new(MemoryFs::new());
187 let exec = Arc::new(NoopExecutor);
188 let registry = all_executor(fs, exec, "/");
189 assert!(registry.has_tool("read"));
190 assert!(registry.has_tool("write"));
191 assert!(registry.has_tool("edit"));
192 assert!(registry.has_tool("bash"));
193 assert!(registry.has_tool("grep"));
194 assert!(registry.has_tool("find"));
195 assert!(registry.has_tool("ls"));
196 assert_eq!(registry.definitions().len(), 7);
197 }
198
199 #[test]
200 fn coding_executor_has_four() {
201 let fs = Arc::new(MemoryFs::new());
202 let exec = Arc::new(NoopExecutor);
203 let registry = coding_executor(fs, exec, "/");
204 assert!(registry.has_tool("read"));
205 assert!(registry.has_tool("bash"));
206 assert_eq!(registry.definitions().len(), 4);
207 }
208
209 #[tokio::test]
210 async fn executor_registry_routes_correctly() {
211 let fs = Arc::new(MemoryFs::new());
212 fs.write("/test.txt", "hello").await.unwrap();
213 let exec = Arc::new(NoopExecutor);
214 let registry = all_executor(fs, exec, "/");
215
216 let result = registry
217 .execute("read", "c1", serde_json::json!({"path": "/test.txt"}), None)
218 .await
219 .unwrap();
220 assert!(!result.is_error);
221 assert!(result.content.contains("hello"));
222 }
223}