mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-19 05:12:16 +03:00
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:
1
changelog.d/features/disable-thinking-level-variants.md
Normal file
1
changelog.d/features/disable-thinking-level-variants.md
Normal 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))
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
25
tests/unit/disable-thinking-level-variants-gate.test.ts
Normal file
25
tests/unit/disable-thinking-level-variants-gate.test.ts
Normal 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",
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user