feat: allow user-selectable tool format for OpenAI Compatible and Ollama providers

- Show tool protocol selector for OpenAI Compatible and Ollama providers regardless of model support
- Allow users to manually choose between XML and native tool formats for custom models
- Add special handling in resolveToolProtocol to respect user choice for these providers
- Add comprehensive tests for the new behavior

Fixes #9538
This commit is contained in:
Roo Code 2025-11-24 17:30:40 +00:00
parent a20d047417
commit 85d3ae4fb6
3 changed files with 123 additions and 7 deletions

View file

@ -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
})
})
})

View file

@ -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

View file

@ -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(() => {