feat: Refactor model handling & reasoning effort across navigation, typeahead, OpenAI/Codex providers, API shim, configs, and UI (adds EffortPicker, new mappings/options, unique suggestion IDs, effort utilities; removes deprecated aliases; defaults Codex to gpt-5.4; improves selection logic and status display)
This commit is contained in:
@@ -6,8 +6,6 @@ export const MODEL_ALIASES = [
|
||||
'sonnet[1m]',
|
||||
'opus[1m]',
|
||||
'opusplan',
|
||||
'codexplan',
|
||||
'codexspark',
|
||||
] as const
|
||||
export type ModelAlias = (typeof MODEL_ALIASES)[number]
|
||||
|
||||
|
||||
@@ -123,6 +123,10 @@ export function getDefaultOpusModel(): ModelName {
|
||||
if (getAPIProvider() === 'openai') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-4o'
|
||||
}
|
||||
// Codex provider: use user-specified model or default to gpt-5.4
|
||||
if (getAPIProvider() === 'codex') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-5.4'
|
||||
}
|
||||
// 3P providers (Bedrock, Vertex, Foundry) — kept as a separate branch
|
||||
// even when values match, since 3P availability lags firstParty and
|
||||
// these will diverge again at the next model launch.
|
||||
@@ -145,6 +149,10 @@ export function getDefaultSonnetModel(): ModelName {
|
||||
if (getAPIProvider() === 'openai') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-4o'
|
||||
}
|
||||
// Codex provider
|
||||
if (getAPIProvider() === 'codex') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-5.4'
|
||||
}
|
||||
// Default to Sonnet 4.5 for 3P since they may not have 4.6 yet
|
||||
if (getAPIProvider() !== 'firstParty') {
|
||||
return getModelStrings().sonnet45
|
||||
@@ -165,6 +173,10 @@ export function getDefaultHaikuModel(): ModelName {
|
||||
if (getAPIProvider() === 'openai') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-4o-mini'
|
||||
}
|
||||
// Codex provider
|
||||
if (getAPIProvider() === 'codex') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-5.4'
|
||||
}
|
||||
|
||||
// Haiku 4.5 is available on all platforms (first-party, Foundry, Bedrock, Vertex)
|
||||
return getModelStrings().haiku45
|
||||
@@ -217,6 +229,10 @@ export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias {
|
||||
if (getAPIProvider() === 'openai') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-4o'
|
||||
}
|
||||
// Codex provider: always use the configured Codex model (default gpt-5.4)
|
||||
if (getAPIProvider() === 'codex') {
|
||||
return process.env.OPENAI_MODEL || 'gpt-5.4'
|
||||
}
|
||||
|
||||
// Ants default to defaultModel from flag config, or Opus 1M if not configured
|
||||
if (process.env.USER_TYPE === 'ant') {
|
||||
@@ -343,12 +359,6 @@ export function renderDefaultModelSetting(
|
||||
if (setting === 'opusplan') {
|
||||
return 'Opus 4.6 in plan mode, else Sonnet 4.6'
|
||||
}
|
||||
if (setting === 'codexplan') {
|
||||
return 'Codex Plan (GPT-5.4 high reasoning)'
|
||||
}
|
||||
if (setting === 'codexspark') {
|
||||
return 'Codex Spark (GPT-5.3 Codex Spark)'
|
||||
}
|
||||
return renderModelName(parseUserSpecifiedModel(setting))
|
||||
}
|
||||
|
||||
@@ -383,11 +393,12 @@ export function renderModelSetting(setting: ModelName | ModelAlias): string {
|
||||
if (setting === 'opusplan') {
|
||||
return 'Opus Plan'
|
||||
}
|
||||
// Handle Codex models - show actual model name + resolved model
|
||||
if (setting === 'codexplan') {
|
||||
return 'Codex Plan'
|
||||
return 'codexplan (gpt-5.4)'
|
||||
}
|
||||
if (setting === 'codexspark') {
|
||||
return 'Codex Spark'
|
||||
return 'codexspark (gpt-5.3-codex-spark)'
|
||||
}
|
||||
if (isModelAlias(setting)) {
|
||||
return capitalize(setting)
|
||||
@@ -401,8 +412,8 @@ export function renderModelSetting(setting: ModelName | ModelAlias): string {
|
||||
* if the model is not recognized as a public model.
|
||||
*/
|
||||
export function getPublicModelDisplayName(model: ModelName): string | null {
|
||||
// For OpenAI/Gemini providers, show the actual model name not a Claude alias
|
||||
if (getAPIProvider() === 'openai' || getAPIProvider() === 'gemini') {
|
||||
// For OpenAI/Gemini/Codex providers, show the actual model name not a Claude alias
|
||||
if (getAPIProvider() === 'openai' || getAPIProvider() === 'gemini' || getAPIProvider() === 'codex') {
|
||||
return null
|
||||
}
|
||||
switch (model) {
|
||||
@@ -517,10 +528,6 @@ export function parseUserSpecifiedModel(
|
||||
|
||||
if (isModelAlias(modelString)) {
|
||||
switch (modelString) {
|
||||
case 'codexplan':
|
||||
return modelInputTrimmed
|
||||
case 'codexspark':
|
||||
return modelInputTrimmed
|
||||
case 'opusplan':
|
||||
return getDefaultSonnetModel() + (has1mTag ? '[1m]' : '') // Sonnet is default, Opus in plan mode
|
||||
case 'sonnet':
|
||||
@@ -535,6 +542,14 @@ export function parseUserSpecifiedModel(
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Codex aliases - map to actual model names
|
||||
if (modelString === 'codexplan') {
|
||||
return 'gpt-5.4'
|
||||
}
|
||||
if (modelString === 'codexspark') {
|
||||
return 'gpt-5.3-codex-spark'
|
||||
}
|
||||
|
||||
// Opus 4/4.1 are no longer available on the first-party API (same as
|
||||
// Claude.ai) — silently remap to the current Opus default. The 'opus'
|
||||
// alias already resolves to 4.6, so the only users on these explicit
|
||||
|
||||
@@ -268,20 +268,65 @@ function getOpusPlanOption(): ModelOption {
|
||||
|
||||
function getCodexPlanOption(): ModelOption {
|
||||
return {
|
||||
value: 'codexplan',
|
||||
label: 'Codex Plan',
|
||||
value: 'gpt-5.4',
|
||||
label: 'gpt-5.4',
|
||||
description: 'GPT-5.4 on the Codex backend with high reasoning',
|
||||
}
|
||||
}
|
||||
|
||||
function getCodexSparkOption(): ModelOption {
|
||||
return {
|
||||
value: 'codexspark',
|
||||
label: 'Codex Spark',
|
||||
value: 'gpt-5.3-codex-spark',
|
||||
label: 'gpt-5.3-codex-spark',
|
||||
description: 'GPT-5.3 Codex Spark on the Codex backend for fast tool loops',
|
||||
}
|
||||
}
|
||||
|
||||
function getCodexModelOptions(): ModelOption[] {
|
||||
return [
|
||||
{
|
||||
value: 'gpt-5.4',
|
||||
label: 'gpt-5.4',
|
||||
description: 'GPT-5.4 with high reasoning',
|
||||
},
|
||||
{
|
||||
value: 'gpt-5.3-codex',
|
||||
label: 'gpt-5.3-codex',
|
||||
description: 'GPT-5.3 Codex with high reasoning',
|
||||
},
|
||||
{
|
||||
value: 'gpt-5.3-codex-spark',
|
||||
label: 'gpt-5.3-codex-spark',
|
||||
description: 'GPT-5.3 Codex Spark for fast tool loops',
|
||||
},
|
||||
{
|
||||
value: 'codexspark',
|
||||
label: 'codexspark',
|
||||
description: 'GPT-5.3 Codex Spark alias for fast tool loops',
|
||||
},
|
||||
{
|
||||
value: 'gpt-5.2-codex',
|
||||
label: 'gpt-5.2-codex',
|
||||
description: 'GPT-5.2 Codex with high reasoning',
|
||||
},
|
||||
{
|
||||
value: 'gpt-5.1-codex-max',
|
||||
label: 'gpt-5.1-codex-max',
|
||||
description: 'GPT-5.1 Codex Max for deep reasoning',
|
||||
},
|
||||
{
|
||||
value: 'gpt-5.1-codex-mini',
|
||||
label: 'gpt-5.1-codex-mini',
|
||||
description: 'GPT-5.1 Codex Mini - faster, cheaper',
|
||||
},
|
||||
{
|
||||
value: 'gpt-5.4-mini',
|
||||
label: 'gpt-5.4-mini',
|
||||
description: 'GPT-5.4 Mini - faster, cheaper',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
// @[MODEL LAUNCH]: Update the model picker lists below to include/reorder options for the new model.
|
||||
// Each user tier (ant, Max/Team Premium, Pro/Team Standard/Enterprise, PAYG 1P, PAYG 3P) has its own list.
|
||||
function getModelOptionsBase(fastMode = false): ModelOption[] {
|
||||
@@ -360,8 +405,9 @@ function getModelOptionsBase(fastMode = false): ModelOption[] {
|
||||
// PAYG 3P: Default (Sonnet 4.5) + Sonnet (3P custom) or Sonnet 4.6/1M + Opus (3P custom) or Opus 4.1/Opus 4.6/Opus1M + Haiku + Opus 4.1
|
||||
const payg3pOptions = [getDefaultOptionForUser(fastMode)]
|
||||
|
||||
if (getAPIProvider() === 'openai') {
|
||||
payg3pOptions.push(getCodexPlanOption(), getCodexSparkOption())
|
||||
// Add Codex models for openai and codex providers
|
||||
if (getAPIProvider() === 'openai' || getAPIProvider() === 'codex') {
|
||||
payg3pOptions.push(...getCodexModelOptions())
|
||||
}
|
||||
|
||||
const customSonnet = getCustomSonnetOption()
|
||||
@@ -517,9 +563,9 @@ export function getModelOptions(fastMode = false): ModelOption[] {
|
||||
return filterModelOptionsByAllowlist(options)
|
||||
} else if (customModel === 'opusplan') {
|
||||
return filterModelOptionsByAllowlist([...options, getOpusPlanOption()])
|
||||
} else if (customModel === 'codexplan') {
|
||||
} else if (customModel === 'gpt-5.4') {
|
||||
return filterModelOptionsByAllowlist([...options, getCodexPlanOption()])
|
||||
} else if (customModel === 'codexspark') {
|
||||
} else if (customModel === 'gpt-5.3-codex-spark') {
|
||||
return filterModelOptionsByAllowlist([...options, getCodexSparkOption()])
|
||||
} else if (customModel === 'opus' && getAPIProvider() === 'firstParty') {
|
||||
return filterModelOptionsByAllowlist([
|
||||
@@ -554,11 +600,23 @@ export function getModelOptions(fastMode = false): ModelOption[] {
|
||||
*/
|
||||
function filterModelOptionsByAllowlist(options: ModelOption[]): ModelOption[] {
|
||||
const settings = getSettings_DEPRECATED() || {}
|
||||
if (!settings.availableModels) {
|
||||
return options // No restrictions
|
||||
}
|
||||
return options.filter(
|
||||
const filtered = !settings.availableModels
|
||||
? options // No restrictions
|
||||
: options.filter(
|
||||
opt =>
|
||||
opt.value === null || (opt.value !== null && isModelAllowed(opt.value)),
|
||||
)
|
||||
|
||||
// Select state uses option values as identity keys. If two entries share the
|
||||
// same value (e.g. provider-specific aliases collapsing to one model ID),
|
||||
// navigation/focus can become inconsistent and appear as duplicate rendering.
|
||||
const seen = new Set<string>()
|
||||
return filtered.filter(opt => {
|
||||
const key = String(opt.value)
|
||||
if (seen.has(key)) {
|
||||
return false
|
||||
}
|
||||
seen.add(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,9 +23,12 @@ export type ModelStrings = Record<ModelKey, string>
|
||||
const MODEL_KEYS = Object.keys(ALL_MODEL_CONFIGS) as ModelKey[]
|
||||
|
||||
function getBuiltinModelStrings(provider: APIProvider): ModelStrings {
|
||||
// Codex piggybacks on the OpenAI provider transport for Anthropic tier aliases.
|
||||
// Reuse OpenAI mappings so model string lookups never return undefined.
|
||||
const providerKey = provider === 'codex' ? 'openai' : provider
|
||||
const out = {} as ModelStrings
|
||||
for (const key of MODEL_KEYS) {
|
||||
out[key] = ALL_MODEL_CONFIGS[key][provider]
|
||||
out[key] = ALL_MODEL_CONFIGS[key][providerKey]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export type APIProvider =
|
||||
| 'openai'
|
||||
| 'gemini'
|
||||
| 'github'
|
||||
| 'codex'
|
||||
|
||||
export function getAPIProvider(): APIProvider {
|
||||
return isEnvTruthy(process.env.CLAUDE_CODE_USE_GEMINI)
|
||||
@@ -16,7 +17,9 @@ export function getAPIProvider(): APIProvider {
|
||||
: isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB)
|
||||
? 'github'
|
||||
: isEnvTruthy(process.env.CLAUDE_CODE_USE_OPENAI)
|
||||
? 'openai'
|
||||
? isCodexModel()
|
||||
? 'codex'
|
||||
: 'openai'
|
||||
: isEnvTruthy(process.env.CLAUDE_CODE_USE_BEDROCK)
|
||||
? 'bedrock'
|
||||
: isEnvTruthy(process.env.CLAUDE_CODE_USE_VERTEX)
|
||||
@@ -29,6 +32,19 @@ export function getAPIProvider(): APIProvider {
|
||||
export function usesAnthropicAccountFlow(): boolean {
|
||||
return getAPIProvider() === 'firstParty'
|
||||
}
|
||||
function isCodexModel(): boolean {
|
||||
const model = (process.env.OPENAI_MODEL || '').toLowerCase()
|
||||
return (
|
||||
model === 'codexplan' ||
|
||||
model === 'codexspark' ||
|
||||
model === 'gpt-5.4' ||
|
||||
model === 'gpt-5.3-codex' ||
|
||||
model === 'gpt-5.3-codex-spark' ||
|
||||
model === 'gpt-5.2-codex' ||
|
||||
model === 'gpt-5.1-codex-max' ||
|
||||
model === 'gpt-5.1-codex-mini'
|
||||
)
|
||||
}
|
||||
|
||||
export function getAPIProviderForStatsig(): AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS {
|
||||
return getAPIProvider() as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
||||
|
||||
Reference in New Issue
Block a user