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