use super::super::read_byte_range;
use super::FileOpsTool;
use crate::config::constants::tools;
use crate::tools::builder::ToolResponseBuilder;
use crate::tools::types::Input;
use crate::utils::file_utils::read_file_with_context;
use anyhow::{Context, Result, anyhow};
use serde_json::{Value, json};
use std::path::Path;
impl FileOpsTool {
pub(super) async fn read_file_paged(&self, file_path: &Path, input: &Input) -> Result<(String, Value, bool)> {
let file_metadata = tokio::fs::metadata(file_path)
.await
.with_context(|| format!("Failed to read metadata for file: {}", file_path.display()))?;
if !file_metadata.is_file() {
return Err(anyhow!("Path is not a file: {}", file_path.display()));
}
let file_size = file_metadata.len();
let (final_content, is_truncated) = if input.offset_lines.is_some() || input.page_size_lines.is_some() {
self.read_file_by_lines(file_path, input, file_size as usize).await?
} else {
self.read_file_by_bytes(file_path, input, file_size).await?
};
let mut builder = ToolResponseBuilder::new(tools::READ_FILE)
.success()
.message(format!("Successfully read file {} (paged)", self.workspace_relative_display(file_path)))
.content(final_content.clone())
.data("size_bytes", json!(file_size))
.data("size_lines", json!(final_content.lines().count()))
.data("is_truncated", json!(is_truncated))
.data("content_kind", json!("text"))
.data("encoding", json!("utf8"));
if let Some(offset_bytes) = input.offset_bytes {
builder = builder.data("offset_bytes", json!(offset_bytes));
}
if let Some(page_size_bytes) = input.page_size_bytes {
builder = builder.data("page_size_bytes", json!(page_size_bytes));
}
if let Some(offset_lines) = input.offset_lines {
builder = builder.data("offset_lines", json!(offset_lines));
}
if let Some(page_size_lines) = input.page_size_lines {
builder = builder.data("page_size_lines", json!(page_size_lines));
}
Ok((final_content, builder.build_json()["metadata"].clone(), is_truncated))
}
async fn read_file_by_lines(&self, file_path: &Path, input: &Input, _file_size: usize) -> Result<(String, bool)> {
let offset_lines = input.offset_lines.unwrap_or(0);
let page_size_lines = input.page_size_lines.unwrap_or(1000);
if offset_lines > usize::MAX / 2 {
return Err(anyhow!("Offset too large: {offset_lines}"));
}
if page_size_lines == 0 {
return Err(anyhow!("Page size must be greater than 0"));
}
if offset_lines > usize::MAX - page_size_lines {
return Err(anyhow!("Offset_lines + page_size_lines would overflow: {offset_lines} + {page_size_lines}"));
}
let content = read_file_with_context(file_path, "file content")
.await
.with_context(|| format!("Failed to read file content: {}", file_path.display()))?;
let all_lines: Vec<&str> = content.lines().collect();
let total_lines = all_lines.len();
if total_lines == 0 || offset_lines >= total_lines {
return Ok((String::new(), false));
}
let end_pos = std::cmp::min(offset_lines + page_size_lines, total_lines);
let selected_lines = &all_lines[offset_lines..end_pos];
let final_content = selected_lines.join("\n");
let is_truncated = end_pos < total_lines;
Ok((final_content, is_truncated))
}
async fn read_file_by_bytes(&self, file_path: &Path, input: &Input, _file_size: u64) -> Result<(String, bool)> {
let offset_bytes = input.offset_bytes.unwrap_or(0);
let page_size_bytes = input.page_size_bytes.unwrap_or(8192);
if page_size_bytes == 0 {
return Err(anyhow!("Page size must be greater than 0"));
}
let result = read_byte_range(file_path, offset_bytes, page_size_bytes, false).await?;
Ok((result.content, result.has_more))
}
}