The eligibility gate in grokChatResponsesBridgeEligibility rejected any
structured content (arrays), including standard OpenAI image_url parts.
This forced image-bearing requests to fall back to raw Chat Completions
forwarding, where non-Composer models have no image bridge — silently
dropping the images or causing upstream errors.
Changes:
- Allow content arrays containing only text and image_url parts to pass
eligibility, so ChatCompletionsToResponses can convert them to
input_image parts that Grok vision models natively support.
- Force image-bearing requests to use Responses even when no prompt
cache identity is available, since the raw path cannot forward images
for non-Composer models.
- Add grokChatStructuredContentBridgeable helper with conservative
validation: unknown part types (e.g. input_audio) still fall back.
- Update tests: image_url content is now bridgeable; add cases for
text+image, text-only arrays, unknown parts, and empty arrays.
Add an opt-in first semantic output budget for native HTTP Responses, including response-header wait. Keep preamble and keepalive bytes non-semantic so a stalled account can fail over once without replaying its response IDs. Defaults remain disabled.
Related to #4201, #4185, and #4248. Complements the HTTP/2 dead-connection fix in #4207.
Adversarial re-verification found one more divergence from the
double-conversion chain: an upstream 200 with empty choices (or a nil
response) left stop_reason as an empty string, while the old chain
reports end_turn. Derive the fallback from the content blocks — the
guard never fires when choices exist, since every finish_reason maps to
a non-empty stop_reason.
Also strengthen the equivalence tests: compare tool_use Content[].Input
and tool_call Function.Arguments across bridges, and add an
empty-choices stop_reason parity case.
Adversarial review of the direct bridge found divergences from the
double-conversion chain it replaces; all are now aligned and covered by
tests that fail against the previous implementation:
- flush argument fragments buffered before a deferred tool announcement,
and announce name-less tools at finalize, so no tool arguments are lost
when upstreams stream arguments before the name
- fold text-only user array content into a single string; parts form only
when an image requires it (strict chat upstreams reject array content)
- drop tool_choice pointing at undeclared tools and unknown choice types
- treat cache_write_tokens/cache_creation_tokens as alternate spellings
(prefer write), not additive
- generate a response id when the upstream omits one
- derive stop_reason from blocks for content_filter/unknown finish reasons
- emit input_json_delta "{}" when a tool block closes without argument
deltas