adk_browser/tools/
type_text.rs1use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{Value, json};
7use std::sync::Arc;
8
9pub struct TypeTool {
11 browser: Arc<BrowserSession>,
12}
13
14impl TypeTool {
15 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 let element = self.browser.wait_for_element(selector, 10).await?;
84
85 if clear_first {
87 element
88 .clear()
89 .await
90 .map_err(|e| adk_core::AdkError::Tool(format!("Clear failed: {}", e)))?;
91 }
92
93 element
95 .send_keys(text)
96 .await
97 .map_err(|e| adk_core::AdkError::Tool(format!("Type failed: {}", e)))?;
98
99 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 let field_value =
109 element.attr("value").await.ok().flatten().unwrap_or_else(|| text.to_string());
110
111 let context = self.browser.page_context().await.unwrap_or_default();
113
114 Ok(json!({
115 "success": true,
116 "typed_text": text,
117 "field_value": field_value,
118 "page": context
119 }))
120 }
121}
122
123pub struct ClearTool {
125 browser: Arc<BrowserSession>,
126}
127
128impl ClearTool {
129 pub fn new(browser: Arc<BrowserSession>) -> Self {
130 Self { browser }
131 }
132}
133
134#[async_trait]
135impl Tool for ClearTool {
136 fn name(&self) -> &str {
137 "browser_clear"
138 }
139
140 fn description(&self) -> &str {
141 "Clear the contents of an input field or text area."
142 }
143
144 fn parameters_schema(&self) -> Option<Value> {
145 Some(json!({
146 "type": "object",
147 "properties": {
148 "selector": {
149 "type": "string",
150 "description": "CSS selector for the input element to clear"
151 }
152 },
153 "required": ["selector"]
154 }))
155 }
156
157 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
158 let selector = args
159 .get("selector")
160 .and_then(|v| v.as_str())
161 .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
162
163 self.browser.clear(selector).await?;
164
165 let context = self.browser.page_context().await.unwrap_or_default();
166
167 Ok(json!({
168 "success": true,
169 "cleared": selector,
170 "page": context
171 }))
172 }
173}
174
175pub struct SelectTool {
177 browser: Arc<BrowserSession>,
178}
179
180impl SelectTool {
181 pub fn new(browser: Arc<BrowserSession>) -> Self {
182 Self { browser }
183 }
184}
185
186#[async_trait]
187impl Tool for SelectTool {
188 fn name(&self) -> &str {
189 "browser_select"
190 }
191
192 fn description(&self) -> &str {
193 "Select an option from a dropdown/select element by value, text, or index."
194 }
195
196 fn parameters_schema(&self) -> Option<Value> {
197 Some(json!({
198 "type": "object",
199 "properties": {
200 "selector": {
201 "type": "string",
202 "description": "CSS selector for the select element"
203 },
204 "value": {
205 "type": "string",
206 "description": "The value attribute of the option to select"
207 },
208 "text": {
209 "type": "string",
210 "description": "The visible text of the option to select"
211 },
212 "index": {
213 "type": "integer",
214 "description": "The index of the option to select (0-based)"
215 }
216 },
217 "required": ["selector"]
218 }))
219 }
220
221 async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
222 let selector = args
223 .get("selector")
224 .and_then(|v| v.as_str())
225 .ok_or_else(|| adk_core::AdkError::Tool("Missing 'selector' parameter".to_string()))?;
226
227 let value = args.get("value").and_then(|v| v.as_str());
228 let text = args.get("text").and_then(|v| v.as_str());
229 let index = args.get("index").and_then(|v| v.as_u64());
230
231 let escaped_selector = crate::escape::escape_js_string(selector);
232
233 let option_selector = if let Some(val) = value {
235 let escaped_val = crate::escape::escape_js_string(val);
236 format!("{selector} option[value='{escaped_val}']")
237 } else if let Some(txt) = text {
238 let escaped_txt = crate::escape::escape_js_string(txt);
239 let script = format!(
241 r#"
242 var select = document.querySelector('{escaped_selector}');
243 for (var i = 0; i < select.options.length; i++) {{
244 if (select.options[i].text === '{escaped_txt}') {{
245 select.selectedIndex = i;
246 select.dispatchEvent(new Event('change', {{ bubbles: true }}));
247 return true;
248 }}
249 }}
250 return false;
251 "#,
252 );
253
254 let result = self.browser.execute_script(&script).await?;
255 if result.as_bool() == Some(true) {
256 let context = self.browser.page_context().await.unwrap_or_default();
257 return Ok(json!({
258 "success": true,
259 "selected_text": txt,
260 "page": context
261 }));
262 } else {
263 return Err(adk_core::AdkError::Tool(format!(
264 "Option with text '{}' not found",
265 txt
266 )));
267 }
268 } else if let Some(idx) = index {
269 let script = format!(
270 r#"
271 var select = document.querySelector('{escaped_selector}');
272 if (select && select.options.length > {idx}) {{
273 select.selectedIndex = {idx};
274 select.dispatchEvent(new Event('change', {{ bubbles: true }}));
275 return select.options[{idx}].text;
276 }}
277 return null;
278 "#,
279 );
280
281 let result = self.browser.execute_script(&script).await?;
282 if let Some(selected_text) = result.as_str() {
283 let context = self.browser.page_context().await.unwrap_or_default();
284 return Ok(json!({
285 "success": true,
286 "selected_text": selected_text,
287 "selected_index": idx,
288 "page": context
289 }));
290 } else {
291 return Err(adk_core::AdkError::Tool(format!("Option at index {} not found", idx)));
292 }
293 } else {
294 return Err(adk_core::AdkError::Tool(
295 "Must specify 'value', 'text', or 'index'".to_string(),
296 ));
297 };
298
299 self.browser.click(&option_selector).await?;
301
302 let context = self.browser.page_context().await.unwrap_or_default();
303
304 Ok(json!({
305 "success": true,
306 "selected_value": value,
307 "page": context
308 }))
309 }
310}