mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
Refactor model handling and deduplication logic in settings service
- Updated ModelDropdown to use option names directly instead of model names. - Introduced canonicalModelNameForProvider to standardize model names across providers. - Implemented deduplication of provider models to avoid duplicates and manage aliases effectively. - Enhanced model fallback logic in modelCapabilities for better compatibility with new AI models. - Adjusted settings service to incorporate deduplication and normalization of model names.
This commit is contained in:
parent
d4ee802099
commit
d5dcfe2f07
4 changed files with 151 additions and 454 deletions
|
|
@ -37,8 +37,8 @@ const ModelSelectBox = ({ options, featureName, className }: { options: ModelOpt
|
||||||
options={options}
|
options={options}
|
||||||
selectedOption={selectedOption}
|
selectedOption={selectedOption}
|
||||||
onChangeOption={onChangeOption}
|
onChangeOption={onChangeOption}
|
||||||
getOptionDisplayName={(option) => option.selection.modelName}
|
getOptionDisplayName={(option) => option.name}
|
||||||
getOptionDropdownName={(option) => option.selection.modelName}
|
getOptionDropdownName={(option) => option.name}
|
||||||
getOptionDropdownDetail={(option) => option.selection.providerName}
|
getOptionDropdownDetail={(option) => option.selection.providerName}
|
||||||
getOptionsEqual={(a, b) => optionsEqual([a], [b])}
|
getOptionsEqual={(a, b) => optionsEqual([a], [b])}
|
||||||
className={className}
|
className={className}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||||
*--------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { defaultModelsOfProvider } from './modelCapabilities.js';
|
||||||
import { ProviderName, VoidStatefulModelInfo } from './voidSettingsTypes.js';
|
import { ProviderName, VoidStatefulModelInfo } from './voidSettingsTypes.js';
|
||||||
|
|
||||||
/** One loaded model per endpoint (mlx_lm.server / afm). */
|
/** One loaded model per endpoint (mlx_lm.server / afm). */
|
||||||
|
|
@ -13,16 +14,100 @@ export type SingleAutodetectedLocalProvider = typeof singleAutodetectedLocalProv
|
||||||
const canonicalAppleFoundationModelName = (modelName: string) =>
|
const canonicalAppleFoundationModelName = (modelName: string) =>
|
||||||
modelName === 'foundation-models' || modelName === 'foundation-model' ? 'foundation' : modelName
|
modelName === 'foundation-models' || modelName === 'foundation-model' ? 'foundation' : modelName
|
||||||
|
|
||||||
|
/** Map legacy / alias API ids to one canonical name per provider (avoids duplicate list entries). */
|
||||||
|
export const canonicalModelNameForProvider = (providerName: ProviderName, modelName: string): string => {
|
||||||
|
const lower = modelName.toLowerCase()
|
||||||
|
|
||||||
|
if (providerName === 'appleFoundationModels') {
|
||||||
|
return canonicalAppleFoundationModelName(modelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerName === 'anthropic') {
|
||||||
|
if (lower === 'claude-sonnet-4-5-20250929') return 'claude-sonnet-4-5'
|
||||||
|
if (lower === 'claude-3-7-sonnet-20250219') return 'claude-3-7-sonnet-latest'
|
||||||
|
if (lower === 'claude-sonnet-4-20250514') return 'claude-sonnet-4-6'
|
||||||
|
if (lower === 'claude-opus-4-20250514') return 'claude-opus-4-6'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerName === 'openAI') {
|
||||||
|
if (lower === 'gpt-4o-mini') return 'gpt-4.1-mini'
|
||||||
|
if (lower === 'gpt-4o') return 'gpt-4.1'
|
||||||
|
if (lower === 'o1-mini') return 'o4-mini'
|
||||||
|
if (lower === 'o1') return 'o3'
|
||||||
|
if (lower === 'o3-mini') return 'o4-mini'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerName === 'xAI') {
|
||||||
|
if (lower.startsWith('grok-2') || lower.startsWith('grok-3')) return 'grok-4.3'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerName === 'mistral') {
|
||||||
|
if (lower === 'magistral-small-latest') return 'mistral-small-latest'
|
||||||
|
if (lower === 'devstral-small-latest') return 'devstral-latest'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (providerName === 'gemini') {
|
||||||
|
if (lower.includes('preview') || lower.includes('-exp-') || lower.includes('2.0') || lower.includes('1.5')) {
|
||||||
|
if (lower.includes('pro')) return 'gemini-2.5-pro'
|
||||||
|
if (lower.includes('flash-lite') || lower.includes('flash_lite')) return 'gemini-2.5-flash-lite'
|
||||||
|
return 'gemini-2.5-flash'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modelName
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelTypePriority: Record<VoidStatefulModelInfo['type'], number> = {
|
||||||
|
autodetected: 3,
|
||||||
|
default: 2,
|
||||||
|
custom: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Collapse duplicate model names (aliases, default+custom overlap, etc.). */
|
||||||
|
export const dedupeProviderModels = (providerName: ProviderName, models: VoidStatefulModelInfo[]): VoidStatefulModelInfo[] => {
|
||||||
|
const defaultNames = new Set<string>(defaultModelsOfProvider[providerName] ?? [])
|
||||||
|
const groups = new Map<string, VoidStatefulModelInfo[]>()
|
||||||
|
|
||||||
|
for (const model of models) {
|
||||||
|
const canonical = canonicalModelNameForProvider(providerName, model.modelName)
|
||||||
|
const normalized = canonical === model.modelName ? model : { ...model, modelName: canonical }
|
||||||
|
const key = canonical.toLowerCase()
|
||||||
|
const group = groups.get(key) ?? []
|
||||||
|
group.push(normalized)
|
||||||
|
groups.set(key, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deduped: VoidStatefulModelInfo[] = []
|
||||||
|
for (const group of groups.values()) {
|
||||||
|
const best = group.reduce((keep, candidate) => {
|
||||||
|
const keepPriority = modelTypePriority[keep.type]
|
||||||
|
const candidatePriority = modelTypePriority[candidate.type]
|
||||||
|
if (candidatePriority > keepPriority) return candidate
|
||||||
|
if (keepPriority > candidatePriority) return keep
|
||||||
|
const keepInDefaults = defaultNames.has(keep.modelName) ? 1 : 0
|
||||||
|
const candidateInDefaults = defaultNames.has(candidate.modelName) ? 1 : 0
|
||||||
|
return candidateInDefaults > keepInDefaults ? candidate : keep
|
||||||
|
})
|
||||||
|
deduped.push({
|
||||||
|
...best,
|
||||||
|
modelName: best.modelName,
|
||||||
|
isHidden: group.every(m => m.isHidden),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return deduped
|
||||||
|
}
|
||||||
|
|
||||||
export const normalizeAutodetectedModelNamesForProvider = (providerName: ProviderName, modelNames: string[]): string[] => {
|
export const normalizeAutodetectedModelNamesForProvider = (providerName: ProviderName, modelNames: string[]): string[] => {
|
||||||
if (providerName === 'appleFoundationModels') {
|
if (providerName === 'appleFoundationModels') {
|
||||||
const normalized = modelNames.map(canonicalAppleFoundationModelName)
|
const normalized = modelNames.map(canonicalAppleFoundationModelName)
|
||||||
return [new Set(normalized).values().next().value ?? 'foundation']
|
return [new Set(normalized).values().next().value ?? 'foundation']
|
||||||
}
|
}
|
||||||
if (providerName === 'mlx') {
|
if (providerName === 'mlx') {
|
||||||
const unique = [...new Set(modelNames)]
|
const unique = [...new Set(modelNames.map(n => canonicalModelNameForProvider(providerName, n)))]
|
||||||
return unique.length > 0 ? [unique[0]] : []
|
return unique.length > 0 ? [unique[0]] : []
|
||||||
}
|
}
|
||||||
return modelNames
|
return modelNames.map(n => canonicalModelNameForProvider(providerName, n))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const consolidateSingleAutodetectedProviderModels = (
|
export const consolidateSingleAutodetectedProviderModels = (
|
||||||
|
|
@ -45,8 +130,8 @@ export const consolidateSingleAutodetectedProviderModels = (
|
||||||
modelName: primaryName,
|
modelName: primaryName,
|
||||||
type: 'autodetected',
|
type: 'autodetected',
|
||||||
}
|
}
|
||||||
return [
|
return dedupeProviderModels(providerName, [
|
||||||
primary,
|
primary,
|
||||||
...customModels.filter(m => m.modelName !== primaryName),
|
...customModels.filter(m => canonicalModelNameForProvider(providerName, m.modelName).toLowerCase() !== primaryName.toLowerCase()),
|
||||||
]
|
])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -420,8 +420,10 @@ const extensiveModelOptionsFallback: VoidStaticProviderInfo['modelOptionsFallbac
|
||||||
if (lower.includes('flash')) return toFallback(geminiModelOptions, 'gemini-2.5-flash')
|
if (lower.includes('flash')) return toFallback(geminiModelOptions, 'gemini-2.5-flash')
|
||||||
return toFallback(geminiModelOptions, 'gemini-2.5-pro')
|
return toFallback(geminiModelOptions, 'gemini-2.5-pro')
|
||||||
}
|
}
|
||||||
|
if (lower.includes('gemini')) return toFallback(geminiModelOptions, 'gemini-2.5-flash')
|
||||||
|
|
||||||
if (lower.includes('claude-3-5') || lower.includes('claude-3.5')) return toFallback(anthropicModelOptions, 'claude-3-5-sonnet-20241022')
|
if (lower.includes('claude-3-7') || lower.includes('claude-3.7')) return toFallback(anthropicModelOptions, 'claude-3-7-sonnet-20250219')
|
||||||
|
if (lower.includes('claude-3-5') || lower.includes('claude-3.5')) return toFallback(anthropicModelOptions, 'claude-sonnet-4-6')
|
||||||
if (lower.includes('opus-4-7') || lower.includes('opus-4.7')) return toFallback(anthropicModelOptions, 'claude-opus-4-7')
|
if (lower.includes('opus-4-7') || lower.includes('opus-4.7')) return toFallback(anthropicModelOptions, 'claude-opus-4-7')
|
||||||
if (lower.includes('sonnet-4-6') || lower.includes('sonnet-4.6')) return toFallback(anthropicModelOptions, 'claude-sonnet-4-6')
|
if (lower.includes('sonnet-4-6') || lower.includes('sonnet-4.6')) return toFallback(anthropicModelOptions, 'claude-sonnet-4-6')
|
||||||
if (lower.includes('haiku-4-5') || lower.includes('haiku-4.5')) return toFallback(anthropicModelOptions, 'claude-haiku-4-5')
|
if (lower.includes('haiku-4-5') || lower.includes('haiku-4.5')) return toFallback(anthropicModelOptions, 'claude-haiku-4-5')
|
||||||
|
|
@ -434,7 +436,7 @@ const extensiveModelOptionsFallback: VoidStaticProviderInfo['modelOptionsFallbac
|
||||||
if (lower.includes('reasoning')) return toFallback(xAIModelOptions, 'grok-4.20-0309-reasoning')
|
if (lower.includes('reasoning')) return toFallback(xAIModelOptions, 'grok-4.20-0309-reasoning')
|
||||||
return toFallback(xAIModelOptions, 'grok-4.3')
|
return toFallback(xAIModelOptions, 'grok-4.3')
|
||||||
}
|
}
|
||||||
if (lower.includes('grok2') || lower.includes('grok-2')) return toFallback(xAIModelOptions, 'grok-2')
|
if (lower.includes('grok2') || lower.includes('grok-2') || lower.includes('grok-3') || lower.includes('grok3')) return toFallback(xAIModelOptions, 'grok-4.3')
|
||||||
if (lower.includes('grok')) return toFallback(xAIModelOptions, 'grok-4.3')
|
if (lower.includes('grok')) return toFallback(xAIModelOptions, 'grok-4.3')
|
||||||
|
|
||||||
if (lower.includes('deepseek') && (lower.includes('v4-pro') || lower.includes('v4_pro'))) return toFallback(deepseekModelOptions, 'deepseek-v4-pro')
|
if (lower.includes('deepseek') && (lower.includes('v4-pro') || lower.includes('v4_pro'))) return toFallback(deepseekModelOptions, 'deepseek-v4-pro')
|
||||||
|
|
@ -473,12 +475,12 @@ const extensiveModelOptionsFallback: VoidStaticProviderInfo['modelOptionsFallbac
|
||||||
if (lower.includes('gpt') && lower.includes('nano') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions, 'gpt-4.1-nano')
|
if (lower.includes('gpt') && lower.includes('nano') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions, 'gpt-4.1-nano')
|
||||||
if (lower.includes('gpt') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions, 'gpt-4.1')
|
if (lower.includes('gpt') && (lower.includes('4.1') || lower.includes('4-1'))) return toFallback(openAIModelOptions, 'gpt-4.1')
|
||||||
|
|
||||||
if (lower.includes('4o') && lower.includes('mini')) return toFallback(openAIModelOptions, 'gpt-4o-mini')
|
if (lower.includes('4o') && lower.includes('mini')) return toFallback(openAIModelOptions, 'gpt-4.1-mini')
|
||||||
if (lower.includes('4o')) return toFallback(openAIModelOptions, 'gpt-4o')
|
if (lower.includes('4o')) return toFallback(openAIModelOptions, 'gpt-4.1')
|
||||||
|
|
||||||
if (lower.includes('o1') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o1-mini')
|
if (lower.includes('o1') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o4-mini')
|
||||||
if (lower.includes('o1')) return toFallback(openAIModelOptions, 'o1')
|
if (lower.includes('o1')) return toFallback(openAIModelOptions, 'o3')
|
||||||
if (lower.includes('o3') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o3-mini')
|
if (lower.includes('o3') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o4-mini')
|
||||||
if (lower.includes('o3')) return toFallback(openAIModelOptions, 'o3')
|
if (lower.includes('o3')) return toFallback(openAIModelOptions, 'o3')
|
||||||
if (lower.includes('o4') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o4-mini')
|
if (lower.includes('o4') && lower.includes('mini')) return toFallback(openAIModelOptions, 'o4-mini')
|
||||||
|
|
||||||
|
|
@ -565,67 +567,6 @@ const anthropicModelOptions = {
|
||||||
reasoningCapabilities: anthropicThinkingCapabilities,
|
reasoningCapabilities: anthropicThinkingCapabilities,
|
||||||
|
|
||||||
},
|
},
|
||||||
'claude-opus-4-20250514': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 15.00, cache_read: 1.50, cache_write: 18.75, output: 30.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'anthropic-style',
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
reasoningCapabilities: anthropicThinkingCapabilities,
|
|
||||||
|
|
||||||
},
|
|
||||||
'claude-sonnet-4-20250514': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 6.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'anthropic-style',
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
reasoningCapabilities: anthropicThinkingCapabilities,
|
|
||||||
|
|
||||||
},
|
|
||||||
'claude-3-5-sonnet-20241022': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 3.00, cache_read: 0.30, cache_write: 3.75, output: 15.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'anthropic-style',
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'claude-3-5-haiku-20241022': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 0.80, cache_read: 0.08, cache_write: 1.00, output: 4.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'anthropic-style',
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'claude-3-opus-20240229': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: 4_096,
|
|
||||||
cost: { input: 15.00, cache_read: 1.50, cache_write: 18.75, output: 75.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'anthropic-style',
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'claude-3-sonnet-20240229': { // no point of using this, but including this for people who put it in
|
|
||||||
contextWindow: 200_000, cost: { input: 3.00, output: 15.00 },
|
|
||||||
downloadable: false,
|
|
||||||
reservedOutputTokenSpace: 4_096,
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'anthropic-style',
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
}
|
|
||||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||||
|
|
||||||
const anthropicSettings: VoidStaticProviderInfo = {
|
const anthropicSettings: VoidStaticProviderInfo = {
|
||||||
|
|
@ -650,13 +591,10 @@ const anthropicSettings: VoidStaticProviderInfo = {
|
||||||
else if (lower.includes('haiku-4-5') || lower.includes('haiku-4.5')) fallbackName = 'claude-haiku-4-5'
|
else if (lower.includes('haiku-4-5') || lower.includes('haiku-4.5')) fallbackName = 'claude-haiku-4-5'
|
||||||
else if (lower.includes('opus-4-6') || lower.includes('opus-4.6')) fallbackName = 'claude-opus-4-6'
|
else if (lower.includes('opus-4-6') || lower.includes('opus-4.6')) fallbackName = 'claude-opus-4-6'
|
||||||
else if (lower.includes('sonnet-4-5') || lower.includes('sonnet-4.5')) fallbackName = 'claude-sonnet-4-5-20250929'
|
else if (lower.includes('sonnet-4-5') || lower.includes('sonnet-4.5')) fallbackName = 'claude-sonnet-4-5-20250929'
|
||||||
else if (lower.includes('claude-4-opus') || lower.includes('claude-opus-4')) fallbackName = 'claude-opus-4-20250514'
|
|
||||||
else if (lower.includes('claude-4-sonnet') || lower.includes('claude-sonnet-4')) fallbackName = 'claude-sonnet-4-20250514'
|
|
||||||
else if (lower.includes('claude-3-7-sonnet')) fallbackName = 'claude-3-7-sonnet-20250219'
|
else if (lower.includes('claude-3-7-sonnet')) fallbackName = 'claude-3-7-sonnet-20250219'
|
||||||
else if (lower.includes('claude-3-5-sonnet')) fallbackName = 'claude-3-5-sonnet-20241022'
|
else if (lower.includes('claude-3-5-sonnet') || lower.includes('claude-3-5-haiku') || lower.includes('claude-3-opus') || lower.includes('claude-3-sonnet')) fallbackName = 'claude-sonnet-4-6'
|
||||||
else if (lower.includes('claude-3-5-haiku')) fallbackName = 'claude-3-5-haiku-20241022'
|
else if (lower.includes('claude-4-opus') || lower.includes('claude-opus-4')) fallbackName = 'claude-opus-4-6'
|
||||||
else if (lower.includes('claude-3-opus')) fallbackName = 'claude-3-opus-20240229'
|
else if (lower.includes('claude-4-sonnet') || lower.includes('claude-sonnet-4')) fallbackName = 'claude-sonnet-4-6'
|
||||||
else if (lower.includes('claude-3-sonnet')) fallbackName = 'claude-3-sonnet-20240229'
|
|
||||||
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...anthropicModelOptions[fallbackName] }
|
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...anthropicModelOptions[fallbackName] }
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
|
@ -762,53 +700,6 @@ const openAIModelOptions = { // https://platform.openai.com/docs/pricing
|
||||||
supportsSystemMessage: 'developer-role',
|
supportsSystemMessage: 'developer-role',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
'o1': {
|
|
||||||
contextWindow: 128_000,
|
|
||||||
reservedOutputTokenSpace: 100_000,
|
|
||||||
cost: { input: 15.00, cache_read: 7.50, output: 60.00, },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'developer-role',
|
|
||||||
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'medium', 'high'], default: 'low' } },
|
|
||||||
},
|
|
||||||
'o3-mini': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: 100_000,
|
|
||||||
cost: { input: 1.10, cache_read: 0.55, output: 4.40, },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'developer-role',
|
|
||||||
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'medium', 'high'], default: 'low' } },
|
|
||||||
},
|
|
||||||
'gpt-4o': {
|
|
||||||
contextWindow: 128_000,
|
|
||||||
reservedOutputTokenSpace: 16_384,
|
|
||||||
cost: { input: 2.50, cache_read: 1.25, output: 10.00, },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'openai-style',
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'o1-mini': {
|
|
||||||
contextWindow: 128_000,
|
|
||||||
reservedOutputTokenSpace: 65_536,
|
|
||||||
cost: { input: 1.10, cache_read: 0.55, output: 4.40, },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: false, // does not support any system
|
|
||||||
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'medium', 'high'], default: 'low' } },
|
|
||||||
},
|
|
||||||
'gpt-4o-mini': {
|
|
||||||
contextWindow: 128_000,
|
|
||||||
reservedOutputTokenSpace: 16_384,
|
|
||||||
cost: { input: 0.15, cache_read: 0.075, output: 0.60, },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'openai-style',
|
|
||||||
supportsSystemMessage: 'system-role', // ??
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -837,17 +728,17 @@ const openAISettings: VoidStaticProviderInfo = {
|
||||||
else fallbackName = 'gpt-5.4'
|
else fallbackName = 'gpt-5.4'
|
||||||
}
|
}
|
||||||
else if (lower.includes('o4') && lower.includes('mini')) fallbackName = 'o4-mini'
|
else if (lower.includes('o4') && lower.includes('mini')) fallbackName = 'o4-mini'
|
||||||
else if (lower.includes('o3') && lower.includes('mini')) fallbackName = 'o3-mini'
|
else if (lower.includes('o3') && lower.includes('mini')) fallbackName = 'o4-mini'
|
||||||
else if (lower.includes('o3')) fallbackName = 'o3'
|
else if (lower.includes('o3')) fallbackName = 'o3'
|
||||||
else if (lower.includes('o1') && lower.includes('mini')) fallbackName = 'o1-mini'
|
else if (lower.includes('o1') && lower.includes('mini')) fallbackName = 'o4-mini'
|
||||||
else if (lower.includes('o1')) fallbackName = 'o1'
|
else if (lower.includes('o1')) fallbackName = 'o3'
|
||||||
else if (lower.includes('gpt-4.1') || lower.includes('gpt4.1')) {
|
else if (lower.includes('gpt-4.1') || lower.includes('gpt4.1')) {
|
||||||
if (lower.includes('nano')) fallbackName = 'gpt-4.1-nano'
|
if (lower.includes('nano')) fallbackName = 'gpt-4.1-nano'
|
||||||
else if (lower.includes('mini')) fallbackName = 'gpt-4.1-mini'
|
else if (lower.includes('mini')) fallbackName = 'gpt-4.1-mini'
|
||||||
else fallbackName = 'gpt-4.1'
|
else fallbackName = 'gpt-4.1'
|
||||||
}
|
}
|
||||||
else if (lower.includes('4o') && lower.includes('mini')) fallbackName = 'gpt-4o-mini'
|
else if (lower.includes('4o') && lower.includes('mini')) fallbackName = 'gpt-4.1-mini'
|
||||||
else if (lower.includes('4o') || lower.includes('gpt-4o')) fallbackName = 'gpt-4o'
|
else if (lower.includes('4o') || lower.includes('gpt-4o')) fallbackName = 'gpt-4.1'
|
||||||
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...openAIModelOptions[fallbackName] }
|
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...openAIModelOptions[fallbackName] }
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
|
@ -897,57 +788,6 @@ const xAIModelOptions = {
|
||||||
specialToolFormat: 'openai-style',
|
specialToolFormat: 'openai-style',
|
||||||
reasoningCapabilities: false,
|
reasoningCapabilities: false,
|
||||||
},
|
},
|
||||||
'grok-2': {
|
|
||||||
contextWindow: 131_072,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 2.00, output: 10.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
specialToolFormat: 'openai-style',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'grok-3': {
|
|
||||||
contextWindow: 131_072,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 3.00, output: 15.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
specialToolFormat: 'openai-style',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'grok-3-fast': {
|
|
||||||
contextWindow: 131_072,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 5.00, output: 25.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
specialToolFormat: 'openai-style',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
// only mini supports thinking
|
|
||||||
'grok-3-mini': {
|
|
||||||
contextWindow: 131_072,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0.30, output: 0.50 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
specialToolFormat: 'openai-style',
|
|
||||||
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'high'], default: 'low' } },
|
|
||||||
},
|
|
||||||
'grok-3-mini-fast': {
|
|
||||||
contextWindow: 131_072,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0.60, output: 4.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
specialToolFormat: 'openai-style',
|
|
||||||
reasoningCapabilities: { supportsReasoning: true, canTurnOffReasoning: false, canIOReasoning: false, reasoningSlider: { type: 'effort_slider', values: ['low', 'high'], default: 'low' } },
|
|
||||||
},
|
|
||||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||||
|
|
||||||
const xAISettings: VoidStaticProviderInfo = {
|
const xAISettings: VoidStaticProviderInfo = {
|
||||||
|
|
@ -960,8 +800,7 @@ const xAISettings: VoidStaticProviderInfo = {
|
||||||
else fallbackName = 'grok-4.20-0309-reasoning'
|
else fallbackName = 'grok-4.20-0309-reasoning'
|
||||||
}
|
}
|
||||||
else if (lower.includes('grok-4') || lower.includes('grok4.3') || lower.includes('grok-4.3')) fallbackName = 'grok-4.3'
|
else if (lower.includes('grok-4') || lower.includes('grok4.3') || lower.includes('grok-4.3')) fallbackName = 'grok-4.3'
|
||||||
else if (lower.includes('grok-2')) fallbackName = 'grok-2'
|
else if (lower.includes('grok-2') || lower.includes('grok-3')) fallbackName = 'grok-4.3'
|
||||||
else if (lower.includes('grok-3')) fallbackName = 'grok-4.3' // grok-3 aliases redirect to grok-4.3
|
|
||||||
else if (lower.includes('grok')) fallbackName = 'grok-4.3'
|
else if (lower.includes('grok')) fallbackName = 'grok-4.3'
|
||||||
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...xAIModelOptions[fallbackName] }
|
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...xAIModelOptions[fallbackName] }
|
||||||
return null
|
return null
|
||||||
|
|
@ -1039,115 +878,6 @@ const geminiModelOptions = { // https://ai.google.dev/gemini-api/docs/pricing
|
||||||
reasoningReservedOutputTokenSpace: 8192,
|
reasoningReservedOutputTokenSpace: 8192,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// https://ai.google.dev/gemini-api/docs/thinking#set-budget
|
|
||||||
'gemini-2.5-pro-preview-05-06': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 0, output: 0 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
specialToolFormat: 'gemini-style',
|
|
||||||
reasoningCapabilities: {
|
|
||||||
supportsReasoning: true,
|
|
||||||
canTurnOffReasoning: true,
|
|
||||||
canIOReasoning: false,
|
|
||||||
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // max is really 24576
|
|
||||||
reasoningReservedOutputTokenSpace: 8192,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'gemini-2.0-flash-lite': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 0, output: 0 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
specialToolFormat: 'gemini-style',
|
|
||||||
reasoningCapabilities: false, // no reasoning
|
|
||||||
},
|
|
||||||
'gemini-2.5-flash-preview-04-17': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 0.15, output: .60 }, // TODO $3.50 output with thinking not included
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
specialToolFormat: 'gemini-style',
|
|
||||||
reasoningCapabilities: {
|
|
||||||
supportsReasoning: true,
|
|
||||||
canTurnOffReasoning: true,
|
|
||||||
canIOReasoning: false,
|
|
||||||
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // max is really 24576
|
|
||||||
reasoningReservedOutputTokenSpace: 8192,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'gemini-2.5-pro-exp-03-25': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 0, output: 0 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
specialToolFormat: 'gemini-style',
|
|
||||||
reasoningCapabilities: {
|
|
||||||
supportsReasoning: true,
|
|
||||||
canTurnOffReasoning: true,
|
|
||||||
canIOReasoning: false,
|
|
||||||
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // max is really 24576
|
|
||||||
reasoningReservedOutputTokenSpace: 8192,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'gemini-2.0-flash': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: 8_192, // 8_192,
|
|
||||||
cost: { input: 0.10, output: 0.40 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
specialToolFormat: 'gemini-style',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'gemini-2.0-flash-lite-preview-02-05': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: 8_192, // 8_192,
|
|
||||||
cost: { input: 0.075, output: 0.30 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
specialToolFormat: 'gemini-style',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'gemini-1.5-flash': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: 8_192, // 8_192,
|
|
||||||
cost: { input: 0.075, output: 0.30 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
specialToolFormat: 'gemini-style',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'gemini-1.5-pro': {
|
|
||||||
contextWindow: 2_097_152,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 1.25, output: 5.00 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
specialToolFormat: 'gemini-style',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'gemini-1.5-flash-8b': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 0.0375, output: 0.15 }, // TODO!!! price doubles after 128K tokens, we are NOT encoding that info right now
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'separated',
|
|
||||||
specialToolFormat: 'gemini-style',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||||
|
|
||||||
const geminiSettings: VoidStaticProviderInfo = {
|
const geminiSettings: VoidStaticProviderInfo = {
|
||||||
|
|
@ -1161,9 +891,7 @@ const geminiSettings: VoidStaticProviderInfo = {
|
||||||
else if (lower.includes('flash')) fallbackName = 'gemini-2.5-flash'
|
else if (lower.includes('flash')) fallbackName = 'gemini-2.5-flash'
|
||||||
else fallbackName = 'gemini-2.5-pro'
|
else fallbackName = 'gemini-2.5-pro'
|
||||||
}
|
}
|
||||||
else if (lower.includes('2.0') && lower.includes('flash')) fallbackName = 'gemini-2.0-flash'
|
else if (lower.includes('2.0') || lower.includes('1.5') || lower.includes('preview') || lower.includes('-exp-')) fallbackName = 'gemini-2.5-flash'
|
||||||
else if (lower.includes('1.5') && lower.includes('pro')) fallbackName = 'gemini-1.5-pro'
|
|
||||||
else if (lower.includes('1.5') && lower.includes('flash')) fallbackName = 'gemini-1.5-flash'
|
|
||||||
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...geminiModelOptions[fallbackName] }
|
if (fallbackName) return { modelName: fallbackName, recognizedModelName: fallbackName, ...geminiModelOptions[fallbackName] }
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
|
@ -1289,26 +1017,6 @@ const mistralModelOptions = { // https://docs.mistral.ai/getting-started/models/
|
||||||
supportsSystemMessage: 'system-role',
|
supportsSystemMessage: 'system-role',
|
||||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: true, canTurnOffReasoning: false, openSourceThinkTags: ['<think>', '</think>'] },
|
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: true, canTurnOffReasoning: false, openSourceThinkTags: ['<think>', '</think>'] },
|
||||||
},
|
},
|
||||||
'magistral-small-latest': { // Magistral Small 1.2 (deprecated → Mistral Small 4) — https://docs.mistral.ai/models/model-cards/magistral-small-1-2-25-09
|
|
||||||
contextWindow: 128_000,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 0.30, output: 0.90 },
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'openai-style',
|
|
||||||
downloadable: { sizeGb: 'not-known' },
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: true, canTurnOffReasoning: false, openSourceThinkTags: ['<think>', '</think>'] },
|
|
||||||
},
|
|
||||||
'devstral-small-latest': { // Devstral Small 2 (labs, deprecated → Devstral 2) — https://docs.mistral.ai/models/model-cards/devstral-small-2-25-12
|
|
||||||
contextWindow: 256_000,
|
|
||||||
reservedOutputTokenSpace: 8_192,
|
|
||||||
cost: { input: 0.20, output: 0.80 },
|
|
||||||
supportsFIM: false,
|
|
||||||
specialToolFormat: 'openai-style',
|
|
||||||
downloadable: { sizeGb: 14 },
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'ministral-14b-latest': { // Ministral 3 14B — https://docs.mistral.ai/models/model-cards/ministral-3-14b-25-12
|
'ministral-14b-latest': { // Ministral 3 14B — https://docs.mistral.ai/models/model-cards/ministral-3-14b-25-12
|
||||||
contextWindow: 256_000,
|
contextWindow: 256_000,
|
||||||
reservedOutputTokenSpace: 4_096,
|
reservedOutputTokenSpace: 4_096,
|
||||||
|
|
@ -1347,8 +1055,8 @@ const mistralSettings: VoidStaticProviderInfo = {
|
||||||
const lower = modelName.toLowerCase()
|
const lower = modelName.toLowerCase()
|
||||||
let fallbackName: keyof typeof mistralModelOptions | null = null
|
let fallbackName: keyof typeof mistralModelOptions | null = null
|
||||||
if (lower.includes('codestral')) fallbackName = 'codestral-latest'
|
if (lower.includes('codestral')) fallbackName = 'codestral-latest'
|
||||||
else if (lower.includes('magistral')) fallbackName = lower.includes('small') ? 'magistral-small-latest' : 'magistral-medium-latest'
|
else if (lower.includes('magistral')) fallbackName = lower.includes('small') ? 'mistral-small-latest' : 'magistral-medium-latest'
|
||||||
else if (lower.includes('devstral')) fallbackName = lower.includes('small') ? 'devstral-small-latest' : 'devstral-latest'
|
else if (lower.includes('devstral')) fallbackName = 'devstral-latest'
|
||||||
else if (lower.includes('ministral')) {
|
else if (lower.includes('ministral')) {
|
||||||
if (lower.includes('14')) fallbackName = 'ministral-14b-latest'
|
if (lower.includes('14')) fallbackName = 'ministral-14b-latest'
|
||||||
else if (lower.includes('8')) fallbackName = 'ministral-8b-latest'
|
else if (lower.includes('8')) fallbackName = 'ministral-8b-latest'
|
||||||
|
|
@ -1719,51 +1427,6 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
||||||
supportsSystemMessage: 'system-role',
|
supportsSystemMessage: 'system-role',
|
||||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: true, canTurnOffReasoning: false },
|
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: true, canTurnOffReasoning: false },
|
||||||
},
|
},
|
||||||
'microsoft/phi-4-reasoning-plus:free': { // a 14B model...
|
|
||||||
contextWindow: 32_768,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0, output: 0 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: true, canTurnOffReasoning: false },
|
|
||||||
},
|
|
||||||
'mistralai/mistral-small-3.1-24b-instruct:free': {
|
|
||||||
contextWindow: 128_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0, output: 0 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'google/gemini-2.0-flash-lite-preview-02-05:free': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0, output: 0 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'google/gemini-2.0-pro-exp-02-05:free': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0, output: 0 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'google/gemini-2.0-flash-exp:free': {
|
|
||||||
contextWindow: 1_048_576,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0, output: 0 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'deepseek/deepseek-r1': {
|
'deepseek/deepseek-r1': {
|
||||||
...openSourceModelOptions_assumingOAICompat.deepseekR1,
|
...openSourceModelOptions_assumingOAICompat.deepseekR1,
|
||||||
contextWindow: 128_000,
|
contextWindow: 128_000,
|
||||||
|
|
@ -1771,87 +1434,6 @@ const openRouterModelOptions_assumingOpenAICompat = {
|
||||||
cost: { input: 0.8, output: 2.4 },
|
cost: { input: 0.8, output: 2.4 },
|
||||||
downloadable: false,
|
downloadable: false,
|
||||||
},
|
},
|
||||||
'anthropic/claude-opus-4': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 15.00, output: 75.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'anthropic/claude-sonnet-4': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 3.00, output: 15.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'anthropic/claude-3.7-sonnet:thinking': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 3.00, output: 15.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: { // same as anthropic, see above
|
|
||||||
supportsReasoning: true,
|
|
||||||
canTurnOffReasoning: false,
|
|
||||||
canIOReasoning: true,
|
|
||||||
reasoningReservedOutputTokenSpace: 8192,
|
|
||||||
reasoningSlider: { type: 'budget_slider', min: 1024, max: 8192, default: 1024 }, // they recommend batching if max > 32_000.
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'anthropic/claude-3.7-sonnet': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 3.00, output: 15.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false, // stupidly, openrouter separates thinking from non-thinking
|
|
||||||
},
|
|
||||||
'anthropic/claude-3.5-sonnet': {
|
|
||||||
contextWindow: 200_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 3.00, output: 15.00 },
|
|
||||||
downloadable: false,
|
|
||||||
supportsFIM: false,
|
|
||||||
supportsSystemMessage: 'system-role',
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'mistralai/codestral-2501': {
|
|
||||||
...openSourceModelOptions_assumingOAICompat.codestral,
|
|
||||||
contextWindow: 256_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0.3, output: 0.9 },
|
|
||||||
downloadable: false,
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'mistralai/devstral-small:free': {
|
|
||||||
...openSourceModelOptions_assumingOAICompat.devstral,
|
|
||||||
contextWindow: 130_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0, output: 0 },
|
|
||||||
downloadable: false,
|
|
||||||
reasoningCapabilities: false,
|
|
||||||
},
|
|
||||||
'qwen/qwen-2.5-coder-32b-instruct': {
|
|
||||||
...openSourceModelOptions_assumingOAICompat['qwen2.5coder'],
|
|
||||||
contextWindow: 33_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0.07, output: 0.16 },
|
|
||||||
downloadable: false,
|
|
||||||
},
|
|
||||||
'qwen/qwq-32b': {
|
|
||||||
...openSourceModelOptions_assumingOAICompat['qwq'],
|
|
||||||
contextWindow: 33_000,
|
|
||||||
reservedOutputTokenSpace: null,
|
|
||||||
cost: { input: 0.07, output: 0.16 },
|
|
||||||
downloadable: false,
|
|
||||||
}
|
|
||||||
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
} as const satisfies { [s: string]: VoidStaticModelInfo }
|
||||||
|
|
||||||
const openRouterSettings: VoidStaticProviderInfo = {
|
const openRouterSettings: VoidStaticProviderInfo = {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { IMetricsService } from './metricsService.js';
|
||||||
import { defaultProviderSettings, getModelCapabilities, ModelOverrides } from './modelCapabilities.js';
|
import { defaultProviderSettings, getModelCapabilities, ModelOverrides } from './modelCapabilities.js';
|
||||||
import { VOID_SETTINGS_STORAGE_KEY } from './storageKeys.js';
|
import { VOID_SETTINGS_STORAGE_KEY } from './storageKeys.js';
|
||||||
import { isMacintosh } from '../../../../base/common/platform.js';
|
import { isMacintosh } from '../../../../base/common/platform.js';
|
||||||
import { consolidateSingleAutodetectedProviderModels, normalizeAutodetectedModelNamesForProvider } from './localSingleModelProviders.js';
|
import { consolidateSingleAutodetectedProviderModels, dedupeProviderModels, normalizeAutodetectedModelNamesForProvider } from './localSingleModelProviders.js';
|
||||||
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidStatefulModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode, OverridesOfModel, defaultOverridesOfModel, MCPUserStateOfName as MCPUserStateOfName, MCPUserState } from './voidSettingsTypes.js';
|
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidStatefulModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode, OverridesOfModel, defaultOverridesOfModel, MCPUserStateOfName as MCPUserStateOfName, MCPUserState } from './voidSettingsTypes.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -86,8 +86,8 @@ export interface IVoidSettingsService {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const _modelsWithSwappedInNewModels = (options: { existingModels: VoidStatefulModelInfo[], models: string[], type: 'autodetected' | 'default' }) => {
|
const _modelsWithSwappedInNewModels = (options: { providerName: ProviderName, existingModels: VoidStatefulModelInfo[], models: string[], type: 'autodetected' | 'default' }) => {
|
||||||
const { existingModels, models, type } = options
|
const { providerName, existingModels, models, type } = options
|
||||||
|
|
||||||
const existingModelsMap: Record<string, VoidStatefulModelInfo> = {}
|
const existingModelsMap: Record<string, VoidStatefulModelInfo> = {}
|
||||||
for (const existingModel of existingModels) {
|
for (const existingModel of existingModels) {
|
||||||
|
|
@ -96,13 +96,13 @@ const _modelsWithSwappedInNewModels = (options: { existingModels: VoidStatefulMo
|
||||||
|
|
||||||
const newDefaultModels = models.map((modelName, i) => ({ modelName, type, isHidden: !!existingModelsMap[modelName]?.isHidden, }))
|
const newDefaultModels = models.map((modelName, i) => ({ modelName, type, isHidden: !!existingModelsMap[modelName]?.isHidden, }))
|
||||||
|
|
||||||
return [
|
return dedupeProviderModels(providerName, [
|
||||||
...newDefaultModels, // swap out all the models of this type for the new models of this type
|
...newDefaultModels, // swap out all the models of this type for the new models of this type
|
||||||
...existingModels.filter(m => {
|
...existingModels.filter(m => {
|
||||||
const keep = m.type !== type
|
const keep = m.type !== type
|
||||||
return keep
|
return keep
|
||||||
})
|
})
|
||||||
]
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
export const modelFilterOfFeatureName: {
|
export const modelFilterOfFeatureName: {
|
||||||
|
|
@ -129,7 +129,7 @@ const _stateWithMergedDefaultModels = (state: VoidSettingsState): VoidSettingsSt
|
||||||
const defaultModels = defaultSettingsOfProvider[providerName]?.models ?? []
|
const defaultModels = defaultSettingsOfProvider[providerName]?.models ?? []
|
||||||
const currentModels = newSettingsOfProvider[providerName]?.models ?? []
|
const currentModels = newSettingsOfProvider[providerName]?.models ?? []
|
||||||
const defaultModelNames = defaultModels.map(m => m.modelName)
|
const defaultModelNames = defaultModels.map(m => m.modelName)
|
||||||
const newModels = _modelsWithSwappedInNewModels({ existingModels: currentModels, models: defaultModelNames, type: 'default' })
|
const newModels = _modelsWithSwappedInNewModels({ providerName, existingModels: currentModels, models: defaultModelNames, type: 'default' })
|
||||||
newSettingsOfProvider = {
|
newSettingsOfProvider = {
|
||||||
...newSettingsOfProvider,
|
...newSettingsOfProvider,
|
||||||
[providerName]: {
|
[providerName]: {
|
||||||
|
|
@ -165,14 +165,40 @@ const _validatedModelState = (state: Omit<VoidSettingsState, '_modelOptions'>):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dedupe models (aliases, default+custom overlap, persisted legacy ids)
|
||||||
|
for (const providerName of providerNames) {
|
||||||
|
const models = newSettingsOfProvider[providerName].models
|
||||||
|
const deduped = dedupeProviderModels(providerName, models)
|
||||||
|
if (deduped.length !== models.length || deduped.some((m, i) => m.modelName !== models[i]?.modelName || m.type !== models[i]?.type || m.isHidden !== models[i]?.isHidden)) {
|
||||||
|
newSettingsOfProvider = {
|
||||||
|
...newSettingsOfProvider,
|
||||||
|
[providerName]: {
|
||||||
|
...newSettingsOfProvider[providerName],
|
||||||
|
models: deduped,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update model options
|
// update model options
|
||||||
|
const visibleModelNameCounts = new Map<string, number>()
|
||||||
|
for (const providerName of providerNames) {
|
||||||
|
if (!newSettingsOfProvider[providerName]._didFillInProviderSettings) continue
|
||||||
|
for (const { modelName, isHidden } of newSettingsOfProvider[providerName].models) {
|
||||||
|
if (isHidden) continue
|
||||||
|
visibleModelNameCounts.set(modelName, (visibleModelNameCounts.get(modelName) ?? 0) + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let newModelOptions: ModelOption[] = []
|
let newModelOptions: ModelOption[] = []
|
||||||
for (const providerName of providerNames) {
|
for (const providerName of providerNames) {
|
||||||
const providerTitle = providerName // displayInfoOfProviderName(providerName).title.toLowerCase() // looks better lowercase, best practice to not use raw providerName
|
const providerTitle = providerName // displayInfoOfProviderName(providerName).title.toLowerCase() // looks better lowercase, best practice to not use raw providerName
|
||||||
if (!newSettingsOfProvider[providerName]._didFillInProviderSettings) continue // if disabled, don't display model options
|
if (!newSettingsOfProvider[providerName]._didFillInProviderSettings) continue // if disabled, don't display model options
|
||||||
for (const { modelName, isHidden } of newSettingsOfProvider[providerName].models) {
|
for (const { modelName, isHidden } of newSettingsOfProvider[providerName].models) {
|
||||||
if (isHidden) continue
|
if (isHidden) continue
|
||||||
newModelOptions.push({ name: `${modelName} (${providerTitle})`, selection: { providerName, modelName } })
|
const showProviderInName = (visibleModelNameCounts.get(modelName) ?? 0) > 1
|
||||||
|
const name = showProviderInName ? `${modelName} (${providerTitle})` : modelName
|
||||||
|
newModelOptions.push({ name, selection: { providerName, modelName } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,6 +371,10 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||||
readS.settingsOfProvider[providerName].models,
|
readS.settingsOfProvider[providerName].models,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
readS.settingsOfProvider[providerName].models = dedupeProviderModels(
|
||||||
|
providerName,
|
||||||
|
readS.settingsOfProvider[providerName].models,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -521,7 +551,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||||
const oldModelNames = models.map(m => m.modelName)
|
const oldModelNames = models.map(m => m.modelName)
|
||||||
|
|
||||||
const normalizedNames = normalizeAutodetectedModelNamesForProvider(providerName, autodetectedModelNames)
|
const normalizedNames = normalizeAutodetectedModelNamesForProvider(providerName, autodetectedModelNames)
|
||||||
let newModels = _modelsWithSwappedInNewModels({ existingModels: models, models: normalizedNames, type: 'autodetected' })
|
let newModels = _modelsWithSwappedInNewModels({ providerName, existingModels: models, models: normalizedNames, type: 'autodetected' })
|
||||||
if (providerName === 'mlx' || providerName === 'appleFoundationModels') {
|
if (providerName === 'mlx' || providerName === 'appleFoundationModels') {
|
||||||
newModels = consolidateSingleAutodetectedProviderModels(providerName, newModels)
|
newModels = consolidateSingleAutodetectedProviderModels(providerName, newModels)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue