Skip to main content

ai-core-media-generation

Image, audio, video, speech (TTS), and transcription generation using activity-specific adapters: generateImage() with openaiImage/geminiImage/byteplusImage, generateAudio() with geminiAudio/falAudio, generateVideo() with async polling (openaiVideo/geminiVideo/grokVideo/falVideo/byteplusVideo/openRouterVideo, per-model typed durations), generateSpeech() with openaiSpeech/byteplusSpeech/elevenlabsSpeech, generateTranscription() with openaiTranscription/byteplusTranscription, generateVoice() with elevenlabsVoiceDesign (create a voice, then speak with it). React hooks: useGenerateImage, useGenerateAudio, useGenerateSpeech, useTranscription, useGenerateVideo. TanStack Start server function integration with toServerSentEventsResponse.

Source facts

Repository
TanStack/ai
Last source activity
October 8, 2026 at 14:47
Detected SKILL.md language
English
Stars
3,199
Forks
366

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
ai-core/media-generation
description
Image, audio, video, speech (TTS), and transcription generation using activity-specific adapters: generateImage() with openaiImage/geminiImage/byteplusImage, generateAudio() with geminiAudio/falAudio, generateVideo() with async polling (openaiVideo/geminiVideo/grokVideo/falVideo/byteplusVideo/openRouterVideo, per-model typed durations), generateSpeech() with openaiSpeech/byteplusSpeech/elevenlabsSpeech, generateTranscription() with openaiTranscription/byteplusTranscription, generateVoice() with elevenlabsVoiceDesign (create a voice, then speak with it). React hooks: useGenerateImage, useGenerateAudio, useGenerateSpeech, useTranscription, useGenerateVideo. TanStack Start server function integration with toServerSentEventsResponse.
type
sub-skill
library
tanstack-ai
library_version
0.42.0
sources
["TanStack/ai:docs/media/generations.md","TanStack/ai:docs/media/generation-hooks.md","TanStack/ai:docs/media/image-generation.md","TanStack/ai:docs/media/audio-generation.md","TanStack/ai:docs/media/video-generation.md","TanStack/ai:docs/media/text-to-speech.md","TanStack/ai:docs/adapters/elevenlabs.md","TanStack/ai:docs/media/voice-creation.md","TanStack/ai:docs/media/transcription.md","TanStack/ai:docs/advanced/debug-logging.md"]
# Media Generation > **Dependency note:** This skill builds on ai-core. Read it first for critical rules. All media activities (image, speech, transcription, video) follow the same server/client architecture: a `generate*()` function on the server, an SSE transport via `toServerSentEventsResponse()`, and a framework hook on the client. ## Setup -- Image Generation End-to-End ### Server (API route or TanStack Start server function) ```typescript // routes/api/generate/image.ts import { generateImage, toServerSentEventsResponse } from '@tanstack/ai' import { openaiImage } from '@tanstack/ai-openai' export async function POST(req: Request) { const { prompt, size, numberOfImages } = await req.json() const stream = generateImage({ adapter: openaiImage('gpt-image-1'), prompt, size, numberOfImages, stream: true, }) return toServerSentEventsResponse(stream) } ``` ### Client (React) ```tsx import { useGenerateImage, fetchServerSentEvents } from '@tanstack/ai-react' import { useState } from 'react' function ImageGenerator() { const [prompt, setPrompt] = useState('') const { generate, result, isLoading, error, reset } = useGenerateImage({ connection: fetchServerSentEvents('/api/generate/image'), }) return ( <div> <input value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="Describe an image..." /> <button onClick={() => generate({ prompt })} disabled={isLoading || !prompt.trim()} > {isLoading ? 'Generating...' : 'Generate'} </button> {error && <p>Error: {error.message}</p>} {result?.images.map((img, i) => ( <img key={i} src={img.url || `data:image/png;base64,${img.b64Json}`} alt={img.revisedPrompt || 'Generated image'} /> ))} {result && <button onClick={reset}>Clear</button>} </div> ) } ``` ### TanStack Start: Server Function Streaming (recommended) When using TanStack Start, return `toServerSentEventsResponse()` from a server function. The client fetcher receives a `Response` and the hook parses it as SSE automatically: ```typescript // lib/server-functions.ts import { createServerFn } from '@tanstack/react-start' import { generateImage, toServerSentEventsResponse } from '@tanstack/ai' import { openaiImage } from '@tanstack/ai-openai' import type { OpenAIImageModel } from '@tanstack/ai-openai' export const generateImageStreamFn = createServerFn({ method: 'POST' }) .inputValidator((data: { prompt: string; model?: OpenAIImageModel }) => data) .handler(({ data }) => { return toServerSentEventsResponse( generateImage({ adapter: openaiImage(data.model ?? 'gpt-image-1'), prompt: data.prompt, stream: true, }), ) }) ``` ```tsx import { useGenerateImage } from '@tanstack/ai-react' import { generateImageStreamFn } from '../lib/server-functions' function ImageGenerator() { const { generate, result, isLoading } = useGenerateImage({ fetcher: (input) => generateImageStreamFn({ data: input }), }) return ( <button onClick={() => generate({ prompt: 'A sunset over mountains' })} disabled={isLoading} > {isLoading ? 'Generating...' : 'Generate'} </button> ) } ``` --- ## Core Patterns ### 1. Image Generation Supported adapters: `openaiImage` (dall-e-2, dall-e-3, gpt-image-1, gpt-image-1-mini, gpt-image-2), `geminiImage` (gemini-3.1-flash-image, gemini-3.1-flash-lite-image, gemini-nano-banana-2.1, gemini-3-pro-image, imagen-4.0-generate-001, etc.) and `byteplusImage` (Seedream — `seedream-4-0-250828`, `seedream-4-5-251128`, the 5.0 family). > **Use the GA Gemini image ids.** `gemini-3.1-flash-image-preview` and > `gemini-3-pro-image-preview` were shut down on 2026-06-25 and now 404. They > survive in the type union only as deprecated aliases so existing code keeps > compiling — a call to them typechecks and then fails at runtime. Use > `gemini-3.1-flash-image` / `gemini-3-pro-image` instead. > **Seedream quirks:** `watermark` defaults to **`true`** (pass > `modelOptions: { watermark: false }` for a clean image), `size` is a token > (`'1K'` | `'2K'` | `'4K'`) **or** explicit `'2048x2048'` pixels but never a > mix, and `numberOfImages` is an **upper bound** — Seedream has no `n`, so it > maps onto group-image mode and the model may return fewer. Reads > `ARK_API_KEY`. ```typescript import { generateImage } from '@tanstack/ai' import { openaiImage } from '@tanstack/ai-openai' import { geminiImage } from '@tanstack/ai-gemini' // OpenAI with quality/background options const openaiResult = await generateImage({ adapter: openaiImage('gpt-image-1'), prompt: 'A cat wearing a hat', size: '1024x1024', numberOfImages: 2, modelOptions: { quality: 'high', background: 'transparent', output_format: 'png', }, }) // Gemini native model with aspect-ratio sizes const geminiResult = await generateImage({ adapter: geminiImage('gemini-3.1-flash-image'), prompt: 'A futuristic cityscape at night', size: '16:9_4K', }) // Edit that image. geminiResult.id is the interaction id. // Send only the new prompt. The previous image stays on the server. const edited = await generateImage({ adapter: geminiImage('gemini-3.1-flash-image'), prompt: 'Make the sky orange', modelOptions: { previous_interaction_id: geminiResult.id }, }) // Gemini Imagen model. Text-to-image only. No interaction id to chain. const imagenResult = await generateImage({ adapter: geminiImage('imagen-4.0-generate-001'), prompt: 'A landscape photo', modelOptions: { aspectRatio: '16:9' }, }) ``` Gemini-native image models (`gemini-3.1-flash-image`, `gemini-3.1-flash-lite-image`, `gemini-nano-banana-2.1`, `gemini-3-pro-image`, `gemini-2.5-flash-image`) use the Interactions API. `result.id` is the interaction id. Omit `modelOptions.store` to keep the API default (`true`). `store: false` cannot be chained. Gemini keeps a stored interaction for 1 day on the free tier and 55 days on a paid tier. Set `thinkingConfig.thinkingLevel` to `'minimal'`, `'low'`, `'medium'`, or `'high'`. `thinkingConfig.thinkingBudget` throws. Imagen stays on `generateImages`. Result shape: `ImageGenerationResult` with `images` array where each entry has `b64Json?`, `url?`, and `revisedPrompt?`. OpenAI image URLs expire after 1 hour -- download or display immediately. #### Image-conditioned generation: multimodal `prompt` parts Both `generateImage()` and `generateVideo()` accept the `prompt` either as a plain string or as an ordered array of content parts (`TextPart` / `ImagePart` / `VideoPart` / `AudioPart` — the same shapes used elsewhere in TanStack AI). Part order is meaningful: natively multimodal providers (Gemini, OpenRouter) receive parts in order; named-field providers (OpenAI, fal, xAI) extract media parts and flatten the text. Prompt text is always sent verbatim — to reference inputs from the prompt, write the provider's own syntax (fal `@Image1`, OpenAI "image 1" prose); the SDK never injects or rewrites markers. Each media part may carry an optional `metadata.role` hint that adapters use to route the part to the provider-specific field. The accepted part types are narrowed per model at compile time via the adapter's input-modality map. ```typescript import { generateImage } from '@tanstack/ai' import { openaiImage } from '@tanstack/ai-openai' // Image-to-image (OpenAI gpt-image-2 / gpt-image-1, dall-e-2) await generateImage({ adapter: openaiImage('gpt-image-2'), prompt: [ { type: 'text', content: 'Turn this into a cinematic product photo' }, { type: 'image', source: { type: 'url', value: 'https://…/product.png' } }, ], }) // Multi-reference (up to 16 for gpt-image models; up to ~14 for Gemini native // — a provider limit, not enforced by the SDK) await generateImage({ adapter: openaiImage('gpt-image-2'), prompt: [ { type: 'text', content: 'Apply the second image as style to the first' }, { type: 'image', source: { type: 'url', value: 'https://…/product.png' } }, { type: 'image', source: { type: 'url', value: 'https://…/style.png' } }, ], }) // Inpaint via metadata.role === 'mask' (OpenAI gpt-image models, dall-e-2; fal mask_url) await generateImage({ adapter: openaiImage('gpt-image-2'), prompt: [ { type: 'text', content: 'Replace the masked region with a tree' }, { type: 'image', source: { type: 'url', value: 'https://…/photo.png' } }, { type: 'image', source: { type: 'url', value: 'https://…/mask.png' }, metadata: { role: 'mask' }, }, ], }) // Image-to-video (OpenAI Sora: single input_reference; fal: image_url + optional // end_image_url; OpenRouter: frame_images + input_references) import { generateVideo } from '@tanstack/ai' import { falVideo } from '@tanstack/ai-fal' await generateVideo({ adapter: falVideo('fal-ai/kling-video/v3/pro/image-to-video'), prompt: [ { type: 'image', source: { type: 'url', value: 'https://…/first.png' } }, { type: 'text', content: 'Slow cinematic push-in' }, { type: 'image', source: { type: 'url', value: 'https://…/last.png' }, metadata: { role: 'end_frame' }, }, ], }) ``` Reference images / start frames that are reused (or arrive as inline base64 on memory-constrained runtimes) can instead be uploaded once via the provider's Files adapter and referenced with `source: fileSourceFromHandle(handle)` — supported for Gemini image generation (`geminiFiles()`) and fal image/video inputs (`falFiles()`). Endpoints that require raw bytes (OpenAI `images/edits`, Sora `input_reference`, Gemini Veo) reject file sources with a clear error. See `ai-core/adapter-configuration/SKILL.md` §7. **URL inputs that require an upload throw by default.** Most adapters pass a `type: 'url'` source straight through to the provider. Three paths can't — OpenAI `images.edit()`, OpenAI Sora `input_reference`, and Gemini **Veo** — because the provider only accepts uploaded bytes (Veo also takes a `gs://` reference). For those, an HTTP(S) URL would have to be downloaded and buffered in memory, which can OOM constrained runtimes, so they **throw** on an HTTP(S) URL image input by default. Pass a `data:` URI (or `gs://` for Veo), or opt in with `allowUrlFetch: true` on the adapter config (`createOpenaiImage(model, apiKey, { allowUrlFetch: true })`, and likewise on `createOpenaiVideo` / `createGeminiVideo`). `data:` URIs never need the flag. **Role hints** (`metadata.role`): | Role | Maps to | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `'reference'` | fal `reference_image_urls`; OpenRouter video `input_references[]`; Gemini multimodal part; positional otherwise |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub