fix(agent): catch ChatGPT-account Codex data-URL rejection so images are stripped instead of cascading to compression (#23602)

When the user's main provider is openai-codex on the ChatGPT-account
backend (https://chatgpt.com/backend-api/codex), sending a native image
attachment encodes it as data:image/...base64,... in the input_image
field. The OpenAI Responses API on the public endpoint accepts that, but
the ChatGPT-account variant rejects it with HTTP 400:

  Invalid 'input[N].content[K].image_url'. Expected a valid URL, but got
  a value with an invalid format.

Hermes' image-rejection phrase list didn't include this wording, so the
error escaped the strip-and-retry branch and fell through to the generic
recovery path: model fallback → context-too-large → compression cascade
→ auxiliary OpenRouter 402 spam (issue #23570).

Add a NARROW phrase keyed on the field-path apostrophe used by the Codex
Responses error format: "image_url'. expected". This matches the actual
error format without false-tripping on generic 'Expected a valid URL'
errors from unrelated tools (webhooks, redirect_uri, etc.). Once matched,
the existing branch strips images from history, sets _vision_supported=
False for the session, and retries text-only.

Refs #23570 (1 of 3 image-replay improvements; persistence rewrite to
store image PATHS instead of inlined base64 is a separate follow-up)
This commit is contained in:
Teknium 2026-05-11 07:37:22 -07:00 committed by GitHub
parent 3e7145e0bb
commit 7026af4e23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 39 additions and 0 deletions

View file

@ -13117,6 +13117,21 @@ class AIAgent:
"does not support multimodal", "does not support multimodal",
"does not support vision", "does not support vision",
"model does not support image", "model does not support image",
# ChatGPT-account Codex backend
# (https://chatgpt.com/backend-api/codex) rejects
# data:image/...base64 URLs in input_image fields
# with HTTP 400 "Invalid 'input[N].content[K].image_url'.
# Expected a valid URL, but got a value with an
# invalid format." The OpenAI Responses API on the
# public endpoint accepts data URLs, but the
# ChatGPT-account variant does not. Without this
# phrase the agent cascaded into compression /
# context-too-large recovery instead of just
# stripping the images. Match is narrow on
# purpose — keyed on the field-path apostrophe so
# we don't false-trip on other URL validation
# errors. (issue #23570)
"image_url'. expected",
) )
_err_lower = _err_body.lower() _err_lower = _err_body.lower()
_looks_like_image_rejection = any( _looks_like_image_rejection = any(

View file

@ -194,6 +194,7 @@ class TestImageRejectionPhraseIsolation:
"does not support multimodal", "does not support multimodal",
"does not support vision", "does not support vision",
"model does not support image", "model does not support image",
"image_url'. expected",
) )
def _matches(self, body: str) -> bool: def _matches(self, body: str) -> bool:
@ -238,6 +239,29 @@ class TestImageRejectionPhraseIsolation:
"This model does not support images", "This model does not support images",
"vision is not supported on this endpoint", "vision is not supported on this endpoint",
"model does not support image input", "model does not support image input",
# ChatGPT-account Codex backend (issue #23570) — rejects
# data:image/...base64 URLs in input_image fields. Without this
# match the agent cascaded into compression / context-too-large
# recovery instead of just stripping the images.
"Invalid 'input[56].content[1].image_url'. Expected a valid URL, but got a value with an invalid format.",
] ]
for body in bodies: for body in bodies:
assert self._matches(body) is True, f"false negative on: {body}" assert self._matches(body) is True, f"false negative on: {body}"
def test_codex_data_url_rejection_does_not_false_match_other_url_errors(self):
"""The narrow 'image_url'. expected' phrase (keyed on the
field-path apostrophe used in the Codex Responses error format)
must NOT trip on URL validation errors that aren't about
image_url specifically. See issue #23570 for the original error.
"""
bodies = [
# Generic URL validation errors — should NOT trip
"Invalid webhook_url. Must be a valid URL.",
"Expected a valid URL but got an empty string.",
"redirect_uri does not look like a valid URL.",
# An image_url error worded differently — also should not trip
# the narrow phrase (a separate phrase would be needed)
"image_url field cannot be empty",
]
for body in bodies:
assert self._matches(body) is False, f"false positive on: {body}"