1pub mod bash;
4pub mod read;
5pub mod write;
6pub mod edit;
7pub mod generate_image;
8pub mod load_skill;
9pub mod ls;
10pub mod find;
11pub mod grep;
12pub mod memory;
13pub mod search_history;
14
15use async_trait::async_trait;
16use robit_ai::ChatCompletionTools;
17use serde_json::Value;
18use std::collections::HashMap;
19use std::any::Any;
20use std::path::{Path, PathBuf};
21use std::sync::Arc;
22
23use crate::error::Result;
24use crate::event::SessionId;
25use crate::frontend::Frontend;
26
27#[async_trait]
33pub trait Tool: Send + Sync {
34 fn name(&self) -> &str;
36
37 fn description(&self) -> &str;
39
40 fn parameters_schema(&self) -> Value;
42
43 fn requires_confirmation(&self) -> bool;
45
46 async fn execute(&self, args: Value, ctx: &ToolContext) -> Result<ToolResult>;
48}
49
50#[derive(Debug, Clone)]
56pub struct ToolResult {
57 pub content: String,
59 pub is_error: bool,
61}
62
63impl ToolResult {
64 pub fn success(content: impl Into<String>) -> Self {
65 Self {
66 content: content.into(),
67 is_error: false,
68 }
69 }
70
71 pub fn error(content: impl Into<String>) -> Self {
72 Self {
73 content: content.into(),
74 is_error: true,
75 }
76 }
77}
78
79pub fn resolve_path(file_path: &str, working_dir: &Path) -> PathBuf {
85 let p = PathBuf::from(file_path);
86 if p.is_absolute() {
87 p
88 } else {
89 working_dir.join(p)
90 }
91}
92
93pub struct ToolContext {
99 pub working_dir: PathBuf,
101 pub session_id: SessionId,
103 pub frontend: Arc<dyn Frontend>,
105 pub extensions: HashMap<String, Arc<dyn Any + Send + Sync>>,
109}
110
111#[derive(Debug, Clone)]
117pub struct ToolCallInfo {
118 pub id: String,
119 pub name: String,
120 pub arguments: String,
121}
122
123pub struct ToolRegistry {
129 tools: HashMap<String, Box<dyn Tool>>,
130}
131
132impl ToolRegistry {
133 pub fn new() -> Self {
134 Self {
135 tools: HashMap::new(),
136 }
137 }
138
139 pub fn register(&mut self, tool: impl Tool + 'static) {
141 self.tools.insert(tool.name().to_string(), Box::new(tool));
142 }
143
144 pub fn tool_names(&self) -> Vec<&str> {
146 self.tools.keys().map(|s| s.as_str()).collect()
147 }
148
149 pub fn contains(&self, name: &str) -> bool {
151 self.tools.contains_key(name)
152 }
153
154 pub fn tool_schemas(&self) -> Vec<ChatCompletionTools> {
156 self.tools
157 .values()
158 .map(|tool| {
159 let function = serde_json::json!({
160 "name": tool.name(),
161 "description": tool.description(),
162 "parameters": tool.parameters_schema(),
163 });
164
165 let tool_json = serde_json::json!({
167 "type": "function",
168 "function": function,
169 });
170
171 serde_json::from_value(tool_json)
172 .expect("tool schema should be valid ChatCompletionTools")
173 })
174 .collect()
175 }
176
177 pub async fn execute(
179 &self,
180 name: &str,
181 args: Value,
182 ctx: &ToolContext,
183 ) -> ToolResult {
184 tracing::info!("ToolRegistry.execute called: name='{}', args={:?}", name, args);
185 tracing::debug!("Available tools: {:?}", self.tool_names());
186
187 match self.tools.get(name) {
188 Some(tool) => {
189 tracing::debug!("Found tool '{}', executing...", name);
190 let started = std::time::Instant::now();
191 let outcome = tool.execute(args, ctx).await;
192 let elapsed = started.elapsed();
193 match &outcome {
194 Ok(result) => tracing::trace!(
195 "[tool:{}] execution finished in {:?}: is_error={}, content_len={}",
196 name,
197 elapsed,
198 result.is_error,
199 result.content.len()
200 ),
201 Err(e) => tracing::warn!(
202 "[tool:{}] execution returned error after {:?}: {}",
203 name,
204 elapsed,
205 e
206 ),
207 }
208 match outcome {
209 Ok(result) => result,
210 Err(e) => ToolResult::error(format!("Tool execution error: {}", e)),
211 }
212 },
213 None => {
214 let available: Vec<&str> = self.tools.keys().map(|s| s.as_str()).collect();
215 tracing::error!("Tool '{}' not found! Available tools: {:?}", name, available);
216 ToolResult::error(format!(
217 "Tool '{}' not found. Available tools: {:?}",
218 name, available
219 ))
220 }
221 }
222 }
223
224 pub fn requires_confirmation(&self, name: &str) -> bool {
226 self.tools
227 .get(name)
228 .map(|t| t.requires_confirmation())
229 .unwrap_or(false)
230 }
231
232 pub fn tools(&self) -> Vec<&dyn Tool> {
234 self.tools.values().map(|t| t.as_ref()).collect()
235 }
236}
237
238impl Default for ToolRegistry {
239 fn default() -> Self {
240 Self::new()
241 }
242}