Skip to main content

cortexai_tools/
web.rs

1//! Web-related tools
2
3use async_trait::async_trait;
4use cortexai_core::{errors::ToolError, ExecutionContext, Tool, ToolSchema};
5use serde_json::json;
6
7/// Web search tool (stub - requires external API)
8pub struct WebSearchTool;
9
10impl Default for WebSearchTool {
11    fn default() -> Self {
12        Self::new()
13    }
14}
15
16impl WebSearchTool {
17    pub fn new() -> Self {
18        Self
19    }
20}
21
22#[async_trait]
23impl Tool for WebSearchTool {
24    fn schema(&self) -> ToolSchema {
25        ToolSchema::new("web_search", "Search the web for information").with_parameters(json!({
26            "type": "object",
27            "properties": {
28                "query": {
29                    "type": "string",
30                    "description": "Search query"
31                },
32                "max_results": {
33                    "type": "integer",
34                    "description": "Maximum number of results",
35                    "default": 5
36                }
37            },
38            "required": ["query"]
39        }))
40    }
41
42    async fn execute(
43        &self,
44        _context: &ExecutionContext,
45        arguments: serde_json::Value,
46    ) -> Result<serde_json::Value, ToolError> {
47        let query = arguments["query"]
48            .as_str()
49            .ok_or_else(|| ToolError::InvalidArguments("Missing 'query' field".to_string()))?;
50
51        // TODO: Implement actual web search (e.g., using SerpAPI, Google Custom Search)
52        Ok(json!({
53            "query": query,
54            "results": [
55                {
56                    "title": "Example Result 1",
57                    "url": "https://example.com/1",
58                    "snippet": "This is a sample search result..."
59                }
60            ],
61            "note": "Web search not fully implemented - this is mock data"
62        }))
63    }
64}
65
66/// HTTP request tool
67pub struct HttpRequestTool;
68
69impl Default for HttpRequestTool {
70    fn default() -> Self {
71        Self::new()
72    }
73}
74
75impl HttpRequestTool {
76    pub fn new() -> Self {
77        Self
78    }
79}
80
81#[async_trait]
82impl Tool for HttpRequestTool {
83    fn schema(&self) -> ToolSchema {
84        ToolSchema::new("http_request", "Make an HTTP request to a URL")
85            .with_parameters(json!({
86                "type": "object",
87                "properties": {
88                    "url": {
89                        "type": "string",
90                        "description": "URL to request"
91                    },
92                    "method": {
93                        "type": "string",
94                        "enum": ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"],
95                        "default": "GET"
96                    },
97                    "headers": {
98                        "type": "object",
99                        "description": "Request headers"
100                    },
101                    "body": {
102                        "type": "string",
103                        "description": "Request body (for POST/PUT)"
104                    }
105                },
106                "required": ["url"]
107            }))
108            .with_dangerous(true)
109    }
110
111    async fn execute(
112        &self,
113        _context: &ExecutionContext,
114        arguments: serde_json::Value,
115    ) -> Result<serde_json::Value, ToolError> {
116        let url = arguments["url"]
117            .as_str()
118            .ok_or_else(|| ToolError::InvalidArguments("Missing 'url' field".to_string()))?;
119
120        let method = arguments["method"].as_str().unwrap_or("GET").to_uppercase();
121        let body_content = arguments["body"].as_str().unwrap_or("").to_string();
122
123        // Build the request
124        let client = reqwest::Client::new();
125        let mut request_builder = match method.as_str() {
126            "GET" => client.get(url),
127            "POST" => client.post(url).body(body_content),
128            "PUT" => client.put(url).body(body_content),
129            "DELETE" => client.delete(url),
130            "PATCH" => client.patch(url).body(body_content),
131            "HEAD" => client.head(url),
132            _ => {
133                return Err(ToolError::InvalidArguments(format!(
134                    "Unsupported method: {}",
135                    method
136                )))
137            }
138        };
139
140        // Add custom headers if provided
141        if let Some(headers) = arguments["headers"].as_object() {
142            for (key, value) in headers {
143                if let Some(header_value) = value.as_str() {
144                    request_builder = request_builder.header(key.as_str(), header_value);
145                }
146            }
147        }
148
149        // Execute the request
150        let response = request_builder.send().await;
151
152        match response {
153            Ok(resp) => {
154                let status = resp.status().as_u16();
155                let response_headers: serde_json::Map<String, serde_json::Value> = resp
156                    .headers()
157                    .iter()
158                    .filter_map(|(k, v)| v.to_str().ok().map(|val| (k.to_string(), json!(val))))
159                    .collect();
160                let body = resp.text().await.unwrap_or_default();
161
162                Ok(json!({
163                    "status": status,
164                    "headers": response_headers,
165                    "body": body,
166                    "success": (200..300).contains(&status)
167                }))
168            }
169            Err(e) => Err(ToolError::ExecutionFailed(e.to_string())),
170        }
171    }
172}