mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 12:22:14 +03:00
* chore(ci): add .mergify.yml to main — Mergify only reads config from the default branch (#7168) * fix(ci): add the auto-enqueue pull_request_rule to the Mergify config (queue_conditions alone are eligibility-only) (#7179) * fix(guardrails/chat): stop Vision Bridge hijacking credentialed models to opencode-zen OpenCode (and similar clients) often send image parts in long sessions. Vision Bridge treated the request model as non-vision and whole-request- rerouted to getBestVisionModel(), which preferred opencode-* (priority 0). That landed on a noauth connection and returned 401 Missing API key — while proxies/combos still logged the original target (zai/glm-5.2, grok-cli, …). Also: after resolveRoutingModel(X-Route-Model), keep body.model aligned so the post-guardrail "body.model !== modelStr" path cannot undo the routing header. - visionBridge: skip whole-request reroute when original model has usable creds - visionBridge: refuse reroute to targets known unusable (noauth without key) - visionBridgeRouter: deprioritize opencode-* for auto vision pick - chat: alignBodyModelWithRouting + only adopt true guardrail model mutations - tests: VB-CRED-01/02 + alignBodyModelWithRouting coverage * fix(guardrails/chat): keep chat.ts under the file-size ratchet and update stale vision-bridge tests for the credential-aware reroute skip - Extract the routing-model reconciliation logic (X-Route-Model align, post-guardrail reroute policy re-check, hook model override) into RoutingModelOps helpers in resolveRoutingModel.ts, shrinking chat.ts back under the frozen 1796-line file-size baseline (was 1837). - Update tests/unit/guardrails/vision-bridge-callmodel.test.ts: the fallback mock must match whichever API shape the selected fallback model actually calls (OpenAI-compatible vs Anthropic), since the vision-bridge router priority fix in this PR can now legitimately select an Anthropic fallback model instead of always defaulting to an OpenAI-shaped opencode-* model. - Update tests/unit/vision-bridge-policy-reroute-6640.test.ts: per this PR's own VB-CRED-01 test, a credentialed original model is now intentionally never whole-request-rerouted (it always falls through to describe-then- forward) — so the pre-existing #6640 tests are updated to assert the final, user-facing answer always comes from the original credentialed model, matching the new intended behavior instead of the retired whole-request-reroute path. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(guardrails/chat): reduce isProviderConnectionUsable cyclomatic complexity to satisfy the project-wide complexity ratchet The new isProviderConnectionUsable helper (complexity 21) regressed the project-wide complexity ratchet from 2056 to 2057. Refactor it to use Set membership checks and small extracted helpers (hasNonEmptyString, hasOAuthCredential) instead of chained === / || comparisons — same behavior, verified by the existing "isProviderConnectionUsable rejects noauth without api key" test, with complexity back under the 15-per-function threshold and the project-wide ratchet back at the 2056 baseline. Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
// Regression guard for #4863: X-Route-Model header overrides body.model for routing.
|
|
// Also covers alignBodyModelWithRouting — without body alignment the post-guardrail
|
|
// path silently restores body.model and undoes the header (zai header + opencode body → 401).
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
alignBodyModelWithRouting,
|
|
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");
|
|
});
|
|
});
|
|
|
|
describe("alignBodyModelWithRouting (X-Route-Model body lockstep)", () => {
|
|
it("rewrites body.model when it differs from the routing model", () => {
|
|
const body = { model: "opencode-zen/gpt-5.4", messages: [{ role: "user", content: "hi" }] };
|
|
const routed = resolveRoutingModel(req({ "x-route-model": "zai/glm-5.2" }), body);
|
|
const result = alignBodyModelWithRouting(body, routed);
|
|
assert.equal(routed, "zai/glm-5.2");
|
|
assert.equal(result.aligned, true);
|
|
assert.equal(result.previousModel, "opencode-zen/gpt-5.4");
|
|
assert.equal(result.body.model, "zai/glm-5.2");
|
|
// Original body object is not mutated
|
|
assert.equal(body.model, "opencode-zen/gpt-5.4");
|
|
});
|
|
|
|
it("is a no-op when body.model already matches", () => {
|
|
const body = { model: "zai/glm-5.2" };
|
|
const result = alignBodyModelWithRouting(body, "zai/glm-5.2");
|
|
assert.equal(result.aligned, false);
|
|
assert.equal(result.body, body);
|
|
});
|
|
|
|
it("is a no-op when routing model is empty", () => {
|
|
const body = { model: "opencode-zen/gpt-5.4" };
|
|
const result = alignBodyModelWithRouting(body, null);
|
|
assert.equal(result.aligned, false);
|
|
assert.equal(result.body.model, "opencode-zen/gpt-5.4");
|
|
});
|
|
});
|