Skip to main content

brainwires_tool_system/
web.rs

1use anyhow::Result;
2use reqwest::Client;
3use serde::Deserialize;
4use serde_json::{Value, json};
5use std::collections::HashMap;
6
7use brainwires_core::{Tool, ToolContext, ToolInputSchema, ToolResult};
8
9/// Web fetching tool implementation
10pub struct WebTool;
11
12impl WebTool {
13    /// Return tool definitions for web operations.
14    pub fn get_tools() -> Vec<Tool> {
15        vec![Self::fetch_url_tool()]
16    }
17
18    fn fetch_url_tool() -> Tool {
19        let mut properties = HashMap::new();
20        properties.insert(
21            "url".to_string(),
22            json!({"type": "string", "description": "URL to fetch"}),
23        );
24        Tool {
25            name: "fetch_url".to_string(),
26            description: "Fetch content from a URL on the internet.".to_string(),
27            input_schema: ToolInputSchema::object(properties, vec!["url".to_string()]),
28            requires_approval: false,
29            ..Default::default()
30        }
31    }
32
33    /// Execute a web tool by name.
34    #[tracing::instrument(name = "tool.execute", skip(input, _context), fields(tool_name))]
35    pub async fn execute(
36        tool_use_id: &str,
37        tool_name: &str,
38        input: &Value,
39        _context: &ToolContext,
40    ) -> ToolResult {
41        let result = match tool_name {
42            "fetch_url" => Self::fetch_url(input).await,
43            _ => Err(anyhow::anyhow!("Unknown web tool: {}", tool_name)),
44        };
45        match result {
46            Ok(output) => ToolResult::success(tool_use_id.to_string(), output),
47            Err(e) => ToolResult::error(
48                tool_use_id.to_string(),
49                format!("Web operation failed: {}", e),
50            ),
51        }
52    }
53
54    async fn fetch_url(input: &Value) -> Result<String> {
55        #[derive(Deserialize)]
56        struct Input {
57            url: String,
58        }
59        let params: Input = serde_json::from_value(input.clone())?;
60        let client = Client::new();
61        let response = client.get(&params.url).send().await?;
62        let text = response.text().await?;
63        Ok(format!(
64            "URL: {}\nContent length: {} bytes\n\n{}",
65            params.url,
66            text.len(),
67            text
68        ))
69    }
70
71    /// Fetch URL content (helper for orchestrator integration)
72    pub async fn fetch_url_content(url: &str) -> Result<String> {
73        let client = Client::new();
74        let response = client.get(url).send().await?;
75        let text = response.text().await?;
76        Ok(format!(
77            "URL: {}\nContent length: {} bytes\n\n{}",
78            url,
79            text.len(),
80            text
81        ))
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    fn create_test_context() -> ToolContext {
90        ToolContext {
91            working_directory: ".".to_string(),
92            ..Default::default()
93        }
94    }
95
96    #[test]
97    fn test_get_tools() {
98        let tools = WebTool::get_tools();
99        assert_eq!(tools.len(), 1);
100        assert_eq!(tools[0].name, "fetch_url");
101    }
102
103    #[tokio::test]
104    async fn test_execute_unknown_tool() {
105        let context = create_test_context();
106        let input = json!({"url": "https://example.com"});
107        let result = WebTool::execute("1", "unknown_tool", &input, &context).await;
108        assert!(result.is_error);
109    }
110}