feat(catalog): add feature flag to disable thinking level variants in catalog (#11971)

Feature flag para desabilitar variantes de nível de thinking no catálogo, com testes de gate e de settings. Validado no worktree combinado. Obrigado!
This commit is contained in:
b3nw
2026-08-30 09:09:19 -05:00
committed by GitHub
parent 4d20d37974
commit 5d07bf32fe
7 changed files with 124 additions and 10 deletions

View File

@@ -0,0 +1 @@
- **feat(catalog):** add `OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS` feature flag to optionally filter out thinking level variants from model catalog ([#PR_NUMBER](https://github.com/diegosouzapw/OmniRoute/pull/PR_NUMBER))

View File

@@ -33,7 +33,11 @@ import {
type CatalogEnrichmentSnapshot,
} from "@/lib/modelMetadataRegistry";
import { createModelCapabilityResolutionSnapshot } from "@/lib/modelCapabilityResolutionSnapshot";
import { isModelCatalogNamesEnabled } from "@/shared/utils/featureFlags";
import {
isModelCatalogNamesEnabled,
isNoThinkingAliasEnabled,
isDisableThinkingLevelVariantsEnabled,
} from "@/shared/utils/featureFlags";
import { extractApiKey } from "@/sse/services/auth";
import { maybeOmitCatalogModelName } from "./catalogHelpers";
import { isCodexModelCatalogClient } from "./catalogRequest";
@@ -85,11 +89,16 @@ export async function applyCatalogPostFilters(
// Advertise no-thinking gateway variants (Fase 8.1). Derived from the already
// key-filtered list, so a variant only appears when its real model is permitted.
// #9418: skip when hideNoThinkVariants is on — the ids are still routable when
// sent explicitly, just not advertised in the catalog.
// sent explicitly, just not advertised in the catalog. The NO_THINKING_ALIAS_ENABLED
// feature flag is the stronger switch: it also stops the ids from routing (see
// src/sse/handlers/chat.ts), so nothing is advertised when it is off. Resolved once
// here and injected, keeping the open-sse helper I/O-free (one flag read per catalog
// build, not one per model).
if (!ctx.hideNoThinkVariants) {
finalModels = appendNoThinkingVariants(
finalModels,
ctx.prefixMode === "canonical" ? ctx.aliasToProviderId : undefined
ctx.prefixMode === "canonical" ? ctx.aliasToProviderId : undefined,
{ featureEnabled: isNoThinkingAliasEnabled() }
);
}
@@ -151,7 +160,9 @@ export async function applyCatalogPostFilters(
// #7694: advertise `<provider>/<model>-<tier>` variants for synced models that
// captured `reasoning.supported_efforts` at sync time (capabilities.effort_tiers).
// Derived from the already key-filtered list; skips codex/kimi (own suffix mechanism).
finalModels = appendSyncedEffortVariants(finalModels);
if (!isDisableThinkingLevelVariantsEnabled()) {
finalModels = appendSyncedEffortVariants(finalModels);
}
await yieldTurn();

View File

@@ -16,6 +16,8 @@ const CATALOG_RELEVANT_FEATURE_FLAGS = new Set([
"MODEL_CATALOG_INCLUDE_NAMES",
"MODELS_CATALOG_PREFIX_MODE",
"EXPOSE_CC_DISCOVERY_ALIASES",
"NO_THINKING_ALIAS_ENABLED",
"OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS",
]);
/**
@@ -60,14 +62,14 @@ export function setFeatureFlagOverride(key: string, value: string): void {
!definition.enumValues.includes(value)
) {
throw new Error(
`Invalid value "${value}" for enum flag ${key}. Allowed: ${definition.enumValues.join(", ")}`,
`Invalid value "${value}" for enum flag ${key}. Allowed: ${definition.enumValues.join(", ")}`
);
}
const db = getDbInstance();
db.prepare("INSERT OR REPLACE INTO key_value (namespace, key, value) VALUES (?, ?, ?)").run(
NAMESPACE,
key,
value,
value
);
if (CATALOG_RELEVANT_FEATURE_FLAGS.has(key)) {
finishModelCatalogWriteWithoutBackup();
@@ -91,10 +93,14 @@ export function removeFeatureFlagOverride(key: string): void {
*/
export function clearAllFeatureFlagOverrides(): void {
const db = getDbInstance();
// Placeholders are derived from the set size — a hardcoded `IN (?, ?, ?)` breaks
// (parameter-count mismatch) the moment a flag is added to the set above.
const catalogFlags = Array.from(CATALOG_RELEVANT_FEATURE_FLAGS);
const placeholders = catalogFlags.map(() => "?").join(", ");
const hadRelevantOverride = Boolean(
db
.prepare("SELECT 1 FROM key_value WHERE namespace = ? AND key IN (?, ?, ?) LIMIT 1")
.get(NAMESPACE, ...Array.from(CATALOG_RELEVANT_FEATURE_FLAGS)),
.prepare(`SELECT 1 FROM key_value WHERE namespace = ? AND key IN (${placeholders}) LIMIT 1`)
.get(NAMESPACE, ...catalogFlags)
);
db.prepare("DELETE FROM key_value WHERE namespace = ?").run(NAMESPACE);
if (hadRelevantOverride) {

View File

@@ -496,6 +496,30 @@ export const FEATURE_FLAG_DEFINITIONS: FeatureFlagDefinition[] = [
requiresRestart: false,
warningLevel: "info",
},
{
key: "NO_THINKING_ALIAS_ENABLED",
label: "No-Thinking Model Aliases",
description:
"Master switch for the no-think/<provider>/<model> gateway aliases. On (default): /v1/models advertises a no-thinking variant for every eligible thinking-capable Claude model, and a no-think/ id sent on a request resolves back to the real model with reasoning suppressed. Off: no variants are advertised and a no-think/ id is treated like any other unknown model id. The per-model ModelSpec.noThinkingAlias opt-in/opt-out still applies while this is on.",
descriptionI18nKey: "featureFlagNoThinkingAliasEnabledDescription",
category: "runtime",
defaultValue: "true",
type: "boolean",
requiresRestart: false,
warningLevel: "info",
},
{
key: "OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS",
label: "Disable Thinking Level Variants",
description:
"Disable the generation of thinking level variants (e.g. -low, -medium, -high) in the /v1/models catalog.",
descriptionI18nKey: "featureFlagOmnirouteDisableThinkingLevelVariantsDescription",
category: "runtime",
defaultValue: "false",
type: "boolean",
requiresRestart: false,
warningLevel: "info",
},
{
key: "OMNIROUTE_CHAT_VIRTUAL_LANES",
label: "Adaptive Virtual Admission Lanes",

View File

@@ -112,6 +112,39 @@ export function getModelsCatalogPrefixMode(): ModelsCatalogPrefixMode {
return "dual";
}
/**
* No-thinking gateway alias master switch (`no-think/<provider>/<model>`).
*
* Fail-safe on: an unreadable flag store must not silently strip catalog
* variants a client already has configured, nor stop suppressing reasoning for
* a `no-think/…` id that was selected precisely to disable thinking. Matches the
* definition default (`"true"`), so the only way the feature turns off is an
* explicit operator override.
*/
export function isNoThinkingAliasEnabled(): boolean {
try {
return isFeatureFlagEnabled("NO_THINKING_ALIAS_ENABLED");
} catch (error) {
console.error(
"[featureFlags] Failed to resolve NO_THINKING_ALIAS_ENABLED, defaulting to enabled:",
error instanceof Error ? error.message : error
);
return true;
}
}
export function isDisableThinkingLevelVariantsEnabled(): boolean {
try {
return isFeatureFlagEnabled("OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS");
} catch (error) {
console.error(
"[featureFlags] Failed to resolve OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS, defaulting to disabled:",
error instanceof Error ? error.message : error
);
return false;
}
}
export function isArenaEloSyncEnabled(): boolean {
return isFeatureFlagEnabled("ARENA_ELO_SYNC_ENABLED");
}

View File

@@ -0,0 +1,25 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { appendSyncedEffortVariants } from "../../open-sse/utils/syncedEffortVariants";
describe("OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS helper behavior", () => {
it("appendSyncedEffortVariants generates variants for eligible models", () => {
const input = [
{
id: "my-provider/my-model",
capabilities: { effort_tiers: ["low", "medium", "high"] },
},
];
const result = appendSyncedEffortVariants(input);
assert.equal(result.length, 4);
assert.deepEqual(
result.map((m) => m.id),
[
"my-provider/my-model",
"my-provider/my-model-low",
"my-provider/my-model-medium",
"my-provider/my-model-high",
]
);
});
});

View File

@@ -29,13 +29,16 @@ const {
isArenaEloSyncEnabled,
isControlPlaneProxyDirectFallbackEnabled,
areContextWindowChecksDisabled,
isDisableThinkingLevelVariantsEnabled,
} = await import("../../src/shared/utils/featureFlags.ts");
// #10889 added OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN, bumping the count to 51.
// The codex-app-server work then added OMNIROUTE_CODEX_APP_SERVER_ENABLED
// (feature flag gating the opt-in Codex app-server WebSocket transport),
// bumping it from 51 to 52.
const EXPECTED_FEATURE_FLAG_COUNT = 52;
// bumping it from 51 to 52. NO_THINKING_ALIAS_ENABLED (master switch for the
// no-think/<provider>/<model> gateway aliases) then bumped it from 52 to 53.
// OMNIROUTE_DISABLE_THINKING_LEVEL_VARIANTS bumped it from 53 to 54.
const EXPECTED_FEATURE_FLAG_COUNT = 54;
// ──────────────────────────────────────────────────────
// Test group 1 — Flag definitions registry
@@ -198,6 +201,17 @@ describe("featureFlagDefinitions", () => {
assert.strictEqual(def.requiresRestart, false);
});
it("defines the no-thinking alias master switch as a runtime boolean enabled by default", () => {
// Default ON: turning the shipped no-think/ alias feature into a flag must not
// silently drop catalog variants operators already point their clients at.
const def = FEATURE_FLAG_DEFINITIONS.find((d) => d.key === "NO_THINKING_ALIAS_ENABLED");
assert.ok(def, "NO_THINKING_ALIAS_ENABLED should exist");
assert.strictEqual(def.category, "runtime");
assert.strictEqual(def.type, "boolean");
assert.strictEqual(def.defaultValue, "true");
assert.strictEqual(def.requiresRestart, false);
});
it("defines CLI profile auto-sync flags as CLI booleans disabled by default", () => {
for (const key of [
"OMNIROUTE_AUTO_SYNC_CODEX_PROFILES",