Skip to content

Commit 3e0bd0e

Browse files
mrubensellipsis-dev[bot]daniel-lxs
authored
Move the native tool call toggle to experimental settings (#9297)
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> Co-authored-by: daniel-lxs <[email protected]>
1 parent 7a06902 commit 3e0bd0e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+242
-147
lines changed

packages/build/src/__tests__/index.test.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,6 @@ describe("generatePackageJson", () => {
100100
default: "",
101101
description: "%settings.customStoragePath.description%",
102102
},
103-
"roo-cline.toolProtocol": {
104-
type: "string",
105-
enum: ["xml", "native"],
106-
default: "xml",
107-
description: "%settings.toolProtocol.description%",
108-
},
109103
},
110104
},
111105
},
@@ -219,12 +213,6 @@ describe("generatePackageJson", () => {
219213
default: "",
220214
description: "%settings.customStoragePath.description%",
221215
},
222-
"roo-code-nightly.toolProtocol": {
223-
type: "string",
224-
enum: ["xml", "native"],
225-
default: "xml",
226-
description: "%settings.toolProtocol.description%",
227-
},
228216
},
229217
},
230218
},

packages/types/src/experiment.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const experimentIds = [
1212
"preventFocusDisruption",
1313
"imageGeneration",
1414
"runSlashCommand",
15+
"nativeToolCalling",
1516
] as const
1617

1718
export const experimentIdsSchema = z.enum(experimentIds)
@@ -28,6 +29,7 @@ export const experimentsSchema = z.object({
2829
preventFocusDisruption: z.boolean().optional(),
2930
imageGeneration: z.boolean().optional(),
3031
runSlashCommand: z.boolean().optional(),
32+
nativeToolCalling: z.boolean().optional(),
3133
})
3234

3335
export type Experiments = z.infer<typeof experimentsSchema>

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -280,18 +280,18 @@ export async function presentAssistantMessage(cline: Task) {
280280
// Track if we've already pushed a tool result for this tool call (native protocol only)
281281
let hasToolResult = false
282282

283-
const pushToolResult = (content: ToolResponse) => {
284-
// Check if we're using native tool protocol
285-
const toolProtocol = resolveToolProtocol(
286-
cline.apiConfiguration,
287-
cline.api.getModel().info,
288-
cline.apiConfiguration.apiProvider,
289-
)
290-
const isNative = isNativeProtocol(toolProtocol)
291-
292-
// Get the tool call ID if this is a native tool call
293-
const toolCallId = (block as any).id
283+
// Check if we're using native tool protocol (do this once before defining pushToolResult)
284+
const state = await cline.providerRef.deref()?.getState()
285+
const toolProtocol = resolveToolProtocol(
286+
cline.apiConfiguration,
287+
cline.api.getModel().info,
288+
cline.apiConfiguration.apiProvider,
289+
state?.experiments,
290+
)
291+
const isNative = isNativeProtocol(toolProtocol)
292+
const toolCallId = (block as any).id
294293

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

520520
// Check if native protocol is enabled - if so, always use single-file class-based tool
521+
const state = await cline.providerRef.deref()?.getState()
521522
const applyDiffToolProtocol = resolveToolProtocol(
522523
cline.apiConfiguration,
523524
cline.api.getModel().info,
524525
cline.apiConfiguration.apiProvider,
526+
state?.experiments,
525527
)
526528
if (isNativeProtocol(applyDiffToolProtocol)) {
527529
await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {

src/core/prompts/responses.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import * as path from "path"
33
import * as diff from "diff"
44
import { RooIgnoreController, LOCK_TEXT_SYMBOL } from "../ignore/RooIgnoreController"
55
import { RooProtectedController } from "../protect/RooProtectedController"
6-
import { ToolProtocol, isNativeProtocol } from "@roo-code/types"
7-
import { getToolProtocolFromSettings } from "../../utils/toolProtocol"
6+
import { ToolProtocol, isNativeProtocol, TOOL_PROTOCOL } from "@roo-code/types"
87

98
export const formatResponse = {
109
toolDenied: () => `The user denied this operation.`,
@@ -245,10 +244,10 @@ Always ensure you provide all required parameters for the tool you wish to use.`
245244
/**
246245
* Gets the appropriate tool use instructions reminder based on the protocol.
247246
*
248-
* @param protocol - Optional tool protocol, falls back to default if not provided
247+
* @param protocol - Optional tool protocol, defaults to XML if not provided
249248
* @returns The tool use instructions reminder text
250249
*/
251250
function getToolInstructionsReminder(protocol?: ToolProtocol): string {
252-
const effectiveProtocol = protocol ?? getToolProtocolFromSettings()
251+
const effectiveProtocol = protocol ?? TOOL_PROTOCOL.XML
253252
return isNativeProtocol(effectiveProtocol) ? toolUseInstructionsReminderNative : toolUseInstructionsReminder
254253
}

src/core/task/Task.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import delay from "delay"
1010
import pWaitFor from "p-wait-for"
1111
import { serializeError } from "serialize-error"
1212
import { Package } from "../../shared/package"
13-
import { getCurrentToolProtocol, formatToolInvocation } from "../tools/helpers/toolResultFormatting"
13+
import { formatToolInvocation } from "../tools/helpers/toolResultFormatting"
1414

1515
import {
1616
type TaskLike,
@@ -46,7 +46,6 @@ import {
4646
} from "@roo-code/types"
4747
import { TelemetryService } from "@roo-code/telemetry"
4848
import { CloudService, BridgeOrchestrator } from "@roo-code/cloud"
49-
import { getToolProtocolFromSettings } from "../../utils/toolProtocol"
5049
import { resolveToolProtocol } from "../../utils/resolveToolProtocol"
5150

5251
// api
@@ -321,6 +320,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
321320
task,
322321
images,
323322
historyItem,
323+
experiments: experimentsConfig,
324324
startTask = true,
325325
rootTask,
326326
parentTask,
@@ -410,10 +410,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
410410

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

@@ -1268,7 +1270,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
12681270
} without value for required parameter '${paramName}'. Retrying...`,
12691271
)
12701272
const modelInfo = this.api.getModel().info
1271-
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo, this.apiConfiguration.apiProvider)
1273+
const state = await this.providerRef.deref()?.getState()
1274+
const toolProtocol = resolveToolProtocol(
1275+
this.apiConfiguration,
1276+
modelInfo,
1277+
this.apiConfiguration.apiProvider,
1278+
state?.experiments,
1279+
)
12721280
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName, toolProtocol))
12731281
}
12741282

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

14101418
// 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
14111419
// Now also protocol-aware: format according to current protocol setting
1420+
const state = await this.providerRef.deref()?.getState()
14121421
const protocol = resolveToolProtocol(
14131422
this.apiConfiguration,
14141423
this.api.getModel().info,
14151424
this.apiConfiguration.apiProvider,
1425+
state?.experiments,
14161426
)
14171427
const useNative = isNativeProtocol(protocol)
14181428

@@ -1786,10 +1796,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
17861796
break
17871797
} else {
17881798
const modelInfo = this.api.getModel().info
1799+
const state = await this.providerRef.deref()?.getState()
17891800
const toolProtocol = resolveToolProtocol(
17901801
this.apiConfiguration,
17911802
modelInfo,
17921803
this.apiConfiguration.apiProvider,
1804+
state?.experiments,
17931805
)
17941806
nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed(toolProtocol) }]
17951807
this.consecutiveMistakeCount++
@@ -2436,11 +2448,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
24362448
const parsedBlocks = this.assistantMessageParser.getContentBlocks()
24372449

24382450
// Check if we're using native protocol
2451+
const state = await this.providerRef.deref()?.getState()
24392452
const isNative = isNativeProtocol(
24402453
resolveToolProtocol(
24412454
this.apiConfiguration,
24422455
this.api.getModel().info,
24432456
this.apiConfiguration.apiProvider,
2457+
state?.experiments,
24442458
),
24452459
)
24462460

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

25822596
if (!didToolUse) {
25832597
const modelInfo = this.api.getModel().info
2598+
const state = await this.providerRef.deref()?.getState()
25842599
const toolProtocol = resolveToolProtocol(
25852600
this.apiConfiguration,
25862601
modelInfo,
25872602
this.apiConfiguration.apiProvider,
2603+
state?.experiments,
25882604
)
25892605
this.userMessageContent.push({ type: "text", text: formatResponse.noToolsUsed(toolProtocol) })
25902606
this.consecutiveMistakeCount++
@@ -2610,12 +2626,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
26102626
// apiConversationHistory at line 1876. Since the assistant failed to respond,
26112627
// we need to remove that message before retrying to avoid having two consecutive
26122628
// user messages (which would cause tool_result validation errors).
2629+
let state = await this.providerRef.deref()?.getState()
26132630
if (
26142631
isNativeProtocol(
26152632
resolveToolProtocol(
26162633
this.apiConfiguration,
26172634
this.api.getModel().info,
26182635
this.apiConfiguration.apiProvider,
2636+
state?.experiments,
26192637
),
26202638
) &&
26212639
this.apiConversationHistory.length > 0
@@ -2628,7 +2646,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
26282646
}
26292647

26302648
// Check if we should auto-retry or prompt the user
2631-
const state = await this.providerRef.deref()?.getState()
2649+
// Reuse the state variable from above
26322650
if (state?.autoApprovalEnabled && state?.alwaysApproveResubmit) {
26332651
// Auto-retry with backoff - don't persist failure message when retrying
26342652
const errorMsg =
@@ -2681,12 +2699,14 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
26812699
} else {
26822700
// User declined to retry
26832701
// For native protocol, re-add the user message we removed
2702+
// Reuse the state variable from above
26842703
if (
26852704
isNativeProtocol(
26862705
resolveToolProtocol(
26872706
this.apiConfiguration,
26882707
this.api.getModel().info,
26892708
this.apiConfiguration.apiProvider,
2709+
state?.experiments,
26902710
),
26912711
)
26922712
) {
@@ -2791,6 +2811,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
27912811
apiConfiguration ?? this.apiConfiguration,
27922812
modelInfo,
27932813
(apiConfiguration ?? this.apiConfiguration)?.apiProvider,
2814+
experiments,
27942815
)
27952816

27962817
return SYSTEM_PROMPT(
@@ -3030,7 +3051,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
30303051
// 1. Tool protocol is set to NATIVE
30313052
// 2. Model supports native tools
30323053
const modelInfo = this.api.getModel().info
3033-
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo, this.apiConfiguration.apiProvider)
3054+
const toolProtocol = resolveToolProtocol(
3055+
this.apiConfiguration,
3056+
modelInfo,
3057+
this.apiConfiguration.apiProvider,
3058+
state?.experiments,
3059+
)
30343060
const shouldIncludeTools = toolProtocol === TOOL_PROTOCOL.NATIVE && (modelInfo.supportsNativeTools ?? false)
30353061

30363062
// Build complete tools array: native tools + dynamic MCP tools, filtered by mode restrictions

src/core/tools/MultiApplyDiffTool.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,13 @@ export async function applyDiffTool(
6262
removeClosingTag: RemoveClosingTag,
6363
) {
6464
// Check if native protocol is enabled - if so, always use single-file class-based tool
65+
const provider = cline.providerRef.deref()
66+
const state = await provider?.getState()
6567
const toolProtocol = resolveToolProtocol(
6668
cline.apiConfiguration,
6769
cline.api.getModel().info,
6870
cline.apiConfiguration.apiProvider,
71+
state?.experiments,
6972
)
7073
if (isNativeProtocol(toolProtocol)) {
7174
return applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
@@ -77,9 +80,7 @@ export async function applyDiffTool(
7780
}
7881

7982
// Check if MULTI_FILE_APPLY_DIFF experiment is enabled
80-
const provider = cline.providerRef.deref()
81-
if (provider) {
82-
const state = await provider.getState()
83+
if (provider && state) {
8384
const isMultiFileApplyDiffEnabled = experiments.isEnabled(
8485
state.experiments ?? {},
8586
EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF,

src/core/tools/ReadFileTool.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,13 @@ export class ReadFileTool extends BaseTool<"read_file"> {
109109
const { handleError, pushToolResult } = callbacks
110110
const fileEntries = params.files
111111
const modelInfo = task.api.getModel().info
112-
const protocol = resolveToolProtocol(task.apiConfiguration, modelInfo, task.apiConfiguration.apiProvider)
112+
const state = await task.providerRef.deref()?.getState()
113+
const protocol = resolveToolProtocol(
114+
task.apiConfiguration,
115+
modelInfo,
116+
task.apiConfiguration.apiProvider,
117+
state?.experiments,
118+
)
113119
const useNative = isNativeProtocol(protocol)
114120

115121
if (!fileEntries || fileEntries.length === 0) {

src/core/tools/__tests__/applyDiffTool.experiment.spec.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,6 @@ describe("applyDiffTool experiment routing", () => {
156156
})
157157

158158
it("should use class-based tool when native protocol is enabled regardless of experiment", async () => {
159-
// Enable native protocol
160-
const vscode = await import("vscode")
161-
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue({
162-
get: vi.fn().mockReturnValue(TOOL_PROTOCOL.NATIVE),
163-
} as any)
164-
165159
// Update model to support native tools
166160
mockCline.api.getModel = vi.fn().mockReturnValue({
167161
id: "test-model",
@@ -176,6 +170,7 @@ describe("applyDiffTool experiment routing", () => {
176170
mockProvider.getState.mockResolvedValue({
177171
experiments: {
178172
[EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF]: true,
173+
[EXPERIMENT_IDS.NATIVE_TOOL_CALLING]: true, // Enable native tool calling experiment
179174
},
180175
})
181176
;(applyDiffToolClass.handle as any).mockResolvedValue(undefined)

src/core/webview/generateSystemPrompt.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web
7070
const canUseBrowserTool = modelSupportsBrowser && modeSupportsBrowser && (browserToolEnabled ?? true)
7171

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

7575
const systemPrompt = await SYSTEM_PROMPT(
7676
provider.context,

src/package.json

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -436,15 +436,6 @@
436436
"minimum": 1,
437437
"maximum": 200,
438438
"description": "%settings.codeIndex.embeddingBatchSize.description%"
439-
},
440-
"roo-cline.toolProtocol": {
441-
"type": "string",
442-
"enum": [
443-
"xml",
444-
"native"
445-
],
446-
"default": "xml",
447-
"description": "%settings.toolProtocol.description%"
448439
}
449440
}
450441
}

0 commit comments

Comments
 (0)