Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/huge-lizards-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/ai-gemini': minor
---

Added Gemini Realtime Adapter
8 changes: 5 additions & 3 deletions packages/typescript/ai-gemini/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@
"adapter"
],
"dependencies": {
"@google/genai": "^1.43.0"
"@google/genai": "^1.46.0"
},
"peerDependencies": {
"@tanstack/ai": "workspace:^"
"@tanstack/ai": "workspace:^",
"@tanstack/ai-client": "workspace:^"
},
"devDependencies": {
"@tanstack/ai": "workspace:*",
"@tanstack/ai-client": "workspace:*",
"@vitest/coverage-v8": "4.0.14",
"vite": "^7.2.7"
"vite": "^7.3.1"
}
}
52 changes: 52 additions & 0 deletions packages/typescript/ai-gemini/src/realtime/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type {
AnyClientTool,
RealtimeEvent,
RealtimeEventHandler,
RealtimeToken,
} from '@tanstack/ai'
import type { RealtimeAdapter, RealtimeConnection } from '@tanstack/ai-client'
import type { GeminiRealtimeOptions } from './types'

/**
* Creates a Gemini realtime adapter for client-side use.
*
* @param options - Optional configuration
* @returns A RealtimeAdapter for use with RealtimeClient
*
* @example
* ```typescript
* import { RealtimeClient } from '@tanstack/ai-client'
* import { geminiRealtime } from '@tanstack/ai-gemini'
*
* const client = new RealtimeClient({
* getToken: () => fetch('/api/realtime-token').then(r => r.json()),
* adapter: geminiRealtime(),
* })
* ```
*/
export function geminiRealtime(
options: GeminiRealtimeOptions = {},
): RealtimeAdapter {
return {
provider: 'gemini',

connect(
token: RealtimeToken,
_clientTools?: ReadonlyArray<AnyClientTool>,
): Promise<RealtimeConnection> {
return createWebSocketConnection(token)
},
}
}

/**
* Creates a WebSocket connection to Gemini's realtime API
*/
function createWebSocketConnection(
token: RealtimeToken,
): Promise<RealtimeConnection> {
const model = token.config.model ?? 'gemini-live-2.5-flash-native-audio'
const eventHandlers = new Map<RealtimeEvent, Set<RealtimeEventHandler<any>>>()

return new Promise((resolve, reject) => {})
}
12 changes: 12 additions & 0 deletions packages/typescript/ai-gemini/src/realtime/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Token adapter for server-side use
export { geminiRealtimeToken } from './token'

// Client adapter for browser use
export { geminiRealtime } from './adapter'

// Types
export type {
GeminiRealtimeModel,
GeminiRealtimeTokenOptions,
GeminiRealtimeOptions,
} from './types'
78 changes: 78 additions & 0 deletions packages/typescript/ai-gemini/src/realtime/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { GoogleGenAI, Modality } from '@google/genai'
import { getGeminiApiKeyFromEnv } from '../utils'
import type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai'
import type { GeminiRealtimeModel, GeminiRealtimeTokenOptions } from './types'

/**
* Creates a Google Gemini realtime token adapter.
*
* This adapter generates ephemeral tokens for client-side WebSocket connections.
*
* @param options - Configuration options for the realtime session
* @returns A RealtimeTokenAdapter for use with realtimeToken()
*
* @example
* ```typescript
* import { realtimeToken } from '@tanstack/ai'
* import { geminiRealtimeToken } from '@tanstack/ai-gemini'
*
* const token = await realtimeToken({
* adapter: geminiRealtimeToken({
* model: 'gemini-live-2.5-flash-native-audio',
* }),
* })
* ```
*/
export function geminiRealtimeToken(
options: GeminiRealtimeTokenOptions = {},
): RealtimeTokenAdapter {
const apiKey = getGeminiApiKeyFromEnv()

const client = new GoogleGenAI({
apiKey,
})

// Defaults to 30 minutes
const expireTime = options.expiresAt ?? Date.now() + 30 * 60 * 1000

return {
provider: 'gemini',
async generateToken(): Promise<RealtimeToken> {
const model: GeminiRealtimeModel =
options.model ?? 'gemini-live-2.5-flash-native-audio'

const token = await client.authTokens.create({
config: {
uses: 1, // The default
expireTime: new Date(expireTime).toISOString(),
liveConnectConstraints: {
model,
config: {
sessionResumption: {},
maxOutputTokens: options.maxOutputTokens,
responseModalities: [Modality.AUDIO],
},
},
httpOptions: {
apiVersion: 'v1alpha',
},
},
})

if (!token.name) {
throw new Error('Gemini realtime token creation failed')
}

return {
provider: 'gemini',
token: token.name,
expiresAt: expireTime,
config: {
model,
maxOutputTokens: options.maxOutputTokens,
outputModalities: ['audio'],
},
}
},
}
}
27 changes: 27 additions & 0 deletions packages/typescript/ai-gemini/src/realtime/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Gemini realtime model options
*/
export type GeminiRealtimeModel = 'gemini-live-2.5-flash-native-audio'

/**
* Options for the Gemini realtime client adapter
*/
export interface GeminiRealtimeOptions {
/** Connection mode (default: 'websocket' in browser) */
connectionMode?: 'websocket'
}

/**
* Options for the Gemini realtime token adapter
*/
export interface GeminiRealtimeTokenOptions {
/** Model to use (default: 'gemini-live-2.5-flash-native-audio') */
model?: GeminiRealtimeModel
expiresAt?: number
maxOutputTokens?: number
}

/**
* Gemini Realtime session response from the API
*/
export interface GeminiRealtimeSessionResponse {}
Loading
Loading