From 210c9add7f5c1cc19df1c30afd080ab174042c05 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Wed, 19 Nov 2025 23:59:34 -0500 Subject: [PATCH 1/2] Improve read_file tool description with examples - Add explicit JSON structure documentation - Include three concrete examples (single file, with line ranges, multiple files) - Clarify that 'path' is required and 'line_ranges' is optional - Better explain line range format (1-based inclusive) This addresses agent confusion by providing clear examples similar to the XML tool definition. --- src/core/prompts/tools/native-tools/read_file.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/prompts/tools/native-tools/read_file.ts b/src/core/prompts/tools/native-tools/read_file.ts index 6118f585870..8a9c3f290ed 100644 --- a/src/core/prompts/tools/native-tools/read_file.ts +++ b/src/core/prompts/tools/native-tools/read_file.ts @@ -5,7 +5,13 @@ export const read_file = { function: { name: "read_file", description: - "Read one or more files and return their contents with line numbers for diffing or discussion. Use line ranges when available to keep reads efficient and combine related files when possible.", + "Read one or more files and return their contents with line numbers for diffing or discussion. " + + "Structure: { files: [{ path: 'relative/path.ts', line_ranges: ['1-50', '100-150'] }] }. " + + "The 'path' is required and relative to workspace. " + + "The 'line_ranges' is optional for reading specific sections (format: 'start-end', 1-based inclusive). " + + "Example single file: { files: [{ path: 'src/app.ts' }] }. " + + "Example with line ranges: { files: [{ path: 'src/app.ts', line_ranges: ['1-50', '100-150'] }] }. " + + "Example multiple files: { files: [{ path: 'file1.ts', line_ranges: ['1-50'] }, { path: 'file2.ts' }] }", strict: true, parameters: { type: "object", From 8b235d485b3bf5797ae99898207d7e9f2f118af3 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Thu, 20 Nov 2025 01:13:22 -0500 Subject: [PATCH 2/2] Make read_file tool dynamic based on partialReadsEnabled setting - Convert read_file from static export to createReadFileTool() factory function - Add getNativeTools() function that accepts partialReadsEnabled parameter - Create buildNativeToolsArray() helper to encapsulate tool building logic - Update Task.ts to build native tools dynamically using maxReadFileLine setting - When partialReadsEnabled is false, line_ranges parameter is excluded from schema - Examples and descriptions adjust based on whether line ranges are supported This matches the behavior of the XML tool definition which dynamically adjusts its documentation based on settings, reducing confusion for agents. --- src/core/prompts/tools/native-tools/index.ts | 53 ++++---- .../prompts/tools/native-tools/read_file.ts | 113 +++++++++++------- src/core/task/Task.ts | 39 ++---- src/core/task/build-tools.ts | 62 ++++++++++ 4 files changed, 178 insertions(+), 89 deletions(-) create mode 100644 src/core/task/build-tools.ts diff --git a/src/core/prompts/tools/native-tools/index.ts b/src/core/prompts/tools/native-tools/index.ts index c12a681704e..941fd3fddb0 100644 --- a/src/core/prompts/tools/native-tools/index.ts +++ b/src/core/prompts/tools/native-tools/index.ts @@ -10,7 +10,7 @@ import insertContent from "./insert_content" import listCodeDefinitionNames from "./list_code_definition_names" import listFiles from "./list_files" import newTask from "./new_task" -import { read_file } from "./read_file" +import { createReadFileTool } from "./read_file" import runSlashCommand from "./run_slash_command" import searchFiles from "./search_files" import switchMode from "./switch_mode" @@ -21,23 +21,34 @@ import { apply_diff_single_file } from "./apply_diff" export { getMcpServerTools } from "./mcp_server" export { convertOpenAIToolToAnthropic, convertOpenAIToolsToAnthropic } from "./converters" -export const nativeTools = [ - apply_diff_single_file, - askFollowupQuestion, - attemptCompletion, - browserAction, - codebaseSearch, - executeCommand, - fetchInstructions, - generateImage, - insertContent, - listCodeDefinitionNames, - listFiles, - newTask, - read_file, - runSlashCommand, - searchFiles, - switchMode, - updateTodoList, - writeToFile, -] satisfies OpenAI.Chat.ChatCompletionTool[] +/** + * Get native tools array, optionally customizing based on settings. + * + * @param partialReadsEnabled - Whether to include line_ranges support in read_file tool (default: true) + * @returns Array of native tool definitions + */ +export function getNativeTools(partialReadsEnabled: boolean = true): OpenAI.Chat.ChatCompletionTool[] { + return [ + apply_diff_single_file, + askFollowupQuestion, + attemptCompletion, + browserAction, + codebaseSearch, + executeCommand, + fetchInstructions, + generateImage, + insertContent, + listCodeDefinitionNames, + listFiles, + newTask, + createReadFileTool(partialReadsEnabled), + runSlashCommand, + searchFiles, + switchMode, + updateTodoList, + writeToFile, + ] satisfies OpenAI.Chat.ChatCompletionTool[] +} + +// Backward compatibility: export default tools with line ranges enabled +export const nativeTools = getNativeTools(true) diff --git a/src/core/prompts/tools/native-tools/read_file.ts b/src/core/prompts/tools/native-tools/read_file.ts index 8a9c3f290ed..81bee82d5e3 100644 --- a/src/core/prompts/tools/native-tools/read_file.ts +++ b/src/core/prompts/tools/native-tools/read_file.ts @@ -1,49 +1,80 @@ import type OpenAI from "openai" -export const read_file = { - type: "function", - function: { - name: "read_file", - description: - "Read one or more files and return their contents with line numbers for diffing or discussion. " + - "Structure: { files: [{ path: 'relative/path.ts', line_ranges: ['1-50', '100-150'] }] }. " + - "The 'path' is required and relative to workspace. " + - "The 'line_ranges' is optional for reading specific sections (format: 'start-end', 1-based inclusive). " + - "Example single file: { files: [{ path: 'src/app.ts' }] }. " + +/** + * Creates the read_file tool definition, optionally including line_ranges support + * based on whether partial reads are enabled. + * + * @param partialReadsEnabled - Whether to include line_ranges parameter + * @returns Native tool definition for read_file + */ +export function createReadFileTool(partialReadsEnabled: boolean = true): OpenAI.Chat.ChatCompletionTool { + const baseDescription = + "Read one or more files and return their contents with line numbers for diffing or discussion. " + + "Structure: { files: [{ path: 'relative/path.ts'" + + (partialReadsEnabled ? ", line_ranges: ['1-50', '100-150']" : "") + + " }] }. " + + "The 'path' is required and relative to workspace. " + + const optionalRangesDescription = partialReadsEnabled + ? "The 'line_ranges' is optional for reading specific sections (format: 'start-end', 1-based inclusive). " + : "" + + const examples = partialReadsEnabled + ? "Example single file: { files: [{ path: 'src/app.ts' }] }. " + "Example with line ranges: { files: [{ path: 'src/app.ts', line_ranges: ['1-50', '100-150'] }] }. " + - "Example multiple files: { files: [{ path: 'file1.ts', line_ranges: ['1-50'] }, { path: 'file2.ts' }] }", - strict: true, - parameters: { - type: "object", - properties: { - files: { - type: "array", - description: "List of files to read; request related files together when allowed", - items: { - type: "object", - properties: { - path: { - type: "string", - description: "Path to the file to read, relative to the workspace", - }, - line_ranges: { - type: ["array", "null"], - description: - "Optional 1-based inclusive ranges to read (format: start-end). Use multiple ranges for non-contiguous sections and keep ranges tight to the needed context.", - items: { - type: "string", - pattern: "^[0-9]+-[0-9]+$", - }, - }, + "Example multiple files: { files: [{ path: 'file1.ts', line_ranges: ['1-50'] }, { path: 'file2.ts' }] }" + : "Example single file: { files: [{ path: 'src/app.ts' }] }. " + + "Example multiple files: { files: [{ path: 'file1.ts' }, { path: 'file2.ts' }] }" + + const description = baseDescription + optionalRangesDescription + examples + + // Build the properties object conditionally + const fileProperties: Record = { + path: { + type: "string", + description: "Path to the file to read, relative to the workspace", + }, + } + + // Only include line_ranges if partial reads are enabled + if (partialReadsEnabled) { + fileProperties.line_ranges = { + type: ["array", "null"], + description: + "Optional 1-based inclusive ranges to read (format: start-end). Use multiple ranges for non-contiguous sections and keep ranges tight to the needed context.", + items: { + type: "string", + pattern: "^[0-9]+-[0-9]+$", + }, + } + } + + return { + type: "function", + function: { + name: "read_file", + description, + strict: true, + parameters: { + type: "object", + properties: { + files: { + type: "array", + description: "List of files to read; request related files together when allowed", + items: { + type: "object", + properties: fileProperties, + required: ["path"], + additionalProperties: false, }, - required: ["path"], - additionalProperties: false, + minItems: 1, }, - minItems: 1, }, + required: ["files"], + additionalProperties: false, }, - required: ["files"], - additionalProperties: false, }, - }, -} satisfies OpenAI.Chat.ChatCompletionTool + } satisfies OpenAI.Chat.ChatCompletionTool +} + +export const read_file = createReadFileTool(false) diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 84919f88a47..ef4001b5625 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -86,8 +86,7 @@ import { getWorkspacePath } from "../../utils/path" // prompts import { formatResponse } from "../prompts/responses" import { SYSTEM_PROMPT } from "../prompts/system" -import { nativeTools, getMcpServerTools } from "../prompts/tools/native-tools" -import { filterNativeToolsForMode, filterMcpToolsForMode } from "../prompts/tools/filter-tools-for-mode" +import { buildNativeToolsArray } from "./build-tools" // core modules import { ToolRepetitionDetector } from "../tools/ToolRepetitionDetector" @@ -3120,34 +3119,20 @@ export class Task extends EventEmitter implements TaskLike { let allTools: OpenAI.Chat.ChatCompletionTool[] = [] if (shouldIncludeTools) { const provider = this.providerRef.deref() - const mcpHub = provider?.getMcpHub() - - // Get CodeIndexManager for feature checking - const { CodeIndexManager } = await import("../../services/code-index/manager") - const codeIndexManager = CodeIndexManager.getInstance(provider!.context, this.cwd) - - // Build settings object for tool filtering - // Include browserToolEnabled to filter browser_action when disabled by user - const filterSettings = { - todoListEnabled: apiConfiguration?.todoListEnabled ?? true, - browserToolEnabled: state?.browserToolEnabled ?? true, + if (!provider) { + throw new Error("Provider reference lost during tool building") } - // Filter native tools based on mode restrictions (similar to XML tool filtering) - const filteredNativeTools = filterNativeToolsForMode( - nativeTools, + allTools = await buildNativeToolsArray({ + provider, + cwd: this.cwd, mode, - state?.customModes, - state?.experiments, - codeIndexManager, - filterSettings, - ) - - // Filter MCP tools based on mode restrictions - const mcpTools = getMcpServerTools(mcpHub) - const filteredMcpTools = filterMcpToolsForMode(mcpTools, mode, state?.customModes, state?.experiments) - - allTools = [...filteredNativeTools, ...filteredMcpTools] + customModes: state?.customModes, + experiments: state?.experiments, + apiConfiguration, + maxReadFileLine: state?.maxReadFileLine ?? -1, + browserToolEnabled: state?.browserToolEnabled ?? true, + }) } const metadata: ApiHandlerCreateMessageMetadata = { diff --git a/src/core/task/build-tools.ts b/src/core/task/build-tools.ts new file mode 100644 index 00000000000..4708a462d63 --- /dev/null +++ b/src/core/task/build-tools.ts @@ -0,0 +1,62 @@ +import type OpenAI from "openai" +import type { ProviderSettings, ModeConfig } from "@roo-code/types" +import type { ClineProvider } from "../webview/ClineProvider" +import { getNativeTools, getMcpServerTools } from "../prompts/tools/native-tools" +import { filterNativeToolsForMode, filterMcpToolsForMode } from "../prompts/tools/filter-tools-for-mode" + +interface BuildToolsOptions { + provider: ClineProvider + cwd: string + mode: string | undefined + customModes: ModeConfig[] | undefined + experiments: Record | undefined + apiConfiguration: ProviderSettings | undefined + maxReadFileLine: number + browserToolEnabled: boolean +} + +/** + * Builds the complete tools array for native protocol requests. + * Combines native tools and MCP tools, filtered by mode restrictions. + * + * @param options - Configuration options for building the tools + * @returns Array of filtered native and MCP tools + */ +export async function buildNativeToolsArray(options: BuildToolsOptions): Promise { + const { provider, cwd, mode, customModes, experiments, apiConfiguration, maxReadFileLine, browserToolEnabled } = + options + + const mcpHub = provider.getMcpHub() + + // Get CodeIndexManager for feature checking + const { CodeIndexManager } = await import("../../services/code-index/manager") + const codeIndexManager = CodeIndexManager.getInstance(provider.context, cwd) + + // Build settings object for tool filtering + const filterSettings = { + todoListEnabled: apiConfiguration?.todoListEnabled ?? true, + browserToolEnabled: browserToolEnabled ?? true, + } + + // Determine if partial reads are enabled based on maxReadFileLine setting + const partialReadsEnabled = maxReadFileLine !== -1 + + // Build native tools with dynamic read_file tool based on partialReadsEnabled + const nativeTools = getNativeTools(partialReadsEnabled) + + // Filter native tools based on mode restrictions + const filteredNativeTools = filterNativeToolsForMode( + nativeTools, + mode, + customModes, + experiments, + codeIndexManager, + filterSettings, + ) + + // Filter MCP tools based on mode restrictions + const mcpTools = getMcpServerTools(mcpHub) + const filteredMcpTools = filterMcpToolsForMode(mcpTools, mode, customModes, experiments) + + return [...filteredNativeTools, ...filteredMcpTools] +}