Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 32 additions & 21 deletions src/core/prompts/tools/native-tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
109 changes: 73 additions & 36 deletions src/core/prompts/tools/native-tools/read_file.ts
Original file line number Diff line number Diff line change
@@ -1,43 +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. Use line ranges when available to keep reads efficient and combine related files when possible.",
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]+$",
},
},
/**
* 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' }] }"
: "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<string, any> = {
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)
39 changes: 12 additions & 27 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -3120,34 +3119,20 @@ export class Task extends EventEmitter<TaskEvents> 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 = {
Expand Down
62 changes: 62 additions & 0 deletions src/core/task/build-tools.ts
Original file line number Diff line number Diff line change
@@ -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<string, boolean> | 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<OpenAI.Chat.ChatCompletionTool[]> {
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]
}
Loading