1pub mod click;
7pub mod close;
8pub mod close_tab;
9pub mod evaluate;
10pub mod extract;
11pub mod get_clickable_elements;
12pub mod go_back;
13pub mod go_forward;
14pub mod hover;
15pub mod html_to_markdown;
16pub mod input;
17pub mod markdown;
18pub mod navigate;
19pub mod new_tab;
20pub mod press_key;
21pub mod read_links;
22pub mod readability_script;
23pub mod screenshot;
24pub mod scroll;
25pub mod select;
26pub mod switch_tab;
27pub mod tab_list;
28pub mod wait;
29
30pub use click::ClickParams;
32pub use close::CloseParams;
33pub use close_tab::CloseTabParams;
34pub use evaluate::EvaluateParams;
35pub use extract::ExtractParams;
36pub use get_clickable_elements::GetClickableElementsParams;
37pub use go_back::GoBackParams;
38pub use go_forward::GoForwardParams;
39pub use hover::HoverParams;
40pub use input::InputParams;
41pub use markdown::GetMarkdownParams;
42pub use navigate::NavigateParams;
43pub use new_tab::NewTabParams;
44pub use press_key::PressKeyParams;
45pub use read_links::ReadLinksParams;
46pub use screenshot::ScreenshotParams;
47pub use scroll::ScrollParams;
48pub use select::SelectParams;
49pub use switch_tab::SwitchTabParams;
50pub use tab_list::TabListParams;
51pub use wait::WaitParams;
52
53use crate::browser::BrowserSession;
54use crate::dom::DomTree;
55use crate::error::Result;
56use serde_json::Value;
57use std::collections::HashMap;
58use std::sync::Arc;
59
60pub struct ToolContext<'a> {
62 pub session: &'a BrowserSession,
64
65 pub dom_tree: Option<DomTree>,
67}
68
69impl<'a> ToolContext<'a> {
70 pub fn new(session: &'a BrowserSession) -> Self {
72 Self {
73 session,
74 dom_tree: None,
75 }
76 }
77
78 pub fn with_dom(session: &'a BrowserSession, dom_tree: DomTree) -> Self {
80 Self {
81 session,
82 dom_tree: Some(dom_tree),
83 }
84 }
85
86 pub fn get_dom(&mut self) -> Result<&DomTree> {
88 if self.dom_tree.is_none() {
89 self.dom_tree = Some(self.session.extract_dom()?);
90 }
91 Ok(self.dom_tree.as_ref().unwrap())
92 }
93}
94
95#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
97pub struct ToolResult {
98 pub success: bool,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub data: Option<Value>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub error: Option<String>,
108
109 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
111 pub metadata: HashMap<String, Value>,
112}
113
114impl ToolResult {
115 pub fn success(data: Option<Value>) -> Self {
117 Self {
118 success: true,
119 data,
120 error: None,
121 metadata: HashMap::new(),
122 }
123 }
124
125 pub fn success_with<T: serde::Serialize>(data: T) -> Self {
127 Self {
128 success: true,
129 data: serde_json::to_value(data).ok(),
130 error: None,
131 metadata: HashMap::new(),
132 }
133 }
134
135 pub fn failure(error: impl Into<String>) -> Self {
137 Self {
138 success: false,
139 data: None,
140 error: Some(error.into()),
141 metadata: HashMap::new(),
142 }
143 }
144
145 pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
147 self.metadata.insert(key.into(), value);
148 self
149 }
150}
151
152pub trait Tool: Send + Sync + Default {
154 type Params: serde::Serialize + for<'de> serde::Deserialize<'de> + schemars::JsonSchema;
156
157 fn name(&self) -> &str;
159
160 fn parameters_schema(&self) -> Value {
162 serde_json::to_value(schemars::schema_for!(Self::Params)).unwrap_or_default()
163 }
164
165 fn execute_typed(&self, params: Self::Params, context: &mut ToolContext) -> Result<ToolResult>;
167
168 fn execute(&self, params: Value, context: &mut ToolContext) -> Result<ToolResult> {
170 let typed_params: Self::Params = serde_json::from_value(params).map_err(|e| {
171 crate::error::BrowserError::InvalidArgument(format!("Invalid parameters: {}", e))
172 })?;
173 self.execute_typed(typed_params, context)
174 }
175}
176
177pub trait DynTool: Send + Sync {
179 fn name(&self) -> &str;
180 fn parameters_schema(&self) -> Value;
181 fn execute(&self, params: Value, context: &mut ToolContext) -> Result<ToolResult>;
182}
183
184impl<T: Tool> DynTool for T {
186 fn name(&self) -> &str {
187 Tool::name(self)
188 }
189
190 fn parameters_schema(&self) -> Value {
191 Tool::parameters_schema(self)
192 }
193
194 fn execute(&self, params: Value, context: &mut ToolContext) -> Result<ToolResult> {
195 Tool::execute(self, params, context)
196 }
197}
198
199pub struct ToolRegistry {
201 tools: HashMap<String, Arc<dyn DynTool>>,
202}
203
204impl ToolRegistry {
205 pub fn new() -> Self {
207 Self {
208 tools: HashMap::new(),
209 }
210 }
211
212 pub fn with_defaults() -> Self {
214 let mut registry = Self::new();
215
216 registry.register(navigate::NavigateTool);
218 registry.register(go_back::GoBackTool);
219 registry.register(go_forward::GoForwardTool);
220 registry.register(wait::WaitTool);
221
222 registry.register(click::ClickTool);
224 registry.register(input::InputTool);
225 registry.register(select::SelectTool);
226 registry.register(hover::HoverTool);
227 registry.register(press_key::PressKeyTool);
228 registry.register(scroll::ScrollTool);
229
230 registry.register(new_tab::NewTabTool);
232 registry.register(tab_list::TabListTool);
233 registry.register(switch_tab::SwitchTabTool);
234 registry.register(close_tab::CloseTabTool);
235
236 registry.register(extract::ExtractContentTool);
238 registry.register(markdown::GetMarkdownTool);
239 registry.register(read_links::ReadLinksTool);
240 registry.register(get_clickable_elements::GetClickableElementsTool);
241
242 registry.register(screenshot::ScreenshotTool);
244 registry.register(evaluate::EvaluateTool);
245 registry.register(close::CloseTool);
246
247 registry
248 }
249
250 pub fn register<T: Tool + 'static>(&mut self, tool: T) {
252 let name = tool.name().to_string();
253 self.tools.insert(name, Arc::new(tool));
254 }
255
256 pub fn get(&self, name: &str) -> Option<&Arc<dyn DynTool>> {
258 self.tools.get(name)
259 }
260
261 pub fn has(&self, name: &str) -> bool {
263 self.tools.contains_key(name)
264 }
265
266 pub fn list_names(&self) -> Vec<String> {
268 self.tools.keys().cloned().collect()
269 }
270
271 pub fn all_tools(&self) -> Vec<Arc<dyn DynTool>> {
273 self.tools.values().cloned().collect()
274 }
275
276 pub fn execute(
278 &self,
279 name: &str,
280 params: Value,
281 context: &mut ToolContext,
282 ) -> Result<ToolResult> {
283 match self.get(name) {
284 Some(tool) => tool.execute(params, context),
285 None => Ok(ToolResult::failure(format!("Tool '{}' not found", name))),
286 }
287 }
288
289 pub fn count(&self) -> usize {
291 self.tools.len()
292 }
293}
294
295impl Default for ToolRegistry {
296 fn default() -> Self {
297 Self::with_defaults()
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn test_tool_result_success() {
307 let result = ToolResult::success(Some(serde_json::json!({"url": "https://example.com"})));
308 assert!(result.success);
309 assert!(result.data.is_some());
310 assert!(result.error.is_none());
311 }
312
313 #[test]
314 fn test_tool_result_failure() {
315 let result = ToolResult::failure("Test error");
316 assert!(!result.success);
317 assert!(result.data.is_none());
318 assert_eq!(result.error, Some("Test error".to_string()));
319 }
320
321 #[test]
322 fn test_tool_result_with_metadata() {
323 let result = ToolResult::success(None).with_metadata("duration_ms", serde_json::json!(100));
324
325 assert!(result.metadata.contains_key("duration_ms"));
326 }
327}