Files
OmniRoute/tests/unit/openai-responses-verbosity.test.ts
Diego Rodrigues de Sa e Souza 280c27bf2d fix(sse): stop dropping tool_search and leaking OpenAI-only params in Responses->Chat translation (#7571)
* fix(sse): stop dropping tool_search and stop leaking OpenAI-only params in Responses->Chat translation (#7532, #7533)

#7532: `openai-responses.ts` unconditionally dropped `tool_search` when
downgrading a Responses-shaped request to Chat Completions, hiding the tool
from the model and breaking Codex's deferred/lazy tool-discovery protocol for
any provider that gets downgraded (e.g. built-in providers like opencode-go).
tool_search carries `execution: "client"` — the client resolves the call
locally regardless of wire shape — so it is now mapped to a normal Chat
function tool, mirroring the existing local_shell -> shell pattern in the
same file, instead of being silently discarded.

#7533: the same translator unconditionally copied two GPT-5/OpenAI-only
fields (`verbosity`, `prompt_cache_key`) into the translated Chat body
regardless of destination provider. A strict-protocol non-OpenAI upstream
(NVIDIA confirmed by the reporter) 400s on unrecognized top-level parameters.
Both fields are now gated on `credentials.provider === "openai"`, stripped
otherwise; the existing OpenAI-destined behavior (needed for #517's
prompt-caching fix) is preserved byte-identical via a dedicated sanity test.

Regression tests: tests/unit/tool-search-filtered-responses-to-chat-7532.test.ts,
tests/unit/verbosity-prompt-cache-key-provider-gate-7533.test.ts. Two existing
tests that encoded the old buggy contract (unconditional tool_search drop /
unconditional field leak with no credentials) were aligned to the corrected
contract: tests/unit/translator-openai-responses-req.test.ts,
tests/unit/openai-responses-verbosity.test.ts.

Gates run green: file-size, complexity, cognitive-complexity, typecheck:core,
lint (scoped to changed files), and the full touched-area unit test suite
(329 tests, 0 failures).

* fix(sse): keep prompt_cache_key/verbosity for the codex destination (#7533)

The #7533 provider gate allowlisted only "openai", but /v1/responses routes
EVERY request through this downgrade (handleResponsesCore ->
convertResponsesApiFormat) regardless of provider, and codex is an
OpenAI-operated upstream (chatgpt.com/backend-api/codex). Gating it out
stripped prompt_cache_key for Codex and silently re-broke the prompt-cache
affinity #517 exists to protect — with no test covering it.

Allowlist is now {openai, codex} and carries two #517 regression guards.
Non-OpenAI upstreams (NVIDIA) still get both fields stripped, per #7533.
2026-07-17 10:41:49 -03:00

72 lines
2.3 KiB
TypeScript

/**
* Verbosity mapping across the OpenAI Chat <-> Responses request translators.
*
* Chat Completions carries GPT-5 verbosity as top-level `verbosity`; the Responses API
* nests it as `text.verbosity`. These tests pin both directions so the hint is not lost
* when a request crosses formats (e.g. a Chat client routed to a Responses backend).
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
openaiToOpenAIResponsesRequest,
openaiResponsesToOpenAIRequest,
} from "../../open-sse/translator/request/openai-responses.ts";
function asRecord(value: unknown): Record<string, unknown> {
return value as Record<string, unknown>;
}
test("Chat -> Responses maps verbosity to text.verbosity", () => {
const out = asRecord(
openaiToOpenAIResponsesRequest(
"gpt-5.5",
{ model: "gpt-5.5", messages: [{ role: "user", content: "hi" }], verbosity: "low" },
true,
{}
)
);
assert.deepEqual(out.text, { verbosity: "low" });
assert.equal(out.verbosity, undefined);
});
test("Chat -> Responses ignores an invalid verbosity value", () => {
const out = asRecord(
openaiToOpenAIResponsesRequest(
"gpt-5.5",
{ model: "gpt-5.5", messages: [{ role: "user", content: "hi" }], verbosity: "loud" },
true,
{}
)
);
assert.equal(out.text, undefined);
});
test("Responses -> Chat maps text.verbosity to top-level verbosity and drops text", () => {
// #7533: verbosity is a GPT-5/OpenAI-only Chat Completions parameter and is only
// carried across for an OpenAI-destined request — pass `provider: "openai"` so this
// pins the real OpenAI-routed contract instead of the pre-#7533 unconditional one.
const out = asRecord(
openaiResponsesToOpenAIRequest(
"gpt-5.5",
{ model: "gpt-5.5", input: [{ role: "user", content: "hi" }], text: { verbosity: "high" } },
false,
{ provider: "openai" }
)
);
assert.equal(out.verbosity, "high");
assert.equal(out.text, undefined);
});
test("Responses -> Chat drops a stray non-verbosity text wrapper", () => {
const out = asRecord(
openaiResponsesToOpenAIRequest(
"gpt-5.5",
{ model: "gpt-5.5", input: [{ role: "user", content: "hi" }], text: { format: { type: "json" } } },
false,
{}
)
);
assert.equal(out.text, undefined);
assert.equal(out.verbosity, undefined);
});