From 1d70e35a2654c496b841ecd635d0cc40a55583ea Mon Sep 17 00:00:00 2001 From: Emre Sokullu Date: Wed, 23 Sep 2026 09:14:59 -0700 Subject: [PATCH] Add GPT-6 Sol and Astra compatibility --- src/chrome/src/providers/context-windows.js | 2 +- src/chrome/src/providers/openai.js | 4 +- .../src/providers/provider-compatibility.js | 6 +- src/chrome/src/ui/settings.js | 4 +- src/firefox/src/providers/context-windows.js | 2 +- src/firefox/src/providers/openai.js | 4 +- .../src/providers/provider-compatibility.js | 6 +- src/firefox/src/ui/settings.js | 4 +- test/run.js | 120 ++++++++++++------ 9 files changed, 97 insertions(+), 55 deletions(-) diff --git a/src/chrome/src/providers/context-windows.js b/src/chrome/src/providers/context-windows.js index 061db1f2c..7082506e6 100644 --- a/src/chrome/src/providers/context-windows.js +++ b/src/chrome/src/providers/context-windows.js @@ -279,7 +279,7 @@ export function inferContextWindow(config = {}) { if (!model) return DEFAULT_CLOUD_CONTEXT_WINDOW; // OpenAI - if (/^gpt-6-luna-pro(?:[.\-]|$)/.test(model) || model.includes('/gpt-6-luna-pro')) return 1050000; + if (/(?:^|\/)gpt-6-(?:luna-pro|sol|astra)(?:[.\-:]|$)/.test(model)) return 1050000; if (/^gpt-5\.6(?:[.\-]|$)/.test(model) || model.includes('/gpt-5.6')) return 1050000; if (model.includes('gpt-5.5-pro')) return 1050000; if (/^gpt-5(?:[.\-]|$)/.test(model) || model.includes('/gpt-5')) return 400000; diff --git a/src/chrome/src/providers/openai.js b/src/chrome/src/providers/openai.js index 2ba5beb5a..444462363 100644 --- a/src/chrome/src/providers/openai.js +++ b/src/chrome/src/providers/openai.js @@ -150,7 +150,7 @@ export class OpenAICompatibleProvider extends BaseLLMProvider { * `supportsVision` getter above. */ _modelNameSniffedVision(model) { - return /gpt-4o|gpt-4\.1|gpt-4-turbo|gpt-5|gpt-6-luna-pro(?:$|[-_.:/])|claude|gemini|grok|minimax-m3|kimi-k(?:-?3|2\.[5-9])|llava|qwen.*vl|qwen2.*vl|qwen3.*vl|qwen3\.[5-9]|qwen3p8-27b|pixtral|llama.*vision|gemma.*vision|gemma-?[34]|step-3/.test(String(model || '')); + return /gpt-4o|gpt-4\.1|gpt-4-turbo|gpt-5|gpt-6-(?:luna-pro|sol|astra)(?:$|[-_.:/])|claude|gemini|grok|minimax-m3|kimi-k(?:-?3|2\.[5-9])|llava|qwen.*vl|qwen2.*vl|qwen3.*vl|qwen3\.[5-9]|qwen3p8-27b|pixtral|llama.*vision|gemma.*vision|gemma-?[34]|step-3/.test(String(model || '')); } get useCompactPrompt() { @@ -294,7 +294,7 @@ export class OpenAICompatibleProvider extends BaseLLMProvider { } _addTemperature(body, options) { - // GPT-5, GPT-6 Luna Pro, and o-series models only accept the default + // GPT-5, supported GPT-6, and o-series models only accept the default // temperature. Provider configs can impose // the same omission for fixed-temperature models such as Kimi K2.5/K3. // In both cases, let the API apply its required default. diff --git a/src/chrome/src/providers/provider-compatibility.js b/src/chrome/src/providers/provider-compatibility.js index def6a94f0..d05026293 100644 --- a/src/chrome/src/providers/provider-compatibility.js +++ b/src/chrome/src/providers/provider-compatibility.js @@ -265,7 +265,7 @@ export function isNewOpenAIContractConfig(config = {}) { } /** - * GPT-6 Luna Pro uses Chat Completions with `max_tokens`, but rejects an + * Supported GPT-6 models use Chat Completions with `max_tokens`, but reject an * explicit temperature. Keep this separate from the GPT-5/o-series contract, * whose token-field migration is different. */ @@ -274,9 +274,9 @@ export function requiresOpenAIDefaultTemperature(config = {}) { const providerName = clean(config.providerName); const model = clean(config.model); if (providerName === 'openrouter') { - return /(?:^|\/)openai\/gpt-6-luna-pro(?:$|[-_.\/:])/.test(model); + return /(?:^|\/)openai\/gpt-6-(?:luna-pro|sol|astra)(?:$|[-_.\/:])/.test(model); } - return isOfficialOpenAIConfig(config) && /^gpt-6-luna-pro(?:$|[-_.:])/.test(model); + return isOfficialOpenAIConfig(config) && /^gpt-6-(?:luna-pro|sol|astra)(?:$|[-_.:])/.test(model); } export function supportsOpenAIAskStreaming(config = {}) { diff --git a/src/chrome/src/ui/settings.js b/src/chrome/src/ui/settings.js index b31b70015..3769bd7e1 100644 --- a/src/chrome/src/ui/settings.js +++ b/src/chrome/src/ui/settings.js @@ -3242,6 +3242,8 @@ function renderProviders() { { key: 'model', labelKey: 'st.provider.field.model', type: 'text', placeholder: 'gpt-5.6-terra', suggestions: [ 'gpt-6-luna-pro', + 'gpt-6-sol', + 'gpt-6-astra', 'gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-5.6-luna', @@ -3261,7 +3263,7 @@ function renderProviders() { fields: [ { key: 'apiKey', labelKey: 'st.provider.field.api_key', type: 'password', placeholder: 'sk-or-...' }, { key: 'model', labelKey: 'st.provider.field.model', type: 'text', placeholder: 'openrouter/free', - suggestions: ['openrouter/free', 'qwen/qwen3.8-27b', 'moonshotai/kimi-k3', 'z-ai/glm-5.3', 'minimax/minimax-m3'] }, + suggestions: ['openrouter/free', 'anthropic/claude-opus-5.5', 'qwen/qwen3.8-27b', 'moonshotai/kimi-k3', 'z-ai/glm-5.3', 'minimax/minimax-m3'] }, { key: 'baseUrl', labelKey: 'st.provider.field.api_base_url', type: 'text', placeholder: 'https://openrouter.ai/api/v1' }, PROMPT_TIER_FIELD, ], diff --git a/src/firefox/src/providers/context-windows.js b/src/firefox/src/providers/context-windows.js index 061db1f2c..7082506e6 100644 --- a/src/firefox/src/providers/context-windows.js +++ b/src/firefox/src/providers/context-windows.js @@ -279,7 +279,7 @@ export function inferContextWindow(config = {}) { if (!model) return DEFAULT_CLOUD_CONTEXT_WINDOW; // OpenAI - if (/^gpt-6-luna-pro(?:[.\-]|$)/.test(model) || model.includes('/gpt-6-luna-pro')) return 1050000; + if (/(?:^|\/)gpt-6-(?:luna-pro|sol|astra)(?:[.\-:]|$)/.test(model)) return 1050000; if (/^gpt-5\.6(?:[.\-]|$)/.test(model) || model.includes('/gpt-5.6')) return 1050000; if (model.includes('gpt-5.5-pro')) return 1050000; if (/^gpt-5(?:[.\-]|$)/.test(model) || model.includes('/gpt-5')) return 400000; diff --git a/src/firefox/src/providers/openai.js b/src/firefox/src/providers/openai.js index af3c740ea..85cd1427b 100644 --- a/src/firefox/src/providers/openai.js +++ b/src/firefox/src/providers/openai.js @@ -151,7 +151,7 @@ export class OpenAICompatibleProvider extends BaseLLMProvider { * `supportsVision` getter above. */ _modelNameSniffedVision(model) { - return /gpt-4o|gpt-4\.1|gpt-4-turbo|gpt-5|gpt-6-luna-pro(?:$|[-_.:/])|claude|gemini|grok|minimax-m3|kimi-k(?:-?3|2\.[5-9])|llava|qwen.*vl|qwen2.*vl|qwen3.*vl|qwen3\.[5-9]|qwen3p8-27b|pixtral|llama.*vision|gemma.*vision|gemma-?[34]|step-3/.test(String(model || '')); + return /gpt-4o|gpt-4\.1|gpt-4-turbo|gpt-5|gpt-6-(?:luna-pro|sol|astra)(?:$|[-_.:/])|claude|gemini|grok|minimax-m3|kimi-k(?:-?3|2\.[5-9])|llava|qwen.*vl|qwen2.*vl|qwen3.*vl|qwen3\.[5-9]|qwen3p8-27b|pixtral|llama.*vision|gemma.*vision|gemma-?[34]|step-3/.test(String(model || '')); } get useCompactPrompt() { @@ -304,7 +304,7 @@ export class OpenAICompatibleProvider extends BaseLLMProvider { } _addTemperature(body, options) { - // GPT-5, GPT-6 Luna Pro, and o-series models only accept the default + // GPT-5, supported GPT-6, and o-series models only accept the default // temperature. Provider configs can impose // the same omission for fixed-temperature models such as Kimi K2.5/K3. // In both cases, let the API apply its required default. diff --git a/src/firefox/src/providers/provider-compatibility.js b/src/firefox/src/providers/provider-compatibility.js index def6a94f0..d05026293 100644 --- a/src/firefox/src/providers/provider-compatibility.js +++ b/src/firefox/src/providers/provider-compatibility.js @@ -265,7 +265,7 @@ export function isNewOpenAIContractConfig(config = {}) { } /** - * GPT-6 Luna Pro uses Chat Completions with `max_tokens`, but rejects an + * Supported GPT-6 models use Chat Completions with `max_tokens`, but reject an * explicit temperature. Keep this separate from the GPT-5/o-series contract, * whose token-field migration is different. */ @@ -274,9 +274,9 @@ export function requiresOpenAIDefaultTemperature(config = {}) { const providerName = clean(config.providerName); const model = clean(config.model); if (providerName === 'openrouter') { - return /(?:^|\/)openai\/gpt-6-luna-pro(?:$|[-_.\/:])/.test(model); + return /(?:^|\/)openai\/gpt-6-(?:luna-pro|sol|astra)(?:$|[-_.\/:])/.test(model); } - return isOfficialOpenAIConfig(config) && /^gpt-6-luna-pro(?:$|[-_.:])/.test(model); + return isOfficialOpenAIConfig(config) && /^gpt-6-(?:luna-pro|sol|astra)(?:$|[-_.:])/.test(model); } export function supportsOpenAIAskStreaming(config = {}) { diff --git a/src/firefox/src/ui/settings.js b/src/firefox/src/ui/settings.js index 9499132f9..1ad38990f 100644 --- a/src/firefox/src/ui/settings.js +++ b/src/firefox/src/ui/settings.js @@ -2627,6 +2627,8 @@ function renderProviders() { { key: 'model', labelKey: 'st.provider.field.model', type: 'text', placeholder: 'gpt-5.6-terra', suggestions: [ 'gpt-6-luna-pro', + 'gpt-6-sol', + 'gpt-6-astra', 'gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-5.6-luna', @@ -2646,7 +2648,7 @@ function renderProviders() { fields: [ { key: 'apiKey', labelKey: 'st.provider.field.api_key', type: 'password', placeholder: 'sk-or-...' }, { key: 'model', labelKey: 'st.provider.field.model', type: 'text', placeholder: 'openrouter/free', - suggestions: ['openrouter/free', 'qwen/qwen3.8-27b', 'moonshotai/kimi-k3', 'z-ai/glm-5.3', 'minimax/minimax-m3'] }, + suggestions: ['openrouter/free', 'anthropic/claude-opus-5.5', 'qwen/qwen3.8-27b', 'moonshotai/kimi-k3', 'z-ai/glm-5.3', 'minimax/minimax-m3'] }, { key: 'baseUrl', labelKey: 'st.provider.field.api_base_url', type: 'text', placeholder: 'https://openrouter.ai/api/v1' }, PROMPT_TIER_FIELD, ], diff --git a/test/run.js b/test/run.js index 9f510073a..f3e751119 100644 --- a/test/run.js +++ b/test/run.js @@ -67181,7 +67181,7 @@ test('inferContextWindow: model-aware cloud/router defaults and local 16k fallba for (const providerName of ['lmstudio', 'jan', 'vllm', 'sglang', 'localai', 'gpt4all', 'local-openai-proxy']) { assert.equal(infer({ category: 'local', providerName, model: 'qwen3.7-plus' }), 16384); } - for (const model of ['gpt-6-luna-pro', 'gpt-5.6', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna']) { + for (const model of ['gpt-6-luna-pro', 'gpt-6-sol', 'gpt-6-astra', 'gpt-5.6', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna']) { assert.equal(infer({ category: 'cloud', providerName: 'openai', model }), 1050000); } assert.equal(infer({ category: 'cloud', providerName: 'openai', model: 'gpt-5.5-pro' }), 1050000); @@ -71313,13 +71313,15 @@ test('built-in catalog defaults opt into vision when the model name is multimoda } }); -test('GPT-6 Luna Pro vision capability is mirrored for direct and routed OpenAI models', () => { +test('supported GPT-6 vision capability is mirrored for direct and routed OpenAI models', () => { for (const Provider of [OpenAIProviderCh, OpenAIProviderFx]) { - for (const config of [ - { providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'gpt-6-luna-pro' }, - { providerName: 'openrouter', baseUrl: 'https://openrouter.ai/api/v1', model: 'openai/gpt-6-luna-pro' }, - ]) { - assert.equal(new Provider(config).supportsVision, true, `${config.model} should receive screenshots`); + for (const model of ['gpt-6-luna-pro', 'gpt-6-sol', 'gpt-6-astra']) { + for (const config of [ + { providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model }, + { providerName: 'openrouter', baseUrl: 'https://openrouter.ai/api/v1', model: `openai/${model}` }, + ]) { + assert.equal(new Provider(config).supportsVision, true, `${config.model} should receive screenshots`); + } } assert.equal( new Provider({ providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'gpt-6-luna' }).supportsVision, @@ -71329,9 +71331,11 @@ test('GPT-6 Luna Pro vision capability is mirrored for direct and routed OpenAI } }); -test('OpenAI settings list GPT-6 Luna Pro, the GPT-5.6 family, and current dated models', () => { +test('OpenAI settings list supported GPT-6 models, the GPT-5.6 family, and current dated models', () => { const expectedModels = [ 'gpt-6-luna-pro', + 'gpt-6-sol', + 'gpt-6-astra', 'gpt-5.6-terra', 'gpt-5.6-sol', 'gpt-5.6-luna', @@ -71355,6 +71359,33 @@ test('OpenAI settings list GPT-6 Luna Pro, the GPT-5.6 family, and current dated } }); +test('OpenRouter settings list Claude Opus 5.5', () => { + for (const prefix of ['src/chrome', 'src/firefox']) { + const source = fs.readFileSync(path.join(ROOT, prefix, 'src/ui/settings.js'), 'utf8'); + assert.match( + source, + /openrouter:\s*\{[\s\S]*?suggestions:\s*\[[^\]]*'anthropic\/claude-opus-5\.5'/, + `${prefix}: OpenRouter should offer Claude Opus 5.5`, + ); + } +}); + +test('OpenRouter Claude Opus 5.5 uses its advertised multimodal legacy contract', () => { + for (const Provider of [OpenAIProviderCh, OpenAIProviderFx]) { + const provider = new Provider({ + providerName: 'openrouter', + baseUrl: 'https://openrouter.ai/api/v1', + model: 'anthropic/claude-opus-5.5', + }); + assert.equal(provider.supportsVision, true, 'Claude Opus 5.5 should receive screenshots'); + assert.equal(provider.contextWindow, 1000000, 'Claude Opus 5.5 should use its 1M context window'); + const body = provider._buildChatCompletionsBody([], { maxTokens: 123, temperature: 0.2 }, false); + assert.equal(body.max_tokens, 123, 'Claude Opus 5.5 should use max_tokens'); + assert.equal(body.max_completion_tokens, undefined, 'Claude Opus 5.5 must not use max_completion_tokens'); + assert.equal(body.temperature, 0.2, 'OpenRouter Claude Opus 5.5 should preserve the requested temperature'); + } +}); + test('provider settings expose cache pricing with zero-cost support', () => { for (const prefix of ['src/chrome', 'src/firefox']) { const source = fs.readFileSync(path.join(ROOT, prefix, 'src/ui/settings.js'), 'utf8'); @@ -71814,6 +71845,8 @@ test('official OpenAI Ask streaming follows the documented model capability', () for (const config of [ { providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'gpt-5.5-pro' }, { providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'gpt-5.5-pro', supportsAskStreaming: true }, + { providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'gpt-6-sol' }, + { providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'gpt-6-astra' }, { providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'gpt-3.5-turbo' }, { providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'gpt-4' }, { providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'o1-mini' }, @@ -73892,25 +73925,27 @@ test('OpenAI reasoning and GPT-6 ids use the advertised Chat Completions contrac for (const model of legacyContractModels) { assert.equal(compatibility.isNewOpenAIContractConfig({ providerName: 'openrouter', model }), false, `${model} should keep the legacy contract`); } - assert.equal( - compatibility.requiresOpenAIDefaultTemperature({ providerName: 'openrouter', model: 'openai/gpt-6-luna-pro' }), - true, - 'OpenRouter GPT-6 Luna Pro should omit temperature', - ); - assert.equal( - compatibility.requiresOpenAIDefaultTemperature({ - providerName: 'openai', - baseUrl: 'https://api.openai.com/v1', - model: 'gpt-6-luna-pro', - }), - true, - 'official GPT-6 Luna Pro should omit temperature', - ); - assert.equal( - compatibility.requiresOpenAIDefaultTemperature({ providerName: 'custom-proxy', model: 'openai/gpt-6-luna-pro' }), - false, - 'a custom proxy must not inherit GPT-6 Luna Pro temperature behavior', - ); + for (const model of ['gpt-6-luna-pro', 'gpt-6-sol', 'gpt-6-astra']) { + assert.equal( + compatibility.requiresOpenAIDefaultTemperature({ providerName: 'openrouter', model: `openai/${model}` }), + true, + `OpenRouter ${model} should omit temperature`, + ); + assert.equal( + compatibility.requiresOpenAIDefaultTemperature({ + providerName: 'openai', + baseUrl: 'https://api.openai.com/v1', + model, + }), + true, + `official ${model} should omit temperature`, + ); + assert.equal( + compatibility.requiresOpenAIDefaultTemperature({ providerName: 'custom-proxy', model: `openai/${model}` }), + false, + `a custom proxy must not inherit ${model} temperature behavior`, + ); + } } for (const Provider of [OpenAIProviderCh, OpenAIProviderFx]) { @@ -73940,26 +73975,28 @@ test('OpenAI reasoning and GPT-6 ids use the advertised Chat Completions contrac assert.equal(body.temperature, 0.7, `${model} should keep the default temperature`); } - for (const config of [ - { - label: 'OpenRouter GPT-6 Luna Pro', - providerName: 'openrouter', - baseUrl: 'https://openrouter.ai/api/v1', - model: 'openai/gpt-6-luna-pro', - }, - { - label: 'official GPT-6 Luna Pro', - providerName: 'openai', - baseUrl: 'https://api.openai.com/v1', - model: 'gpt-6-luna-pro', - }, - ]) { + for (const model of ['gpt-6-luna-pro', 'gpt-6-sol', 'gpt-6-astra']) { + for (const config of [ + { + label: `OpenRouter ${model}`, + providerName: 'openrouter', + baseUrl: 'https://openrouter.ai/api/v1', + model: `openai/${model}`, + }, + { + label: `official ${model}`, + providerName: 'openai', + baseUrl: 'https://api.openai.com/v1', + model, + }, + ]) { const provider = new Provider(config); assert.equal(provider._isNewOpenAIContract(), false, `${config.label} should keep the Chat Completions token contract`); const body = provider._buildChatCompletionsBody(messages, { maxTokens: 123, temperature: 0.2 }, false); assert.equal(body.max_tokens, 123, `${config.label} should use max_tokens`); assert.equal(body.max_completion_tokens, undefined, `${config.label} must not send max_completion_tokens`); assert.equal(body.temperature, undefined, `${config.label} must omit temperature`); + } } // gpt-4.1 accepts both parameter sets; it must stay legacy so explicit @@ -118206,6 +118243,7 @@ for (const [label, Provider, VertexProvider, AgentClass] of [ for (const model of [ 'claude-opus-5', + 'claude-opus-5.5', 'claude-opus-4-8@20260801', 'claude-sonnet-5@20260801', 'claude-mythos-preview',