feat: add openAiStrictToolMode setting for OpenAI-compatible providers

This adds a new setting `openAiStrictToolMode` that allows users to disable
OpenAI strict mode for tool definitions. When strict mode is disabled:
- No `strict: true` is added to tool definitions
- No schema modifications (additionalProperties: false, all required) are made

This is useful for providers like kie.ai that use the Gemini 3 Pro model
and do not support OpenAI's strict mode for function calling.

The setting defaults to true for backward compatibility.

Closes #11011
This commit is contained in:
Roo Code 2026-01-27 18:47:28 +00:00
parent 17d3456e96
commit 4cdd126a47
5 changed files with 44 additions and 5 deletions

View file

@ -243,6 +243,9 @@ 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 enabled, adds strict: true to tool definitions and modifies schemas for OpenAI strict mode.
// Disable this for providers like kie.ai that don't support OpenAI's strict mode.
openAiStrictToolMode: z.boolean().optional(),
})
const ollamaSchema = baseProviderSettingsSchema.extend({

View file

@ -23,8 +23,12 @@ export abstract class BaseProvider implements ApiHandler {
* Converts an array of tools to be compatible with OpenAI's strict mode.
* Filters for function tools, applies schema conversion to their parameters,
* and ensures all tools have consistent strict: true values.
*
* @param tools - Array of tools to convert
* @param useStrictMode - When true (default), adds strict: true and modifies schemas for OpenAI strict mode.
* When false, skips strict mode modifications for providers that don't support it (e.g. kie.ai).
*/
protected convertToolsForOpenAI(tools: any[] | undefined): any[] | undefined {
protected convertToolsForOpenAI(tools: any[] | undefined, useStrictMode: boolean = true): any[] | undefined {
if (!tools) {
return undefined
}
@ -38,6 +42,12 @@ export abstract class BaseProvider implements ApiHandler {
// to preserve optional parameters from the MCP server schema
const isMcp = isMcpTool(tool.function.name)
// When strict mode is disabled (for providers like kie.ai), don't add strict: true
// and don't modify the schema
if (!useStrictMode) {
return tool
}
return {
...tool,
function: {

View file

@ -152,6 +152,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl)
// Strict tool mode is enabled by default for backward compatibility
// Disable for providers like kie.ai that don't support OpenAI's strict mode
const useStrictToolMode = this.options.openAiStrictToolMode !== false
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = {
model: modelId,
temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0),
@ -159,7 +163,7 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
stream: true as const,
...(isGrokXAI ? {} : { stream_options: { include_usage: true } }),
...(reasoning && reasoning),
tools: this.convertToolsForOpenAI(metadata?.tools),
tools: this.convertToolsForOpenAI(metadata?.tools, useStrictToolMode),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
}
@ -221,13 +225,17 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
yield this.processUsageMetrics(lastUsage, modelInfo)
}
} else {
// Strict tool mode is enabled by default for backward compatibility
// Disable for providers like kie.ai that don't support OpenAI's strict mode
const useStrictToolMode = this.options.openAiStrictToolMode !== false
const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
model: modelId,
messages: deepseekReasoner
? convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
: [systemMessage, ...convertToOpenAiMessages(messages)],
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
tools: this.convertToolsForOpenAI(metadata?.tools),
tools: this.convertToolsForOpenAI(metadata?.tools, useStrictToolMode),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
}
@ -329,6 +337,10 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
const modelInfo = this.getModel().info
const methodIsAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl)
// Strict tool mode is enabled by default for backward compatibility
// Disable for providers like kie.ai that don't support OpenAI's strict mode
const useStrictToolMode = this.options.openAiStrictToolMode !== false
if (this.options.openAiStreamingEnabled ?? true) {
const isGrokXAI = this._isGrokXAI(this.options.openAiBaseUrl)
@ -346,7 +358,7 @@ 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),
tools: this.convertToolsForOpenAI(metadata?.tools, useStrictToolMode),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
}
@ -380,7 +392,7 @@ 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),
tools: this.convertToolsForOpenAI(metadata?.tools, useStrictToolMode),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? false,
}

View file

@ -158,6 +158,16 @@ export const OpenAICompatible = ({
onChange={handleInputChange("openAiStreamingEnabled", noTransform)}>
{t("settings:modelInfo.enableStreaming")}
</Checkbox>
<div>
<Checkbox
checked={apiConfiguration?.openAiStrictToolMode ?? true}
onChange={handleInputChange("openAiStrictToolMode", noTransform)}>
{t("settings:providers.openAi.strictToolMode")}
</Checkbox>
<div className="text-sm text-vscode-descriptionForeground ml-6">
{t("settings:providers.openAi.strictToolModeDescription")}
</div>
</div>
<div>
<Checkbox
checked={apiConfiguration?.includeMaxTokens ?? true}

View file

@ -493,6 +493,10 @@
"learnMore": "Learn more about provider routing"
}
},
"openAi": {
"strictToolMode": "Enable strict tool mode",
"strictToolModeDescription": "Adds OpenAI-specific strict mode to tool definitions. Disable this for providers like kie.ai that don't support strict mode."
},
"customModel": {
"capabilities": "Configure the capabilities and pricing for your custom OpenAI-compatible model. Be careful when specifying the model capabilities, as they can affect how Roo Code performs.",
"maxTokens": {