1use crate::tools::registry::Tool;
2use crate::types::Result;
3use async_trait::async_trait;
4use serde_json::{json, Value};
5
6pub struct WebSearch {
8 _client: reqwest::Client,
9}
10
11impl WebSearch {
12 pub fn new() -> Self {
14 Self {
15 _client: reqwest::Client::new(),
16 }
17 }
18}
19
20impl Default for WebSearch {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26#[async_trait]
27impl Tool for WebSearch {
28 fn name(&self) -> &str {
29 "web_search"
30 }
31
32 fn description(&self) -> &str {
33 "Search the web for information using DuckDuckGo. Returns a list of search results with titles, snippets, and URLs."
34 }
35
36 fn parameters_schema(&self) -> Value {
37 json!({
38 "type": "object",
39 "properties": {
40 "query": {
41 "type": "string",
42 "description": "The search query to look up"
43 },
44 "max_results": {
45 "type": "integer",
46 "description": "Maximum number of results to return (default: 5)",
47 "default": 5
48 }
49 },
50 "required": ["query"]
51 })
52 }
53
54 async fn execute(&self, args: Value) -> Result<Value> {
55 let query = args["query"]
56 .as_str()
57 .ok_or_else(|| crate::types::AppError::InvalidInput("query is required".to_string()))?;
58
59 let max_results = args["max_results"].as_i64().unwrap_or(5) as usize;
60
61 let search_args = daedra::types::SearchArgs {
63 query: query.to_string(),
64 options: Some(daedra::types::SearchOptions {
65 num_results: max_results,
66 ..Default::default()
67 }),
68 };
69
70 let results = daedra::tools::search::perform_search(&search_args)
71 .await
72 .map_err(|e| crate::types::AppError::External(format!("Search failed: {}", e)))?;
73
74 let json_results: Vec<Value> = results
76 .data
77 .into_iter()
78 .map(|result| {
79 json!({
80 "title": result.title,
81 "url": result.url,
82 "snippet": result.description
83 })
84 })
85 .collect();
86
87 Ok(json!({
88 "query": query,
89 "results": json_results,
90 "count": json_results.len()
91 }))
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn test_schema() {
101 let tool = WebSearch::new();
102 let schema = tool.parameters_schema();
103 assert_eq!(schema["type"], "object");
104 assert!(schema["properties"]["query"].is_object());
105 assert!(schema["required"]
106 .as_array()
107 .unwrap()
108 .contains(&json!("query")));
109 }
110
111 #[tokio::test]
112 async fn test_missing_query() {
113 let tool = WebSearch::new();
114 let result = tool.execute(json!({})).await;
115 assert!(result.is_err());
116 }
117}