mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
Adds opt-in modelVisibilityAllowlist/modelVisibilityDenylist settings so an operator can curate exactly which models GET /v1/models advertises, mirrored into auto/* combo candidate pools (the same trap #6512 fixed for hidePaidModels). Default off, no behavior change for anyone who doesn't opt in. TDD: 4 new test files, 22/22 passing (16 node:test + 6 vitest) + regression sweep across virtual-auto-combo/hide-paid/hide-auto-no-think suites (21/21). Rebased onto the updated tip (a sibling #9133 landed first, same file) — kept both rebaseline annotations in file-size-baseline.json and set the value to the real measured line count after both merged.
80 lines
3.4 KiB
TypeScript
80 lines
3.4 KiB
TypeScript
/**
|
|
* #11481 — explicit model exposure allow/deny list for `/v1/models` (and, via the
|
|
* mirror in `open-sse/services/autoCombo/modelExposureFilter.ts`, the `auto/*`
|
|
* candidate pool). Follows the `hidePaidModels`/`hideAutoCombos` opt-in shape
|
|
* (`src/lib/db/settings.ts`): default-off, two independent string arrays.
|
|
*
|
|
* Tests the pure predicate `isModelExposureAllowed()` — the single chokepoint
|
|
* both `catalog.ts` and the combo-pool mirror call into.
|
|
*/
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { isModelExposureAllowed } from "@/shared/utils/modelExposureList";
|
|
|
|
test("default off (both lists empty) — every model is exposed", () => {
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o", {}), true);
|
|
assert.equal(
|
|
isModelExposureAllowed("openai", "gpt-4o", {
|
|
modelVisibilityAllowlist: [],
|
|
modelVisibilityDenylist: [],
|
|
}),
|
|
true
|
|
);
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o", null), true);
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o", undefined), true);
|
|
});
|
|
|
|
test("denylist hides an exact-id match, leaves everything else exposed", () => {
|
|
const settings = { modelVisibilityDenylist: ["openai/gpt-4o"] };
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o", settings), false);
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o-mini", settings), true);
|
|
assert.equal(isModelExposureAllowed("anthropic", "claude-opus-5", settings), true);
|
|
});
|
|
|
|
test("denylist also matches the bare model id (no provider prefix required)", () => {
|
|
const settings = { modelVisibilityDenylist: ["gpt-4o"] };
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o", settings), false);
|
|
});
|
|
|
|
test("allowlist restricts exposure to exactly the listed models", () => {
|
|
const settings = { modelVisibilityAllowlist: ["openai/gpt-4o-mini", "anthropic/claude-opus-5"] };
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o-mini", settings), true);
|
|
assert.equal(isModelExposureAllowed("anthropic", "claude-opus-5", settings), true);
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o", settings), false);
|
|
assert.equal(isModelExposureAllowed("google", "gemini-2.5-pro", settings), false);
|
|
});
|
|
|
|
test("denylist wins over allowlist for the same entry (deny is checked first)", () => {
|
|
const settings = {
|
|
modelVisibilityAllowlist: ["openai/gpt-4o"],
|
|
modelVisibilityDenylist: ["openai/gpt-4o"],
|
|
};
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o", settings), false);
|
|
});
|
|
|
|
test("glob patterns are supported via the shared globToRegex matcher", () => {
|
|
const denyGlob = { modelVisibilityDenylist: ["openai/gpt-4*"] };
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o", denyGlob), false);
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4.1", denyGlob), false);
|
|
assert.equal(isModelExposureAllowed("openai", "o1-preview", denyGlob), true);
|
|
|
|
const allowGlob = { modelVisibilityAllowlist: ["anthropic/*"] };
|
|
assert.equal(isModelExposureAllowed("anthropic", "claude-opus-5", allowGlob), true);
|
|
assert.equal(isModelExposureAllowed("openai", "gpt-4o", allowGlob), false);
|
|
});
|
|
|
|
test("non-array / malformed list values are treated as empty (fail open, never throw)", () => {
|
|
assert.equal(
|
|
isModelExposureAllowed("openai", "gpt-4o", {
|
|
modelVisibilityDenylist: "openai/gpt-4o" as unknown as string[],
|
|
}),
|
|
true
|
|
);
|
|
assert.equal(
|
|
isModelExposureAllowed("openai", "gpt-4o", {
|
|
modelVisibilityDenylist: [123 as unknown as string, " "],
|
|
}),
|
|
true
|
|
);
|
|
});
|