diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index c65d070086..b151067d1d 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -12,6 +12,7 @@ export * from "./message.js"
export * from "./mode.js"
export * from "./model.js"
export * from "./provider-settings.js"
+export * from "./single-file-read-models.js"
export * from "./task.js"
export * from "./todo.js"
export * from "./telemetry.js"
diff --git a/packages/types/src/single-file-read-models.ts b/packages/types/src/single-file-read-models.ts
new file mode 100644
index 0000000000..4e6f4e1f57
--- /dev/null
+++ b/packages/types/src/single-file-read-models.ts
@@ -0,0 +1,32 @@
+/**
+ * Configuration for models that should use simplified single-file read_file tool
+ * These models will use the simpler ... format
+ * instead of the more complex multi-file args format
+ */
+
+// List of model IDs (or patterns) that should use single file reads only
+export const SINGLE_FILE_READ_MODELS = new Set(["roo/sonic"])
+
+/**
+ * Check if a model should use single file read format
+ * @param modelId The model ID to check
+ * @returns true if the model should use single file reads
+ */
+export function shouldUseSingleFileRead(modelId: string): boolean {
+ // Direct match
+ if (SINGLE_FILE_READ_MODELS.has(modelId)) {
+ return true
+ }
+
+ // Pattern matching for model families
+ // Check if model ID starts with any configured pattern
+ // Using Array.from for compatibility with older TypeScript targets
+ const patterns = Array.from(SINGLE_FILE_READ_MODELS)
+ for (const pattern of patterns) {
+ if (pattern.endsWith("*") && modelId.startsWith(pattern.slice(0, -1))) {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts
index acdc7f5412..a8b90728b1 100644
--- a/src/core/assistant-message/presentAssistantMessage.ts
+++ b/src/core/assistant-message/presentAssistantMessage.ts
@@ -10,6 +10,8 @@ import type { ToolParamName, ToolResponse } from "../../shared/tools"
import { fetchInstructionsTool } from "../tools/fetchInstructionsTool"
import { listFilesTool } from "../tools/listFilesTool"
import { getReadFileToolDescription, readFileTool } from "../tools/readFileTool"
+import { getSimpleReadFileToolDescription, simpleReadFileTool } from "../tools/simpleReadFileTool"
+import { shouldUseSingleFileRead } from "@roo-code/types"
import { writeToFileTool } from "../tools/writeToFileTool"
import { applyDiffTool } from "../tools/multiApplyDiffTool"
import { insertContentTool } from "../tools/insertContentTool"
@@ -155,7 +157,13 @@ export async function presentAssistantMessage(cline: Task) {
case "execute_command":
return `[${block.name} for '${block.params.command}']`
case "read_file":
- return getReadFileToolDescription(block.name, block.params)
+ // Check if this model should use the simplified description
+ const modelId = cline.api.getModel().id
+ if (shouldUseSingleFileRead(modelId)) {
+ return getSimpleReadFileToolDescription(block.name, block.params)
+ } else {
+ return getReadFileToolDescription(block.name, block.params)
+ }
case "fetch_instructions":
return `[${block.name} for '${block.params.task}']`
case "write_to_file":
@@ -454,8 +462,20 @@ export async function presentAssistantMessage(cline: Task) {
await searchAndReplaceTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
break
case "read_file":
- await readFileTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
-
+ // Check if this model should use the simplified single-file read tool
+ const modelId = cline.api.getModel().id
+ if (shouldUseSingleFileRead(modelId)) {
+ await simpleReadFileTool(
+ cline,
+ block,
+ askApproval,
+ handleError,
+ pushToolResult,
+ removeClosingTag,
+ )
+ } else {
+ await readFileTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
+ }
break
case "fetch_instructions":
await fetchInstructionsTool(cline, block, askApproval, handleError, pushToolResult)
diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts
index 4ed1185da7..3cc327c815 100644
--- a/src/core/prompts/system.ts
+++ b/src/core/prompts/system.ts
@@ -61,6 +61,7 @@ async function generatePrompt(
partialReadsEnabled?: boolean,
settings?: SystemPromptSettings,
todoList?: TodoItem[],
+ modelId?: string,
): Promise {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
@@ -106,6 +107,7 @@ ${getToolDescriptionsForMode(
partialReadsEnabled,
settings,
enableMcpServerCreation,
+ modelId,
)}
${getToolUseGuidelinesSection(codeIndexManager)}
@@ -150,6 +152,7 @@ export const SYSTEM_PROMPT = async (
partialReadsEnabled?: boolean,
settings?: SystemPromptSettings,
todoList?: TodoItem[],
+ modelId?: string,
): Promise => {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
@@ -221,5 +224,6 @@ ${customInstructions}`
partialReadsEnabled,
settings,
todoList,
+ modelId,
)
}
diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts
index d455abb8d7..3eb112d270 100644
--- a/src/core/prompts/tools/index.ts
+++ b/src/core/prompts/tools/index.ts
@@ -7,7 +7,9 @@ import { Mode, getModeConfig, isToolAllowedForMode, getGroupName } from "../../.
import { ToolArgs } from "./types"
import { getExecuteCommandDescription } from "./execute-command"
import { getReadFileDescription } from "./read-file"
+import { getSimpleReadFileDescription } from "./simple-read-file"
import { getFetchInstructionsDescription } from "./fetch-instructions"
+import { shouldUseSingleFileRead } from "@roo-code/types"
import { getWriteToFileDescription } from "./write-to-file"
import { getSearchFilesDescription } from "./search-files"
import { getListFilesDescription } from "./list-files"
@@ -28,7 +30,14 @@ import { CodeIndexManager } from "../../../services/code-index/manager"
// Map of tool names to their description functions
const toolDescriptionMap: Record string | undefined> = {
execute_command: (args) => getExecuteCommandDescription(args),
- read_file: (args) => getReadFileDescription(args),
+ read_file: (args) => {
+ // Check if the current model should use the simplified read_file tool
+ const modelId = args.settings?.modelId
+ if (modelId && shouldUseSingleFileRead(modelId)) {
+ return getSimpleReadFileDescription(args)
+ }
+ return getReadFileDescription(args)
+ },
fetch_instructions: (args) => getFetchInstructionsDescription(args.settings?.enableMcpServerCreation),
write_to_file: (args) => getWriteToFileDescription(args),
search_files: (args) => getSearchFilesDescription(args),
@@ -62,6 +71,7 @@ export function getToolDescriptionsForMode(
partialReadsEnabled?: boolean,
settings?: Record,
enableMcpServerCreation?: boolean,
+ modelId?: string,
): string {
const config = getModeConfig(mode, customModes)
const args: ToolArgs = {
@@ -74,6 +84,7 @@ export function getToolDescriptionsForMode(
settings: {
...settings,
enableMcpServerCreation,
+ modelId,
},
experiments,
}
@@ -138,6 +149,7 @@ export function getToolDescriptionsForMode(
export {
getExecuteCommandDescription,
getReadFileDescription,
+ getSimpleReadFileDescription,
getFetchInstructionsDescription,
getWriteToFileDescription,
getSearchFilesDescription,
diff --git a/src/core/prompts/tools/simple-read-file.ts b/src/core/prompts/tools/simple-read-file.ts
new file mode 100644
index 0000000000..28f4f1129e
--- /dev/null
+++ b/src/core/prompts/tools/simple-read-file.ts
@@ -0,0 +1,35 @@
+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
+ */
+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.
+
+Parameters:
+- path: (required) File path (relative to workspace directory ${args.cwd})
+
+Usage:
+
+path/to/file
+
+
+Examples:
+
+1. Reading a TypeScript file:
+
+src/app.ts
+
+
+2. Reading a configuration file:
+
+config.json
+
+
+3. Reading a markdown file:
+
+README.md
+`
+}
diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts
index cff8d5aec3..34f3218236 100644
--- a/src/core/task/Task.ts
+++ b/src/core/task/Task.ts
@@ -2226,6 +2226,8 @@ export class Task extends EventEmitter implements TaskLike {
todoListEnabled: apiConfiguration?.todoListEnabled ?? true,
useAgentRules: vscode.workspace.getConfiguration("roo-cline").get("useAgentRules") ?? true,
},
+ undefined, // todoList
+ this.api.getModel().id,
)
})()
}
diff --git a/src/core/tools/simpleReadFileTool.ts b/src/core/tools/simpleReadFileTool.ts
new file mode 100644
index 0000000000..ee6656c5c8
--- /dev/null
+++ b/src/core/tools/simpleReadFileTool.ts
@@ -0,0 +1,287 @@
+import path from "path"
+import { isBinaryFile } from "isbinaryfile"
+
+import { Task } from "../task/Task"
+import { ClineSayTool } from "../../shared/ExtensionMessage"
+import { formatResponse } from "../prompts/responses"
+import { t } from "../../i18n"
+import { ToolUse, AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "../../shared/tools"
+import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
+import { isPathOutsideWorkspace } from "../../utils/pathUtils"
+import { getReadablePath } from "../../utils/path"
+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 {
+ DEFAULT_MAX_IMAGE_FILE_SIZE_MB,
+ DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB,
+ isSupportedImageFormat,
+ validateImageForProcessing,
+ processImageFile,
+} from "./helpers/imageHelpers"
+
+/**
+ * Simplified read file tool for models that only support single file reads
+ * Uses the format: file/path.ext
+ *
+ * This is a streamlined version of readFileTool that:
+ * - Only accepts a single path parameter
+ * - Does not support multiple files
+ * - Does not support line ranges
+ * - Has simpler XML parsing
+ */
+export async function simpleReadFileTool(
+ cline: Task,
+ block: ToolUse,
+ askApproval: AskApproval,
+ handleError: HandleError,
+ pushToolResult: PushToolResult,
+ _removeClosingTag: RemoveClosingTag,
+) {
+ const filePath: string | undefined = block.params.path
+
+ // Check if the current model supports images
+ const modelInfo = cline.api.getModel().info
+ const supportsImages = modelInfo.supportsImages ?? false
+
+ // Handle partial message
+ if (block.partial) {
+ const fullPath = filePath ? path.resolve(cline.cwd, filePath) : ""
+ const sharedMessageProps: ClineSayTool = {
+ tool: "readFile",
+ path: getReadablePath(cline.cwd, filePath || ""),
+ isOutsideWorkspace: filePath ? isPathOutsideWorkspace(fullPath) : false,
+ }
+ const partialMessage = JSON.stringify({
+ ...sharedMessageProps,
+ content: undefined,
+ } satisfies ClineSayTool)
+ await cline.ask("tool", partialMessage, block.partial).catch(() => {})
+ return
+ }
+
+ // Validate path parameter
+ if (!filePath) {
+ cline.consecutiveMistakeCount++
+ cline.recordToolError("read_file")
+ const errorMsg = await cline.sayAndCreateMissingParamError("read_file", "path")
+ pushToolResult(`${errorMsg}`)
+ return
+ }
+
+ const relPath = filePath
+ const fullPath = path.resolve(cline.cwd, relPath)
+
+ try {
+ // Check RooIgnore validation
+ const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath)
+ if (!accessAllowed) {
+ await cline.say("rooignore_error", relPath)
+ const errorMsg = formatResponse.rooIgnoreError(relPath)
+ pushToolResult(`${relPath}${errorMsg}`)
+ return
+ }
+
+ // Get max read file line setting
+ const { maxReadFileLine = -1 } = (await cline.providerRef.deref()?.getState()) ?? {}
+
+ // Create approval message
+ const isOutsideWorkspace = isPathOutsideWorkspace(fullPath)
+ let lineSnippet = ""
+ if (maxReadFileLine === 0) {
+ lineSnippet = t("tools:readFile.definitionsOnly")
+ } else if (maxReadFileLine > 0) {
+ lineSnippet = t("tools:readFile.maxLines", { max: maxReadFileLine })
+ }
+
+ const completeMessage = JSON.stringify({
+ tool: "readFile",
+ path: getReadablePath(cline.cwd, relPath),
+ isOutsideWorkspace,
+ content: fullPath,
+ reason: lineSnippet,
+ } satisfies ClineSayTool)
+
+ const { response, text, images } = await cline.ask("tool", completeMessage, false)
+
+ if (response !== "yesButtonClicked") {
+ // Handle denial
+ if (text) {
+ await cline.say("user_feedback", text, images)
+ }
+ cline.didRejectTool = true
+
+ const statusMessage = text ? formatResponse.toolDeniedWithFeedback(text) : formatResponse.toolDenied()
+
+ pushToolResult(`${statusMessage}\n${relPath}Denied by user`)
+ return
+ }
+
+ // Handle approval with feedback
+ if (text) {
+ await cline.say("user_feedback", text, images)
+ }
+
+ // Process the file
+ const [totalLines, isBinary] = await Promise.all([countFileLines(fullPath), isBinaryFile(fullPath)])
+
+ // Handle binary files
+ if (isBinary) {
+ const fileExtension = path.extname(relPath).toLowerCase()
+ const supportedBinaryFormats = getSupportedBinaryFormats()
+
+ // Check if it's a supported image format
+ if (isSupportedImageFormat(fileExtension)) {
+ try {
+ const {
+ maxImageFileSize = DEFAULT_MAX_IMAGE_FILE_SIZE_MB,
+ maxTotalImageSize = DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB,
+ } = (await cline.providerRef.deref()?.getState()) ?? {}
+
+ // Validate image for processing
+ const validationResult = await validateImageForProcessing(
+ fullPath,
+ supportsImages,
+ maxImageFileSize,
+ maxTotalImageSize,
+ 0, // No cumulative memory for single file
+ )
+
+ if (!validationResult.isValid) {
+ await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
+ pushToolResult(
+ `${relPath}\n${validationResult.notice}\n`,
+ )
+ return
+ }
+
+ // Process the image
+ const imageResult = await processImageFile(fullPath)
+ await cline.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
+
+ // Return result with image data
+ const result = formatResponse.toolResult(
+ `${relPath}\n${imageResult.notice}\n`,
+ supportsImages ? [imageResult.dataUrl] : undefined,
+ )
+
+ if (typeof result === "string") {
+ pushToolResult(result)
+ } else {
+ pushToolResult(result)
+ }
+ return
+ } catch (error) {
+ const errorMsg = error instanceof Error ? error.message : String(error)
+ pushToolResult(
+ `${relPath}Error reading image file: ${errorMsg}`,
+ )
+ await handleError(
+ `reading image file ${relPath}`,
+ error instanceof Error ? error : new Error(errorMsg),
+ )
+ return
+ }
+ }
+
+ // Check if it's a supported binary format that can be processed
+ if (supportedBinaryFormats && supportedBinaryFormats.includes(fileExtension)) {
+ // For supported binary formats (.pdf, .docx, .ipynb), continue to extractTextFromFile
+ // Fall through to the normal extractTextFromFile processing below
+ } else {
+ // Handle unknown binary format
+ const fileFormat = fileExtension.slice(1) || "bin"
+ pushToolResult(
+ `${relPath}\nBinary file - content not displayed\n`,
+ )
+ return
+ }
+ }
+
+ // Handle definitions-only mode
+ if (maxReadFileLine === 0) {
+ try {
+ const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController)
+ if (defResult) {
+ let xmlInfo = `Showing only definitions. Use standard read_file if you need to read actual content\n`
+ pushToolResult(
+ `${relPath}\n${defResult}\n${xmlInfo}`,
+ )
+ }
+ } catch (error) {
+ if (error instanceof Error && error.message.startsWith("Unsupported language:")) {
+ console.warn(`[simple_read_file] Warning: ${error.message}`)
+ } else {
+ console.error(
+ `[simple_read_file] Unhandled error: ${error instanceof Error ? error.message : String(error)}`,
+ )
+ }
+ }
+ return
+ }
+
+ // Handle files exceeding line threshold
+ if (maxReadFileLine > 0 && totalLines > maxReadFileLine) {
+ const content = addLineNumbers(await readLines(fullPath, maxReadFileLine - 1, 0))
+ const lineRangeAttr = ` lines="1-${maxReadFileLine}"`
+ let xmlInfo = `\n${content}\n`
+
+ try {
+ const defResult = await parseSourceCodeDefinitionsForFile(fullPath, cline.rooIgnoreController)
+ if (defResult) {
+ xmlInfo += `${defResult}\n`
+ }
+ xmlInfo += `Showing only ${maxReadFileLine} of ${totalLines} total lines. File is too large for complete display\n`
+ pushToolResult(`${relPath}\n${xmlInfo}`)
+ } catch (error) {
+ if (error instanceof Error && error.message.startsWith("Unsupported language:")) {
+ console.warn(`[simple_read_file] Warning: ${error.message}`)
+ } else {
+ console.error(
+ `[simple_read_file] Unhandled error: ${error instanceof Error ? error.message : String(error)}`,
+ )
+ }
+ }
+ return
+ }
+
+ // Handle normal file read
+ const content = await extractTextFromFile(fullPath)
+ const lineRangeAttr = ` lines="1-${totalLines}"`
+ let xmlInfo = totalLines > 0 ? `\n${content}\n` : ``
+
+ if (totalLines === 0) {
+ xmlInfo += `File is empty\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}`)
+ }
+ } catch (error) {
+ const errorMsg = error instanceof Error ? error.message : String(error)
+ pushToolResult(`${relPath}Error reading file: ${errorMsg}`)
+ await handleError(`reading file ${relPath}`, error instanceof Error ? error : new Error(errorMsg))
+ }
+}
+
+/**
+ * Get description for the simple read file tool
+ * @param blockName The name of the tool block
+ * @param blockParams The parameters passed to the tool
+ * @returns A description string for the tool use
+ */
+export function getSimpleReadFileToolDescription(blockName: string, blockParams: any): string {
+ if (blockParams.path) {
+ return `[${blockName} for '${blockParams.path}']`
+ } else {
+ return `[${blockName} with missing path]`
+ }
+}