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
1 change: 1 addition & 0 deletions src/chrome/src/providers/context-windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +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-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
14 changes: 10 additions & 4 deletions src/chrome/src/providers/openai.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
isNewOpenAIContractConfig,
isOfficialOpenAIConfig,
isOpenCodeZenConfig,
requiresOpenAIDefaultTemperature,
shouldUseOpenAIResponsesApi,
supportsOpenAIAskStreaming,
applyOpenRouterRoutingVariant,
Expand Down Expand Up @@ -149,7 +150,7 @@ export class OpenAICompatibleProvider extends BaseLLMProvider {
* `supportsVision` getter above.
*/
_modelNameSniffedVision(model) {
return /gpt-4o|gpt-4\.1|gpt-4-turbo|gpt-5|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(?:$|[-_.:/])|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 @@ -293,11 +294,16 @@ export class OpenAICompatibleProvider extends BaseLLMProvider {
}

_addTemperature(body, options) {
// GPT-5 / o-series only accept the default temperature (1). Sending
// anything else returns 400. Provider configs can impose
// GPT-5, GPT-6 Luna Pro, 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.
if (this._isNewOpenAIContract() || this.config.omitTemperature) return;
if (requiresOpenAIDefaultTemperature({
...this.config,
providerName: this.config.providerName || this.name,
baseUrl: this.baseUrl,
model: this.model,
}) || this.config.omitTemperature) return;
body.temperature = options.temperature ?? 0.7;
}

Expand Down
16 changes: 16 additions & 0 deletions src/chrome/src/providers/provider-compatibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,21 @@ export function isNewOpenAIContractConfig(config = {}) {
return isNewOpenAIContractModel(config.model);
}

/**
* GPT-6 Luna Pro uses Chat Completions with `max_tokens`, but rejects an
* explicit temperature. Keep this separate from the GPT-5/o-series contract,
* whose token-field migration is different.
*/
export function requiresOpenAIDefaultTemperature(config = {}) {
if (isNewOpenAIContractConfig(config)) return true;
const providerName = clean(config.providerName);
const model = clean(config.model);
if (providerName === 'openrouter') {
return /(?:^|\/)openai\/gpt-6-luna-pro(?:$|[-_.\/:])/.test(model);
}
return isOfficialOpenAIConfig(config) && /^gpt-6-luna-pro(?:$|[-_.:])/.test(model);
}

export function supportsOpenAIAskStreaming(config = {}) {
if (!isOfficialOpenAIConfig(config)) return false;

Expand All @@ -274,6 +289,7 @@ export function supportsOpenAIAskStreaming(config = {}) {
if (shouldUseOpenAIResponsesApi(config)) return true;

return [
/^gpt-6-luna-pro(?:$|[-_.:])/,
/^gpt-5\.5(?:$|-\d{4}-\d{2}-\d{2}$)/,
/^gpt-5\.4(?:$|-\d{4}-\d{2}-\d{2}$|-(?:mini|nano)(?:$|-\d{4}-\d{2}-\d{2}$))/,
/^gpt-5\.(?:1|2)(?:$|-\d{4}-\d{2}-\d{2}$)/,
Expand Down
1 change: 1 addition & 0 deletions src/chrome/src/ui/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,7 @@ function renderProviders() {
{ key: 'apiKey', labelKey: 'st.provider.field.api_key', type: 'password', placeholder: 'sk-...' },
{ key: 'model', labelKey: 'st.provider.field.model', type: 'text', placeholder: 'gpt-5.6-terra',
suggestions: [
'gpt-6-luna-pro',
'gpt-5.6-terra',
'gpt-5.6-sol',
'gpt-5.6-luna',
Expand Down
1 change: 1 addition & 0 deletions src/firefox/src/providers/context-windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +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-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
14 changes: 10 additions & 4 deletions src/firefox/src/providers/openai.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
isNewOpenAIContractConfig,
isOfficialOpenAIConfig,
isOpenCodeZenConfig,
requiresOpenAIDefaultTemperature,
shouldUseOpenAIResponsesApi,
supportsOpenAIAskStreaming,
applyOpenRouterRoutingVariant,
Expand Down Expand Up @@ -150,7 +151,7 @@ export class OpenAICompatibleProvider extends BaseLLMProvider {
* `supportsVision` getter above.
*/
_modelNameSniffedVision(model) {
return /gpt-4o|gpt-4\.1|gpt-4-turbo|gpt-5|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(?:$|[-_.:/])|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 @@ -303,11 +304,16 @@ export class OpenAICompatibleProvider extends BaseLLMProvider {
}

_addTemperature(body, options) {
// GPT-5 / o-series only accept the default temperature (1). Sending
// anything else returns 400. Provider configs can impose
// GPT-5, GPT-6 Luna Pro, 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.
if (this._isNewOpenAIContract() || this.config.omitTemperature) return;
if (requiresOpenAIDefaultTemperature({
...this.config,
providerName: this.config.providerName || this.name,
baseUrl: this.baseUrl,
model: this.model,
}) || this.config.omitTemperature) return;
body.temperature = options.temperature ?? 0.7;
}

Expand Down
16 changes: 16 additions & 0 deletions src/firefox/src/providers/provider-compatibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,21 @@ export function isNewOpenAIContractConfig(config = {}) {
return isNewOpenAIContractModel(config.model);
}

/**
* GPT-6 Luna Pro uses Chat Completions with `max_tokens`, but rejects an
* explicit temperature. Keep this separate from the GPT-5/o-series contract,
* whose token-field migration is different.
*/
export function requiresOpenAIDefaultTemperature(config = {}) {
if (isNewOpenAIContractConfig(config)) return true;
const providerName = clean(config.providerName);
const model = clean(config.model);
if (providerName === 'openrouter') {
return /(?:^|\/)openai\/gpt-6-luna-pro(?:$|[-_.\/:])/.test(model);
}
return isOfficialOpenAIConfig(config) && /^gpt-6-luna-pro(?:$|[-_.:])/.test(model);
}

export function supportsOpenAIAskStreaming(config = {}) {
if (!isOfficialOpenAIConfig(config)) return false;

Expand All @@ -274,6 +289,7 @@ export function supportsOpenAIAskStreaming(config = {}) {
if (shouldUseOpenAIResponsesApi(config)) return true;

return [
/^gpt-6-luna-pro(?:$|[-_.:])/,
/^gpt-5\.5(?:$|-\d{4}-\d{2}-\d{2}$)/,
/^gpt-5\.4(?:$|-\d{4}-\d{2}-\d{2}$|-(?:mini|nano)(?:$|-\d{4}-\d{2}-\d{2}$))/,
/^gpt-5\.(?:1|2)(?:$|-\d{4}-\d{2}-\d{2}$)/,
Expand Down
1 change: 1 addition & 0 deletions src/firefox/src/ui/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2626,6 +2626,7 @@ function renderProviders() {
{ key: 'apiKey', labelKey: 'st.provider.field.api_key', type: 'password', placeholder: 'sk-...' },
{ key: 'model', labelKey: 'st.provider.field.model', type: 'text', placeholder: 'gpt-5.6-terra',
suggestions: [
'gpt-6-luna-pro',
'gpt-5.6-terra',
'gpt-5.6-sol',
'gpt-5.6-luna',
Expand Down
65 changes: 62 additions & 3 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-5.6', 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna']) {
for (const model of ['gpt-6-luna-pro', '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,8 +71313,25 @@ test('built-in catalog defaults opt into vision when the model name is multimoda
}
});

test('OpenAI settings list only the GPT-5.6 family and current dated models', () => {
test('GPT-6 Luna Pro 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`);
}
assert.equal(
new Provider({ providerName: 'openai', baseUrl: 'https://api.openai.com/v1', model: 'gpt-6-luna' }).supportsVision,
false,
'unlisted GPT-6 variants must not inherit Luna Pro vision capability',
);
}
});

test('OpenAI settings list GPT-6 Luna Pro, the GPT-5.6 family, and current dated models', () => {
const expectedModels = [
'gpt-6-luna-pro',
'gpt-5.6-terra',
'gpt-5.6-sol',
'gpt-5.6-luna',
Expand Down Expand Up @@ -71755,6 +71772,7 @@ test('Responses reasoning effort is normalized for GPT-5 Pro model constraints',

test('official OpenAI Ask streaming follows the documented model capability', () => {
const supportedModels = [
'gpt-6-luna-pro',
'gpt-5.6-terra',
'gpt-5.5',
'gpt-5.5-2026-04-23',
Expand Down Expand Up @@ -73848,7 +73866,7 @@ test('OpenAI-compatible local providers always use legacy request token fields',
}
});

test('router-prefixed OpenAI reasoning ids use the advertised Chat Completions contract', () => {
test('OpenAI reasoning and GPT-6 ids use the advertised Chat Completions contract', () => {
const messages = [{ role: 'user', content: 'hello' }];
const newContractModels = ['openai/gpt-5.6-terra', 'openai/gpt-5.6-terra:batch', 'openai/gpt-5.6-terra:image'];
const legacyContractModels = [
Expand All @@ -73874,6 +73892,25 @@ test('router-prefixed OpenAI reasoning ids use the advertised Chat Completions c
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 Provider of [OpenAIProviderCh, OpenAIProviderFx]) {
Expand Down Expand Up @@ -73903,6 +73940,28 @@ test('router-prefixed OpenAI reasoning ids use the advertised Chat Completions c
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',
},
]) {
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
// temperatures (deterministic planner/compaction paths) are not dropped.
const gpt41 = new Provider({
Expand Down
Loading