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.

Activity

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

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