1pub mod click;
7pub mod close;
8pub mod close_tab;
9pub mod evaluate;
10pub mod extract;
11pub mod go_back;
12pub mod go_forward;
13pub mod hover;
14pub mod html_to_markdown;
15pub mod input;
16pub mod markdown;
17pub mod navigate;
18pub mod new_tab;
19pub mod press_key;
20pub mod read_links;
21pub mod readability_script;
22pub mod screenshot;
23pub mod scroll;
24pub mod select;
25pub mod snapshot;
26pub mod switch_tab;
27pub mod tab_list;
28mod utils;
29pub mod wait;
30
31pub use click::ClickParams;
33pub use close::CloseParams;
34pub use close_tab::CloseTabParams;
35pub use evaluate::EvaluateParams;
36pub use extract::ExtractParams;
37pub use go_back::GoBackParams;
38pub use go_forward::GoForwardParams;
39pub use hover::HoverParams;
40pub use input::InputParams;
41pub use markdown::GetMarkdownParams;
42pub use navigate::NavigateParams;
43pub use new_tab::NewTabParams;
44pub use press_key::PressKeyParams;
45pub use read_links::ReadLinksParams;
46pub use screenshot::ScreenshotParams;
47pub use scroll::ScrollParams;
48pub use select::SelectParams;
49pub use snapshot::SnapshotParams;
50pub use switch_tab::SwitchTabParams;
51pub use tab_list::TabListParams;
52pub use wait::WaitParams;
53
54use crate::browser::BrowserSession;
55use crate::dom::DomTree;
56use crate::error::Result;
57use serde_json::Value;
58use std::collections::HashMap;
59use std::sync::Arc;
60
61pub struct ToolContext<'a> {
63 pub session: &'a BrowserSession,
65
66 pub dom_tree: Option<DomTree>,
68}
69
70impl<'a> ToolContext<'a> {
71 pub fn new(session: &'a BrowserSession) -> Self {
73 Self {
74 session,
75 dom_tree: None,
76 }
77 }
78
79 pub fn with_dom(session: &'a BrowserSession, dom_tree: DomTree) -> Self {
81 Self {
82 session,
83 dom_tree: Some(dom_tree),
84 }
85 }
86
87 pub fn get_dom(&mut self) -> Result<&DomTree> {
89 if self.dom_tree.is_none() {
90 self.dom_tree = Some(self.session.extract_dom()?);
91 }
92 Ok(self.dom_tree.as_ref().unwrap())
93 }
94}
95
96#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
98pub struct ToolResult {
99 pub success: bool,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub data: Option<Value>,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub error: Option<String>,
109
110 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
112 pub metadata: HashMap<String, Value>,
113}
114
115impl ToolResult {
116 pub fn success(data: Option<Value>) -> Self {
118 Self {
119 success: true,
120 data,
121 error: None,
122 metadata: HashMap::new(),
123 }
124 }
125
126 pub fn success_with<T: serde::Serialize>(data: T) -> Self {
128 Self {
129 success: true,
130 data: serde_json::to_value(data).ok(),
131 error: None,
132 metadata: HashMap::new(),
133 }
134 }
135
136 pub fn failure(error: impl Into<String>) -> Self {
138 Self {
139 success: false,
140 data: None,
141 error: Some(error.into()),
142 metadata: HashMap::new(),
143 }
144 }
145
146 pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
148 self.metadata.insert(key.into(), value);
149 self
150 }
151}
152
153pub trait Tool: Send + Sync + Default {
155 type Params: serde::Serialize + for<'de> serde::Deserialize<'de> + schemars::JsonSchema;
157
158 fn name(&self) -> &str;
160
161 fn parameters_schema(&self) -> Value {
163 serde_json::to_value(schemars::schema_for!(Self::Params)).unwrap_or_default()
164 }
165
166 fn execute_typed(&self, params: Self::Params, context: &mut ToolContext) -> Result<ToolResult>;
168
169 fn execute(&self, params: Value, context: &mut ToolContext) -> Result<ToolResult> {
171 let typed_params: Self::Params = serde_json::from_value(params).map_err(|e| {
172 crate::error::BrowserError::InvalidArgument(format!("Invalid parameters: {}", e))
173 })?;
174 self.execute_typed(typed_params, context)
175 }
176}
177
178pub trait DynTool: Send + Sync {
180 fn name(&self) -> &str;
181 fn parameters_schema(&self) -> Value;
182 fn execute(&self, params: Value, context: &mut ToolContext) -> Result<ToolResult>;
183}
184
185impl<T: Tool> DynTool for T {
187 fn name(&self) -> &str {
188 Tool::name(self)
189 }
190
191 fn parameters_schema(&self) -> Value {
192 Tool::parameters_schema(self)
193 }
194
195 fn execute(&self, params: Value, context: &mut ToolContext) -> Result<ToolResult> {
196 Tool::execute(self, params, context)
197 }
198}
199
200pub struct ToolRegistry {
202 tools: HashMap<String, Arc<dyn DynTool>>,
203}
204
205impl ToolRegistry {
206 pub fn new() -> Self {
208 Self {
209 tools: HashMap::new(),
210 }
211 }
212
213 pub fn with_defaults() -> Self {
215 let mut registry = Self::new();
216
217 registry.register(navigate::NavigateTool);
219 registry.register(go_back::GoBackTool);
220 registry.register(go_forward::GoForwardTool);
221 registry.register(wait::WaitTool);
222
223 registry.register(click::ClickTool);
225 registry.register(input::InputTool);
226 registry.register(select::SelectTool);
227 registry.register(hover::HoverTool);
228 registry.register(press_key::PressKeyTool);
229 registry.register(scroll::ScrollTool);
230
231 registry.register(new_tab::NewTabTool);
233 registry.register(tab_list::TabListTool);
234 registry.register(switch_tab::SwitchTabTool);
235 registry.register(close_tab::CloseTabTool);
236
237 registry.register(extract::ExtractContentTool);
239 registry.register(markdown::GetMarkdownTool);
240 registry.register(read_links::ReadLinksTool);
241 registry.register(snapshot::SnapshotTool);
242
243 registry.register(screenshot::ScreenshotTool);
245 registry.register(evaluate::EvaluateTool);
246 registry.register(close::CloseTool);
247
248 registry
249 }
250
251 pub fn register<T: Tool + 'static>(&mut self, tool: T) {
253 let name = tool.name().to_string();
254 self.tools.insert(name, Arc::new(tool));
255 }
256
257 pub fn get(&self, name: &str) -> Option<&Arc<dyn DynTool>> {
259 self.tools.get(name)
260 }
261
262 pub fn has(&self, name: &str) -> bool {
264 self.tools.contains_key(name)
265 }
266
267 pub fn list_names(&self) -> Vec<String> {
269 self.tools.keys().cloned().collect()
270 }
271
272 pub fn all_tools(&self) -> Vec<Arc<dyn DynTool>> {
274 self.tools.values().cloned().collect()
275 }
276
277 pub fn execute(
279 &self,
280 name: &str,
281 params: Value,
282 context: &mut ToolContext,
283 ) -> Result<ToolResult> {
284 match self.get(name) {
285 Some(tool) => tool.execute(params, context),
286 None => Ok(ToolResult::failure(format!("Tool '{}' not found", name))),
287 }
288 }
289
290 pub fn count(&self) -> usize {
292 self.tools.len()
293 }
294}
295
296impl Default for ToolRegistry {
297 fn default() -> Self {
298 Self::with_defaults()
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn test_tool_result_success() {
308 let result = ToolResult::success(Some(serde_json::json!({"url": "https://example.com"})));
309 assert!(result.success);
310 assert!(result.data.is_some());
311 assert!(result.error.is_none());
312 }
313
314 #[test]
315 fn test_tool_result_failure() {
316 let result = ToolResult::failure("Test error");
317 assert!(!result.success);
318 assert!(result.data.is_none());
319 assert_eq!(result.error, Some("Test error".to_string()));
320 }
321
322 #[test]
323 fn test_tool_result_with_metadata() {
324 let result = ToolResult::success(None).with_metadata("duration_ms", serde_json::json!(100));
325
326 assert!(result.metadata.contains_key("duration_ms"));
327 }
328}