diff --git a/api/models/tx.js b/api/models/tx.js index ba7a04218..ca69660f2 100644 --- a/api/models/tx.js +++ b/api/models/tx.js @@ -87,6 +87,9 @@ const tokenValues = Object.assign( 'gpt-4.1': { prompt: 2, completion: 8 }, 'gpt-4.5': { prompt: 75, completion: 150 }, 'gpt-4o-mini': { prompt: 0.15, completion: 0.6 }, + 'gpt-5': { prompt: 1.25, completion: 10 }, + 'gpt-5-mini': { prompt: 0.25, completion: 2 }, + 'gpt-5-nano': { prompt: 0.05, completion: 0.4 }, 'gpt-4o': { prompt: 2.5, completion: 10 }, 'gpt-4o-2024-05-13': { prompt: 5, completion: 15 }, 'gpt-4-1106': { prompt: 10, completion: 30 }, @@ -217,6 +220,12 @@ const getValueKey = (model, endpoint) => { return 'gpt-4.1'; } else if (modelName.includes('gpt-4o-2024-05-13')) { return 'gpt-4o-2024-05-13'; + } else if (modelName.includes('gpt-5-nano')) { + return 'gpt-5-nano'; + } else if (modelName.includes('gpt-5-mini')) { + return 'gpt-5-mini'; + } else if (modelName.includes('gpt-5')) { + return 'gpt-5'; } else if (modelName.includes('gpt-4o-mini')) { return 'gpt-4o-mini'; } else if (modelName.includes('gpt-4o')) { diff --git a/api/models/tx.spec.js b/api/models/tx.spec.js index 393a29482..d315d5862 100644 --- a/api/models/tx.spec.js +++ b/api/models/tx.spec.js @@ -25,8 +25,14 @@ describe('getValueKey', () => { expect(getValueKey('gpt-4-some-other-info')).toBe('8k'); }); - it('should return undefined for model names that do not match any known patterns', () => { - expect(getValueKey('gpt-5-some-other-info')).toBeUndefined(); + it('should return "gpt-5" for model name containing "gpt-5"', () => { + expect(getValueKey('gpt-5-some-other-info')).toBe('gpt-5'); + expect(getValueKey('gpt-5-2025-01-30')).toBe('gpt-5'); + expect(getValueKey('gpt-5-2025-01-30-0130')).toBe('gpt-5'); + expect(getValueKey('openai/gpt-5')).toBe('gpt-5'); + expect(getValueKey('openai/gpt-5-2025-01-30')).toBe('gpt-5'); + expect(getValueKey('gpt-5-turbo')).toBe('gpt-5'); + expect(getValueKey('gpt-5-0130')).toBe('gpt-5'); }); it('should return "gpt-3.5-turbo-1106" for model name containing "gpt-3.5-turbo-1106"', () => { @@ -84,6 +90,29 @@ describe('getValueKey', () => { expect(getValueKey('gpt-4.1-nano-0125')).toBe('gpt-4.1-nano'); }); + it('should return "gpt-5" for model type of "gpt-5"', () => { + expect(getValueKey('gpt-5-2025-01-30')).toBe('gpt-5'); + expect(getValueKey('gpt-5-2025-01-30-0130')).toBe('gpt-5'); + expect(getValueKey('openai/gpt-5')).toBe('gpt-5'); + expect(getValueKey('openai/gpt-5-2025-01-30')).toBe('gpt-5'); + expect(getValueKey('gpt-5-turbo')).toBe('gpt-5'); + expect(getValueKey('gpt-5-0130')).toBe('gpt-5'); + }); + + it('should return "gpt-5-mini" for model type of "gpt-5-mini"', () => { + expect(getValueKey('gpt-5-mini-2025-01-30')).toBe('gpt-5-mini'); + expect(getValueKey('openai/gpt-5-mini')).toBe('gpt-5-mini'); + expect(getValueKey('gpt-5-mini-0130')).toBe('gpt-5-mini'); + expect(getValueKey('gpt-5-mini-2025-01-30-0130')).toBe('gpt-5-mini'); + }); + + it('should return "gpt-5-nano" for model type of "gpt-5-nano"', () => { + expect(getValueKey('gpt-5-nano-2025-01-30')).toBe('gpt-5-nano'); + expect(getValueKey('openai/gpt-5-nano')).toBe('gpt-5-nano'); + expect(getValueKey('gpt-5-nano-0130')).toBe('gpt-5-nano'); + expect(getValueKey('gpt-5-nano-2025-01-30-0130')).toBe('gpt-5-nano'); + }); + it('should return "gpt-4o" for model type of "gpt-4o"', () => { expect(getValueKey('gpt-4o-2024-08-06')).toBe('gpt-4o'); expect(getValueKey('gpt-4o-2024-08-06-0718')).toBe('gpt-4o'); @@ -207,6 +236,48 @@ describe('getMultiplier', () => { ); }); + it('should return the correct multiplier for gpt-5', () => { + const valueKey = getValueKey('gpt-5-2025-01-30'); + expect(getMultiplier({ valueKey, tokenType: 'prompt' })).toBe(tokenValues['gpt-5'].prompt); + expect(getMultiplier({ valueKey, tokenType: 'completion' })).toBe( + tokenValues['gpt-5'].completion, + ); + expect(getMultiplier({ model: 'gpt-5-preview', tokenType: 'prompt' })).toBe( + tokenValues['gpt-5'].prompt, + ); + expect(getMultiplier({ model: 'openai/gpt-5', tokenType: 'completion' })).toBe( + tokenValues['gpt-5'].completion, + ); + }); + + it('should return the correct multiplier for gpt-5-mini', () => { + const valueKey = getValueKey('gpt-5-mini-2025-01-30'); + expect(getMultiplier({ valueKey, tokenType: 'prompt' })).toBe(tokenValues['gpt-5-mini'].prompt); + expect(getMultiplier({ valueKey, tokenType: 'completion' })).toBe( + tokenValues['gpt-5-mini'].completion, + ); + expect(getMultiplier({ model: 'gpt-5-mini-preview', tokenType: 'prompt' })).toBe( + tokenValues['gpt-5-mini'].prompt, + ); + expect(getMultiplier({ model: 'openai/gpt-5-mini', tokenType: 'completion' })).toBe( + tokenValues['gpt-5-mini'].completion, + ); + }); + + it('should return the correct multiplier for gpt-5-nano', () => { + const valueKey = getValueKey('gpt-5-nano-2025-01-30'); + expect(getMultiplier({ valueKey, tokenType: 'prompt' })).toBe(tokenValues['gpt-5-nano'].prompt); + expect(getMultiplier({ valueKey, tokenType: 'completion' })).toBe( + tokenValues['gpt-5-nano'].completion, + ); + expect(getMultiplier({ model: 'gpt-5-nano-preview', tokenType: 'prompt' })).toBe( + tokenValues['gpt-5-nano'].prompt, + ); + expect(getMultiplier({ model: 'openai/gpt-5-nano', tokenType: 'completion' })).toBe( + tokenValues['gpt-5-nano'].completion, + ); + }); + it('should return the correct multiplier for gpt-4o', () => { const valueKey = getValueKey('gpt-4o-2024-08-06'); expect(getMultiplier({ valueKey, tokenType: 'prompt' })).toBe(tokenValues['gpt-4o'].prompt); @@ -307,7 +378,7 @@ describe('getMultiplier', () => { }); it('should return defaultRate if derived valueKey does not match any known patterns', () => { - expect(getMultiplier({ tokenType: 'prompt', model: 'gpt-5-some-other-info' })).toBe( + expect(getMultiplier({ tokenType: 'prompt', model: 'gpt-10-some-other-info' })).toBe( defaultRate, ); }); diff --git a/api/utils/tokens.js b/api/utils/tokens.js index f33a82526..0785dda01 100644 --- a/api/utils/tokens.js +++ b/api/utils/tokens.js @@ -19,6 +19,9 @@ const openAIModels = { 'gpt-4.1': 1047576, 'gpt-4.1-mini': 1047576, 'gpt-4.1-nano': 1047576, + 'gpt-5': 400000, + 'gpt-5-mini': 400000, + 'gpt-5-nano': 400000, 'gpt-4o': 127500, // -500 from max 'gpt-4o-mini': 127500, // -500 from max 'gpt-4o-2024-05-13': 127500, // -500 from max @@ -253,6 +256,9 @@ const modelMaxOutputs = { o1: 32268, // -500 from max: 32,768 'o1-mini': 65136, // -500 from max: 65,536 'o1-preview': 32268, // -500 from max: 32,768 + 'gpt-5': 128000, + 'gpt-5-mini': 128000, + 'gpt-5-nano': 128000, 'gpt-oss-20b': 131000, 'gpt-oss-120b': 131000, system_default: 1024, diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js index 246fee80b..cc09bab31 100644 --- a/api/utils/tokens.spec.js +++ b/api/utils/tokens.spec.js @@ -156,6 +156,35 @@ describe('getModelMaxTokens', () => { ); }); + test('should return correct tokens for gpt-5 matches', () => { + expect(getModelMaxTokens('gpt-5')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-5']); + expect(getModelMaxTokens('gpt-5-preview')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-5']); + expect(getModelMaxTokens('openai/gpt-5')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-5']); + expect(getModelMaxTokens('gpt-5-2025-01-30')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-5'], + ); + }); + + test('should return correct tokens for gpt-5-mini matches', () => { + expect(getModelMaxTokens('gpt-5-mini')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-5-mini']); + expect(getModelMaxTokens('gpt-5-mini-preview')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-5-mini'], + ); + expect(getModelMaxTokens('openai/gpt-5-mini')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-5-mini'], + ); + }); + + test('should return correct tokens for gpt-5-nano matches', () => { + expect(getModelMaxTokens('gpt-5-nano')).toBe(maxTokensMap[EModelEndpoint.openAI]['gpt-5-nano']); + expect(getModelMaxTokens('gpt-5-nano-preview')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-5-nano'], + ); + expect(getModelMaxTokens('openai/gpt-5-nano')).toBe( + maxTokensMap[EModelEndpoint.openAI]['gpt-5-nano'], + ); + }); + test('should return correct tokens for Anthropic models', () => { const models = [ 'claude-2.1', @@ -363,6 +392,19 @@ describe('getModelMaxTokens', () => { }); }); + test('should return correct max output tokens for GPT-5 models', () => { + const { getModelMaxOutputTokens } = require('./tokens'); + ['gpt-5', 'gpt-5-mini', 'gpt-5-nano'].forEach((model) => { + expect(getModelMaxOutputTokens(model)).toBe(maxOutputTokensMap[EModelEndpoint.openAI][model]); + expect(getModelMaxOutputTokens(model, EModelEndpoint.openAI)).toBe( + maxOutputTokensMap[EModelEndpoint.openAI][model], + ); + expect(getModelMaxOutputTokens(model, EModelEndpoint.azureOpenAI)).toBe( + maxOutputTokensMap[EModelEndpoint.azureOpenAI][model], + ); + }); + }); + test('should return correct max output tokens for GPT-OSS models', () => { const { getModelMaxOutputTokens } = require('./tokens'); ['gpt-oss-20b', 'gpt-oss-120b'].forEach((model) => { @@ -446,6 +488,25 @@ describe('matchModelName', () => { expect(matchModelName('gpt-4.1-nano-2024-08-06')).toBe('gpt-4.1-nano'); }); + it('should return the closest matching key for gpt-5 matches', () => { + expect(matchModelName('openai/gpt-5')).toBe('gpt-5'); + expect(matchModelName('gpt-5-preview')).toBe('gpt-5'); + expect(matchModelName('gpt-5-2025-01-30')).toBe('gpt-5'); + expect(matchModelName('gpt-5-2025-01-30-0130')).toBe('gpt-5'); + }); + + it('should return the closest matching key for gpt-5-mini matches', () => { + expect(matchModelName('openai/gpt-5-mini')).toBe('gpt-5-mini'); + expect(matchModelName('gpt-5-mini-preview')).toBe('gpt-5-mini'); + expect(matchModelName('gpt-5-mini-2025-01-30')).toBe('gpt-5-mini'); + }); + + it('should return the closest matching key for gpt-5-nano matches', () => { + expect(matchModelName('openai/gpt-5-nano')).toBe('gpt-5-nano'); + expect(matchModelName('gpt-5-nano-preview')).toBe('gpt-5-nano'); + expect(matchModelName('gpt-5-nano-2025-01-30')).toBe('gpt-5-nano'); + }); + // Tests for Google models it('should return the exact model name if it exists in maxTokensMap - Google models', () => { expect(matchModelName('text-bison-32k', EModelEndpoint.google)).toBe('text-bison-32k'); diff --git a/client/src/components/Endpoints/MessageEndpointIcon.tsx b/client/src/components/Endpoints/MessageEndpointIcon.tsx index 1eb859c06..0a9782ce9 100644 --- a/client/src/components/Endpoints/MessageEndpointIcon.tsx +++ b/client/src/components/Endpoints/MessageEndpointIcon.tsx @@ -25,7 +25,7 @@ type EndpointIcon = { function getOpenAIColor(_model: string | null | undefined) { const model = _model?.toLowerCase() ?? ''; - if (model && /\b(o\d)\b/i.test(model)) { + if (model && (/\b(o\d)\b/i.test(model) || /\bgpt-[5-9]\b/i.test(model))) { return '#000000'; } return model.includes('gpt-4') ? '#AB68FF' : '#19C37D';