adk_browser/tools/
type_text.rs

1//! Type tool for entering text into form fields.
2
3use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{json, Value};
7use std::sync::Arc;
8
9/// Tool for typing text into input fields.
10pub struct TypeTool {
11    browser: Arc<BrowserSession>,
12}
13
14impl TypeTool {
15    /// Create a new type tool with a shared browser session.
16    pub fn new(browser: Arc<BrowserSession>) -> Self {
17        Self { browser }
18    }
19}
20
21#[async_trait]
22impl Tool for TypeTool {
23    fn name(&self) -> &str {
24        "browser_type"
25    }
26
27    fn description(&self) -> &str {
28        "Type text into an input field or text area. Can optionally clear the field first."
29    }
30
31    fn parameters_schema(&self) -> Option<Value> {
32        Some(json!({
33            "type": "object",
34            "properties": {
35                "selector": {
36                    "type": "string",
37                    "description": "CSS selector for the input element (e.g., '#username', 'input[name=email]')"
38                },
39                "text": {
40                    "type": "string",
41                    "description": "The text to type into the field"
42                },
43                "clear_first": {
44                    "type": "boolean",
45                    "description": "Whether to clear the field before typing (default: true)"
46                },
47                "press_enter": {
48                    "type": "boolean",
49                    "description": "Whether to press Enter after typing (default: false)"
50                }
51            },
52            "required": ["selector", "text"]
53        }))
54    }
55
56    fn response_schema(&self) -> Option<Value> {
57        Some(json!({
58            "type": "object",
59            "properties": {
60                "success": { "type": "boolean" },
61                "typed_text": { "type": "string" },
62                "field_value": { "type": "string" }
63            }
64        }))
65    }
66
67    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
68        let selector = args
69            .get("selector")
70            .and_then(|v| v.as_str())
71            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
72
73        let text = args
74            .get("text")
75            .and_then(|v| v.as_str())
76            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'text' parameter".to_string()))?;
77
78        let clear_first = args.get("clear_first").and_then(|v| v.as_bool()).unwrap_or(true);
79
80        let press_enter = args.get("press_enter").and_then(|v| v.as_bool()).unwrap_or(false);
81
82        // Wait for element
83        let element = self.browser.wait_for_element(selector, 10).await?;
84
85        // Clear if requested
86        if clear_first {
87            element
88                .clear()
89                .await
90                .map_err(|e| adk_core::AdkError::Tool(format!("Clear failed: {}", e)))?;
91        }
92
93        // Type the text
94        element
95            .send_keys(text)
96            .await
97            .map_err(|e| adk_core::AdkError::Tool(format!("Type failed: {}", e)))?;
98
99        // Press Enter if requested
100        if press_enter {
101            element
102                .send_keys("\n")
103                .await
104                .map_err(|e| adk_core::AdkError::Tool(format!("Enter key failed: {}", e)))?;
105        }
106
107        // Get the current value
108        let field_value =
109            element.attr("value").await.ok().flatten().unwrap_or_else(|| text.to_string());
110
111        Ok(json!({
112            "success": true,
113            "typed_text": text,
114            "field_value": field_value
115        }))
116    }
117}
118
119/// Tool for clearing input fields.
120pub struct ClearTool {
121    browser: Arc<BrowserSession>,
122}
123
124impl ClearTool {
125    pub fn new(browser: Arc<BrowserSession>) -> Self {
126        Self { browser }
127    }
128}
129
130#[async_trait]
131impl Tool for ClearTool {
132    fn name(&self) -> &str {
133        "browser_clear"
134    }
135
136    fn description(&self) -> &str {
137        "Clear the contents of an input field or text area."
138    }
139
140    fn parameters_schema(&self) -> Option<Value> {
141        Some(json!({
142            "type": "object",
143            "properties": {
144                "selector": {
145                    "type": "string",
146                    "description": "CSS selector for the input element to clear"
147                }
148            },
149            "required": ["selector"]
150        }))
151    }
152
153    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
154        let selector = args
155            .get("selector")
156            .and_then(|v| v.as_str())
157            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
158
159        self.browser.clear(selector).await?;
160
161        Ok(json!({
162            "success": true,
163            "cleared": selector
164        }))
165    }
166}
167
168/// Tool for selecting options from dropdown menus.
169pub struct SelectTool {
170    browser: Arc<BrowserSession>,
171}
172
173impl SelectTool {
174    pub fn new(browser: Arc<BrowserSession>) -> Self {
175        Self { browser }
176    }
177}
178
179#[async_trait]
180impl Tool for SelectTool {
181    fn name(&self) -> &str {
182        "browser_select"
183    }
184
185    fn description(&self) -> &str {
186        "Select an option from a dropdown/select element by value, text, or index."
187    }
188
189    fn parameters_schema(&self) -> Option<Value> {
190        Some(json!({
191            "type": "object",
192            "properties": {
193                "selector": {
194                    "type": "string",
195                    "description": "CSS selector for the select element"
196                },
197                "value": {
198                    "type": "string",
199                    "description": "The value attribute of the option to select"
200                },
201                "text": {
202                    "type": "string",
203                    "description": "The visible text of the option to select"
204                },
205                "index": {
206                    "type": "integer",
207                    "description": "The index of the option to select (0-based)"
208                }
209            },
210            "required": ["selector"]
211        }))
212    }
213
214    async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
215        let selector = args
216            .get("selector")
217            .and_then(|v| v.as_str())
218            .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
219
220        let value = args.get("value").and_then(|v| v.as_str());
221        let text = args.get("text").and_then(|v| v.as_str());
222        let index = args.get("index").and_then(|v| v.as_u64());
223
224        // Build the appropriate selector for the option
225        let option_selector = if let Some(val) = value {
226            format!("{} option[value='{}']", selector, val)
227        } else if let Some(txt) = text {
228            // Use XPath for text matching isn't available, use JS instead
229            let script = format!(
230                r#"
231                var select = document.querySelector('{}');
232                for (var i = 0; i < select.options.length; i++) {{
233                    if (select.options[i].text === '{}') {{
234                        select.selectedIndex = i;
235                        select.dispatchEvent(new Event('change', {{ bubbles: true }}));
236                        return true;
237                    }}
238                }}
239                return false;
240                "#,
241                selector.replace('\'', "\\'"),
242                txt.replace('\'', "\\'")
243            );
244
245            let result = self.browser.execute_script(&script).await?;
246            if result.as_bool() == Some(true) {
247                return Ok(json!({
248                    "success": true,
249                    "selected_text": txt
250                }));
251            } else {
252                return Err(adk_core::AdkError::Tool(format!(
253                    "Option with text '{}' not found",
254                    txt
255                )));
256            }
257        } else if let Some(idx) = index {
258            let script = format!(
259                r#"
260                var select = document.querySelector('{}');
261                if (select && select.options.length > {}) {{
262                    select.selectedIndex = {};
263                    select.dispatchEvent(new Event('change', {{ bubbles: true }}));
264                    return select.options[{}].text;
265                }}
266                return null;
267                "#,
268                selector.replace('\'', "\\'"),
269                idx,
270                idx,
271                idx
272            );
273
274            let result = self.browser.execute_script(&script).await?;
275            if let Some(selected_text) = result.as_str() {
276                return Ok(json!({
277                    "success": true,
278                    "selected_text": selected_text,
279                    "selected_index": idx
280                }));
281            } else {
282                return Err(adk_core::AdkError::Tool(format!("Option at index {} not found", idx)));
283            }
284        } else {
285            return Err(adk_core::AdkError::Tool(
286                "Must specify 'value', 'text', or 'index'".to_string(),
287            ));
288        };
289
290        // Click the option
291        self.browser.click(&option_selector).await?;
292
293        Ok(json!({
294            "success": true,
295            "selected_value": value
296        }))
297    }
298}