diff --git a/src/_locales/de/main.json b/src/_locales/de/main.json index ad4a25d9..b3845deb 100644 --- a/src/_locales/de/main.json +++ b/src/_locales/de/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/en/main.json b/src/_locales/en/main.json index 0bc75df8..a6f0d29e 100644 --- a/src/_locales/en/main.json +++ b/src/_locales/en/main.json @@ -198,5 +198,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/es/main.json b/src/_locales/es/main.json index 4ea2c208..7f916a9c 100644 --- a/src/_locales/es/main.json +++ b/src/_locales/es/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/fr/main.json b/src/_locales/fr/main.json index e718e2a7..c9341943 100644 --- a/src/_locales/fr/main.json +++ b/src/_locales/fr/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/in/main.json b/src/_locales/in/main.json index 0e19c9f0..1fa5b7d9 100644 --- a/src/_locales/in/main.json +++ b/src/_locales/in/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/it/main.json b/src/_locales/it/main.json index 60cf3f6d..400fa546 100644 --- a/src/_locales/it/main.json +++ b/src/_locales/it/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/ja/main.json b/src/_locales/ja/main.json index 75010c90..ac19edc8 100644 --- a/src/_locales/ja/main.json +++ b/src/_locales/ja/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/ko/main.json b/src/_locales/ko/main.json index 856614e4..4348a7c1 100644 --- a/src/_locales/ko/main.json +++ b/src/_locales/ko/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/pt/main.json b/src/_locales/pt/main.json index c71da47b..d3a5bc56 100644 --- a/src/_locales/pt/main.json +++ b/src/_locales/pt/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/ru/main.json b/src/_locales/ru/main.json index 45f233a5..b6852bbd 100644 --- a/src/_locales/ru/main.json +++ b/src/_locales/ru/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/tr/main.json b/src/_locales/tr/main.json index 042879bc..4bf6bb9d 100644 --- a/src/_locales/tr/main.json +++ b/src/_locales/tr/main.json @@ -197,5 +197,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/zh-hans/main.json b/src/_locales/zh-hans/main.json index 55d2446e..01c4299e 100644 --- a/src/_locales/zh-hans/main.json +++ b/src/_locales/zh-hans/main.json @@ -204,5 +204,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/_locales/zh-hant/main.json b/src/_locales/zh-hant/main.json index e1c7c603..25686ca2 100644 --- a/src/_locales/zh-hant/main.json +++ b/src/_locales/zh-hant/main.json @@ -199,5 +199,7 @@ "OpenAI (GPT-5.2)": "OpenAI (GPT-5.2)", "OpenAI (GPT-5.3 latest)": "OpenAI (GPT-5.3 latest)", "OpenAI (GPT-5.4)": "OpenAI (GPT-5.4)", + "OpenAI (GPT-5.4 mini)": "OpenAI (GPT-5.4 mini)", + "OpenAI (GPT-5.4 nano)": "OpenAI (GPT-5.4 nano)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)" } diff --git a/src/config/index.mjs b/src/config/index.mjs index d425b78b..cb1e0c3e 100644 --- a/src/config/index.mjs +++ b/src/config/index.mjs @@ -58,6 +58,8 @@ export const chatgptApiModelKeys = [ 'chatgptApi5_2', 'chatgptApi5_3Latest', 'chatgptApi5_4', + 'chatgptApi5_4Mini', + 'chatgptApi5_4Nano', 'chatgptApi4oMini', 'chatgptApi4_8k', 'chatgptApi4_8k_0613', @@ -260,6 +262,8 @@ export const Models = { chatgptApi5_2: { value: 'gpt-5.2', desc: 'OpenAI (GPT-5.2)' }, chatgptApi5_3Latest: { value: 'gpt-5.3-chat-latest', desc: 'OpenAI (GPT-5.3 latest)' }, chatgptApi5_4: { value: 'gpt-5.4', desc: 'OpenAI (GPT-5.4)' }, + chatgptApi5_4Mini: { value: 'gpt-5.4-mini', desc: 'OpenAI (GPT-5.4 mini)' }, + chatgptApi5_4Nano: { value: 'gpt-5.4-nano', desc: 'OpenAI (GPT-5.4 nano)' }, chatgptApi4_1: { value: 'gpt-4.1', desc: 'OpenAI (GPT-4.1)' }, chatgptApi4_1_mini: { value: 'gpt-4.1-mini', desc: 'OpenAI (GPT-4.1 mini)' }, diff --git a/tests/unit/config/config-predicates.test.mjs b/tests/unit/config/config-predicates.test.mjs index f5dfae19..8ea2f9a6 100644 --- a/tests/unit/config/config-predicates.test.mjs +++ b/tests/unit/config/config-predicates.test.mjs @@ -34,6 +34,8 @@ const representativeChatgptApiModelNames = [ 'chatgptApi5_1', 'chatgptApi5_2', 'chatgptApi5_4', + 'chatgptApi5_4Mini', + 'chatgptApi5_4Nano', ] const representativeGptCompletionApiModelNames = ['gptApiInstruct'] const representativeClaudeApiModelNames = ['claude37SonnetApi', 'claudeOpus4Api'] diff --git a/tests/unit/services/apis/openai-api-compat.test.mjs b/tests/unit/services/apis/openai-api-compat.test.mjs index 89292f1b..39b89059 100644 --- a/tests/unit/services/apis/openai-api-compat.test.mjs +++ b/tests/unit/services/apis/openai-api-compat.test.mjs @@ -209,6 +209,76 @@ test('generateAnswersWithOpenAiApi uses OpenAI token params for a latest mapped assert.equal(Object.hasOwn(body, 'max_tokens'), false) }) +test('generateAnswersWithOpenAiApi uses max_completion_tokens for GPT-5.4 mini', async (t) => { + t.mock.method(console, 'debug', () => {}) + setStorage({ + customOpenAiApiUrl: 'https://api.openai.example.com', + maxConversationContextLength: 3, + maxResponseTokenLength: 444, + temperature: 0.3, + }) + + const session = { + modelName: 'chatgptApi5_4Mini', + conversationRecords: [], + isRetry: false, + } + const port = createFakePort() + + let capturedInput + let capturedInit + t.mock.method(globalThis, 'fetch', async (input, init) => { + capturedInput = input + capturedInit = init + return createMockSseResponse([ + 'data: {"choices":[{"delta":{"content":"OK"},"finish_reason":"stop"}]}\n\n', + ]) + }) + + await generateAnswersWithOpenAiApi(port, 'CurrentQ', session, 'sk-test') + + const body = JSON.parse(capturedInit.body) + assert.equal(capturedInput, 'https://api.openai.example.com/v1/chat/completions') + assert.equal(body.model, 'gpt-5.4-mini') + assert.equal(body.max_completion_tokens, 444) + assert.equal(Object.hasOwn(body, 'max_tokens'), false) +}) + +test('generateAnswersWithOpenAiApi uses max_completion_tokens for GPT-5.4 nano', async (t) => { + t.mock.method(console, 'debug', () => {}) + setStorage({ + customOpenAiApiUrl: 'https://api.openai.example.com', + maxConversationContextLength: 3, + maxResponseTokenLength: 555, + temperature: 0.3, + }) + + const session = { + modelName: 'chatgptApi5_4Nano', + conversationRecords: [], + isRetry: false, + } + const port = createFakePort() + + let capturedInput + let capturedInit + t.mock.method(globalThis, 'fetch', async (input, init) => { + capturedInput = input + capturedInit = init + return createMockSseResponse([ + 'data: {"choices":[{"delta":{"content":"OK"},"finish_reason":"stop"}]}\n\n', + ]) + }) + + await generateAnswersWithOpenAiApi(port, 'CurrentQ', session, 'sk-test') + + const body = JSON.parse(capturedInit.body) + assert.equal(capturedInput, 'https://api.openai.example.com/v1/chat/completions') + assert.equal(body.model, 'gpt-5.4-nano') + assert.equal(body.max_completion_tokens, 555) + assert.equal(Object.hasOwn(body, 'max_tokens'), false) +}) + test('generateAnswersWithOpenAiApiCompat keeps max_tokens for latest mapped gpt-5 models in compat provider', async (t) => { t.mock.method(console, 'debug', () => {}) setStorage({ diff --git a/tests/unit/services/apis/openai-token-params.test.mjs b/tests/unit/services/apis/openai-token-params.test.mjs index 53913595..4c50a250 100644 --- a/tests/unit/services/apis/openai-token-params.test.mjs +++ b/tests/unit/services/apis/openai-token-params.test.mjs @@ -22,6 +22,8 @@ test('uses max_completion_tokens for recent gpt-5.x model names', () => { 'gpt-5.2-chat-latest', 'gpt-5.3', 'gpt-5.3-chat-latest', + 'gpt-5.4-mini', + 'gpt-5.4-nano', ] for (const model of models) { diff --git a/tests/unit/utils/model-name-convert.test.mjs b/tests/unit/utils/model-name-convert.test.mjs index 73e00dbf..fb1fe488 100644 --- a/tests/unit/utils/model-name-convert.test.mjs +++ b/tests/unit/utils/model-name-convert.test.mjs @@ -123,6 +123,8 @@ test('modelNameToDesc returns desc for GPT-5 stable presets', () => { assert.equal(modelNameToDesc('chatgptApi5_1'), 'OpenAI (GPT-5.1)') assert.equal(modelNameToDesc('chatgptApi5_2'), 'OpenAI (GPT-5.2)') assert.equal(modelNameToDesc('chatgptApi5_4'), 'OpenAI (GPT-5.4)') + assert.equal(modelNameToDesc('chatgptApi5_4Mini'), 'OpenAI (GPT-5.4 mini)') + assert.equal(modelNameToDesc('chatgptApi5_4Nano'), 'OpenAI (GPT-5.4 nano)') }) test('modelNameToDesc appends extraCustomModelName for customModel', () => {