From bbf96a20fb98d38c8377384e931bd8189256e678 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 17 Jun 2026 11:49:08 +0200 Subject: [PATCH 1/2] Proactively block free models when data collection is not allowed --- .../src/app/api/openrouter/[...path]/route.ts | 13 ++----------- apps/web/src/lib/ai-gateway/models.test.ts | 8 ++++---- apps/web/src/lib/ai-gateway/models.ts | 16 ++++++++++------ 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/apps/web/src/app/api/openrouter/[...path]/route.ts b/apps/web/src/app/api/openrouter/[...path]/route.ts index d2b1bf7b0..ff73b4643 100644 --- a/apps/web/src/app/api/openrouter/[...path]/route.ts +++ b/apps/web/src/app/api/openrouter/[...path]/route.ts @@ -28,7 +28,7 @@ import { isDeadFreeModel, isExcludedForFeature, isKiloExclusiveFreeModel, - isKiloExclusiveModelRequiringDataCollection, + hasBestEffortGuessDataCollectionRequirement, } from '@/lib/ai-gateway/models'; import { isFreeModel } from '@/lib/ai-gateway/is-free-model'; import { @@ -554,17 +554,8 @@ export async function POST(request: NextRequest): Promise { test('requires data collection for paid training-enabled offerings', () => { expect( - isKiloExclusiveModelRequiringDataCollection(claude_opus_4_7_stealth_model.public_id) + hasBestEffortGuessDataCollectionRequirement(claude_opus_4_7_stealth_model.public_id) ).toBe(true); expect( - isKiloExclusiveModelRequiringDataCollection(claude_sonnet_4_6_stealth_model.public_id) + hasBestEffortGuessDataCollectionRequirement(claude_sonnet_4_6_stealth_model.public_id) ).toBe(true); expect( - isKiloExclusiveModelRequiringDataCollection(claude_opus_4_6_stealth_model.public_id) + hasBestEffortGuessDataCollectionRequirement(claude_opus_4_6_stealth_model.public_id) ).toBe(true); }); diff --git a/apps/web/src/lib/ai-gateway/models.ts b/apps/web/src/lib/ai-gateway/models.ts index 7864ae115..1e6125958 100644 --- a/apps/web/src/lib/ai-gateway/models.ts +++ b/apps/web/src/lib/ai-gateway/models.ts @@ -37,6 +37,7 @@ import { GPT_CURRENT_MODEL_ID, isOpenAiModel } from '@/lib/ai-gateway/providers/ import { GLM_CURRENT_MODEL_ID } from '@/lib/ai-gateway/providers/zai'; import { deepseekDiscountedModels } from '@/lib/ai-gateway/providers/deepseek'; import { type ProviderId } from '@/lib/ai-gateway/providers/types'; +import { isFreeModel } from '@/lib/ai-gateway/is-free-model'; export const PRIMARY_DEFAULT_MODEL = CLAUDE_SONNET_CURRENT_MODEL_ID; @@ -93,12 +94,15 @@ export const kiloExclusiveModels = [ stepfun_37_flash_free_model, ] as KiloExclusiveModel[]; -export function isKiloExclusiveModelRequiringDataCollection(model: string): boolean { - return kiloExclusiveModels.some( - m => - m.public_id === model && - m.status !== 'disabled' && - (!m.pricing || m.flags.includes('requires-data-collection')) +export async function hasBestEffortGuessDataCollectionRequirement(model: string): Promise { + return ( + (await isFreeModel(model)) || + kiloExclusiveModels.some( + m => + m.public_id === model && + m.status !== 'disabled' && + m.flags.includes('requires-data-collection') + ) ); } From 2d5a1985d47102e6c8c5231d69948f35f2f216a0 Mon Sep 17 00:00:00 2001 From: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:05:33 +0000 Subject: [PATCH 2/2] fix(ai-gateway): await data collection checks Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../src/app/api/openrouter/[...path]/route.ts | 6 ++- apps/web/src/lib/ai-gateway/is-free-model.ts | 18 +++++++- apps/web/src/lib/ai-gateway/models.test.ts | 45 +++++++++++-------- apps/web/src/lib/ai-gateway/models.ts | 13 ------ 4 files changed, 47 insertions(+), 35 deletions(-) diff --git a/apps/web/src/app/api/openrouter/[...path]/route.ts b/apps/web/src/app/api/openrouter/[...path]/route.ts index ff73b4643..9352326ac 100644 --- a/apps/web/src/app/api/openrouter/[...path]/route.ts +++ b/apps/web/src/app/api/openrouter/[...path]/route.ts @@ -28,9 +28,11 @@ import { isDeadFreeModel, isExcludedForFeature, isKiloExclusiveFreeModel, - hasBestEffortGuessDataCollectionRequirement, } from '@/lib/ai-gateway/models'; -import { isFreeModel } from '@/lib/ai-gateway/is-free-model'; +import { + hasBestEffortGuessDataCollectionRequirement, + isFreeModel, +} from '@/lib/ai-gateway/is-free-model'; import { accountForMicrodollarUsage, captureProxyError, diff --git a/apps/web/src/lib/ai-gateway/is-free-model.ts b/apps/web/src/lib/ai-gateway/is-free-model.ts index cbd8eab67..9592637d2 100644 --- a/apps/web/src/lib/ai-gateway/is-free-model.ts +++ b/apps/web/src/lib/ai-gateway/is-free-model.ts @@ -1,5 +1,9 @@ import { KILO_AUTO_FREE_MODEL } from '@/lib/ai-gateway/auto-model'; -import { isKiloExclusiveFreeModel, isOpenRouterStealthModel } from '@/lib/ai-gateway/models'; +import { + isKiloExclusiveFreeModel, + isOpenRouterStealthModel, + kiloExclusiveModels, +} from '@/lib/ai-gateway/models'; import { isPublicIdExperimented } from '@/lib/ai-gateway/experiments/membership'; /** @@ -22,3 +26,15 @@ export async function isFreeModel(model: string): Promise { (await isPublicIdExperimented(model ?? '')) ); } + +export async function hasBestEffortGuessDataCollectionRequirement(model: string): Promise { + return ( + (await isFreeModel(model)) || + kiloExclusiveModels.some( + candidate => + candidate.public_id === model && + candidate.status !== 'disabled' && + candidate.flags.includes('requires-data-collection') + ) + ); +} diff --git a/apps/web/src/lib/ai-gateway/models.test.ts b/apps/web/src/lib/ai-gateway/models.test.ts index 93591c547..774c96dc8 100644 --- a/apps/web/src/lib/ai-gateway/models.test.ts +++ b/apps/web/src/lib/ai-gateway/models.test.ts @@ -1,11 +1,6 @@ import { describe, test, expect } from '@jest/globals'; -import { - autoFreeModels, - findKiloExclusiveModel, - kiloExclusiveModels, - hasBestEffortGuessDataCollectionRequirement, -} from './models'; -import { isFreeModel } from './is-free-model'; +import { autoFreeModels, findKiloExclusiveModel, kiloExclusiveModels } from './models'; +import { hasBestEffortGuessDataCollectionRequirement, isFreeModel } from './is-free-model'; import { getInferenceProvider } from './providers/kilo-exclusive-model'; import { claude_opus_4_7_stealth_model, @@ -75,18 +70,6 @@ describe('isFreeModel', () => { expect(claude_opus_4_6_stealth_model.public_id).toBe('stealth/claude-opus-4.6'); }); - test('requires data collection for paid training-enabled offerings', () => { - expect( - hasBestEffortGuessDataCollectionRequirement(claude_opus_4_7_stealth_model.public_id) - ).toBe(true); - expect( - hasBestEffortGuessDataCollectionRequirement(claude_sonnet_4_6_stealth_model.public_id) - ).toBe(true); - expect( - hasBestEffortGuessDataCollectionRequirement(claude_opus_4_6_stealth_model.public_id) - ).toBe(true); - }); - test('all Kilo exclusive models should have either no pricing or valid ordered pricing tiers', () => { for (const model of kiloExclusiveModels) { if (model.pricing) { @@ -186,3 +169,27 @@ describe('isFreeModel', () => { }); }); }); + +describe('hasBestEffortGuessDataCollectionRequirement', () => { + test('requires data collection for paid training-enabled offerings', async () => { + expect( + await hasBestEffortGuessDataCollectionRequirement(claude_opus_4_7_stealth_model.public_id) + ).toBe(true); + expect( + await hasBestEffortGuessDataCollectionRequirement(claude_sonnet_4_6_stealth_model.public_id) + ).toBe(true); + expect( + await hasBestEffortGuessDataCollectionRequirement(claude_opus_4_6_stealth_model.public_id) + ).toBe(true); + }); + + test('requires data collection for free models', async () => { + expect(await hasBestEffortGuessDataCollectionRequirement('openrouter/free')).toBe(true); + }); + + test('does not require data collection for regular paid models', async () => { + expect(await hasBestEffortGuessDataCollectionRequirement('anthropic/claude-sonnet-4')).toBe( + false + ); + }); +}); diff --git a/apps/web/src/lib/ai-gateway/models.ts b/apps/web/src/lib/ai-gateway/models.ts index 1e6125958..37b1b8cf0 100644 --- a/apps/web/src/lib/ai-gateway/models.ts +++ b/apps/web/src/lib/ai-gateway/models.ts @@ -37,7 +37,6 @@ import { GPT_CURRENT_MODEL_ID, isOpenAiModel } from '@/lib/ai-gateway/providers/ import { GLM_CURRENT_MODEL_ID } from '@/lib/ai-gateway/providers/zai'; import { deepseekDiscountedModels } from '@/lib/ai-gateway/providers/deepseek'; import { type ProviderId } from '@/lib/ai-gateway/providers/types'; -import { isFreeModel } from '@/lib/ai-gateway/is-free-model'; export const PRIMARY_DEFAULT_MODEL = CLAUDE_SONNET_CURRENT_MODEL_ID; @@ -94,18 +93,6 @@ export const kiloExclusiveModels = [ stepfun_37_flash_free_model, ] as KiloExclusiveModel[]; -export async function hasBestEffortGuessDataCollectionRequirement(model: string): Promise { - return ( - (await isFreeModel(model)) || - kiloExclusiveModels.some( - m => - m.public_id === model && - m.status !== 'disabled' && - m.flags.includes('requires-data-collection') - ) - ); -} - export function isKiloStealthModel(model: string): boolean { return kiloExclusiveModels.some(m => m.public_id === model && m.flags.includes('stealth')); }