Skip to main content

llama_cpp_v3_agent_sdk/tools/
read_file.rs

1use crate::error::AgentError;
2use crate::tool::{Tool, ToolResult};
3
4/// Read file contents with line numbers.
5pub struct ReadFileTool;
6
7impl Tool for ReadFileTool {
8    fn name(&self) -> &str {
9        "read"
10    }
11
12    fn description(&self) -> &str {
13        "Read file contents with line numbers. Optionally specify a line range \
14         with 'start_line' and 'end_line' (1-indexed, inclusive)."
15    }
16
17    fn parameters_schema(&self) -> serde_json::Value {
18        serde_json::json!({
19            "type": "object",
20            "properties": {
21                "path": {
22                    "type": "string",
23                    "description": "Path to the file to read"
24                },
25                "start_line": {
26                    "type": "integer",
27                    "description": "First line to read (1-indexed, inclusive). Omit to read from the beginning."
28                },
29                "end_line": {
30                    "type": "integer",
31                    "description": "Last line to read (1-indexed, inclusive). Omit to read to the end."
32                }
33            },
34            "required": ["path"]
35        })
36    }
37
38    fn execute(&self, args: &serde_json::Value) -> Result<ToolResult, AgentError> {
39        let path = args["path"].as_str().ok_or_else(|| AgentError::Tool {
40            tool: "read".to_string(),
41            message: "Missing 'path' argument".to_string(),
42        })?;
43
44        let content = match std::fs::read_to_string(path) {
45            Ok(c) => c,
46            Err(e) => return Ok(ToolResult::err(format!("Failed to read '{}': {}", path, e))),
47        };
48
49        let lines: Vec<&str> = content.lines().collect();
50        let total_lines = lines.len();
51
52        let start = args["start_line"]
53            .as_u64()
54            .map(|n| n.max(1) as usize - 1)
55            .unwrap_or(0);
56        let end = args["end_line"]
57            .as_u64()
58            .map(|n| (n as usize).min(total_lines))
59            .unwrap_or(total_lines);
60
61        if start >= total_lines {
62            return Ok(ToolResult::err(format!(
63                "start_line {} is beyond file length ({} lines)",
64                start + 1,
65                total_lines
66            )));
67        }
68
69        let mut output = String::new();
70        output.push_str(&format!("{} ({} lines total)\n", path, total_lines));
71
72        for (i, line) in lines[start..end].iter().enumerate() {
73            let line_num = start + i + 1;
74            output.push_str(&format!("{:>4}| {}\n", line_num, line));
75        }
76
77        Ok(ToolResult::ok(output))
78    }
79
80    fn requires_permission(&self) -> bool {
81        false
82    }
83}