From 945764439018b5532cfa8cf63dea95d1921ab37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vesper=20=F0=9F=8C=99?= Date: Mon, 4 May 2026 01:45:32 +0000 Subject: [PATCH] fix: surface Codex CLI-only models --- hermes_cli/codex_models.py | 11 +++++---- hermes_cli/model_switch.py | 2 +- .../hermes_cli/test_codex_cli_model_picker.py | 24 +++++++++++++++++++ tests/hermes_cli/test_codex_models.py | 8 +++---- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/hermes_cli/codex_models.py b/hermes_cli/codex_models.py index 8ff2dd60a89..9e388ef0892 100644 --- a/hermes_cli/codex_models.py +++ b/hermes_cli/codex_models.py @@ -80,8 +80,10 @@ def _fetch_models_from_api(access_token: str) -> List[str]: if not isinstance(slug, str) or not slug.strip(): continue slug = slug.strip() - if item.get("supported_in_api") is False: - continue + # Codex CLI's catalog uses ``supported_in_api`` for the public OpenAI + # API, not for the OAuth-backed Codex backend that this provider uses. + # Some valid Codex CLI models (for example gpt-5.3-codex-spark) are + # marked false here but are still accepted by the Codex route. visibility = item.get("visibility", "") if isinstance(visibility, str) and visibility.strip().lower() in ("hide", "hidden"): continue @@ -130,8 +132,9 @@ def _read_cache_models(codex_home: Path) -> List[str]: if not isinstance(slug, str) or not slug.strip(): continue slug = slug.strip() - if item.get("supported_in_api") is False: - continue + # Do not filter on ``supported_in_api`` here. It describes the + # public OpenAI API, while Hermes openai-codex talks to the same + # OAuth-backed Codex backend as Codex CLI. visibility = item.get("visibility") if isinstance(visibility, str) and visibility.strip().lower() in ("hide", "hidden"): continue diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index b6cf63442a3..3a282425dc4 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -1342,7 +1342,7 @@ def list_authenticated_providers( if not has_creds: continue - if hermes_slug in {"copilot", "copilot-acp"}: + if hermes_slug in {"openai-codex", "copilot", "copilot-acp"}: model_ids = provider_model_ids(hermes_slug) # For aws_sdk providers (bedrock), use live discovery so the list # reflects the active region (eu.*, ap.*) not the static us.* list. diff --git a/tests/hermes_cli/test_codex_cli_model_picker.py b/tests/hermes_cli/test_codex_cli_model_picker.py index 56e364fda56..0143297b308 100644 --- a/tests/hermes_cli/test_codex_cli_model_picker.py +++ b/tests/hermes_cli/test_codex_cli_model_picker.py @@ -75,6 +75,30 @@ def test_normal_path_still_works(hermes_auth_only_env): assert "openai-codex" in slugs +def test_codex_picker_uses_live_codex_catalog(hermes_auth_only_env, tmp_path, monkeypatch): + """The gateway /model picker should surface Codex CLI-only listed models.""" + from hermes_cli.model_switch import list_authenticated_providers + + codex_home = tmp_path / "codex-home" + codex_home.mkdir() + (codex_home / "models_cache.json").write_text(json.dumps({ + "models": [ + {"slug": "gpt-5.5", "priority": 0, "supported_in_api": True}, + {"slug": "gpt-5.3-codex-spark", "priority": 7, "supported_in_api": False}, + ] + })) + monkeypatch.setenv("CODEX_HOME", str(codex_home)) + + providers = list_authenticated_providers( + current_provider="openai-codex", + max_models=10, + ) + + codex = next(p for p in providers if p["slug"] == "openai-codex") + assert "gpt-5.3-codex-spark" in codex["models"] + assert codex["total_models"] == len(codex["models"]) + + @pytest.fixture() def claude_code_only_env(tmp_path, monkeypatch): """Set up an environment where Anthropic credentials only exist in diff --git a/tests/hermes_cli/test_codex_models.py b/tests/hermes_cli/test_codex_models.py index 821558a1413..41cbfeb058c 100644 --- a/tests/hermes_cli/test_codex_models.py +++ b/tests/hermes_cli/test_codex_models.py @@ -1,10 +1,6 @@ import json -import os -import sys from unittest.mock import patch -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) - from hermes_cli.codex_models import DEFAULT_CODEX_MODELS, get_codex_model_ids @@ -17,6 +13,7 @@ def test_get_codex_model_ids_prioritizes_default_and_cache(tmp_path, monkeypatch { "models": [ {"slug": "gpt-5.3-codex", "priority": 20, "supported_in_api": True}, + {"slug": "gpt-5.3-codex-spark", "priority": 6, "supported_in_api": False}, {"slug": "gpt-5.1-codex", "priority": 5, "supported_in_api": True}, {"slug": "gpt-5.4", "priority": 1, "supported_in_api": True}, {"slug": "gpt-5-hidden-codex", "priority": 2, "visibility": "hidden"}, @@ -31,6 +28,9 @@ def test_get_codex_model_ids_prioritizes_default_and_cache(tmp_path, monkeypatch assert models[0] == "gpt-5.2-codex" assert "gpt-5.1-codex" in models assert "gpt-5.3-codex" in models + # Codex CLI marks Spark unsupported in the public API, but the Codex + # backend still accepts it via the OAuth-backed CLI/Hermes route. + assert "gpt-5.3-codex-spark" in models # Non-codex-suffixed models are included when the cache says they're available assert "gpt-5.4" in models assert "gpt-5.4-mini" in models