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
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",
"multipleNativeToolCalls",
] 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(),
multipleNativeToolCalls: z.boolean().optional(),
})

export type Experiments = z.infer<typeof experimentsSchema>
Expand Down
1 change: 0 additions & 1 deletion src/api/providers/openrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
allow_fallbacks: false,
},
}),
parallel_tool_calls: false, // Ensure only one tool call at a time
...(transforms && { transforms }),
...(reasoning && { reasoning }),
...(metadata?.tools && { tools: metadata.tools }),
Expand Down
34 changes: 26 additions & 8 deletions src/core/assistant-message/presentAssistantMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ export async function presentAssistantMessage(cline: Task) {
cline.presentAssistantMessageLocked = true
cline.presentAssistantMessageHasPendingUpdates = false

const cachedModelId = cline.api.getModel().id

if (cline.currentStreamingContentIndex >= cline.assistantMessageContent.length) {
// This may happen if the last content block was completed before
// streaming could finish. If streaming is finished, and we're out of
Expand Down Expand Up @@ -176,7 +174,8 @@ export async function presentAssistantMessage(cline: Task) {
return `[${block.name} for '${block.params.command}']`
case "read_file":
// Check if this model should use the simplified description
if (shouldUseSingleFileRead(cachedModelId)) {
const modelId = cline.api.getModel().id
if (shouldUseSingleFileRead(modelId)) {
return getSimpleReadFileToolDescription(block.name, block.params)
} else {
// Prefer native typed args when available; fall back to legacy params
Expand Down Expand Up @@ -311,6 +310,14 @@ export async function presentAssistantMessage(cline: Task) {
const toolCallId = (block as any).id
const toolProtocol = toolCallId ? TOOL_PROTOCOL.NATIVE : TOOL_PROTOCOL.XML

// Check experimental setting for multiple native tool calls
const provider = cline.providerRef.deref()
const state = await provider?.getState()
const isMultipleNativeToolCallsEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.MULTIPLE_NATIVE_TOOL_CALLS,
)

const pushToolResult = (content: ToolResponse) => {
if (toolProtocol === TOOL_PROTOCOL.NATIVE) {
// For native protocol, only allow ONE tool_result per tool call
Expand Down Expand Up @@ -366,10 +373,20 @@ export async function presentAssistantMessage(cline: Task) {
}
}

// Once a tool result has been collected, ignore all other tool
// uses since we should only ever present one tool result per
// message.
cline.didAlreadyUseTool = true
// For XML protocol: Only one tool per message is allowed
// For native protocol with experimental flag enabled: Multiple tools can be executed in sequence
// For native protocol with experimental flag disabled: Single tool per message (default safe behavior)
if (toolProtocol === TOOL_PROTOCOL.XML) {
// Once a tool result has been collected, ignore all other tool
// uses since we should only ever present one tool result per
// message (XML protocol only).
cline.didAlreadyUseTool = true
} else if (toolProtocol === TOOL_PROTOCOL.NATIVE && !isMultipleNativeToolCallsEnabled) {
// For native protocol with experimental flag disabled, enforce single tool per message
cline.didAlreadyUseTool = true
}
// If toolProtocol is NATIVE and isMultipleNativeToolCallsEnabled is true,
// allow multiple tool calls in sequence (don't set didAlreadyUseTool)
}

const askApproval = async (
Expand Down Expand Up @@ -626,7 +643,8 @@ export async function presentAssistantMessage(cline: Task) {
break
case "read_file":
// Check if this model should use the simplified single-file read tool
if (shouldUseSingleFileRead(cachedModelId)) {
const modelId = cline.api.getModel().id
if (shouldUseSingleFileRead(modelId)) {
await simpleReadFileTool(
cline,
block,
Expand Down

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

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

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

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

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

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

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

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

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

Loading
Loading