browser_use/tools/
input.rs

1use crate::error::{BrowserError, Result};
2use crate::tools::snapshot::{RenderMode, render_aria_tree};
3use crate::tools::{Tool, ToolContext, ToolResult};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
8pub struct InputParams {
9    /// CSS selector (use either this or index, not both)
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub selector: Option<String>,
12
13    /// Element index from DOM tree (use either this or selector, not both)
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub index: Option<usize>,
16
17    /// Text to type into the element
18    pub text: String,
19
20    /// Clear existing content first (default: false)
21    #[serde(default)]
22    pub clear: bool,
23}
24
25#[derive(Default)]
26pub struct InputTool;
27
28impl Tool for InputTool {
29    type Params = InputParams;
30
31    fn name(&self) -> &str {
32        "input"
33    }
34
35    fn execute_typed(&self, params: InputParams, context: &mut ToolContext) -> Result<ToolResult> {
36        // Validate that exactly one selector method is provided
37        match (&params.selector, &params.index) {
38            (Some(_), Some(_)) => {
39                return Err(BrowserError::ToolExecutionFailed {
40                    tool: "input".to_string(),
41                    reason: "Cannot specify both 'selector' and 'index'. Use one or the other."
42                        .to_string(),
43                });
44            }
45            (None, None) => {
46                return Err(BrowserError::ToolExecutionFailed {
47                    tool: "input".to_string(),
48                    reason: "Must specify either 'selector' or 'index'.".to_string(),
49                });
50            }
51            _ => {}
52        }
53
54        // Get the CSS selector (either directly or from index)
55        let css_selector = if let Some(selector) = params.selector.clone() {
56            selector
57        } else if let Some(index) = params.index {
58            let dom = context.get_dom()?;
59            let selector = dom.get_selector(index).ok_or_else(|| {
60                BrowserError::ElementNotFound(format!("No element with index {}", index))
61            })?;
62            selector.clone()
63        } else {
64            unreachable!("Validation above ensures one field is Some")
65        };
66
67        let tab = context.session.tab()?;
68        let element = context.session.find_element(&tab, &css_selector)?;
69
70        if params.clear {
71            element.click().ok(); // Focus
72            // Clear with Ctrl+A and Delete
73            tab.press_key("End").ok();
74            for _ in 0..params.text.len() + 100 {
75                tab.press_key("Backspace").ok();
76            }
77        }
78
79        element
80            .type_into(&params.text)
81            .map_err(|e| BrowserError::ToolExecutionFailed {
82                tool: "input".to_string(),
83                reason: e.to_string(),
84            })?;
85
86        let snapshot = {
87            let dom = context.get_dom()?;
88            render_aria_tree(&dom.root, RenderMode::Ai, None)
89        };
90
91        let result_json = serde_json::json!({
92            "snapshot": snapshot
93        });
94
95        Ok(ToolResult::success_with(result_json))
96    }
97}