diff --git a/plugins/image_gen/openai-codex/__init__.py b/plugins/image_gen/openai-codex/__init__.py index 6790028bb05a..26c7bb827b07 100644 --- a/plugins/image_gen/openai-codex/__init__.py +++ b/plugins/image_gen/openai-codex/__init__.py @@ -40,6 +40,24 @@ from agent.image_gen_provider import ( logger = logging.getLogger(__name__) +class CodexImageGenerationUnsupportedError(RuntimeError): + """The active Codex account cannot use the hosted image tool.""" + + +_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." +) + + +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 + + # --------------------------------------------------------------------------- # Model catalog — mirrors the ``openai`` plugin so the picker UX is identical. # --------------------------------------------------------------------------- @@ -394,6 +412,11 @@ def _collect_image_b64( except httpx.HTTPStatusError as exc: exc.response.read() body = exc.response.text[:500] + if _is_image_generation_unsupported_error( + exc.response.status_code, + body, + ): + raise CodexImageGenerationUnsupportedError(body) from exc raise RuntimeError( f"Codex Responses API returned HTTP {exc.response.status_code}: {body}" ) from exc @@ -542,6 +565,19 @@ class OpenAICodexImageGenProvider(ImageGenProvider): quality=meta["quality"], input_images=input_images or None, ) + except CodexImageGenerationUnsupportedError: + logger.debug( + "Codex account does not expose image generation", + exc_info=True, + ) + return error_response( + error=_IMAGE_GENERATION_UNAVAILABLE_MESSAGE, + error_type="capability_unsupported", + provider="openai-codex", + model=tier_id, + prompt=prompt, + aspect_ratio=aspect, + ) except Exception as exc: logger.debug("Codex image generation failed", exc_info=True) return error_response( diff --git a/tests/plugins/image_gen/test_openai_codex_provider.py b/tests/plugins/image_gen/test_openai_codex_provider.py index dc8560c8b3c7..eaf8f09264b2 100644 --- a/tests/plugins/image_gen/test_openai_codex_provider.py +++ b/tests/plugins/image_gen/test_openai_codex_provider.py @@ -307,6 +307,47 @@ class TestGenerate: assert result["error_type"] == "api_error" assert "cloudflare 403" in result["error"] + def test_unsupported_image_tool_returns_capability_error(self, provider, monkeypatch): + monkeypatch.setattr(codex_plugin, "_read_codex_access_token", lambda: "codex-token") + + def _unsupported(*args, **kwargs): + raise codex_plugin.CodexImageGenerationUnsupportedError( + "Tool choice 'image_generation' not found in 'tools' parameter." + ) + + monkeypatch.setattr(codex_plugin, "_collect_image_b64", _unsupported) + + result = provider.generate("a cat") + + assert result["success"] is False + assert result["error_type"] == "capability_unsupported" + assert "current Codex account" in result["error"] + assert "OpenAI API key, FAL, or xAI" in result["error"] + + +class TestCapabilityErrorDetection: + @pytest.mark.parametrize( + "body", + [ + "Tool choice 'image_generation' not found in 'tools' parameter.", + '{"error":{"message":"Tool choice \'image_generation\' not found in \'tools\' parameter."}}', + ], + ) + def test_detects_exact_codex_image_tool_rejection(self, body): + assert codex_plugin._is_image_generation_unsupported_error(400, body) is True + + @pytest.mark.parametrize( + ("status_code", "body"), + [ + (401, "Tool choice 'image_generation' not found in 'tools' parameter."), + (400, "Tool choice 'web_search' not found in 'tools' parameter."), + (400, "The image_generation request was rejected by moderation."), + (500, "Tool choice 'image_generation' not found in 'tools' parameter."), + ], + ) + def test_does_not_misclassify_other_failures(self, status_code, body): + assert codex_plugin._is_image_generation_unsupported_error(status_code, body) is False + # ── Plugin entry point ──────────────────────────────────────────────────────