mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-14 03:02:14 +03:00
fix(translator): honor Chat targets for Responses clients (#9161)
Honor explicit Chat targets for Responses-shaped clients while preserving native Responses providers and selecting token fields from the outbound protocol. Includes focused regression coverage and the required changelog fragment.
This commit is contained in:
1
changelog.d/fixes/9161-codex-responses-chat-targets.md
Normal file
1
changelog.d/fixes/9161-codex-responses-chat-targets.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(translator):** Honor configured Chat targets for Responses-shaped clients while preserving native Responses providers and outbound token fields ([#9161](https://github.com/diegosouzapw/OmniRoute/pull/9161)) — thanks @Zartharas
|
||||
@@ -6,8 +6,8 @@ export function sanitizeChatRequestBody(
|
||||
sourceFormat: string,
|
||||
targetFormat: string
|
||||
): Record<string, unknown> {
|
||||
const prefersResponsesTokenField =
|
||||
sourceFormat === FORMATS.OPENAI_RESPONSES || targetFormat === FORMATS.OPENAI_RESPONSES;
|
||||
void sourceFormat;
|
||||
const prefersResponsesTokenField = targetFormat === FORMATS.OPENAI_RESPONSES;
|
||||
|
||||
if (prefersResponsesTokenField) {
|
||||
if (body.max_output_tokens === undefined) {
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
* chatCore wire target-format resolver (Quality Gate v2 / Fase 9 — chatCore god-file
|
||||
* decomposition, #3501).
|
||||
*
|
||||
* Pure resolution of the provider alias + the upstream target format used to translate the request:
|
||||
* apiFormat==="responses" forces OpenAI Responses; otherwise the model's registry target format, then
|
||||
* the per-model custom override (#2905), then AgentRouter's matching inbound protocol when the
|
||||
* connection has no explicit override, then the provider default. Returns both `alias` (reused by
|
||||
* the handler when stripping the `alias/` prefix off the upstream model id) and `targetFormat`.
|
||||
* Side-effect-free; sits alongside the other request-setup resolvers
|
||||
* (resolveChatCoreRequestSetup / resolveChatCoreRequestFormat).
|
||||
* Pure resolution of the provider alias + the upstream target format used to translate the request.
|
||||
* Model/custom overrides win first. A Responses-shaped inbound request normally keeps the Responses
|
||||
* wire format, except for custom OpenAI-compatible connections explicitly configured for Chat.
|
||||
* AgentRouter may inherit the inbound protocol when no explicit connection override exists.
|
||||
* Returns both `alias` (reused by the handler when stripping the `alias/` prefix off the upstream
|
||||
* model id) and `targetFormat`.
|
||||
*/
|
||||
|
||||
import { PROVIDER_ID_TO_ALIAS, getModelTargetFormat } from "../../config/providerModels.ts";
|
||||
@@ -46,15 +45,19 @@ export function resolveChatCoreTargetFormat(opts: {
|
||||
sourceFormat === FORMATS.CLAUDE)
|
||||
? sourceFormat
|
||||
: undefined;
|
||||
const providerTargetFormat = getTargetFormat(provider, providerSpecificData);
|
||||
const customOpenAICompatible = provider.startsWith("openai-compatible-");
|
||||
// #8994: model-level targetFormat overrides (from registry or custom-model DB override)
|
||||
// take precedence over apiFormat="responses" — otherwise Vertex Claude models with
|
||||
// targetFormat="claude" get wrongly routed to OpenAI Responses format.
|
||||
// #9161: a custom OpenAI-compatible Chat connection must likewise keep its configured
|
||||
// outbound protocol when a Responses-shaped client (for example Codex) calls /responses.
|
||||
let targetFormat =
|
||||
modelTargetFormat ||
|
||||
customModelTargetFormat ||
|
||||
(apiFormat === "responses"
|
||||
(apiFormat === "responses" && !customOpenAICompatible
|
||||
? FORMATS.OPENAI_RESPONSES
|
||||
: inferredAgentRouterTargetFormat || getTargetFormat(provider, providerSpecificData));
|
||||
: inferredAgentRouterTargetFormat || providerTargetFormat);
|
||||
if (nativeXaiResponsesPassthrough) targetFormat = FORMATS.OPENAI_RESPONSES;
|
||||
return { alias, targetFormat };
|
||||
}
|
||||
|
||||
67
tests/unit/codex-responses-to-chat-9161.test.ts
Normal file
67
tests/unit/codex-responses-to-chat-9161.test.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { resolveChatCoreTargetFormat } from "../../open-sse/handlers/chatCore/targetFormat.ts";
|
||||
import { sanitizeChatRequestBody } from "../../open-sse/handlers/chatCore/sanitization.ts";
|
||||
import { FORMATS } from "../../open-sse/translator/formats.ts";
|
||||
|
||||
test("#9161 Responses client honors a custom OpenAI-compatible Chat target", () => {
|
||||
const result = resolveChatCoreTargetFormat({
|
||||
provider: "openai-compatible-chat-regression",
|
||||
resolvedModel: "custom-chat-model",
|
||||
apiFormat: "responses",
|
||||
sourceFormat: FORMATS.OPENAI_RESPONSES,
|
||||
customModelTargetFormat: undefined,
|
||||
providerSpecificData: { apiType: "chat" },
|
||||
});
|
||||
|
||||
assert.equal(result.targetFormat, FORMATS.OPENAI);
|
||||
});
|
||||
|
||||
test("#9161 Responses client preserves a custom OpenAI-compatible Responses target", () => {
|
||||
const result = resolveChatCoreTargetFormat({
|
||||
provider: "openai-compatible-responses-regression",
|
||||
resolvedModel: "custom-responses-model",
|
||||
apiFormat: "responses",
|
||||
sourceFormat: FORMATS.OPENAI_RESPONSES,
|
||||
customModelTargetFormat: undefined,
|
||||
providerSpecificData: { apiType: "responses" },
|
||||
});
|
||||
|
||||
assert.equal(result.targetFormat, FORMATS.OPENAI_RESPONSES);
|
||||
});
|
||||
|
||||
test("#9161 official OpenAI keeps Responses routing for the Responses API", () => {
|
||||
const result = resolveChatCoreTargetFormat({
|
||||
provider: "openai",
|
||||
resolvedModel: "gpt-4o",
|
||||
apiFormat: "responses",
|
||||
sourceFormat: FORMATS.OPENAI_RESPONSES,
|
||||
customModelTargetFormat: undefined,
|
||||
providerSpecificData: undefined,
|
||||
});
|
||||
|
||||
assert.equal(result.targetFormat, FORMATS.OPENAI_RESPONSES);
|
||||
});
|
||||
|
||||
test("#9161 Responses source targeting Chat converts max_output_tokens to max_tokens", () => {
|
||||
const body = sanitizeChatRequestBody(
|
||||
{ max_output_tokens: 256 },
|
||||
FORMATS.OPENAI_RESPONSES,
|
||||
FORMATS.OPENAI
|
||||
);
|
||||
|
||||
assert.equal(body.max_tokens, 256);
|
||||
assert.equal(body.max_output_tokens, undefined);
|
||||
});
|
||||
|
||||
test("#9161 Responses target keeps the Responses token field", () => {
|
||||
const body = sanitizeChatRequestBody(
|
||||
{ max_tokens: 128 },
|
||||
FORMATS.OPENAI,
|
||||
FORMATS.OPENAI_RESPONSES
|
||||
);
|
||||
|
||||
assert.equal(body.max_output_tokens, 128);
|
||||
assert.equal(body.max_tokens, undefined);
|
||||
});
|
||||
Reference in New Issue
Block a user