mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
feat: add line_range support to simpleReadFileTool and improve truncation notices
This PR addresses Issue #10239 by: 1. Adding line_range parameter support to simpleReadFileTool - Models using the simple read_file tool can now use <line_range>start-end</line_range> - Enables incremental file reading for models that previously could not continue reading truncated files 2. Improving truncation notices across both tools to include: - The exact next line number to continue from - A concrete example of the syntax to use (e.g., <line_range>501-1000</line_range>) 3. Updated tool description in simple-read-file.ts to document the new parameter Files modified: - src/core/prompts/tools/simple-read-file.ts - src/core/tools/simpleReadFileTool.ts - src/core/tools/ReadFileTool.ts - src/shared/tools.ts (added line_range to toolParamNames)
This commit is contained in:
parent
78dc34498b
commit
13ab4dd271
4 changed files with 93 additions and 16 deletions
|
|
@ -2,34 +2,46 @@ import { ToolArgs } from "./types"
|
|||
|
||||
/**
|
||||
* Generate a simplified read_file tool description for models that only support single file reads
|
||||
* Uses the simpler format: <read_file><path>file/path.ext</path></read_file>
|
||||
* Supports optional line_range for reading specific portions of a file
|
||||
* Uses the simpler format: <read_file><path>file/path.ext</path><line_range>start-end</line_range></read_file>
|
||||
*/
|
||||
export function getSimpleReadFileDescription(args: ToolArgs): string {
|
||||
return `## read_file
|
||||
Description: Request to read the contents of a file. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when discussing code.
|
||||
Description: Request to read the contents of a file. The tool outputs line-numbered content (e.g. "1 | const x = 1") for easy reference when discussing code. Use line_range to efficiently read specific portions of large files.
|
||||
|
||||
Parameters:
|
||||
- path: (required) File path (relative to workspace directory ${args.cwd})
|
||||
- line_range: (optional) Line range in format "start-end" (1-based, inclusive). Use this to read specific sections of a file, especially useful for continuing to read a large file that was truncated.
|
||||
|
||||
Usage:
|
||||
<read_file>
|
||||
<path>path/to/file</path>
|
||||
<line_range>start-end</line_range>
|
||||
</read_file>
|
||||
|
||||
Examples:
|
||||
|
||||
1. Reading a TypeScript file:
|
||||
1. Reading a TypeScript file (full file):
|
||||
<read_file>
|
||||
<path>src/app.ts</path>
|
||||
</read_file>
|
||||
|
||||
2. Reading a configuration file:
|
||||
2. Reading specific lines from a file:
|
||||
<read_file>
|
||||
<path>src/app.ts</path>
|
||||
<line_range>1-100</line_range>
|
||||
</read_file>
|
||||
|
||||
3. Continuing to read after truncation (e.g., after reading lines 1-500):
|
||||
<read_file>
|
||||
<path>src/app.ts</path>
|
||||
<line_range>501-1000</line_range>
|
||||
</read_file>
|
||||
|
||||
4. Reading a configuration file:
|
||||
<read_file>
|
||||
<path>config.json</path>
|
||||
</read_file>
|
||||
|
||||
3. Reading a markdown file:
|
||||
<read_file>
|
||||
<path>README.md</path>
|
||||
</read_file>`
|
||||
IMPORTANT: When a file is too large to display completely, the tool will show a truncation notice with the exact line number to continue from. Use the line_range parameter to read the next section of the file.`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -458,7 +458,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
|||
task.rooIgnoreController,
|
||||
)
|
||||
if (defResult) {
|
||||
const notice = `Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines`
|
||||
const notice = `Showing only definitions of ${totalLines} total lines. To read actual content, use the read_file tool again with the line_range parameter (e.g., line_ranges: [[1, 500]])`
|
||||
updateFileResult(relPath, {
|
||||
xmlContent: `<file><path>${relPath}</path>\n<list_code_definition_names>${defResult}</list_code_definition_names>\n<notice>${notice}</notice>\n</file>`,
|
||||
nativeContent: `File: ${relPath}\nCode Definitions:\n${defResult}\n\nNote: ${notice}`,
|
||||
|
|
@ -493,7 +493,9 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
|||
nativeInfo += `\nCode Definitions:\n${truncatedDefs}\n`
|
||||
}
|
||||
|
||||
const notice = `Showing only ${maxReadFileLine} of ${totalLines} total lines. Use line_range if you need to read more lines`
|
||||
const nextStart = maxReadFileLine + 1
|
||||
const suggestedEnd = Math.min(maxReadFileLine * 2, totalLines)
|
||||
const notice = `Showing lines 1-${maxReadFileLine} of ${totalLines} total lines. To continue reading, use the read_file tool again with the line_range parameter starting at line ${nextStart} (e.g., line_ranges: [[${nextStart}, ${suggestedEnd}]])`
|
||||
xmlInfo += `<notice>${notice}</notice>\n`
|
||||
nativeInfo += `\nNote: ${notice}`
|
||||
|
||||
|
|
@ -548,7 +550,9 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
|||
|
||||
if (!result.complete) {
|
||||
// File was truncated
|
||||
const notice = `File truncated: showing ${result.lineCount} lines (${result.tokenCount} tokens) due to context budget. Use line_range to read specific sections.`
|
||||
const nextStart = result.lineCount + 1
|
||||
const suggestedEnd = Math.min(result.lineCount * 2, totalLines)
|
||||
const notice = `File truncated: showing lines 1-${result.lineCount} of ${totalLines} total (${result.tokenCount} tokens) due to context budget. To continue reading, use the read_file tool again with the line_range parameter starting at line ${nextStart} (e.g., line_ranges: [[${nextStart}, ${suggestedEnd}]])`
|
||||
const lineRangeAttr = result.lineCount > 0 ? ` lines="1-${result.lineCount}"` : ""
|
||||
xmlInfo =
|
||||
result.lineCount > 0
|
||||
|
|
|
|||
|
|
@ -22,14 +22,27 @@ import {
|
|||
processImageFile,
|
||||
} from "./helpers/imageHelpers"
|
||||
|
||||
/**
|
||||
* Helper function to parse line range string (e.g., "1-100" -> {start: 1, end: 100})
|
||||
*/
|
||||
function parseLineRange(lineRangeStr: string | undefined): { start: number; end: number } | null {
|
||||
if (!lineRangeStr) return null
|
||||
const match = lineRangeStr.match(/^(\d+)-(\d+)$/)
|
||||
if (!match) return null
|
||||
const start = parseInt(match[1], 10)
|
||||
const end = parseInt(match[2], 10)
|
||||
if (isNaN(start) || isNaN(end) || start < 1 || end < start) return null
|
||||
return { start, end }
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified read file tool for models that only support single file reads
|
||||
* Uses the format: <read_file><path>file/path.ext</path></read_file>
|
||||
* Uses the format: <read_file><path>file/path.ext</path><line_range>start-end</line_range></read_file>
|
||||
*
|
||||
* This is a streamlined version of readFileTool that:
|
||||
* - Only accepts a single path parameter
|
||||
* - Does not support multiple files
|
||||
* - Does not support line ranges
|
||||
* - Supports a single optional line_range parameter for reading specific portions
|
||||
* - Has simpler XML parsing
|
||||
*/
|
||||
export async function simpleReadFileTool(
|
||||
|
|
@ -42,6 +55,10 @@ export async function simpleReadFileTool(
|
|||
toolProtocol?: ToolProtocol,
|
||||
) {
|
||||
const filePath: string | undefined = block.params.path
|
||||
const lineRangeStr: string | undefined = block.params.line_range
|
||||
|
||||
// Parse line range if provided
|
||||
const lineRange = parseLineRange(lineRangeStr)
|
||||
|
||||
// Check if the current model supports images
|
||||
const modelInfo = cline.api.getModel().info
|
||||
|
|
@ -91,7 +108,9 @@ export async function simpleReadFileTool(
|
|||
// Create approval message
|
||||
const isOutsideWorkspace = isPathOutsideWorkspace(fullPath)
|
||||
let lineSnippet = ""
|
||||
if (maxReadFileLine === 0) {
|
||||
if (lineRange) {
|
||||
lineSnippet = t("tools:readFile.linesRange", { start: lineRange.start, end: lineRange.end })
|
||||
} else if (maxReadFileLine === 0) {
|
||||
lineSnippet = t("tools:readFile.definitionsOnly")
|
||||
} else if (maxReadFileLine > 0) {
|
||||
lineSnippet = t("tools:readFile.maxLines", { max: maxReadFileLine })
|
||||
|
|
@ -201,6 +220,44 @@ export async function simpleReadFileTool(
|
|||
}
|
||||
}
|
||||
|
||||
// Handle specific line range reading (when line_range parameter is provided)
|
||||
if (lineRange) {
|
||||
// Validate line range against total lines
|
||||
if (lineRange.start > totalLines) {
|
||||
const errorMsg = `Invalid line range: start line ${lineRange.start} exceeds total lines ${totalLines}`
|
||||
pushToolResult(`<file><path>${relPath}</path><error>${errorMsg}</error></file>`)
|
||||
return
|
||||
}
|
||||
|
||||
// Clamp end line to total lines
|
||||
const effectiveEnd = Math.min(lineRange.end, totalLines)
|
||||
const content = addLineNumbers(
|
||||
await readLines(fullPath, effectiveEnd - 1, lineRange.start - 1),
|
||||
lineRange.start,
|
||||
)
|
||||
const lineRangeAttr = ` lines="${lineRange.start}-${effectiveEnd}"`
|
||||
let xmlInfo = `<content${lineRangeAttr}>\n${content}</content>\n`
|
||||
|
||||
// Add notice if there are more lines after this range
|
||||
if (effectiveEnd < totalLines) {
|
||||
const nextStart = effectiveEnd + 1
|
||||
const suggestedEnd = Math.min(effectiveEnd + (effectiveEnd - lineRange.start + 1), totalLines)
|
||||
xmlInfo += `<notice>Showing lines ${lineRange.start}-${effectiveEnd} of ${totalLines} total lines. To continue reading, use the read_file tool again with the line_range parameter starting at line ${nextStart} (e.g., <line_range>${nextStart}-${suggestedEnd}</line_range>)</notice>\n`
|
||||
}
|
||||
|
||||
// Track file read
|
||||
await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
|
||||
|
||||
// Return the result
|
||||
if (text) {
|
||||
const statusMessage = formatResponse.toolApprovedWithFeedback(text)
|
||||
pushToolResult(`${statusMessage}\n<file><path>${relPath}</path>\n${xmlInfo}</file>`)
|
||||
} else {
|
||||
pushToolResult(`<file><path>${relPath}</path>\n${xmlInfo}</file>`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle definitions-only mode
|
||||
if (maxReadFileLine === 0) {
|
||||
try {
|
||||
|
|
@ -234,7 +291,9 @@ export async function simpleReadFileTool(
|
|||
if (defResult) {
|
||||
xmlInfo += `<list_code_definition_names>${defResult}</list_code_definition_names>\n`
|
||||
}
|
||||
xmlInfo += `<notice>Showing only ${maxReadFileLine} of ${totalLines} total lines. File is too large for complete display</notice>\n`
|
||||
const nextStart = maxReadFileLine + 1
|
||||
const suggestedEnd = Math.min(maxReadFileLine * 2, totalLines)
|
||||
xmlInfo += `<notice>Showing lines 1-${maxReadFileLine} of ${totalLines} total lines. To continue reading, use the read_file tool again with the line_range parameter starting at line ${nextStart} (e.g., <line_range>${nextStart}-${suggestedEnd}</line_range>)</notice>\n`
|
||||
pushToolResult(`<file><path>${relPath}</path>\n${xmlInfo}</file>`)
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith("Unsupported language:")) {
|
||||
|
|
@ -282,7 +341,8 @@ export async function simpleReadFileTool(
|
|||
*/
|
||||
export function getSimpleReadFileToolDescription(blockName: string, blockParams: any): string {
|
||||
if (blockParams.path) {
|
||||
return `[${blockName} for '${blockParams.path}']`
|
||||
const lineRangeInfo = blockParams.line_range ? ` (lines ${blockParams.line_range})` : ""
|
||||
return `[${blockName} for '${blockParams.path}'${lineRangeInfo}]`
|
||||
} else {
|
||||
return `[${blockName} with missing path]`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ export const toolParamNames = [
|
|||
"prompt",
|
||||
"image",
|
||||
"files", // Native protocol parameter for read_file
|
||||
"line_range", // Simple read_file parameter for single line range
|
||||
"operations", // search_and_replace parameter for multiple operations
|
||||
"patch", // apply_patch parameter
|
||||
"file_path", // search_replace and edit_file parameter
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue