Skip to content

[Bug]: parallelToolCalls=false only sends parallel_tool_calls:false for the NVIDIA NIM baseUrl; ignored for all other opted-out providers #1650

Description

@TooSpace

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

  1. Configure an OpenAI-compatible openai-chat provider (e.g. a Kimi-K3 gateway) with "parallelToolCalls": false.
  2. Send a Codex turn that naturally invites two independent tool calls (e.g. "read file A and list dir B").
  3. Observe the upstream returns 2 parallel tool_calls chunks in the same choice; parallel_tool_calls: false was never sent.
  4. 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)

  1. Send body.parallel_tool_calls = false whenever provider.parallelToolCalls === false, regardless of baseUrl; or
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirksproxyHTTP proxy, routing, reverse-proxy / management authtoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions