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:
@@ -174,10 +174,14 @@ class TestLiveModelFetching:
|
||||
"_handle_live_models must have SSRF protection for private IP ranges (#375)"
|
||||
)
|
||||
|
||||
def test_live_models_unsupported_providers_gracefully_handled(self):
|
||||
"""Providers without /v1/models support must return not_supported gracefully."""
|
||||
assert "not_supported" in ROUTES_PY, (
|
||||
"_handle_live_models must return not_supported for Anthropic/Google (#375)"
|
||||
def test_live_models_all_providers_handled_via_agent(self):
|
||||
"""_handle_live_models must delegate to provider_model_ids() which handles all
|
||||
providers gracefully — live fetch where possible, static fallback otherwise.
|
||||
The old 'not_supported' return for Anthropic/Google is superseded: those
|
||||
providers now return live or static model lists via the agent delegate."""
|
||||
assert "provider_model_ids" in ROUTES_PY, (
|
||||
"_handle_live_models must delegate to hermes_cli.models.provider_model_ids() "
|
||||
"so all providers are handled uniformly (#375 upgrade)"
|
||||
)
|
||||
|
||||
def test_frontend_has_fetch_live_models_function(self):
|
||||
@@ -204,11 +208,16 @@ class TestLiveModelFetching:
|
||||
"_fetchLiveModels must track existing model IDs to avoid duplicates (#375)"
|
||||
)
|
||||
|
||||
def test_frontend_live_fetch_skips_unsupported_providers(self):
|
||||
"""_fetchLiveModels must skip providers that don't support live fetching (#375)."""
|
||||
assert "anthropic" in UI_JS and "google" in UI_JS, (
|
||||
"_fetchLiveModels must skip Anthropic and Google (no /v1/models support) (#375)"
|
||||
)
|
||||
def test_frontend_live_fetch_covers_all_providers(self):
|
||||
"""_fetchLiveModels no longer skips any provider — all providers return
|
||||
live or fallback models via provider_model_ids() on the backend (#375 upgrade)."""
|
||||
# The old skip list (anthropic, google, gemini) must be gone from the guard
|
||||
skip_guard_pos = UI_JS.find("includes(provider)")
|
||||
if skip_guard_pos != -1:
|
||||
guard_line = UI_JS[max(0,skip_guard_pos-100):skip_guard_pos+50]
|
||||
assert "anthropic" not in guard_line, (
|
||||
"_fetchLiveModels must not skip anthropic — backend now handles it (#375 upgrade)"
|
||||
)
|
||||
|
||||
def test_live_models_endpoint_wired_in_routes(self):
|
||||
"""The /api/models/live path must be handled in handle_get()."""
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user