Skip to content

Commit dbaaef7

Browse files
authored
Remove experimental setting for native tool calls (#9333)
1 parent c5b18c1 commit dbaaef7

30 files changed

+91
-520
lines changed

packages/types/src/experiment.ts

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

1817
export const experimentIdsSchema = z.enum(experimentIds)
@@ -29,7 +28,6 @@ export const experimentsSchema = z.object({
2928
preventFocusDisruption: z.boolean().optional(),
3029
imageGeneration: z.boolean().optional(),
3130
runSlashCommand: z.boolean().optional(),
32-
nativeToolCalling: z.boolean().optional(),
3331
})
3432

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

src/core/assistant-message/presentAssistantMessage.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,7 @@ export async function presentAssistantMessage(cline: Task) {
281281
let hasToolResult = false
282282

283283
// 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-
)
284+
const toolProtocol = resolveToolProtocol(cline.apiConfiguration, cline.api.getModel().info)
291285
const isNative = isNativeProtocol(toolProtocol)
292286
const toolCallId = (block as any).id
293287

@@ -518,13 +512,7 @@ export async function presentAssistantMessage(cline: Task) {
518512
await checkpointSaveAndMark(cline)
519513

520514
// Check if native protocol is enabled - if so, always use single-file class-based tool
521-
const state = await cline.providerRef.deref()?.getState()
522-
const applyDiffToolProtocol = resolveToolProtocol(
523-
cline.apiConfiguration,
524-
cline.api.getModel().info,
525-
cline.apiConfiguration.apiProvider,
526-
state?.experiments,
527-
)
515+
const applyDiffToolProtocol = resolveToolProtocol(cline.apiConfiguration, cline.api.getModel().info)
528516
if (isNativeProtocol(applyDiffToolProtocol)) {
529517
await applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
530518
askApproval,

src/core/task/Task.ts

Lines changed: 10 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -411,12 +411,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
411411
// Initialize the assistant message parser only for XML protocol.
412412
// For native protocol, tool calls come as tool_call chunks, not XML.
413413
// experiments is always provided via TaskOptions (defaults to experimentDefault in provider)
414-
const toolProtocol = resolveToolProtocol(
415-
this.apiConfiguration,
416-
this.api.getModel().info,
417-
this.apiConfiguration.apiProvider,
418-
experimentsConfig,
419-
)
414+
const toolProtocol = resolveToolProtocol(this.apiConfiguration, this.api.getModel().info)
420415
this.assistantMessageParser = toolProtocol === "xml" ? new AssistantMessageParser() : undefined
421416

422417
this.messageQueueService = new MessageQueueService()
@@ -1271,12 +1266,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
12711266
)
12721267
const modelInfo = this.api.getModel().info
12731268
const state = await this.providerRef.deref()?.getState()
1274-
const toolProtocol = resolveToolProtocol(
1275-
this.apiConfiguration,
1276-
modelInfo,
1277-
this.apiConfiguration.apiProvider,
1278-
state?.experiments,
1279-
)
1269+
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo)
12801270
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName, toolProtocol))
12811271
}
12821272

@@ -1420,12 +1410,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
14201410
// conversations with tool uses and no tool schema.
14211411
// For native protocol, we preserve tool_use and tool_result blocks as they're expected by the API.
14221412
const state = await this.providerRef.deref()?.getState()
1423-
const protocol = resolveToolProtocol(
1424-
this.apiConfiguration,
1425-
this.api.getModel().info,
1426-
this.apiConfiguration.apiProvider,
1427-
state?.experiments,
1428-
)
1413+
const protocol = resolveToolProtocol(this.apiConfiguration, this.api.getModel().info)
14291414
const useNative = isNativeProtocol(protocol)
14301415

14311416
// Only convert tool blocks to text for XML protocol
@@ -1803,12 +1788,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
18031788
} else {
18041789
const modelInfo = this.api.getModel().info
18051790
const state = await this.providerRef.deref()?.getState()
1806-
const toolProtocol = resolveToolProtocol(
1807-
this.apiConfiguration,
1808-
modelInfo,
1809-
this.apiConfiguration.apiProvider,
1810-
state?.experiments,
1811-
)
1791+
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo)
18121792
nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed(toolProtocol) }]
18131793
this.consecutiveMistakeCount++
18141794
}
@@ -2456,12 +2436,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
24562436
// Check if we're using native protocol
24572437
const state = await this.providerRef.deref()?.getState()
24582438
const isNative = isNativeProtocol(
2459-
resolveToolProtocol(
2460-
this.apiConfiguration,
2461-
this.api.getModel().info,
2462-
this.apiConfiguration.apiProvider,
2463-
state?.experiments,
2464-
),
2439+
resolveToolProtocol(this.apiConfiguration, this.api.getModel().info),
24652440
)
24662441

24672442
if (isNative) {
@@ -2602,12 +2577,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
26022577
if (!didToolUse) {
26032578
const modelInfo = this.api.getModel().info
26042579
const state = await this.providerRef.deref()?.getState()
2605-
const toolProtocol = resolveToolProtocol(
2606-
this.apiConfiguration,
2607-
modelInfo,
2608-
this.apiConfiguration.apiProvider,
2609-
state?.experiments,
2610-
)
2580+
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo)
26112581
this.userMessageContent.push({ type: "text", text: formatResponse.noToolsUsed(toolProtocol) })
26122582
this.consecutiveMistakeCount++
26132583
}
@@ -2634,14 +2604,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
26342604
// user messages (which would cause tool_result validation errors).
26352605
let state = await this.providerRef.deref()?.getState()
26362606
if (
2637-
isNativeProtocol(
2638-
resolveToolProtocol(
2639-
this.apiConfiguration,
2640-
this.api.getModel().info,
2641-
this.apiConfiguration.apiProvider,
2642-
state?.experiments,
2643-
),
2644-
) &&
2607+
isNativeProtocol(resolveToolProtocol(this.apiConfiguration, this.api.getModel().info)) &&
26452608
this.apiConversationHistory.length > 0
26462609
) {
26472610
const lastMessage = this.apiConversationHistory[this.apiConversationHistory.length - 1]
@@ -2707,14 +2670,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
27072670
// For native protocol, re-add the user message we removed
27082671
// Reuse the state variable from above
27092672
if (
2710-
isNativeProtocol(
2711-
resolveToolProtocol(
2712-
this.apiConfiguration,
2713-
this.api.getModel().info,
2714-
this.apiConfiguration.apiProvider,
2715-
state?.experiments,
2716-
),
2717-
)
2673+
isNativeProtocol(resolveToolProtocol(this.apiConfiguration, this.api.getModel().info))
27182674
) {
27192675
await this.addToApiConversationHistory({
27202676
role: "user",
@@ -2813,12 +2769,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
28132769
const canUseBrowserTool = modelSupportsBrowser && modeSupportsBrowser && (browserToolEnabled ?? true)
28142770

28152771
// Resolve the tool protocol based on profile, model, and provider settings
2816-
const toolProtocol = resolveToolProtocol(
2817-
apiConfiguration ?? this.apiConfiguration,
2818-
modelInfo,
2819-
(apiConfiguration ?? this.apiConfiguration)?.apiProvider,
2820-
experiments,
2821-
)
2772+
const toolProtocol = resolveToolProtocol(apiConfiguration ?? this.apiConfiguration, modelInfo)
28222773

28232774
return SYSTEM_PROMPT(
28242775
provider.context,
@@ -3057,12 +3008,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
30573008
// 1. Tool protocol is set to NATIVE
30583009
// 2. Model supports native tools
30593010
const modelInfo = this.api.getModel().info
3060-
const toolProtocol = resolveToolProtocol(
3061-
this.apiConfiguration,
3062-
modelInfo,
3063-
this.apiConfiguration.apiProvider,
3064-
state?.experiments,
3065-
)
3011+
const toolProtocol = resolveToolProtocol(this.apiConfiguration, modelInfo)
30663012
const shouldIncludeTools = toolProtocol === TOOL_PROTOCOL.NATIVE && (modelInfo.supportsNativeTools ?? false)
30673013

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

src/core/tools/MultiApplyDiffTool.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,7 @@ 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()
67-
const toolProtocol = resolveToolProtocol(
68-
cline.apiConfiguration,
69-
cline.api.getModel().info,
70-
cline.apiConfiguration.apiProvider,
71-
state?.experiments,
72-
)
65+
const toolProtocol = resolveToolProtocol(cline.apiConfiguration, cline.api.getModel().info)
7366
if (isNativeProtocol(toolProtocol)) {
7467
return applyDiffToolClass.handle(cline, block as ToolUse<"apply_diff">, {
7568
askApproval,
@@ -80,6 +73,8 @@ export async function applyDiffTool(
8073
}
8174

8275
// Check if MULTI_FILE_APPLY_DIFF experiment is enabled
76+
const provider = cline.providerRef.deref()
77+
const state = await provider?.getState()
8378
if (provider && state) {
8479
const isMultiFileApplyDiffEnabled = experiments.isEnabled(
8580
state.experiments ?? {},

src/core/tools/ReadFileTool.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,7 @@ 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 state = await task.providerRef.deref()?.getState()
113-
const protocol = resolveToolProtocol(
114-
task.apiConfiguration,
115-
modelInfo,
116-
task.apiConfiguration.apiProvider,
117-
state?.experiments,
118-
)
112+
const protocol = resolveToolProtocol(task.apiConfiguration, modelInfo)
119113
const useNative = isNativeProtocol(protocol)
120114

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

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,22 +155,22 @@ describe("applyDiffTool experiment routing", () => {
155155
expect(applyDiffToolClass.handle).not.toHaveBeenCalled()
156156
})
157157

158-
it("should use class-based tool when native protocol is enabled regardless of experiment", async () => {
159-
// Update model to support native tools
158+
it("should use class-based tool when model defaults to native protocol", async () => {
159+
// Update model to support native tools and default to native protocol
160160
mockCline.api.getModel = vi.fn().mockReturnValue({
161161
id: "test-model",
162162
info: {
163163
maxTokens: 4096,
164164
contextWindow: 128000,
165165
supportsPromptCache: false,
166-
supportsNativeTools: true, // Enable native tools support
166+
supportsNativeTools: true, // Model supports native tools
167+
defaultToolProtocol: "native", // Model defaults to native protocol
167168
},
168169
})
169170

170171
mockProvider.getState.mockResolvedValue({
171172
experiments: {
172173
[EXPERIMENT_IDS.MULTI_FILE_APPLY_DIFF]: true,
173-
[EXPERIMENT_IDS.NATIVE_TOOL_CALLING]: true, // Enable native tool calling experiment
174174
},
175175
})
176176
;(applyDiffToolClass.handle as any).mockResolvedValue(undefined)
@@ -184,7 +184,7 @@ describe("applyDiffTool experiment routing", () => {
184184
mockRemoveClosingTag,
185185
)
186186

187-
// When native protocol is enabled, should always use class-based tool
187+
// When native protocol is used, should always use class-based tool
188188
expect(applyDiffToolClass.handle).toHaveBeenCalledWith(mockCline, mockBlock, {
189189
askApproval: mockAskApproval,
190190
handleError: mockHandleError,

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, experiments)
73+
const toolProtocol = resolveToolProtocol(apiConfiguration, modelInfo)
7474

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

src/shared/__tests__/experiments.spec.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,6 @@ describe("experiments", () => {
2323
})
2424
})
2525

26-
describe("NATIVE_TOOL_CALLING", () => {
27-
it("is configured correctly", () => {
28-
expect(EXPERIMENT_IDS.NATIVE_TOOL_CALLING).toBe("nativeToolCalling")
29-
expect(experimentConfigsMap.NATIVE_TOOL_CALLING).toMatchObject({
30-
enabled: false,
31-
})
32-
})
33-
})
34-
3526
describe("isEnabled", () => {
3627
it("returns false when POWER_STEERING experiment is not enabled", () => {
3728
const experiments: Record<ExperimentId, boolean> = {
@@ -40,7 +31,6 @@ describe("experiments", () => {
4031
preventFocusDisruption: false,
4132
imageGeneration: false,
4233
runSlashCommand: false,
43-
nativeToolCalling: false,
4434
}
4535
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
4636
})
@@ -52,7 +42,6 @@ describe("experiments", () => {
5242
preventFocusDisruption: false,
5343
imageGeneration: false,
5444
runSlashCommand: false,
55-
nativeToolCalling: false,
5645
}
5746
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(true)
5847
})
@@ -64,7 +53,6 @@ describe("experiments", () => {
6453
preventFocusDisruption: false,
6554
imageGeneration: false,
6655
runSlashCommand: false,
67-
nativeToolCalling: false,
6856
}
6957
expect(Experiments.isEnabled(experiments, EXPERIMENT_IDS.POWER_STEERING)).toBe(false)
7058
})

src/shared/experiments.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export const EXPERIMENT_IDS = {
66
PREVENT_FOCUS_DISRUPTION: "preventFocusDisruption",
77
IMAGE_GENERATION: "imageGeneration",
88
RUN_SLASH_COMMAND: "runSlashCommand",
9-
NATIVE_TOOL_CALLING: "nativeToolCalling",
109
} as const satisfies Record<string, ExperimentId>
1110

1211
type _AssertExperimentIds = AssertEqual<Equals<ExperimentId, Values<typeof EXPERIMENT_IDS>>>
@@ -23,7 +22,6 @@ export const experimentConfigsMap: Record<ExperimentKey, ExperimentConfig> = {
2322
PREVENT_FOCUS_DISRUPTION: { enabled: false },
2423
IMAGE_GENERATION: { enabled: false },
2524
RUN_SLASH_COMMAND: { enabled: false },
26-
NATIVE_TOOL_CALLING: { enabled: false },
2725
}
2826

2927
export const experimentDefault = Object.fromEntries(

0 commit comments

Comments
 (0)