// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered import { getInitialMainLoopModel } from '../../bootstrap/state.js' import { isClaudeAISubscriber, isMaxSubscriber, isTeamPremiumSubscriber, getOpenCodeModelName, } from '../auth.js' import { getModelStrings } from './modelStrings.js' import { COST_TIER_3_15, COST_HAIKU_35, COST_HAIKU_45, formatModelPricing, } from '../modelCost.js' import { getSettings_DEPRECATED } from '../settings/settings.js' import { checkOpus1mAccess, checkSonnet1mAccess } from './check1mAccess.js' import { getAPIProvider } from './providers.js' import { isModelAllowed } from './modelAllowlist.js' import { getCanonicalName, getClaudeAiUserDefaultModelDescription, getDefaultSonnetModel, getDefaultOpusModel, getDefaultHaikuModel, getDefaultMainLoopModelSetting, getMarketingNameForModel, getUserSpecifiedModelSetting, isOpus1mMergeEnabled, getOpus46PricingSuffix, renderDefaultModelSetting, type ModelSetting, } from './model.js' import { has1mContext } from '../context.js' import { getGlobalConfig } from '../config.js' // @[MODEL LAUNCH]: Update all the available and default model option strings below. export type ModelOption = { value: ModelSetting label: string description: string descriptionForModel?: string } export function getDefaultOptionForUser(fastMode = false): ModelOption { if (process.env.USER_TYPE === 'ant') { const currentModel = renderDefaultModelSetting( getDefaultMainLoopModelSetting(), ) return { value: null, label: 'Default (recommended)', description: `Use the default model for Ants (currently ${currentModel})`, descriptionForModel: `Default model (currently ${currentModel})`, } } // Subscribers if (isClaudeAISubscriber()) { return { value: null, label: 'Default (recommended)', description: getClaudeAiUserDefaultModelDescription(fastMode), } } // PAYG const provider = getAPIProvider() const isOpenAI = provider === 'openai' const isLocal = provider === 'local' const isOpenCode = provider === 'opencode' const is3P = provider !== 'firstParty' // Check if we should use local model let localModelName: string | null = null if (isLocal || provider === null) { // Try to get local model name from file try { // eslint-disable-next-line @typescript-eslint/no-require-imports const { readFileSync } = require('fs') as typeof import('fs') const { getGlobalClaudeFile } = require('../env.js') as typeof import('../env.js') const raw = readFileSync(getGlobalClaudeFile(), 'utf8') const config = JSON.parse(raw) as { authProvider?: string localBaseUrl?: string localModelName?: string } if (config.authProvider === 'local' && config.localBaseUrl && config.localModelName) { localModelName = config.localModelName } } catch { // Ignore errors } } // Check if we should use OpenCode Zen model if (isOpenCode) { const currentModel = getOpenCodeModelName() || 'big-pickle' const displayName = currentModel === 'big-pickle' ? 'Big Pickle' : currentModel return { value: null, label: 'Default (recommended)', description: `Use the default OpenCode Zen model (currently ${displayName})`, descriptionForModel: `Default OpenCode Zen model (currently ${displayName})`, } } // Fallback: when provider cache is stale (null), check config file directly if (provider === null) { try { // eslint-disable-next-line @typescript-eslint/no-require-imports const { readFileSync } = require('fs') as typeof import('fs') const { getGlobalClaudeFile } = require('../env.js') as typeof import('../env.js') const raw = readFileSync(getGlobalClaudeFile(), 'utf8') const fileConfig = JSON.parse(raw) as { authProvider?: string openCodeModelName?: string } if (fileConfig.authProvider === 'opencode') { const currentModel = fileConfig.openCodeModelName || 'big-pickle' const displayName = currentModel === 'big-pickle' ? 'Big Pickle' : currentModel return { value: null, label: 'Default (recommended)', description: `Use the default OpenCode Zen model (currently ${displayName})`, descriptionForModel: `Default OpenCode Zen model (currently ${displayName})`, } } } catch { // Ignore errors } } if (localModelName) { return { value: null, label: 'Default (recommended)', description: `Use the default local model (currently ${localModelName})`, descriptionForModel: `Default local model (currently ${localModelName})`, } } return { value: null, label: 'Default (recommended)', description: isOpenAI ? `Use the default OpenAI model (currently ${renderDefaultModelSetting(getDefaultMainLoopModelSetting())})` : `Use the default model (currently ${renderDefaultModelSetting(getDefaultMainLoopModelSetting())})${is3P ? '' : ` · ${formatModelPricing(COST_TIER_3_15)}`}`, } } function getCustomSonnetOption(): ModelOption | undefined { const is3P = getAPIProvider() !== 'firstParty' const customSonnetModel = process.env.ANTHROPIC_DEFAULT_SONNET_MODEL // When a 3P user has a custom sonnet model string, show it directly if (is3P && customSonnetModel) { const is1m = has1mContext(customSonnetModel) return { value: 'sonnet', label: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL_NAME ?? customSonnetModel, description: process.env.ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION ?? `Custom Sonnet model${is1m ? ' (1M context)' : ''}`, descriptionForModel: `${process.env.ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION ?? `Custom Sonnet model${is1m ? ' with 1M context' : ''}`} (${customSonnetModel})`, } } } // @[MODEL LAUNCH]: Update or add model option functions (getSonnetXXOption, getOpusXXOption, etc.) // with the new model's label and description. These appear in the /model picker. function getSonnet46Option(): ModelOption { const provider = getAPIProvider() const is3P = provider !== 'firstParty' if (provider === 'openai') { return { value: getModelStrings().sonnet46, label: 'GPT-5.4', description: 'GPT-5.4 · Recommended for most coding tasks', descriptionForModel: 'GPT-5.4 - recommended for most coding and agentic tasks on OpenAI', } } return { value: is3P ? getModelStrings().sonnet46 : 'sonnet', label: 'Sonnet', description: `Sonnet 4.6 · Best for everyday tasks${is3P ? '' : ` · ${formatModelPricing(COST_TIER_3_15)}`}`, descriptionForModel: 'Sonnet 4.6 - best for everyday tasks. Generally recommended for most coding tasks', } } function getCustomOpusOption(): ModelOption | undefined { const is3P = getAPIProvider() !== 'firstParty' const customOpusModel = process.env.ANTHROPIC_DEFAULT_OPUS_MODEL // When a 3P user has a custom opus model string, show it directly if (is3P && customOpusModel) { const is1m = has1mContext(customOpusModel) return { value: 'opus', label: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL_NAME ?? customOpusModel, description: process.env.ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION ?? `Custom Opus model${is1m ? ' (1M context)' : ''}`, descriptionForModel: `${process.env.ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION ?? `Custom Opus model${is1m ? ' with 1M context' : ''}`} (${customOpusModel})`, } } } function getOpus41Option(): ModelOption { return { value: 'opus', label: 'Opus 4.1', description: `Opus 4.1 · Legacy`, descriptionForModel: 'Opus 4.1 - legacy version', } } function getOpus46Option(fastMode = false): ModelOption { const provider = getAPIProvider() const is3P = provider !== 'firstParty' if (provider === 'openai') { return { value: getModelStrings().opus46, label: 'GPT-5.4', description: 'GPT-5.4 · Most capable OpenAI coding model', descriptionForModel: 'GPT-5.4 - most capable OpenAI model for complex coding work', } } return { value: is3P ? getModelStrings().opus46 : 'opus', label: 'Opus', description: `Opus 4.6 · Most capable for complex work${getOpus46PricingSuffix(fastMode)}`, descriptionForModel: 'Opus 4.6 - most capable for complex work', } } export function getSonnet46_1MOption(): ModelOption { const provider = getAPIProvider() const is3P = provider !== 'firstParty' if (provider === 'openai') { return { value: getModelStrings().sonnet46, label: 'GPT-5.4', description: 'GPT-5.4 for long-running OpenAI sessions', descriptionForModel: 'GPT-5.4 for long-running OpenAI sessions and large codebases', } } return { value: is3P ? getModelStrings().sonnet46 + '[1m]' : 'sonnet[1m]', label: 'Sonnet (1M context)', description: `Sonnet 4.6 for long sessions${is3P ? '' : ` · ${formatModelPricing(COST_TIER_3_15)}`}`, descriptionForModel: 'Sonnet 4.6 with 1M context window - for long sessions with large codebases', } } export function getOpus46_1MOption(fastMode = false): ModelOption { const provider = getAPIProvider() const is3P = provider !== 'firstParty' if (provider === 'openai') { return { value: getModelStrings().opus46, label: 'GPT-5.4', description: 'GPT-5.4 for long-running OpenAI sessions', descriptionForModel: 'GPT-5.4 for long-running OpenAI sessions and large codebases', } } return { value: is3P ? getModelStrings().opus46 + '[1m]' : 'opus[1m]', label: 'Opus (1M context)', description: `Opus 4.6 for long sessions${getOpus46PricingSuffix(fastMode)}`, descriptionForModel: 'Opus 4.6 with 1M context window - for long sessions with large codebases', } } function getCustomHaikuOption(): ModelOption | undefined { const is3P = getAPIProvider() !== 'firstParty' const customHaikuModel = process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL // When a 3P user has a custom haiku model string, show it directly if (is3P && customHaikuModel) { return { value: 'haiku', label: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME ?? customHaikuModel, description: process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION ?? 'Custom Haiku model', descriptionForModel: `${process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION ?? 'Custom Haiku model'} (${customHaikuModel})`, } } } function getHaiku45Option(): ModelOption { const provider = getAPIProvider() const is3P = provider !== 'firstParty' if (provider === 'openai') { return { value: getModelStrings().haiku45, label: 'GPT-5.4 Mini', description: 'GPT-5.4 Mini · Fastest OpenAI option for quick answers', descriptionForModel: 'GPT-5.4 Mini - fastest OpenAI option for quick answers and lightweight tasks', } } return { value: 'haiku', label: 'Haiku', description: `Haiku 4.5 · Fastest for quick answers${is3P ? '' : ` · ${formatModelPricing(COST_HAIKU_45)}`}`, descriptionForModel: 'Haiku 4.5 - fastest for quick answers. Lower cost but less capable than Sonnet 4.6.', } } function getHaiku35Option(): ModelOption { const provider = getAPIProvider() const is3P = provider !== 'firstParty' if (provider === 'openai') { return { value: getModelStrings().haiku35, label: 'GPT-5.4 Mini', description: 'GPT-5.4 Mini · Fastest OpenAI option for simple tasks', descriptionForModel: 'GPT-5.4 Mini - fastest OpenAI option for simple tasks and lightweight requests', } } return { value: 'haiku', label: 'Haiku', description: `Haiku 3.5 for simple tasks${is3P ? '' : ` · ${formatModelPricing(COST_HAIKU_35)}`}`, descriptionForModel: 'Haiku 3.5 - faster and lower cost, but less capable than Sonnet. Use for simple tasks.', } } function getHaikuOption(): ModelOption { // Return correct Haiku option based on provider const haikuModel = getDefaultHaikuModel() return haikuModel === getModelStrings().haiku45 ? getHaiku45Option() : getHaiku35Option() } function getMaxOpusOption(fastMode = false): ModelOption { return { value: 'opus', label: 'Opus', description: `Opus 4.6 · Most capable for complex work${fastMode ? getOpus46PricingSuffix(true) : ''}`, } } export function getMaxSonnet46_1MOption(): ModelOption { const is3P = getAPIProvider() !== 'firstParty' const billingInfo = isClaudeAISubscriber() ? ' · Billed as extra usage' : '' return { value: 'sonnet[1m]', label: 'Sonnet (1M context)', description: `Sonnet 4.6 with 1M context${billingInfo}${is3P ? '' : ` · ${formatModelPricing(COST_TIER_3_15)}`}`, } } export function getMaxOpus46_1MOption(fastMode = false): ModelOption { const billingInfo = isClaudeAISubscriber() ? ' · Billed as extra usage' : '' return { value: 'opus[1m]', label: 'Opus (1M context)', description: `Opus 4.6 with 1M context${billingInfo}${getOpus46PricingSuffix(fastMode)}`, } } function getMergedOpus1MOption(fastMode = false): ModelOption { const is3P = getAPIProvider() !== 'firstParty' return { value: is3P ? getModelStrings().opus46 + '[1m]' : 'opus[1m]', label: 'Opus (1M context)', description: `Opus 4.6 with 1M context · Most capable for complex work${!is3P && fastMode ? getOpus46PricingSuffix(fastMode) : ''}`, descriptionForModel: 'Opus 4.6 with 1M context - most capable for complex work', } } const MaxSonnet46Option: ModelOption = { value: 'sonnet', label: 'Sonnet', description: 'Sonnet 4.6 · Best for everyday tasks', } const MaxHaiku45Option: ModelOption = { value: 'haiku', label: 'Haiku', description: 'Haiku 4.5 · Fastest for quick answers', } function getOpusPlanOption(): ModelOption { return { value: 'opusplan', label: 'Opus Plan Mode', description: 'Use Opus 4.6 in plan mode, Sonnet 4.6 otherwise', } } // @[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[] { const provider = getAPIProvider() // If no provider is configured, only return Default option if (!provider) { return [getDefaultOptionForUser(fastMode)] } if (process.env.USER_TYPE === 'ant') { // Build options from antModels config const antModelOptions: ModelOption[] = getAntModels().map(m => ({ value: m.alias, label: m.label, description: m.description ?? `[ANT-ONLY] ${m.label} (${m.model})`, })) return [ getDefaultOptionForUser(), ...antModelOptions, getMergedOpus1MOption(fastMode), getSonnet46Option(), getSonnet46_1MOption(), getHaiku45Option(), ] } if (isClaudeAISubscriber()) { if (isMaxSubscriber() || isTeamPremiumSubscriber()) { // Max and Team Premium users: Opus is default, show Sonnet as alternative const premiumOptions = [getDefaultOptionForUser(fastMode)] if (!isOpus1mMergeEnabled() && checkOpus1mAccess()) { premiumOptions.push(getMaxOpus46_1MOption(fastMode)) } premiumOptions.push(MaxSonnet46Option) if (checkSonnet1mAccess()) { premiumOptions.push(getMaxSonnet46_1MOption()) } premiumOptions.push(MaxHaiku45Option) return premiumOptions } // Pro/Team Standard/Enterprise users: Sonnet is default, show Opus as alternative const standardOptions = [getDefaultOptionForUser(fastMode)] if (checkSonnet1mAccess()) { standardOptions.push(getMaxSonnet46_1MOption()) } if (isOpus1mMergeEnabled()) { standardOptions.push(getMergedOpus1MOption(fastMode)) } else { standardOptions.push(getMaxOpusOption(fastMode)) if (checkOpus1mAccess()) { standardOptions.push(getMaxOpus46_1MOption(fastMode)) } } standardOptions.push(MaxHaiku45Option) return standardOptions } // PAYG 1P API: Default (Sonnet) + Sonnet 1M + Opus 4.6 + Opus 1M + Haiku if (getAPIProvider() === 'firstParty') { const payg1POptions = [getDefaultOptionForUser(fastMode)] if (checkSonnet1mAccess()) { payg1POptions.push(getSonnet46_1MOption()) } if (isOpus1mMergeEnabled()) { payg1POptions.push(getMergedOpus1MOption(fastMode)) } else { payg1POptions.push(getOpus46Option(fastMode)) if (checkOpus1mAccess()) { payg1POptions.push(getOpus46_1MOption(fastMode)) } } payg1POptions.push(getHaiku45Option()) return payg1POptions } // OpenAI API: Default (GPT-5.4) + GPT-5.4 + GPT-5.4 Mini if (getAPIProvider() === 'openai') { const openAIOptions = [getDefaultOptionForUser(fastMode)] openAIOptions.push(getSonnet46Option()) openAIOptions.push(getOpus46Option(fastMode)) openAIOptions.push(getHaiku45Option()) return openAIOptions } // Local API: Show only Default option if (getAPIProvider() === 'local') { return [getDefaultOptionForUser(fastMode)] } // NVIDIA API: Show dynamically fetched models from the NVIDIA API catalog if (getAPIProvider() === 'nvidia') { // Trigger background fetch of NVIDIA models (non-blocking) // This will populate the cache for subsequent calls void import('../../services/api/nvidiaClient.js').then(({ fetchNvidiaModels }) => { fetchNvidiaModels() }) const { getCachedNvidiaModels } = require('../../services/api/nvidiaClient.js') as { getCachedNvidiaModels: () => string[] } const models = getCachedNvidiaModels() if (models && models.length > 0) { return [ getDefaultOptionForUser(fastMode), ...models.map(m => ({ value: m, label: m, description: 'NVIDIA NIM model', })), ] } return [getDefaultOptionForUser(fastMode)] } // OpenCode Zen: Fetch models dynamically from API // If no API key, show only free models // If API key is provided, show all models except free ones if (getAPIProvider() === 'opencode') { const defaultOpt = getDefaultOptionForUser(fastMode) try { const { getCachedOpencodeModels } = require('../../services/api/opencodeClient.js') const { getOpenCodeApiKey: getApiKey } = require('../../utils/auth.js') const models = getCachedOpencodeModels() const hasApiKey = !!getApiKey() if (models && Array.isArray(models) && models.length > 0) { const filtered = hasApiKey ? models : models.filter(m => m.isFree) if (filtered.length > 0) { return [ defaultOpt, ...filtered.map(m => { let label = m.name || m.id let description = hasApiKey ? 'OpenCode Zen Model' : '限时免费模型' if (m.id === 'big-pickle') { label = 'Big Pickle' description = '旗舰模型,限时免费,适合复杂任务' } else if (m.id === 'gpt-5-nano') { label = 'GPT 5 Nano' description = '永久免费,轻量快速,隐私安全' } else if (m.id === 'minimax-m2.5-free') { label = 'MiniMax M2.5 Free' } else if (m.id === 'hy3-preview-free') { label = 'HY3 Preview Free' } else if (m.id === 'ling-2.6-flash-free') { label = 'Ling 2.6 Flash Free' } else if (m.id === 'trinity-large-preview-free') { label = 'Trinity Large Preview Free' } else if (m.id === 'nemotron-3-super-free') { label = 'Nemotron 3 Super Free' } return { value: m.id, label, description } }), ] } } } catch { // Ignore errors } return [defaultOpt] } // OpenRouter API: Show all available models from OpenRouter if (getAPIProvider() === 'openrouter') { // Trigger background fetch of OpenRouter models (non-blocking) // This will populate the cache for subsequent calls void import('./openRouterModels.js').then(({ startOpenRouterModelsFetch }) => { startOpenRouterModelsFetch() }) // Check if we have cached models from a previous fetch // We use require to access the module cache synchronously let cachedModels: ModelOption[] = [] try { // eslint-disable-next-line @typescript-eslint/no-require-imports const openRouterModelsModule = require('./openRouterModels.js') as { getCachedOpenRouterModels: () => ModelOption[] getFirstFreeModel: () => ModelOption | null } cachedModels = openRouterModelsModule.getCachedOpenRouterModels() // Find the first free model const firstFreeModel = openRouterModelsModule.getFirstFreeModel() // If we have cached models, show them if (cachedModels.length > 0) { let modelsToShow = cachedModels // Show all models // If there's a free model, put it at the front if (firstFreeModel) { // Remove the free model from its current position modelsToShow = modelsToShow.filter(m => m.value !== firstFreeModel.value) // Add it to the front modelsToShow = [firstFreeModel, ...modelsToShow] } return modelsToShow } } catch { // Module not yet loaded - will be populated by background fetch } // No cached models yet, return empty array // The background fetch will populate the cache for next time return [] } // 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)] const customSonnet = getCustomSonnetOption() if (customSonnet !== undefined) { payg3pOptions.push(customSonnet) } else { // Add Sonnet 4.6 since Sonnet 4.5 is the default payg3pOptions.push(getSonnet46Option()) if (checkSonnet1mAccess()) { payg3pOptions.push(getSonnet46_1MOption()) } } const customOpus = getCustomOpusOption() if (customOpus !== undefined) { payg3pOptions.push(customOpus) } else { // Add Opus 4.1, Opus 4.6 and Opus 4.6 1M payg3pOptions.push(getOpus41Option()) // This is the default opus payg3pOptions.push(getOpus46Option(fastMode)) if (checkOpus1mAccess()) { payg3pOptions.push(getOpus46_1MOption(fastMode)) } } const customHaiku = getCustomHaikuOption() if (customHaiku !== undefined) { payg3pOptions.push(customHaiku) } else { payg3pOptions.push(getHaikuOption()) } return payg3pOptions } // @[MODEL LAUNCH]: Add the new model ID to the appropriate family pattern below // so the "newer version available" hint works correctly. /** * Map a full model name to its family alias and the marketing name of the * version the alias currently resolves to. Used to detect when a user has * a specific older version pinned and a newer one is available. */ function getModelFamilyInfo( model: string, ): { alias: string; currentVersionName: string } | null { const canonical = getCanonicalName(model) // Sonnet family if ( canonical.includes('claude-sonnet-4-6') || canonical.includes('claude-sonnet-4-5') || canonical.includes('claude-sonnet-4-') || canonical.includes('claude-3-7-sonnet') || canonical.includes('claude-3-5-sonnet') ) { const currentName = getMarketingNameForModel(getDefaultSonnetModel()) if (currentName) { return { alias: 'Sonnet', currentVersionName: currentName } } } // Opus family if (canonical.includes('claude-opus-4')) { const currentName = getMarketingNameForModel(getDefaultOpusModel()) if (currentName) { return { alias: 'Opus', currentVersionName: currentName } } } // Haiku family if ( canonical.includes('claude-haiku') || canonical.includes('claude-3-5-haiku') ) { const currentName = getMarketingNameForModel(getDefaultHaikuModel()) if (currentName) { return { alias: 'Haiku', currentVersionName: currentName } } } return null } /** * Returns a ModelOption for a known Anthropic model with a human-readable * label, and an upgrade hint if a newer version is available via the alias. * Returns null if the model is not recognized. */ function getKnownModelOption(model: string): ModelOption | null { const marketingName = getMarketingNameForModel(model) if (!marketingName) return null const familyInfo = getModelFamilyInfo(model) if (!familyInfo) { return { value: model, label: marketingName, description: model, } } // Check if the alias currently resolves to a different (newer) version if (marketingName !== familyInfo.currentVersionName) { return { value: model, label: marketingName, description: `Newer version available · select ${familyInfo.alias} for ${familyInfo.currentVersionName}`, } } // Same version as the alias — just show the friendly name return { value: model, label: marketingName, description: model, } } export function getModelOptions(fastMode = false): ModelOption[] { const options = getModelOptionsBase(fastMode) // Add the custom model from the ANTHROPIC_CUSTOM_MODEL_OPTION env var const envCustomModel = process.env.ANTHROPIC_CUSTOM_MODEL_OPTION if ( envCustomModel && !options.some(existing => existing.value === envCustomModel) ) { options.push({ value: envCustomModel, label: process.env.ANTHROPIC_CUSTOM_MODEL_OPTION_NAME ?? envCustomModel, description: process.env.ANTHROPIC_CUSTOM_MODEL_OPTION_DESCRIPTION ?? `Custom model (${envCustomModel})`, }) } // Append additional model options fetched during bootstrap for (const opt of getGlobalConfig().additionalModelOptionsCache ?? []) { if (!options.some(existing => existing.value === opt.value)) { options.push(opt) } } // Add custom model from either the current model value or the initial one // if it is not already in the options. let customModel: ModelSetting = null const currentMainLoopModel = getUserSpecifiedModelSetting() const initialMainLoopModel = getInitialMainLoopModel() if (currentMainLoopModel !== undefined && currentMainLoopModel !== null) { customModel = currentMainLoopModel } else if (initialMainLoopModel !== null) { customModel = initialMainLoopModel } if (customModel === null || options.some(opt => opt.value === customModel)) { return filterModelOptionsByAllowlist(options) } else if (customModel === 'opusplan') { return filterModelOptionsByAllowlist([...options, getOpusPlanOption()]) } else if (customModel === 'opus' && getAPIProvider() === 'firstParty') { return filterModelOptionsByAllowlist([ ...options, getMaxOpusOption(fastMode), ]) } else if (customModel === 'opus[1m]' && getAPIProvider() === 'firstParty') { return filterModelOptionsByAllowlist([ ...options, getMergedOpus1MOption(fastMode), ]) } else { // Try to show a human-readable label for known Anthropic models, with an // upgrade hint if the alias now resolves to a newer version. const knownOption = getKnownModelOption(customModel) if (knownOption) { options.push(knownOption) } else { options.push({ value: customModel, label: customModel, description: 'Custom model', }) } return filterModelOptionsByAllowlist(options) } } /** * Filter model options by the availableModels allowlist. * Always preserves the "Default" option (value: null). */ function filterModelOptionsByAllowlist(options: ModelOption[]): ModelOption[] { const settings = getSettings_DEPRECATED() || {} if (!settings.availableModels) { return options // No restrictions } return options.filter( opt => opt.value === null || (opt.value !== null && isModelAllowed(opt.value)), ) }