Skip to content

feat(google): enable Gemini inline image output - #355

Merged
Wibias merged 12 commits into
lidge-jun:devfrom
tizerluo:feat/gemini-inline-image
Jul 27, 2026
Merged

feat(google): enable Gemini inline image output#355
Wibias merged 12 commits into
lidge-jun:devfrom
tizerluo:feat/gemini-inline-image

Conversation

@tizerluo

@tizerluo tizerluo commented Jul 23, 2026

Copy link
Copy Markdown

Summary

  • Enable Gemini image generation through Cloud Code Assist (Antigravity) with two complementary paths:
    1. /v1/images/generations fallback (primary): When no OpenAI upstream is configured, Codex's built-in image_gen skill 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.
    2. Inline inlineData parsing: For users who select gemini-3.1-flash-image directly as their chat model, inlineData parts in the response are materialized to ~/.opencodex/artifacts/ and emitted as markdown.
  • Add responseModalities: ["TEXT", "IMAGE"] to generationConfig for non-thinking Gemini models.
  • Add gemini-3.1-flash-image to the CCA model catalog (from FetchAvailableModels.imageGenerationModelIds).
  • Automatically omit responseModalities when a thinking level is active (thinking models reject IMAGE modality).

How it works (Codex workflow)

User: "draw me a cat"
  → Chat model calls image_gen.imagegen tool
  → codex-rs POSTs /v1/images/generations {prompt: "a cat", model: "gpt-image-1"}
  → OpenCodex: no OpenAI upstream? → CCA fallback
  → Calls gemini-3.1-flash-image via v1internal:generateContent
  → Returns {created, data: [{b64_json: "..."}]}
  → codex-rs renders the image

No model switching required. Works with just a CCA login (ocx login google-antigravity).

Verification

  • bun run typecheck — clean
  • bun test tests/images/gemini-inline.test.ts — 8/8 pass
  • bun test tests/google-adapter.test.ts tests/google-vertex-stream.test.ts — 17/17 pass
  • bun run privacy:scan — passed
  • E2E (Images API): POST /v1/images/generations {"prompt":"A cute green frog on a lily pad"} → 2MB JPEG returned in standard {created, data:[{b64_json}]} format
  • E2E (inline): POST /v1/chat/completions with model gemini-3.1-flash-image![image](/abs/path.jpg) markdown with materialized 515KB JPEG

Note: bun run test (full suite) has 24 pre-existing failures on dev in catalog/discovery tests, unrelated to this change.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Summary by CodeRabbit

  • New Features
    • Added support for Gemini image-capable models, including the new gemini-3.1-flash-image model.
    • Inline images are saved as local artifacts and displayed through Markdown image links.
    • Added Google Antigravity fallback support for image generation requests.
  • Bug Fixes
    • Added validation and size limits for generated image data.
    • Improved Markdown path escaping for image links.
  • Documentation
    • Documented native Gemini image generation, artifact handling, and image size limits.

@github-actions github-actions Bot added the enhancement New feature or request label Jul 23, 2026
@coderabbitai

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as duplicate.

chatgpt-codex-connector[bot]

This comment was marked as duplicate.

@tizerluo
tizerluo marked this pull request as draft July 23, 2026 19:15
@tizerluo
tizerluo marked this pull request as ready for review July 23, 2026 19:28
chatgpt-codex-connector[bot]

This comment was marked as duplicate.

coderabbitai[bot]

This comment was marked as duplicate.

@Wibias
Wibias marked this pull request as draft July 23, 2026 20:31
@Wibias

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@tizerluo

This comment was marked as outdated.

@Wibias

This comment was marked as outdated.

@lidge-jun

This comment was marked as outdated.

lidge-jun added a commit that referenced this pull request Jul 24, 2026
@Ingwannu

This comment was marked as outdated.

@tizerluo
tizerluo force-pushed the feat/gemini-inline-image branch from 8069565 to b106701 Compare July 24, 2026 09:00
@tizerluo

This comment was marked as outdated.

@tizerluo
tizerluo force-pushed the feat/gemini-inline-image branch 2 times, most recently from 230ba5f to d665a3b Compare July 24, 2026 16:12
@tizerluo
tizerluo marked this pull request as ready for review July 24, 2026 16:12
coderabbitai[bot]

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

@lidge-jun

This comment was marked as outdated.

@lidge-jun

This comment was marked as outdated.

tizerluo pushed a commit to tizerluo/opencodex that referenced this pull request Jul 27, 2026
R5 findings fixed:

2. OAuth preflight signal propagation: wrap getValidAccessToken in
   abortableRace against linkedSignal.signal so client abort / timeout
   during token refresh surfaces as 499/504 instead of hanging.

5. HTTP-layer payload limit: add Content-Length pre-check in
   parseResponse before response.json() buffers the body.

Codex P2-1: Reject malformed CCA inlineData.data (non-string types
   were forwarded as fake b64_json).

Codex P2-2: Fix duplicate 'neither' bullet in JA/KO/RU/ZH locale docs.

Codex P2-3: Exempt gemini-*-image chat models from routed catalog
   media-generation filter (shouldExposeRoutedModel).

Codex P2-4: Map CCA safety blocks (finishReason SAFETY/BLOCKLIST/etc)
   to non-retryable 400 instead of retried 502.

Finding 3 (file: URL local-only) addressed in review response as a
design positioning decision, not a code bug.
@tizerluo
tizerluo force-pushed the feat/gemini-inline-image branch from 9602aef to df6a745 Compare July 27, 2026 15:48
@tizerluo
tizerluo marked this pull request as ready for review July 27, 2026 15:48
@tizerluo

Copy link
Copy Markdown
Author

R5 follow-up — all blockers addressed (df6a7458)

Rebased onto latest dev. Typecheck, all image/server/catalog test suites, and privacy scan pass.

Finding 2 — OAuth preflight signal ✅

Wrapped getValidAccessToken("google-antigravity") in an abortableRace() against linkedSignal.signal. When the deadline fires or the client aborts during token refresh, our code stops awaiting the OAuth call immediately and surfaces 499 (client abort) or 504 (timeout) — checked parent signal first, linked signal second, matching the body-read pattern.

Regression tests: Two new tests mock the OAuth token endpoint to hang indefinitely, then verify both client abort (→ 499) and deadline expiry (→ 504) during OAuth preflight return the correct status within 5 seconds.

Finding 5 — HTTP-layer payload limit ✅

Added a Content-Length pre-check in parseResponse() before response.json() buffers the body. If the header indicates a body larger than 100 MiB, an error event is returned without buffering. Streaming path already has per-chunk protection via MAX_ENCODED_BYTES_PER_IMAGE on each inlineData.data — added a comment documenting this.

Finding 3 — file: URL local-only (design response)

opencodex is fundamentally a local proxy — the image bridge runs on 127.0.0.1 by default, and the Codex client executes on the same machine. The file: URI is the correct mechanism for this architecture: it gives the local client zero-copy access to the artifact on disk. For the edge case of remote/container bindings, the artifact host is the proxy machine itself; a full HTTP artifact route is a valid enhancement but introduces scope (opaque IDs, auth, traversal safety, lifecycle) that doesn't belong in this PR. We document the local-first design assumption in the adapter reference. If maintainers want remote-client artifact serving, we'll track it as a separate follow-up.

Codex P2-1 — Malformed inlineData.data

Changed the truthiness check to typeof part.inlineData?.data === "string" && part.inlineData.data.length > 0. Non-string types (numbers, objects) are now silently skipped; if no valid images remain, the existing empty-images guard returns 502.

Regression tests: Two tests covering all-malformed (→ 502) and mixed malformed+valid (→ 200 with only valid data).

Codex P2-2 — Locale docs duplication ✅

Fixed the duplicate "neither" bullet in JA/KO/RU/ZH translations. Each locale now has exactly one complete "neither" bullet after the CCA fallback bullet, matching the English reference.

Codex P2-3 — Catalog filter exemption ✅

gemini-3.1-flash-image was being filtered out by shouldExposeRoutedModel because isMediaGenerationModelId matched the image suffix. Added isGeminiImageChatModel() check that exempts Gemini image-capable chat models (IDs containing both gemini and image) from the media-generation filter. This generalizes the previous hardcoded cursor/gemini-3-pro-image-preview special case.

Regression tests: 4 tests verifying gemini-*-image models are exposed while true media-gen models (dall-e, sora, veo, flux) are still filtered.

Codex P2-4 — CCA safety blocks ✅

Added CCA_BLOCKING_FINISH_REASONS set (SAFETY, BLOCKLIST, PROHIBITED_CONTENT, SPII). When the CCA response has promptFeedback.blockReason or a candidate finishReason matching these, the handler returns 400 (invalid_request_error) instead of 502 — safety blocks are permanent for the same prompt, so codex must not retry.

Regression tests: 5 tests covering each blocking reason + a regression confirming STOP with valid image still returns 200.


Requesting re-review at df6a7458.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

@Wibias
Wibias marked this pull request as draft July 27, 2026 16:09

@Wibias Wibias left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review @ df6a745

Verdict: request changes. Tip closes most earlier Wibias/R5 code blockers and the 14:07 Codex batch. It is still not merge-ready. Remaining work below must ship in this PR before merge; optional follow-ups are not accepted.

Fixed on tip (do not reopen)

Area Status
Registry destination pin + sink-host regression Fixed
CCA Images fallback documented (EN + locales) + CCA-after-OpenAI-auth-fail Fixed
Empty/whitespace prompt → 400 before OAuth Fixed
materializeInlineImage try/catch → structured error Fixed
Exclusive wx create + UUID collision retry Fixed
Full PNG 8-byte magic; throw on unrecognized Fixed
Artifact retention/prune Fixed
pathToFileURL for local file: syntax Fixed (local only — remote still open below)
ANTIGRAVITY_REQUEST_UA, linked timeout finally, body-read 499/504 parent-first Fixed
OAuth login → 401 vs refresh → 502; deadline + abortableRace around preflight Fixed
CCA envelope Array.isArray(parts) + non-object skip Fixed
Safety finish reasons → non-retryable 400 (partial — missing RECITATION) Mostly fixed
Non-string inlineData.data not forwarded as b64_json Partial — type check only; base64/magic still missing
Locale duplicate “neither” bullets Fixed
Keep Gemini image chat in catalog Partial — exemption too broad
Rebase vs dev Fixed — ahead 8 / behind 0
Draft cleared Fixed

Keep registry pin, OAuth redaction, timeouts, private artifact permissions, and retention unchanged while fixing the items below.

Remaining blockers (required in this PR)

  1. Admission bearer must allow CCA-only Images requestssrc/server/images.ts
    validateForwardAdmissionCredential() runs before the CCA-only branch and rejects valid Authorization: Bearer <OPENCODEX_API_AUTH_TOKEN> for non-loopback clients. CCA never forwards that bearer; it uses stored Google OAuth. Bypass/strip the admission check when selecting CCA. Never send the proxy admission bearer upstream.

  2. Reject n !== 1 for CCA unless real multi-image support existssrc/server/images.ts
    CCA envelope ignores n and can succeed with fewer images than requested. Reject unsupported n before OAuth or generation (or implement proper multi-image and enforce returned count).

  3. Treat RECITATION as a permanent CCA content-filtersrc/server/images.ts
    Add RECITATION to CCA_BLOCKING_FINISH_REASONS. Google adapter already maps it to content_filter; omitting it here turns paid refusals into retried 502s.

  4. Strictly validate every CCA inlineData.data before b64_jsonsrc/server/images.ts
    Require bounded base64 and a supported image magic signature before returning success. Non-string was tightened; invalid base64 / fake bytes can still become a “successful” Images payload that fails in Codex.

  5. Replace broad gemini + image heuristics with explicit allowlistssrc/codex/catalog/parsing.ts + src/adapters/google.ts
    Current /gemini/i && /image/i resurrects standalone media-gen IDs (e.g. gemini-3-pro-image) into the picker / responseModalities path. Allowlist known chat-capable provider/model pairs only; keep media-generation IDs filtered.

  6. Replace proxy-local file: links with an authenticated artifact HTTP routesrc/adapters/google.ts (+ server route)
    pathToFileURL is fine for same-host clients; remote/container clients cannot open host filesystem paths and paths should not leak. Serve artifacts via an authenticated, traversal-safe HTTP route using opaque IDs for both local and remote clients.

  7. Enforce Gemini response / SSE-frame size limits before full buffering or JSON parsesrc/adapters/google.ts
    Encoded per-image check and non-stream Content-Length help, but missing/lying Content-Length still fully buffers via response.json(), and a single oversized SSE frame is still parsed first. Cap response/frame size before buffering/parsing.

Focused regressions (keep minimal)

  • Admission-bearer CCA-only request succeeds; bearer never appears upstream
  • n > 1 rejected (or multi-image correct) before OAuth
  • RECITATION → non-retryable content-filter, not 502
  • Malformed base64 + fake image bytes → sanitized failure, not b64_json
  • Negative catalog cases: media-gen IDs stay filtered; allowlisted chat image models stay exposed
  • Remote client can fetch artifact via opaque authenticated route (no server path in markdown)
  • Oversized non-stream / chunked SSE frame rejected before full parse

Do not expand into a large test matrix beyond these behaviors.

Process gates

Gate Status
Base branch dev (correct)
Divergence vs dev OK — ahead 8 / behind 0
Cross-platform CI on exact tip Still open — only enforce-target / label / CodeRabbit
Maintainer security review (OAuth/CCA + new artifact route) Still required
Unresolved review threads Still open — resolve/reply after tip matches

After fixes

Land blockers 1–7 + the focused regressions above, run exact-head Cross-platform CI green, then ping for maintainer security review.

Your Name and others added 9 commits July 28, 2026 00:09
Rebuilt on current dev (terminal-truth parser rework).

- Parse inlineData parts in both streaming and non-streaming Google
  adapter paths, materializing images to OPENCODEX_HOME/artifacts with
  async writes, per-image (50 MiB) and per-response (100 MiB) decoded
  byte budgets, 0o700/0o600 permissions, and full-UUID filenames.
- Restrict responseModalities=["TEXT","IMAGE"] to explicit image-capable
  models (gemini-3.1-flash-image class) — non-image Gemini, Vertex,
  Claude/GPT-on-Antigravity, and thinking models are unaffected.
- Whitelist responseModalities in compileGenerationConfig so the setting
  survives CCA/Vertex wire compilation.
- Add gemini-3.1-flash-image to the Antigravity model catalog (wire IDs,
  picker, context windows).
- CCA /v1/images/generations fallback: gated to generations only, uses
  signalWithTimeout, caps response via arrayBuffer (IMAGES_RESPONSE_MAX_BYTES),
  redacts errors via safeAntigravityHttpErrorMessage.
- Markdown paths escape spaces/parentheses; tests cover special-char dirs.
- Update model-count pins in provider-registry-parity and antigravity-wire.
Replace lidge-jun#355's standalone artifacts.ts with lidge-jun#424's fuller version (adds
downloadImageToArtifact, SSRF protection, HTTPS enforcement). Also bring
in destination-policy.ts exports needed by the module. Removes lidge-jun#355's
duplicate guessExtFromMagic — lidge-jun#424's version is the canonical one.
- Rewrite CCA tests to intercept registry host (not local URL) + sink-host regression
- Strip home directory from artifact paths in google adapter markdown output
- Add try/catch around materializeInlineImage to prevent stream abort on bad parts
- Reject empty/whitespace prompts with 400 before OAuth token refresh
- Document CCA /v1/images/generations fallback in codex-integration guide (+ locales)
- Replace hardcoded artifact path in adapters docs with platform-agnostic wording
- Add exclusive create (flag: wx) to artifact writes
- guessExtFromMagic throws on unrecognized format (no silent png fallback)
- Emit file: URI instead of ~/ for artifact paths (resolvable + privacy)
- Remove imagen-4.0-generate-001 from IMAGE_CAPABLE_MODELS (wrong API)
- Try CCA fallback when OpenAI candidate exists but auth fails
- Return 502 on OAuth refresh failure (not misleading 400 'none configured')
- Reuse ANTIGRAVITY_REQUEST_UA instead of hard-coded UA
- Wrap fetch+body-read in try/finally for linkedSignal.cleanup()
- Catch body-read timeout/abort → 504/499
- Preserve CCA 4xx statuses instead of collapsing to 502
- Add UUID collision retry on exclusive-create (flag: wx)
- pruneOldArtifacts() caps artifacts/ to 200 files, deleting oldest by mtime
- Runs after each successful write in materializeInlineImage + downloadImageToArtifact
- Best-effort: errors caught and warned, never fails the write
- Use pathToFileURL for standard file: URIs (Windows + RFC8089 compliant)
- Validate CCA envelope: Array.isArray(parts) + per-part object check
- Map OAuthLoginRequiredError → 401 (login required), not 502 (refresh failed)
- Fix body-read timeout: check linkedSignal.signal.aborted before parent signal
- Remove Imagen from adapters.md docs
- Document CCA fallback also fires after OpenAI auth failure (+ locales)
- Document artifacts emitted as file: URIs
R4 findings fixed:

1. Body-read client cancellation now reports 499 (not 504):
   signalWithTimeout propagates parent abort into the linked signal,
   so both are aborted on client cancel. Check parent signal first
   in the body-read catch block.

2. OAuth preflight now runs inside the image deadline:
   Move signalWithTimeout creation before getValidAccessToken so token
   refresh and project discovery are bounded by timeoutMs and can be
   cancelled by client abort. Add 499/504 checks after OAuth preflight.

4. PNG magic bytes: validate complete 8-byte signature
   (89 50 4E 47 0D 0A 1A 0A) instead of just the first 4 bytes.

5. Inline payload size pre-check: reject oversized base64 strings
   in the adapter before normalization copies them, preventing large
   allocations before the decoded-size guard runs.

Finding 3 (file: URL for remote clients) is a design discussion,
not a code bug — addressed in PR review response.
R5 findings fixed:

2. OAuth preflight signal propagation: wrap getValidAccessToken in
   abortableRace against linkedSignal.signal so client abort / timeout
   during token refresh surfaces as 499/504 instead of hanging.

5. HTTP-layer payload limit: add Content-Length pre-check in
   parseResponse before response.json() buffers the body.

Codex P2-1: Reject malformed CCA inlineData.data (non-string types
   were forwarded as fake b64_json).

Codex P2-2: Fix duplicate 'neither' bullet in JA/KO/RU/ZH locale docs.

Codex P2-3: Exempt gemini-*-image chat models from routed catalog
   media-generation filter (shouldExposeRoutedModel).

Codex P2-4: Map CCA safety blocks (finishReason SAFETY/BLOCKLIST/etc)
   to non-retryable 400 instead of retried 502.

Finding 3 (file: URL local-only) addressed in review response as a
design positioning decision, not a code bug.
… takeover

Address remaining Wibias blockers: admission bearer allows CCA/keyed without
forwarding the secret, CCA n=1 + RECITATION + validated base64, explicit image
model allowlists, authenticated opaque artifact HTTP route, and size caps with
focused regressions.
@Wibias
Wibias force-pushed the feat/gemini-inline-image branch from df6a745 to 168a516 Compare July 27, 2026 22:24
@Wibias
Wibias marked this pull request as ready for review July 27, 2026 22:24
@Wibias

Wibias commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Maintainer takeover

Rebased onto latest dev and applied the remaining Wibias R5 blockers plus CodeRabbit/Codex hardening:

  • Admission bearer authenticates the caller for CCA/keyed image paths without being forwarded upstream
  • CCA rejects n !== 1, treats RECITATION as blocking, and validates base64 + image magic before b64_json
  • Explicit Gemini image-capable allowlists (catalog + Google adapter) instead of broad /gemini/ && /image/ matching
  • Markdown emits authenticated opaque /v1/opencodex/artifacts/<id> URLs (no file: / host paths)
  • Pre-parse size caps for non-stream bodies and SSE frames
  • Focused regressions for the above

Waiting on fresh CI + bot review before squash-merge. Author @tizerluo will be credited on merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

buffer += decoder.decode(value, { stream: true });

P2 Badge Cap SSE frames before waiting for a newline

When a compromised or stalled Google upstream sends one very large data: frame without a newline, this append grows buffer without limit because MAX_SSE_FRAME_BYTES is checked only later in handleDataLine(), after split("\n") produces a complete line. Fresh evidence after the earlier buffering finding is that the new frame-size check remains unreachable for an unterminated frame; enforce the cap while accumulating bytes/chars and add a no-newline regression.

AGENTS.md reference: AGENTS.md:L93-L95

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/images/artifacts.ts
if (!ARTIFACT_ID_RE.test(name)) {
throw new Error("artifact filename is not a valid opaque id");
}
return `${ARTIFACT_HTTP_PREFIX}/${name}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include the proxy origin in artifact links

When Codex CLI/App receives this Markdown outside a document served by the proxy, the root-relative /v1/opencodex/artifacts/... URL resolves against the client's own UI/current context rather than the OpenCodex server, so the newly added HTTP route still cannot render the generated image, especially for remote or containerized proxies. Fresh evidence beyond the earlier remote-client comment is that the replacement now emits only a path with no scheme or authority; construct a client-resolvable URL using the proxy's externally reachable origin.

Useful? React with 👍 / 👎.

Comment on lines +83 to +87
`gemini-2.0-flash-preview-image-generation`, or any model id matching both `gemini` and `image`),
the adapter sends `responseModalities: ["TEXT", "IMAGE"]`. Returned `inlineData` parts are
materialized to the `artifacts/` subdirectory of the configured OpenCodex config directory and
surfaced to the client as a markdown image link referencing a `file:` URI
(`![image](file:///...)`). Each image is capped at 50 MB and each response at 100 MB of decoded

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Synchronize inline-image docs with the runtime

The documented behavior no longer matches this change: the adapter now uses an exact three-model allowlist rather than accepting any ID containing gemini and image, and emitted links now target /v1/opencodex/artifacts/... rather than a file: URI. Users following this reference may select unsupported models and will troubleshoot the wrong artifact mechanism; update the model list and link description to match the implementation.

AGENTS.md reference: AGENTS.md:L96-L97

Useful? React with 👍 / 👎.

Comment thread src/images/artifacts.ts
export function readArtifactBytes(id: string): { bytes: Buffer; contentType: string } | null {
const path = resolveArtifactPath(id);
if (!path) return null;
const bytes = readFileSync(path);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stream artifact files instead of synchronously copying them

When a client retrieves an artifact near the supported 50 MiB limit, readFileSync() blocks Bun's JavaScript event loop while loading the entire file, and the route then copies that buffer again with new Uint8Array(artifact.bytes). A few concurrent image views can therefore retain roughly twice the artifact size per request and stall unrelated proxy traffic; serve the file through an asynchronous or streaming body such as Bun.file instead of materializing two in-memory copies.

Useful? React with 👍 / 👎.

Comment thread src/server/images.ts
}
return formatErrorResponse(502, "upstream_error", "CCA image generation failed: OAuth token refresh failed");
}
const project = getOAuthCredentialProjectId("google-antigravity");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve the OAuth token and project from one account snapshot

When the active Google Antigravity account is switched while token refresh is in progress, getValidAccessToken() can return account A's token and this later lookup can reload account B's project ID. The request then combines credentials from different accounts, producing authorization failures or charging/selecting the wrong project when account A can access it; extend the account-scoped access snapshot to carry the matching project ID and use both values atomically.

AGENTS.md reference: AGENTS.md:L83-L89

Useful? React with 👍 / 👎.

Comment thread src/adapters/google.ts
Comment on lines +593 to +594
if (Number.isFinite(contentLength) && contentLength > MAX_RESPONSE_BYTES) {
return [{ type: "error", message: `google response too large (content-length ${contentLength} exceeds ${MAX_RESPONSE_BYTES} bytes)` }];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cancel oversized non-streaming responses on the header path

When Google declares a Content-Length above the cap, this early return leaves response.body uncancelled. A large or indefinitely streaming upstream body can therefore keep its fetch connection and buffers alive until garbage collection even though the client has already received an adapter error, allowing repeated oversized responses to exhaust upstream connection slots; explicitly cancel the body before returning and add a cancellation regression for this header-only rejection path.

AGENTS.md reference: AGENTS.md:L93-L95

Useful? React with 👍 / 👎.

Wibias added 2 commits July 28, 2026 00:37
Include gemini-3.1-flash-image in the OAuth preset migration assertion so
Cross-platform CI matches the Antigravity picker seed.
When Antigravity is logged in but has no Cloud Code Assist project id,
surface a project-discovery 400 instead of falling through to the generic
provider-not-configured message.
Cancel oversized non-stream bodies on the Content-Length path, cap SSE
accumulation before a newline, stream artifacts via Bun.file, and sync the
adapters reference docs with the explicit allowlist and opaque HTTP route.

@Wibias Wibias left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainer review @ 379cb3c9

Verdict: approve.

All Wibias R5 blockers are addressed on tip (rebased onto latest dev):

  1. Admission bearer allows CCA/keyed Images without forwarding the proxy secret
  2. CCA rejects n !== 1
  3. RECITATION is a non-retryable content filter
  4. Strict base64 + magic validation before CCA b64_json
  5. Explicit Gemini image-capable allowlists (catalog + adapter); media-gen IDs stay filtered
  6. Authenticated opaque /v1/opencodex/artifacts/<id> route (no file: / host paths)
  7. Pre-parse size caps for non-stream bodies + SSE accumulation/frames

Tip also includes focused regressions, docs sync, Bun.file artifact streaming, Content-Length cancel, and a clear CCA project-discovery error.

Security review

No medium/high/critical findings in the changed image/artifact/auth paths. Admission non-forwarding, artifact path containment, base64/magic validation, and allowlist gating look solid.

Process

  • Cross-platform CI green on exact tip
  • CodeRabbit completed; tip Codex P2s from the takeover batch addressed
  • Remaining Codex notes (absolute artifact origin, OAuth snapshot atomicity) are non-blocking follow-ups

Ready to squash-merge with credit to @tizerluo.

@Wibias
Wibias merged commit 65e3fee into lidge-jun:dev Jul 27, 2026
14 of 15 checks passed
@Wibias

Wibias commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Merged — thank you @tizerluo!

Huge thanks for this PR. Gemini inline image output is a meaningful capability for Codex users, and you carried it through a long review loop with a ton of care (CCA Images fallback, artifacts, catalog wiring, and many hardening passes). That work is genuinely appreciated.

Squash-merged to dev as 65e3fee1 with you credited as the commit author (plus Co-authored-by). The history will keep your name on this feature.

Welcome contribution — looking forward to more from you if you feel like it.

Wibias added a commit to Wibias/opencodex that referenced this pull request Jul 27, 2026
Reconcile with lidge-jun#355 Gemini inline artifact helpers: keep DNS-pinned downloads
and HTTP artifact serving APIs in a shared artifacts module.
Wibias added a commit to Wibias/opencodex that referenced this pull request Jul 27, 2026
…akes

These suites measured 5.6–7.5s on windows-latest under the default 5s harness
budget after the lidge-jun#355 merge; give them the same 20s ceiling used elsewhere.
Wibias added a commit that referenced this pull request Jul 27, 2026
Credit: @tizerluo for the original feature on #424.
Folds stacked #528 hardening and maintainer review/security follow-ups.
Reconciled with #355 artifact helpers on latest dev.

Closes #424
Closes #528
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants