diff --git a/openhands/cli/settings.py b/openhands/cli/settings.py
index 5c8395bb4c..7ce8f7de41 100644
--- a/openhands/cli/settings.py
+++ b/openhands/cli/settings.py
@@ -13,6 +13,7 @@ from openhands.cli.tui import (
)
from openhands.cli.utils import (
VERIFIED_ANTHROPIC_MODELS,
+ VERIFIED_MISTRAL_MODELS,
VERIFIED_OPENAI_MODELS,
VERIFIED_PROVIDERS,
organize_models_and_providers,
@@ -158,7 +159,7 @@ async def modify_llm_settings_basic(
provider_completer = FuzzyWordCompleter(provider_list)
session = PromptSession(key_bindings=kb_cancel())
- # Set default provider - prefer 'anthropic' if available, otherwise use the first provider
+ # Set default provider - prefer 'anthropic' if available, otherwise use first
provider = 'anthropic' if 'anthropic' in provider_list else provider_list[0]
model = None
api_key = None
@@ -168,15 +169,26 @@ async def modify_llm_settings_basic(
print_formatted_text(
HTML(f'\nDefault provider: {provider}')
)
- change_provider = (
- cli_confirm(
- 'Do you want to use a different provider?',
- [f'Use {provider}', 'Select another provider'],
- )
- == 1
+
+ # Show verified providers plus "Select another provider" option
+ provider_choices = verified_providers + ['Select another provider']
+ provider_choice = cli_confirm(
+ '(Step 1/3) Select LLM Provider:',
+ provider_choices,
)
- if change_provider:
+ # Ensure provider_choice is an integer (for test compatibility)
+ try:
+ choice_index = int(provider_choice)
+ except (TypeError, ValueError):
+ # If conversion fails (e.g., in tests with mocks), default to 0
+ choice_index = 0
+
+ if choice_index < len(verified_providers):
+ # User selected one of the verified providers
+ provider = verified_providers[choice_index]
+ else:
+ # User selected "Select another provider" - use manual selection
# Define a validator function that prints an error message
def provider_validator(x):
is_valid = x in organized_models
@@ -196,7 +208,8 @@ async def modify_llm_settings_basic(
# Make sure the provider exists in organized_models
if provider not in organized_models:
- # If the provider doesn't exist, prefer 'anthropic' if available, otherwise use the first provider
+ # If the provider doesn't exist, prefer 'anthropic' if available,
+ # otherwise use the first provider
provider = (
'anthropic'
if 'anthropic' in organized_models
@@ -214,6 +227,11 @@ async def modify_llm_settings_basic(
m for m in provider_models if m not in VERIFIED_ANTHROPIC_MODELS
]
provider_models = VERIFIED_ANTHROPIC_MODELS + provider_models
+ if provider == 'mistral':
+ provider_models = [
+ m for m in provider_models if m not in VERIFIED_MISTRAL_MODELS
+ ]
+ provider_models = VERIFIED_MISTRAL_MODELS + provider_models
# Set default model to the best verified model for the provider
if provider == 'anthropic' and VERIFIED_ANTHROPIC_MODELS:
@@ -222,6 +240,9 @@ async def modify_llm_settings_basic(
elif provider == 'openai' and VERIFIED_OPENAI_MODELS:
# Use the first model in the VERIFIED_OPENAI_MODELS list as it's the best/newest
default_model = VERIFIED_OPENAI_MODELS[0]
+ elif provider == 'mistral' and VERIFIED_MISTRAL_MODELS:
+ # Use the first model in the VERIFIED_MISTRAL_MODELS list as it's the best/newest
+ default_model = VERIFIED_MISTRAL_MODELS[0]
else:
# For other providers, use the first model in the list
default_model = (
diff --git a/openhands/cli/utils.py b/openhands/cli/utils.py
index 802b8a94c1..a88f76585a 100644
--- a/openhands/cli/utils.py
+++ b/openhands/cli/utils.py
@@ -102,6 +102,8 @@ def extract_model_and_provider(model: str) -> ModelInfo:
return ModelInfo(provider='openai', model=split[0], separator='/')
if split[0] in VERIFIED_ANTHROPIC_MODELS:
return ModelInfo(provider='anthropic', model=split[0], separator='/')
+ if split[0] in VERIFIED_MISTRAL_MODELS:
+ return ModelInfo(provider='mistral', model=split[0], separator='/')
# return as model only
return ModelInfo(provider='', model=model, separator='')
@@ -143,9 +145,10 @@ def organize_models_and_providers(
return result_dict
-VERIFIED_PROVIDERS = ['openai', 'azure', 'anthropic', 'deepseek']
+VERIFIED_PROVIDERS = ['anthropic', 'openai', 'mistral']
VERIFIED_OPENAI_MODELS = [
+ 'o4-mini',
'gpt-4o',
'gpt-4o-mini',
'gpt-4-turbo',
@@ -171,6 +174,10 @@ VERIFIED_ANTHROPIC_MODELS = [
'claude-2',
]
+VERIFIED_MISTRAL_MODELS = [
+ 'devstral-small-2505',
+]
+
class ProviderInfo(BaseModel):
"""Information about a provider and its models."""
diff --git a/tests/unit/test_cli_utils.py b/tests/unit/test_cli_utils.py
index 02ffdd09ad..26fb011630 100644
--- a/tests/unit/test_cli_utils.py
+++ b/tests/unit/test_cli_utils.py
@@ -361,6 +361,22 @@ class TestModelAndProviderFunctions:
assert result['model'] == 'claude-sonnet-4-20250514'
assert result['separator'] == '/'
+ def test_extract_model_and_provider_mistral_implicit(self):
+ model = 'devstral-small-2505'
+ result = extract_model_and_provider(model)
+
+ assert result['provider'] == 'mistral'
+ assert result['model'] == 'devstral-small-2505'
+ assert result['separator'] == '/'
+
+ def test_extract_model_and_provider_o4_mini(self):
+ model = 'o4-mini'
+ result = extract_model_and_provider(model)
+
+ assert result['provider'] == 'openai'
+ assert result['model'] == 'o4-mini'
+ assert result['separator'] == '/'
+
def test_extract_model_and_provider_versioned(self):
model = 'deepseek.deepseek-coder-1.3b'
result = extract_model_and_provider(model)
@@ -382,6 +398,9 @@ class TestModelAndProviderFunctions:
'openai/gpt-4o',
'anthropic/claude-sonnet-4-20250514',
'o3-mini',
+ 'o4-mini',
+ 'devstral-small-2505',
+ 'mistral/devstral-small-2505',
'anthropic.claude-3-5', # Should be ignored as it uses dot separator for anthropic
'unknown-model',
]
@@ -390,15 +409,20 @@ class TestModelAndProviderFunctions:
assert 'openai' in result
assert 'anthropic' in result
+ assert 'mistral' in result
assert 'other' in result
- assert len(result['openai']['models']) == 2
+ assert len(result['openai']['models']) == 3
assert 'gpt-4o' in result['openai']['models']
assert 'o3-mini' in result['openai']['models']
+ assert 'o4-mini' in result['openai']['models']
assert len(result['anthropic']['models']) == 1
assert 'claude-sonnet-4-20250514' in result['anthropic']['models']
+ assert len(result['mistral']['models']) == 2
+ assert 'devstral-small-2505' in result['mistral']['models']
+
assert len(result['other']['models']) == 1
assert 'unknown-model' in result['other']['models']