Stage 301: PR #1685

This commit is contained in:
Nathan Esquenazi
2026-05-05 15:41:43 +00:00
2 changed files with 182 additions and 0 deletions
+78
View File
@@ -1902,6 +1902,47 @@ def _get_label_for_model(model_id: str, existing_groups: list) -> str:
)
def _read_visible_codex_cache_model_ids() -> list[str]:
"""Return visible model slugs from Codex's local models_cache.json.
The agent's provider_model_ids('openai-codex') intentionally filters IDs
with ``supported_in_api: false``. Codex CLI still lists some of those models
in its picker (notably ``gpt-5.3-codex-spark`` from #1680), so the WebUI
merges this visible local catalog to stay in sync with Codex itself.
"""
codex_home = Path(os.getenv("CODEX_HOME", "").strip() or (HOME / ".codex")).expanduser()
cache_path = codex_home / "models_cache.json"
try:
payload = json.loads(cache_path.read_text(encoding="utf-8"))
except Exception:
return []
entries = payload.get("models") if isinstance(payload, dict) else None
if not isinstance(entries, list):
return []
sortable: list[tuple[int, str]] = []
for item in entries:
if not isinstance(item, dict):
continue
slug = item.get("slug")
if not isinstance(slug, str) or not slug.strip():
continue
visibility = item.get("visibility", "")
if isinstance(visibility, str) and visibility.strip().lower() in ("hide", "hidden"):
continue
priority = item.get("priority")
rank = int(priority) if isinstance(priority, (int, float)) else 10_000
sortable.append((rank, slug.strip()))
sortable.sort(key=lambda item: (item[0], item[1]))
ordered: list[str] = []
for _, slug in sortable:
if slug not in ordered:
ordered.append(slug)
return ordered
def get_available_models() -> dict:
"""
Return available models grouped by provider.
@@ -2671,6 +2712,43 @@ def get_available_models() -> dict:
except Exception:
logger.warning("Failed to load Ollama Cloud models from hermes_cli")
if raw_models:
models = _apply_provider_prefix(raw_models, pid, active_provider)
groups.append(
{
"provider": provider_name,
"provider_id": pid,
"models": models,
}
)
elif pid == "openai-codex":
# Codex account catalogs drift faster than WebUI releases
# (for example gpt-5.3-codex-spark in #1680). Ask the
# agent's Codex resolver first so /api/models inherits the
# live Codex API / local ~/.codex cache / static fallback
# chain instead of freezing the picker to WebUI's curated
# _PROVIDER_MODELS snapshot.
raw_models = []
codex_ids = []
try:
from hermes_cli.models import provider_model_ids as _provider_model_ids
codex_ids = [mid for mid in (_provider_model_ids("openai-codex") or []) if mid]
except Exception:
logger.warning("Failed to load OpenAI Codex models from hermes_cli")
for mid in _read_visible_codex_cache_model_ids():
if mid not in codex_ids:
codex_ids.append(mid)
raw_models = [
{"id": mid, "label": _get_label_for_model(mid, [])}
for mid in codex_ids
]
if not raw_models:
raw_models = copy.deepcopy(_PROVIDER_MODELS.get("openai-codex", []))
if raw_models:
models = _apply_provider_prefix(raw_models, pid, active_provider)
groups.append(
+104
View File
@@ -0,0 +1,104 @@
"""Regression tests for #1680 — Codex model picker uses live Codex discovery."""
import json
import sys
import types
from api import config
def _flatten_ids(groups):
return [m.get("id") for g in groups for m in g.get("models", [])]
def _install_fake_hermes_models(monkeypatch, provider_model_ids):
hermes_cli = types.ModuleType("hermes_cli")
hermes_cli.__path__ = []
models = types.ModuleType("hermes_cli.models")
models._PROVIDER_ALIASES = {}
models.provider_model_ids = provider_model_ids
monkeypatch.setitem(sys.modules, "hermes_cli", hermes_cli)
monkeypatch.setitem(sys.modules, "hermes_cli.models", models)
def _configure_codex(monkeypatch, tmp_path, default="gpt-5.3-codex-spark"):
monkeypatch.setattr(config, "_get_config_path", lambda: tmp_path / "missing-config.yaml")
monkeypatch.setattr(config, "_models_cache_path", tmp_path / "models_cache.json")
monkeypatch.setattr(config, "cfg", {
"model": {"provider": "openai-codex", "default": default},
"providers": {},
"fallback_providers": [],
})
monkeypatch.setattr(config, "_cfg_mtime", 0.0)
config.invalidate_models_cache()
def test_openai_codex_group_uses_provider_model_ids_for_spark(monkeypatch, tmp_path):
"""Codex-only models from the Codex catalog must surface in /api/models.
The static WebUI fallback chronically drifts. ``gpt-5.3-codex-spark`` is
the regression case from #1680: it is discoverable by the Codex provider
resolver but was missing from the picker because get_available_models()
copied _PROVIDER_MODELS["openai-codex"] without asking hermes_cli.
"""
calls = []
def provider_model_ids(provider):
calls.append(provider)
assert provider == "openai-codex"
return ["gpt-5.4", "gpt-5.3-codex-spark", "gpt-5.3-codex"]
_install_fake_hermes_models(monkeypatch, provider_model_ids)
_configure_codex(monkeypatch, tmp_path)
result = config.get_available_models()
codex_groups = [g for g in result["groups"] if g.get("provider_id") == "openai-codex"]
assert calls == ["openai-codex"]
assert codex_groups, "OpenAI Codex group should be present"
assert "gpt-5.3-codex-spark" in _flatten_ids(codex_groups)
assert codex_groups[0]["models"][0]["label"] == "GPT 5.4"
def test_openai_codex_group_merges_visible_codex_cache_models(monkeypatch, tmp_path):
"""Visible Codex CLI cache models should appear even if API-filtered.
Michael's local Codex cache lists ``gpt-5.3-codex-spark`` with
``supported_in_api: false``. The agent helper currently filters those IDs
out, but the WebUI picker is a Codex-model selection surface and should
mirror the visible Codex catalog instead of hiding Spark.
"""
def provider_model_ids(provider):
assert provider == "openai-codex"
return ["gpt-5.4", "gpt-5.3-codex"]
_install_fake_hermes_models(monkeypatch, provider_model_ids)
_configure_codex(monkeypatch, tmp_path, default="gpt-5.4")
codex_home = tmp_path / "codex-home"
codex_home.mkdir()
(codex_home / "models_cache.json").write_text(
json.dumps(
{
"models": [
{"slug": "gpt-5.4", "visibility": "list", "priority": 0},
{
"slug": "gpt-5.3-codex-spark",
"visibility": "list",
"supported_in_api": False,
"priority": 7,
},
{"slug": "hidden-test-model", "visibility": "hide", "priority": 8},
]
}
),
encoding="utf-8",
)
monkeypatch.setenv("CODEX_HOME", str(codex_home))
result = config.get_available_models()
codex_groups = [g for g in result["groups"] if g.get("provider_id") == "openai-codex"]
ids = _flatten_ids(codex_groups)
assert "gpt-5.3-codex-spark" in ids
assert "hidden-test-model" not in ids