From 14dbc1088c649a5afb5a2f18db4c5354ce47f298 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Wed, 19 Nov 2025 10:25:46 +0000 Subject: [PATCH] fix: respect toolProtocol configuration in OpenAI provider - Add support for user-configured toolProtocol setting in OpenAI compatible provider - Check toolProtocol configuration before enabling native tools - Allows users to force XML protocol for models like Gemini and ik_llamacpp - Fixes #9385 --- src/api/providers/openai.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 79d65e82e2..6e6a5d1283 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -8,11 +8,13 @@ import { openAiModelInfoSaneDefaults, DEEP_SEEK_DEFAULT_TEMPERATURE, OPENAI_AZURE_AI_INFERENCE_PATH, + TOOL_PROTOCOL, } from "@roo-code/types" import type { ApiHandlerOptions } from "../../shared/api" import { XmlMatcher } from "../../utils/xml-matcher" +import { resolveToolProtocol } from "../../utils/resolveToolProtocol" import { convertToOpenAiMessages } from "../transform/openai-format" import { convertToR1Format } from "../transform/r1-format" @@ -94,6 +96,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl const deepseekReasoner = modelId.includes("deepseek-reasoner") || enabledR1Format const ark = modelUrl.includes(".volces.com") + // Check if we should force XML protocol based on user configuration + const toolProtocol = resolveToolProtocol(this.options, modelInfo) + const shouldUseNativeTools = toolProtocol === TOOL_PROTOCOL.NATIVE && metadata?.tools + if (modelId.includes("o1") || modelId.includes("o3") || modelId.includes("o4")) { yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages, metadata) return @@ -164,8 +170,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl stream: true as const, ...(isGrokXAI ? {} : { stream_options: { include_usage: true } }), ...(reasoning && reasoning), - ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...(shouldUseNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(shouldUseNativeTools && metadata?.tool_choice && { tool_choice: metadata.tool_choice }), } // Add max_tokens if needed @@ -261,8 +267,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl : enabledLegacyFormat ? [systemMessage, ...convertToSimpleMessages(messages)] : [systemMessage, ...convertToOpenAiMessages(messages)], - ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...(shouldUseNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(shouldUseNativeTools && metadata?.tool_choice && { tool_choice: metadata.tool_choice }), } // Add max_tokens if needed @@ -362,6 +368,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl const modelInfo = this.getModel().info const methodIsAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl) + // O3 family models should always use native tools when available + // (they don't support XML protocol) + const shouldUseNativeTools = metadata?.tools + if (this.options.openAiStreamingEnabled ?? true) { const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl) @@ -378,8 +388,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ...(isGrokXAI ? {} : { stream_options: { include_usage: true } }), reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined, temperature: undefined, - ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...(shouldUseNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(shouldUseNativeTools && metadata?.tool_choice && { tool_choice: metadata.tool_choice }), } // O3 family models do not support the deprecated max_tokens parameter @@ -410,8 +420,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ], reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined, temperature: undefined, - ...(metadata?.tools && { tools: this.convertToolsForOpenAI(metadata.tools) }), - ...(metadata?.tool_choice && { tool_choice: metadata.tool_choice }), + ...(shouldUseNativeTools && { tools: this.convertToolsForOpenAI(metadata.tools) }), + ...(shouldUseNativeTools && metadata?.tool_choice && { tool_choice: metadata.tool_choice }), } // O3 family models do not support the deprecated max_tokens parameter