-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat: provider-specific file reading limits (maxReadFileLine) #11408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -216,7 +216,8 @@ export class ReadFileTool extends BaseTool<"read_file"> { | |
| // (they become U+FFFD replacement characters instead of throwing) | ||
| const buffer = await fs.readFile(fullPath) | ||
| const fileContent = buffer.toString("utf-8") | ||
| const result = this.processTextFile(fileContent, entry) | ||
| const providerMaxReadFileLine = task.apiConfiguration?.maxReadFileLine | ||
| const result = this.processTextFile(fileContent, entry, providerMaxReadFileLine) | ||
|
|
||
| await task.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource) | ||
|
|
||
|
|
@@ -265,20 +266,30 @@ export class ReadFileTool extends BaseTool<"read_file"> { | |
|
|
||
| /** | ||
| * Process a text file according to the requested mode. | ||
| * | ||
| * @param content - The raw file content | ||
| * @param entry - The parsed file entry parameters | ||
| * @param providerMaxReadFileLine - Optional provider-level cap on returned lines | ||
| */ | ||
| private processTextFile(content: string, entry: InternalFileEntry): string { | ||
| private processTextFile(content: string, entry: InternalFileEntry, providerMaxReadFileLine?: number): string { | ||
| const mode = entry.mode || "slice" | ||
| const defaultLimit = providerMaxReadFileLine ?? DEFAULT_LINE_LIMIT | ||
|
|
||
| if (mode === "indentation") { | ||
| // Indentation mode: semantic block extraction | ||
| // When anchor_line is not provided, default to offset (which defaults to 1) | ||
| const anchorLine = entry.anchor_line ?? entry.offset ?? 1 | ||
| // Clamp the limit: if the provider has a max, enforce it even when the model requests more | ||
| const requestedLimit = entry.limit ?? defaultLimit | ||
| const effectiveLimit = providerMaxReadFileLine | ||
| ? Math.min(requestedLimit, providerMaxReadFileLine) | ||
| : requestedLimit | ||
|
Comment on lines
+282
to
+286
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The clamping logic here ( Fix it with Roo Code or mention @roomote and request a fix. |
||
| const result = readWithIndentation(content, { | ||
| anchorLine, | ||
| maxLevels: entry.max_levels, | ||
| includeSiblings: entry.include_siblings, | ||
| includeHeader: entry.include_header, | ||
| limit: entry.limit ?? DEFAULT_LINE_LIMIT, | ||
| limit: effectiveLimit, | ||
| maxLines: entry.max_lines, | ||
| }) | ||
|
|
||
|
|
@@ -287,7 +298,6 @@ export class ReadFileTool extends BaseTool<"read_file"> { | |
| if (result.wasTruncated && result.includedRanges.length > 0) { | ||
| const [start, end] = result.includedRanges[0] | ||
| const nextOffset = end + 1 | ||
| const effectiveLimit = entry.limit ?? DEFAULT_LINE_LIMIT | ||
| // Put truncation warning at TOP (before content) to match @ mention format | ||
| output = `IMPORTANT: File content truncated. | ||
| Status: Showing lines ${start}-${end} of ${result.totalLines} total lines. | ||
|
|
@@ -306,7 +316,11 @@ export class ReadFileTool extends BaseTool<"read_file"> { | |
| // NOTE: read_file offset is 1-based externally; convert to 0-based for readWithSlice. | ||
| const offset1 = entry.offset ?? 1 | ||
| const offset0 = Math.max(0, offset1 - 1) | ||
| const limit = entry.limit ?? DEFAULT_LINE_LIMIT | ||
| // Clamp the limit: if the provider has a max, enforce it even when the model requests more | ||
| const requestedSliceLimit = entry.limit ?? defaultLimit | ||
| const limit = providerMaxReadFileLine | ||
| ? Math.min(requestedSliceLimit, providerMaxReadFileLine) | ||
| : requestedSliceLimit | ||
|
|
||
| const result = readWithSlice(content, offset0, limit) | ||
|
|
||
|
|
@@ -786,8 +800,10 @@ export class ReadFileTool extends BaseTool<"read_file"> { | |
| } | ||
| content = selectedLines.join("\n") | ||
| } else { | ||
| // Read with default limits using slice mode | ||
| const result = readWithSlice(rawContent, 0, DEFAULT_LINE_LIMIT) | ||
| // Read with default limits using slice mode, clamped by provider setting | ||
| const providerMaxReadFileLine = task.apiConfiguration?.maxReadFileLine | ||
| const legacyLimit = providerMaxReadFileLine ?? DEFAULT_LINE_LIMIT | ||
| const result = readWithSlice(rawContent, 0, legacyLimit) | ||
| content = result.content | ||
| if (result.wasTruncated) { | ||
| content += `\n\n[File truncated: showing ${result.returnedLines} of ${result.totalLines} total lines]` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { useAppTranslation } from "@/i18n/TranslationContext" | ||
|
|
||
| interface MaxReadFileLineControlProps { | ||
| value: number | undefined | ||
| onChange: (value: number | undefined) => void | ||
| } | ||
|
|
||
| export const MaxReadFileLineControl = ({ value, onChange }: MaxReadFileLineControlProps) => { | ||
| const { t } = useAppTranslation() | ||
|
|
||
| return ( | ||
| <div className="flex flex-col gap-1"> | ||
| <label className="block font-medium mb-1">{t("settings:providers.maxReadFileLine.label")}</label> | ||
| <div className="flex items-center gap-2"> | ||
| <input | ||
| type="number" | ||
| className="w-24 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border px-2 py-1 rounded text-right [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none" | ||
| value={value ?? ""} | ||
| min={1} | ||
| max={100000} | ||
| placeholder="2000" | ||
| onChange={(e) => { | ||
| const raw = e.target.value | ||
| if (raw === "") { | ||
| onChange(undefined) | ||
| return | ||
| } | ||
| const newValue = parseInt(raw, 10) | ||
| if (!isNaN(newValue) && newValue >= 1) { | ||
| onChange(newValue) | ||
| } | ||
| }} | ||
| /> | ||
| <span>{t("settings:providers.maxReadFileLine.unit")}</span> | ||
| </div> | ||
| <div className="text-sm text-vscode-descriptionForeground"> | ||
| {t("settings:providers.maxReadFileLine.description")} | ||
| </div> | ||
| </div> | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parseMentionsnow has 9 positional parameters, which makes call sites fragile and hard to read (the tests that had to addundefined, // maxReadFileLineillustrate the problem). Its callerprocessUserContentMentionsalready uses a destructured options object. Consider refactoringparseMentionsto accept an options object too, which would make adding future parameters straightforward and eliminate positional-ordering mistakes.Fix it with Roo Code or mention @roomote and request a fix.