feat(tool): read_file tool by lines

This commit is contained in:
afjcjsbx 2026-03-24 22:54:13 +01:00
parent 13fbc786d6
commit 48a30a4804
3 changed files with 18 additions and 9 deletions

View file

@ -319,12 +319,12 @@ Text-oriented behavior, optimized for source files, markdown, logs, and configs.
Parameters: Parameters:
* `path` (required): File path * `path` (required): File path
* `start_line` (optional): Starting line number, 1-indexed and inclusive, default `0` * `start_line` (optional): Starting line number, 1-indexed and inclusive, default `1`
* `max_lines` (optional): Maximum number of lines to read, default = all remaining lines until EOF or byte budget * `max_lines` (optional): Maximum number of lines to read, default = all remaining lines until EOF or byte budget
Behavior notes: Behavior notes:
* Binary-looking files are rejected with guidance to use `read_file` * Binary-looking files are rejected with guidance to switch `read_file` to `mode = bytes`
* Extremely long single lines are truncated rather than skipped * Extremely long single lines are truncated rather than skipped
Use `mode = lines` when: Use `mode = lines` when:

View file

@ -572,7 +572,7 @@ func (t *ReadFileLinesTool) Execute(ctx context.Context, args map[string]any) *T
} }
sample = sample[:sampleN] sample = sample[:sampleN]
if isBinaryReadFileData(sample) { if isBinaryReadFileData(sample) {
return ErrorResult("file appears to be binary; use read_file for byte-based inspection") return ErrorResult("file appears to be binary; switch read_file mode to 'bytes' for byte-based inspection")
} }
reader := bufio.NewReaderSize(io.MultiReader(bytes.NewReader(sample), file), 32*1024) reader := bufio.NewReaderSize(io.MultiReader(bytes.NewReader(sample), file), 32*1024)

View file

@ -1135,11 +1135,11 @@ func TestReadFileLinesTool_BinaryFileRejected(t *testing.T) {
if !result.IsError { if !result.IsError {
t.Fatalf("expected binary file rejection in line mode, got: %s", result.ForLLM) t.Fatalf("expected binary file rejection in line mode, got: %s", result.ForLLM)
} }
if !strings.Contains(result.ForLLM, "file appears to be binary") { if !strings.Contains(result.ForLLM, "switch read_file mode to 'bytes'") {
t.Fatalf("expected binary file rejection message, got: %s", result.ForLLM) t.Fatalf("expected binary file rejection message, got: %s", result.ForLLM)
} }
if !strings.Contains(result.ForLLM, "use read_file") { if !strings.Contains(result.ForLLM, "mode to 'bytes'") {
t.Fatalf("expected suggestion to use read_file, got: %s", result.ForLLM) t.Fatalf("expected suggestion to switch read_file mode, got: %s", result.ForLLM)
} }
} }
@ -1190,7 +1190,10 @@ func TestReadFileLinesTool_NoTrailingNewline(t *testing.T) {
t.Fatalf("Execute() error = %s", result.ForLLM) t.Fatalf("Execute() error = %s", result.ForLLM)
} }
if !strings.Contains(result.ForLLM, "1|line 1\n2|line 2") { if !strings.Contains(result.ForLLM, "1|line 1\n2|line 2") {
t.Fatalf("expected final line without trailing newline to be preserved, got: %s", result.ForLLM) t.Fatalf(
"expected final line without trailing newline to be preserved, got: %s",
result.ForLLM,
)
} }
if !strings.Contains(result.ForLLM, "[END OF FILE - no further content.]") { if !strings.Contains(result.ForLLM, "[END OF FILE - no further content.]") {
t.Fatalf("expected EOF marker, got: %s", result.ForLLM) t.Fatalf("expected EOF marker, got: %s", result.ForLLM)
@ -1215,10 +1218,16 @@ func TestReadFileLinesTool_ExactByteBudgetBoundaryIncludesPrefix(t *testing.T) {
t.Fatalf("Execute() error = %s", result.ForLLM) t.Fatalf("Execute() error = %s", result.ForLLM)
} }
if !strings.Contains(result.ForLLM, "1|1234567\n") { if !strings.Contains(result.ForLLM, "1|1234567\n") {
t.Fatalf("expected first line to fit exactly in the byte budget with its prefix, got: %s", result.ForLLM) t.Fatalf(
"expected first line to fit exactly in the byte budget with its prefix, got: %s",
result.ForLLM,
)
} }
if strings.Contains(result.ForLLM, "2|") { if strings.Contains(result.ForLLM, "2|") {
t.Fatalf("expected second line to be excluded once the exact output byte budget was reached, got: %s", result.ForLLM) t.Fatalf(
"expected second line to be excluded once the exact output byte budget was reached, got: %s",
result.ForLLM,
)
} }
if !strings.Contains(result.ForLLM, "file_bytes: 8 | output_bytes: 10") { if !strings.Contains(result.ForLLM, "file_bytes: 8 | output_bytes: 10") {
t.Fatalf("expected separate file/output byte counters, got: %s", result.ForLLM) t.Fatalf("expected separate file/output byte counters, got: %s", result.ForLLM)