diff --git a/studio/backend/core/inference/codex_availability.py b/studio/backend/core/inference/codex_availability.py index 8ae57deadd..bbb78bef64 100644 --- a/studio/backend/core/inference/codex_availability.py +++ b/studio/backend/core/inference/codex_availability.py @@ -182,7 +182,9 @@ async def _detect_logged_in() -> bool: # Negative prefixes win, regardless of rc. We anchor on word # boundaries so "not logged in" / "not authenticated" both match # without being fooled by the substring "logged in" inside them. - negative = re.compile(r"\b(not logged in|not authenticated|please log in|run\s+`?codex login`?)\b") + negative = re.compile( + r"\b(not logged in|not authenticated|please log in|run\s+`?codex login`?)\b" + ) if negative.search(combined): return False diff --git a/studio/backend/core/inference/codex_provider.py b/studio/backend/core/inference/codex_provider.py index 299de46be3..55cda300f5 100644 --- a/studio/backend/core/inference/codex_provider.py +++ b/studio/backend/core/inference/codex_provider.py @@ -353,8 +353,7 @@ async def _stream_codex_single( async_codex_cls = getattr(sdk, "AsyncCodex", None) if async_codex_cls is None: raise CodexUnavailableError( - "Codex SDK is installed but AsyncCodex is missing -- " - "upgrade the SDK." + "Codex SDK is installed but AsyncCodex is missing -- " "upgrade the SDK." ) completion_text_chars = 0 @@ -746,7 +745,9 @@ async def stream_codex_device_login() -> AsyncGenerator[dict[str, Any], None]: # Anchor on the upstream URL shape: ``.../codex/device`` (optionally # with a query string). The pattern accepts any host because some # builds redirect via a staging host. - url_re = re.compile(r"https?://[^\s\x1b]+?/codex/device(?:\?[^\s\x1b]*)?", re.IGNORECASE) + url_re = re.compile( + r"https?://[^\s\x1b]+?/codex/device(?:\?[^\s\x1b]*)?", re.IGNORECASE + ) # One-time-code format from upstream device_code_auth.rs: 4 chars, # dash, 4 chars. Pattern is tolerant of any uppercase alphanum. code_re = re.compile(r"\b([A-Z0-9]{4}-[A-Z0-9]{4})\b") diff --git a/studio/backend/routes/codex.py b/studio/backend/routes/codex.py index 5570706646..60b0d3b18e 100644 --- a/studio/backend/routes/codex.py +++ b/studio/backend/routes/codex.py @@ -88,11 +88,17 @@ async def codex_device_login( exc_type = type(exc).__name__, error = str(exc), ) - yield "data: " + json.dumps({ - "type": "error", - "message": "Codex login failed", - "exception_type": type(exc).__name__, - }) + "\n\n" + yield ( + "data: " + + json.dumps( + { + "type": "error", + "message": "Codex login failed", + "exception_type": type(exc).__name__, + } + ) + + "\n\n" + ) yield "data: " + json.dumps({"type": "done", "ok": False}) + "\n\n" # Frontend treats the trailing [DONE] the same way it does for # chat streams, so we emit it for parity. diff --git a/studio/backend/tests/test_codex_provider.py b/studio/backend/tests/test_codex_provider.py index 1943bcf7ad..b6a255a785 100644 --- a/studio/backend/tests/test_codex_provider.py +++ b/studio/backend/tests/test_codex_provider.py @@ -521,10 +521,11 @@ class TestCodexHardenedRegressions: monkeypatch.setattr("importlib.util.find_spec", _shim) from core.inference.codex_availability import _sdk_importable + assert _sdk_importable() is False - assert calls and calls[0] == "openai_codex", ( - f"availability probe must check openai_codex first; saw {calls}" - ) + assert ( + calls and calls[0] == "openai_codex" + ), f"availability probe must check openai_codex first; saw {calls}" def test_login_status_uses_login_subcommand(self): """Upstream is `codex login status`, NOT `codex auth status`.""" @@ -533,9 +534,9 @@ class TestCodexHardenedRegressions: "studio/backend/core/inference/codex_availability.py" ) text = open(src).read() - assert '"auth", "status"' not in text, ( - "_detect_logged_in must use `codex login status`, not `codex auth status`" - ) + assert ( + '"auth", "status"' not in text + ), "_detect_logged_in must use `codex login status`, not `codex auth status`" assert '"login", "status"' in text def test_device_login_uses_login_subcommand(self): @@ -544,9 +545,9 @@ class TestCodexHardenedRegressions: "studio/backend/core/inference/codex_provider.py" ) text = open(src).read() - assert '"auth", "login", "--device-auth"' not in text, ( - "stream_codex_device_login must use `codex login --device-auth`" - ) + assert ( + '"auth", "login", "--device-auth"' not in text + ), "stream_codex_device_login must use `codex login --device-auth`" assert '"login", "--device-auth"' in text def test_not_logged_in_not_misparsed_as_logged_in(self): @@ -594,9 +595,9 @@ class TestCodexHardenedRegressions: ] prompt = _last_user_prompt(msgs) assert "and germany?" in prompt - assert "Paris" in prompt, ( - f"PRIOR ASSISTANT TURN DROPPED — multi-turn broken. Prompt:\n{prompt}" - ) + assert ( + "Paris" in prompt + ), f"PRIOR ASSISTANT TURN DROPPED — multi-turn broken. Prompt:\n{prompt}" assert "capital of france" in prompt.lower() def test_single_turn_prompt_unchanged(self): @@ -611,9 +612,9 @@ class TestCodexHardenedRegressions: from core.inference.providers import PROVIDER_REGISTRY codex = PROVIDER_REGISTRY["codex"] - assert "o3" not in codex["default_models"], ( - "o3 is not a Codex model; remove from default_models" - ) + assert ( + "o3" not in codex["default_models"] + ), "o3 is not a Codex model; remove from default_models" assert "gpt-5.5" in codex["default_models"] def test_inference_route_no_raw_exc_leak(self):