Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/web/src/lib/ai-gateway/providers/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,20 @@ export const GPT_CURRENT_VERCEL_MODEL_ID = GPT_CURRENT_MODEL_ID;
export const GPT_MINI_CURRENT_MODEL_ID = 'openai/gpt-5.4-mini';

export const GPT_MINI_CURRENT_VERCEL_MODEL_ID = GPT_MINI_CURRENT_MODEL_ID;

// OpenRouter BYOK must be disabled for these models so requests use the discounted endpoint.
export const ENABLE_OPENROUTER_GPT56_PROMO = true;

export const OPENROUTER_GPT56_PROMO_MODEL_IDS = [
'openai/gpt-5.6-terra',
'openai/gpt-5.6-terra-pro',
'openai/gpt-5.6-luna',
'openai/gpt-5.6-luna-pro',
] as const;

export function isOpenRouterGpt56PromoModel(modelId: string): boolean {
return (
ENABLE_OPENROUTER_GPT56_PROMO &&
OPENROUTER_GPT56_PROMO_MODEL_IDS.some(promoModelId => promoModelId === modelId)
);
}
34 changes: 34 additions & 0 deletions apps/web/src/lib/ai-gateway/providers/openrouter/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals
import {
formatName,
getEnhancedOpenRouterModels,
getModelDisplayPricing,
getOpenRouterTranscriptionModels,
shouldSuppressOpenRouterModel,
undoPricingDiscount,
Expand All @@ -19,6 +20,7 @@ import {
import type { KiloExclusiveModel } from '@/lib/ai-gateway/providers/kilo-exclusive-model';
import { isFableModel } from '@/lib/ai-gateway/providers/anthropic.constants';
import { KILO_AUTO_EFFICIENT_MODEL } from '@/lib/ai-gateway/auto-model';
import { OPENROUTER_GPT56_PROMO_MODEL_IDS } from '@/lib/ai-gateway/providers/openai';

jest.mock('@/lib/ai-gateway/providers/gateway-models-cache', () => ({
getOpenRouterModelsMetadataFromDatabase: jest.fn(() => Promise.resolve({})),
Expand Down Expand Up @@ -150,6 +152,17 @@ describe('formatName', () => {
});
expect(formatName(model, NOT_PREFERRED)).toBe('OpenRouter Test Model ($$$$)');
});

it.each(OPENROUTER_GPT56_PROMO_MODEL_IDS)('prefers the 50% off marker for %s', modelId => {
const recentlyCreated = Math.floor(Date.now() / 1000) - 24 * 3600;
const model = buildModel({
id: modelId,
created: recentlyCreated,
expiration_date: '2026-07-01',
pricing: { prompt: '0.00001', completion: '0' },
});
expect(formatName(model, 0)).toBe('Test Model (50% off)');
});
});

describe('undoPricingDiscount', () => {
Expand Down Expand Up @@ -191,6 +204,27 @@ describe('undoPricingDiscount', () => {
});
});

describe('OpenRouter GPT-5.6 promotion', () => {
const discountedPricing = {
prompt: '0.000001',
completion: '0.000004',
input_cache_read: '0.0000001',
discount: 0.5,
};

it.each(OPENROUTER_GPT56_PROMO_MODEL_IDS)('preserves discounted pricing for %s', modelId => {
expect(getModelDisplayPricing(modelId, discountedPricing)).toBe(discountedPricing);
});

it('continues to undo endpoint discounts for other models', () => {
expect(getModelDisplayPricing('openai/gpt-5.6-sol', discountedPricing)).toEqual({
prompt: '0.000002000000',
completion: '0.000008000000',
input_cache_read: '0.000000200000',
});
});
});

describe('shouldSuppressOpenRouterModel', () => {
it('does not suppress disabled paid Kilo-exclusive models returned by OpenRouter', () => {
expect(disabledPaidModel.pricing).not.toBeNull();
Expand Down
12 changes: 11 additions & 1 deletion apps/web/src/lib/ai-gateway/providers/openrouter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { getTerminalBenchSummaries, terminalBenchFor } from '@/lib/model-stats/t
import { isFreeNemotronModel, NVIDIA_TRIAL_TOS } from '@/lib/ai-gateway/providers/nvidia';
import { applyCustomPricingToModel } from '@/lib/ai-gateway/custom-pricing';
import { addMonths } from 'date-fns';
import { isOpenRouterGpt56PromoModel } from '@/lib/ai-gateway/providers/openai';

// Re-export from shared module for backwards compatibility
export { normalizeModelId } from '@/lib/ai-gateway/model-utils';
Expand Down Expand Up @@ -80,6 +81,7 @@ export function formatName(model: OpenRouterModel, preferredIndex: number) {
model.id.startsWith('openrouter/') && !model.name.includes('OpenRouter')
? 'OpenRouter ' + model.name
: model.name;
if (isOpenRouterGpt56PromoModel(model.id)) return name + ' (50% off)';
const promptPrice = Number.parseFloat(model.pricing.prompt);
const isExpensive = Number.isFinite(promptPrice) && promptPrice >= 0.00001; // Opus 4.8 Fast price
if (isExpensive) return name + ' ($$$$)';
Expand Down Expand Up @@ -118,6 +120,14 @@ export function undoPricingDiscount(pricing: EndpointPricing): EndpointPricing {
return result;
}

export function getModelDisplayPricing(
modelId: string,
pricing: EndpointPricing | undefined
): EndpointPricing | undefined {
if (!pricing) return undefined;
return isOpenRouterGpt56PromoModel(modelId) ? pricing : undoPricingDiscount(pricing);
Comment thread
chrarnoldus marked this conversation as resolved.
}

export function shouldSuppressOpenRouterModel(model: KiloExclusiveModel): boolean {
return model.status !== 'disabled' || model.pricing === null;
}
Expand Down Expand Up @@ -151,7 +161,7 @@ async function enhancedModelList(models: OpenRouterModel[]) {
normalizeInferenceProviderId(e.tag) ===
normalizeInferenceProviderId(preferredProvider)
)?.pricing);
const pricing = rawPricing ? undoPricingDiscount(rawPricing) : rawPricing;
const pricing = getModelDisplayPricing(model.id, rawPricing);
const terminalBench = terminalBenchFor(summaries, model.id);
return {
...model,
Expand Down
23 changes: 22 additions & 1 deletion apps/web/src/lib/ai-gateway/providers/vercel/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { describe, it, expect } from '@jest/globals';
import { describe, it, expect, jest } from '@jest/globals';
import {
convertProviderOptions,
getAnthropicProviderOptionsForVercel,
getVercelInferenceProvidersExcludingIgnored,
hasCompatibleVercelInferenceProvider,
passesVercelRoutingPercentage,
shouldRouteToVercel,
} from '@/lib/ai-gateway/providers/vercel';
import { getRandomNumber } from '@/lib/ai-gateway/getRandomNumber';
import type { GatewayRequest } from '@/lib/ai-gateway/providers/openrouter/types';
import { OPENROUTER_GPT56_PROMO_MODEL_IDS } from '@/lib/ai-gateway/providers/openai';

describe('getAnthropicProviderOptionsForVercel', () => {
it('maps chat completion verbosity to Anthropic effort', () => {
Expand Down Expand Up @@ -154,3 +156,22 @@ describe('passesVercelRoutingPercentage', () => {
expect(seedsInFinalBucket.some(seed => !passesVercelRoutingPercentage(seed, 99.9))).toBe(true);
});
});

describe('OpenRouter GPT-5.6 promotion', () => {
it.each(OPENROUTER_GPT56_PROMO_MODEL_IDS)('does not route %s to Vercel', async model => {
const debugSpy = jest.spyOn(console, 'debug').mockImplementation(() => {});
const request: GatewayRequest = {
kind: 'chat_completions',
body: {
model,
messages: [{ role: 'user', content: 'hello' }],
},
};

await expect(shouldRouteToVercel(model, request, 'promo-test')).resolves.toBe(false);
expect(debugSpy).toHaveBeenCalledWith(
`[shouldRouteToVercel] routing ${model} to OpenRouter for the GPT-5.6 promotion`
);
debugSpy.mockRestore();
});
});
8 changes: 8 additions & 0 deletions apps/web/src/lib/ai-gateway/providers/vercel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
getVercelModelsFromRedis,
} from '@/lib/ai-gateway/providers/gateway-models-cache';
import type { AnthropicProviderOptions } from '@ai-sdk/anthropic';
import { isOpenRouterGpt56PromoModel } from '@/lib/ai-gateway/providers/openai';

type VercelRoutingPercentages = {
paid: number;
Expand Down Expand Up @@ -94,6 +95,13 @@ export async function shouldRouteToVercel(
request: GatewayRequest,
randomSeed: string
) {
if (isOpenRouterGpt56PromoModel(requestedModel)) {
console.debug(
`[shouldRouteToVercel] routing ${requestedModel} to OpenRouter for the GPT-5.6 promotion`
);
return false;
}

console.debug('[shouldRouteToVercel] randomizing user to either OpenRouter or Vercel');
const percentages = await getVercelRoutingPercentages();
const routingPercentage = (await isFreeModel(requestedModel))
Expand Down