mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-07 07:42:13 +03:00
feat(routing): honor X-Route-Model header to override body.model (#4863)
Integrated into release/v3.8.36
This commit is contained in:
@@ -217,7 +217,7 @@
|
||||
"src/shared/constants/sidebarVisibility.ts": 1100,
|
||||
"src/shared/services/cliRuntime.ts": 1090,
|
||||
"src/shared/validation/schemas.ts": 2523,
|
||||
"src/sse/handlers/chat.ts": 1521,
|
||||
"src/sse/handlers/chat.ts": 1525,
|
||||
"src/sse/services/auth.ts": 2289
|
||||
},
|
||||
"testCap": 800,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { randomUUID } from "crypto";
|
||||
import { resolveChatRequestBody } from "./requestBody";
|
||||
import { resolveRoutingModel } from "./resolveRoutingModel";
|
||||
import {
|
||||
getProviderCredentialsWithQuotaPreflight,
|
||||
markAccountUnavailable,
|
||||
@@ -254,7 +255,10 @@ export async function handleChat(
|
||||
log.debug("NO_THINKING", `Resolved no-thinking alias → ${noThinking.realModel}`);
|
||||
}
|
||||
|
||||
let modelStr = body.model;
|
||||
// X-Route-Model header overrides body.model for routing purposes (see
|
||||
// resolveRoutingModel). The resolved model still passes through
|
||||
// enforceApiKeyPolicy below, so it cannot bypass per-key allowlists.
|
||||
let modelStr = resolveRoutingModel(request, body);
|
||||
|
||||
// Count messages (support both messages[] and input[] formats)
|
||||
const msgCount = body.messages?.length || body.input?.length || 0;
|
||||
|
||||
17
src/sse/handlers/resolveRoutingModel.ts
Normal file
17
src/sse/handlers/resolveRoutingModel.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// Resolve the model used for routing. The `X-Route-Model` header, when present,
|
||||
// overrides `body.model` — letting a caller/proxy force a specific combo/alias/model
|
||||
// regardless of what the client CLI sent. This is useful when a CLI hardcodes
|
||||
// `body.model` to a fixed provider/model (bypassing combo routing): an upstream
|
||||
// proxy can send `X-Route-Model` to restore routing control without mutating the
|
||||
// request body. The resolved value still flows through `enforceApiKeyPolicy`, so
|
||||
// it cannot bypass per-key model/combo allowlists. See PR #4863.
|
||||
|
||||
type HeaderCarrier = { headers: { get(name: string): string | null } };
|
||||
|
||||
export function resolveRoutingModel(
|
||||
request: HeaderCarrier,
|
||||
body: { model?: string | null }
|
||||
): string | null | undefined {
|
||||
const headerModel = request.headers.get("x-route-model")?.trim();
|
||||
return headerModel || body.model;
|
||||
}
|
||||
32
tests/unit/resolve-routing-model.test.ts
Normal file
32
tests/unit/resolve-routing-model.test.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// Regression guard for #4863: X-Route-Model header overrides body.model for routing.
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveRoutingModel } from "../../src/sse/handlers/resolveRoutingModel.ts";
|
||||
|
||||
function req(headers: Record<string, string>) {
|
||||
return { headers: { get: (n: string) => headers[n.toLowerCase()] ?? null } };
|
||||
}
|
||||
|
||||
describe("resolveRoutingModel (#4863)", () => {
|
||||
it("uses body.model when no X-Route-Model header is present", () => {
|
||||
assert.equal(resolveRoutingModel(req({}), { model: "gpt-5.3-codex" }), "gpt-5.3-codex");
|
||||
});
|
||||
|
||||
it("X-Route-Model header overrides body.model", () => {
|
||||
assert.equal(
|
||||
resolveRoutingModel(req({ "x-route-model": "my-combo" }), { model: "codex/gpt-5.3-codex" }),
|
||||
"my-combo"
|
||||
);
|
||||
});
|
||||
|
||||
it("trims surrounding whitespace from the header value", () => {
|
||||
assert.equal(
|
||||
resolveRoutingModel(req({ "x-route-model": " alias-x " }), { model: "fallback" }),
|
||||
"alias-x"
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to body.model when the header is empty/whitespace-only", () => {
|
||||
assert.equal(resolveRoutingModel(req({ "x-route-model": " " }), { model: "fallback" }), "fallback");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user