browser_use/tools/
mod.rs

1//! Browser automation tools module
2//!
3//! This module provides a framework for browser automation tools and
4//! includes implementations of common browser operations.
5
6pub mod click;
7pub mod close;
8pub mod close_tab;
9pub mod evaluate;
10pub mod extract;
11pub mod go_back;
12pub mod go_forward;
13pub mod hover;
14pub mod html_to_markdown;
15pub mod input;
16pub mod markdown;
17pub mod navigate;
18pub mod new_tab;
19pub mod press_key;
20pub mod read_links;
21pub mod readability_script;
22pub mod screenshot;
23pub mod scroll;
24pub mod select;
25pub mod snapshot;
26pub mod switch_tab;
27pub mod tab_list;
28mod utils;
29pub mod wait;
30
31// Re-export Params types for use by MCP layer
32pub use click::ClickParams;
33pub use close::CloseParams;
34pub use close_tab::CloseTabParams;
35pub use evaluate::EvaluateParams;
36pub use extract::ExtractParams;
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 snapshot::SnapshotParams;
50pub use switch_tab::SwitchTabParams;
51pub use tab_list::TabListParams;
52pub use wait::WaitParams;
53
54use crate::browser::BrowserSession;
55use crate::dom::DomTree;
56use crate::error::Result;
57use serde_json::Value;
58use std::collections::HashMap;
59use std::sync::Arc;
60
61/// Tool execution context
62pub struct ToolContext<'a> {
63    /// Browser session
64    pub session: &'a BrowserSession,
65
66    /// Optional DOM tree (extracted on demand)
67    pub dom_tree: Option<DomTree>,
68}
69
70impl<'a> ToolContext<'a> {
71    /// Create a new tool context
72    pub fn new(session: &'a BrowserSession) -> Self {
73        Self {
74            session,
75            dom_tree: None,
76        }
77    }
78
79    /// Create a context with a pre-extracted DOM tree
80    pub fn with_dom(session: &'a BrowserSession, dom_tree: DomTree) -> Self {
81        Self {
82            session,
83            dom_tree: Some(dom_tree),
84        }
85    }
86
87    /// Get or extract the DOM tree
88    pub fn get_dom(&mut self) -> Result<&DomTree> {
89        if self.dom_tree.is_none() {
90            self.dom_tree = Some(self.session.extract_dom()?);
91        }
92        Ok(self.dom_tree.as_ref().unwrap())
93    }
94}
95
96/// Result of tool execution
97#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
98pub struct ToolResult {
99    /// Whether the tool execution was successful
100    pub success: bool,
101
102    /// Result data (JSON value)
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub data: Option<Value>,
105
106    /// Error message if execution failed
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub error: Option<String>,
109
110    /// Additional metadata
111    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
112    pub metadata: HashMap<String, Value>,
113}
114
115impl ToolResult {
116    /// Create a successful result
117    pub fn success(data: Option<Value>) -> Self {
118        Self {
119            success: true,
120            data,
121            error: None,
122            metadata: HashMap::new(),
123        }
124    }
125
126    /// Create a successful result with data
127    pub fn success_with<T: serde::Serialize>(data: T) -> Self {
128        Self {
129            success: true,
130            data: serde_json::to_value(data).ok(),
131            error: None,
132            metadata: HashMap::new(),
133        }
134    }
135
136    /// Create a failure result
137    pub fn failure(error: impl Into<String>) -> Self {
138        Self {
139            success: false,
140            data: None,
141            error: Some(error.into()),
142            metadata: HashMap::new(),
143        }
144    }
145
146    /// Add metadata to the result
147    pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
148        self.metadata.insert(key.into(), value);
149        self
150    }
151}
152
153/// Trait for browser automation tools with associated parameter types
154pub trait Tool: Send + Sync + Default {
155    /// Associated parameter type for this tool
156    type Params: serde::Serialize + for<'de> serde::Deserialize<'de> + schemars::JsonSchema;
157
158    /// Get tool name
159    fn name(&self) -> &str;
160
161    /// Get tool parameter schema (JSON Schema)
162    fn parameters_schema(&self) -> Value {
163        serde_json::to_value(schemars::schema_for!(Self::Params)).unwrap_or_default()
164    }
165
166    /// Execute the tool with strongly-typed parameters
167    fn execute_typed(&self, params: Self::Params, context: &mut ToolContext) -> Result<ToolResult>;
168
169    /// Execute the tool with JSON parameters (default implementation)
170    fn execute(&self, params: Value, context: &mut ToolContext) -> Result<ToolResult> {
171        let typed_params: Self::Params = serde_json::from_value(params).map_err(|e| {
172            crate::error::BrowserError::InvalidArgument(format!("Invalid parameters: {}", e))
173        })?;
174        self.execute_typed(typed_params, context)
175    }
176}
177
178/// Type-erased tool trait for dynamic dispatch
179pub trait DynTool: Send + Sync {
180    fn name(&self) -> &str;
181    fn parameters_schema(&self) -> Value;
182    fn execute(&self, params: Value, context: &mut ToolContext) -> Result<ToolResult>;
183}
184
185/// Blanket implementation to convert any Tool into DynTool
186impl<T: Tool> DynTool for T {
187    fn name(&self) -> &str {
188        Tool::name(self)
189    }
190
191    fn parameters_schema(&self) -> Value {
192        Tool::parameters_schema(self)
193    }
194
195    fn execute(&self, params: Value, context: &mut ToolContext) -> Result<ToolResult> {
196        Tool::execute(self, params, context)
197    }
198}
199
200/// Tool registry for managing and accessing tools
201pub struct ToolRegistry {
202    tools: HashMap<String, Arc<dyn DynTool>>,
203}
204
205impl ToolRegistry {
206    /// Create a new empty tool registry
207    pub fn new() -> Self {
208        Self {
209            tools: HashMap::new(),
210        }
211    }
212
213    /// Create a registry with default tools
214    pub fn with_defaults() -> Self {
215        let mut registry = Self::new();
216
217        // Register navigation tools
218        registry.register(navigate::NavigateTool);
219        registry.register(go_back::GoBackTool);
220        registry.register(go_forward::GoForwardTool);
221        registry.register(wait::WaitTool);
222
223        // Register interaction tools
224        registry.register(click::ClickTool);
225        registry.register(input::InputTool);
226        registry.register(select::SelectTool);
227        registry.register(hover::HoverTool);
228        registry.register(press_key::PressKeyTool);
229        registry.register(scroll::ScrollTool);
230
231        // Register tab management tools
232        registry.register(new_tab::NewTabTool);
233        registry.register(tab_list::TabListTool);
234        registry.register(switch_tab::SwitchTabTool);
235        registry.register(close_tab::CloseTabTool);
236
237        // Register reading and extraction tools
238        registry.register(extract::ExtractContentTool);
239        registry.register(markdown::GetMarkdownTool);
240        registry.register(read_links::ReadLinksTool);
241        registry.register(snapshot::SnapshotTool);
242
243        // Register utility tools
244        registry.register(screenshot::ScreenshotTool);
245        registry.register(evaluate::EvaluateTool);
246        registry.register(close::CloseTool);
247
248        registry
249    }
250
251    /// Register a tool
252    pub fn register<T: Tool + 'static>(&mut self, tool: T) {
253        let name = tool.name().to_string();
254        self.tools.insert(name, Arc::new(tool));
255    }
256
257    /// Get a tool by name
258    pub fn get(&self, name: &str) -> Option<&Arc<dyn DynTool>> {
259        self.tools.get(name)
260    }
261
262    /// Check if a tool exists
263    pub fn has(&self, name: &str) -> bool {
264        self.tools.contains_key(name)
265    }
266
267    /// List all tool names
268    pub fn list_names(&self) -> Vec<String> {
269        self.tools.keys().cloned().collect()
270    }
271
272    /// Get all tools
273    pub fn all_tools(&self) -> Vec<Arc<dyn DynTool>> {
274        self.tools.values().cloned().collect()
275    }
276
277    /// Execute a tool by name
278    pub fn execute(
279        &self,
280        name: &str,
281        params: Value,
282        context: &mut ToolContext,
283    ) -> Result<ToolResult> {
284        match self.get(name) {
285            Some(tool) => tool.execute(params, context),
286            None => Ok(ToolResult::failure(format!("Tool '{}' not found", name))),
287        }
288    }
289
290    /// Get the number of registered tools
291    pub fn count(&self) -> usize {
292        self.tools.len()
293    }
294}
295
296impl Default for ToolRegistry {
297    fn default() -> Self {
298        Self::with_defaults()
299    }
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    #[test]
307    fn test_tool_result_success() {
308        let result = ToolResult::success(Some(serde_json::json!({"url": "https://example.com"})));
309        assert!(result.success);
310        assert!(result.data.is_some());
311        assert!(result.error.is_none());
312    }
313
314    #[test]
315    fn test_tool_result_failure() {
316        let result = ToolResult::failure("Test error");
317        assert!(!result.success);
318        assert!(result.data.is_none());
319        assert_eq!(result.error, Some("Test error".to_string()));
320    }
321
322    #[test]
323    fn test_tool_result_with_metadata() {
324        let result = ToolResult::success(None).with_metadata("duration_ms", serde_json::json!(100));
325
326        assert!(result.metadata.contains_key("duration_ms"));
327    }
328}