From 8a406ae823c60290b4dcdf7acc898da47807d0ea Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Wed, 23 Sep 2026 03:32:25 +0800 Subject: [PATCH] Add OpenAI GPT-6 Sol and GPT-6 Luna model Register GPT-6 Sol and Luna as built-in OpenAI models and replace their GPT-5.6 counterparts in the default preset list. Use GPT-6-compatible completion and temperature parameters while keeping the GPT-5.6 presets available for existing configurations. References: - https://developers.openai.com/api/docs/models/gpt-6-sol - https://developers.openai.com/api/docs/models/gpt-6-luna - https://openai.com/index/introducing-gpt-6-sol-and-luna/ --- src/_locales/en/main.json | 2 ++ src/config/index.mjs | 8 ++++-- src/services/apis/openai-token-params.mjs | 2 +- src/services/apis/temperature-params.mjs | 2 ++ tests/unit/config/config-predicates.test.mjs | 10 ++++++++ .../services/apis/openai-api-compat.test.mjs | 25 +++++++++++++++++-- .../apis/openai-token-params.test.mjs | 18 ++++++++++--- .../services/apis/temperature-params.test.mjs | 8 ++++-- tests/unit/utils/model-name-convert.test.mjs | 4 ++- 9 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/_locales/en/main.json b/src/_locales/en/main.json index 303721fdf..5c7099bdd 100644 --- a/src/_locales/en/main.json +++ b/src/_locales/en/main.json @@ -227,6 +227,8 @@ "OpenAI (GPT-5.6 Terra)": "OpenAI (GPT-5.6 Terra)", "OpenAI (GPT-5.6 Luna)": "OpenAI (GPT-5.6 Luna)", "OpenAI (GPT-6 Astra)": "OpenAI (GPT-6 Astra)", + "OpenAI (GPT-6 Sol)": "OpenAI (GPT-6 Sol)", + "OpenAI (GPT-6 Luna)": "OpenAI (GPT-6 Luna)", "Anthropic (Claude Sonnet 4.6)": "Anthropic (Claude Sonnet 4.6)", "This provider is still used by other API modes or saved conversations": "This provider is still used by other API modes or saved conversations", "This API key is set on the selected custom mode. Editing it here will create a dedicated provider for that mode.": "This API key is set on the selected custom mode. Editing it here will create a dedicated provider for that mode.", diff --git a/src/config/index.mjs b/src/config/index.mjs index 87309080f..8f3dc2dd1 100644 --- a/src/config/index.mjs +++ b/src/config/index.mjs @@ -80,6 +80,8 @@ export const chatgptApiModelKeys = [ 'chatgptApi5_6Terra', 'chatgptApi5_6Luna', 'chatgptApi6Astra', + 'chatgptApi6Sol', + 'chatgptApi6Luna', 'chatgptApi4oMini', 'chatgptApi4_1', 'chatgptApi4_1_mini', @@ -335,6 +337,8 @@ export const Models = { chatgptApi5_6Terra: { value: 'gpt-5.6-terra', desc: 'OpenAI (GPT-5.6 Terra)' }, chatgptApi5_6Luna: { value: 'gpt-5.6-luna', desc: 'OpenAI (GPT-5.6 Luna)' }, chatgptApi6Astra: { value: 'gpt-6-astra', desc: 'OpenAI (GPT-6 Astra)' }, + chatgptApi6Sol: { value: 'gpt-6-sol', desc: 'OpenAI (GPT-6 Sol)' }, + chatgptApi6Luna: { value: 'gpt-6-luna', desc: 'OpenAI (GPT-6 Luna)' }, 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)' }, @@ -765,9 +769,9 @@ export const defaultApiModeIds = [ 'ollamaModel', 'customModel', 'azureOpenAi', - 'chatgptApi5_6Sol', + 'chatgptApi6Sol', 'chatgptApi5_6Terra', - 'chatgptApi5_6Luna', + 'chatgptApi6Luna', 'chatgptApi6Astra', 'xaiGrok4_6', 'xaiGrok4_5', diff --git a/src/services/apis/openai-token-params.mjs b/src/services/apis/openai-token-params.mjs index 652bbb8aa..3ad85cf7c 100644 --- a/src/services/apis/openai-token-params.mjs +++ b/src/services/apis/openai-token-params.mjs @@ -1,5 +1,5 @@ const OPENAI_MAX_COMPLETION_TOKENS_MODEL_PATTERN = - /^(?:gpt-5(?:[.-]|$)|gpt-6-astra(?:-|$)|chat-latest$)/ + /^(?:gpt-5(?:[.-]|$)|gpt-6-(?:astra|sol|luna)(?:-|$)|chat-latest$)/ function shouldUseMaxCompletionTokens(provider, model) { const normalizedProvider = String(provider || '').toLowerCase() diff --git a/src/services/apis/temperature-params.mjs b/src/services/apis/temperature-params.mjs index 42357b992..033b33f5f 100644 --- a/src/services/apis/temperature-params.mjs +++ b/src/services/apis/temperature-params.mjs @@ -7,6 +7,8 @@ const MODELS_WITHOUT_CUSTOM_TEMPERATURE = new Set([ 'claude-fable-5', 'claude-fable-5-1', 'gpt-6-astra', + 'gpt-6-sol', + 'gpt-6-luna', ]) function normalizeModelId(model) { diff --git a/tests/unit/config/config-predicates.test.mjs b/tests/unit/config/config-predicates.test.mjs index 56b6250ba..9a07e3997 100644 --- a/tests/unit/config/config-predicates.test.mjs +++ b/tests/unit/config/config-predicates.test.mjs @@ -4,6 +4,7 @@ import { getNavigatorLanguage, getPreferredLanguageKey, Models, + defaultApiModeIds, chatgptApiModelKeys, gptApiModelKeys, claudeApiModelKeys, @@ -52,6 +53,8 @@ const representativeChatgptApiModelNames = [ 'chatgptApi5_6Sol', 'chatgptApi5_6Terra', 'chatgptApi5_6Luna', + 'chatgptApi6Sol', + 'chatgptApi6Luna', ] const representativeGptCompletionApiModelNames = ['gptApiInstruct'] const representativeClaudeApiModelNames = ['claudeOpus48Api', 'claudeSonnet46Api'] @@ -82,6 +85,13 @@ afterEach(() => { restoreNavigator() }) +test('GPT-6 Sol and Luna replace their GPT-5.6 counterparts in the default presets', () => { + assert.equal(defaultApiModeIds.includes('chatgptApi6Sol'), true) + assert.equal(defaultApiModeIds.includes('chatgptApi6Luna'), true) + assert.equal(defaultApiModeIds.includes('chatgptApi5_6Sol'), false) + assert.equal(defaultApiModeIds.includes('chatgptApi5_6Luna'), false) +}) + test('getNavigatorLanguage returns zh-Hant for zh-TW style locales', () => { setNavigatorLanguage('zh-TW') assert.equal(getNavigatorLanguage(), 'zh-Hant') diff --git a/tests/unit/services/apis/openai-api-compat.test.mjs b/tests/unit/services/apis/openai-api-compat.test.mjs index e8825d487..8adc7ae87 100644 --- a/tests/unit/services/apis/openai-api-compat.test.mjs +++ b/tests/unit/services/apis/openai-api-compat.test.mjs @@ -751,7 +751,7 @@ test('generateAnswersWithOpenAiApi uses max_completion_tokens for GPT-5.4 mini', assert.equal(Object.hasOwn(body, 'max_tokens'), false) }) -test('generateAnswersWithOpenAiApi uses max_completion_tokens for GPT-6 Astra and omits unsupported temperature', async (t) => { +test('generateAnswersWithOpenAiApi uses max_completion_tokens for GPT-6 and omits unsupported temperature', async (t) => { t.mock.method(console, 'debug', () => {}) setStorage({ customOpenAiApiUrl: 'https://api.openai.example.com', @@ -780,13 +780,34 @@ test('generateAnswersWithOpenAiApi uses max_completion_tokens for GPT-6 Astra an await generateAnswersWithOpenAiApi(port, 'CurrentQ', session, 'sk-test') - const body = JSON.parse(capturedInit.body) + let body = JSON.parse(capturedInit.body) assert.equal(capturedInput, 'https://api.openai.example.com/v1/chat/completions') assert.equal(body.model, 'gpt-6-astra') assert.equal(body.max_completion_tokens, 444) assert.equal(Object.hasOwn(body, 'max_tokens'), false) assert.equal(Object.hasOwn(body, 'temperature'), false) assert.equal(session.conversationRecords.at(-1).answer, 'OK') + + for (const [modelName, model] of [ + ['chatgptApi6Sol', 'gpt-6-sol'], + ['chatgptApi6Luna', 'gpt-6-luna'], + ]) { + const newSession = { + modelName, + conversationRecords: [], + isRetry: false, + } + const newPort = createFakePort() + + await generateAnswersWithOpenAiApi(newPort, 'CurrentQ', newSession, 'sk-test') + + body = JSON.parse(capturedInit.body) + assert.equal(body.model, model) + assert.equal(body.max_completion_tokens, 444) + assert.equal(Object.hasOwn(body, 'max_tokens'), false) + assert.equal(Object.hasOwn(body, 'temperature'), false) + assert.equal(newSession.conversationRecords.at(-1).answer, 'OK') + } }) test('generateAnswersWithOpenAiApi uses max_completion_tokens for GPT-5.4 nano', async (t) => { diff --git a/tests/unit/services/apis/openai-token-params.test.mjs b/tests/unit/services/apis/openai-token-params.test.mjs index b05747c3e..b775e06fb 100644 --- a/tests/unit/services/apis/openai-token-params.test.mjs +++ b/tests/unit/services/apis/openai-token-params.test.mjs @@ -89,8 +89,14 @@ test('uses max_tokens when provider is undefined', () => { }) }) -test('uses completion token limits only for OpenAI GPT-6 Astra models', () => { - for (const model of ['gpt-6-astra', 'gpt-6-astra-20260901', 'GPT-6-ASTRA']) { +test('uses completion token limits only for supported OpenAI GPT-6 models', () => { + for (const model of [ + 'gpt-6-astra', + 'gpt-6-astra-20260901', + 'GPT-6-ASTRA', + 'gpt-6-sol', + 'gpt-6-luna', + ]) { assert.deepEqual(getChatCompletionsTokenParams('openai', model, 1024), { max_completion_tokens: 1024, }) @@ -98,7 +104,13 @@ test('uses completion token limits only for OpenAI GPT-6 Astra models', () => { max_tokens: 1024, }) } - for (const model of ['gpt-6-astral', 'my-gpt-6-astra', 'gpt-60', 'openai/gpt-6-astra']) { + for (const model of [ + 'gpt-6-astral', + 'gpt-6-solar', + 'my-gpt-6-astra', + 'gpt-60', + 'openai/gpt-6-astra', + ]) { assert.deepEqual(getChatCompletionsTokenParams('openai', model, 1024), { max_tokens: 1024, }) diff --git a/tests/unit/services/apis/temperature-params.test.mjs b/tests/unit/services/apis/temperature-params.test.mjs index 93a295e13..f26489f0f 100644 --- a/tests/unit/services/apis/temperature-params.test.mjs +++ b/tests/unit/services/apis/temperature-params.test.mjs @@ -103,12 +103,16 @@ test('temperature overrides remain available for earlier Gemini models', () => { } }) -test('temperature overrides omit GPT-6 Astra across provider ID formats', () => { +test('temperature overrides omit GPT-6 models across provider ID formats', () => { for (const model of [ 'gpt-6-astra', 'openai/gpt-6-astra', 'GPT-6-ASTRA', 'gpt-6-astra-20260901', + 'gpt-6-sol', + 'openai/gpt-6-sol', + 'gpt-6-luna', + 'openai/gpt-6-luna', ]) { assert.equal(canApplyTemperatureOverride(model), false, model) assert.deepEqual( @@ -117,7 +121,7 @@ test('temperature overrides omit GPT-6 Astra across provider ID formats', () => model, ) } - for (const model of ['gpt-6-astral', 'my-gpt-6-astra', 'gpt-4.1']) { + for (const model of ['gpt-6-astral', 'gpt-6-solar', 'my-gpt-6-astra', 'gpt-4.1']) { assert.equal(canApplyTemperatureOverride(model), true, model) } }) diff --git a/tests/unit/utils/model-name-convert.test.mjs b/tests/unit/utils/model-name-convert.test.mjs index 4bc15040b..3feb26aa4 100644 --- a/tests/unit/utils/model-name-convert.test.mjs +++ b/tests/unit/utils/model-name-convert.test.mjs @@ -778,7 +778,7 @@ test('modelNameToDesc returns desc for a known model name without t function', ( assert.equal(desc, 'ChatGPT (Web)') }) -test('modelNameToDesc returns desc for GPT-5 stable presets', () => { +test('modelNameToDesc returns desc for stable GPT API presets', () => { assert.equal(modelNameToDesc('chatgptApi5'), 'OpenAI (GPT-5)') assert.equal(modelNameToDesc('chatgptApi5_1'), 'OpenAI (GPT-5.1)') assert.equal(modelNameToDesc('chatgptApi5_2'), 'OpenAI (GPT-5.2)') @@ -790,6 +790,8 @@ test('modelNameToDesc returns desc for GPT-5 stable presets', () => { assert.equal(modelNameToDesc('chatgptApi5_6Sol'), 'OpenAI (GPT-5.6 Sol)') assert.equal(modelNameToDesc('chatgptApi5_6Terra'), 'OpenAI (GPT-5.6 Terra)') assert.equal(modelNameToDesc('chatgptApi5_6Luna'), 'OpenAI (GPT-5.6 Luna)') + assert.equal(modelNameToDesc('chatgptApi6Sol'), 'OpenAI (GPT-6 Sol)') + assert.equal(modelNameToDesc('chatgptApi6Luna'), 'OpenAI (GPT-6 Luna)') }) test('modelNameToDesc appends extraCustomModelName for customModel', () => {