mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
align non-streaming body.model with X-OmniRoute-Model (#6426, 3/3). Integrated into release/v3.8.46.
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **fix(api):** non-streaming `/v1/completions` responses now echo `body.model` aligned with the `X-OmniRoute-Model` header ([#6426](https://github.com/diegosouzapw/OmniRoute/issues/6426)). Regression guard: `tests/unit/v1-completions-model-header-match-6426.test.ts`. (thanks @chirag127)
|
||||
- **fix(api):** unknown `/v1/*` routes now return a JSON `404 not_found` instead of the Next.js dashboard HTML shell ([#6405](https://github.com/diegosouzapw/OmniRoute/issues/6405)). Regression guard: `tests/unit/api/v1-catchall-json-404.test.ts`. (thanks @chirag127)
|
||||
- **fix(api):** the per-connection provider models route now degrades to the shipped catalog when a provider's `/models` endpoint answers with a redirect ([#6267](https://github.com/diegosouzapw/OmniRoute/issues/6267)) — a `qwen-web` import failed with a raw `Redirect blocked … (307)` 503. `safeOutboundFetch` throws `REDIRECT_BLOCKED` on the 307, `getSafeOutboundFetchErrorStatus` maps it to 503, and `buildDiscoveryErrorFallbackResponse` treated every 503 as a hard error — so the non-empty `getModelsByProviderId("qwen-web")` catalog was never surfaced. A models-endpoint redirect is not a fixable-config error (unlike `URL_GUARD_BLOCKED`/`INVALID_URL`, which stay hard errors), so it now falls back to the local/cached catalog before the 503 short-circuit. General fix — covers any config-driven provider that 307s. Regression guard: `tests/unit/provider-models-qwen-web-redirect-6267.test.ts`. (thanks @chirag127)
|
||||
- **fix(api):** the per-connection provider models route (MCP `list_models_catalog` + the dashboard import view) now merges USER-ADDED custom models into its response ([#6247](https://github.com/diegosouzapw/OmniRoute/issues/6247)) — custom models live in the `key_value` namespace `customModels`, which the live REST `/api/v1/models` already merges, but `src/app/api/providers/[id]/models/route.ts` never read `getCustomModels`, so custom models were dropped on both the discovery-success and local_catalog paths. They are now folded into the returned model list (deduped by id, stamped `owned_by: provider`), fixing MCP + the dashboard import view in one place. Regression guard: `tests/unit/provider-models-custom-merge-6247.test.ts`. (thanks @RCrushMe)
|
||||
|
||||
@@ -4056,6 +4056,13 @@ export async function handleChatCore({
|
||||
requestId: skillRequestId,
|
||||
compressionResponseMeta,
|
||||
});
|
||||
// #6426: align response body `model` with the `X-OmniRoute-Model` header
|
||||
// (both must be the resolved backend model). Some upstreams (notably legacy
|
||||
// /v1/completions text-completion path) return a body `model` field that
|
||||
// differs from the resolved backend id we advertised in the header, leaving
|
||||
// strict clients unable to reconcile the two. Rewrite body.model to `model`
|
||||
// FIRST, then let #1311 echo override it when the opt-in setting is on.
|
||||
if (typeof model === "string" && model) echoModelInObject(translatedResponse, model);
|
||||
// #1311: echo the requested alias/combo name in the non-streaming response model.
|
||||
if (echoModel) echoModelInObject(translatedResponse, echoModel);
|
||||
return {
|
||||
|
||||
65
tests/unit/v1-completions-model-header-match-6426.test.ts
Normal file
65
tests/unit/v1-completions-model-header-match-6426.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { echoModelInObject } from "../../open-sse/services/responseModelEcho.ts";
|
||||
|
||||
// #6426: on non-streaming success (including /v1/completions), the response body
|
||||
// `model` field must equal the resolved backend model advertised in the
|
||||
// `X-OmniRoute-Model` header. Some upstreams (notably legacy text-completions)
|
||||
// return a body `model` that drifts from the id we advertised. chatCore now
|
||||
// rewrites body.model to the resolved `model` before the optional #1311 echo,
|
||||
// so strict clients can reconcile body ↔ header.
|
||||
|
||||
test("#6426 body.model rewritten to resolved backend model aligns with header value", () => {
|
||||
// simulate the non-streaming success path: header carries resolved model,
|
||||
// upstream body carries a drifted id.
|
||||
const resolvedModel = "gpt-5.5"; // this is what attachOmniRouteMetaHeaders wrote to X-OmniRoute-Model
|
||||
const translatedResponse: Record<string, unknown> = {
|
||||
id: "cmpl-1",
|
||||
object: "text_completion",
|
||||
model: "gpt-5.5-2026-06-preview", // upstream drifted alias
|
||||
choices: [{ text: "hi" }],
|
||||
};
|
||||
|
||||
// the fix: rewrite body.model to the resolved id
|
||||
echoModelInObject(translatedResponse, resolvedModel);
|
||||
|
||||
assert.equal(
|
||||
translatedResponse.model,
|
||||
resolvedModel,
|
||||
"body.model must equal the resolved backend model that is in X-OmniRoute-Model header"
|
||||
);
|
||||
});
|
||||
|
||||
test("#6426 #1311 echo still wins when both alignments apply", () => {
|
||||
// when the user opts in to #1311 (echoRequestedModelName), the echo (alias/combo name)
|
||||
// wins because the #6426 alignment runs first and #1311 runs second.
|
||||
const resolvedModel = "gpt-5.5";
|
||||
const echoModel = "claude-sonnet-cx"; // client-requested alias
|
||||
const translatedResponse: Record<string, unknown> = {
|
||||
id: "cmpl-2",
|
||||
model: "gpt-5.5-drift",
|
||||
choices: [],
|
||||
};
|
||||
|
||||
// step 1: #6426 aligns to resolved model
|
||||
echoModelInObject(translatedResponse, resolvedModel);
|
||||
// step 2: #1311 overrides with the client-requested alias
|
||||
echoModelInObject(translatedResponse, echoModel);
|
||||
|
||||
assert.equal(translatedResponse.model, echoModel);
|
||||
});
|
||||
|
||||
test("#6426 alignment is a no-op when resolved model is empty/null", () => {
|
||||
const translatedResponse: Record<string, unknown> = {
|
||||
id: "cmpl-3",
|
||||
model: "gpt-5.5",
|
||||
choices: [],
|
||||
};
|
||||
|
||||
echoModelInObject(translatedResponse, null);
|
||||
assert.equal(translatedResponse.model, "gpt-5.5");
|
||||
|
||||
echoModelInObject(translatedResponse, "");
|
||||
assert.equal(translatedResponse.model, "gpt-5.5");
|
||||
});
|
||||
Reference in New Issue
Block a user