From 8022f48d1a6d69c718598f08977ad2dddd41da69 Mon Sep 17 00:00:00 2001 From: JOBOYA Date: Tue, 12 May 2026 10:56:42 +0200 Subject: [PATCH] Address Codex OAuth review feedback --- strix/llm/codex_oauth.py | 2 +- strix/llm/llm.py | 7 ++-- tests/llm/test_codex_oauth.py | 65 +++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/strix/llm/codex_oauth.py b/strix/llm/codex_oauth.py index 2ecb0528..80e24e72 100644 --- a/strix/llm/codex_oauth.py +++ b/strix/llm/codex_oauth.py @@ -206,7 +206,7 @@ def _post_codex_responses( "Authorization": f"Bearer {credentials.access_token}", "Accept": "text/event-stream", "Content-Type": "application/json", - "version": "strix-codex-oauth", + "User-Agent": "strix-codex-oauth", } if credentials.account_id: headers["ChatGPT-Account-ID"] = credentials.account_id diff --git a/strix/llm/llm.py b/strix/llm/llm.py index ec35621e..4f70c013 100644 --- a/strix/llm/llm.py +++ b/strix/llm/llm.py @@ -10,7 +10,7 @@ from litellm import acompletion, completion_cost, stream_chunk_builder, supports from litellm.utils import supports_prompt_caching, supports_vision from strix.config import Config -from strix.llm.codex_oauth import complete_codex_oauth +from strix.llm.codex_oauth import CodexOAuthError, complete_codex_oauth from strix.llm.config import LLMConfig from strix.llm.memory_compressor import MemoryCompressor, get_message_tokens from strix.llm.utils import ( @@ -314,9 +314,6 @@ class LLM: self._total_stats.input_tokens += usage.get("input_tokens", 0) self._total_stats.output_tokens += usage.get("output_tokens", 0) - if content: - yield LLMResponse(content=content) - content = _THINKING_BLOCK_RE.sub("", content) content = normalize_tool_format(content) content = fix_incomplete_tool_call(_truncate_to_first_function(content)) @@ -384,6 +381,8 @@ class LLM: return 0.0 def _should_retry(self, e: Exception) -> bool: + if isinstance(e, CodexOAuthError): + return False code = getattr(e, "status_code", None) or getattr( getattr(e, "response", None), "status_code", None ) diff --git a/tests/llm/test_codex_oauth.py b/tests/llm/test_codex_oauth.py index 234247cb..c848f22b 100644 --- a/tests/llm/test_codex_oauth.py +++ b/tests/llm/test_codex_oauth.py @@ -4,13 +4,16 @@ from pathlib import Path import pytest from strix.llm.codex_oauth import ( + CodexOAuthCredentials, CodexOAuthError, + _post_codex_responses, build_codex_responses_payload, load_codex_oauth_credentials, parse_responses_sse_events, refresh_codex_oauth_credentials, ) from strix.llm.config import LLMConfig +from strix.llm.llm import LLM def test_llm_config_detects_codex_oauth_provider(monkeypatch: pytest.MonkeyPatch) -> None: @@ -22,6 +25,41 @@ def test_llm_config_detects_codex_oauth_provider(monkeypatch: pytest.MonkeyPatch assert config.codex_model == "gpt-5.5" +def test_llm_does_not_retry_codex_oauth_errors(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("STRIX_LLM", "codex/gpt-5.5") + + llm = LLM(LLMConfig()) + + assert llm._should_retry(CodexOAuthError("Run `codex login` first.")) is False + + +@pytest.mark.asyncio +async def test_codex_oauth_stream_yields_single_processed_response( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setenv("STRIX_LLM", "codex/gpt-5.5") + + def fake_complete_codex_oauth(*args, **kwargs): + return ( + "hiddendone", + {"input_tokens": 1, "output_tokens": 2}, + ) + + monkeypatch.setattr("strix.llm.llm.complete_codex_oauth", fake_complete_codex_oauth) + + llm = LLM(LLMConfig()) + responses = [ + response + async for response in llm._stream_codex_oauth([{"role": "user", "content": "Hi"}]) + ] + + assert len(responses) == 1 + assert "" not in responses[0].content + assert responses[0].tool_invocations + assert llm._total_stats.input_tokens == 1 + assert llm._total_stats.output_tokens == 2 + + def test_load_codex_oauth_credentials_reads_codex_auth_json(tmp_path: Path) -> None: auth_file = tmp_path / "auth.json" auth_file.write_text( @@ -95,6 +133,33 @@ def test_refresh_codex_oauth_credentials_persists_new_tokens(tmp_path: Path) -> assert calls[0][1]["json"]["grant_type"] == "refresh_token" +def test_post_codex_responses_uses_user_agent_header( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls = [] + + class FakeResponse: + pass + + def fake_post(url: str, **kwargs): + calls.append((url, kwargs)) + return FakeResponse() + + monkeypatch.setattr("strix.llm.codex_oauth.requests.post", fake_post) + + response = _post_codex_responses( + "https://example.test/codex", + CodexOAuthCredentials(access_token="access-token", account_id="account-id"), + {"model": "gpt-5.5"}, + 60, + ) + + assert isinstance(response, FakeResponse) + headers = calls[0][1]["headers"] + assert headers["User-Agent"] == "strix-codex-oauth" + assert "version" not in headers + + def test_build_codex_responses_payload_converts_chat_messages() -> None: payload = build_codex_responses_payload( model="gpt-5.5",