Files
OmniRoute/tests/unit/executor-codex-responses-lite-translated-path.test.ts
Diego Rodrigues de Sa e Souza 71093eda77 fix(codex): keep parallel_tool_calls:false on translated Responses Lite path (#11707) (#11984)
enforceCodexResponsesLiteParallelToolCalls() forces parallel_tool_calls:false
at the top of CodexExecutor.execute(), but transformRequest() early-returns
the body before its RESPONSES_API_ALLOWLIST field filter only when
_nativeCodexPassthrough is set. Any request that reaches the codex
executor via the translated (non-native-passthrough) path never gets that
flag, so the allowlist filter silently deleted parallel_tool_calls right
before the fetch body was sent, reproducing the reported upstream
rejection ('X-OpenAI-Internal-Codex-Responses-Lite requires
parallel_tool_calls to be false') for every model.

Add parallel_tool_calls to RESPONSES_API_ALLOWLIST so the value survives
the translated path too. Update the sibling #2608 allowlist test that
previously asserted parallel_tool_calls gets stripped like other Chat
Completions-only fields -- it is a legitimate Responses API field that
must now survive.

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-29 08:10:26 -03:00

75 lines
3.2 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { CodexExecutor } from "../../open-sse/executors/codex.ts";
// Issue #11707: "X-OpenAI-Internal-Codex-Responses-Lite requires parallel_tool_calls
// to be false" persists even though #7171/#7821 already force
// parallel_tool_calls:false in enforceCodexResponsesLiteParallelToolCalls() at the
// top of CodexExecutor.execute().
//
// Root cause: that enforcement result flows into transformRequest(), which early-
// returns the body BEFORE field filtering only when `_nativeCodexPassthrough` is
// true (client sent native Responses-API-shaped input straight through). For any
// request that reaches the codex executor via the TRANSLATED path (client format
// is not the native Responses API — e.g. Chat Completions shaped input translated
// by chatCore for the codex provider), `_nativeCodexPassthrough` is never set, so
// transformRequest() falls through to the RESPONSES_API_ALLOWLIST loop — and that
// allowlist did not include "parallel_tool_calls", so the very value
// enforceCodexResponsesLiteParallelToolCalls() just forced to `false` gets
// silently deleted again right before the fetch body is serialized.
//
// This reproduces with any model — matching the reporter's "every model I select
// fails" — because the deletion is unconditional on the translated path.
async function runLiteRequest(body: Record<string, unknown>): Promise<Record<string, unknown>[]> {
const executor = new CodexExecutor();
const originalFetch = globalThis.fetch;
const capturedBodies: Record<string, unknown>[] = [];
globalThis.fetch = async (_url, init) => {
capturedBodies.push(JSON.parse(String(init?.body || "{}")));
return new Response(JSON.stringify({ id: "resp_lite", object: "response" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
};
try {
await executor.execute({
model: String(body.model),
body,
stream: true,
credentials: { accessToken: "codex-token" },
clientHeaders: { "X-OpenAI-Internal-Codex-Responses-Lite": "true" },
});
} finally {
globalThis.fetch = originalFetch;
}
return capturedBodies;
}
test("#11707: Responses Lite parallel_tool_calls:false survives the TRANSLATED (non-native-passthrough) codex path", async () => {
// No `_nativeCodexPassthrough` marker — this is how a request arrives when the
// client's own format isn't detected as native OpenAI Responses (e.g. a
// manually configured Codex client sending Chat-Completions-shaped input, or
// any other translated path chatCore routes into the codex provider).
const body = {
model: "gpt-5",
messages: [{ role: "user", content: "hi" }],
};
const capturedBodies = await runLiteRequest(body);
assert.equal(
capturedBodies[0].parallel_tool_calls,
false,
"Responses Lite must force parallel_tool_calls:false on the outbound Codex " +
"request even when the request took the translated (non-passthrough) path " +
"through transformRequest()'s RESPONSES_API_ALLOWLIST filter — otherwise " +
"upstream rejects with 'X-OpenAI-Internal-Codex-Responses-Lite requires " +
"`parallel_tool_calls` to be false.' (#11707)"
);
});