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
12 changes: 0 additions & 12 deletions packages/build/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,6 @@ describe("generatePackageJson", () => {
default: "",
description: "%settings.customStoragePath.description%",
},
"roo-cline.toolProtocol": {
type: "string",
enum: ["xml", "native"],
default: "xml",
description: "%settings.toolProtocol.description%",
},
},
},
},
Expand Down Expand Up @@ -219,12 +213,6 @@ describe("generatePackageJson", () => {
default: "",
description: "%settings.customStoragePath.description%",
},
"roo-code-nightly.toolProtocol": {
type: "string",
enum: ["xml", "native"],
default: "xml",
description: "%settings.toolProtocol.description%",
},
},
},
},
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/experiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const experimentIds = [
"preventFocusDisruption",
"imageGeneration",
"runSlashCommand",
"nativeToolCalling",
] as const

export const experimentIdsSchema = z.enum(experimentIds)
Expand All @@ -28,6 +29,7 @@ export const experimentsSchema = z.object({
preventFocusDisruption: z.boolean().optional(),
imageGeneration: z.boolean().optional(),
runSlashCommand: z.boolean().optional(),
nativeToolCalling: z.boolean().optional(),
})

export type Experiments = z.infer<typeof experimentsSchema>
Expand Down
24 changes: 13 additions & 11 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,18 +280,18 @@ export async function presentAssistantMessage(cline: Task) {
// Track if we've already pushed a tool result for this tool call (native protocol only)
let hasToolResult = false

const pushToolResult = (content: ToolResponse) => {
// Check if we're using native tool protocol
const toolProtocol = resolveToolProtocol(
cline.apiConfiguration,
cline.api.getModel().info,
cline.apiConfiguration.apiProvider,
)
const isNative = isNativeProtocol(toolProtocol)

// Get the tool call ID if this is a native tool call
const toolCallId = (block as any).id
// Check if we're using native tool protocol (do this once before defining pushToolResult)
const state = await cline.providerRef.deref()?.getState()
const toolProtocol = resolveToolProtocol(
cline.apiConfiguration,
cline.api.getModel().info,
cline.apiConfiguration.apiProvider,
state?.experiments,
)
const isNative = isNativeProtocol(toolProtocol)
const toolCallId = (block as any).id

const pushToolResult = (content: ToolResponse) => {
if (isNative && toolCallId) {
// For native protocol, only allow ONE tool_result per tool call
if (hasToolResult) {
Expand Down Expand Up @@ -518,10 +518,12 @@ export async function presentAssistantMessage(cline: Task) {
await checkpointSaveAndMark(cline)

// Check if native protocol is enabled - if so, always use single-file class-based tool
const state = await cline.providerRef.deref()?.getState()
const applyDiffToolProtocol = resolveToolProtocol(
cline.apiConfiguration,
cline.api.getModel().info,
cline.apiConfiguration.apiProvider,
state?.experiments,
)
if (isNativeProtocol(applyDiffToolProtocol)) {
await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
Expand Down
7 changes: 3 additions & 4 deletions src/core/prompts/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import * as path from "path"
import * as diff from "diff"
import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/RooIgnoreController"
import { RooProtectedController } from "../protect/RooProtectedController"
import { ToolProtocol, isNativeProtocol } from "@roo-code/types"
import { getToolProtocolFromSettings } from "../../utils/toolProtocol"
import { ToolProtocol, isNativeProtocol, TOOL_PROTOCOL } from "@roo-code/types"

export const formatResponse = {
toolDenied: () => `The user denied this operation.`,
Expand Down Expand Up @@ -245,10 +244,10 @@ Always ensure you provide all required parameters for the tool you wish to use.`
/**
* Gets the appropriate tool use instructions reminder based on the protocol.
*
* @param protocol - Optional tool protocol, falls back to default if not provided
* @param protocol - Optional tool protocol, defaults to XML if not provided
* @returns The tool use instructions reminder text
*/
function getToolInstructionsReminder(protocol?: ToolProtocol): string {
const effectiveProtocol = protocol ?? getToolProtocolFromSettings()
const effectiveProtocol = protocol ?? TOOL_PROTOCOL.XML
return isNativeProtocol(effectiveProtocol) ? toolUseInstructionsReminderNative : toolUseInstructionsReminder
}
36 changes: 31 additions & 5 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import delay from "delay"
import pWaitFor from "p-wait-for"
import { serializeError } from "serialize-error"
import { Package } from "../../shared/package"
import { getCurrentToolProtocol, formatToolInvocation } from "../tools/helpers/toolResultFormatting"
import { formatToolInvocation } from "../tools/helpers/toolResultFormatting"

import {
type TaskLike,
Expand Down Expand Up @@ -46,7 +46,6 @@ import {
} from "@roo-code/types"
import { TelemetryService } from "@roo-code/telemetry"
import { CloudService, BridgeOrchestrator } from "@roo-code/cloud"
import { getToolProtocolFromSettings } from "../../utils/toolProtocol"
import { resolveToolProtocol } from "../../utils/resolveToolProtocol"

// api
Expand Down Expand Up @@ -321,6 +320,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
task,
images,
historyItem,
experiments: experimentsConfig,
startTask = true,
rootTask,
parentTask,
Expand Down Expand Up @@ -410,10 +410,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {

// Initialize the assistant message parser only for XML protocol.
// For native protocol, tool calls come as tool_call chunks, not XML.
// experiments is always provided via TaskOptions (defaults to experimentDefault in provider)
const toolProtocol = resolveToolProtocol(
this.apiConfiguration,
this.api.getModel().info,
this.apiConfiguration.apiProvider,
experimentsConfig,
)
this.assistantMessageParser = toolProtocol === "xml" ? new AssistantMessageParser() : undefined

Expand Down Expand Up @@ -1268,7 +1270,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
} without value for required parameter '${paramName}'. Retrying...`,
)
const modelInfo = this.api.getModel().info
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo, this.apiConfiguration.apiProvider)
const state = await this.providerRef.deref()?.getState()
const toolProtocol = resolveToolProtocol(
this.apiConfiguration,
modelInfo,
this.apiConfiguration.apiProvider,
state?.experiments,
)
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName, toolProtocol))
}

Expand Down Expand Up @@ -1409,10 +1417,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {

// v2.0 xml tags refactor caveat: since we don't use tools anymore, we need to replace all tool use blocks with a text block since the API disallows conversations with tool uses and no tool schema
// Now also protocol-aware: format according to current protocol setting
const state = await this.providerRef.deref()?.getState()
const protocol = resolveToolProtocol(
this.apiConfiguration,
this.api.getModel().info,
this.apiConfiguration.apiProvider,
state?.experiments,
)
const useNative = isNativeProtocol(protocol)

Expand Down Expand Up @@ -1786,10 +1796,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
break
} else {
const modelInfo = this.api.getModel().info
const state = await this.providerRef.deref()?.getState()
const toolProtocol = resolveToolProtocol(
this.apiConfiguration,
modelInfo,
this.apiConfiguration.apiProvider,
state?.experiments,
)
nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed(toolProtocol) }]
this.consecutiveMistakeCount++
Expand Down Expand Up @@ -2436,11 +2448,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
const parsedBlocks = this.assistantMessageParser.getContentBlocks()

// Check if we're using native protocol
const state = await this.providerRef.deref()?.getState()
const isNative = isNativeProtocol(
resolveToolProtocol(
this.apiConfiguration,
this.api.getModel().info,
this.apiConfiguration.apiProvider,
state?.experiments,
),
)

Expand Down Expand Up @@ -2581,10 +2595,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {

if (!didToolUse) {
const modelInfo = this.api.getModel().info
const state = await this.providerRef.deref()?.getState()
const toolProtocol = resolveToolProtocol(
this.apiConfiguration,
modelInfo,
this.apiConfiguration.apiProvider,
state?.experiments,
)
this.userMessageContent.push({ type: "text", text: formatResponse.noToolsUsed(toolProtocol) })
this.consecutiveMistakeCount++
Expand All @@ -2610,12 +2626,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// apiConversationHistory at line 1876. Since the assistant failed to respond,
// we need to remove that message before retrying to avoid having two consecutive
// user messages (which would cause tool_result validation errors).
let state = await this.providerRef.deref()?.getState()
if (
isNativeProtocol(
resolveToolProtocol(
this.apiConfiguration,
this.api.getModel().info,
this.apiConfiguration.apiProvider,
state?.experiments,
),
) &&
this.apiConversationHistory.length > 0
Expand All @@ -2628,7 +2646,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}

// Check if we should auto-retry or prompt the user
const state = await this.providerRef.deref()?.getState()
// Reuse the state variable from above
if (state?.autoApprovalEnabled && state?.alwaysApproveResubmit) {
// Auto-retry with backoff - don't persist failure message when retrying
const errorMsg =
Expand Down Expand Up @@ -2681,12 +2699,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
} else {
// User declined to retry
// For native protocol, re-add the user message we removed
// Reuse the state variable from above
if (
isNativeProtocol(
resolveToolProtocol(
this.apiConfiguration,
this.api.getModel().info,
this.apiConfiguration.apiProvider,
state?.experiments,
),
)
) {
Expand Down Expand Up @@ -2791,6 +2811,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
apiConfiguration ?? this.apiConfiguration,
modelInfo,
(apiConfiguration ?? this.apiConfiguration)?.apiProvider,
experiments,
)

return SYSTEM_PROMPT(
Expand Down Expand Up @@ -3030,7 +3051,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// 1. Tool protocol is set to NATIVE
// 2. Model supports native tools
const modelInfo = this.api.getModel().info
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo, this.apiConfiguration.apiProvider)
const toolProtocol = resolveToolProtocol(
this.apiConfiguration,
modelInfo,
this.apiConfiguration.apiProvider,
state?.experiments,
)
const shouldIncludeTools = toolProtocol === TOOL_PROTOCOL.NATIVE && (modelInfo.supportsNativeTools ?? false)

// Build complete tools array: native tools + dynamic MCP tools, filtered by mode restrictions
Expand Down
7 changes: 4 additions & 3 deletions src/core/tools/MultiApplyDiffTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,13 @@ export async function applyDiffTool(
removeClosingTag: RemoveClosingTag,
) {
// Check if native protocol is enabled - if so, always use single-file class-based tool
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const toolProtocol = resolveToolProtocol(
cline.apiConfiguration,
cline.api.getModel().info,
cline.apiConfiguration.apiProvider,
state?.experiments,
)
if (isNativeProtocol(toolProtocol)) {
return applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
Expand All @@ -77,9 +80,7 @@ export async function applyDiffTool(
}

// Check if MULTI_FILE_APPLY_DIFF experiment is enabled
const provider = cline.providerRef.deref()
if (provider) {
const state = await provider.getState()
if (provider && state) {
const isMultiFileApplyDiffEnabled = experiments.isEnabled(
state.experiments ?? {},
EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF,
Expand Down
8 changes: 7 additions & 1 deletion src/core/tools/ReadFileTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,13 @@ export class ReadFileTool extends BaseTool<"read_file"> {
const { handleError, pushToolResult } = callbacks
const fileEntries = params.files
const modelInfo = task.api.getModel().info
const protocol = resolveToolProtocol(task.apiConfiguration, modelInfo, task.apiConfiguration.apiProvider)
const state = await task.providerRef.deref()?.getState()
const protocol = resolveToolProtocol(
task.apiConfiguration,
modelInfo,
task.apiConfiguration.apiProvider,
state?.experiments,
)
const useNative = isNativeProtocol(protocol)

if (!fileEntries || fileEntries.length === 0) {
Expand Down
7 changes: 1 addition & 6 deletions src/core/tools/__tests__/applyDiffTool.experiment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,6 @@ describe("applyDiffTool experiment routing", () => {
})

it("should use class-based tool when native protocol is enabled regardless of experiment", async () => {
// Enable native protocol
const vscode = await import("vscode")
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
get: vi.fn().mockReturnValue(TOOL_PROTOCOL.NATIVE),
} as any)

// Update model to support native tools
mockCline.api.getModel = vi.fn().mockReturnValue({
id: "test-model",
Expand All @@ -176,6 +170,7 @@ describe("applyDiffTool experiment routing", () => {
mockProvider.getState.mockResolvedValue({
experiments: {
[EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF]: true,
[EXPERIMENT_IDS.NATIVE_TOOL_CALLING]: true, // Enable native tool calling experiment
},
})
;(applyDiffToolClass.handle as any).mockResolvedValue(undefined)
Expand Down
2 changes: 1 addition & 1 deletion src/core/webview/generateSystemPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
const canUseBrowserTool = modelSupportsBrowser && modeSupportsBrowser && (browserToolEnabled ?? true)

// Resolve tool protocol for system prompt generation
const toolProtocol = resolveToolProtocol(apiConfiguration, modelInfo, apiConfiguration.apiProvider)
const toolProtocol = resolveToolProtocol(apiConfiguration, modelInfo, apiConfiguration.apiProvider, experiments)

const systemPrompt = await SYSTEM_PROMPT(
provider.context,
Expand Down
9 changes: 0 additions & 9 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -436,15 +436,6 @@
"minimum": 1,
"maximum": 200,
"description": "%settings.codeIndex.embeddingBatchSize.description%"
},
"roo-cline.toolProtocol": {
"type": "string",
"enum": [
"xml",
"native"
],
"default": "xml",
"description": "%settings.toolProtocol.description%"
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/package.nls.ca.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/package.nls.de.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/package.nls.es.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/package.nls.fr.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading