fix: delegate all live model fetching to agent provider_model_ids() (#411)

* fix: delegate all live model fetching to agent's provider_model_ids()

Previously _handle_live_models() maintained its own per-provider logic:
- anthropic, google, gemini returned 'not_supported' (hardcoded exclusions)
- openai-codex had a custom branch (added in v0.50.30)
- openai/copilot had hardcoded base URLs
- other providers fell through to a generic /v1/models fetch

Now the handler delegates entirely to hermes_cli.models.provider_model_ids(),
which is the agent's authoritative resolver:
- anthropic:    live fetch via /v1/models with correct API-key or OAuth headers
- copilot:      live fetch from api.githubcopilot.com/models with Copilot headers
- openai-codex: Codex OAuth endpoint + ~/.codex/ cache fallback
- nous:         live fetch from Nous inference portal
- deepseek, kimi-coding: generic OpenAI-compat /v1/models
- opencode-zen/go: OpenCode live catalog
- openrouter:   curated static list (live returns 300+ which is overwhelming)
- google/gemini, zai, minimax: static list (non-standard or Anthropic-compat endpoints)
- any others:   graceful static fallback

Also removed the client-side skip guard in _fetchLiveModels() (ui.js) that
blocked live fetching for anthropic, google, and gemini.

The hardcoded model lists in _PROVIDER_MODELS remain as the fallback when
credentials are missing or network is unavailable — they are never shown
when live data is available.

* docs: v0.50.31 release — version badge and CHANGELOG

---------

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
This commit is contained in:
nesquena-hermes
2026-04-13 22:57:58 -07:00
committed by GitHub
parent c91eaaf05f
commit 539501ed2b
6 changed files with 130 additions and 183 deletions

View File

@@ -86,29 +86,36 @@ def test_openai_codex_display_name():
assert config._PROVIDER_DISPLAY["openai-codex"] == "OpenAI Codex"
def test_live_models_handler_uses_codex_agent_path():
"""_handle_live_models for openai-codex must use get_codex_model_ids(), not the
standard /v1/models endpoint (which returns 403 for OAuth-based Codex auth).
Verify structurally that the routes.py handler has a dedicated codex branch.
def test_live_models_handler_delegates_to_provider_model_ids():
"""_handle_live_models must delegate to the agent's provider_model_ids()
rather than maintain its own per-provider fetch logic.
"""
import pathlib
routes_src = (pathlib.Path(__file__).parent.parent / "api" / "routes.py").read_text()
# Must have a dedicated openai-codex branch before any base_url assignment
assert 'provider == "openai-codex"' in routes_src, (
"_handle_live_models must have a dedicated openai-codex branch "
"that uses get_codex_model_ids() instead of /v1/models"
assert "provider_model_ids" in routes_src, (
"_handle_live_models must call hermes_cli.models.provider_model_ids() "
"to delegate all provider-specific live-fetch logic to the agent"
)
# Must delegate to the agent's get_codex_model_ids
assert "get_codex_model_ids" in routes_src, (
"_handle_live_models must call hermes_cli.codex_models.get_codex_model_ids() "
"for openai-codex provider"
# The old per-provider base_url hardcoding should be gone
assert "https://api.openai.com/v1" not in routes_src, (
"_handle_live_models must not hardcode api.openai.com — "
"provider resolution is handled by the agent"
)
# Must NOT route openai-codex through the standard OpenAI base URL
# (the old bug: openai-codex was grouped with openai and sent to api.openai.com)
codex_block_start = routes_src.find('provider == "openai-codex"')
openai_base_url_line = routes_src.find('"https://api.openai.com/v1"', codex_block_start)
openai_base_url_before = routes_src.find('"https://api.openai.com/v1"')
assert openai_base_url_before > codex_block_start or openai_base_url_line == -1, (
"openai-codex must be handled before the api.openai.com/v1 fallback, "
"not grouped with it"
assert "not_supported" not in routes_src, (
"_handle_live_models must not return not_supported for any provider — "
"provider_model_ids() falls back to static list automatically"
)
def test_live_models_ui_no_longer_skips_any_provider():
"""_fetchLiveModels in ui.js must not exclude any provider from live fetching.
Previously anthropic, google, and gemini were skipped — now provider_model_ids()
handles them all (with graceful fallback to static lists).
"""
import pathlib
ui_src = (pathlib.Path(__file__).parent.parent / "static" / "ui.js").read_text()
# The old exclusion list must be gone
assert "includes(provider)" not in ui_src or "anthropic" not in ui_src[:ui_src.find("includes(provider)")+100], (
"_fetchLiveModels must not skip anthropic, google, or gemini — "
"the backend now returns live models for all providers"
)