Skip to content
This repository was archived by the owner on Sep 27, 2026. It is now read-only.

Commit 53555ec

Browse files
committed
fix(codex): return Codex CLI-compatible /v1/models response
Codex CLI deserializes /v1/models as {"models": [...]} (ModelsResponse) while the proxy returns OpenAI-compatible {"object": "list", "data": [...]}. Detect Codex CLI via client_version query param and passthrough the upstream models.json catalog. Fixes: #59
1 parent b3d91dd commit 53555ec

2 files changed

Lines changed: 35 additions & 0 deletions

File tree

‎src/proxy_app/main.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1548,7 +1548,24 @@ async def list_models(
15481548
enriched: If True (default), returns detailed model info with pricing and capabilities.
15491549
If False, returns minimal OpenAI-compatible response.
15501550
refresh: If True, triggers all providers to refresh their upstream model listings and updates the metadata cache.
1551+
1552+
Codex CLI sends ``client_version`` as a query parameter and deserializes
1553+
the response as ``{"models": [...]}``. When we detect that parameter we
1554+
return the upstream models.json catalog verbatim so the CLI's model
1555+
picker/refresh works without warnings.
15511556
"""
1557+
# Codex CLI compat: return upstream {"models": [...]} catalog when
1558+
# the request carries a client_version query param (Codex-specific).
1559+
client_version = request.query_params.get("client_version")
1560+
if client_version is not None:
1561+
try:
1562+
from rotator_library.providers.codex_provider import get_raw_models_catalog
1563+
raw_catalog = get_raw_models_catalog()
1564+
if raw_catalog is not None:
1565+
return {"models": raw_catalog}
1566+
except ImportError:
1567+
pass
1568+
15521569
if refresh:
15531570
# Trigger model registry refresh if available
15541571
if hasattr(request.app.state, "model_info_service"):

‎src/rotator_library/providers/codex_provider.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ def _derive_ws_endpoint() -> str:
171171
# Module-level cache for dynamic model data
172172
_models_cache: Optional[Dict[str, Any]] = None
173173
_models_cache_time: float = 0.0
174+
# Raw upstream models.json for Codex CLI passthrough ({"models": [...]})
175+
_raw_models_catalog: Optional[List[Dict[str, Any]]] = None
174176

175177

176178
def _fetch_models_from_github() -> Optional[Dict[str, Any]]:
@@ -198,6 +200,9 @@ def _fetch_models_from_github() -> Optional[Dict[str, Any]]:
198200
lib_logger.warning("[Codex] models.json from GitHub had empty models list")
199201
return None
200202

203+
global _raw_models_catalog
204+
_raw_models_catalog = models_list
205+
201206
base_models = []
202207
reasoning_efforts = {}
203208
context_limits: Dict[str, int] = {}
@@ -343,6 +348,19 @@ def get_model_plan_access() -> Dict[str, set]:
343348
return _get_model_data().get("plan_access", {})
344349

345350

351+
def get_raw_models_catalog() -> Optional[List[Dict[str, Any]]]:
352+
"""
353+
Return the raw upstream models.json catalog for Codex CLI passthrough.
354+
355+
Codex CLI expects {"models": [...]} with full ModelInfo objects (slug,
356+
display_name, context_window, etc.) rather than the OpenAI-compatible
357+
{"object": "list", "data": [...]}. Ensures the cache is populated
358+
before returning.
359+
"""
360+
_get_model_data()
361+
return _raw_models_catalog
362+
363+
346364
def _model_requires_paid_tier(model: str) -> bool:
347365
"""Check if a model is restricted to paid plans only (not available on free-class plans)."""
348366
plan_map = get_model_plan_access()

0 commit comments

Comments
 (0)