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.
3.7 KiB
title, version, lastUpdated
| title | version | lastUpdated |
|---|---|---|
| Model Exposure Allow/Deny List | 3.8.51 | 2026-08-29 |
Model Exposure Allow/Deny List
Opt-in, off by default (
modelVisibilityAllowlist/modelVisibilityDenylistboth empty leaves the/v1/modelscatalog AND everyauto/*candidate pool byte-identical). A sibling ofhidePaidModels/hideAutoCombos(src/lib/db/settings.ts) for operators who want a curated model list for reasons that have nothing to do with cost.
Why this exists
hidePaidModels answers "is this model free?" and hideAutoCombos answers "should auto/*
virtual ids be advertised at all?" — neither lets an operator curate an arbitrary subset of
models (e.g. presenting exactly the models a given Claude Code / OpenCode client should see,
independent of pricing). #11481 adds that as two independent, opt-in string-array settings.
Settings
| Key | Type | Default | Meaning |
|---|---|---|---|
modelVisibilityDenylist |
string[] |
[] |
Entries matching a candidate hide it from the catalog/candidate pool. |
modelVisibilityAllowlist |
string[] |
[] |
When non-empty, ONLY entries matching a candidate stay exposed. |
Both accept up to 500 entries of up to 200 characters each (Zod-validated in
src/shared/validation/settingsSchemas.ts). An entry is either:
- an exact catalog id —
"gpt-4o"(bare model id) or"openai/gpt-4o"(provider-prefixed), or - a glob pattern using
*/?— e.g."openai/gpt-4*"or"anthropic/*"— resolved via the same sharedglobToRegex()matcher (src/shared/utils/globPattern.ts) already used byModelRoutingSection's per-model combo mappings andfreeModels.ts::matchesOnlyPaidModels.
Precedence: the denylist is checked first (a denied entry is always hidden, even if it also matches the allowlist); when the allowlist is non-empty, only entries it matches survive.
Two chokepoints, not one
The lesson from #6512 (a hidePaidModels-only catalog filter still let auto/* route to a
paid model, since the combo candidate pool was built independently) applies identically here.
The matching predicate isModelExposureAllowed() (src/shared/utils/modelExposureList.ts) is
called from BOTH:
src/app/api/v1/models/catalog.ts— the/v1/modelslisting itself, at the same 5 per-source chokepointsshouldHidePaid()already gates (staticPROVIDER_MODELS, synced provider rows, custom rows, alias-backed rows, managed-fallback rows).open-sse/services/autoCombo/modelExposureFilter.ts::filterModelExposureCandidates()— called fromvirtualFactory.ts::buildPreparedPool, immediately after the equivalentfilterPaidOnlyCandidates()call, so a denied model can never be selected into anauto/*candidate pool either.
What is NOT filtered
Mirrors hideAutoCombos's existing behaviour: a model id sent explicitly (not via auto/*,
and not discovered through the catalog listing) is never blocked at dispatch — only
advertisement/candidate-pool membership is filtered. This is independent of hidePaidModels;
an operator may want a curated set for reasons that have nothing to do with cost, so both
settings compose as independent AND-ed filters, same as the existing multi-flag composition in
catalog.ts.
Settings export (GET /api/settings/export-json) includes both arrays verbatim, like any other
settings field — unlike hidePaidModels's combo-step export filter, there is no re-hydration
risk here: a denied id embedded in an exported combo step is the operator's own explicit routing
choice, not something the export boundary needs to strip.