use crate::error::AgentError;
use crate::tool::{Tool, ToolResult};
pub struct ReadFileTool;
impl Tool for ReadFileTool {
fn name(&self) -> &str {
"read"
}
fn description(&self) -> &str {
"Read file contents with line numbers. Optionally specify a line range \
with 'start_line' and 'end_line' (1-indexed, inclusive)."
}
fn parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Path to the file to read"
},
"start_line": {
"type": "integer",
"description": "First line to read (1-indexed, inclusive). Omit to read from the beginning."
},
"end_line": {
"type": "integer",
"description": "Last line to read (1-indexed, inclusive). Omit to read to the end."
}
},
"required": ["path"]
})
}
fn execute(&self, args: &serde_json::Value) -> Result<ToolResult, AgentError> {
let path = args["path"].as_str().ok_or_else(|| AgentError::Tool {
tool: "read".to_string(),
message: "Missing 'path' argument".to_string(),
})?;
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(e) => return Ok(ToolResult::err(format!("Failed to read '{}': {}", path, e))),
};
let lines: Vec<&str> = content.lines().collect();
let total_lines = lines.len();
let start = args["start_line"]
.as_u64()
.map(|n| n.max(1) as usize - 1)
.unwrap_or(0);
let end = args["end_line"]
.as_u64()
.map(|n| (n as usize).min(total_lines))
.unwrap_or(total_lines);
if start >= total_lines {
return Ok(ToolResult::err(format!(
"start_line {} is beyond file length ({} lines)",
start + 1,
total_lines
)));
}
let mut output = String::new();
output.push_str(&format!("{} ({} lines total)\n", path, total_lines));
for (i, line) in lines[start..end].iter().enumerate() {
let line_num = start + i + 1;
output.push_str(&format!("{:>4}| {}\n", line_num, line));
}
Ok(ToolResult::ok(output))
}
fn requires_permission(&self) -> bool {
false
}
}