From ac0c694dff474ad6313c5469abd831cfa77516f9 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 29 Dec 2025 02:02:09 +0000 Subject: [PATCH 1/2] feat(openai): add setting to disable native tool calling for models that do not support it - Add openAiDisableNativeTools setting to provider-settings schema - Modify OpenAiHandler.getModel() to respect the new setting - Expand DeepSeek reasoner detection to include deepseek-r1 pattern - Add UI checkbox in OpenAI Compatible settings - Add translations for the new setting in all 18 supported locales This addresses the issue where DeepSeek-R1 and similar models return 400 errors when native function calling (tools) is included in the request. Users can now explicitly disable native tools via a setting, giving them control when model name detection is insufficient (e.g., custom models or proxies). Closes #10368 --- packages/types/src/provider-settings.ts | 1 + src/api/providers/openai.ts | 11 +++++++++++ .../settings/providers/OpenAICompatible.tsx | 12 ++++++++++++ webview-ui/src/i18n/locales/ca/settings.json | 2 ++ webview-ui/src/i18n/locales/de/settings.json | 2 ++ webview-ui/src/i18n/locales/en/settings.json | 2 ++ webview-ui/src/i18n/locales/es/settings.json | 2 ++ webview-ui/src/i18n/locales/fr/settings.json | 2 ++ webview-ui/src/i18n/locales/hi/settings.json | 2 ++ webview-ui/src/i18n/locales/id/settings.json | 2 ++ webview-ui/src/i18n/locales/it/settings.json | 2 ++ webview-ui/src/i18n/locales/ja/settings.json | 2 ++ webview-ui/src/i18n/locales/ko/settings.json | 2 ++ webview-ui/src/i18n/locales/nl/settings.json | 2 ++ webview-ui/src/i18n/locales/pl/settings.json | 2 ++ webview-ui/src/i18n/locales/pt-BR/settings.json | 2 ++ webview-ui/src/i18n/locales/ru/settings.json | 2 ++ webview-ui/src/i18n/locales/tr/settings.json | 2 ++ webview-ui/src/i18n/locales/vi/settings.json | 2 ++ webview-ui/src/i18n/locales/zh-CN/settings.json | 2 ++ webview-ui/src/i18n/locales/zh-TW/settings.json | 2 ++ 21 files changed, 60 insertions(+) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 95fbcf10c98..d040766a380 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -244,6 +244,7 @@ const openAiSchema = baseProviderSettingsSchema.extend({ openAiApiKey: z.string().optional(), openAiLegacyFormat: z.boolean().optional(), openAiR1FormatEnabled: z.boolean().optional(), + openAiDisableNativeTools: z.boolean().optional(), openAiModelId: z.string().optional(), openAiCustomModelInfo: modelInfoSchema.nullish(), openAiUseAzure: z.boolean().optional(), diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 7ab2b00524b..99a37026f46 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -292,12 +292,23 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl override getModel() { const id = this.options.openAiModelId ?? "" + const enabledR1Format = this.options.openAiR1FormatEnabled ?? false + const disableNativeTools = this.options.openAiDisableNativeTools ?? false + + // Detect DeepSeek reasoner models that don't support native tool calling + const isDeepSeekReasoner = + id.includes("deepseek-reasoner") || id.includes("deepseek-r1") || enabledR1Format + // Ensure OpenAI-compatible models default to supporting native tool calling. // This is required for [`Task.attemptApiRequest()`](src/core/task/Task.ts:3817) to // include tool definitions in the request. + // However, disable native tools for DeepSeek reasoner models or when explicitly disabled + // by the user, as these models don't support function calling and will return 400 errors. const info: ModelInfo = { ...NATIVE_TOOL_DEFAULTS, ...(this.options.openAiCustomModelInfo ?? openAiModelInfoSaneDefaults), + // Override: Disable native tools if user setting is enabled or model is a DeepSeek reasoner + ...((disableNativeTools || isDeepSeekReasoner) && { supportsNativeTools: false }), } const params = getModelParams({ format: "openai", modelId: id, model: info, settings: this.options }) return { id, info, ...params } diff --git a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx index ad338d342ab..2f1519a2c62 100644 --- a/webview-ui/src/components/settings/providers/OpenAICompatible.tsx +++ b/webview-ui/src/components/settings/providers/OpenAICompatible.tsx @@ -155,6 +155,18 @@ export const OpenAICompatible = ({ onChange={handleInputChange("openAiR1FormatEnabled", noTransform)} openAiR1FormatEnabled={apiConfiguration?.openAiR1FormatEnabled ?? false} /> +
+
+ + {t("settings:modelInfo.disableNativeTools")} + +
+

+ {t("settings:modelInfo.disableNativeToolsTips")} +

+
Date: Tue, 30 Dec 2025 02:42:48 +0000 Subject: [PATCH 2/2] test(openai): add test coverage for openAiDisableNativeTools setting --- src/api/providers/__tests__/openai.spec.ts | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts index 4469efd4d17..3f8fc8b2bd4 100644 --- a/src/api/providers/__tests__/openai.spec.ts +++ b/src/api/providers/__tests__/openai.spec.ts @@ -585,6 +585,56 @@ describe("OpenAiHandler", () => { expect(model.id).toBe("") expect(model.info).toBeDefined() }) + + it("should default to supportsNativeTools true for standard models", () => { + const model = handler.getModel() + expect(model.info.supportsNativeTools).toBe(true) + }) + + it("should disable native tools when openAiDisableNativeTools is true", () => { + const handlerWithDisabledTools = new OpenAiHandler({ + ...mockOptions, + openAiDisableNativeTools: true, + }) + const model = handlerWithDisabledTools.getModel() + expect(model.info.supportsNativeTools).toBe(false) + }) + + it("should disable native tools for deepseek-r1 model", () => { + const handlerWithDeepSeekR1 = new OpenAiHandler({ + ...mockOptions, + openAiModelId: "deepseek-r1", + }) + const model = handlerWithDeepSeekR1.getModel() + expect(model.info.supportsNativeTools).toBe(false) + }) + + it("should disable native tools for deepseek-reasoner model", () => { + const handlerWithDeepSeekReasoner = new OpenAiHandler({ + ...mockOptions, + openAiModelId: "deepseek-reasoner", + }) + const model = handlerWithDeepSeekReasoner.getModel() + expect(model.info.supportsNativeTools).toBe(false) + }) + + it("should disable native tools when openAiR1FormatEnabled is true", () => { + const handlerWithR1Format = new OpenAiHandler({ + ...mockOptions, + openAiR1FormatEnabled: true, + }) + const model = handlerWithR1Format.getModel() + expect(model.info.supportsNativeTools).toBe(false) + }) + + it("should disable native tools for deepseek-r1 variants (e.g., deepseek-r1-distill-qwen-32b)", () => { + const handlerWithDeepSeekR1Variant = new OpenAiHandler({ + ...mockOptions, + openAiModelId: "deepseek-r1-distill-qwen-32b", + }) + const model = handlerWithDeepSeekR1Variant.getModel() + expect(model.info.supportsNativeTools).toBe(false) + }) }) describe("Azure AI Inference Service", () => {