Port pattern from zed-industries/zed#62729: request ungated Codex model catalog (clean-room)

The ChatGPT Codex models endpoint interprets client_version as a Codex
CLI compatibility version and filters out any model whose
minimal_client_version is newer than the value sent. Hermes hardcoded
client_version=1.0.0 at both catalog request sites, so model visibility
was accidentally coupled to a version scheme Hermes doesn't follow —
future models gated behind a higher minimal version would silently
vanish from the account catalog.

The backend accepts the exact sentinel 0.0.0 as an ungated request
returning the complete account catalog (verified live: 0.0.0 and
current versions return identical model sets today, while omitting the
parameter is HTTP 400 and out-of-sequence values like 0.0.1 return no
models). Both request sites (hermes_cli/codex_models.py and the
context-length probe in agent/model_metadata.py) now share one
CODEX_UNGATED_CLIENT_VERSION constant.

Clean-room port of the observed behavior in zed-industries/zed#62729;
no GPL code translated.
This commit is contained in:
Teknium
2026-08-23 16:07:38 -07:00
parent bc86ff6c4a
commit 2aad4035a5
3 changed files with 54 additions and 5 deletions

View File

@@ -1595,6 +1595,13 @@ def _verified_codex_ctx_for_slug(model_bare: str) -> Optional[int]:
_codex_oauth_context_cache: Dict[str, Tuple[Dict[str, int], float]] = {}
_CODEX_OAUTH_CONTEXT_CACHE_TTL = 3600 # 1 hour
# The Codex models endpoint reads ``client_version`` as a Codex CLI compatibility version and
# hides models whose ``minimal_client_version`` is newer, so a made-up version (the old
# "1.0.0") silently drops future models. "0.0.0" is the backend's ungated sentinel returning
# the full account catalog; other out-of-sequence values return an empty catalog and omitting
# the parameter is HTTP 400.
CODEX_UNGATED_CLIENT_VERSION = "0.0.0"
CODEX_MODELS_CATALOG_URL = f"https://chatgpt.com/backend-api/codex/models?client_version={CODEX_UNGATED_CLIENT_VERSION}"
def _codex_oauth_token_fingerprint(access_token: str) -> str:
@@ -1630,7 +1637,7 @@ def _fetch_codex_oauth_context_lengths_with_source(access_token: str) -> Tuple[D
headers["ChatGPT-Account-Id"] = acct_id
try:
_ensure_requests()
resp = requests.get("https://chatgpt.com/backend-api/codex/models?client_version=1.0.0", headers=headers, timeout=(5, 10), verify=_resolve_requests_verify())
resp = requests.get(CODEX_MODELS_CATALOG_URL, headers=headers, timeout=(5, 10), verify=_resolve_requests_verify())
if resp.status_code != 200:
logger.debug("Codex /models probe returned HTTP %s; falling back to hardcoded defaults", resp.status_code)
return {}, False

View File

@@ -155,10 +155,8 @@ def _fetch_models_from_api(access_token: str) -> List[str]:
acct_id = _extract_chatgpt_account_id(access_token)
if acct_id:
headers["ChatGPT-Account-Id"] = acct_id
resp = httpx.get(
"https://chatgpt.com/backend-api/codex/models?client_version=1.0.0",
headers=headers,
timeout=10)
from agent.model_metadata import CODEX_MODELS_CATALOG_URL
resp = httpx.get(CODEX_MODELS_CATALOG_URL, headers=headers, timeout=10)
if resp.status_code != 200:
return []
data = resp.json()

View File

@@ -269,3 +269,47 @@ class TestNormalizeModelForProvider:
assert changed is True
# Uses first from available list
assert cli.model == "gpt-5.3-codex"
def test_catalog_requests_use_ungated_client_version(monkeypatch):
"""Both catalog request sites send the backend's ungated ``0.0.0`` sentinel: the endpoint
hides models whose ``minimal_client_version`` is newer than ``client_version``, so a
made-up version silently drops future models."""
import sys
from urllib.parse import parse_qs, urlparse
from agent import model_metadata
from hermes_cli import codex_models
seen_urls = []
class _FakeResp:
status_code = 200
def json(self):
return {"models": []}
class _FakeHttpx:
@staticmethod
def get(url, headers=None, timeout=None):
seen_urls.append(url)
return _FakeResp()
class _FakeRequests:
@staticmethod
def get(url, headers=None, timeout=None, verify=None):
seen_urls.append(url)
return _FakeResp()
monkeypatch.setitem(sys.modules, "httpx", _FakeHttpx)
codex_models._fetch_models_from_api(access_token="tok")
monkeypatch.setattr(model_metadata, "requests", _FakeRequests)
monkeypatch.setattr(model_metadata, "_ensure_requests", lambda: None)
monkeypatch.setattr(model_metadata, "_codex_oauth_context_cache", {})
model_metadata._fetch_codex_oauth_context_lengths_with_source("tok")
assert len(seen_urls) == 2
for url in seen_urls:
parsed = urlparse(url)
assert parsed.netloc == "chatgpt.com" and parsed.path == "/backend-api/codex/models"
assert parse_qs(parsed.query)["client_version"] == ["0.0.0"]