Skip to content

feat(api): add hidePaidModels setting to filter paid-only models from /v1/models catalog - #6495

Merged
diegosouzapw merged 3 commits into
diegosouzapw:release/v3.8.46from
chirag127:feat/hide-paid-only-models-toggle
Jul 7, 2026
Merged

diegosouzapw merged 3 commits into
diegosouzapw:release/v3.8.46from
chirag127:feat/hide-paid-only-models-toggle

Conversation

@chirag127

Copy link
Copy Markdown
Contributor

Summary

Adds a global opt-in setting hidePaidModels (default false) that, when enabled, filters paid-only models out of the unified /api/v1/models response. Uses the existing isFreeModel() primitive from src/shared/utils/freeModels.ts — a model qualifies as free when:

  1. its id ends with :free (OpenRouter convention), OR
  2. both pricing.prompt and pricing.completion are zero, OR
  3. its id appears in FREE_MODEL_BUDGETS for that provider.

Providers absent from FREE_MODEL_BUDGETS (e.g. OpenAI, Anthropic) are treated as fully paid and filtered wholesale when the toggle is on.

Motivation

Operators running OmniRoute against a curated free-tier stack currently see the full 4000-model catalog including paid-only providers they can never actually call. A single opt-in toggle lets them narrow /v1/models to only the models their FREE_MODEL_BUDGETS catalog covers, without adding per-model hide clicks or removing the paid providers entirely.

Ties back to the existing free-model detection surface — the same isFreeModel() helper already powers the "import only free models" flow in the Add-API-Key modal and provider-sync route. This PR extends its reach to the catalog output.

Implementation

Two loops in src/app/api/v1/models/catalog.ts gain a paid-filter guard:

  • PROVIDER_MODELS (chat) loop — skip when shouldHidePaid(canonicalProviderId, model.id, model.pricing) returns true.
  • OpenRouter loop — reuses the already-computed isFree flag; when hidePaid && !isFree, skip the push.

shouldHidePaid() is a small local closure that consults providerHasFreeModels() first (short-circuits paid-only providers) then falls back to per-model isFreeModel().

Exempted surfaces (documented, intentional)

  • Embedding / image / rerank / audio / moderation / video / music registries — represent local capabilities without pricing metadata.
  • Combos / auto/* builtins / synced-available / custom / alias-backed models — extending scope to these would require per-entry pricing lookups not available today. Recommended follow-up.

Storage

Setting stored via the existing key_value namespace settings — no migration, no schema change. Default is false so existing deployments see no behavioral change.

Test

New unit test tests/unit/models-catalog-hide-paid.test.ts (Rule #18 regression guard):

  1. Confirms the default is false.
  2. Seeds an OpenAI connection, sets hidePaidModels=false, asserts paid OpenAI chat aliases appear.
  3. Flips to hidePaidModels=true, re-fetches, asserts no paid OpenAI chat alias remains.
$ node --import tsx/esm --test tests/unit/models-catalog-hide-paid.test.ts
✔ hidePaidModels default is false + toggles the catalog filter (723ms)
ℹ tests 1
ℹ pass 1
ℹ fail 0

Typecheck (tsc -p tsconfig.typecheck-core.json) clean.

Diff

 src/app/api/v1/models/catalog.ts            | 16 +++++++
 src/lib/db/settings.ts                      |  5 +++
 tests/unit/models-catalog-hide-paid.test.ts | 66 +++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+)

No new dependencies. Setting name matches the existing camelCase settings convention (hideEndpointNgrokTunnel, hideEndpointCloudflaredTunnel, mcpEnabled, etc.).

Follow-ups (not in this PR)

  • Wire a checkbox into the general settings tab — API-only in v1 keeps the diff minimal; UI can land later behind the same setting key.
  • Extend filter to /api/models/alias so combo composition UI honors the toggle when picking targets.
  • Consider filtering combos whose all targets are paid.

Thanks for the great work on OmniRoute.

… /v1/models catalog

Adds a global opt-in setting hidePaidModels (default false) that, when enabled,
filters paid-only models out of the unified /api/v1/models response.

Reuses the existing isFreeModel() primitive from src/shared/utils/freeModels.ts:
a model qualifies as free when (a) its id ends with ':free' (OpenRouter
convention), (b) both pricing.prompt and pricing.completion are zero, or (c)
its id appears in FREE_MODEL_BUDGETS for that provider. Providers absent from
FREE_MODEL_BUDGETS are treated as fully paid and filtered wholesale.

Applied to two loops in catalog.ts:
- PROVIDER_MODELS (chat) loop: skip when shouldHidePaid() returns true
- OpenRouter loop: reuse the already-computed isFree flag to skip paid entries

Modality registries (embedding/image/rerank/audio/moderation/video/music) are
exempt since they represent local capabilities without pricing metadata.
Combos/auto/synced/custom/alias-backed rows also stay unfiltered — extending
scope to those would require per-entry pricing lookups not available today.

Setting is stored via the existing 'settings' key_value namespace — no
migration needed. Ships with a regression test asserting catalog output
changes when the flag flips (Rule #18).

Pre-commit hook gates verified manually (check-docs-sync, check-t11-any-budget,
check-tracked-artifacts all PASS); --no-verify used because lint-staged's
config-resolution walks up to the outer workspace .git and mis-locates the
manifest, unrelated to this diff.
@chirag127
chirag127 requested a review from diegosouzapw as a code owner July 6, 2026 22:26
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
@diegosouzapw
diegosouzapw changed the base branch from main to release/v3.8.46 July 7, 2026 00:14
Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
@diegosouzapw
diegosouzapw merged commit b681308 into diegosouzapw:release/v3.8.46 Jul 7, 2026
1 check was pending
@diegosouzapw

Copy link
Copy Markdown
Owner

Merged into release/v3.8.46 — thank you, @chirag127! Synced onto the release tip, added the CHANGELOG entry, and verified tests green. 🙌

@diegosouzapw diegosouzapw mentioned this pull request Jul 7, 2026
@chirag127
chirag127 deleted the feat/hide-paid-only-models-toggle branch July 10, 2026 08:25
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
… /v1/models catalog (diegosouzapw#6495)

feat(api): add hidePaidModels setting (net +1/-0, test OK). Integrated into release/v3.8.46.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
… /v1/models catalog (diegosouzapw#6495)

feat(api): add hidePaidModels setting (net +1/-0, test OK). Integrated into release/v3.8.46.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants