diff --git a/.changeset/huge-lizards-admire.md b/.changeset/huge-lizards-admire.md
new file mode 100644
index 000000000..977c6dd68
--- /dev/null
+++ b/.changeset/huge-lizards-admire.md
@@ -0,0 +1,8 @@
+---
+'@tanstack/ai-gemini': minor
+'@tanstack/ai-client': minor
+'@tanstack/ai-react': minor
+'@tanstack/ai': minor
+---
+
+Added Gemini Realtime Adapter
diff --git a/docs/media/realtime-chat.md b/docs/media/realtime-chat.md
index c800d57d6..8516f89c0 100644
--- a/docs/media/realtime-chat.md
+++ b/docs/media/realtime-chat.md
@@ -187,7 +187,7 @@ import { useRealtimeChat } from '@tanstack/ai-react'
import { openaiRealtime } from '@tanstack/ai-openai'
import { getToken } from './token'
-const { startListening, stopListening, vadMode, setVADMode } = useRealtimeChat({
+const { startListening, stopListening, updateSession } = useRealtimeChat({
getToken,
adapter: openaiRealtime(),
vadMode: 'manual', // or 'server' or 'semantic'
@@ -202,10 +202,10 @@ With `manual` VAD mode, use push-to-talk style interactions:
```
-You can switch VAD mode at runtime without reconnecting:
+You can switch VAD mode at runtime without reconnecting, via `updateSession`:
```typescript ignore
-setVADMode('semantic')
+updateSession({ vadMode: 'semantic' })
```
For semantic VAD, configure eagerness to control how long the model waits before deciding you've finished speaking:
diff --git a/examples/ts-react-chat/src/lib/use-realtime.ts b/examples/ts-react-chat/src/lib/use-realtime.ts
index c32dd5031..44874ef0d 100644
--- a/examples/ts-react-chat/src/lib/use-realtime.ts
+++ b/examples/ts-react-chat/src/lib/use-realtime.ts
@@ -6,10 +6,11 @@ import {
elevenlabsRealtime,
elevenlabsRealtimeToken,
} from '@tanstack/ai-elevenlabs'
+import { geminiRealtime, geminiRealtimeToken } from '@tanstack/ai-gemini'
import { grokRealtime, grokRealtimeToken } from '@tanstack/ai-grok'
import { realtimeClientTools } from '@/lib/realtime-tools'
-type Provider = 'openai' | 'elevenlabs' | 'grok'
+type Provider = 'openai' | 'elevenlabs' | 'gemini' | 'grok'
const getRealtimeTokenFn = createServerFn({ method: 'POST' })
.inputValidator((data: { provider: Provider; language?: string }) => {
@@ -25,6 +26,12 @@ const getRealtimeTokenFn = createServerFn({ method: 'POST' })
})
}
+ if (data.provider === 'gemini') {
+ return realtimeToken({
+ adapter: geminiRealtimeToken(),
+ })
+ }
+
if (data.provider === 'elevenlabs') {
return realtimeToken({
adapter: elevenlabsRealtimeToken({
@@ -48,6 +55,8 @@ function adapterForProvider(provider: Provider) {
return openaiRealtime()
case 'elevenlabs':
return elevenlabsRealtime()
+ case 'gemini':
+ return geminiRealtime()
case 'grok':
return grokRealtime()
}
@@ -91,7 +100,9 @@ Keep your responses concise and conversational since this is a voice interface.
When using tools, briefly explain what you're doing and then share the results naturally.
If the user sends an image, describe what you see and answer any questions about it.
Be friendly and engaging!`,
- voice: voice ?? (provider === 'grok' ? 'eve' : 'alloy'),
+ voice:
+ voice ??
+ (provider === 'grok' ? 'eve' : provider === 'gemini' ? 'Puck' : 'alloy'),
tools: realtimeClientTools,
outputModalities,
temperature,
diff --git a/examples/ts-react-chat/src/routes/realtime.tsx b/examples/ts-react-chat/src/routes/realtime.tsx
index 5f75fd3e7..d20ebe2fa 100644
--- a/examples/ts-react-chat/src/routes/realtime.tsx
+++ b/examples/ts-react-chat/src/routes/realtime.tsx
@@ -13,12 +13,13 @@ import {
import { AudioSparkline } from '@/components/AudioSparkline'
import { useRealtime } from '@/lib/use-realtime'
-type Provider = 'openai' | 'elevenlabs' | 'grok'
+type Provider = 'openai' | 'elevenlabs' | 'gemini' | 'grok'
type OutputMode = 'audio+text' | 'text-only' | 'audio-only'
const PROVIDER_OPTIONS: Array<{ value: Provider; label: string }> = [
{ value: 'openai', label: 'OpenAI Realtime' },
{ value: 'elevenlabs', label: 'ElevenLabs' },
+ { value: 'gemini', label: 'Google Gemini' },
{ value: 'grok', label: 'Grok Voice Agent' },
]
@@ -287,7 +288,9 @@ function RealtimePage() {
)}
{/* Output mode selector (OpenAI-compatible realtime) */}
- {(provider === 'openai' || provider === 'grok') && (
+ {(provider === 'openai' ||
+ provider === 'gemini' ||
+ provider === 'grok') && (