adk_browser/tools/
type_text.rs1use crate::session::BrowserSession;
4use adk_core::{Result, Tool, ToolContext};
5use async_trait::async_trait;
6use serde_json::{json, Value};
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 Ok(json!({
112 "success": true,
113 "typed_text": text,
114 "field_value": field_value
115 }))
116 }
117}
118
119pub 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
168pub 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 let option_selector = if let Some(val) = value {
226 format!("{} option[value='{}']", selector, val)
227 } else if let Some(txt) = text {
228 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 self.browser.click(&option_selector).await?;
292
293 Ok(json!({
294 "success": true,
295 "selected_value": value
296 }))
297 }
298}