Skip to content

fix(ai-gemini, ai-openai): align image-input URL handling — passthrough where possible, error-by-default where an upload is required#908

Merged
AlemTuzlak merged 2 commits into
mainfrom
907-fixai-gemini-align-creategeminiimage-url-handling-with-chat-stop-fetching-arbitrary-https-urls
Jul 7, 2026
Merged

fix(ai-gemini, ai-openai): align image-input URL handling — passthrough where possible, error-by-default where an upload is required#908
AlemTuzlak merged 2 commits into
mainfrom
907-fixai-gemini-align-creategeminiimage-url-handling-with-chat-stop-fetching-arbitrary-https-urls

Conversation

@tombeckenham

@tombeckenham tombeckenham commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🎯 Changes

Aligns how the Gemini and OpenAI media adapters handle type: 'url' image inputs, so none of them silently fetch + base64-buffer arbitrary HTTPS URLs (an OOM hazard on Workers/edge). Two complementary parts:

1. Gemini native image — passthrough (fixes #907)

createGeminiImage() previously fetched arbitrary https:// image inputs locally and re-encoded them as base64 inlineData, unlike createGeminiChat(). imagePartToGeminiPart now passes all URL sources (public HTTPS, Files API URIs, gs://) through as fileData.fileUri — Gemini fetches server-side, nothing is buffered locally. data sources still map to inlineData.

2. Paths that can't passthrough — error by default + allowUrlFetch opt-in

Three paths have no URL passthrough because the provider requires uploaded bytes: Gemini Veo (imagePartToVeoImage), OpenAI image edits (imagePartToFile), and OpenAI Sora input_reference. For an HTTP(S) URL these must download + buffer the image in memory. They now throw by default with an actionable message instead of silently buffering. data: URIs (and gs:// for Veo) still work with no flag. Opt back in with allowUrlFetch: true:

createOpenaiImage('gpt-image-1', apiKey, { allowUrlFetch: true })
createOpenaiVideo('sora-2', apiKey, { allowUrlFetch: true })
createGeminiVideo('veo-3.1-generate-preview', apiKey, { allowUrlFetch: true })

Threaded through GeminiVideoConfig, OpenAIImageConfig, OpenAIVideoConfig. (Bedrock already errors on URL image sources — this brings these three onto the same stance.)

Also updated

  • Unit tests: Gemini image passthrough (dropped the old stubGlobal('fetch') hack); error-by-default + allowUrlFetch opt-in + data:-URI-still-works cases for Veo, OpenAI edits, and Sora.
  • Docs: media/image-generation.md and the media-generation agent skill document passthrough vs. allowUrlFetch.
  • Changesets: patch (Gemini image passthrough) + minor (URL-input error-by-default, a breaking default change).

Gemini's generateContent image mapping stays unit-test covered (aimock doesn't mock that endpoint); image E2E specs pass unchanged.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Prevented local downloading/inlining of external HTTP(S) image URLs for image generation, improving reliability and reducing memory use on constrained runtimes.
    • Added safer default behavior that rejects URL-based image inputs for certain flows unless explicitly opted in.
  • New Features
    • Introduced an allowUrlFetch option to enable remote URL fetching/buffering when desired (Gemini video, and OpenAI image/video).
  • Documentation
    • Updated image-generation guidance to clarify server-side URL handling for Gemini, provider limitations, and default throwing behavior.

…miniImage

URL sources in multimodal image-generation prompts now pass through as
fileData.fileUri (Gemini fetches them server-side), matching the chat
adapter, instead of being fetched locally and inlined as base64 — which
double-buffered the image and could OOM on memory-constrained runtimes
such as Cloudflare Workers.

Veo keeps its fetch: the predict API's Image type only accepts
imageBytes or gcsUri, so there is no URI passthrough to align with.

Fixes #907

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

2 package(s) bumped directly, 0 bumped as dependents.

🟨 Minor bumps

Package Version Reason
@tanstack/ai-gemini 0.19.1 → 0.20.0 Changeset
@tanstack/ai-openai 0.16.0 → 0.17.0 Changeset

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates Gemini image generation to pass URL inputs through to Gemini instead of fetching them locally, adds an opt-in fetch flag for Gemini video and OpenAI image/video flows, and updates tests, docs, and changesets to match the new URL handling.

Changes

Gemini image URL passthrough fix

Layer / File(s) Summary
Synchronous URL passthrough
packages/ai-gemini/src/adapters/image.ts
buildContents becomes synchronous and URL image parts now return fileData with fileUri and mimeType instead of fetched base64 inlineData.
Image adapter tests
packages/ai-gemini/tests/image-adapter.test.ts
Tests now assert URL passthrough as fileData and preserve explicit mimeType values.
Docs and changeset
docs/media/image-generation.md, docs/config.json, .changeset/gemini-image-url-passthrough.md
Docs and release metadata describe the new Gemini URL passthrough behavior and timestamp update.

Gemini video URL fetch opt-in

Layer / File(s) Summary
Video URL guard and wiring
packages/ai-gemini/src/adapters/video.ts
GeminiVideoConfig adds allowUrlFetch, URL image parts throw by default when fetching is disabled, and routed image parts receive the flag.
Video adapter tests
packages/ai-gemini/tests/video-adapter.test.ts
Tests cover default rejection and the enabled fetch path for URL-based video inputs.

OpenAI URL fetch opt-in

Layer / File(s) Summary
OpenAI image edit handling
packages/ai-openai/src/adapters/image.ts
OpenAIImageConfig adds allowUrlFetch, the adapter stores it separately, and editImages forwards it into imagePartToFile.
OpenAI video routing and file conversion
packages/ai-openai/src/adapters/video.ts, packages/ai-openai/src/image/image-input-to-file.ts
OpenAIVideoConfig adds allowUrlFetch, the adapter passes it into imagePartToFile, and URL inputs now throw unless fetching is enabled.
OpenAI tests, skill note, and changeset
packages/ai-openai/tests/*, packages/ai/skills/ai-core/media-generation/SKILL.md, .changeset/gemini-openai-url-input-error-by-default.md
Tests cover default rejection and enabled fetch paths, the skill note documents the behavior, and the changeset records the release note.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

  • TanStack/ai#624 — It touched packages/ai-gemini/src/adapters/image.ts and the multimodal image serialization path that this PR now changes for URL inputs.

Suggested reviewers: AlemTuzlak

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The OpenAI image/video URL opt-in and docs/test updates are beyond linked issue #907, which only asked to fix Gemini image passthrough. If intended, link the missing issue(s) or split the OpenAI/Gemini Veo URL-handling work into a separate PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The Gemini image adapter now passes HTTPS URLs through as fileData.fileUri, preserves data URIs, and updates tests, matching #907.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title accurately summarizes the main URL-handling change across Gemini and OpenAI adapters.
Description check ✅ Passed The description follows the template and includes changes, checklist items, and release impact details.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 907-fixai-gemini-align-creategeminiimage-url-handling-with-chat-stop-fetching-arbitrary-https-urls

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Jul 7, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit d3661bb

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 11s View ↗

☁️ Nx Cloud last updated this comment at 2026-07-07 09:41:52 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jul 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@908

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@908

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@908

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@908

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@908

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@908

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@908

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@908

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@908

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@908

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@908

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@908

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@908

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@908

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@908

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@908

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@908

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@908

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@908

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@908

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@908

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@908

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@908

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@908

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@908

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@908

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@908

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@908

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@908

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@908

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@908

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@908

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@908

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@908

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@908

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@908

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@908

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@908

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@908

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@908

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@908

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@908

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@908

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@908

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@908

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@908

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@908

commit: d3661bb

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/ai-gemini/tests/image-adapter.test.ts (1)

802-835: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that URL passthrough does not call fetch.

This test verifies fileData, but the regression objective is also “without fetching.” Add a local fetch spy that throws or assert it was not called so future changes cannot reintroduce local buffering while preserving the same payload shape.

🧪 Proposed test hardening
     it('passes arbitrary HTTPS URL sources through as fileData without fetching (`#907`)', async () => {
+      const fetchSpy = vi.spyOn(globalThis, 'fetch')
+
       // Matches the chat adapter: Gemini fetches URL inputs server-side.
       // Fetching locally and inlining as base64 OOMs on memory-constrained
       // runtimes (e.g. Cloudflare Workers).
       const { adapter, mockGenerateContent } = mockedNativeAdapter()
@@
       expect(args.contents).toEqual([
@@
       ])
+      expect(fetchSpy).not.toHaveBeenCalled()
     })
🤖 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 `@packages/ai-gemini/tests/image-adapter.test.ts` around lines 802 - 835, The
`generateImage` URL passthrough test only asserts the `fileData` payload and
does not verify that no local fetching occurs. Update the `passes arbitrary
HTTPS URL sources through as fileData without fetching` case in
`image-adapter.test.ts` to spy on `fetch` (or mock it to fail) and assert it is
never called while keeping the existing
`mockedNativeAdapter`/`mockGenerateContent` payload expectations intact. This
will harden the regression coverage around the no-fetch behavior for HTTPS image
sources.
🤖 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.

Nitpick comments:
In `@packages/ai-gemini/tests/image-adapter.test.ts`:
- Around line 802-835: The `generateImage` URL passthrough test only asserts the
`fileData` payload and does not verify that no local fetching occurs. Update the
`passes arbitrary HTTPS URL sources through as fileData without fetching` case
in `image-adapter.test.ts` to spy on `fetch` (or mock it to fail) and assert it
is never called while keeping the existing
`mockedNativeAdapter`/`mockGenerateContent` payload expectations intact. This
will harden the regression coverage around the no-fetch behavior for HTTPS image
sources.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19d5fce4-c23e-4ce7-b9a5-6715be28a3e8

📥 Commits

Reviewing files that changed from the base of the PR and between e3de949 and 3199b11.

📒 Files selected for processing (6)
  • .changeset/gemini-image-url-passthrough.md
  • docs/config.json
  • docs/media/image-generation.md
  • packages/ai-gemini/src/adapters/image.ts
  • packages/ai-gemini/src/adapters/video.ts
  • packages/ai-gemini/tests/image-adapter.test.ts

…uire upload

Veo (createGeminiVideo), OpenAI image edits (createOpenaiImage), and OpenAI
Sora input_reference (createOpenaiVideo) have no URL passthrough — the provider
only accepts uploaded bytes (or gs:// for Veo). Previously an HTTP(S) URL image
input was silently fetched and buffered in memory, which can OOM constrained
runtimes (e.g. Cloudflare Workers).

These paths now throw by default on an HTTP(S) URL image input, with an error
pointing to the alternatives. data: URIs (and gs:// for Veo) still work with no
flag. Opt back into fetching + buffering via allowUrlFetch: true on the adapter
config. Threaded through GeminiVideoConfig, OpenAIImageConfig, and
OpenAIVideoConfig.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@tombeckenham tombeckenham changed the title fix(ai-gemini): align createGeminiImage URL handling with chat — stop fetching arbitrary HTTPS URLs fix(ai-gemini, ai-openai): align image-input URL handling — passthrough where possible, error-by-default where an upload is required Jul 7, 2026
@tombeckenham tombeckenham requested a review from AlemTuzlak July 7, 2026 09:41
@AlemTuzlak AlemTuzlak merged commit dcc7407 into main Jul 7, 2026
10 checks passed
@AlemTuzlak AlemTuzlak deleted the 907-fixai-gemini-align-creategeminiimage-url-handling-with-chat-stop-fetching-arbitrary-https-urls branch July 7, 2026 18:41
@github-actions github-actions Bot mentioned this pull request Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(ai-gemini): align createGeminiImage URL handling with chat — stop fetching arbitrary HTTPS URLs

2 participants