Skip to main content

a_agent/tools/
read.rs

1use std::path::Path;
2
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5
6use super::path::unrestricted_path;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ReadArgs {
10    pub path: String,
11    #[serde(default)]
12    pub offset: usize,
13    pub limit: Option<usize>,
14}
15
16pub async fn read_text_file(root: &Path, args: &ReadArgs, max_lines: usize) -> Result<String> {
17    read_text_file_bounded(root, args, max_lines, usize::MAX).await
18}
19
20pub async fn read_text_file_bounded(
21    root: &Path,
22    args: &ReadArgs,
23    max_lines: usize,
24    max_bytes: usize,
25) -> Result<String> {
26    let path = unrestricted_path(root, &args.path, true)?;
27    let bytes = tokio::fs::read(&path)
28        .await
29        .with_context(|| format!("read {}", path.display()))?;
30    if bytes.contains(&0) {
31        anyhow::bail!("binary file cannot be read as text: {}", args.path);
32    }
33    let source = String::from_utf8(bytes)
34        .with_context(|| format!("file is not valid UTF-8: {}", args.path))?;
35    let lines = source.lines().collect::<Vec<_>>();
36    let limit = args.limit.unwrap_or(max_lines).min(max_lines);
37    let selected = lines.iter().skip(args.offset).take(limit);
38    let mut output = selected
39        .enumerate()
40        .map(|(index, line)| format!("{}: {line}", args.offset + index + 1))
41        .collect::<Vec<_>>()
42        .join("\n");
43    let consumed = args.offset.saturating_add(limit).min(lines.len());
44    let remaining = lines.len().saturating_sub(consumed);
45    if remaining > 0 {
46        if !output.is_empty() {
47            output.push('\n');
48        }
49        output.push_str(&format!(
50            "[truncated; {remaining} more line{}]",
51            if remaining == 1 { "" } else { "s" }
52        ));
53    }
54    if output.len() > max_bytes {
55        let mut end = max_bytes.min(output.len());
56        while end > 0 && !output.is_char_boundary(end) {
57            end -= 1;
58        }
59        output.truncate(end);
60        output.push_str("\n[truncated by output byte limit]");
61    }
62    Ok(output)
63}