1pub mod bash;
4pub mod read;
5pub mod write;
6pub mod edit;
7pub mod load_skill;
8pub mod ls;
9pub mod find;
10pub mod grep;
11pub mod memory;
12
13use async_trait::async_trait;
14use robit_ai::ChatCompletionTools;
15use serde_json::Value;
16use std::collections::HashMap;
17use std::any::Any;
18use std::path::{Path, PathBuf};
19use std::sync::Arc;
20
21use crate::error::Result;
22use crate::event::SessionId;
23use crate::frontend::Frontend;
24
25#[async_trait]
31pub trait Tool: Send + Sync {
32 fn name(&self) -> &str;
34
35 fn description(&self) -> &str;
37
38 fn parameters_schema(&self) -> Value;
40
41 fn requires_confirmation(&self) -> bool;
43
44 async fn execute(&self, args: Value, ctx: &ToolContext) -> Result<ToolResult>;
46}
47
48#[derive(Debug, Clone)]
54pub struct ToolResult {
55 pub content: String,
57 pub is_error: bool,
59}
60
61impl ToolResult {
62 pub fn success(content: impl Into<String>) -> Self {
63 Self {
64 content: content.into(),
65 is_error: false,
66 }
67 }
68
69 pub fn error(content: impl Into<String>) -> Self {
70 Self {
71 content: content.into(),
72 is_error: true,
73 }
74 }
75}
76
77pub fn resolve_path(file_path: &str, working_dir: &Path) -> PathBuf {
83 let p = PathBuf::from(file_path);
84 if p.is_absolute() {
85 p
86 } else {
87 working_dir.join(p)
88 }
89}
90
91pub struct ToolContext {
97 pub working_dir: PathBuf,
99 pub session_id: SessionId,
101 pub frontend: Arc<dyn Frontend>,
103 pub extensions: HashMap<String, Arc<dyn Any + Send + Sync>>,
107}
108
109#[derive(Debug, Clone)]
115pub struct ToolCallInfo {
116 pub id: String,
117 pub name: String,
118 pub arguments: String,
119}
120
121pub struct ToolRegistry {
127 tools: HashMap<String, Box<dyn Tool>>,
128}
129
130impl ToolRegistry {
131 pub fn new() -> Self {
132 Self {
133 tools: HashMap::new(),
134 }
135 }
136
137 pub fn register(&mut self, tool: impl Tool + 'static) {
139 self.tools.insert(tool.name().to_string(), Box::new(tool));
140 }
141
142 pub fn tool_names(&self) -> Vec<&str> {
144 self.tools.keys().map(|s| s.as_str()).collect()
145 }
146
147 pub fn contains(&self, name: &str) -> bool {
149 self.tools.contains_key(name)
150 }
151
152 pub fn tool_schemas(&self) -> Vec<ChatCompletionTools> {
154 self.tools
155 .values()
156 .map(|tool| {
157 let function = serde_json::json!({
158 "name": tool.name(),
159 "description": tool.description(),
160 "parameters": tool.parameters_schema(),
161 });
162
163 let tool_json = serde_json::json!({
165 "type": "function",
166 "function": function,
167 });
168
169 serde_json::from_value(tool_json)
170 .expect("tool schema should be valid ChatCompletionTools")
171 })
172 .collect()
173 }
174
175 pub async fn execute(
177 &self,
178 name: &str,
179 args: Value,
180 ctx: &ToolContext,
181 ) -> ToolResult {
182 tracing::info!("ToolRegistry.execute called: name='{}', args={:?}", name, args);
183 tracing::debug!("Available tools: {:?}", self.tool_names());
184
185 match self.tools.get(name) {
186 Some(tool) => {
187 tracing::debug!("Found tool '{}', executing...", name);
188 match tool.execute(args, ctx).await {
189 Ok(result) => result,
190 Err(e) => ToolResult::error(format!("Tool execution error: {}", e)),
191 }
192 },
193 None => {
194 let available: Vec<&str> = self.tools.keys().map(|s| s.as_str()).collect();
195 tracing::error!("Tool '{}' not found! Available tools: {:?}", name, available);
196 ToolResult::error(format!(
197 "Tool '{}' not found. Available tools: {:?}",
198 name, available
199 ))
200 }
201 }
202 }
203
204 pub fn requires_confirmation(&self, name: &str) -> bool {
206 self.tools
207 .get(name)
208 .map(|t| t.requires_confirmation())
209 .unwrap_or(false)
210 }
211
212 pub fn tools(&self) -> Vec<&dyn Tool> {
214 self.tools.values().map(|t| t.as_ref()).collect()
215 }
216}
217
218impl Default for ToolRegistry {
219 fn default() -> Self {
220 Self::new()
221 }
222}