feat(google): enable Gemini inline image output#355
Conversation
- Add responseModalities [TEXT, IMAGE] to generationConfig for Gemini models - Parse inlineData parts in streaming and non-streaming responses - Materialize base64 images to ~/.opencodex/artifacts/ with absolute path output - Add unit tests for inline image parsing and materialization - Add user guide for Gemini image output
- Use getConfigDir() instead of hardcoded homedir (respects OPENCODEX_HOME) - Gate responseModalities behind thinkingConfig (thinking models reject IMAGE) - Add random suffix to prevent same-millisecond filename collision - Guard against empty base64 data - Isolate tests with temp OPENCODEX_HOME, add non-streaming parseResponse test - Broaden docs to cover AI Studio and note thinking model exception
This comment was marked as outdated.
This comment was marked as outdated.
CCA exposes a dedicated image generation model (gemini-3.1-flash-image) via FetchAvailableModels.imageGenerationModelIds. Adding it to the picker-visible model list enables users to generate images inline — the model returns inlineData parts which are materialized to disk.
When no OpenAI upstream is configured, fall back to Google Cloud Code
Assist's dedicated image model (gemini-3.1-flash-image). This enables
Codex's built-in image_gen skill to work with a CCA login alone —
the model call triggers a client-side POST to /v1/images/generations,
which is now served by CCA instead of returning a 400 error.
Returns the standard {created, data: [{b64_json}]} format that
codex-rs expects.
- Preserve responseModalities through compileGenerationConfig whitelist - Restrict CCA image fallback to generations endpoint only - Add timeout and error handling to CCA image fetch - Use full UUID for artifact filenames - Create artifacts with private permissions (0700/0600) - Fix Windows path assertions in tests - Escape special characters in markdown image paths - Update antigravity catalog test expectation
Dual review (security + bugs/edge cases)Bot review threads are not cleared yet (12 unresolved CodeRabbit/Codex comments). Below is an independent second look focused on malware/backdoor/XSS/auth, then functional edge cases. Security categories checkedNo evidence of malware, backdoor, hidden non-Google C2, local admission bypass, classic XSS/HTML sink, or credential logging. Persistence is under Blockers
Should-fix before merge
Aligns with open bot threadsThese match the still-open CodeRabbit/Codex items (compiler preservation, edits vs generations, timeout, path escaping, private file mode, base64 validation, collisions, Windows path assertions). Please resolve those threads after fixing, or reply why a finding does not apply. Suggested fix order
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/server/images.ts (2)
66-67: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReturn a prompt validation error instead of a configuration error.
For a missing or empty
prompt, this returnsundefined; lines 155-160 then incorrectly report that no provider is configured even when Antigravity is authenticated. Return a400 invalid_request_errorhere (and reject whitespace-only prompts).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/server/images.ts` around lines 66 - 67, Update prompt validation in the request handling flow around the prompt extraction to return a 400 invalid_request_error for missing, empty, or whitespace-only prompts instead of returning undefined. Preserve the existing valid prompt path and ensure the later provider-configuration error is only reached after prompt validation succeeds.
115-115: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winBound and safely map CCA response parsing failures.
upstream.json()has no size limit and rejects on malformed or non-object JSON. Unlike the standard relay at lines 229-235, a CCA response can therefore consume unbounded memory or escapehandleImagesas a server error. Buffer withIMAGES_RESPONSE_MAX_BYTES, then catch JSON/shape failures and return the existing502 upstream_errorcontract.As per path instructions,
src/**changes must not bypass shared routing/config layers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/server/images.ts` at line 115, Update the CCA response parsing in handleImages around upstream.json() to buffer the body with IMAGES_RESPONSE_MAX_BYTES, then safely parse and validate that the result is an object. Catch size, malformed JSON, and shape failures and return the existing 502 upstream_error contract, reusing the standard relay’s handling pattern without bypassing shared routing or configuration layers.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/images/gemini-inline.test.ts`:
- Line 91: Strengthen the Markdown assertions in the streaming and non-streaming
tests around the textEvents checks by using a temporary config path containing
spaces, parentheses, and Windows separators, then assert the exact emitted
Markdown path rather than a permissive regex. Add focused neighboring Bun
regression coverage for both response modes while preserving the existing
image-output expectations.
---
Outside diff comments:
In `@src/server/images.ts`:
- Around line 66-67: Update prompt validation in the request handling flow
around the prompt extraction to return a 400 invalid_request_error for missing,
empty, or whitespace-only prompts instead of returning undefined. Preserve the
existing valid prompt path and ensure the later provider-configuration error is
only reached after prompt validation succeeds.
- Line 115: Update the CCA response parsing in handleImages around
upstream.json() to buffer the body with IMAGES_RESPONSE_MAX_BYTES, then safely
parse and validate that the result is an object. Catch size, malformed JSON, and
shape failures and return the existing 502 upstream_error contract, reusing the
standard relay’s handling pattern without bypassing shared routing or
configuration layers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 723de6ee-65eb-4025-9b60-75db1d31b5ed
📒 Files selected for processing (6)
src/adapters/google-wire-compiler.tssrc/adapters/google.tssrc/images/artifacts.tssrc/server/images.tstests/google-antigravity-wire.test.tstests/images/gemini-inline.test.ts
Address remaining review findings (Wibias lidge-jun#4, CodeRabbit nitpick): - Cap decoded inline image data at 50 MiB to prevent unbounded disk writes - Add tests asserting exact markdown escaping for paths with spaces/parens
|
Thanks for the thorough dual review @Wibias! Status on each finding: Blockers (both fixed in b8743e0, before your review landed):
Should-fix:
All 11 tests pass, typecheck clean. |
Dual review (security + bugs/edge cases) — re-review @ 8069565Earlier High blockers from comment 5063121093 are fixed on this head. A fresh pass still finds merge blockers. Tone note: the previous soft “expectations satisfied” comment is superseded by this review. Security categories checkedNo malware, backdoor, hidden non-Google C2, local admission bypass, classic XSS/HTML sink, or credential logging found. Persistence is under Prior High blockers — status
Current blockers / should-fix
Residual (non-blocking)
What looks solid
VerdictNot merge-ready. Prior dual-review High blockers are cleared, but CI is red on |
|
Maintainer triage review (2026-07-24). Direction is valid, but this cannot merge in its current shape — rebuild on current dev is needed: Conflicts with terminal-truth rework. dev's google.ts parser was just rewritten (8668a2f8 / 0aed47b: fail-closed EOF, malformed-frame errors, finishReason/stopReason propagation). Modality scope too broad (High). Known test breaks (High). tests/google-hardening.test.ts:142 (no generationConfig for non-reasoning requests) and tests/provider-registry-parity.test.ts:604 (Antigravity model count 5 -> 6 without updating the pin) deterministically fail; required CI has not run on this head. Memory/streaming bounds (Medium). A single inline image is 50 MiB-capped but there's no per-response decoded-byte cap; base64 is fully buffered + JSON-parsed + Buffer-copied inside the stream parser; synchronous writeFileSync blocks the event loop per image. The CCA image endpoint calls upstream.json() unbounded, inconsistent with IMAGES_RESPONSE_MAX_BYTES elsewhere. Privacy (Medium). Absolute local paths (incl. server username / custom OPENCODEX_HOME) leak into downstream text; CCA error bodies are returned raw (200 chars) instead of via the Antigravity redaction helper. New OAuth token retrieval + Authorization forwarding makes this an explicit security-review item. Suggested path: rebuild on current dev with modality gating to image-capable models, inlineData folded into the new terminal-state parser, bounded decoded bytes + async writes, path/redaction hygiene, and the two test pins updated. Happy to re-review after a rebuild. |
Summary
/v1/images/generationsfallback (primary): When no OpenAI upstream is configured, Codex's built-inimage_genskill now falls back to CCA's dedicated image model (gemini-3.1-flash-image). This is the standard Codex workflow — the model calls the tool, codex-rs POSTs to the Images API, and the proxy serves it via CCA.inlineDataparsing: For users who selectgemini-3.1-flash-imagedirectly as their chat model,inlineDataparts in the response are materialized to~/.opencodex/artifacts/and emitted as markdown.responseModalities: ["TEXT", "IMAGE"]togenerationConfigfor non-thinking Gemini models.gemini-3.1-flash-imageto the CCA model catalog (fromFetchAvailableModels.imageGenerationModelIds).responseModalitieswhen a thinking level is active (thinking models reject IMAGE modality).How it works (Codex workflow)
No model switching required. Works with just a CCA login (
ocx login google-antigravity).Verification
bun run typecheck— cleanbun test tests/images/gemini-inline.test.ts— 8/8 passbun test tests/google-adapter.test.ts tests/google-vertex-stream.test.ts— 17/17 passbun run privacy:scan— passedPOST /v1/images/generations {"prompt":"A cute green frog on a lily pad"}→ 2MB JPEG returned in standard{created, data:[{b64_json}]}formatPOST /v1/chat/completionswith modelgemini-3.1-flash-image→markdown with materialized 515KB JPEGNote:
bun run test(full suite) has 24 pre-existing failures ondevin catalog/discovery tests, unrelated to this change.Checklist
Summary by CodeRabbit
gemini-3.1-flash-imageto the model catalog./v1/images/{generations,edits}and enabledresponseModalitiesfor image output.