Skip to content
Draft
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
49 changes: 45 additions & 4 deletions src/utils/__tests__/resolveToolProtocol.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)", () => {
Expand Down Expand Up @@ -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",
}
Expand All @@ -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 = {}
Expand Down
25 changes: 17 additions & 8 deletions src/utils/resolveToolProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -246,6 +256,31 @@ export const OpenAICompatible = ({
)}
</div>

{/* Tool Protocol Selector - for third-party API providers that may not support native tool calling */}
<div>
<label className="block font-medium mb-1">{t("settings:toolProtocol.label")}</label>
<Select
value={apiConfiguration.toolProtocol || "default"}
onValueChange={(value) => {
const newValue = value === "default" ? undefined : (value as ToolProtocol)
setApiConfigurationField("toolProtocol", newValue)
}}>
<SelectTrigger className="w-full">
<SelectValue placeholder={t("settings:common.select")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="default">
{t("settings:toolProtocol.default")} ({t("settings:toolProtocol.native")})
</SelectItem>
<SelectItem value={TOOL_PROTOCOL.XML}>{t("settings:toolProtocol.xml")}</SelectItem>
<SelectItem value={TOOL_PROTOCOL.NATIVE}>{t("settings:toolProtocol.native")}</SelectItem>
</SelectContent>
</Select>
<div className="text-sm text-vscode-descriptionForeground mt-1">
{t("settings:toolProtocol.description")}
</div>
</div>

<div className="flex flex-col gap-1">
<Checkbox
checked={apiConfiguration.enableReasoningEffort ?? false}
Expand Down
Loading