Skip to main content

matrixcode_core/tools/
webfetch.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4
5use super::{Tool, ToolDefinition};
6
7pub struct WebFetchTool;
8
9#[async_trait]
10impl Tool for WebFetchTool {
11    fn definition(&self) -> ToolDefinition {
12        ToolDefinition {
13            name: "webfetch".to_string(),
14            description: "从 URL 获取内容并返回为文本".to_string(),
15            parameters: json!({
16                "type": "object",
17                "properties": {
18                    "url": {
19                        "type": "string",
20                        "description": "要获取的 URL"
21                    },
22                    "max_length": {
23                        "type": "integer",
24                        "description": "最大响应长度(字符数,默认 10000)"
25                    }
26                },
27                "required": ["url"]
28            }),
29            ..Default::default()
30        }
31    }
32
33    async fn execute(&self, params: Value) -> Result<String> {
34        let url = params["url"]
35            .as_str()
36            .ok_or_else(|| anyhow::anyhow!("missing 'url'"))?;
37        let max_length = params["max_length"].as_u64().unwrap_or(10000) as usize;
38
39        // Show spinner while fetching - RAII guard ensures cleanup on error
40        // let mut spinner = ToolSpinner::new(&format!("fetching {}", url));
41
42        let response = reqwest::get(url).await?;
43        let status = response.status();
44
45        if !status.is_success() {
46            // spinner.finish_error(&format!("HTTP {}", status));
47            anyhow::bail!("HTTP {} for {}", status, url);
48        }
49
50        let body = response.text().await?;
51
52        let truncated = if body.len() > max_length {
53            // 找到不超过 max_length 的最后一个有效字符边界
54            let end = body.floor_char_boundary(max_length);
55            format!(
56                "{}...\n\n(truncated, {} total bytes)",
57                &body[..end],
58                body.len()
59            )
60        } else {
61            body
62        };
63
64        // spinner.finish_success(&format!("{} bytes", bytes));
65        Ok(truncated)
66    }
67}