fix(models): keep an observed Codex catalog cap when a refresh fails
After the 1h context TTL, a failed catalog refresh sent resolution to the static fallback, which applied the -900k bump without the max_context_window the same token and endpoint had already published. A known 600K cap became 900K with no newer evidence. The fallback branch now passes the cached cap. Introduced with the cap itself in #115872. Reported by andrexibiza on #128288.
This commit is contained in:
@@ -1914,15 +1914,20 @@ def _resolve_codex_oauth_context_length_with_source(model: str, access_token: st
|
||||
# main-agent path normalizes it away before reaching here, but display/auxiliary callers pass it through
|
||||
# (#92797 review).
|
||||
lookup_bare = _bare_codex_slug(strip_codex_context_variant_suffix(model_bare))
|
||||
live_max: Dict[str, int] = {}
|
||||
|
||||
def _catalog_key(keys) -> Optional[str]: # exact slug, then case-insensitive in case casing drifts
|
||||
return lookup_bare if lookup_bare in keys else next((s for s in keys if s.lower() == lookup_bare.lower()), None)
|
||||
|
||||
if access_token:
|
||||
live, fresh_probe = _fetch_codex_oauth_context_lengths_with_source(access_token, base_url=base_url)
|
||||
live_max = _codex_oauth_max_context_cache.get(_codex_oauth_token_fingerprint(access_token, base_url), {})
|
||||
# Exact slug, then case-insensitive in case casing drifts.
|
||||
slug = lookup_bare if lookup_bare in live else next((s for s in live if s.lower() == lookup_bare.lower()), None)
|
||||
slug = _catalog_key(live)
|
||||
if slug is not None:
|
||||
return _apply_verified_bump(live[slug], "live" if fresh_probe else "memory", live_max.get(slug))
|
||||
hit = _longest_key_match(_CODEX_OAUTH_CONTEXT_FALLBACK, lookup_bare.lower())
|
||||
return _apply_verified_bump(hit[1], "fallback") if hit else (None, "")
|
||||
# A failed refresh past the TTL must not lift an already observed catalog cap.
|
||||
return _apply_verified_bump(hit[1], "fallback", live_max.get(_catalog_key(live_max))) if hit else (None, "")
|
||||
|
||||
|
||||
def _resolve_nous_context_length(model: str, base_url: str = "", api_key: str = "") -> Tuple[Optional[int], str]:
|
||||
|
||||
@@ -428,6 +428,7 @@ class TestCodexOAuthContextLength:
|
||||
def setup_method(self):
|
||||
import agent.model_metadata as mm
|
||||
mm._codex_oauth_context_cache = {}
|
||||
mm._codex_oauth_max_context_cache = {} # the fallback branch reads it too
|
||||
|
||||
|
||||
def test_live_catalogue_cache_is_scoped_to_access_token(self):
|
||||
@@ -680,6 +681,23 @@ class TestCodexOAuthContextLength:
|
||||
)
|
||||
assert ctx == 272_000
|
||||
|
||||
def test_failed_refresh_past_ttl_keeps_the_observed_catalog_cap(self):
|
||||
"""A refresh that fails after the context TTL must not lift a cap the same token and
|
||||
endpoint already published: no newer evidence, so the -900k alias stays at the old max."""
|
||||
import agent.model_metadata as mm
|
||||
|
||||
ok = MagicMock(status_code=200)
|
||||
ok.json.return_value = {"models": [{"slug": "GPT-5.6-Luna", "context_window": 272_000, "max_context_window": 600_000}]}
|
||||
route = dict(base_url="https://chatgpt.com/backend-api/codex", api_key=_codex_jwt("fake-token"), provider="openai-codex")
|
||||
with patch.object(mm, "_codex_oauth_context_cache", {}), patch.object(mm, "_codex_oauth_max_context_cache", {}), \
|
||||
patch("agent.model_metadata.get_cached_context_length", return_value=None), \
|
||||
patch("agent.model_metadata.save_context_length"):
|
||||
with patch("agent.model_metadata.model_metadata_http.get", return_value=ok):
|
||||
assert mm.get_model_context_length(model="gpt-5.6-luna-900k", **route) == 600_000
|
||||
with patch("agent.model_metadata.model_metadata_http.get", side_effect=OSError("catalog down")), \
|
||||
patch("agent.model_metadata.time.time", return_value=time.time() + mm._CODEX_OAUTH_CONTEXT_CACHE_TTL + 1):
|
||||
assert mm.get_model_context_length(model="gpt-5.6-luna-900k", **route) == 600_000
|
||||
|
||||
def test_catalog_max_is_published_before_the_context_entry(self):
|
||||
"""A reader that sees a fresh context entry must already be able to see its cap, or a
|
||||
concurrent -900k resolution between the two writes skips the catalog max."""
|
||||
|
||||
Reference in New Issue
Block a user