Skip to content

feat: openai-compatible-strict-reasoning (1/2) - #1124

Open
myk1yt wants to merge 11 commits into
Zoo-Code-Org:mainfrom
myk1yt:pr/b05a-strict-reasoning-v2
Open

feat: openai-compatible-strict-reasoning (1/2)#1124
myk1yt wants to merge 11 commits into
Zoo-Code-Org:mainfrom
myk1yt:pr/b05a-strict-reasoning-v2

Conversation

@myk1yt

@myk1yt myk1yt commented Aug 4, 2026

Copy link
Copy Markdown

Stack Position

  • Feature Branch: feat/openai-compatible-strict-reasoning (1/2) + fix/mimo-parallel-tool-call-policy (shared 1/2 root)
  • Stage: 1/2
  • Depends on: None

Description

image

Full Feature Description

  • Feature Branch: feat/openai-compatible-strict-reasoning
  • Feature Name: OpenAI-Compatible Strict Reasoning and Provider Cost
  • Purpose: Resolves the problem where, despite differing levels of strict JSON schema and reasoning effort support across OpenAI-compatible endpoints, request control is scattered per provider, and cost is calculated as zero or inaccurately due to usage field differences. Enables users to explicitly opt in to strict tool schema and extended reasoning effort on supported endpoints, and normalizes response usage according to provider contracts to consistently display actual cost.
  • Full Change Description: B05a adds provider settings contracts, cached settings UI, strict schema opt-in, reasoning effort values, and base request shaping. B17 normalizes conditional fields, cached tokens, missing/zero values, and price lookup for OpenAI, OpenAI-compatible, Anthropic Vertex, and Qwen family usage. B05a is also shared as the foundation for the MiMo chain, but in this chain B17 is a sibling outcome that does not depend on B12.
  • Impact Scope: Affects provider-settings.ts, base-openai-compatible-provider.ts, base-provider.ts, OpenAICompatible.tsx, openai.ts, openai-compatible.ts, anthropic-vertex.ts, qwen-code.ts.
  • Errors and Edge Cases: Strict mode is opt-in and does not change existing schemas when disabled. Strict/reasoning fields are not sent to unsupported providers. MCP schemas are not aggressively hardened. The settings UI only modifies cachedState before saving. Cost calculation treats missing fields as unknown or zero per provider contract and does not produce negative tokens. Cached input/output tokens and provider-specific price units are not double-counted. B17 does not change request payload or tool-call policy.
  • Testing Method: Run B05a's settings serialization, schema conversion, opt-in/omitted-field, request fixture, and UI/locale tests, and B17's provider-specific usage/cost fixtures. Manually compare requests with strict toggle off vs. on, and feed fixed usage fixtures into OpenAI-compatible and non-OpenAI providers, comparing against documented calculation formulas and costs.

Why Split Into 17 PRs

Instead of submitting this feature as a single unified PR, it was split into individual PRs because as code size grows, safely reviewing a PR becomes very difficult. The feature was broken into mutually exclusive individual PRs so that each can be reviewed independently.

What This PR Specifically Changes

Adds provider-neutral strict schema opt-in, reasoning effort setting, cached settings UI, and OpenAI-compatible base request shaping. Does not include MiMo-specific enforcement or provider cost calculation.

Included Files

  • packages/types/src/provider-settings.ts
  • src/api/providers/base-openai-compatible-provider.ts
  • src/api/providers/base-provider.ts
  • src/api/providers/openai.ts
  • webview-ui/src/components/settings/providers/OpenAICompatible.tsx
  • Related locale and direct tests

Exclusion Scope

  • MiMo-specific capability/stream enforcement
  • Tool-call retention policy
  • Provider cost normalization changes
  • All items in the common removal rules

Summary by CodeRabbit

  • New Features
    • Added an optional setting to enable strict tool schemas for OpenAI-compatible providers.
    • Strict mode improves schema validation while preserving MCP tool behavior.
    • The setting is saved per provider profile and defaults to disabled for compatibility.
    • Added localized descriptions across supported languages.
  • Bug Fixes
    • Requests without tools no longer include unnecessary parallel tool-call options.
    • Improved handling of incomplete or empty tool schemas.
  • Tests
    • Expanded coverage for strict and non-strict tool conversion, provider profiles, and request behavior.

Zoo (VP) added 9 commits August 2, 2026 10:02
…penAI Compatible provider

- Add openAiToolStrictMode boolean to provider settings (profile-scoped, default false)
- Add strict toggle checkbox in OpenAICompatible settings UI
- BaseProvider.convertToolsForOpenAI now accepts strictMode parameter
  - strictMode=true: strict:true + hardened schema
  - strictMode=false: strict:false + best-effort original schema
  - MCP tools: always strict:false regardless of setting
- Wire setting into all 4 openai.ts request paths
- Fix reasoning effort unsafe cast, add xhigh and max values
- Make parallel_tool_calls conditional on tools being present
Merge debris left strictToolSchemas/strictToolSchemasDescription twice
in the modelInfo object; JSON.parse silently kept the last occurrence.
Add scripts/find-dup-json-keys.js to detect duplicate sibling keys;
scan of all 18 locales shows en was the only affected file.
openAiToolStrictMode is honored by all OpenAI-protocol providers in a
profile, but the checkbox only exists under the OpenAI Compatible
section. Extend strictToolSchemasDescription to state the setting is
saved per profile and applies to other OpenAI-protocol providers.
Non-en locales hold untranslated English text for this key, so they
get the clarification appended as an English parenthetical.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7369169b-9474-4229-b3ab-0208d8c1acef

📥 Commits

Reviewing files that changed from the base of the PR and between 5ac1ec0 and 84facbf.

📒 Files selected for processing (20)
  • src/api/providers/__tests__/openai.spec.ts
  • src/api/providers/deepseek.ts
  • webview-ui/src/i18n/locales/ca/settings.json
  • webview-ui/src/i18n/locales/de/settings.json
  • webview-ui/src/i18n/locales/en/settings.json
  • webview-ui/src/i18n/locales/es/settings.json
  • webview-ui/src/i18n/locales/fr/settings.json
  • webview-ui/src/i18n/locales/hi/settings.json
  • webview-ui/src/i18n/locales/id/settings.json
  • webview-ui/src/i18n/locales/it/settings.json
  • webview-ui/src/i18n/locales/ja/settings.json
  • webview-ui/src/i18n/locales/ko/settings.json
  • webview-ui/src/i18n/locales/nl/settings.json
  • webview-ui/src/i18n/locales/pl/settings.json
  • webview-ui/src/i18n/locales/pt-BR/settings.json
  • webview-ui/src/i18n/locales/ru/settings.json
  • webview-ui/src/i18n/locales/tr/settings.json
  • webview-ui/src/i18n/locales/vi/settings.json
  • webview-ui/src/i18n/locales/zh-CN/settings.json
  • webview-ui/src/i18n/locales/zh-TW/settings.json
🚧 Files skipped from review as they are similar to previous changes (20)
  • webview-ui/src/i18n/locales/es/settings.json
  • src/api/providers/tests/openai.spec.ts
  • src/api/providers/deepseek.ts
  • webview-ui/src/i18n/locales/tr/settings.json
  • webview-ui/src/i18n/locales/ja/settings.json
  • webview-ui/src/i18n/locales/id/settings.json
  • webview-ui/src/i18n/locales/en/settings.json
  • webview-ui/src/i18n/locales/hi/settings.json
  • webview-ui/src/i18n/locales/fr/settings.json
  • webview-ui/src/i18n/locales/pl/settings.json
  • webview-ui/src/i18n/locales/ko/settings.json
  • webview-ui/src/i18n/locales/vi/settings.json
  • webview-ui/src/i18n/locales/de/settings.json
  • webview-ui/src/i18n/locales/nl/settings.json
  • webview-ui/src/i18n/locales/ru/settings.json
  • webview-ui/src/i18n/locales/zh-TW/settings.json
  • webview-ui/src/i18n/locales/ca/settings.json
  • webview-ui/src/i18n/locales/it/settings.json
  • webview-ui/src/i18n/locales/zh-CN/settings.json
  • webview-ui/src/i18n/locales/pt-BR/settings.json

📝 Walkthrough

Walkthrough

The change adds an optional provider setting for OpenAI strict tool schemas. Provider handlers pass the setting into tool conversion. MCP tools remain non-strict. OpenAI requests omit parallel_tool_calls when no tools exist.

Changes

Strict tool schema support

Layer / File(s) Summary
Settings contract and UI
packages/types/src/..., webview-ui/src/components/settings/providers/OpenAICompatible.tsx, webview-ui/src/i18n/locales/*/settings.json
The OpenAI profile accepts openAiToolStrictMode. The UI exposes a checkbox and localized descriptions. Tests cover optional, default, and provider-specific deserialization.
Tool conversion behavior
src/api/providers/base-provider.ts, src/api/providers/__tests__/base-provider.spec.ts
Tool conversion supports strict and non-strict modes. MCP tools retain their schemas with strict: false. Strict mode hardens nested schemas and array item schemas.
Provider strict-mode wiring
src/api/providers/base-openai-compatible-provider.ts, src/api/providers/{deepseek,friendli,kenari,lite-llm,lm-studio,openai-compatible,opencode-go,openrouter}.ts
OpenAI-compatible providers pass openAiToolStrictMode to tool conversion and default to false when unset.
OpenAI request construction
src/api/providers/openai.ts, src/api/providers/__tests__/openai.spec.ts
Streaming and non-streaming requests conditionally include parallel_tool_calls. O-series requests use computed reasoning parameters. Tests update Azure and O3 request expectations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: edelauna

Sequence Diagram(s)

sequenceDiagram
  participant SettingsUI
  participant Provider
  participant BaseProvider
  participant OpenAIAPI
  SettingsUI->>Provider: Set openAiToolStrictMode
  Provider->>BaseProvider: Convert tools with strictMode
  BaseProvider-->>Provider: Return converted tools
  Provider->>OpenAIAPI: Send request with tools and conditional parallel_tool_calls
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the feature and testing scope, but it omits the required issue link, checklist, and several template sections. Add the approved GitHub issue number, complete the pre-submission checklist, and provide the required test, visual snapshot, documentation, and reviewer details.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title identifies the OpenAI-compatible strict reasoning feature and matches the primary changes in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/api/providers/__tests__/openai.spec.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/api/providers/deepseek.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.87097% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...c/api/providers/base-openai-compatible-provider.ts 33.33% 1 Missing and 1 partial ⚠️
src/api/providers/openai.ts 81.81% 1 Missing and 1 partial ⚠️
src/api/providers/openai-compatible.ts 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot added the awaiting-review PR changes are ready and waiting for maintainer re-review label Aug 4, 2026
@myk1yt
myk1yt force-pushed the pr/b05a-strict-reasoning-v2 branch 3 times, most recently from 77e7207 to a3b22a7 Compare August 4, 2026 20:40
@myk1yt
myk1yt force-pushed the pr/b05a-strict-reasoning-v2 branch from ce96757 to 5ac1ec0 Compare August 6, 2026 20:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/api/providers/deepseek.ts (1)

124-124: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add DeepSeek request assertions for strict-mode defaulting.

Line 124 changes the serialized tool schema, but src/api/providers/__tests__/deepseek.spec.ts only verifies streamed reasoning and tool-call output at Lines 693-726. Add focused tests for openAiToolStrictMode: true, false, and undefined. Assert strict schemas when enabled and non-strict schemas when disabled or unset.

Run:

pnpm --dir src exec vitest run api/providers/__tests__/deepseek.spec.ts

As per coding guidelines, cover true and false/unset defaulting cases at the narrowest test layer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/deepseek.ts` at line 124, Add focused DeepSeek request
tests around the provider flow that serializes tools via convertToolsForOpenAI,
covering openAiToolStrictMode set to true, false, and undefined. Assert that
true produces strict tool schemas, while false and unset produce non-strict
schemas, and keep the coverage at the narrowest DeepSeek test layer.

Source: Coding guidelines

src/api/providers/__tests__/openai.spec.ts (1)

894-894: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add negative case coverage for the O3 request contracts.

The O3 test suite lacks focused cases for the omitted/false branches:

  • Tools present: parallel_tool_calls is included.
  • parallelToolCalls: false: the explicit false value is preserved.
  • openAiToolStrictMode: true, false, and unset behavior.
  • Reasoning disabled/unset: reasoning_effort and related params are omitted.
  • O-series requests without tools, when optional metadata is supported.

Run the narrow path test after adding or covering these cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/__tests__/openai.spec.ts` at line 894, Add focused O3
request-contract coverage in the existing OpenAI provider tests around the
current parallel_tool_calls assertion: verify tools include parallel_tool_calls,
false preserves an explicit false value, openAiToolStrictMode handles
true/false/unset, disabled or unset reasoning omits reasoning_effort and related
parameters, and O-series requests without tools work when optional metadata is
supported. Run the narrow openai.spec.ts test path.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/providers/base-provider.ts`:
- Around line 33-40: Update the tool schema construction logic in the base
provider to omit function.strict when strictMode is disabled, while preserving
original schemas and MCP behavior. Set strict: true only for non-MCP tools when
strictMode is enabled, and revise default-mode tests to expect strict to be
undefined.

In `@src/api/providers/openai.ts`:
- Around line 372-377: Update both O3-family request objects to set
parallel_tool_calls only when metadata.tools exists and contains at least one
tool, matching the non-O3 branches; otherwise omit the property. Apply the guard
wherever parallel_tool_calls is assigned in the O3 request construction paths.

In `@webview-ui/src/i18n/locales/en/settings.json`:
- Line 1044: Correct the strictToolSchemasDescription text so strict mode
describes generated function-call arguments matching the JSON Schema, not tool
outputs. Apply the equivalent localized wording update in
webview-ui/src/i18n/locales/en/settings.json:1044-1044,
ca/settings.json:969-969, de/settings.json:969-969, tr/settings.json:969-969,
vi/settings.json:969-969, zh-CN/settings.json:969-969, and
zh-TW/settings.json:996-996.

---

Nitpick comments:
In `@src/api/providers/__tests__/openai.spec.ts`:
- Line 894: Add focused O3 request-contract coverage in the existing OpenAI
provider tests around the current parallel_tool_calls assertion: verify tools
include parallel_tool_calls, false preserves an explicit false value,
openAiToolStrictMode handles true/false/unset, disabled or unset reasoning omits
reasoning_effort and related parameters, and O-series requests without tools
work when optional metadata is supported. Run the narrow openai.spec.ts test
path.

In `@src/api/providers/deepseek.ts`:
- Line 124: Add focused DeepSeek request tests around the provider flow that
serializes tools via convertToolsForOpenAI, covering openAiToolStrictMode set to
true, false, and undefined. Assert that true produces strict tool schemas, while
false and unset produce non-strict schemas, and keep the coverage at the
narrowest DeepSeek test layer.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f47cb076-ef6f-4103-9af4-c12636597553

📥 Commits

Reviewing files that changed from the base of the PR and between f149073 and 5ac1ec0.

📒 Files selected for processing (35)
  • packages/types/src/__tests__/provider-settings.test.ts
  • packages/types/src/provider-settings.ts
  • progress.txt
  • src/api/providers/__tests__/base-provider.spec.ts
  • src/api/providers/__tests__/openai.spec.ts
  • src/api/providers/base-openai-compatible-provider.ts
  • src/api/providers/base-provider.ts
  • src/api/providers/deepseek.ts
  • src/api/providers/friendli.ts
  • src/api/providers/kenari.ts
  • src/api/providers/lite-llm.ts
  • src/api/providers/lm-studio.ts
  • src/api/providers/openai-compatible.ts
  • src/api/providers/openai.ts
  • src/api/providers/opencode-go.ts
  • src/api/providers/openrouter.ts
  • webview-ui/src/components/settings/providers/OpenAICompatible.tsx
  • webview-ui/src/i18n/locales/ca/settings.json
  • webview-ui/src/i18n/locales/de/settings.json
  • webview-ui/src/i18n/locales/en/settings.json
  • webview-ui/src/i18n/locales/es/settings.json
  • webview-ui/src/i18n/locales/fr/settings.json
  • webview-ui/src/i18n/locales/hi/settings.json
  • webview-ui/src/i18n/locales/id/settings.json
  • webview-ui/src/i18n/locales/it/settings.json
  • webview-ui/src/i18n/locales/ja/settings.json
  • webview-ui/src/i18n/locales/ko/settings.json
  • webview-ui/src/i18n/locales/nl/settings.json
  • webview-ui/src/i18n/locales/pl/settings.json
  • webview-ui/src/i18n/locales/pt-BR/settings.json
  • webview-ui/src/i18n/locales/ru/settings.json
  • webview-ui/src/i18n/locales/tr/settings.json
  • webview-ui/src/i18n/locales/vi/settings.json
  • webview-ui/src/i18n/locales/zh-CN/settings.json
  • webview-ui/src/i18n/locales/zh-TW/settings.json
💤 Files with no reviewable changes (1)
  • progress.txt

Comment on lines +33 to +40
* When `strictMode` is false (default), non-MCP function tools get
* `strict: false` and their original best-effort schemas are preserved
* without hardening. This is semantically consistent: `strict: false`
* should not imply strict-schema transformations.
*
* MCP tools are ALWAYS `strict: false` with original parameters preserved,
* regardless of the `strictMode` setting, because MCP schemas may contain
* optional properties that must remain optional.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

pnpm --dir src exec vitest run api/providers/__tests__/base-provider.spec.ts

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 1468


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)base-provider\.(ts|tsx)$|base-provider.*spec|eslint-suppressions\.json|package\.json$' || true

echo
echo "== base-provider outline =="
ast-grep outline src/api/providers/base-provider.ts 2>/dev/null || true

echo
echo "== relevant base-provider lines =="
sed -n '1,140p' src/api/providers/base-provider.ts | cat -n

echo
echo "== tests mentioning strict / convertToolsForOpenAI =="
rg -n "strictMode|convertToolsForOpenAI|strict:|strictMode" src/api/providers src -t ts -t tsx || true

echo
echo "== local npm dependency files for nock =="
rg -n '"nock"|`@types/nock`' package.json src package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null || true

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 6790


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== call sites for convertToolsForOpenAI =="
rg -n "convertToolsForOpenAI|StrictMode|strictMode|createMessage\\(" src/api/providers src -g '*.ts' -g '*.tsx' || true

echo
echo "== base-provider tests outline/content =="
wc -l src/api/providers/__tests__/base-provider.spec.ts
sed -n '1,260p' src/api/providers/__tests__/base-provider.spec.ts | cat -n

echo
echo "== imports from base-provider =="
rg -n "import .*BaseProvider|from .*base-provider" src -g '*.ts' -g '*.tsx' || true

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== base-provider test relevant sections =="
sed -n '1,120p' src/api/providers/__tests__/base-provider.spec.ts | cat -n
echo "---"
sed -n '170,420p' src/api/providers/__tests__/base-provider.spec.ts | cat -n

echo
echo "== openAiToolStrictMode option definitions =="
rg -n "openAiToolStrictMode|StrictMode" src packages apps webview-ui -g '*.ts' -g '*.tsx' -g '*.json' --glob '!**/__tests__/**' --glob '!**/*.lock' | head -200 || true

echo
echo "== behavioral simulation of current tool conversion =="
node - <<'JS'
function isMcpTool(name) {
	return String(name).startsWith("mcp--")
}
function convertToolsForOpenAI(tools, strictMode = false) {
	return tools.map((tool) => {
		if (tool.type !== "function") {
			return tool
		}
		const isMcp = isMcpTool(tool.function.name)
		if (isMcp) {
			return {
				...tool,
				function: {
					...tool.function,
					strict: false,
					parameters: tool.function.parameters,
				},
			}
		}
		if (strictMode) {
			return {
				...tool,
				function: {
					...tool.function,
					strict: true,
					parameters: tool.function.parameters,
				},
			}
		}
		return {
			...tool,
			function: {
				...tool.function,
				strict: false,
				parameters: tool.function.parameters,
			},
		}
	})
}
const tools = [
	{ type: "function", function: { name: "normal", parameters: { type: "object" } } },
	{ type: "function", function: { name: "mcp--server.tool", parameters: { type: "object" } } },
]
for (const strictMode of [false, true]) {
	console.log(`strictMode=${strictMode}:`, JSON.stringify(convertToolsForOpenAI(tools, strictMode)))
}
JS

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 15336


🌐 Web query:

OpenAI function calling strict mode strict true False undefined parameters behavior

💡 Result:

In OpenAI's function calling API, the strict parameter controls Structured Outputs, which guarantees that the model's generated arguments adhere exactly to the provided JSON schema [1][2]. Strict Mode Behavior: - strict: true: Forces the model to adhere strictly to the provided JSON schema [1]. This requires that all fields in the schema are marked as required and additionalProperties is set to false for every object [1]. If these conditions are not met, the API will reject the request with an error [1][3]. - strict: false (or omitted): Uses "best-effort" function calling [1]. The model attempts to follow the schema but is not constrained by the strict Structured Outputs mechanism [1]. - Undefined/Omitted: When strict is omitted, the default behavior depends on the API implementation [1]. Generally, for Chat Completions, it defaults to non-strict (best-effort) [1]. However, some API paths may attempt to automatically normalize schemas into strict mode if possible; if they fail to do so, they fall back to non-strict mode [1]. Key Requirements for strict: true: 1. All fields within the schema's properties must be listed in the required array [1][4]. 2. additionalProperties must be set to false for every object in the parameters schema [1]. If you use strict: true and your schema does not comply with these requirements, the API will return a validation error [1][3]. Structured Outputs works by using the schema to construct a grammar that constrains the token-level generation process, ensuring output compliance [4].

Citations:


🌐 Web query:

OpenAI function calling tool_calls strict false omitted supported by API providers tool strict

💡 Result:

In the OpenAI API, the strict parameter is an optional field within the tool definition used to enable Structured Outputs [1][2]. When configuring function calling, the behavior of omitting this parameter depends on the specific API endpoint being used [1][2]: - Chat Completions API: Function calling remains non-strict by default [1][2]. - Responses API: This API attempts to normalize your schema into strict mode when possible [1][2]. If the schema can be made compatible, it will enable strict mode; otherwise, it falls back to non-strict, best-effort function calling [1][2]. When such a fallback occurs, the tool response will reflect strict: false [1][2]. To explicitly opt out of strict mode and ensure non-strict, best-effort function calling, you should explicitly set strict: false in your tool definition [1][2]. Enabling strict mode (strict: true) requires meeting specific schema constraints, including setting additionalProperties to false for each object in the parameters and ensuring all properties are marked as required [1][2]. If these requirements are not met, requests with strict: true will be rejected [1][2]. While documentation and standard behavior are generally reliable, some users have reported discrepancies in specific implementations, such as the Responses API or certain gateway configurations, where omitting the strict parameter can occasionally lead to unexpected behavior (e.g., incorrect parameter population) [3][4]. Consequently, explicit configuration is often recommended for consistent results [1][4].

Citations:


🌐 Web query:

Chat Completions API strict false tool definition not supported by provider OpenAI function calling strict false strict omitted

💡 Result:

In the OpenAI Chat Completions API, the strict parameter is used to enable "Structured Outputs" for function calling [1][2]. When you set strict: true, the model enforces strict adherence to the provided JSON schema, requiring that all fields be marked as required and that additionalProperties be set to false [1][3]. Regarding your query on strict: false or omitting the parameter: 1. Supported Behavior: In the standard Chat Completions API, you do not need to set strict: false. If you omit the strict parameter, the API defaults to non-strict, best-effort function calling [1]. This is the standard behavior where the model attempts to follow the schema but is not constrained by the strict requirements of Structured Outputs [1]. 2. "Not Supported" Errors: If you receive an error when attempting to use strict: false or when providing strict in the wrong location, it is typically because the API is strict about its own schema [4][5]. For instance: - Placement: The strict parameter must be placed inside the function object definition (within the tools array), not at the top level of the tool or as a sibling to the function object [4]. - Invalid Parameters: While strict: true is an explicit opt-in, some API versions or wrappers may not explicitly recognize strict: false as a valid parameter if it is being passed incorrectly or to an endpoint that expects the parameter to be omitted entirely to signify non-strict mode [4][5]. 3. Comparison with Responses API: It is important to note that the newer Responses API handles strict differently than the Chat Completions API [6]. In the Responses API, omitting strict will attempt to normalize the schema into strict mode if possible [6]. In that specific API, if you wish to explicitly ensure non-strict behavior, you must set strict: false [6]. If you are seeing documentation or behavior regarding strict: false requirements, ensure you are not conflating the Chat Completions API with the newer Responses API [6]. Summary: For the Chat Completions API, if you want non-strict (best-effort) function calling, simply omit the strict parameter entirely [1]. If you are encountering an error with strict: false, it is likely because you are attempting to pass an unnecessary parameter that the endpoint does not expect or you have placed it in an incorrect location within the JSON structure [4][5].

Citations:


Omit strict when strict mode is disabled.

Default strictMode = false currently adds function.strict: false to every function tool. For non-strict OpenAI-compatible function calling, omit function.strict instead of sending a less-supported disabled value. Keep strict: true only for non-MCP tools when strictMode is enabled, and update the default-mode tests to expect strict to be undefined.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/base-provider.ts` around lines 33 - 40, Update the tool
schema construction logic in the base provider to omit function.strict when
strictMode is disabled, while preserving original schemas and MCP behavior. Set
strict: true only for non-MCP tools when strictMode is enabled, and revise
default-mode tests to expect strict to be undefined.

Comment on lines +372 to 377
...(reasoning && reasoning),
temperature: undefined,
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
tools: this.convertToolsForOpenAI(metadata?.tools),
tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file exists =="
git ls-files | grep -F 'src/api/providers/openai.ts' || true

echo "== relevant openai.ts sections =="
if [ -f src/api/providers/openai.ts ]; then
  nl -ba src/api/providers/openai.ts | sed -n '150,425p'
fi

echo "== search ALWAYS_AVAILABLE_TOOLS and parallel_tool_calls =="
rg -n "ALWAYS_AVAILABLE_TOOLS|parallel_tool_calls|parallelToolCalls|generate.*O-series|o1|o3|chat\.completions|tools" src/api/providers/openai.ts src || true

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 279


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant openai.ts sections (line-numbered with cat -n) =="
cat -n src/api/providers/openai.ts | sed -n '150,430p'

echo "== search key identifiers in repository =="
rg -n "ALWAYS_AVAILABLE_TOOLS|parallel_tool_calls|parallelToolCalls|parallel_tool" -S . || true

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 27137


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== openai.ts openAI handler call path and constant imports =="
cat -n src/api/providers/openai.ts | sed -n '1,160p'
echo "== openai.ts handleO-series call sites =="
rg -n "handleO3FamilyMessage|openAiModelId|o3|o1|openai" src/api/providers/openai.ts src/api/index.ts src || true

echo "== OpenAI provider tests around parallel_tool_calls and empty metadata =="
cat -n src/api/providers/__tests__/openai.spec.ts | sed -n '850,960p'

echo "== Tool filtering / ALWAYS behavior in validateToolUse =="
cat -n src/core/tools/validateToolUse.ts | sed -n '120,165p'

echo "== Deterministic probe: OpenAI source line behavior for metadata null/empty tools =="
python3 - <<'PY'
from pathlib import Path
p = Path("src/api/providers/openai.ts")
text = p.read_text()
checks = {
    "o3_stream_parallel_tool_calls_literal": "parallel_tool_calls: metadata?.parallelToolCalls ?? true," in text,
    "o3_nonstream_parallel_tool_calls_literal": "parallel_tool_calls: metadata?.parallelToolCalls ?? true," in text,
    "stream_conditional_guard": "?(metadata?.tools && metadata.tools.length > 0" in text or "? { parallel_tool_calls: metadata?.parallelToolCalls ?? true }" in text,
    "o3_comments_mark_tools_always": "// Tools are always present, but parallel_tool_calls still needs a tools check" in text,
}
print(checks)

import re
for label, body in [
    ("stream", re.search(r"if \(this\.options\.openAiStreamingEnabled \?\? true\) \{[\s\S]*?\}\s*else \{", text).group(0) if re.search(r"if \(this\.options\.openAiStreamingEnabled \?\? true\)", text) else ""),
]:
    # rough extraction for handleO3FamilyMessage only
    pass
PY

Repository: Zoo-Code-Org/Zoo-Code

Length of output: 50378


Guard parallel_tool_calls in both O3-family branches.

metadata is optional, but these requests still set parallel_tool_calls unconditionally. Match the non-O3 branches: omit it whenever metadata.tools is absent or empty. If O3 requests must always include tools, change the callers/contract so metadata.tools cannot be empty and add a regression test.

Proposed fix
-				parallel_tool_calls: metadata?.parallelToolCalls ?? true,
+				...(metadata?.tools && metadata.tools.length > 0
+					? { parallel_tool_calls: metadata.parallelToolCalls ?? true }
+					: {}),

Apply this change in both O3-family request objects.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
...(reasoning && reasoning),
temperature: undefined,
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
tools: this.convertToolsForOpenAI(metadata?.tools),
tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false),
tool_choice: metadata?.tool_choice,
parallel_tool_calls: metadata?.parallelToolCalls ?? true,
...(reasoning && reasoning),
temperature: undefined,
// Tools are always present (minimum ALWAYS_AVAILABLE_TOOLS)
tools: this.convertToolsForOpenAI(metadata?.tools, this.options.openAiToolStrictMode ?? false),
tool_choice: metadata?.tool_choice,
...(metadata?.tools && metadata.tools.length > 0
? { parallel_tool_calls: metadata.parallelToolCalls ?? true }
: {}),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/api/providers/openai.ts` around lines 372 - 377, Update both O3-family
request objects to set parallel_tool_calls only when metadata.tools exists and
contains at least one tool, matching the non-O3 branches; otherwise omit the
property. Apply the guard wherever parallel_tool_calls is assigned in the O3
request construction paths.

"useAzure": "Use Azure",
"azureApiVersion": "Set Azure API version",
"strictToolSchemas": "Strict tool schemas",
"strictToolSchemasDescription": "Enables strict mode for function tool schemas, ensuring tool outputs match the schema exactly. Some providers may not support strict mode. MCP tools are always kept non-strict regardless of this setting. This setting is saved per profile and also applies to other providers that use the OpenAI protocol within the same profile.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

OpenAI strict JSON mode function_call parameters tool outputs schema arguments API documentation

💡 Result:

In the OpenAI API, there is a clear distinction between JSON mode and Structured Outputs (which encompasses strict mode for function calling). While both ensure the model outputs valid, parsable JSON, Structured Outputs provides a formal guarantee that the output will strictly adhere to a provided JSON schema [1][2]. Key Concepts: 1. Structured Outputs (Strict Mode): When using function calling, you can enable strict adherence to a schema by setting the strict parameter to true within your function definition [3][4]. This ensures the model reliably follows the schema, rather than providing a "best-effort" response [4]. Requirements for strict: true: - All properties within the schema must be marked as required [4]. - additionalProperties must be explicitly set to false for every object in the schema [4]. - If these constraints are not met, the API will return an error [3][4]. 2. JSON Mode: JSON mode (enabled via response_format: { "type": "json_object" }) guarantees that the output is valid, parsable JSON [1][5]. However, unlike Structured Outputs, it does not guarantee that the output matches a specific schema [1][6]. It is primarily used when you want the model to respond in JSON format generally rather than strictly adhering to a defined structure [1]. 3. Function Calling vs. JSON Mode: - Function Calling: Automatically uses JSON-constrained sampling for arguments [5]. When you use strict: true with tools, it becomes a form of Structured Output [1][2]. - JSON Mode: An opt-in feature for regular Chat Completions that ensures the output is valid JSON [6]. It requires an explicit instruction (e.g., in the system message) to produce JSON, or the API will return an error [5]. In summary, for reliable schema-compliant output—whether for function arguments or model responses—you should use Structured Outputs (setting strict: true for tools or using json_schema response format) [1][2]. JSON mode should be reserved for cases where you only need syntactically correct JSON without strict schema enforcement [1][6].

Citations:


🌐 Web query:

site:platform.openai.com/docs/api-reference/chat/completions/function-calling true strict parameters tool outputs

💡 Result:

For Chat Completions function calling:

  • Set tools[].function.strict: true to enforce the function’s JSON Schema for generated arguments. Strict mode supports only a subset of JSON Schema. [1]
  • In strict mode, make every property required and use "additionalProperties": false; optional values can be represented with a union such as ["string", "null"]. [1]
  • The model returns a tool call with JSON-stringified arguments; your application executes the function and validates the arguments server-side.
  • Return the result as a new message:
{
  "role": "tool",
  "tool_call_id": "call_123",
  "content": "{\"result\": 42}"
}

content must be a string; JSON-encode structured output yourself. Then send the conversation—including the assistant tool-call message and tool result—back to Chat Completions for the final response. [1]

Example definition:

{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "Get weather for a location",
    "strict": true,
    "parameters": {
      "type": "object",
      "properties": {
        "location": { "type": "string" },
        "unit": { "type": ["string", "null"], "enum": ["celsius", "fahrenheit", null] }
      },
      "required": ["location", "unit"],
      "additionalProperties": false
    }
  }
}

Note: OpenAI’s newer Responses API represents tool results as function_call_output items with call_id and output, rather than Chat Completions’ role: "tool" messages. [2]

Sources:
[1] (platform.openai.com)
[2] (platform.openai.com)

Citations:


Correct the strict-mode behavior in all localized descriptions.

tools[].function.strict: true enforces the function’s JSON Schema for generated arguments, not tool outputs.

  • webview-ui/src/i18n/locales/en/settings.json#L1044-L1044: replace “tool outputs match the schema” with generated function-call arguments matching the schema.
  • Apply the equivalent correction in webview-ui/src/i18n/locales/ca/settings.json#L969-L969, de/settings.json#L969-L969, tr/settings.json#L969-L969, vi/settings.json#L969-L969, zh-CN/settings.json#L969-L969, and zh-TW/settings.json#L996-L996.
📍 Affects 7 files
  • webview-ui/src/i18n/locales/en/settings.json#L1044-L1044 (this comment)
  • webview-ui/src/i18n/locales/ca/settings.json#L969-L969
  • webview-ui/src/i18n/locales/de/settings.json#L969-L969
  • webview-ui/src/i18n/locales/tr/settings.json#L969-L969
  • webview-ui/src/i18n/locales/vi/settings.json#L969-L969
  • webview-ui/src/i18n/locales/zh-CN/settings.json#L969-L969
  • webview-ui/src/i18n/locales/zh-TW/settings.json#L996-L996
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webview-ui/src/i18n/locales/en/settings.json` at line 1044, Correct the
strictToolSchemasDescription text so strict mode describes generated
function-call arguments matching the JSON Schema, not tool outputs. Apply the
equivalent localized wording update in
webview-ui/src/i18n/locales/en/settings.json:1044-1044,
ca/settings.json:969-969, de/settings.json:969-969, tr/settings.json:969-969,
vi/settings.json:969-969, zh-CN/settings.json:969-969, and
zh-TW/settings.json:996-996.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting-review PR changes are ready and waiting for maintainer re-review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant