| |
| import { getInitialMainLoopModel } from '../../bootstrap/state.js' |
| import { |
| isClaudeAISubscriber, |
| isMaxSubscriber, |
| isTeamPremiumSubscriber, |
| } 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' |
|
|
| |
|
|
| 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})`, |
| } |
| } |
|
|
| |
| if (isClaudeAISubscriber()) { |
| return { |
| value: null, |
| label: 'Default (recommended)', |
| description: getClaudeAiUserDefaultModelDescription(fastMode), |
| } |
| } |
|
|
| |
| const is3P = getAPIProvider() !== 'firstParty' |
| return { |
| value: null, |
| label: 'Default (recommended)', |
| description: `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 |
| |
| 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})`, |
| } |
| } |
| } |
|
|
| |
| |
| function getSonnet46Option(): ModelOption { |
| const is3P = getAPIProvider() !== 'firstParty' |
| 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 |
| |
| 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 is3P = getAPIProvider() !== 'firstParty' |
| 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 is3P = getAPIProvider() !== 'firstParty' |
| 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 is3P = getAPIProvider() !== 'firstParty' |
| 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 |
| |
| 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 is3P = getAPIProvider() !== 'firstParty' |
| 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 is3P = getAPIProvider() !== 'firstParty' |
| 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 { |
| |
| 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', |
| } |
| } |
|
|
| |
| |
| function getModelOptionsBase(fastMode = false): ModelOption[] { |
| if (process.env.USER_TYPE === 'ant') { |
| |
| 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()) { |
| |
| 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 |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| const payg3pOptions = [getDefaultOptionForUser(fastMode)] |
|
|
| const customSonnet = getCustomSonnetOption() |
| if (customSonnet !== undefined) { |
| payg3pOptions.push(customSonnet) |
| } else { |
| |
| payg3pOptions.push(getSonnet46Option()) |
| if (checkSonnet1mAccess()) { |
| payg3pOptions.push(getSonnet46_1MOption()) |
| } |
| } |
|
|
| const customOpus = getCustomOpusOption() |
| if (customOpus !== undefined) { |
| payg3pOptions.push(customOpus) |
| } else { |
| |
| payg3pOptions.push(getOpus41Option()) |
| 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 |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function getModelFamilyInfo( |
| model: string, |
| ): { alias: string; currentVersionName: string } | null { |
| const canonical = getCanonicalName(model) |
|
|
| |
| 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 } |
| } |
| } |
|
|
| |
| if (canonical.includes('claude-opus-4')) { |
| const currentName = getMarketingNameForModel(getDefaultOpusModel()) |
| if (currentName) { |
| return { alias: 'Opus', currentVersionName: currentName } |
| } |
| } |
|
|
| |
| if ( |
| canonical.includes('claude-haiku') || |
| canonical.includes('claude-3-5-haiku') |
| ) { |
| const currentName = getMarketingNameForModel(getDefaultHaikuModel()) |
| if (currentName) { |
| return { alias: 'Haiku', currentVersionName: currentName } |
| } |
| } |
|
|
| return null |
| } |
|
|
| |
| |
| |
| |
| |
| 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, |
| } |
| } |
|
|
| |
| if (marketingName !== familyInfo.currentVersionName) { |
| return { |
| value: model, |
| label: marketingName, |
| description: `Newer version available · select ${familyInfo.alias} for ${familyInfo.currentVersionName}`, |
| } |
| } |
|
|
| |
| return { |
| value: model, |
| label: marketingName, |
| description: model, |
| } |
| } |
|
|
| export function getModelOptions(fastMode = false): ModelOption[] { |
| const options = getModelOptionsBase(fastMode) |
|
|
| |
| 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})`, |
| }) |
| } |
|
|
| |
| for (const opt of getGlobalConfig().additionalModelOptionsCache ?? []) { |
| if (!options.some(existing => existing.value === opt.value)) { |
| options.push(opt) |
| } |
| } |
|
|
| |
| |
| 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 { |
| |
| |
| const knownOption = getKnownModelOption(customModel) |
| if (knownOption) { |
| options.push(knownOption) |
| } else { |
| options.push({ |
| value: customModel, |
| label: customModel, |
| description: 'Custom model', |
| }) |
| } |
| return filterModelOptionsByAllowlist(options) |
| } |
| } |
|
|
| |
| |
| |
| |
| function filterModelOptionsByAllowlist(options: ModelOption[]): ModelOption[] { |
| const settings = getSettings_DEPRECATED() || {} |
| if (!settings.availableModels) { |
| return options |
| } |
| return options.filter( |
| opt => |
| opt.value === null || (opt.value !== null && isModelAllowed(opt.value)), |
| ) |
| } |
|
|