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: 1 addition & 1 deletion src/chrome/src/providers/context-windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/chrome/src/providers/openai.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions src/chrome/src/providers/provider-compatibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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 = {}) {
Expand Down
4 changes: 3 additions & 1 deletion src/chrome/src/ui/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
],
Expand Down
2 changes: 1 addition & 1 deletion src/firefox/src/providers/context-windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/firefox/src/providers/openai.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions src/firefox/src/providers/provider-compatibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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 = {}) {
Expand Down
4 changes: 3 additions & 1 deletion src/firefox/src/ui/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
],
Expand Down
120 changes: 79 additions & 41 deletions test/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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',
Expand All @@ -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');
Expand Down Expand Up @@ -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' },
Expand Down Expand Up @@ -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]) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand Down
Loading