feat: add openAiNativeToolsEnabled setting for OpenAI-Compatible provider

This adds a new setting that allows users to disable native tool calling
for the OpenAI-Compatible provider. When disabled, tools are not passed
to the API, allowing users with local providers that do not support
native tool calling to use XML-style tool invocation via the system
prompt instead.

The setting defaults to true (enabled) for backward compatibility.

Fixes #10875
This commit is contained in:
Roo Code 2026-01-22 03:36:02 +00:00
parent 2305888746
commit dd2e33c039
2 changed files with 41 additions and 17 deletions

View file

@ -245,6 +245,10 @@ const openAiSchema = baseProviderSettingsSchema.extend({
openAiStreamingEnabled: z.boolean().optional(),
openAiHostHeader: z.string().optional(), // Keep temporarily for backward compatibility during migration.
openAiHeaders: z.record(z.string(), z.string()).optional(),
// When true (default), native function calling is used. When false, tools are not passed
// to the API, allowing users with local providers that don't support native tool calling
// to use XML-style tool invocation via the system prompt instead.
openAiNativeToolsEnabled: z.boolean().optional(),
})
const ollamaSchema = baseProviderSettingsSchema.extend({

View file

@ -90,9 +90,11 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
const enabledR1Format = this.options.openAiR1FormatEnabled ?? false
const isAzureAiInference = this._isAzureAiInference(modelUrl)
const deepseekReasoner = modelId.includes("deepseek-reasoner") || enabledR1Format
// Default to true for backward compatibility - when false, tools are not passed to the API
const nativeToolsEnabled = this.options.openAiNativeToolsEnabled !== false
if (modelId.includes("o1") || modelId.includes("o3") || modelId.includes("o4")) {
yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages, metadata)
yield* this.handleO3FamilyMessage(modelId, systemPrompt, messages, metadata, nativeToolsEnabled)
return
}
@ -159,9 +161,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
stream: true as const,
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
...(reasoning && reasoning),
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
// Only include tools if native tool calling is enabled
...(nativeToolsEnabled
? {
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
}
: {}),
}
// Add max_tokens if needed
@ -226,10 +233,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
messages: deepseekReasoner
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
: [systemMessage, ...convertToOpenAiMessages(messages)],
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
// Only include tools if native tool calling is enabled
...(nativeToolsEnabled
? {
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
}
: {}),
}
// Add max_tokens if needed
@ -324,7 +335,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
modelId: string,
systemPrompt: string,
messages: Anthropic.Messages.MessageParam[],
metadata?: ApiHandlerCreateMessageMetadata,
metadata: ApiHandlerCreateMessageMetadata | undefined,
nativeToolsEnabled: boolean,
): ApiStream {
const modelInfo = this.getModel().info
const methodIsAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl)
@ -345,10 +357,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined,
temperature: undefined,
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
// Only include tools if native tool calling is enabled
...(nativeToolsEnabled
? {
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
}
: {}),
}
// O3 family models do not support the deprecated max_tokens parameter
@ -379,10 +395,14 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
],
reasoning_effort: modelInfo.reasoningEffort as "low" | "medium" | "high" | undefined,
temperature: undefined,
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
// Only include tools if native tool calling is enabled
...(nativeToolsEnabled
? {
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
}
: {}),
}
// O3 family models do not support the deprecated max_tokens parameter