fix(image-gen): classify unsupported Codex image accounts

This commit is contained in:
Teknium 2026-07-12 22:38:41 -07:00
parent 8a5f8379ed
commit 402969670d
2 changed files with 77 additions and 0 deletions

View file

@ -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(

View file

@ -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 ──────────────────────────────────────────────────────