Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions src/config/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const chatgptApiModelKeys = [
'chatgptApi35_1106',
'chatgptApi35_0125',
'chatgptApi4o_128k',
'chatgptApiChatLatest',
'chatgptApi5Latest',
'chatgptApi5',
'chatgptApi5_1Latest',
Expand Down Expand Up @@ -254,6 +255,7 @@ export const Models = {
value: 'gpt-4-0125-preview',
desc: 'OpenAI (GPT-4-Turbo 128k 0125 Preview)',
},
chatgptApiChatLatest: { value: 'chat-latest', desc: 'OpenAI (Chat latest)' },
chatgptApi5Latest: { value: 'gpt-5-chat-latest', desc: 'OpenAI (GPT-5 latest)' },
chatgptApi5: { value: 'gpt-5', desc: 'OpenAI (GPT-5)' },
chatgptApi5_1Latest: { value: 'gpt-5.1-chat-latest', desc: 'OpenAI (GPT-5.1 latest)' },
Expand Down
4 changes: 2 additions & 2 deletions src/services/apis/openai-token-params.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
const GPT5_CHAT_COMPLETIONS_MODEL_PATTERN = /^gpt-5([.-]|$)/
const OPENAI_MAX_COMPLETION_TOKENS_MODEL_PATTERN = /^(?:gpt-5(?:[.-]|$)|chat-latest$)/

function shouldUseMaxCompletionTokens(provider, model) {
const normalizedProvider = String(provider || '').toLowerCase()
const normalizedModel = String(model || '').toLowerCase()

switch (true) {
case normalizedProvider === 'openai' &&
GPT5_CHAT_COMPLETIONS_MODEL_PATTERN.test(normalizedModel):
OPENAI_MAX_COMPLETION_TOKENS_MODEL_PATTERN.test(normalizedModel):
return true
default:
return false
Expand Down
26 changes: 14 additions & 12 deletions tests/unit/services/apis/openai-api-compat.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {
import { createFakePort } from '../../helpers/port.mjs'
import { createMockSseResponse } from '../../helpers/sse-response.mjs'

const gpt5LatestCompatModelNames = [
const latestCompatModelNames = [
'chatgptApi-chat-latest',
'chatgptApi-gpt-5-chat-latest',
'chatgptApi-gpt-5.1-chat-latest',
'chatgptApi-gpt-5.2-chat-latest',
'chatgptApi-gpt-5.3-chat-latest',
]
const gpt5LatestMappedModels = [
const latestMappedModels = [
['chatgptApiChatLatest', 'chat-latest'],
['chatgptApi5Latest', 'gpt-5-chat-latest'],
['chatgptApi5_1Latest', 'gpt-5.1-chat-latest'],
['chatgptApi5_2Latest', 'gpt-5.2-chat-latest'],
Expand Down Expand Up @@ -93,7 +95,7 @@ test('generateAnswersWithOpenAiApiCompat sends expected request and aggregates S
assert.deepEqual(session.conversationRecords.at(-1), { question: 'CurrentQ', answer: 'Hello' })
})

test('generateAnswersWithOpenAiApiCompat uses max_completion_tokens for OpenAI latest gpt-5 compat models', async (t) => {
test('generateAnswersWithOpenAiApiCompat uses max_completion_tokens for OpenAI latest compat models', async (t) => {
t.mock.method(console, 'debug', () => {})
setStorage({
maxConversationContextLength: 3,
Expand All @@ -108,7 +110,7 @@ test('generateAnswersWithOpenAiApiCompat uses max_completion_tokens for OpenAI l
])
})

for (const modelName of gpt5LatestCompatModelNames) {
for (const modelName of latestCompatModelNames) {
capturedInit = undefined
const session = {
modelName,
Expand All @@ -133,7 +135,7 @@ test('generateAnswersWithOpenAiApiCompat uses max_completion_tokens for OpenAI l
}
})

test('generateAnswersWithOpenAiApiCompat uses latest mapped gpt-5 API model values', async (t) => {
test('generateAnswersWithOpenAiApiCompat uses latest mapped API model values', async (t) => {
t.mock.method(console, 'debug', () => {})
setStorage({
maxConversationContextLength: 3,
Expand All @@ -148,7 +150,7 @@ test('generateAnswersWithOpenAiApiCompat uses latest mapped gpt-5 API model valu
])
})

for (const [modelName, expectedModel] of gpt5LatestMappedModels) {
for (const [modelName, expectedModel] of latestMappedModels) {
capturedInit = undefined
const session = {
modelName,
Expand Down Expand Up @@ -279,7 +281,7 @@ test('generateAnswersWithOpenAiApi uses max_completion_tokens for GPT-5.4 nano',
assert.equal(Object.hasOwn(body, 'max_tokens'), false)
})

test('generateAnswersWithOpenAiApiCompat keeps max_tokens for latest mapped gpt-5 models in compat provider', async (t) => {
test('generateAnswersWithOpenAiApiCompat keeps max_tokens for latest mapped models in compat provider', async (t) => {
t.mock.method(console, 'debug', () => {})
setStorage({
maxConversationContextLength: 3,
Expand All @@ -294,7 +296,7 @@ test('generateAnswersWithOpenAiApiCompat keeps max_tokens for latest mapped gpt-
])
})

for (const [modelName, expectedModel] of gpt5LatestMappedModels) {
for (const [modelName, expectedModel] of latestMappedModels) {
capturedInit = undefined
const session = {
modelName,
Expand Down Expand Up @@ -361,7 +363,7 @@ test('generateAnswersWithOpenAiApiCompat removes conflicting token key from extr
assert.equal(body.top_p, 0.9)
})

test('generateAnswersWithOpenAiApiCompat removes max_tokens from extraBody for OpenAI gpt-5 models', async (t) => {
test('generateAnswersWithOpenAiApiCompat removes max_tokens from extraBody for OpenAI latest models', async (t) => {
t.mock.method(console, 'debug', () => {})
setStorage({
maxConversationContextLength: 3,
Expand All @@ -376,7 +378,7 @@ test('generateAnswersWithOpenAiApiCompat removes max_tokens from extraBody for O
])
})

for (const modelName of gpt5LatestCompatModelNames) {
for (const modelName of latestCompatModelNames) {
capturedInit = undefined
const session = {
modelName,
Expand Down Expand Up @@ -446,7 +448,7 @@ test('generateAnswersWithOpenAiApiCompat allows max_tokens override for compat p
assert.equal(body.top_p, 0.75)
})

test('generateAnswersWithOpenAiApiCompat allows max_completion_tokens override for OpenAI gpt-5 models', async (t) => {
test('generateAnswersWithOpenAiApiCompat allows max_completion_tokens override for OpenAI latest models', async (t) => {
t.mock.method(console, 'debug', () => {})
setStorage({
maxConversationContextLength: 3,
Expand All @@ -461,7 +463,7 @@ test('generateAnswersWithOpenAiApiCompat allows max_completion_tokens override f
])
})

for (const modelName of gpt5LatestCompatModelNames) {
for (const modelName of latestCompatModelNames) {
capturedInit = undefined
const session = {
modelName,
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/services/apis/openai-token-params.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ test('uses max_completion_tokens for gpt-5.x chat models', () => {
})
})

test('uses max_completion_tokens for chat-latest model', () => {
assert.deepEqual(getChatCompletionsTokenParams('openai', 'chat-latest', 1025), {
max_completion_tokens: 1025,
})
})

test('uses max_tokens for provider-prefixed gpt-5.x model names', () => {
assert.deepEqual(getChatCompletionsTokenParams('openai', 'openai/gpt-5.2', 2048), {
max_tokens: 2048,
Expand Down Expand Up @@ -49,6 +55,9 @@ test('uses max_tokens for lookalike model names', () => {
assert.deepEqual(getChatCompletionsTokenParams('openai', 'my-gpt-5-clone', 640), {
max_tokens: 640,
})
assert.deepEqual(getChatCompletionsTokenParams('openai', 'my-chat-latest', 641), {
max_tokens: 641,
})
})

test('uses max_tokens for empty model values', () => {
Expand Down
1 change: 1 addition & 0 deletions tests/unit/utils/model-name-convert.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ test('modelNameToValue returns value for known model', () => {
})

test('modelNameToValue returns endpoint for latest chatgptApi models', () => {
assert.equal(modelNameToValue('chatgptApiChatLatest'), 'chat-latest')
assert.equal(modelNameToValue('chatgptApi5Latest'), 'gpt-5-chat-latest')
assert.equal(modelNameToValue('chatgptApi5_1Latest'), 'gpt-5.1-chat-latest')
assert.equal(modelNameToValue('chatgptApi5_2Latest'), 'gpt-5.2-chat-latest')
Expand Down