fix: allow custom model names in Model Combo Box for static providers

Replace the generic Select component with ModelPicker for providers
with static model lists (Anthropic, Mistral, Gemini, etc.). The
ModelPicker component already supports entering custom model IDs
that are not in the predefined list.

This enables users to use newly released models from providers
without waiting for Roo Code updates.

Fixes #10229
This commit is contained in:
Roo Code 2025-12-20 14:19:39 +00:00
parent 78dc34498b
commit 470501adb8

View file

@ -104,7 +104,7 @@ import {
import { MODELS_BY_PROVIDER, PROVIDERS } from "./constants"
import { inputEventTransform, noTransform } from "./transforms"
import { ModelInfoView } from "./ModelInfoView"
import { ModelPicker } from "./ModelPicker"
import { ApiErrorMessage } from "./ApiErrorMessage"
import { ThinkingBudget } from "./ThinkingBudget"
import { Verbosity } from "./Verbosity"
@ -173,7 +173,6 @@ const ApiOptions = ({
[customHeaders, apiConfiguration?.openAiHeaders, setApiConfigurationField],
)
const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false)
const [isAdvancedSettingsOpen, setIsAdvancedSettingsOpen] = useState(false)
const handleInputChange = useCallback(
@ -272,31 +271,47 @@ const ApiOptions = ({
setErrorMessage(apiValidationResult)
}, [apiConfiguration, routerModels, organizationAllowList, setErrorMessage])
const selectedProviderModels = useMemo(() => {
// Get models for static providers (those with models defined in MODELS_BY_PROVIDER)
const staticProviderModels = useMemo(() => {
const models = MODELS_BY_PROVIDER[selectedProvider]
if (!models) return null
return filterModels(models, selectedProvider, organizationAllowList)
}, [selectedProvider, organizationAllowList])
if (!models) return []
// Get the default model ID for the current static provider
const staticProviderDefaultModelId = useMemo(() => {
const defaults: Partial<Record<ProviderName, string>> = {
anthropic: anthropicDefaultModelId,
"openai-native": openAiNativeDefaultModelId,
gemini: geminiDefaultModelId,
deepseek: deepSeekDefaultModelId,
doubao: doubaoDefaultModelId,
moonshot: moonshotDefaultModelId,
mistral: mistralDefaultModelId,
xai: xaiDefaultModelId,
groq: groqDefaultModelId,
cerebras: cerebrasDefaultModelId,
baseten: basetenDefaultModelId,
bedrock: bedrockDefaultModelId,
vertex: vertexDefaultModelId,
sambanova: sambaNovaDefaultModelId,
zai:
apiConfiguration.zaiApiLine === "china_coding"
? mainlandZAiDefaultModelId
: internationalZAiDefaultModelId,
fireworks: fireworksDefaultModelId,
featherless: featherlessDefaultModelId,
minimax: minimaxDefaultModelId,
"qwen-code": qwenCodeDefaultModelId,
}
return defaults[selectedProvider] || ""
}, [selectedProvider, apiConfiguration.zaiApiLine])
const filteredModels = filterModels(models, selectedProvider, organizationAllowList)
// Include the currently selected model even if deprecated (so users can see what they have selected)
// But filter out other deprecated models from being newly selectable
const availableModels = filteredModels
? Object.entries(filteredModels)
.filter(([modelId, modelInfo]) => {
// Always include the currently selected model
if (modelId === selectedModelId) return true
// Filter out deprecated models that aren't currently selected
return !modelInfo.deprecated
})
.map(([modelId]) => ({
value: modelId,
label: modelId,
}))
: []
return availableModels
}, [selectedProvider, organizationAllowList, selectedModelId])
// Get the provider label for display
const staticProviderLabel = useMemo(() => {
const provider = PROVIDERS.find(({ value }) => value === selectedProvider)
return provider?.label || selectedProvider
}, [selectedProvider])
const onProviderChange = useCallback(
(value: ProviderName) => {
@ -781,16 +796,16 @@ const ApiOptions = ({
)}
{/* Skip generic model picker for claude-code since it has its own in ClaudeCode.tsx */}
{selectedProviderModels.length > 0 && selectedProvider !== "claude-code" && (
{staticProviderModels !== null && selectedProvider !== "claude-code" && (
<>
<div>
<label className="block font-medium mb-1">{t("settings:providers.model")}</label>
<Select
value={selectedModelId === "custom-arn" ? "custom-arn" : selectedModelId}
onValueChange={(value) => {
setApiConfigurationField("apiModelId", value)
<ModelPicker
apiConfiguration={apiConfiguration}
setApiConfigurationField={(field, value, isUserAction) => {
setApiConfigurationField(field, value, isUserAction)
// Clear custom ARN if not using custom ARN option.
// Handle special cases when model changes
if (field === "apiModelId") {
// Clear custom ARN if not using custom ARN option for Bedrock
if (value !== "custom-arn" && selectedProvider === "bedrock") {
setApiConfigurationField("awsCustomArn", "")
}
@ -800,27 +815,17 @@ const ApiOptions = ({
if (selectedProvider === "openai-native") {
setApiConfigurationField("reasoningEffort", undefined)
}
}}>
<SelectTrigger className="w-full">
<SelectValue placeholder={t("settings:common.select")} />
</SelectTrigger>
<SelectContent>
{selectedProviderModels.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
{selectedProvider === "bedrock" && (
<SelectItem value="custom-arn">{t("settings:labels.useCustomArn")}</SelectItem>
)}
</SelectContent>
</Select>
</div>
{/* Show error if a deprecated model is selected */}
{selectedModelInfo?.deprecated && (
<ApiErrorMessage errorMessage={t("settings:validation.modelDeprecated")} />
)}
}
}}
defaultModelId={staticProviderDefaultModelId}
models={staticProviderModels}
modelIdKey="apiModelId"
serviceName={staticProviderLabel}
serviceUrl={docs?.url || ""}
organizationAllowList={organizationAllowList}
simplifySettings={fromWelcomeView}
hidePricing
/>
{selectedProvider === "bedrock" && selectedModelId === "custom-arn" && (
<BedrockCustomArn
@ -828,17 +833,6 @@ const ApiOptions = ({
setApiConfigurationField={setApiConfigurationField}
/>
)}
{/* Only show model info if not deprecated */}
{!selectedModelInfo?.deprecated && (
<ModelInfoView
apiProvider={selectedProvider}
selectedModelId={selectedModelId}
modelInfo={selectedModelInfo}
isDescriptionExpanded={isDescriptionExpanded}
setIsDescriptionExpanded={setIsDescriptionExpanded}
/>
)}
</>
)}