Download src/utils/model/modelOptions.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 28.4 kB
-
https://huggingface.co/chenbhao/codev/resolve/main/src/utils/model/modelOptions.ts
- Command line
-
hf download hf://chenbhao/codev/src/utils/model/modelOptions.ts
-
curl -L -o modelOptions.ts https://huggingface.co/chenbhao/codev/resolve/main/src/utils/model/modelOptions.ts
28.4 kB
| // 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)), | |
| ) | |
| } | |