Skip to main content

cersei_tools/
web_fetch.rs

1//! WebFetch tool: fetch and parse web page content.
2
3use super::*;
4use crate::tool_primitives::http as phttp;
5use serde::Deserialize;
6
7pub struct WebFetchTool;
8
9#[async_trait]
10impl Tool for WebFetchTool {
11    fn name(&self) -> &str {
12        "WebFetch"
13    }
14    fn description(&self) -> &str {
15        "Fetch a URL and return its content as readable text. HTML is converted to markdown."
16    }
17    fn permission_level(&self) -> PermissionLevel {
18        PermissionLevel::ReadOnly
19    }
20    fn category(&self) -> ToolCategory {
21        ToolCategory::Web
22    }
23
24    fn input_schema(&self) -> Value {
25        serde_json::json!({
26            "type": "object",
27            "properties": {
28                "url": { "type": "string", "description": "The URL to fetch" },
29                "max_chars": { "type": "integer", "description": "Max characters to return (default 50000)" }
30            },
31            "required": ["url"]
32        })
33    }
34
35    async fn execute(&self, input: Value, _ctx: &ToolContext) -> ToolResult {
36        #[derive(Deserialize)]
37        struct Input {
38            url: String,
39            max_chars: Option<usize>,
40        }
41
42        let input: Input = match serde_json::from_value(input) {
43            Ok(i) => i,
44            Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
45        };
46
47        let max_chars = input.max_chars.unwrap_or(50_000);
48
49        match phttp::fetch_html(&input.url, max_chars, phttp::HttpOptions::default()).await {
50            Ok(text) => {
51                if text.len() >= max_chars {
52                    ToolResult::success(format!(
53                        "{}\n\n[Truncated: showing first {} chars]",
54                        text, max_chars
55                    ))
56                } else {
57                    ToolResult::success(text)
58                }
59            }
60            Err(e) => ToolResult::error(format!("Fetch failed: {}", e)),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_schema() {
71        let tool = WebFetchTool;
72        let schema = tool.input_schema();
73        assert!(schema["properties"]["url"].is_object());
74        assert_eq!(tool.permission_level(), PermissionLevel::ReadOnly);
75        assert_eq!(tool.category(), ToolCategory::Web);
76    }
77}