Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/core/prompts/tools/read-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ Description: Request to read the contents of ${isMultipleReadsEnabled ? "one or

${isMultipleReadsEnabled ? `**IMPORTANT: You can read a maximum of ${maxConcurrentReads} files in a single request.** If you need to read more files, use multiple sequential read_file requests.` : "**IMPORTANT: Multiple file reads are currently disabled. You can only read one file at a time.**"}

${args.partialReadsEnabled ? `By specifying line ranges, you can efficiently read specific portions of large files without loading the entire file into memory.` : ""}
${args.partialReadsEnabled ? `**Line ranges bypass the "Always read entire file" setting** - when you specify explicit line ranges, you can read any lines from any file regardless of the maxReadFileLine configuration. This is useful for targeted work based on tool outputs that reference specific line numbers (e.g., linters, search results, diffs).` : ""}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Docs alignment: New line‑range behavior is clear. While touching this doc, consider updating the earlier supported-formats sentence to reflect current code — it still says "PDF and DOCX" only, but the code also supports IPYNB. For consistency with implementation, mention all three, e.g. "PDF, DOCX, and IPYNB". See src/integrations/misc/extract-text.ts.

Parameters:
- args: Contains one or more file elements, where each file contains:
- path: (required) File path (relative to workspace directory ${args.cwd})
${args.partialReadsEnabled ? `- line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive)` : ""}
${args.partialReadsEnabled ? `- line_range: (optional) One or more line range elements in format "start-end" (1-based, inclusive). Line ranges always bypass maxReadFileLine restrictions.` : ""}

Usage:
<read_file>
Expand Down
2 changes: 1 addition & 1 deletion src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2434,7 +2434,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
enableMcpServerCreation,
language,
rooIgnoreInstructions,
maxReadFileLine !== -1,
true, // Always enable partial reads - line ranges bypass maxReadFileLine
{
maxConcurrentFileReads: maxConcurrentFileReads ?? 5,
todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
Expand Down
81 changes: 79 additions & 2 deletions src/core/tools/__tests__/readFileTool.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import { parseSourceCodeDefinitionsForFile } from "../../../services/tree-sitter
import { isBinaryFile } from "isbinaryfile"
import { ReadFileToolUse, ToolParamName, ToolResponse } from "../../../shared/tools"
import { readFileTool } from "../readFileTool"
import { formatResponse } from "../../prompts/responses"
import { DEFAULT_MAX_IMAGE_FILE_SIZE_MB, DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB } from "../helpers/imageHelpers"

vi.mock("path", async () => {
const originalPath = await vi.importActual("path")
Expand Down Expand Up @@ -475,6 +473,85 @@ describe("read_file tool with maxReadFileLine setting", () => {
expect(rangeResult).toContain(`<file><path>${testFilePath}</path>`)
expect(rangeResult).toContain(`<content lines="2-4">`)
})

it("should bypass maxReadFileLine when line ranges are specified", async () => {
// Setup - file has 1000 lines, maxReadFileLine is 100, but we request lines 500-600
mockedCountFileLines.mockResolvedValue(1000)
mockedReadLines.mockResolvedValue("Line 500\nLine 501\n...Line 600")
addLineNumbersMock.mockReturnValue("500 | Line 500\n501 | Line 501\n...600 | Line 600")

// Execute with maxReadFileLine=100 but requesting lines 500-600
const result = await executeReadFileTool(
{},
{
maxReadFileLine: 100,
totalLines: 1000,
start_line: "500",
end_line: "600",
},
)

// Verify that we got the requested range, not limited by maxReadFileLine
expect(result).toContain(`<file><path>${testFilePath}</path>`)
expect(result).toContain(`<content lines="500-600">`)
expect(result).not.toContain("Showing only 100 of 1000 total lines")

// Verify readLines was called with the correct range
expect(mockedReadLines).toHaveBeenCalledWith(absoluteFilePath, 599, 499) // end-1, start-1
})

it("should bypass maxReadFileLine=0 (definitions only) when line ranges are specified", async () => {
// Setup - maxReadFileLine is 0 (definitions only), but we request specific lines
mockedCountFileLines.mockResolvedValue(100)
mockedReadLines.mockResolvedValue("Line 10\nLine 11\nLine 12")
addLineNumbersMock.mockReturnValue("10 | Line 10\n11 | Line 11\n12 | Line 12")

// Execute with maxReadFileLine=0 but requesting lines 10-12
const result = await executeReadFileTool(
{},
{
maxReadFileLine: 0,
totalLines: 100,
start_line: "10",
end_line: "12",
skipAddLineNumbersCheck: true,
},
)

// Verify that we got the actual content, not just definitions
expect(result).toContain(`<file><path>${testFilePath}</path>`)
expect(result).toContain(`<content lines="10-12">`)
expect(result).not.toContain("<list_code_definition_names>")

// Verify readLines was called
expect(mockedReadLines).toHaveBeenCalledWith(absoluteFilePath, 11, 9) // end-1, start-1
})

it("should bypass maxReadFileLine=-1 (always read entire file) when line ranges are specified", async () => {
// Setup - maxReadFileLine is -1 (always read entire file), but we request specific lines
mockedCountFileLines.mockResolvedValue(1000)
mockedReadLines.mockResolvedValue("Line 250\nLine 251\nLine 252")
addLineNumbersMock.mockReturnValue("250 | Line 250\n251 | Line 251\n252 | Line 252")

// Execute with maxReadFileLine=-1 but requesting lines 250-252
const result = await executeReadFileTool(
{},
{
maxReadFileLine: -1,
totalLines: 1000,
start_line: "250",
end_line: "252",
},
)

// Verify that we got the requested range, not the entire file
expect(result).toContain(`<file><path>${testFilePath}</path>`)
expect(result).toContain(`<content lines="250-252">`)
expect(result).not.toContain("lines=\"1-1000\"") // Should not read entire file

// Verify readLines was called with the correct range
expect(mockedReadLines).toHaveBeenCalledWith(absoluteFilePath, 251, 249) // end-1, start-1
})
})
})

Expand Down
Loading