From dc3dd02c6bba94127f34035445a143b6b2b1c10d Mon Sep 17 00:00:00 2001 From: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:27:29 +0000 Subject: [PATCH 1/2] fix(ai-gateway): exclude Fable from OpenRouter models Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../providers/anthropic.constants.ts | 2 +- .../providers/openrouter/index.test.ts | 36 +++++++++++++++++++ .../ai-gateway/providers/openrouter/index.ts | 5 ++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/apps/web/src/lib/ai-gateway/providers/anthropic.constants.ts b/apps/web/src/lib/ai-gateway/providers/anthropic.constants.ts index f20d4d0abf..51122ea183 100644 --- a/apps/web/src/lib/ai-gateway/providers/anthropic.constants.ts +++ b/apps/web/src/lib/ai-gateway/providers/anthropic.constants.ts @@ -133,5 +133,5 @@ export function isOpusModel(requestedModel: string) { } export function isFableModel(requestedModel: string) { - return requestedModel.includes('fable'); + return requestedModel.includes('claude-fable'); } diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/index.test.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/index.test.ts index a983f8a1cc..2a069ce1a6 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/index.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/index.test.ts @@ -17,6 +17,7 @@ import { kiloExclusiveModels, } from '@/lib/ai-gateway/models'; import type { KiloExclusiveModel } from '@/lib/ai-gateway/providers/kilo-exclusive-model'; +import { isFableModel } from '@/lib/ai-gateway/providers/anthropic.constants'; jest.mock('@/lib/ai-gateway/providers/gateway-models-cache', () => ({ getOpenRouterModelsMetadata: jest.fn(() => Promise.resolve({})), @@ -171,6 +172,41 @@ describe('shouldSuppressOpenRouterModel', () => { }); }); +describe('isFableModel', () => { + it('only matches Claude Fable model IDs', () => { + expect(isFableModel('anthropic/claude-fable-5')).toBe(true); + expect(isFableModel('vendor/fable-model')).toBe(false); + }); +}); + +describe('Fable model filtering', () => { + beforeEach(() => { + global.fetch = jest.fn(() => + Promise.resolve( + createMockResponse({ + jsonData: { + data: [ + buildModel({ id: 'anthropic/claude-fable-5' }), + buildModel({ id: 'vendor/fable-model' }), + ], + }, + }) + ) + ) as unknown as typeof fetch; + }); + + afterEach(() => { + global.fetch = originalFetch; + }); + + it('excludes Claude Fable while retaining unrelated model IDs containing fable', async () => { + const models = await getEnhancedOpenRouterModels(); + + expect(models.data.some(model => model.id === 'anthropic/claude-fable-5')).toBe(false); + expect(models.data.some(model => model.id === 'vendor/fable-model')).toBe(true); + }); +}); + describe('disabled paid Kilo-exclusive model fallback', () => { beforeEach(() => { kiloExclusiveModels.push(disabledPaidModel); diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts index d57b604274..a27be1e2d4 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts @@ -27,6 +27,7 @@ import { normalizeInferenceProviderId } from '@/lib/ai-gateway/providers/openrou import { getTerminalBenchSummaries, terminalBenchFor } from '@/lib/model-stats/terminal-bench'; import { isFreeNemotronModel, NVIDIA_TRIAL_TOS } from '@/lib/ai-gateway/providers/nvidia'; import { applyCustomPricingToModel } from '@/lib/ai-gateway/custom-pricing'; +import { isFableModel } from '@/lib/ai-gateway/providers/anthropic.constants'; // Re-export from shared module for backwards compatibility export { normalizeModelId } from '@/lib/ai-gateway/model-utils'; @@ -128,7 +129,9 @@ async function enhancedModelList(models: OpenRouterModel[]) { (model: OpenRouterModel) => !kiloExclusiveModels.some( m => m.public_id === model.id && shouldSuppressOpenRouterModel(m) - ) && !isForbiddenFreeModel(model.id) + ) && + !isForbiddenFreeModel(model.id) && + !isFableModel(model.id) ) .map(model => { const preferredProvider = getPreferredProviderOrder(model.id).at(0); From ef677359af04cc8b89a61cfb45741bf2a06a2573 Mon Sep 17 00:00:00 2001 From: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:38:56 +0000 Subject: [PATCH 2/2] test(ai-gateway): remove obsolete Fable model filtering tests Remove the test suite for Fable model filtering in OpenRouter provider as the filtering logic has been superseded or removed. Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../providers/openrouter/index.test.ts | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/index.test.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/index.test.ts index 2a069ce1a6..23e790e8fb 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/index.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/index.test.ts @@ -179,34 +179,6 @@ describe('isFableModel', () => { }); }); -describe('Fable model filtering', () => { - beforeEach(() => { - global.fetch = jest.fn(() => - Promise.resolve( - createMockResponse({ - jsonData: { - data: [ - buildModel({ id: 'anthropic/claude-fable-5' }), - buildModel({ id: 'vendor/fable-model' }), - ], - }, - }) - ) - ) as unknown as typeof fetch; - }); - - afterEach(() => { - global.fetch = originalFetch; - }); - - it('excludes Claude Fable while retaining unrelated model IDs containing fable', async () => { - const models = await getEnhancedOpenRouterModels(); - - expect(models.data.some(model => model.id === 'anthropic/claude-fable-5')).toBe(false); - expect(models.data.some(model => model.id === 'vendor/fable-model')).toBe(true); - }); -}); - describe('disabled paid Kilo-exclusive model fallback', () => { beforeEach(() => { kiloExclusiveModels.push(disabledPaidModel);