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:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user