From fc8459faa3c416ddc2a03a449ba6847f7ca9e5f1 Mon Sep 17 00:00:00 2001 From: Chirag Singhal <76880977+chirag127@users.noreply.github.com> Date: Wed, 8 Jul 2026 04:49:43 +0530 Subject: [PATCH] fix(autoCombo): exclude paid models from fusion candidate pools when hidePaidModels=true (#6328) (#6550) fix(autoCombo): exclude paid-tier auto/* ids from the catalog when hidePaidModels=true (#6328). Integrated into release/v3.8.47. (thanks @chirag127) --- open-sse/services/autoCombo/builtinCatalog.ts | 22 +++++++++++ src/app/api/v1/models/catalog.ts | 6 +++ .../autoCombo/paid-tier-auto-id-6328.test.ts | 38 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 tests/unit/autoCombo/paid-tier-auto-id-6328.test.ts diff --git a/open-sse/services/autoCombo/builtinCatalog.ts b/open-sse/services/autoCombo/builtinCatalog.ts index cc80184aa5..5b4526dce7 100644 --- a/open-sse/services/autoCombo/builtinCatalog.ts +++ b/open-sse/services/autoCombo/builtinCatalog.ts @@ -88,6 +88,28 @@ export function isRecognizedBuiltinAuto(modelStr: string, suffix: string): boole ); } +/** + * #6328 (follow-up to #6495 / #6512): recognize built-in `auto/*` ids whose + * intent is paid-tier only, so callers can REMOVE — not just hide — them from + * advertised catalogs when the operator opts into `hidePaidModels`. + * + * Two shapes qualify as paid-tier: + * - flat variants prefixed `auto/pro-*` (e.g. `auto/pro-coding`) + * - suffix variants with the `:pro` tier (e.g. `auto/coding:pro`) + * + * Non-`pro` `auto/*` ids (auto/coding, auto/best-*, auto/coding:free, …) keep + * their advertised status; the candidate-pool filter in `virtualFactory` (#6512) + * already excludes paid backends from them at request time. `auto/` ids + * are unaffected — the family is a backend selector, not a tier. + */ +export function isPaidTierAutoId(autoId: string): boolean { + if (typeof autoId !== "string" || !autoId.startsWith("auto/")) return false; + const suffix = autoId.slice("auto/".length); + if (suffix.startsWith("pro-")) return true; + const parsed = parseAutoSuffix(suffix); + return parsed.valid && parsed.tier === "pro"; +} + export async function createBuiltinAutoCombo(modelStr: string, suffix: string) { const { createVirtualAutoCombo } = await import("./virtualFactory.ts"); diff --git a/src/app/api/v1/models/catalog.ts b/src/app/api/v1/models/catalog.ts index 521fd2ab2c..f4b1a57319 100644 --- a/src/app/api/v1/models/catalog.ts +++ b/src/app/api/v1/models/catalog.ts @@ -26,6 +26,7 @@ import { AUTO_SUFFIX_VARIANTS, AUTO_FAMILY_IDS, createBuiltinAutoCombo, + isPaidTierAutoId, } from "@omniroute/open-sse/services/autoCombo/builtinCatalog"; import { getAllSyncedAvailableModels, type SyncedAvailableModel } from "@/lib/db/models"; import { getModelCatalogCacheVersion } from "@/lib/db/readCache"; @@ -649,6 +650,11 @@ async function buildUnifiedModelsResponseCore( ...AUTO_FAMILY_IDS, ]) { if (blockedProviders.has("auto") || listedIds.has(autoId)) continue; // #5192 + // #6328 (follow-up to #6495 / #6512): REMOVE — not just hide — paid-tier + // auto/* ids (auto/pro-* + auto/*:pro) from the advertised catalog when the + // operator opts into hidePaidModels. The candidate-pool filter in + // virtualFactory (#6512) still gates request-time routing for the rest. + if (hidePaid && isPaidTierAutoId(autoId)) continue; listedIds.add(autoId); const baseAutoEntry = { id: autoId, diff --git a/tests/unit/autoCombo/paid-tier-auto-id-6328.test.ts b/tests/unit/autoCombo/paid-tier-auto-id-6328.test.ts new file mode 100644 index 0000000000..972fb2ec7b --- /dev/null +++ b/tests/unit/autoCombo/paid-tier-auto-id-6328.test.ts @@ -0,0 +1,38 @@ +/** + * #6328 (follow-up to #6495 / #6512) — regression guard for + * `isPaidTierAutoId`, which lets `/v1/models` REMOVE (not just hide) paid-tier + * `auto/*` ids when the operator opts into `hidePaidModels`. + */ +import { test } from "vitest"; +import assert from "node:assert/strict"; + +import { isPaidTierAutoId } from "../../../open-sse/services/autoCombo/builtinCatalog.ts"; + +test("flat auto/pro-* variants are paid-tier", () => { + assert.equal(isPaidTierAutoId("auto/pro-coding"), true); + assert.equal(isPaidTierAutoId("auto/pro-reasoning"), true); + assert.equal(isPaidTierAutoId("auto/pro-fast"), true); +}); + +test("suffix auto/:pro variants are paid-tier", () => { + assert.equal(isPaidTierAutoId("auto/coding:pro"), true); + assert.equal(isPaidTierAutoId("auto/reasoning:pro"), true); +}); + +test("non-pro auto/* ids are NOT paid-tier (kept in advertised catalog)", () => { + assert.equal(isPaidTierAutoId("auto/coding"), false); + assert.equal(isPaidTierAutoId("auto/coding:free"), false); + assert.equal(isPaidTierAutoId("auto/coding:fast"), false); + assert.equal(isPaidTierAutoId("auto/best-coding"), false); + assert.equal(isPaidTierAutoId("auto/best-free"), false); + assert.equal(isPaidTierAutoId("auto/vision"), false); +}); + +test("non-auto ids and malformed input return false (guard)", () => { + assert.equal(isPaidTierAutoId("openai/gpt-4o"), false); + assert.equal(isPaidTierAutoId(""), false); + assert.equal(isPaidTierAutoId("auto/"), false); + // Argument is typed `string` at the call sites, but the guard tolerates junk. + assert.equal(isPaidTierAutoId(undefined as unknown as string), false); + assert.equal(isPaidTierAutoId(null as unknown as string), false); +});