matrixcode_core/tools/
webfetch.rs1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4use std::time::Duration;
5
6use super::{Tool, ToolDefinition};
7
8const DEFAULT_TIMEOUT_SECS: u64 = 30;
9const MAX_TIMEOUT_SECS: u64 = 120;
10
11pub struct WebFetchTool;
12
13#[async_trait]
14impl Tool for WebFetchTool {
15 fn definition(&self) -> ToolDefinition {
16 ToolDefinition {
17 name: "webfetch".to_string(),
18 description: "从 URL 获取内容并返回为文本。支持自定义超时时间。".to_string(),
19 parameters: json!({
20 "type": "object",
21 "properties": {
22 "url": {
23 "type": "string",
24 "description": "要获取的 URL"
25 },
26 "max_length": {
27 "type": "integer",
28 "description": "最大响应长度(字符数,默认 10000)"
29 },
30 "timeout_secs": {
31 "type": "integer",
32 "description": format!("超时时间(秒,默认 {},最大 {})", DEFAULT_TIMEOUT_SECS, MAX_TIMEOUT_SECS)
33 }
34 },
35 "required": ["url"]
36 }),
37 ..Default::default()
38 }
39 }
40
41 async fn execute(&self, params: Value) -> Result<String> {
42 let url = params["url"]
43 .as_str()
44 .ok_or_else(|| anyhow::anyhow!("missing 'url'"))?;
45 let max_length = params["max_length"].as_u64().unwrap_or(10000) as usize;
46 let timeout_secs = params["timeout_secs"]
47 .as_u64()
48 .unwrap_or(DEFAULT_TIMEOUT_SECS)
49 .min(MAX_TIMEOUT_SECS);
50
51 let client = reqwest::Client::builder()
53 .timeout(Duration::from_secs(timeout_secs))
54 .connect_timeout(Duration::from_secs(10))
55 .build()?;
56
57 let response = tokio::time::timeout(
58 Duration::from_secs(timeout_secs + 5), client.get(url).send()
60 ).await
61 .map_err(|_| anyhow::anyhow!("request timed out after {}s", timeout_secs))?
62 .map_err(|e| anyhow::anyhow!("request failed: {}", e))?;
63
64 let status = response.status();
65
66 if !status.is_success() {
67 anyhow::bail!("HTTP {} for {}", status, url);
68 }
69
70 let body = response.text().await?;
71
72 let truncated = if body.len() > max_length {
73 let end = body.floor_char_boundary(max_length);
74 format!(
75 "{}...\n\n(truncated, {} total bytes)",
76 &body[..end],
77 body.len()
78 )
79 } else {
80 body
81 };
82
83 Ok(truncated)
84 }
85}