diff --git a/src/utils/__tests__/resolveToolProtocol.spec.ts b/src/utils/__tests__/resolveToolProtocol.spec.ts index d5f12b0b22..3df2f7e9a4 100644 --- a/src/utils/__tests__/resolveToolProtocol.spec.ts +++ b/src/utils/__tests__/resolveToolProtocol.spec.ts @@ -317,4 +317,100 @@ describe("resolveToolProtocol", () => { expect(result).toBe(TOOL_PROTOCOL.XML) // Model default wins }) }) + + describe("OpenAI Compatible and Ollama Provider Special Handling", () => { + it("should allow native protocol for OpenAI Compatible provider even without model support", () => { + const settings: ProviderSettings = { + toolProtocol: "native", + apiProvider: "openai", + } + // No model info provided (unknown model) + const result = resolveToolProtocol(settings, undefined) + expect(result).toBe(TOOL_PROTOCOL.NATIVE) // User preference is respected + }) + + it("should allow native protocol for Ollama provider even without model support", () => { + const settings: ProviderSettings = { + toolProtocol: "native", + apiProvider: "ollama", + } + const modelInfo: ModelInfo = { + maxTokens: 4096, + contextWindow: 128000, + supportsPromptCache: false, + supportsNativeTools: false, // Model doesn't support native + } + const result = resolveToolProtocol(settings, modelInfo) + expect(result).toBe(TOOL_PROTOCOL.NATIVE) // User preference is respected for Ollama + }) + + it("should allow XML protocol for OpenAI Compatible provider when user chooses it", () => { + const settings: ProviderSettings = { + toolProtocol: "xml", + apiProvider: "openai", + } + const modelInfo: ModelInfo = { + maxTokens: 4096, + contextWindow: 128000, + supportsPromptCache: false, + supportsNativeTools: true, // Model supports native + defaultToolProtocol: "native", + } + const result = resolveToolProtocol(settings, modelInfo) + expect(result).toBe(TOOL_PROTOCOL.XML) // User preference is respected + }) + + it("should use model default for OpenAI Compatible when no user preference", () => { + const settings: ProviderSettings = { + apiProvider: "openai", + } + const modelInfo: ModelInfo = { + maxTokens: 4096, + contextWindow: 128000, + supportsPromptCache: false, + supportsNativeTools: true, + defaultToolProtocol: "native", + } + const result = resolveToolProtocol(settings, modelInfo) + expect(result).toBe(TOOL_PROTOCOL.NATIVE) // Model default is used + }) + + it("should fall back to XML for OpenAI Compatible when no preference and no model info", () => { + const settings: ProviderSettings = { + apiProvider: "openai", + } + const result = resolveToolProtocol(settings, undefined) + expect(result).toBe(TOOL_PROTOCOL.XML) // XML fallback + }) + + it("should still enforce support check for non-OpenAI/Ollama providers", () => { + const settings: ProviderSettings = { + toolProtocol: "native", + apiProvider: "anthropic", // Not OpenAI or Ollama + } + const modelInfo: ModelInfo = { + maxTokens: 4096, + contextWindow: 128000, + supportsPromptCache: false, + supportsNativeTools: false, // Model doesn't support native + } + const result = resolveToolProtocol(settings, modelInfo) + expect(result).toBe(TOOL_PROTOCOL.XML) // Falls back to XML for non-OpenAI/Ollama + }) + + it("should allow native for Ollama with undefined model support", () => { + const settings: ProviderSettings = { + toolProtocol: "native", + apiProvider: "ollama", + } + const modelInfo: ModelInfo = { + maxTokens: 4096, + contextWindow: 128000, + supportsPromptCache: false, + // supportsNativeTools is undefined + } + const result = resolveToolProtocol(settings, modelInfo) + expect(result).toBe(TOOL_PROTOCOL.NATIVE) // User preference is respected for Ollama + }) + }) }) diff --git a/src/utils/resolveToolProtocol.ts b/src/utils/resolveToolProtocol.ts index 4150442a79..be4e26fd6f 100644 --- a/src/utils/resolveToolProtocol.ts +++ b/src/utils/resolveToolProtocol.ts @@ -15,17 +15,32 @@ import type { ProviderSettings, ModelInfo } from "@roo-code/types" * @returns The resolved tool protocol (either "xml" or "native") */ export function resolveToolProtocol(providerSettings: ProviderSettings, modelInfo?: ModelInfo): ToolProtocol { - // If model doesn't support native tools, return XML immediately - // Treat undefined as unsupported (only allow native when explicitly true) - if (modelInfo?.supportsNativeTools !== true) { - return TOOL_PROTOCOL.XML - } + // Special handling for OpenAI Compatible and Ollama providers: + // Allow user to override tool protocol even for unknown models + const isUserConfigurableProvider = + providerSettings.apiProvider === "openai" || providerSettings.apiProvider === "ollama" // 1. User Preference - Per-Profile (explicit profile setting, highest priority) if (providerSettings.toolProtocol) { + // For user-configurable providers, always respect the user's choice + if (isUserConfigurableProvider) { + return providerSettings.toolProtocol + } + + // For other providers, only use native if model supports it + if (providerSettings.toolProtocol === TOOL_PROTOCOL.NATIVE && modelInfo?.supportsNativeTools !== true) { + return TOOL_PROTOCOL.XML + } + return providerSettings.toolProtocol } + // If model doesn't support native tools and it's not a user-configurable provider, return XML + // Treat undefined as unsupported (only allow native when explicitly true) + if (modelInfo?.supportsNativeTools !== true && !isUserConfigurableProvider) { + return TOOL_PROTOCOL.XML + } + // 2. Model Default - model's preferred protocol if (modelInfo?.defaultToolProtocol) { return modelInfo.defaultToolProtocol diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index c31f57f46f..b512b617f4 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -423,8 +423,13 @@ const ApiOptions = ({ // 3. XML fallback const defaultProtocol = selectedModelInfo?.defaultToolProtocol || TOOL_PROTOCOL.XML - // Show the tool protocol selector when model supports native tools - const showToolProtocolSelector = selectedModelInfo?.supportsNativeTools === true + // Show the tool protocol selector when: + // 1. Model explicitly supports native tools, OR + // 2. Provider is OpenAI Compatible or Ollama (to allow user control for custom models) + const showToolProtocolSelector = + selectedModelInfo?.supportsNativeTools === true || + selectedProvider === "openai" || + selectedProvider === "ollama" // Convert providers to SearchableSelect options const providerOptions = useMemo(() => {