diff --git a/src/core/tools/simpleReadFileTool.ts b/src/core/tools/simpleReadFileTool.ts index 1b41e9e9d6..06686786e5 100644 --- a/src/core/tools/simpleReadFileTool.ts +++ b/src/core/tools/simpleReadFileTool.ts @@ -4,6 +4,7 @@ import { isBinaryFile } from "isbinaryfile" import { Task } from "../task/Task" import { ClineSayTool } from "../../shared/ExtensionMessage" import { formatResponse } from "../prompts/responses" +import { getModelMaxOutputTokens } from "../../shared/api" import { t } from "../../i18n" import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools" import { RecordSource } from "../context-tracking/FileContextTrackerTypes" @@ -13,7 +14,7 @@ import { countFileLines } from "../../integrations/misc/line-counter" import { readLines } from "../../integrations/misc/read-lines" import { extractTextFromFile, addLineNumbers, getSupportedBinaryFormats } from "../../integrations/misc/extract-text" import { parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" -import { ToolProtocol, isNativeProtocol } from "@roo-code/types" +import { ToolProtocol, isNativeProtocol, ANTHROPIC_DEFAULT_MAX_TOKENS } from "@roo-code/types" import { DEFAULT_MAX_IMAGE_FILE_SIZE_MB, DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB, @@ -21,6 +22,7 @@ import { validateImageForProcessing, processImageFile, } from "./helpers/imageHelpers" +import { validateFileTokenBudget, truncateFileContent } from "./helpers/fileTokenBudget" /** * Simplified read file tool for models that only support single file reads @@ -249,12 +251,53 @@ export async function simpleReadFileTool( } // Handle normal file read - const content = await extractTextFromFile(fullPath) - const lineRangeAttr = ` lines="1-${totalLines}"` - let xmlInfo = totalLines > 0 ? `\n${content}\n` : `` + // Get token budget information + const { id: modelId, info: modelInfo } = cline.api.getModel() + const { contextTokens } = cline.getTokenUsage() + const contextWindow = modelInfo.contextWindow - if (totalLines === 0) { - xmlInfo += `File is empty\n` + const maxOutputTokens = + getModelMaxOutputTokens({ + modelId, + model: modelInfo, + settings: cline.apiConfiguration, + }) ?? ANTHROPIC_DEFAULT_MAX_TOKENS + + // Validate if file content fits within token budget + const budgetResult = await validateFileTokenBudget( + fullPath, + contextWindow - maxOutputTokens, + contextTokens || 0, + ) + + let content = await extractTextFromFile(fullPath) + let xmlInfo = "" + + if (budgetResult.shouldTruncate && budgetResult.maxChars !== undefined) { + // File exceeds token budget - truncate it + const truncateResult = truncateFileContent( + content, + budgetResult.maxChars, + content.length, + budgetResult.isPreview, + ) + content = truncateResult.content + + let displayedLines = content.length === 0 ? 0 : content.split(/\r?\n/).length + if (displayedLines > 0 && content.endsWith("\n")) { + displayedLines-- + } + const lineRangeAttr = displayedLines > 0 ? ` lines="1-${displayedLines}"` : "" + xmlInfo = content.length > 0 ? `\n${content}\n` : `` + xmlInfo += `${truncateResult.notice}\n` + } else { + // File fits within budget - read normally + const lineRangeAttr = ` lines="1-${totalLines}"` + xmlInfo = totalLines > 0 ? `\n${content}\n` : `` + + if (totalLines === 0) { + xmlInfo += `File is empty\n` + } } // Track file read