1use crate::pool::BrowserSessionPool;
4use crate::session::BrowserSession;
5use crate::tools::*;
6use adk_core::{ReadonlyContext, Result, Tool, Toolset};
7use async_trait::async_trait;
8use std::sync::Arc;
9
10enum SessionResolver {
13 Fixed(Arc<BrowserSession>),
15 Pool(Arc<BrowserSessionPool>),
17}
18
19impl SessionResolver {
20 async fn resolve(&self, ctx: &Arc<dyn ReadonlyContext>) -> Result<Arc<BrowserSession>> {
21 match self {
22 SessionResolver::Fixed(session) => Ok(session.clone()),
23 SessionResolver::Pool(pool) => pool.get_or_create(ctx.user_id()).await,
24 }
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum BrowserProfile {
45 Minimal,
48 FormFilling,
51 Scraping,
54 Full,
56}
57
58pub struct BrowserToolset {
63 resolver: SessionResolver,
64 include_navigation: bool,
66 include_interaction: bool,
68 include_extraction: bool,
70 include_wait: bool,
72 include_screenshot: bool,
74 include_js: bool,
76 include_cookies: bool,
78 include_windows: bool,
80 include_frames: bool,
82 include_actions: bool,
84}
85
86impl BrowserToolset {
87 pub fn new(browser: Arc<BrowserSession>) -> Self {
89 Self {
90 resolver: SessionResolver::Fixed(browser),
91 include_navigation: true,
92 include_interaction: true,
93 include_extraction: true,
94 include_wait: true,
95 include_screenshot: true,
96 include_js: true,
97 include_cookies: true,
98 include_windows: true,
99 include_frames: true,
100 include_actions: true,
101 }
102 }
103
104 pub fn with_pool(pool: Arc<BrowserSessionPool>) -> Self {
120 Self {
121 resolver: SessionResolver::Pool(pool),
122 include_navigation: true,
123 include_interaction: true,
124 include_extraction: true,
125 include_wait: true,
126 include_screenshot: true,
127 include_js: true,
128 include_cookies: true,
129 include_windows: true,
130 include_frames: true,
131 include_actions: true,
132 }
133 }
134
135 pub fn with_pool_and_profile(pool: Arc<BrowserSessionPool>, profile: BrowserProfile) -> Self {
150 match profile {
151 BrowserProfile::Minimal => Self {
152 resolver: SessionResolver::Pool(pool),
153 include_navigation: true,
154 include_interaction: true,
155 include_extraction: true,
156 include_wait: true,
157 include_screenshot: true,
158 include_js: false,
159 include_cookies: false,
160 include_windows: false,
161 include_frames: false,
162 include_actions: false,
163 },
164 BrowserProfile::FormFilling => Self {
165 resolver: SessionResolver::Pool(pool),
166 include_navigation: true,
167 include_interaction: true,
168 include_extraction: true,
169 include_wait: true,
170 include_screenshot: true,
171 include_js: false,
172 include_cookies: false,
173 include_windows: false,
174 include_frames: false,
175 include_actions: false,
176 },
177 BrowserProfile::Scraping => Self {
178 resolver: SessionResolver::Pool(pool),
179 include_navigation: true,
180 include_interaction: false,
181 include_extraction: true,
182 include_wait: false,
183 include_screenshot: true,
184 include_js: true,
185 include_cookies: false,
186 include_windows: false,
187 include_frames: false,
188 include_actions: false,
189 },
190 BrowserProfile::Full => Self::with_pool(pool),
191 }
192 }
193
194 pub fn with_profile(browser: Arc<BrowserSession>, profile: BrowserProfile) -> Self {
199 match profile {
200 BrowserProfile::Minimal => Self {
201 resolver: SessionResolver::Fixed(browser),
202 include_navigation: true,
203 include_interaction: true,
204 include_extraction: true,
205 include_wait: true,
206 include_screenshot: true,
207 include_js: false,
208 include_cookies: false,
209 include_windows: false,
210 include_frames: false,
211 include_actions: false,
212 },
213 BrowserProfile::FormFilling => Self {
214 resolver: SessionResolver::Fixed(browser),
215 include_navigation: true,
216 include_interaction: true,
217 include_extraction: true,
218 include_wait: true,
219 include_screenshot: true,
220 include_js: false,
221 include_cookies: false,
222 include_windows: false,
223 include_frames: false,
224 include_actions: false,
225 },
226 BrowserProfile::Scraping => Self {
227 resolver: SessionResolver::Fixed(browser),
228 include_navigation: true,
229 include_interaction: false,
230 include_extraction: true,
231 include_wait: false,
232 include_screenshot: true,
233 include_js: true, include_cookies: false,
235 include_windows: false,
236 include_frames: false,
237 include_actions: false,
238 },
239 BrowserProfile::Full => Self::new(browser),
240 }
241 }
242
243 pub fn with_navigation(mut self, enabled: bool) -> Self {
245 self.include_navigation = enabled;
246 self
247 }
248
249 pub fn with_interaction(mut self, enabled: bool) -> Self {
251 self.include_interaction = enabled;
252 self
253 }
254
255 pub fn with_extraction(mut self, enabled: bool) -> Self {
257 self.include_extraction = enabled;
258 self
259 }
260
261 pub fn with_wait(mut self, enabled: bool) -> Self {
263 self.include_wait = enabled;
264 self
265 }
266
267 pub fn with_screenshot(mut self, enabled: bool) -> Self {
269 self.include_screenshot = enabled;
270 self
271 }
272
273 pub fn with_js(mut self, enabled: bool) -> Self {
275 self.include_js = enabled;
276 self
277 }
278
279 pub fn with_cookies(mut self, enabled: bool) -> Self {
281 self.include_cookies = enabled;
282 self
283 }
284
285 pub fn with_windows(mut self, enabled: bool) -> Self {
287 self.include_windows = enabled;
288 self
289 }
290
291 pub fn with_frames(mut self, enabled: bool) -> Self {
293 self.include_frames = enabled;
294 self
295 }
296
297 pub fn with_actions(mut self, enabled: bool) -> Self {
299 self.include_actions = enabled;
300 self
301 }
302
303 pub fn all_tools(&self) -> Vec<Arc<dyn Tool>> {
309 match &self.resolver {
310 SessionResolver::Fixed(session) => self.build_tools(session.clone()),
311 SessionResolver::Pool(_) => {
312 tracing::warn!(
313 "BrowserToolset::all_tools() called on a pool-backed toolset. \
314 Returns empty vec. Use Toolset::tools(ctx) instead."
315 );
316 Vec::new()
317 }
318 }
319 }
320
321 pub fn try_all_tools(&self) -> Result<Vec<Arc<dyn Tool>>> {
325 match &self.resolver {
326 SessionResolver::Fixed(session) => Ok(self.build_tools(session.clone())),
327 SessionResolver::Pool(_) => Err(adk_core::AdkError::Tool(
328 "Cannot resolve tools synchronously for a pool-backed BrowserToolset. \
329 Use Toolset::tools(ctx) instead."
330 .into(),
331 )),
332 }
333 }
334
335 fn build_tools(&self, browser: Arc<BrowserSession>) -> Vec<Arc<dyn Tool>> {
337 let mut tools: Vec<Arc<dyn Tool>> = Vec::new();
338
339 if self.include_navigation {
340 tools.push(Arc::new(NavigateTool::new(browser.clone())));
341 tools.push(Arc::new(BackTool::new(browser.clone())));
342 tools.push(Arc::new(ForwardTool::new(browser.clone())));
343 tools.push(Arc::new(RefreshTool::new(browser.clone())));
344 }
345
346 if self.include_interaction {
347 tools.push(Arc::new(ClickTool::new(browser.clone())));
348 tools.push(Arc::new(DoubleClickTool::new(browser.clone())));
349 tools.push(Arc::new(TypeTool::new(browser.clone())));
350 tools.push(Arc::new(ClearTool::new(browser.clone())));
351 tools.push(Arc::new(SelectTool::new(browser.clone())));
352 }
353
354 if self.include_extraction {
355 tools.push(Arc::new(ExtractTextTool::new(browser.clone())));
356 tools.push(Arc::new(ExtractAttributeTool::new(browser.clone())));
357 tools.push(Arc::new(ExtractLinksTool::new(browser.clone())));
358 tools.push(Arc::new(PageInfoTool::new(browser.clone())));
359 tools.push(Arc::new(PageSourceTool::new(browser.clone())));
360 }
361
362 if self.include_wait {
363 tools.push(Arc::new(WaitForElementTool::new(browser.clone())));
364 tools.push(Arc::new(WaitTool::new()));
365 tools.push(Arc::new(WaitForPageLoadTool::new(browser.clone())));
366 tools.push(Arc::new(WaitForTextTool::new(browser.clone())));
367 }
368
369 if self.include_screenshot {
370 tools.push(Arc::new(ScreenshotTool::new(browser.clone())));
371 }
372
373 if self.include_js {
374 tools.push(Arc::new(EvaluateJsTool::new(browser.clone())));
375 tools.push(Arc::new(ScrollTool::new(browser.clone())));
376 tools.push(Arc::new(HoverTool::new(browser.clone())));
377 tools.push(Arc::new(AlertTool::new(browser.clone())));
378 }
379
380 if self.include_cookies {
381 tools.push(Arc::new(GetCookiesTool::new(browser.clone())));
382 tools.push(Arc::new(GetCookieTool::new(browser.clone())));
383 tools.push(Arc::new(AddCookieTool::new(browser.clone())));
384 tools.push(Arc::new(DeleteCookieTool::new(browser.clone())));
385 tools.push(Arc::new(DeleteAllCookiesTool::new(browser.clone())));
386 }
387
388 if self.include_windows {
389 tools.push(Arc::new(ListWindowsTool::new(browser.clone())));
390 tools.push(Arc::new(NewTabTool::new(browser.clone())));
391 tools.push(Arc::new(NewWindowTool::new(browser.clone())));
392 tools.push(Arc::new(SwitchWindowTool::new(browser.clone())));
393 tools.push(Arc::new(CloseWindowTool::new(browser.clone())));
394 tools.push(Arc::new(MaximizeWindowTool::new(browser.clone())));
395 tools.push(Arc::new(MinimizeWindowTool::new(browser.clone())));
396 tools.push(Arc::new(SetWindowSizeTool::new(browser.clone())));
397 }
398
399 if self.include_frames {
400 tools.push(Arc::new(SwitchToFrameTool::new(browser.clone())));
401 tools.push(Arc::new(SwitchToParentFrameTool::new(browser.clone())));
402 tools.push(Arc::new(SwitchToDefaultContentTool::new(browser.clone())));
403 }
404
405 if self.include_actions {
406 tools.push(Arc::new(DragAndDropTool::new(browser.clone())));
407 tools.push(Arc::new(RightClickTool::new(browser.clone())));
408 tools.push(Arc::new(FocusTool::new(browser.clone())));
409 tools.push(Arc::new(ElementStateTool::new(browser.clone())));
410 tools.push(Arc::new(PressKeyTool::new(browser.clone())));
411 tools.push(Arc::new(FileUploadTool::new(browser.clone())));
412 tools.push(Arc::new(PrintToPdfTool::new(browser)));
413 }
414
415 tools
416 }
417}
418
419#[async_trait]
420impl Toolset for BrowserToolset {
421 fn name(&self) -> &str {
422 "browser"
423 }
424
425 async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>> {
426 let session = self.resolver.resolve(&ctx).await?;
427 Ok(self.build_tools(session))
428 }
429}
430
431pub fn minimal_browser_tools(browser: Arc<BrowserSession>) -> Vec<Arc<dyn Tool>> {
433 vec![
434 Arc::new(NavigateTool::new(browser.clone())),
435 Arc::new(ClickTool::new(browser.clone())),
436 Arc::new(TypeTool::new(browser.clone())),
437 Arc::new(ExtractTextTool::new(browser.clone())),
438 Arc::new(WaitForElementTool::new(browser.clone())),
439 Arc::new(ScreenshotTool::new(browser)),
440 ]
441}
442
443pub fn readonly_browser_tools(browser: Arc<BrowserSession>) -> Vec<Arc<dyn Tool>> {
445 vec![
446 Arc::new(NavigateTool::new(browser.clone())),
447 Arc::new(ExtractTextTool::new(browser.clone())),
448 Arc::new(ExtractAttributeTool::new(browser.clone())),
449 Arc::new(ExtractLinksTool::new(browser.clone())),
450 Arc::new(PageInfoTool::new(browser.clone())),
451 Arc::new(ScreenshotTool::new(browser.clone())),
452 Arc::new(ScrollTool::new(browser)),
453 ]
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use crate::config::BrowserConfig;
460
461 #[test]
462 fn test_toolset_all_tools() {
463 let browser = Arc::new(BrowserSession::new(BrowserConfig::default()));
464 let toolset = BrowserToolset::new(browser);
465 let tools = toolset.all_tools();
466
467 assert!(tools.len() > 40);
469
470 let tool_names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
472 assert!(tool_names.contains(&"browser_navigate"));
473 assert!(tool_names.contains(&"browser_click"));
474 assert!(tool_names.contains(&"browser_type"));
475 assert!(tool_names.contains(&"browser_screenshot"));
476 assert!(tool_names.contains(&"browser_get_cookies"));
478 assert!(tool_names.contains(&"browser_new_tab"));
479 assert!(tool_names.contains(&"browser_switch_to_frame"));
480 assert!(tool_names.contains(&"browser_drag_and_drop"));
481 }
482
483 #[test]
484 fn test_toolset_selective() {
485 let browser = Arc::new(BrowserSession::new(BrowserConfig::default()));
486 let toolset = BrowserToolset::new(browser)
487 .with_navigation(true)
488 .with_interaction(false)
489 .with_extraction(false)
490 .with_wait(false)
491 .with_screenshot(false)
492 .with_js(false)
493 .with_cookies(false)
494 .with_windows(false)
495 .with_frames(false)
496 .with_actions(false);
497
498 let tools = toolset.all_tools();
499
500 assert_eq!(tools.len(), 4);
502 }
503
504 #[test]
505 fn test_minimal_tools() {
506 let browser = Arc::new(BrowserSession::new(BrowserConfig::default()));
507 let tools = minimal_browser_tools(browser);
508
509 assert_eq!(tools.len(), 6);
510 }
511
512 #[test]
513 fn test_profile_form_filling() {
514 let browser = Arc::new(BrowserSession::new(BrowserConfig::default()));
515 let toolset = BrowserToolset::with_profile(browser, BrowserProfile::FormFilling);
516 let tools = toolset.all_tools();
517
518 let tool_names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
520 assert!(tool_names.contains(&"browser_navigate"));
521 assert!(tool_names.contains(&"browser_click"));
522 assert!(tool_names.contains(&"browser_type"));
523 assert!(tool_names.contains(&"browser_select"));
524 assert!(tool_names.contains(&"browser_clear"));
525 assert!(tool_names.contains(&"browser_screenshot"));
526 assert!(!tool_names.contains(&"browser_get_cookies"));
528 assert!(!tool_names.contains(&"browser_new_tab"));
529 assert!(!tool_names.contains(&"browser_switch_to_frame"));
530 assert!(!tool_names.contains(&"browser_drag_and_drop"));
531 }
532
533 #[test]
534 fn test_profile_scraping() {
535 let browser = Arc::new(BrowserSession::new(BrowserConfig::default()));
536 let toolset = BrowserToolset::with_profile(browser, BrowserProfile::Scraping);
537 let tools = toolset.all_tools();
538
539 let tool_names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
540 assert!(tool_names.contains(&"browser_navigate"));
541 assert!(tool_names.contains(&"browser_extract_text"));
542 assert!(tool_names.contains(&"browser_extract_links"));
543 assert!(tool_names.contains(&"browser_screenshot"));
544 assert!(tool_names.contains(&"browser_scroll"));
545 assert!(!tool_names.contains(&"browser_click"));
547 assert!(!tool_names.contains(&"browser_type"));
548 }
549
550 #[test]
551 fn test_profile_full_matches_new() {
552 let browser1 = Arc::new(BrowserSession::new(BrowserConfig::default()));
553 let browser2 = Arc::new(BrowserSession::new(BrowserConfig::default()));
554 let full = BrowserToolset::with_profile(browser1, BrowserProfile::Full);
555 let default = BrowserToolset::new(browser2);
556 assert_eq!(full.all_tools().len(), default.all_tools().len());
557 }
558}