diff --git a/CHANGELOG.md b/CHANGELOG.md index e59c6c9..1245163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Hermes Web UI -- Changelog +## [v0.50.30] fix: openai-codex live model fetch routes through agent's get_codex_model_ids() + +`_handle_live_models()` was grouping `openai-codex` with `openai` and sending `GET https://api.openai.com/v1/models` — which returns 403 because Codex auth is OAuth-based via `chatgpt.com`, not a standard API key. The live fetch silently failed, so users only ever saw the hardcoded static list. + +- `api/routes.py`: dedicated early-return branch for `openai-codex` that calls `hermes_cli.codex_models.get_codex_model_ids()` — the same resolver the agent CLI uses. Resolution order: live Codex API (if OAuth token available, hits `chatgpt.com/backend-api/codex/models`) → `~/.codex/` local cache (written by the Codex CLI) → `DEFAULT_CODEX_MODELS` hardcoded fallback. Users with a valid Codex session now get their exact subscription model list including any models not in the hardcoded list. +- `api/routes.py`: improved label generation for Codex model IDs (e.g. `gpt-5.4-mini` → `GPT 5.4 Mini`) +- `tests/test_opencode_providers.py`: structural regression test verifying the dedicated `openai-codex` branch exists and calls `get_codex_model_ids()` +- 1038 tests total (up from 1037) + ## [v0.50.29] fix: correct tool call card rendering on session load after context compaction (closes #401) (#402) - `static/sessions.js`: replace the flat B9 filter in `loadSession()` with a full sanitization pass that builds `origIdxToSanitizedIdx` — each `session.tool_calls[].assistant_msg_idx` is remapped to the new sanitized-array position as messages are filtered; for tool calls whose empty-assistant host was filtered out, they attach to the nearest prior kept assistant diff --git a/api/routes.py b/api/routes.py index d872225..0d48ffa 100644 --- a/api/routes.py +++ b/api/routes.py @@ -1490,9 +1490,44 @@ def _handle_live_models(handler, parsed): except Exception: pass + # openai-codex: use the agent's get_codex_model_ids() which calls the + # correct chatgpt.com/backend-api/codex/models endpoint with the OAuth + # token and also falls back to ~/.codex/ local cache and DEFAULT_CODEX_MODELS. + # This is the only path that can actually return the user's real Codex model list. + if provider == "openai-codex": + try: + from hermes_cli.codex_models import get_codex_model_ids as _get_codex_ids + access_token = None + try: + from hermes_cli.runtime_provider import resolve_runtime_provider as _rrt + rt2 = _rrt(requested="openai-codex") + access_token = rt2.get("api_key") or rt2.get("access_token") + except Exception: + pass + ids = _get_codex_ids(access_token=access_token) + def _codex_label(mid): + # e.g. "gpt-5.4-mini" -> "GPT-5.4 Mini" + parts = mid.split("-") + result = [] + for p in parts: + if p.lower() == "gpt": + result.append("GPT") + elif p[:1].isdigit(): + result.append(p) # version numbers unchanged: 5.4, 5.1 + else: + result.append(p.capitalize()) + return " ".join(result) + models_out = [{"id": mid, "label": _codex_label(mid)} for mid in ids if mid] + return j(handler, {"provider": provider, "models": models_out, + "count": len(models_out)}) + except Exception as _ce: + logger.debug("Codex live model fetch failed: %s", _ce) + # Fall through to static list (handled by get_available_models()) + return j(handler, {"error": str(_ce), "models": []}) + # Determine the /v1/models endpoint URL if not base_url: - if provider in ("openai", "openai-codex", "copilot"): + if provider in ("openai", "copilot"): base_url = "https://api.openai.com/v1" elif provider == "openrouter": base_url = "https://openrouter.ai/api/v1" diff --git a/static/index.html b/static/index.html index 924337f..0c41aa8 100644 --- a/static/index.html +++ b/static/index.html @@ -535,7 +535,7 @@