diff --git a/src/core/prompts/tools/simple-read-file.ts b/src/core/prompts/tools/simple-read-file.ts index 28f4f1129e..b6c7c896ad 100644 --- a/src/core/prompts/tools/simple-read-file.ts +++ b/src/core/prompts/tools/simple-read-file.ts @@ -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: file/path.ext + * Supports optional line_range for reading specific portions of a file + * Uses the simpler format: file/path.extstart-end */ 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: path/to/file +start-end Examples: -1. Reading a TypeScript file: +1. Reading a TypeScript file (full file): src/app.ts -2. Reading a configuration file: +2. Reading specific lines from a file: + +src/app.ts +1-100 + + +3. Continuing to read after truncation (e.g., after reading lines 1-500): + +src/app.ts +501-1000 + + +4. Reading a configuration file: config.json -3. Reading a markdown file: - -README.md -` +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.` } diff --git a/src/core/tools/ReadFileTool.ts b/src/core/tools/ReadFileTool.ts index 58f3cc495e..d15a3ba76a 100644 --- a/src/core/tools/ReadFileTool.ts +++ b/src/core/tools/ReadFileTool.ts @@ -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: `${relPath}\n${defResult}\n${notice}\n`, 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}\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 diff --git a/src/core/tools/simpleReadFileTool.ts b/src/core/tools/simpleReadFileTool.ts index 1b41e9e9d6..48d81ecc60 100644 --- a/src/core/tools/simpleReadFileTool.ts +++ b/src/core/tools/simpleReadFileTool.ts @@ -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: file/path.ext + * Uses the format: file/path.extstart-end * * 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(`${relPath}${errorMsg}`) + 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 = `\n${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 += `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., ${nextStart}-${suggestedEnd})\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${relPath}\n${xmlInfo}`) + } else { + pushToolResult(`${relPath}\n${xmlInfo}`) + } + return + } + // Handle definitions-only mode if (maxReadFileLine === 0) { try { @@ -234,7 +291,9 @@ export async function simpleReadFileTool( if (defResult) { xmlInfo += `${defResult}\n` } - xmlInfo += `Showing only ${maxReadFileLine} of ${totalLines} total lines. File is too large for complete display\n` + const nextStart = maxReadFileLine + 1 + const suggestedEnd = Math.min(maxReadFileLine * 2, totalLines) + xmlInfo += `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., ${nextStart}-${suggestedEnd})\n` pushToolResult(`${relPath}\n${xmlInfo}`) } 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]` } diff --git a/src/shared/tools.ts b/src/shared/tools.ts index f2b4ec3544..24ccc285c8 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -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