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        }
39    }
40
41    async fn execute(&self, params: Value) -> Result<String> {
42        let path = params["path"]
43            .as_str()
44            .ok_or_else(|| anyhow::anyhow!("missing 'path'"))?;
45
46        // Check file size first
47        let metadata = tokio::fs::metadata(path).await?;
48        let file_size = metadata.len();
49
50        let offset = params["offset"].as_u64().unwrap_or(0) as usize;
51        let limit = params["limit"].as_u64().map(|l| l as usize);
52
53        // Warn for large files
54        if file_size > MAX_FILE_SIZE && limit.is_none() {
55            return Ok(format!(
56                "⚠️ File is large ({:.1}MB). Use offset/limit parameters to read specific sections.\n\
57                 Example: read(path=\"{}\", offset=0, limit=100) to read first 100 lines.",
58                file_size as f64 / 1_000_000.0,
59                path
60            ));
61        }
62
63        let content = tokio::fs::read_to_string(path).await?;
64
65        let lines: Vec<&str> = content.lines().collect();
66        let total_lines = lines.len();
67
68        // Apply default limit if not specified
69        let effective_limit = limit.unwrap_or(DEFAULT_MAX_LINES);
70        let end = (offset + effective_limit).min(total_lines);
71        let selected = &lines[offset.min(total_lines)..end.min(total_lines)];
72
73        let mut result = String::new();
74
75        // Add header for truncated reads
76        if offset > 0 || end < total_lines {
77            result.push_str(&format!(
78                "📄 {} (lines {}-{} of {})\n\n",
79                path,
80                offset + 1,
81                end,
82                total_lines
83            ));
84        }
85
86        for (i, line) in selected.iter().enumerate() {
87            result.push_str(&format!("{:4} | {}\n", offset + i + 1, line));
88        }
89
90        // Add truncation notice
91        if end < total_lines {
92            result.push_str(&format!(
93                "\n... ({} more lines, use offset={} limit=N to continue)",
94                total_lines - end,
95                end
96            ));
97        }
98
99        Ok(result)
100    }
101}