From f7902776d46344f69dadb26b4464e3bc4354a412 Mon Sep 17 00:00:00 2001 From: Frank Song Date: Thu, 7 May 2026 20:41:14 +0800 Subject: [PATCH] fix: use live Codex models in providers card --- CHANGELOG.md | 6 -- api/providers.py | 18 +++++ ...sue1807_codex_provider_card_live_models.py | 81 +++++++++++++++++++ 3 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 tests/test_issue1807_codex_provider_card_live_models.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b2cfb564..a884aeed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,5 @@ # Hermes Web UI -- Changelog -## Unreleased - -### Fixed - -- **Named custom provider routing for local endpoints** — `model.provider: ollama-local` now normalizes to the same `custom:ollama-local` slug used by the model picker when a matching `custom_providers[].name` exists. Auto-discovered `/v1/models` results for that endpoint are folded into the named custom group, and stale base-url-derived slugs such as `custom:local-(127.0.0.1:11434)` no longer create a second picker group that routes to an unsettable API-key env var. Fixes #1806. - ## [v0.51.18] — 2026-05-07 — 5-PR batch (4 contributor + 1 self-built UX polish) ### Fixed diff --git a/api/providers.py b/api/providers.py index 0153bf69..e352b063 100644 --- a/api/providers.py +++ b/api/providers.py @@ -19,6 +19,7 @@ from typing import Any from api.config import ( _PROVIDER_DISPLAY, _PROVIDER_MODELS, + _get_label_for_model, _save_yaml_config_file, get_config, invalidate_models_cache, @@ -691,6 +692,23 @@ def get_providers() -> dict[str, Any]: models = list(_PROVIDER_MODELS.get(pid, [])) models_total = len(models) + # Codex account catalogs are account-specific and can drift faster than + # WebUI's static fallback table (#1807). Prefer the live agent resolver + # for the providers card too so stale static-only model IDs are not + # presented as available when discovery succeeds. + if pid == "openai-codex": + try: + from hermes_cli.models import provider_model_ids as _provider_model_ids + + live_ids = [mid for mid in (_provider_model_ids("openai-codex") or []) if mid] + if live_ids: + models = [ + {"id": mid, "label": _get_label_for_model(mid, [])} + for mid in live_ids + ] + models_total = len(models) + except Exception: + logger.debug("Failed to load OpenAI Codex models from hermes_cli") # Nous Portal: prefer the live catalog so the providers card matches # the dropdown picker (#1538). Same fallback shape as the static-only # case below — when hermes_cli is unavailable or its lookup raises, diff --git a/tests/test_issue1807_codex_provider_card_live_models.py b/tests/test_issue1807_codex_provider_card_live_models.py new file mode 100644 index 00000000..ed734d91 --- /dev/null +++ b/tests/test_issue1807_codex_provider_card_live_models.py @@ -0,0 +1,81 @@ +"""Regression tests for #1807 -- Codex providers card uses live models.""" + +import sys +import types + +import api.config as config +import api.profiles as profiles + + +def _install_fake_hermes_cli(monkeypatch, provider_model_ids): + fake_pkg = types.ModuleType("hermes_cli") + fake_pkg.__path__ = [] + + fake_models = types.ModuleType("hermes_cli.models") + fake_models.list_available_providers = lambda: [] + fake_models.provider_model_ids = provider_model_ids + + fake_auth = types.ModuleType("hermes_cli.auth") + fake_auth.get_auth_status = lambda pid: { + "logged_in": pid == "openai-codex", + "key_source": "oauth", + } + + monkeypatch.setitem(sys.modules, "hermes_cli", fake_pkg) + monkeypatch.setitem(sys.modules, "hermes_cli.models", fake_models) + monkeypatch.setitem(sys.modules, "hermes_cli.auth", fake_auth) + + +def _configure_codex(monkeypatch, tmp_path): + monkeypatch.setattr(profiles, "get_active_hermes_home", lambda: tmp_path) + monkeypatch.setattr(config, "_get_config_path", lambda: tmp_path / "missing-config.yaml") + monkeypatch.setattr(config, "cfg", { + "model": {"provider": "openai-codex", "default": "gpt-5.5"}, + "providers": {}, + "fallback_providers": [], + }) + monkeypatch.setattr(config, "_cfg_mtime", 0.0) + + +def _codex_provider(): + from api.providers import get_providers + + providers = get_providers()["providers"] + return next(p for p in providers if p["id"] == "openai-codex") + + +def test_codex_provider_card_prefers_live_account_catalog(monkeypatch, tmp_path): + live_codex_ids = [ + "gpt-5.5", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.3-codex", + "gpt-5.2", + ] + + def provider_model_ids(pid): + return live_codex_ids if pid == "openai-codex" else [] + + _install_fake_hermes_cli(monkeypatch, provider_model_ids) + _configure_codex(monkeypatch, tmp_path) + + codex = _codex_provider() + ids = [m["id"] for m in codex["models"]] + + assert ids == live_codex_ids + assert codex["models_total"] == len(live_codex_ids) + assert "gpt-5.5-mini" not in ids + assert "gpt-5.2-codex" not in ids + assert "codex-mini-latest" not in ids + + +def test_codex_provider_card_keeps_static_fallback_when_live_catalog_empty(monkeypatch, tmp_path): + _install_fake_hermes_cli(monkeypatch, lambda _pid: []) + _configure_codex(monkeypatch, tmp_path) + + codex = _codex_provider() + ids = [m["id"] for m in codex["models"]] + + assert "gpt-5.5-mini" in ids + assert "codex-mini-latest" in ids + assert codex["models_total"] == len(ids)