From 5d3a583915bf0b6950232b628e3343d9423e525e Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Fri, 29 May 2026 09:47:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20read=E5=B7=A5=E5=85=B7=E5=8D=95=E8=A1=8C?= =?UTF-8?q?=E8=B6=85=E9=95=BF=E6=97=B6=E5=86=85=E5=AE=B9=E8=A2=AB=E5=AE=8C?= =?UTF-8?q?=E5=85=A8=E4=B8=A2=E5=BC=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 单行超过 MAX_CHARS(128K) 时,截断循环 end_idx=0 导致 lines[..0] 为空, 整行内容丢失。改为当首行就超长时使用 take_prefix_chars 字符级截断。 Co-Authored-By: Claude Opus 4.7 --- src/tools/file_read.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/tools/file_read.rs b/src/tools/file_read.rs index 4d6463a..f170d8e 100644 --- a/src/tools/file_read.rs +++ b/src/tools/file_read.rs @@ -3,6 +3,7 @@ use std::path::Path; use async_trait::async_trait; use serde_json::json; +use crate::text::take_prefix_chars; use crate::tools::traits::{Tool, ToolResult}; use crate::tools::extract_u64; @@ -187,8 +188,13 @@ impl Tool for FileReadTool { } end_idx = i + 1; } - result = lines[..end_idx].join("\n"); - let truncated_amount = original_len - result.len(); + if end_idx == 0 && !lines.is_empty() { + // First line alone exceeds MAX_CHARS — take its prefix + result = take_prefix_chars(&lines[0], MAX_CHARS.saturating_sub(100)); + } else { + result = lines[..end_idx].join("\n"); + } + let truncated_amount = original_len.saturating_sub(result.len()); result.push_str(&format!( "\n\n... ({} chars truncated) ...", truncated_amount @@ -312,4 +318,28 @@ mod tests { assert!(!result.success); assert!(result.error.unwrap().contains("Not a file")); } + + #[tokio::test] + async fn test_read_single_long_line() { + let mut file = NamedTempFile::new().unwrap(); + // Write a single line longer than MAX_CHARS + let long_line = "A".repeat(150_000); + file.write_all(long_line.as_bytes()).unwrap(); + + let tool = FileReadTool::new(); + let result = tool + .execute(json!({ "path": file.path().to_str().unwrap() })) + .await + .unwrap(); + + assert!(result.success); + // Should contain the line number prefix and the beginning of the content + assert!(result.output.starts_with("1| AAAA")); + // Should contain truncation notice since content exceeds MAX_CHARS + assert!(result.output.contains("chars truncated")); + // Should contain end-of-file notice (1 line total) + assert!(result.output.contains("End of file — 1 lines total")); + // Should NOT be empty content — the fix ensures the prefix is preserved + assert!(result.output.len() > 100); + } }