mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: add maxConcurrentFileReads limit to native read_file tool schema (#10449)
Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
parent
1459f0113b
commit
c307dc7756
5 changed files with 218 additions and 16 deletions
158
src/core/prompts/tools/native-tools/__tests__/read_file.spec.ts
Normal file
158
src/core/prompts/tools/native-tools/__tests__/read_file.spec.ts
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
import type OpenAI from "openai"
|
||||
import { createReadFileTool, type ReadFileToolOptions } from "../read_file"
|
||||
|
||||
// Helper type to access function tools
|
||||
type FunctionTool = OpenAI.Chat.ChatCompletionTool & { type: "function" }
|
||||
|
||||
// Helper to get function definition from tool
|
||||
const getFunctionDef = (tool: OpenAI.Chat.ChatCompletionTool) => (tool as FunctionTool).function
|
||||
|
||||
describe("createReadFileTool", () => {
|
||||
describe("maxConcurrentFileReads documentation", () => {
|
||||
it("should include default maxConcurrentFileReads limit (5) in description", () => {
|
||||
const tool = createReadFileTool()
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).toContain("maximum of 5 files")
|
||||
expect(description).toContain("If you need to read more files, use multiple sequential read_file requests")
|
||||
})
|
||||
|
||||
it("should include custom maxConcurrentFileReads limit in description", () => {
|
||||
const tool = createReadFileTool({ maxConcurrentFileReads: 3 })
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).toContain("maximum of 3 files")
|
||||
expect(description).toContain("within 3-file limit")
|
||||
})
|
||||
|
||||
it("should indicate single file reads only when maxConcurrentFileReads is 1", () => {
|
||||
const tool = createReadFileTool({ maxConcurrentFileReads: 1 })
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).toContain("Multiple file reads are currently disabled")
|
||||
expect(description).toContain("only read one file at a time")
|
||||
expect(description).not.toContain("Example multiple files")
|
||||
})
|
||||
|
||||
it("should use singular 'Read a file' in base description when maxConcurrentFileReads is 1", () => {
|
||||
const tool = createReadFileTool({ maxConcurrentFileReads: 1 })
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).toMatch(/^Read a file/)
|
||||
expect(description).not.toContain("Read one or more files")
|
||||
})
|
||||
|
||||
it("should use plural 'Read one or more files' in base description when maxConcurrentFileReads is > 1", () => {
|
||||
const tool = createReadFileTool({ maxConcurrentFileReads: 5 })
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).toMatch(/^Read one or more files/)
|
||||
})
|
||||
|
||||
it("should not show multiple files example when maxConcurrentFileReads is 1", () => {
|
||||
const tool = createReadFileTool({ maxConcurrentFileReads: 1, partialReadsEnabled: true })
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).not.toContain("Example multiple files")
|
||||
})
|
||||
|
||||
it("should show multiple files example when maxConcurrentFileReads is > 1", () => {
|
||||
const tool = createReadFileTool({ maxConcurrentFileReads: 5, partialReadsEnabled: true })
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).toContain("Example multiple files")
|
||||
})
|
||||
})
|
||||
|
||||
describe("partialReadsEnabled option", () => {
|
||||
it("should include line_ranges in description when partialReadsEnabled is true", () => {
|
||||
const tool = createReadFileTool({ partialReadsEnabled: true })
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).toContain("line_ranges")
|
||||
expect(description).toContain("Example with line ranges")
|
||||
})
|
||||
|
||||
it("should not include line_ranges in description when partialReadsEnabled is false", () => {
|
||||
const tool = createReadFileTool({ partialReadsEnabled: false })
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).not.toContain("line_ranges")
|
||||
expect(description).not.toContain("Example with line ranges")
|
||||
})
|
||||
|
||||
it("should include line_ranges parameter in schema when partialReadsEnabled is true", () => {
|
||||
const tool = createReadFileTool({ partialReadsEnabled: true })
|
||||
const schema = getFunctionDef(tool).parameters as any
|
||||
|
||||
expect(schema.properties.files.items.properties).toHaveProperty("line_ranges")
|
||||
})
|
||||
|
||||
it("should not include line_ranges parameter in schema when partialReadsEnabled is false", () => {
|
||||
const tool = createReadFileTool({ partialReadsEnabled: false })
|
||||
const schema = getFunctionDef(tool).parameters as any
|
||||
|
||||
expect(schema.properties.files.items.properties).not.toHaveProperty("line_ranges")
|
||||
})
|
||||
})
|
||||
|
||||
describe("combined options", () => {
|
||||
it("should correctly combine low maxConcurrentFileReads with partialReadsEnabled", () => {
|
||||
const tool = createReadFileTool({
|
||||
maxConcurrentFileReads: 2,
|
||||
partialReadsEnabled: true,
|
||||
})
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).toContain("maximum of 2 files")
|
||||
expect(description).toContain("line_ranges")
|
||||
expect(description).toContain("within 2-file limit")
|
||||
})
|
||||
|
||||
it("should correctly handle maxConcurrentFileReads of 1 with partialReadsEnabled false", () => {
|
||||
const tool = createReadFileTool({
|
||||
maxConcurrentFileReads: 1,
|
||||
partialReadsEnabled: false,
|
||||
})
|
||||
const description = getFunctionDef(tool).description
|
||||
|
||||
expect(description).toContain("only read one file at a time")
|
||||
expect(description).not.toContain("line_ranges")
|
||||
expect(description).not.toContain("Example multiple files")
|
||||
})
|
||||
})
|
||||
|
||||
describe("tool structure", () => {
|
||||
it("should have correct tool name", () => {
|
||||
const tool = createReadFileTool()
|
||||
|
||||
expect(getFunctionDef(tool).name).toBe("read_file")
|
||||
})
|
||||
|
||||
it("should be a function type tool", () => {
|
||||
const tool = createReadFileTool()
|
||||
|
||||
expect(tool.type).toBe("function")
|
||||
})
|
||||
|
||||
it("should have strict mode enabled", () => {
|
||||
const tool = createReadFileTool()
|
||||
|
||||
expect(getFunctionDef(tool).strict).toBe(true)
|
||||
})
|
||||
|
||||
it("should require files parameter", () => {
|
||||
const tool = createReadFileTool()
|
||||
const schema = getFunctionDef(tool).parameters as any
|
||||
|
||||
expect(schema.required).toContain("files")
|
||||
})
|
||||
|
||||
it("should require path in file objects", () => {
|
||||
const tool = createReadFileTool({ partialReadsEnabled: false })
|
||||
const schema = getFunctionDef(tool).parameters as any
|
||||
|
||||
expect(schema.properties.files.items.required).toContain("path")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -11,7 +11,7 @@ import fetchInstructions from "./fetch_instructions"
|
|||
import generateImage from "./generate_image"
|
||||
import listFiles from "./list_files"
|
||||
import newTask from "./new_task"
|
||||
import { createReadFileTool } from "./read_file"
|
||||
import { createReadFileTool, type ReadFileToolOptions } from "./read_file"
|
||||
import runSlashCommand from "./run_slash_command"
|
||||
import searchAndReplace from "./search_and_replace"
|
||||
import searchReplace from "./search_replace"
|
||||
|
|
@ -23,14 +23,32 @@ import writeToFile from "./write_to_file"
|
|||
|
||||
export { getMcpServerTools } from "./mcp_server"
|
||||
export { convertOpenAIToolToAnthropic, convertOpenAIToolsToAnthropic } from "./converters"
|
||||
export type { ReadFileToolOptions } from "./read_file"
|
||||
|
||||
/**
|
||||
* Options for customizing the native tools array.
|
||||
*/
|
||||
export interface NativeToolsOptions {
|
||||
/** Whether to include line_ranges support in read_file tool (default: true) */
|
||||
partialReadsEnabled?: boolean
|
||||
/** Maximum number of files that can be read in a single read_file request (default: 5) */
|
||||
maxConcurrentFileReads?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Get native tools array, optionally customizing based on settings.
|
||||
*
|
||||
* @param partialReadsEnabled - Whether to include line_ranges support in read_file tool (default: true)
|
||||
* @param options - Configuration options for the tools
|
||||
* @returns Array of native tool definitions
|
||||
*/
|
||||
export function getNativeTools(partialReadsEnabled: boolean = true): OpenAI.Chat.ChatCompletionTool[] {
|
||||
export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.ChatCompletionTool[] {
|
||||
const { partialReadsEnabled = true, maxConcurrentFileReads = 5 } = options
|
||||
|
||||
const readFileOptions: ReadFileToolOptions = {
|
||||
partialReadsEnabled,
|
||||
maxConcurrentFileReads,
|
||||
}
|
||||
|
||||
return [
|
||||
accessMcpResource,
|
||||
apply_diff,
|
||||
|
|
@ -44,7 +62,7 @@ export function getNativeTools(partialReadsEnabled: boolean = true): OpenAI.Chat
|
|||
generateImage,
|
||||
listFiles,
|
||||
newTask,
|
||||
createReadFileTool(partialReadsEnabled),
|
||||
createReadFileTool(readFileOptions),
|
||||
runSlashCommand,
|
||||
searchAndReplace,
|
||||
searchReplace,
|
||||
|
|
@ -57,4 +75,4 @@ export function getNativeTools(partialReadsEnabled: boolean = true): OpenAI.Chat
|
|||
}
|
||||
|
||||
// Backward compatibility: export default tools with line ranges enabled
|
||||
export const nativeTools = getNativeTools(true)
|
||||
export const nativeTools = getNativeTools()
|
||||
|
|
|
|||
|
|
@ -1,20 +1,36 @@
|
|||
import type OpenAI from "openai"
|
||||
|
||||
const READ_FILE_BASE_DESCRIPTION = `Read one or more files and return their contents with line numbers for diffing or discussion.`
|
||||
|
||||
const READ_FILE_SUPPORTS_NOTE = `Supports text extraction from PDF and DOCX files, but may not handle other binary files properly.`
|
||||
|
||||
/**
|
||||
* Options for creating the read_file tool definition.
|
||||
*/
|
||||
export interface ReadFileToolOptions {
|
||||
/** Whether to include line_ranges parameter (default: true) */
|
||||
partialReadsEnabled?: boolean
|
||||
/** Maximum number of files that can be read in a single request (default: 5) */
|
||||
maxConcurrentFileReads?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param options - Configuration options for the tool
|
||||
* @returns Native tool definition for read_file
|
||||
*/
|
||||
export function createReadFileTool(partialReadsEnabled: boolean = true): OpenAI.Chat.ChatCompletionTool {
|
||||
export function createReadFileTool(options: ReadFileToolOptions = {}): OpenAI.Chat.ChatCompletionTool {
|
||||
const { partialReadsEnabled = true, maxConcurrentFileReads = 5 } = options
|
||||
const isMultipleReadsEnabled = maxConcurrentFileReads > 1
|
||||
|
||||
// Build description intro with concurrent reads limit message
|
||||
const descriptionIntro = isMultipleReadsEnabled
|
||||
? `Read one or more files and return their contents with line numbers for diffing or discussion. IMPORTANT: You can read a maximum of ${maxConcurrentFileReads} files in a single request. If you need to read more files, use multiple sequential read_file requests. `
|
||||
: "Read a file and return its contents with line numbers for diffing or discussion. IMPORTANT: Multiple file reads are currently disabled. You can only read one file at a time. "
|
||||
|
||||
const baseDescription =
|
||||
READ_FILE_BASE_DESCRIPTION +
|
||||
" Structure: { files: [{ path: 'relative/path.ts'" +
|
||||
descriptionIntro +
|
||||
"Structure: { files: [{ path: 'relative/path.ts'" +
|
||||
(partialReadsEnabled ? ", line_ranges: [[1, 50], [100, 150]]" : "") +
|
||||
" }] }. " +
|
||||
"The 'path' is required and relative to workspace. "
|
||||
|
|
@ -26,9 +42,13 @@ export function createReadFileTool(partialReadsEnabled: boolean = true): OpenAI.
|
|||
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' }] }"
|
||||
(isMultipleReadsEnabled
|
||||
? `Example multiple files (within ${maxConcurrentFileReads}-file limit): { 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' }] }"
|
||||
(isMultipleReadsEnabled
|
||||
? `Example multiple files (within ${maxConcurrentFileReads}-file limit): { files: [{ path: 'file1.ts' }, { path: 'file2.ts' }] }`
|
||||
: "")
|
||||
|
||||
const description = baseDescription + optionalRangesDescription + READ_FILE_SUPPORTS_NOTE + " " + examples
|
||||
|
||||
|
|
@ -87,4 +107,4 @@ export function createReadFileTool(partialReadsEnabled: boolean = true): OpenAI.
|
|||
} satisfies OpenAI.Chat.ChatCompletionTool
|
||||
}
|
||||
|
||||
export const read_file = createReadFileTool(false)
|
||||
export const read_file = createReadFileTool({ partialReadsEnabled: false })
|
||||
|
|
|
|||
|
|
@ -3924,6 +3924,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
experiments: state?.experiments,
|
||||
apiConfiguration,
|
||||
maxReadFileLine: state?.maxReadFileLine ?? -1,
|
||||
maxConcurrentFileReads: state?.maxConcurrentFileReads ?? 5,
|
||||
browserToolEnabled: state?.browserToolEnabled ?? true,
|
||||
modelInfo,
|
||||
diffEnabled: this.diffEnabled,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ interface BuildToolsOptions {
|
|||
experiments: Record<string, boolean> | undefined
|
||||
apiConfiguration: ProviderSettings | undefined
|
||||
maxReadFileLine: number
|
||||
maxConcurrentFileReads: number
|
||||
browserToolEnabled: boolean
|
||||
modelInfo?: ModelInfo
|
||||
diffEnabled: boolean
|
||||
|
|
@ -40,6 +41,7 @@ export async function buildNativeToolsArray(options: BuildToolsOptions): Promise
|
|||
experiments,
|
||||
apiConfiguration,
|
||||
maxReadFileLine,
|
||||
maxConcurrentFileReads,
|
||||
browserToolEnabled,
|
||||
modelInfo,
|
||||
diffEnabled,
|
||||
|
|
@ -62,8 +64,11 @@ export async function buildNativeToolsArray(options: BuildToolsOptions): Promise
|
|||
// 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)
|
||||
// Build native tools with dynamic read_file tool based on settings.
|
||||
const nativeTools = getNativeTools({
|
||||
partialReadsEnabled,
|
||||
maxConcurrentFileReads,
|
||||
})
|
||||
|
||||
// Filter native tools based on mode restrictions.
|
||||
const filteredNativeTools = filterNativeToolsForMode(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue