diff --git a/plugins/image_gen/openai-codex/__init__.py b/plugins/image_gen/openai-codex/__init__.py index 26c7bb827b07..d8d5ea80d97e 100644 --- a/plugins/image_gen/openai-codex/__init__.py +++ b/plugins/image_gen/openai-codex/__init__.py @@ -48,14 +48,22 @@ _IMAGE_GENERATION_UNAVAILABLE_MESSAGE = ( "Image generation is not enabled for the current Codex account. " "Switch the image provider to OpenAI API key, FAL, or xAI." ) +_IMAGE_GENERATION_UNSUPPORTED_ERROR = ( + "Tool choice 'image_generation' not found in 'tools' parameter." +) def _is_image_generation_unsupported_error(status_code: int, body: str) -> bool: """Match only Codex's account-capability rejection for the image tool.""" if status_code != 400: return False - normalized = " ".join((body or "").lower().split()) - return "tool choice 'image_generation' not found in 'tools' parameter" in normalized + try: + payload = json.loads(body) + error = payload.get("error") if isinstance(payload, dict) else None + message = error.get("message") if isinstance(error, dict) else None + except (TypeError, ValueError): + message = body + return isinstance(message, str) and message.strip() == _IMAGE_GENERATION_UNSUPPORTED_ERROR # --------------------------------------------------------------------------- @@ -411,12 +419,13 @@ def _collect_image_b64( response.raise_for_status() except httpx.HTTPStatusError as exc: exc.response.read() - body = exc.response.text[:500] + full_body = exc.response.text if _is_image_generation_unsupported_error( exc.response.status_code, - body, + full_body, ): - raise CodexImageGenerationUnsupportedError(body) from exc + raise CodexImageGenerationUnsupportedError(full_body) from exc + body = full_body[:500] raise RuntimeError( f"Codex Responses API returned HTTP {exc.response.status_code}: {body}" ) from exc diff --git a/tests/plugins/image_gen/test_openai_codex_provider.py b/tests/plugins/image_gen/test_openai_codex_provider.py index eaf8f09264b2..338ea97418a7 100644 --- a/tests/plugins/image_gen/test_openai_codex_provider.py +++ b/tests/plugins/image_gen/test_openai_codex_provider.py @@ -9,6 +9,7 @@ endpoint. from __future__ import annotations import importlib +import json from pathlib import Path import pytest @@ -348,6 +349,50 @@ class TestCapabilityErrorDetection: def test_does_not_misclassify_other_failures(self, status_code, body): assert codex_plugin._is_image_generation_unsupported_error(status_code, body) is False + def test_does_not_match_error_message_with_extra_text(self): + body = json.dumps({ + "error": { + "message": ( + "Tool choice 'image_generation' not found in 'tools' parameter " + "because the request is malformed." + ) + } + }) + + assert codex_plugin._is_image_generation_unsupported_error(400, body) is False + + def test_collect_classifies_exact_http_error_after_large_metadata(self, monkeypatch): + import httpx + + body = json.dumps({ + "metadata": "x" * 600, + "error": { + "message": "Tool choice 'image_generation' not found in 'tools' parameter." + }, + }) + + def _handler(request): + return httpx.Response(400, text=body, request=request) + + real_client = httpx.Client + monkeypatch.setattr( + httpx, + "Client", + lambda *args, **kwargs: real_client( + transport=httpx.MockTransport(_handler), + headers=kwargs.get("headers"), + timeout=kwargs.get("timeout"), + ), + ) + + with pytest.raises(codex_plugin.CodexImageGenerationUnsupportedError): + codex_plugin._collect_image_b64( + "codex-token", + prompt="a cat", + size="1024x1024", + quality="low", + ) + # ── Plugin entry point ──────────────────────────────────────────────────────