use crate::agent::memory::AgentMemory;
use crate::agent::views::ActionResult;
use crate::error::{BrowsingError, Result};
use crate::observability::{SessionRecorder, StepResult};
use crate::tools::handlers::{
AdvancedHandler, ContentHandler, FileHandler, Handler, InteractionHandler, NavigationHandler,
TabsHandler,
};
use crate::tools::registry::Registry;
use crate::tools::views::{ActionContext, ActionModel, ActionParams};
use crate::traits::BrowserClient;
pub struct Tools {
pub registry: Registry,
pub display_files_in_done_text: bool,
}
impl Tools {
pub fn new(exclude_actions: Vec<String>) -> Self {
let mut registry = Registry::new(exclude_actions);
Self::register_default_actions(&mut registry);
Self {
registry,
display_files_in_done_text: true,
}
}
fn register_default_actions(registry: &mut Registry) {
registry.register_action(
"search",
"Search the web using a search engine",
None,
);
registry.register_action(
"navigate",
"Navigate to a URL",
None,
);
registry.register_action(
"go_back",
"Go back in browser history",
None,
);
registry.register_action(
"go_forward",
"Go forward in browser history",
None,
);
registry.register_action(
"reload",
"Reload the current page",
None,
);
registry.register_action(
"click",
"Click an element by index",
None,
);
registry.register_action(
"input",
"Input text into a field",
None,
);
registry.register_action(
"done",
"Mark the task as complete",
None,
);
registry.register_action(
"switch",
"Switch to another open tab by tab_id",
None,
);
registry.register_action(
"close",
"Close a tab by tab_id",
None,
);
registry.register_action(
"scroll",
"Scroll the page up or down by pages",
None,
);
registry.register_action(
"wait",
"Wait for a condition (element, text, url, stability) or fixed seconds",
None,
);
registry.register_action(
"send_keys",
"Send keyboard keys (Enter, Escape, Tab, etc.)",
None,
);
registry.register_action(
"evaluate",
"Execute JavaScript code on the page",
None,
);
registry.register_action(
"find_text",
"Scroll to specific text on page",
None,
);
registry.register_action(
"dropdown_options",
"Get dropdown option values",
None,
);
registry.register_action(
"select_dropdown",
"Select dropdown options",
None,
);
registry.register_action(
"upload_file",
"Upload files to file inputs",
None,
);
registry.register_action(
"extract",
"Extract structured data from page content. Use when: on right page, know what to extract, haven't called before on same page+query.",
None,
);
registry.register_action(
"write_file",
"Write content to a file on disk",
None,
);
registry.register_action(
"read_file",
"Read content from a file on disk",
None,
);
registry.register_action(
"replace_file",
"Replace text within a file on disk",
None,
);
}
pub async fn act(
&self,
action: ActionModel,
browser_session: &mut dyn BrowserClient,
selector_map: Option<
&std::collections::HashMap<u32, crate::dom::views::DOMInteractedElement>,
>,
memory: Option<&mut AgentMemory>,
recorder: Option<&mut SessionRecorder>,
dom_snapshot: Option<String>,
) -> Result<ActionResult> {
let start = std::time::Instant::now();
let action_type = action.action_type.clone();
let params = action.params.clone();
let url = browser_session.get_current_url().await.ok();
let result = self
._dispatch(action, browser_session, selector_map, memory, dom_snapshot.clone())
.await;
if let Some(rec) = recorder {
let duration_ms = start.elapsed().as_millis() as u64;
let step_result = match &result {
Ok(r) => StepResult::success(r.extracted_content.clone()),
Err(e) => StepResult::failure(format!("{e}")),
};
rec.record_action(
rec.len(),
&action_type,
params,
url,
dom_snapshot,
step_result,
duration_ms,
);
}
result
}
async fn _dispatch(
&self,
action: ActionModel,
browser_session: &mut dyn BrowserClient,
selector_map: Option<
&std::collections::HashMap<u32, crate::dom::views::DOMInteractedElement>,
>,
memory: Option<&mut AgentMemory>,
dom_snapshot: Option<String>,
) -> Result<ActionResult> {
let action_type = action.action_type.as_str();
if let Some(handler) = self.registry.get_handler(action_type) {
let params =
ActionParams::new(&action.params).with_action_type(action.action_type.clone());
let mut context = ActionContext {
browser: browser_session,
selector_map,
memory,
recorder: None,
dom_snapshot,
};
return handler.execute(¶ms, &mut context).await;
}
let params = ActionParams::new(&action.params).with_action_type(action.action_type.clone());
let mut context = ActionContext {
browser: browser_session,
selector_map,
memory,
recorder: None,
dom_snapshot,
};
match action_type {
"search" | "navigate" | "go_back" | "go_forward" | "reload" => NavigationHandler.handle(¶ms, &mut context).await,
"click" | "input" | "send_keys" => {
InteractionHandler.handle(¶ms, &mut context).await
}
"switch" | "close" => TabsHandler.handle(¶ms, &mut context).await,
"scroll" | "find_text" | "dropdown_options" | "select_dropdown" => {
ContentHandler.handle(¶ms, &mut context).await
}
"done" | "evaluate" | "upload_file" | "wait" => {
AdvancedHandler.handle(¶ms, &mut context).await
}
"write_file" | "read_file" | "replace_file" => FileHandler.handle(¶ms, &mut context).await,
"extract" => {
crate::tools::handlers::extract::handle_extract(action, browser_session).await
}
_ => Err(BrowsingError::Tool(format!(
"Unknown action type: {action_type}"
))),
}
}
pub fn register_custom_action<H: crate::tools::views::ActionHandler + 'static>(
&mut self,
name: String,
description: String,
domains: Option<Vec<String>>,
handler: H,
) {
self.registry
.register_custom_action(name, description, domains, handler);
}
}
impl Default for Tools {
fn default() -> Self {
Self::new(vec![])
}
}