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 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
30// Re-export Params types for use by MCP layer
31pub 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
60/// Tool execution context
61pub struct ToolContext<'a> {
62    /// Browser session
63    pub session: &'a BrowserSession,
64
65    /// Optional DOM tree (extracted on demand)
66    pub dom_tree: Option<DomTree>,
67}
68
69impl<'a> ToolContext<'a> {
70    /// Create a new tool context
71    pub fn new(session: &'a BrowserSession) -> Self {
72        Self {
73            session,
74            dom_tree: None,
75        }
76    }
77
78    /// Create a context with a pre-extracted DOM tree
79    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    /// Get or extract the DOM tree
87    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/// Result of tool execution
96#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
97pub struct ToolResult {
98    /// Whether the tool execution was successful
99    pub success: bool,
100
101    /// Result data (JSON value)
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub data: Option<Value>,
104
105    /// Error message if execution failed
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub error: Option<String>,
108
109    /// Additional metadata
110    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
111    pub metadata: HashMap<String, Value>,
112}
113
114impl ToolResult {
115    /// Create a successful result
116    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    /// Create a successful result with data
126    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    /// Create a failure result
136    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    /// Add metadata to the result
146    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
152/// Trait for browser automation tools with associated parameter types
153pub trait Tool: Send + Sync + Default {
154    /// Associated parameter type for this tool
155    type Params: serde::Serialize + for<'de> serde::Deserialize<'de> + schemars::JsonSchema;
156
157    /// Get tool name
158    fn name(&self) -> &str;
159
160    /// Get tool parameter schema (JSON Schema)
161    fn parameters_schema(&self) -> Value {
162        serde_json::to_value(schemars::schema_for!(Self::Params)).unwrap_or_default()
163    }
164
165    /// Execute the tool with strongly-typed parameters
166    fn execute_typed(&self, params: Self::Params, context: &mut ToolContext) -> Result<ToolResult>;
167
168    /// Execute the tool with JSON parameters (default implementation)
169    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
177/// Type-erased tool trait for dynamic dispatch
178pub 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
184/// Blanket implementation to convert any Tool into DynTool
185impl<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
199/// Tool registry for managing and accessing tools
200pub struct ToolRegistry {
201    tools: HashMap<String, Arc<dyn DynTool>>,
202}
203
204impl ToolRegistry {
205    /// Create a new empty tool registry
206    pub fn new() -> Self {
207        Self {
208            tools: HashMap::new(),
209        }
210    }
211
212    /// Create a registry with default tools
213    pub fn with_defaults() -> Self {
214        let mut registry = Self::new();
215
216        // Register navigation tools
217        registry.register(navigate::NavigateTool);
218        registry.register(go_back::GoBackTool);
219        registry.register(go_forward::GoForwardTool);
220        registry.register(wait::WaitTool);
221
222        // Register interaction tools
223        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        // Register tab management tools
231        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        // Register reading and extraction tools
237        registry.register(extract::ExtractContentTool);
238        registry.register(markdown::GetMarkdownTool);
239        registry.register(read_links::ReadLinksTool);
240        registry.register(get_clickable_elements::GetClickableElementsTool);
241
242        // Register utility tools
243        registry.register(screenshot::ScreenshotTool);
244        registry.register(evaluate::EvaluateTool);
245        registry.register(close::CloseTool);
246
247        registry
248    }
249
250    /// Register a tool
251    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    /// Get a tool by name
257    pub fn get(&self, name: &str) -> Option<&Arc<dyn DynTool>> {
258        self.tools.get(name)
259    }
260
261    /// Check if a tool exists
262    pub fn has(&self, name: &str) -> bool {
263        self.tools.contains_key(name)
264    }
265
266    /// List all tool names
267    pub fn list_names(&self) -> Vec<String> {
268        self.tools.keys().cloned().collect()
269    }
270
271    /// Get all tools
272    pub fn all_tools(&self) -> Vec<Arc<dyn DynTool>> {
273        self.tools.values().cloned().collect()
274    }
275
276    /// Execute a tool by name
277    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    /// Get the number of registered tools
290    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}