diff --git a/src/utils/__tests__/resolveToolProtocol.spec.ts b/src/utils/__tests__/resolveToolProtocol.spec.ts index 513a7eaa35..14c0edf4d9 100644 --- a/src/utils/__tests__/resolveToolProtocol.spec.ts +++ b/src/utils/__tests__/resolveToolProtocol.spec.ts @@ -8,12 +8,15 @@ describe("resolveToolProtocol", () => { /** * XML Protocol Deprecation: * - * XML tool protocol has been fully deprecated. All models now use Native - * tool calling. User preferences and model defaults are ignored. + * XML tool protocol has been deprecated for most providers. All models now + * use Native tool calling by default. However, for OpenAI Compatible providers, + * user preferences are still respected because third-party API proxies may not + * fully support native tool calling. * * Precedence: * 1. Locked Protocol (for resumed tasks that used XML) - * 2. Native (always, for all new tasks) + * 2. User preference for OpenAI Compatible provider (allows XML for compatibility) + * 3. Native (default for all other providers and new tasks) */ describe("Locked Protocol (Precedence Level 0 - Highest Priority)", () => { @@ -66,7 +69,7 @@ describe("resolveToolProtocol", () => { expect(result).toBe(TOOL_PROTOCOL.NATIVE) }) - it("should use native for OpenAI compatible provider", () => { + it("should use native for OpenAI compatible provider without user preference", () => { const settings: ProviderSettings = { apiProvider: "openai", } @@ -75,6 +78,44 @@ describe("resolveToolProtocol", () => { }) }) + describe("OpenAI Compatible Provider - User Preference Respected", () => { + it("should use XML when user explicitly sets toolProtocol to xml for openai provider", () => { + const settings: ProviderSettings = { + apiProvider: "openai", + toolProtocol: "xml", + } + const result = resolveToolProtocol(settings) + expect(result).toBe(TOOL_PROTOCOL.XML) + }) + + it("should use native when user explicitly sets toolProtocol to native for openai provider", () => { + const settings: ProviderSettings = { + apiProvider: "openai", + toolProtocol: "native", + } + const result = resolveToolProtocol(settings) + expect(result).toBe(TOOL_PROTOCOL.NATIVE) + }) + + it("should default to native when no toolProtocol is set for openai provider", () => { + const settings: ProviderSettings = { + apiProvider: "openai", + } + const result = resolveToolProtocol(settings) + expect(result).toBe(TOOL_PROTOCOL.NATIVE) + }) + + it("should respect locked protocol over user preference for openai provider", () => { + const settings: ProviderSettings = { + apiProvider: "openai", + toolProtocol: "xml", + } + // Locked protocol takes precedence + const result = resolveToolProtocol(settings, undefined, "native") + expect(result).toBe(TOOL_PROTOCOL.NATIVE) + }) + }) + describe("Edge Cases", () => { it("should handle missing provider name gracefully", () => { const settings: ProviderSettings = {} diff --git a/src/utils/resolveToolProtocol.ts b/src/utils/resolveToolProtocol.ts index 92041fbeaf..11872cae10 100644 --- a/src/utils/resolveToolProtocol.ts +++ b/src/utils/resolveToolProtocol.ts @@ -15,21 +15,23 @@ type ApiMessageForDetection = Anthropic.MessageParam & { * Resolve the effective tool protocol. * * **Deprecation Note (XML Protocol):** - * XML tool protocol has been deprecated. All models now use Native tool calling. - * User/profile preferences (`providerSettings.toolProtocol`) and model defaults - * (`modelInfo.defaultToolProtocol`) are ignored. + * XML tool protocol has been deprecated for most providers. All models now use + * Native tool calling by default. However, for OpenAI Compatible providers, + * user preferences are still respected because third-party API proxies may not + * fully support native tool calling. * * Precedence: * 1. Locked Protocol (task-level lock for resumed tasks - highest priority) - * 2. Native (always, for all new tasks) + * 2. User preference for OpenAI Compatible provider (allows XML for compatibility) + * 3. Native (default for all other providers and new tasks) * - * @param _providerSettings - The provider settings (toolProtocol field is ignored) + * @param providerSettings - The provider settings (toolProtocol field is respected for openai provider) * @param _modelInfo - Unused, kept for API compatibility * @param lockedProtocol - Optional task-locked protocol that takes absolute precedence * @returns The resolved tool protocol (either "xml" or "native") */ export function resolveToolProtocol( - _providerSettings: ProviderSettings, + providerSettings: ProviderSettings, _modelInfo?: unknown, lockedProtocol?: ToolProtocol, ): ToolProtocol { @@ -39,8 +41,15 @@ export function resolveToolProtocol( return lockedProtocol } - // 2. Always return Native protocol for new tasks - // All models now support native tools; XML is deprecated + // 2. For OpenAI Compatible provider, respect user preference + // Third-party API proxies may not fully support native tool calling, + // so users need the ability to select XML protocol as a workaround. + if (providerSettings.apiProvider === "openai" && providerSettings.toolProtocol) { + return providerSettings.toolProtocol + } + + // 3. Default to Native protocol for new tasks + // All models now support native tools; XML is deprecated for standard providers return TOOL_PROTOCOL.NATIVE } diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx index ad338d342a..b67a4ad965 100644 --- a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx +++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx @@ -8,14 +8,24 @@ import { type ModelInfo, type ReasoningEffort, type OrganizationAllowList, + type ToolProtocol, azureOpenAiDefaultApiVersion, openAiModelInfoSaneDefaults, + TOOL_PROTOCOL, } from "@roo-code/types" import { ExtensionMessage } from "@roo/ExtensionMessage" import { useAppTranslation } from "@src/i18n/TranslationContext" -import { Button, StandardTooltip } from "@src/components/ui" +import { + Button, + StandardTooltip, + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "@src/components/ui" import { convertHeadersToObject } from "../utils/headers" import { inputEventTransform, noTransform } from "../transforms" @@ -246,6 +256,31 @@ export const OpenAICompatible = ({ )} + {/* Tool Protocol Selector - for third-party API providers that may not support native tool calling */} +