feat: NanoBanana Pro 2 support (#320)#321
feat: NanoBanana Pro 2 support (#320)#321tombeckenham wants to merge 6 commits intoTanStack:mainfrom
Conversation
Route all gemini-* image models through generateContent instead of only gemini-3.1-flash-image-preview. Fix incorrect SDK property names (imageGenerationConfig→imageConfig, outputImageSize→imageSize), trim type values to match SDK, rename NanoBanana types to GeminiNativeImage, and bump @google/genai to ^1.43.0 for ImageConfig type support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mage models Add documentation for Gemini native image models (NanoBanana) that use the generateContent API, including size format (aspectRatio_resolution), model tables, and examples. Rename example app from ts-react-fal to ts-react-media to reflect multi-provider support. Add Gemini image models to the example app alongside fal.ai models. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds Gemini native image generation (generateContent) alongside existing Imagen generateImages path, introduces Gemini 3.1 models and native image sizing types, updates docs/examples to ts-react-media, and adapts examples and tests to support multi-provider image flows and new size formats. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client
participant Adapter as GeminiImageAdapter
participant SDK as `@google/genai`
Client->>Adapter: generateImage(model, options)
Adapter->>Adapter: isGeminiImageModel(model)?
alt Gemini native model
Adapter->>SDK: generateContent({ model, contents, imageConfig?, responseModalities: ['IMAGE'] })
SDK-->>Adapter: GenerateContentResponse (inlineData / image parts)
Adapter->>Client: ImageGenerationResult (map inlineData → images)
else Imagen model
Adapter->>SDK: generateImages({ model, prompt, size?, n })
SDK-->>Adapter: GenerateImagesResponse (url/base64 images)
Adapter->>Client: ImageGenerationResult (map images → result)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
View your CI Pipeline Execution ↗ for commit c75e958
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-anthropic
@tanstack/ai-client
@tanstack/ai-devtools-core
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-openrouter
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
packages/typescript/ai-gemini/src/image/image-provider-options.ts (2)
283-288:parseNativeImageSizedoes not validate resolution against allowed values.The regex
^(\d+:\d+)_(.+)$accepts any string after the underscore (e.g.,"16:9_invalid"would parse successfully). Consider validating that the resolution matchesGeminiNativeImageResolution('1K' | '2K' | '4K').🛡️ Suggested fix to validate resolution
+const VALID_NATIVE_RESOLUTIONS = ['1K', '2K', '4K'] as const + export function parseNativeImageSize( size: string, ): { aspectRatio: string; resolution: string } | undefined { - const match = size.match(/^(\d+:\d+)_(.+)$/) + const match = size.match(/^(\d+:\d+)_(1K|2K|4K)$/) if (!match) return undefined return { aspectRatio: match[1]!, resolution: match[2]! } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-gemini/src/image/image-provider-options.ts` around lines 283 - 288, parseNativeImageSize currently accepts any string after the underscore (regex /^(\d+:\d+)_(.+)$/) so inputs like "16:9_invalid" are treated as valid; update parseNativeImageSize to validate that the extracted resolution equals one of the allowed GeminiNativeImageResolution values ('1K' | '2K' | '4K') and return undefined for non-matching resolutions. Locate parseNativeImageSize and after extracting match[2] check it against the GeminiNativeImageResolution union (or a small set/array of allowed values) and only return { aspectRatio: match[1], resolution: match[2] } when it matches; otherwise return undefined.
177-182: DeriveGeminiNativeImageModelsfrom model metadata to prevent drift.
GeminiNativeImageModelsis a hardcoded string union type whileGeminiImageModelsis derived fromGEMINI_IMAGE_MODELSinmodel-meta.ts. If native image models are added or removed from the metadata, this type will not automatically update, leading to potential type mismatches inGeminiImageModelSizeByName. Consider extracting native models from the metadata structure (e.g., by filtering models withimage_generationcapability in their native implementation) to maintain synchronization.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-gemini/src/image/image-provider-options.ts` around lines 177 - 182, Replace the hardcoded GeminiNativeImageModels union with a derived type that filters GEMINI_IMAGE_MODELS for models whose native implementation supports image generation: import GEMINI_IMAGE_MODELS from model-meta.ts, compute the string literal union by filtering entries where model.native?.capabilities (or equivalent) includes "image_generation" (or checks native implementation), and export that inferred type to replace GeminiNativeImageModels; update any usages such as GeminiImageModelSizeByName to reference the new derived type so the set of native image models stays in sync with the metadata.docs/guides/image-generation.md (1)
175-185: Consider adding meaningfulmodelOptionsexample for Gemini native models.The section is titled "Gemini Native Model Options (NanoBanana)" but the code example only shows
sizewithout any actualmodelOptions. If native models support additional configuration, consider demonstrating it. Otherwise, clarify that native models primarily use thesizeparameter.📝 Suggested documentation improvement
### Gemini Native Model Options (NanoBanana) -Gemini native image models accept `GenerateContentConfig` options directly in `modelOptions`: +Gemini native image models primarily use the `size` parameter for configuration. Additional `GenerateContentConfig` options can be passed via `modelOptions` if needed: ```typescript const result = await generateImage({ adapter: geminiImage('gemini-3.1-flash-image-preview'), prompt: 'A beautiful garden', size: '16:9_4K', + // modelOptions are optional for native models })</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@docs/guides/image-generation.mdaround lines 175 - 185, The example under
"Gemini Native Model Options (NanoBanana)" shows only size and omits
modelOptions; update the generateImage example (the call to generateImage and
adapter geminiImage('gemini-3.1-flash-image-preview')) to either include a
representative modelOptions object showing supported GenerateContentConfig
fields for native Gemini models (e.g., any rate/quality/seed-like options the
model accepts) or add a brief clarifying sentence stating that native models
only use size and modelOptions is optional; reference generateImage,
geminiImage, modelOptions, size, and GenerateContentConfig when making the
change so readers can find and understand the example.</details> </blockquote></details> </blockquote></details> <details> <summary>🤖 Prompt for all review comments with AI agents</summary>Verify each finding against the current code and only fix it if needed.
Inline comments:
In@packages/typescript/ai-gemini/src/adapters/image.ts:
- Around line 111-140: generateWithGeminiApi currently ignores the
numberOfImages option so callers passing numberOfImages > 1 silently get one
image; update generateWithGeminiApi (and its JSDoc) to either validate and throw
when options.numberOfImages > 1 for Gemini native models, or map the intent to
the Gemini API by setting candidateCount when supported (e.g., pass
candidateCount = numberOfImages to this.client.models.generateContent), and
ensure any thrown error or limitation is described in the method JSDoc and
surfaced before calling this.client.models.generateContent; update
transformGeminiResponse handling if you choose candidateCount so it returns
multiple ImageGenerationResult entries accordingly.
Nitpick comments:
In@docs/guides/image-generation.md:
- Around line 175-185: The example under "Gemini Native Model Options
(NanoBanana)" shows only size and omits modelOptions; update the generateImage
example (the call to generateImage and adapter
geminiImage('gemini-3.1-flash-image-preview')) to either include a
representative modelOptions object showing supported GenerateContentConfig
fields for native Gemini models (e.g., any rate/quality/seed-like options the
model accepts) or add a brief clarifying sentence stating that native models
only use size and modelOptions is optional; reference generateImage,
geminiImage, modelOptions, size, and GenerateContentConfig when making the
change so readers can find and understand the example.In
@packages/typescript/ai-gemini/src/image/image-provider-options.ts:
- Around line 283-288: parseNativeImageSize currently accepts any string after
the underscore (regex /^(\d+:\d+)_(.+)$/) so inputs like "16:9_invalid" are
treated as valid; update parseNativeImageSize to validate that the extracted
resolution equals one of the allowed GeminiNativeImageResolution values ('1K' |
'2K' | '4K') and return undefined for non-matching resolutions. Locate
parseNativeImageSize and after extracting match[2] check it against the
GeminiNativeImageResolution union (or a small set/array of allowed values) and
only return { aspectRatio: match[1], resolution: match[2] } when it matches;
otherwise return undefined.- Around line 177-182: Replace the hardcoded GeminiNativeImageModels union with
a derived type that filters GEMINI_IMAGE_MODELS for models whose native
implementation supports image generation: import GEMINI_IMAGE_MODELS from
model-meta.ts, compute the string literal union by filtering entries where
model.native?.capabilities (or equivalent) includes "image_generation" (or
checks native implementation), and export that inferred type to replace
GeminiNativeImageModels; update any usages such as GeminiImageModelSizeByName to
reference the new derived type so the set of native image models stays in sync
with the metadata.</details> --- <details> <summary>ℹ️ Review info</summary> **Configuration used**: defaults **Review profile**: CHILL **Plan**: Pro <details> <summary>📥 Commits</summary> Reviewing files that changed from the base of the PR and between 808c32d5cedcd6fa1f80387f1d03d8fc4481d3e1 and 2eaa47c426c71b7dc2c2bf9df4e46b1a77f00b98. </details> <details> <summary>⛔ Files ignored due to path filters (1)</summary> * `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml` </details> <details> <summary>📒 Files selected for processing (23)</summary> * `docs/adapters/fal.md` * `docs/adapters/gemini.md` * `docs/guides/image-generation.md` * `examples/ts-react-media/.env.example` * `examples/ts-react-media/package.json` * `examples/ts-react-media/src/components/Header.tsx` * `examples/ts-react-media/src/components/ImageGenerator.tsx` * `examples/ts-react-media/src/components/VideoGenerator.tsx` * `examples/ts-react-media/src/lib/models.ts` * `examples/ts-react-media/src/lib/prompts.ts` * `examples/ts-react-media/src/lib/server-functions.ts` * `examples/ts-react-media/src/routeTree.gen.ts` * `examples/ts-react-media/src/router.tsx` * `examples/ts-react-media/src/routes/__root.tsx` * `examples/ts-react-media/src/routes/index.tsx` * `examples/ts-react-media/src/styles.css` * `examples/ts-react-media/tsconfig.json` * `examples/ts-react-media/vite.config.ts` * `packages/typescript/ai-gemini/package.json` * `packages/typescript/ai-gemini/src/adapters/image.ts` * `packages/typescript/ai-gemini/src/image/image-provider-options.ts` * `packages/typescript/ai-gemini/src/model-meta.ts` * `packages/typescript/ai-gemini/tests/image-adapter.test.ts` </details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
…te to the prompt. Its the only way to do it
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
packages/typescript/ai-gemini/src/adapters/image.ts (2)
42-55: Consider consistent naming in documentation.The documentation refers to "Nano Banana 2" but the PR objectives and changeset use "NanoBanana" (without space). Consider standardizing the naming for consistency.
📝 Suggested fix
* Tree-shakeable adapter for Gemini image generation functionality. * Supports Imagen 3/4 models (via generateImages API) and Gemini native -* image models like Nano Banana 2 (via generateContent API). +* image models like NanoBanana (via generateContent API). * * Features: * - Aspect ratio-based image sizing * - Person generation controls * - Safety filtering * - Watermark options -* - Extended resolution tiers (Nano Banana 2) +* - Extended resolution tiers (NanoBanana)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-gemini/src/adapters/image.ts` around lines 42 - 55, The doc comment in the Gemini image adapter inconsistently uses "Nano Banana 2" while the PR and changeset use "NanoBanana"; update the documentation string in the image adapter (the top comment block in image.ts referencing "Nano Banana 2" and any other occurrences like "Nano Banana") to the canonical "NanoBanana" spelling used in the PR, ensuring any model mentions (e.g., references to NanoBanana, NanoBanana 2 if versioned) and related phrases are consistently renamed across this file and adjacent docs/comments so names match the changeset.
81-105: Consider adding validation for Gemini native image sizes.The Imagen path validates sizes via
validateImageSize, but the Gemini path usesparseNativeImageSizewhich silently returnsundefinedfor invalid formats. This means invalid size strings like"invalid"or"1024x1024"(Imagen format) passed to a Gemini model will be silently ignored rather than producing an error.Consider adding validation for Gemini models to provide clearer feedback when an invalid size format is passed.
🛡️ Suggested validation approach
private async generateWithGeminiApi( options: ImageGenerationOptions<GeminiImageProviderOptions>, ): Promise<ImageGenerationResult> { const { model, prompt, size, numberOfImages, modelOptions } = options const parsedSize = size ? parseNativeImageSize(size) : undefined + + // Validate size format for Gemini native models + if (size && !parsedSize) { + throw new Error( + `Invalid size format "${size}" for Gemini model. Expected format: "aspectRatio_resolution" (e.g., "16:9_4K")` + ) + } // The generateContent API has no numberOfImages parameter.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-gemini/src/adapters/image.ts` around lines 81 - 105, The Gemini path in generateImages currently skips size validation (when isGeminiImageModel(model) is true) and parseNativeImageSize can return undefined for invalid formats; update generateImages to validate Gemini native image sizes before calling generateWithGeminiApi by calling parseNativeImageSize(options.size) and if it returns undefined (or an unsupported value) throw a descriptive validation error (or call a new helper like validateNativeImageSize) so invalid strings (e.g., "invalid" or Imagen-style "1024x1024") produce a clear error; modify generateImages (and/or add validateNativeImageSize) to perform this check right before returning generateWithGeminiApi.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/typescript/ai-gemini/src/adapters/image.ts`:
- Around line 157-161: The loop that pushes images uses part.inlineData.data
without guarding for undefined, causing b64Json to be set to undefined; update
the loop that iterates over parts (the variable parts and the part.inlineData
access) to only push when part.inlineData?.data is a non-empty string or buffer
(e.g., check typeof part.inlineData?.data === 'string' &&
part.inlineData.data.length > 0 or similar), and skip or handle cases where data
is missing so images.push({ b64Json: ... }) only occurs with valid data.
In `@packages/typescript/ai-gemini/tests/image-adapter.test.ts`:
- Line 286: Update the test description string for the test that currently reads
"calls generateContent without imageGenerationConfig when no size provided" to
use the renamed property name "imageConfig" instead of "imageGenerationConfig";
locate the it(...) block in image-adapter.test.ts that asserts generateContent
behavior (the test referencing generateContent) and change the human-readable
description to "calls generateContent without imageConfig when no size provided"
so the description matches the actual property name used in the code.
---
Nitpick comments:
In `@packages/typescript/ai-gemini/src/adapters/image.ts`:
- Around line 42-55: The doc comment in the Gemini image adapter inconsistently
uses "Nano Banana 2" while the PR and changeset use "NanoBanana"; update the
documentation string in the image adapter (the top comment block in image.ts
referencing "Nano Banana 2" and any other occurrences like "Nano Banana") to the
canonical "NanoBanana" spelling used in the PR, ensuring any model mentions
(e.g., references to NanoBanana, NanoBanana 2 if versioned) and related phrases
are consistently renamed across this file and adjacent docs/comments so names
match the changeset.
- Around line 81-105: The Gemini path in generateImages currently skips size
validation (when isGeminiImageModel(model) is true) and parseNativeImageSize can
return undefined for invalid formats; update generateImages to validate Gemini
native image sizes before calling generateWithGeminiApi by calling
parseNativeImageSize(options.size) and if it returns undefined (or an
unsupported value) throw a descriptive validation error (or call a new helper
like validateNativeImageSize) so invalid strings (e.g., "invalid" or
Imagen-style "1024x1024") produce a clear error; modify generateImages (and/or
add validateNativeImageSize) to perform this check right before returning
generateWithGeminiApi.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.changeset/five-parts-leave.mdpackages/typescript/ai-gemini/src/adapters/image.tspackages/typescript/ai-gemini/tests/image-adapter.test.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/typescript/ai-gemini/tests/image-adapter.test.ts (1)
229-517: Add explicit coverage forgemini-3.1-pro-previewin generateContent tests.Current tests exercise only
gemini-3.1-flash-image-preview. Since this PR adds support for all Gemini native image models (including pro), add a parameterized case so routing regressions are caught for both model IDs.Example parameterized test pattern
+ it.each([ + 'gemini-3.1-flash-image-preview', + 'gemini-3.1-pro-preview', + ] as const)('routes %s through generateContent', async (model) => { + const mockGenerateContent = vi.fn().mockResolvedValueOnce({ + candidates: [{ content: { parts: [{ inlineData: { mimeType: 'image/png', data: 'img' } }] } }], + }) + + const adapter = createGeminiImage(model, 'test-api-key') + ;(adapter as unknown as { client: { models: { generateContent: unknown } } }).client = { + models: { generateContent: mockGenerateContent }, + } + + await adapter.generateImages({ model, prompt: 'A test prompt' }) + expect(mockGenerateContent).toHaveBeenCalled() + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/typescript/ai-gemini/tests/image-adapter.test.ts` around lines 229 - 517, Add parameterized coverage for the pro image model by running the same generateContent tests for both 'gemini-3.1-flash-image-preview' and 'gemini-3.1-pro-preview': refactor the repeated test blocks in image-adapter.test.ts to iterate over an array of model IDs and for each call createGeminiImage(modelId, 'test-api-key'), inject the mockGenerateContent into adapter.client.models.generateContent, call adapter.generateImages(...) and assert the same expectations (mockGenerateContent call shape, result.model, result.images length and contents). Ensure tests that check prompt augmentation (numberOfImages cases), imageConfig presence/absence, and empty responses are all parameterized to include both model strings so routing for createGeminiImage and adapter.generateImages is verified for the pro model as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/typescript/ai-gemini/tests/image-adapter.test.ts`:
- Around line 229-517: Add parameterized coverage for the pro image model by
running the same generateContent tests for both 'gemini-3.1-flash-image-preview'
and 'gemini-3.1-pro-preview': refactor the repeated test blocks in
image-adapter.test.ts to iterate over an array of model IDs and for each call
createGeminiImage(modelId, 'test-api-key'), inject the mockGenerateContent into
adapter.client.models.generateContent, call adapter.generateImages(...) and
assert the same expectations (mockGenerateContent call shape, result.model,
result.images length and contents). Ensure tests that check prompt augmentation
(numberOfImages cases), imageConfig presence/absence, and empty responses are
all parameterized to include both model strings so routing for createGeminiImage
and adapter.generateImages is verified for the pro model as well.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/typescript/ai-gemini/src/adapters/image.tspackages/typescript/ai-gemini/tests/image-adapter.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/typescript/ai-gemini/src/adapters/image.ts
Closes #320
Summary
Adds support for Google's NanoBanana (Gemini native image generation) models, including the new Gemini 3.1 Pro model.
NanoBanana Native Image Generation
gemini-*image models through thegenerateContentAPI instead of onlygemini-3.1-flash-image-previewimageGenerationConfig→imageConfig,outputImageSize→imageSize)NanoBananatypes toGeminiNativeImagefor clarity@google/genaito^1.43.0forImageConfigtype supportaspectRatio_resolution) distinct from Imagen sizingGemini 3.1 Pro Model
gemini-3.1-pro-previewmodel metadata (1M input tokens, 64K output tokens, pricing)GEMINI_MODELSlist and type maps (provider options, input modalities)Documentation & Examples
ts-react-faltots-react-mediato reflect multi-provider supportTest plan
pnpm test:typespasses with new model typespnpm test:libpasses for ai-gemini packagepnpm test:buildsucceeds🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Examples
Tests
Chores