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