Skip to main content

matrixcode_core/tools/
read.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::{Value, json};
4
5use super::{Tool, ToolDefinition};
6
7/// Maximum file size to read without warning (5MB)
8const MAX_FILE_SIZE: u64 = 5_000_000;
9/// Maximum lines to return if no limit specified
10const DEFAULT_MAX_LINES: usize = 500;
11
12pub struct ReadTool;
13
14#[async_trait]
15impl Tool for ReadTool {
16    fn definition(&self) -> ToolDefinition {
17        ToolDefinition {
18            name: "read".to_string(),
19            description: "读取指定路径的文件内容".to_string(),
20            parameters: json!({
21                "type": "object",
22                "properties": {
23                    "path": {
24                        "type": "string",
25                        "description": "要读取的文件路径"
26                    },
27                    "offset": {
28                        "type": "integer",
29                        "description": "起始行号(从 0 开始)"
30                    },
31                    "limit": {
32                        "type": "integer",
33                        "description": "最大读取行数"
34                    }
35                },
36                "required": ["path"]
37            }),
38            ..Default::default()
39        }
40    }
41
42    async fn execute(&self, params: Value) -> Result<String> {
43        let path = params["path"]
44            .as_str()
45            .ok_or_else(|| anyhow::anyhow!("missing 'path'"))?;
46
47        // Check file size first
48        let metadata = tokio::fs::metadata(path).await?;
49        let file_size = metadata.len();
50
51        let offset = params["offset"].as_u64().unwrap_or(0) as usize;
52        let limit = params["limit"].as_u64().map(|l| l as usize);
53
54        // Warn for large files
55        if file_size > MAX_FILE_SIZE && limit.is_none() {
56            return Ok(format!(
57                "⚠️ File is large ({:.1}MB). Use offset/limit parameters to read specific sections.\n\
58                 Example: read(path=\"{}\", offset=0, limit=100) to read first 100 lines.",
59                file_size as f64 / 1_000_000.0,
60                path
61            ));
62        }
63
64        let content = tokio::fs::read_to_string(path).await?;
65
66        let lines: Vec<&str> = content.lines().collect();
67        let total_lines = lines.len();
68
69        // Apply default limit if not specified
70        let effective_limit = limit.unwrap_or(DEFAULT_MAX_LINES);
71        let end = (offset + effective_limit).min(total_lines);
72        let selected = &lines[offset.min(total_lines)..end.min(total_lines)];
73
74        let mut result = String::new();
75
76        // Add header for truncated reads
77        if offset > 0 || end < total_lines {
78            result.push_str(&format!(
79                "📄 {} (lines {}-{} of {})\n\n",
80                path,
81                offset + 1,
82                end,
83                total_lines
84            ));
85        }
86
87        for (i, line) in selected.iter().enumerate() {
88            result.push_str(&format!("{:4} | {}\n", offset + i + 1, line));
89        }
90
91        // Add truncation notice
92        if end < total_lines {
93            result.push_str(&format!(
94                "\n... ({} more lines, use offset={} limit=N to continue)",
95                total_lines - end,
96                end
97            ));
98        }
99
100        Ok(result)
101    }
102}