fix: model dropdown shows only hermes-configured providers (#155)
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
This commit is contained in:
123
api/config.py
123
api/config.py
@@ -363,6 +363,20 @@ _PROVIDER_MODELS = {
|
|||||||
{'id': 'MiniMax-M2.5-highspeed', 'label': 'MiniMax M2.5 Highspeed'},
|
{'id': 'MiniMax-M2.5-highspeed', 'label': 'MiniMax M2.5 Highspeed'},
|
||||||
{'id': 'MiniMax-M2.1', 'label': 'MiniMax M2.1'},
|
{'id': 'MiniMax-M2.1', 'label': 'MiniMax M2.1'},
|
||||||
],
|
],
|
||||||
|
# GitHub Copilot — model IDs served via the Copilot API
|
||||||
|
'copilot': [
|
||||||
|
{'id': 'gpt-5.4', 'label': 'GPT-5.4'},
|
||||||
|
{'id': 'gpt-5.4-mini', 'label': 'GPT-5.4 Mini'},
|
||||||
|
{'id': 'gpt-4o', 'label': 'GPT-4o'},
|
||||||
|
{'id': 'claude-opus-4.6', 'label': 'Claude Opus 4.6'},
|
||||||
|
{'id': 'claude-sonnet-4.6', 'label': 'Claude Sonnet 4.6'},
|
||||||
|
{'id': 'gemini-2.5-pro', 'label': 'Gemini 2.5 Pro'},
|
||||||
|
],
|
||||||
|
# 'gemini' is the hermes_cli provider ID for Google AI Studio
|
||||||
|
'gemini': [
|
||||||
|
{'id': 'gemini-2.5-pro', 'label': 'Gemini 2.5 Pro'},
|
||||||
|
{'id': 'gemini-2.0-flash', 'label': 'Gemini 2.0 Flash'},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -470,50 +484,64 @@ def get_available_models() -> dict:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 4. Check for API keys that imply available providers
|
# 4. Detect available providers.
|
||||||
try:
|
# Primary: ask hermes-agent's auth layer — the authoritative source. It checks
|
||||||
from api.profiles import get_active_hermes_home as _gah2
|
# auth.json, credential pools, and env vars the same way the agent does at runtime,
|
||||||
hermes_env_path = _gah2() / '.env'
|
# so the dropdown reflects exactly what the user has configured.
|
||||||
except ImportError:
|
# Fallback: scan raw API key env vars (matches old behaviour if hermes not available).
|
||||||
hermes_env_path = HOME / '.hermes' / '.env'
|
|
||||||
env_keys = {}
|
|
||||||
if hermes_env_path.exists():
|
|
||||||
try:
|
|
||||||
for line in hermes_env_path.read_text().splitlines():
|
|
||||||
line = line.strip()
|
|
||||||
if line and not line.startswith('#') and '=' in line:
|
|
||||||
k, v = line.split('=', 1)
|
|
||||||
env_keys[k.strip()] = v.strip().strip('"').strip("'")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Merge with actual env
|
|
||||||
all_env = {**env_keys}
|
|
||||||
for k in ('ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'OPENROUTER_API_KEY',
|
|
||||||
'GOOGLE_API_KEY', 'GLM_API_KEY', 'KIMI_API_KEY', 'DEEPSEEK_API_KEY'):
|
|
||||||
val = os.getenv(k)
|
|
||||||
if val:
|
|
||||||
all_env[k] = val
|
|
||||||
|
|
||||||
detected_providers = set()
|
detected_providers = set()
|
||||||
if active_provider:
|
if active_provider:
|
||||||
detected_providers.add(active_provider)
|
detected_providers.add(active_provider)
|
||||||
if all_env.get('ANTHROPIC_API_KEY'):
|
|
||||||
detected_providers.add('anthropic')
|
_hermes_auth_used = False
|
||||||
if all_env.get('OPENAI_API_KEY'):
|
try:
|
||||||
detected_providers.add('openai')
|
from hermes_cli.models import list_available_providers as _lap
|
||||||
if all_env.get('OPENROUTER_API_KEY'):
|
for _p in _lap():
|
||||||
detected_providers.add('openrouter')
|
if _p.get('authenticated'):
|
||||||
if all_env.get('GOOGLE_API_KEY'):
|
detected_providers.add(_p['id'])
|
||||||
detected_providers.add('google')
|
_hermes_auth_used = True
|
||||||
if all_env.get('GLM_API_KEY'):
|
except Exception:
|
||||||
detected_providers.add('zai')
|
pass
|
||||||
if all_env.get('KIMI_API_KEY'):
|
|
||||||
detected_providers.add('kimi-coding')
|
if not _hermes_auth_used:
|
||||||
if all_env.get('MINIMAX_API_KEY') or all_env.get('MINIMAX_CN_API_KEY'):
|
# Fallback: scan .env and os.environ for known API key variables
|
||||||
detected_providers.add('minimax')
|
try:
|
||||||
if all_env.get('DEEPSEEK_API_KEY'):
|
from api.profiles import get_active_hermes_home as _gah2
|
||||||
detected_providers.add('deepseek')
|
hermes_env_path = _gah2() / '.env'
|
||||||
|
except ImportError:
|
||||||
|
hermes_env_path = HOME / '.hermes' / '.env'
|
||||||
|
env_keys = {}
|
||||||
|
if hermes_env_path.exists():
|
||||||
|
try:
|
||||||
|
for line in hermes_env_path.read_text().splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#') and '=' in line:
|
||||||
|
k, v = line.split('=', 1)
|
||||||
|
env_keys[k.strip()] = v.strip().strip('"').strip("'")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
all_env = {**env_keys}
|
||||||
|
for k in ('ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'OPENROUTER_API_KEY',
|
||||||
|
'GOOGLE_API_KEY', 'GLM_API_KEY', 'KIMI_API_KEY', 'DEEPSEEK_API_KEY'):
|
||||||
|
val = os.getenv(k)
|
||||||
|
if val:
|
||||||
|
all_env[k] = val
|
||||||
|
if all_env.get('ANTHROPIC_API_KEY'):
|
||||||
|
detected_providers.add('anthropic')
|
||||||
|
if all_env.get('OPENAI_API_KEY'):
|
||||||
|
detected_providers.add('openai')
|
||||||
|
if all_env.get('OPENROUTER_API_KEY'):
|
||||||
|
detected_providers.add('openrouter')
|
||||||
|
if all_env.get('GOOGLE_API_KEY'):
|
||||||
|
detected_providers.add('google')
|
||||||
|
if all_env.get('GLM_API_KEY'):
|
||||||
|
detected_providers.add('zai')
|
||||||
|
if all_env.get('KIMI_API_KEY'):
|
||||||
|
detected_providers.add('kimi-coding')
|
||||||
|
if all_env.get('MINIMAX_API_KEY') or all_env.get('MINIMAX_CN_API_KEY'):
|
||||||
|
detected_providers.add('minimax')
|
||||||
|
if all_env.get('DEEPSEEK_API_KEY'):
|
||||||
|
detected_providers.add('deepseek')
|
||||||
|
|
||||||
# 3. Fetch models from custom endpoint if base_url is configured
|
# 3. Fetch models from custom endpoint if base_url is configured
|
||||||
auto_detected_models = []
|
auto_detected_models = []
|
||||||
@@ -628,14 +656,11 @@ def get_available_models() -> dict:
|
|||||||
'models': [{'id': default_model, 'label': default_model.split('/')[-1]}],
|
'models': [{'id': default_model, 'label': default_model.split('/')[-1]}],
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
# No providers detected -- use fallback grouped list
|
# No providers detected. Show only the configured default model so the user
|
||||||
by_provider = {}
|
# can at least send messages with their current setting. Avoid showing a
|
||||||
for m in _FALLBACK_MODELS:
|
# generic multi-provider list — those models wouldn't be routable anyway.
|
||||||
by_provider.setdefault(m['provider'], []).append(
|
label = default_model.split('/')[-1] if '/' in default_model else default_model
|
||||||
{'id': m['id'], 'label': m['label']}
|
groups.append({'provider': 'Default', 'models': [{'id': default_model, 'label': label}]})
|
||||||
)
|
|
||||||
for provider_name, models in by_provider.items():
|
|
||||||
groups.append({'provider': provider_name, 'models': models})
|
|
||||||
|
|
||||||
# Ensure the user's configured default_model always appears in the dropdown.
|
# Ensure the user's configured default_model always appears in the dropdown.
|
||||||
# It may be missing if the model isn't in any hardcoded list (e.g. openrouter/free,
|
# It may be missing if the model isn't in any hardcoded list (e.g. openrouter/free,
|
||||||
|
|||||||
Reference in New Issue
Block a user