Stage 397: PR #2684 — fix: repair stale Codex OpenAI slash-qualified model state

Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
This commit is contained in:
Hermes Agent
2026-05-21 17:13:56 +00:00
parent 345762cf70
commit 92f1896754
2 changed files with 47 additions and 9 deletions
+15 -3
View File
@@ -1512,7 +1512,12 @@ def _resolve_compatible_session_model_state(
# qualifier — qualified strings require the catalog to decide whether
# the qualifier matches the active provider (see slow path below).
bare_model, explicit_provider = _split_provider_qualified_model(model)
if not explicit_provider:
model_prefix = model.split("/", 1)[0].strip().lower() if "/" in model else ""
stale_codex_openai_slash_id = (
requested_provider == "openai-codex"
and model_prefix == "openai"
)
if not explicit_provider and not stale_codex_openai_slash_id:
return model, requested_provider, False
catalog = get_available_models()
@@ -1533,7 +1538,14 @@ def _resolve_compatible_session_model_state(
bare_for_context, explicit_provider = _split_provider_qualified_model(model)
if requested_provider and not explicit_provider:
return model, requested_provider, False
model_prefix = model.split("/", 1)[0].strip().lower() if "/" in model else ""
stale_codex_openai_slash_id = (
raw_active_provider == "openai-codex"
and requested_provider == "openai-codex"
and model_prefix == "openai"
)
if not stale_codex_openai_slash_id:
return model, requested_provider, False
if model.startswith("@") and ":" in model:
provider_raw = explicit_provider or ""
@@ -1643,7 +1655,7 @@ def _resolve_compatible_session_model_state(
if (
raw_active_provider == "openai-codex"
and model_provider == "openai"
and requested_provider is None
and requested_provider in {None, "openai-codex"}
and default_model
):
# Persist provider_context = "openai-codex" unconditionally on this
@@ -61,13 +61,12 @@ class TestFastPathInvocation:
)
assert result == ("gpt-5.5", "openai-codex", False)
def test_fast_path_with_slash_qualified_model_skips_catalog(self):
"""Slash-qualified IDs (openrouter/...) still hit the fast path.
def test_fast_path_with_openrouter_slash_qualified_model_skips_catalog(self):
"""OpenRouter slash-qualified IDs still hit the fast path.
The fast path only excludes @provider:model strings, not slash-
qualified ones — those are valid model IDs that the picker emits
for OpenRouter and custom-provider routing, and a stored
model_provider is the authoritative routing decision.
Slash-qualified IDs are valid picker output for OpenRouter and a stored
model_provider is the authoritative routing decision. This remains fast
for explicit OpenRouter selections.
"""
from api.routes import _resolve_compatible_session_model_state
@@ -80,6 +79,33 @@ class TestFastPathInvocation:
assert mock_catalog.call_count == 0
assert result == ("anthropic/claude-opus-4.7", "openrouter", False)
def test_codex_with_stale_openai_slash_id_uses_catalog_repair(self):
"""Codex must repair stale OpenRouter-shaped OpenAI IDs.
Browser/localStorage state can submit ``openai/gpt-...`` while the
session/provider is ``openai-codex``. If the fast path preserves that
pair, runtime resolution routes through OpenRouter instead of Codex.
The Codex + ``openai/...`` shape must therefore use the slow-path repair
and normalize back to the active Codex default.
"""
from api.routes import _resolve_compatible_session_model_state
with patch("api.routes.get_available_models") as mock_catalog:
mock_catalog.return_value = {
"active_provider": "openai-codex",
"default_model": "gpt-5.5",
"groups": [
{"provider_id": "openai-codex", "models": [{"id": "gpt-5.5"}]}
],
}
result = _resolve_compatible_session_model_state(
"openai/gpt-5.4-mini",
"openai-codex",
)
assert mock_catalog.call_count == 1
assert result == ("gpt-5.5", "openai-codex", True)
def test_fast_path_normalizes_provider_default_alias(self):
"""`'default'` is treated as None by _clean_session_model_provider.