Client or integration
Codex App
Area
Proxy and routing
Summary
provider.parallelToolCalls === false only actually sends parallel_tool_calls: false on the wire when the provider's baseUrl is exactly https://integrate.api.nvidia.com/v1. Every other OpenAI-compatible provider that sets parallelToolCalls: false in its config silently gets no wire bit, so upstreams that default to parallel tool calls keep emitting multiple concurrent tool calls in one streamed turn.
For Kimi-family and some GLM-family gateways, the streamed multi-call turn then can't be assembled cleanly into Responses-shaped function-call events, and Codex fails the turn (previously surfaced as upstream response contained invalid tool calls; after the 2.14 null-tool_calls hardening, still a broken/incomplete multi-tool turn). Setting parallelToolCalls: false — which the config schema advertises as the way to opt out — has no effect for these providers.
What I expected
parallelToolCalls: false should pin parallel_tool_calls: false on the request for any opted-out provider, not just the hardcoded NVIDIA NIM host. The config field reads as provider-agnostic; the enforcement is host-specific.
Evidence (code path)
src/adapters/openai-chat.ts, request builder:
if (provider.parallelToolCalls === false) {
// NIM documents the Boolean defaulting to false and kimi rejects true; pin the
// wire bit so Codex cannot opt in via request.options. Other opted-out providers
// omit the field so strict OpenAI-compatible hosts never see an unsupported knob.
if (provider.baseUrl === "https://integrate.api.nvidia.com/v1") {
body.parallel_tool_calls = false;
}
} else if (provider.parallelToolCalls === true) {
body.parallel_tool_calls = parsed.options.parallelToolCalls !== false;
}
The comment's intent ("Other opted-out providers omit the field so strict OpenAI-compatible hosts never see an unsupported knob") is reasonable as a default, but it makes parallelToolCalls: false a no-op for every non-NVIDIA gateway that genuinely needs the wire bit. There's no per-provider way to force it short of matching the NVIDIA URL.
Reproduction
- Configure an OpenAI-compatible
openai-chat provider (e.g. a Kimi-K3 gateway) with "parallelToolCalls": false.
- Send a Codex turn that naturally invites two independent tool calls (e.g. "read file A and list dir B").
- Observe the upstream returns 2 parallel
tool_calls chunks in the same choice; parallel_tool_calls: false was never sent.
- As a control, send the exact same upstream body with
parallel_tool_calls: false added manually — the upstream then returns a single tool call.
Proposal (either is fine)
- Send
body.parallel_tool_calls = false whenever provider.parallelToolCalls === false, regardless of baseUrl; or
- Keep the NVIDIA-only default but add an explicit opt-in (e.g.
provider.forceParallelToolCallsFalse: true) so gateways that reject/ignore parallel calls can pin the bit without a URL match.
Version
opencodex 2.14.2 (ocx --version); running local service confirmed via GET /healthz.
Operating system
macOS 26.5.1 (25F80)
Provider and model
OpenAI-compatible openai-chat routed providers (Kimi-K3, GLM-5.2 families) on a self-hosted gateway. Not NVIDIA NIM. Reproducible with any openai-chat provider whose upstream emits parallel tool calls by default.
Client or integration
Codex App
Area
Proxy and routing
Summary
provider.parallelToolCalls === falseonly actually sendsparallel_tool_calls: falseon the wire when the provider'sbaseUrlis exactlyhttps://integrate.api.nvidia.com/v1. Every other OpenAI-compatible provider that setsparallelToolCalls: falsein its config silently gets no wire bit, so upstreams that default to parallel tool calls keep emitting multiple concurrent tool calls in one streamed turn.For Kimi-family and some GLM-family gateways, the streamed multi-call turn then can't be assembled cleanly into Responses-shaped function-call events, and Codex fails the turn (previously surfaced as
upstream response contained invalid tool calls; after the 2.14 null-tool_calls hardening, still a broken/incomplete multi-tool turn). SettingparallelToolCalls: false— which the config schema advertises as the way to opt out — has no effect for these providers.What I expected
parallelToolCalls: falseshould pinparallel_tool_calls: falseon the request for any opted-out provider, not just the hardcoded NVIDIA NIM host. The config field reads as provider-agnostic; the enforcement is host-specific.Evidence (code path)
src/adapters/openai-chat.ts, request builder:The comment's intent ("Other opted-out providers omit the field so strict OpenAI-compatible hosts never see an unsupported knob") is reasonable as a default, but it makes
parallelToolCalls: falsea no-op for every non-NVIDIA gateway that genuinely needs the wire bit. There's no per-provider way to force it short of matching the NVIDIA URL.Reproduction
openai-chatprovider (e.g. a Kimi-K3 gateway) with"parallelToolCalls": false.tool_callschunks in the same choice;parallel_tool_calls: falsewas never sent.parallel_tool_calls: falseadded manually — the upstream then returns a single tool call.Proposal (either is fine)
body.parallel_tool_calls = falsewheneverprovider.parallelToolCalls === false, regardless ofbaseUrl; orprovider.forceParallelToolCallsFalse: true) so gateways that reject/ignore parallel calls can pin the bit without a URL match.Version
opencodex 2.14.2 (
ocx --version); running local service confirmed viaGET /healthz.Operating system
macOS 26.5.1 (25F80)
Provider and model
OpenAI-compatible
openai-chatrouted providers (Kimi-K3, GLM-5.2 families) on a self-hosted gateway. Not NVIDIA NIM. Reproducible with anyopenai-chatprovider whose upstream emits parallel tool calls by default.