@tanstack/ai-gemini 0.31.1 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/realtime/adapter.js +1 -1
- package/dist/esm/realtime/adapter.js.map +1 -1
- package/dist/esm/realtime/token.d.ts +1 -1
- package/dist/esm/realtime/token.js +1 -1
- package/dist/esm/realtime/token.js.map +1 -1
- package/dist/esm/realtime/types.d.ts +9 -2
- package/package.json +4 -4
- package/src/realtime/adapter.ts +1 -1
- package/src/realtime/token.ts +1 -1
- package/src/realtime/types.ts +12 -2
|
@@ -32,7 +32,7 @@ function geminiRealtime(options = {}) {
|
|
|
32
32
|
/**
|
|
33
33
|
* Creates a WebSocket connection to Gemini's realtime API
|
|
34
34
|
*/
|
|
35
|
-
async function createWebSocketConnection(token, model = "gemini-3.
|
|
35
|
+
async function createWebSocketConnection(token, model = "gemini-3.8-live", tools) {
|
|
36
36
|
const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter();
|
|
37
37
|
let currentMode = "idle";
|
|
38
38
|
let currentMessageId = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","names":[],"sources":["../../../src/realtime/adapter.ts"],"sourcesContent":["import { createRealtimeEventEmitter } from '@tanstack/ai'\nimport { AudioPlayer, AudioStreamer, base64ToArrayBuffer } from './utils'\nimport { GeminiLiveClient } from './client'\nimport type { LiveResponse } from './client'\nimport type {\n AnyClientTool,\n AudioVisualization,\n RealtimeAdapter,\n RealtimeConnection,\n RealtimeMessage,\n RealtimeMode,\n RealtimeToken,\n} from '@tanstack/ai'\nimport type { GeminiRealtimeModel, GeminiRealtimeOptions } from './types'\n\n/**\n * Creates a Gemini realtime adapter for client-side use.\n *\n * @param options - Optional configuration\n * @returns A RealtimeAdapter for use with RealtimeClient\n *\n * @example\n * ```typescript\n * import { RealtimeClient } from '@tanstack/ai-client'\n * import { geminiRealtime } from '@tanstack/ai-gemini'\n *\n * const client = new RealtimeClient({\n * getToken: () => fetch('/api/realtime-token').then(r => r.json()),\n * adapter: geminiRealtime(),\n * onGoAway: () => client.updateSession({ ... }) // Resume session with new config (available only for Gemini Live adapter)\n * })\n *\n * ```\n */\nexport function geminiRealtime(\n options: GeminiRealtimeOptions = {},\n): RealtimeAdapter {\n return {\n provider: 'gemini',\n\n connect(\n token: RealtimeToken,\n clientTools?: ReadonlyArray<AnyClientTool>,\n ): Promise<RealtimeConnection> {\n return createWebSocketConnection(token, options.model, clientTools)\n },\n }\n}\n\n/**\n * Creates a WebSocket connection to Gemini's realtime API\n */\nasync function createWebSocketConnection(\n token: RealtimeToken,\n model: GeminiRealtimeModel = 'gemini-3.1-flash-live-preview',\n tools?: ReadonlyArray<AnyClientTool>,\n): Promise<RealtimeConnection> {\n const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter()\n\n // Current state\n let currentMode: RealtimeMode = 'idle'\n let currentMessageId: string | null = null\n let messageIdCounter = 0\n\n function generateMessageId(): string {\n return `gemini-msg-${Date.now()}-${++messageIdCounter}`\n }\n\n const client = new GeminiLiveClient(token.token, model, tools)\n\n let message: RealtimeMessage = {\n id: '',\n role: 'assistant',\n timestamp: 0,\n parts: [],\n }\n\n let pendingAssistantResponse = ''\n\n client.onClose = () => {\n emit('status_change', { status: 'idle' })\n emit('mode_change', { mode: 'idle' })\n }\n\n client.onError = (error) => {\n emit('error', { error })\n emit('status_change', { status: 'error' })\n emit('mode_change', { mode: 'idle' })\n }\n\n client.onReceiveResponse = (response: LiveResponse) => {\n switch (response.type) {\n case 'text':\n message.parts.push({\n type: 'text',\n content: response.data,\n })\n break\n case 'audio': {\n // Decode once, then reuse for both the stored message part and playback.\n const pcm = base64ToArrayBuffer(response.data.audioData)\n message.parts.push({\n type: 'audio',\n transcript: response.data.transcript,\n audioData: pcm,\n })\n if (currentMode !== 'speaking') {\n currentMode = 'speaking'\n emit('mode_change', { mode: 'speaking' })\n }\n audioPlayer.play(pcm).catch((error) => emit('error', { error }))\n break\n }\n case 'go_away':\n emit('go_away', { timeLeft: response.data.timeLeft })\n break\n case 'usage_metadata':\n emit('usage', {\n completionTokens: response.data.responseTokenCount ?? 0,\n promptTokens: response.data.promptTokenCount ?? 0,\n totalTokens: response.data.totalTokenCount ?? 0,\n })\n break\n case 'input_transcription':\n if (response.data.finished && currentMode !== 'thinking') {\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n }\n emit('transcript', {\n isFinal: response.data.finished,\n transcript: response.data.text,\n role: 'user',\n })\n break\n case 'output_transcription':\n pendingAssistantResponse += response.data.text\n emit('transcript', {\n isFinal: response.data.finished,\n transcript: pendingAssistantResponse,\n role: 'assistant',\n })\n break\n case 'interrupted':\n audioPlayer.interrupt()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', { messageId: currentMessageId ?? undefined })\n break\n case 'tool_call':\n for (const tool of response.data.functionCalls || []) {\n if (tool.id && tool.name) {\n emit('tool_call', {\n toolCallId: tool.id,\n input: tool.args,\n toolName: tool.name,\n })\n }\n }\n break\n case 'turn_complete':\n currentMessageId = generateMessageId()\n message.id = currentMessageId\n message.timestamp = Date.now()\n message.parts.push({\n type: 'text',\n content: pendingAssistantResponse,\n })\n emit('message_complete', { message })\n\n pendingAssistantResponse = ''\n message = {\n id: '',\n role: 'assistant',\n timestamp: 0,\n parts: [],\n }\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n break\n case 'setup_complete':\n emit('status_change', { status: 'connected' })\n break\n case 'error':\n emit('error', {\n error: new Error(response.data),\n })\n break\n case 'thought':\n case 'session_resumption_update':\n // Handled internally by GeminiLiveClient; not surfaced to the client.\n break\n }\n }\n\n await client.connect()\n\n const audioStreamer = new AudioStreamer(client)\n const audioPlayer = new AudioPlayer()\n try {\n await audioPlayer.init()\n await audioStreamer.start()\n } catch (error) {\n // Tear down the socket + audio graph if mic/worklet setup fails (e.g. the\n // user denied microphone access) rather than leaking an open connection.\n audioStreamer.stop()\n audioPlayer.destroy()\n client.disconnect()\n throw error\n }\n\n const connection: RealtimeConnection = {\n async disconnect() {\n audioStreamer.stop()\n audioPlayer.destroy()\n client.disconnect()\n currentMode = 'idle'\n emit('status_change', { status: 'idle' })\n },\n\n async startAudioCapture() {\n // Audio capture is established during connection setup\n audioStreamer.startAudioCapture()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n },\n\n stopAudioCapture() {\n audioStreamer.stopAudioCapture()\n currentMode = 'idle'\n emit('mode_change', { mode: 'idle' })\n },\n\n sendText(text: string) {\n client.sendTextMessage(text)\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n },\n\n sendImage(imageData: string, mimeType: string) {\n client.sendImageMessage(imageData, mimeType)\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n },\n\n sendToolResult(callId: string, result: string) {\n client.sendToolResponse([\n {\n id: callId,\n response: {\n result,\n },\n },\n ])\n },\n\n updateSession(config) {\n client.updateSession(config).catch((error) => emit('error', { error }))\n emit('status_change', { status: 'reconnecting' })\n },\n\n updateToken(newToken) {\n client.updateToken(newToken)\n emit('status_change', { status: 'reconnecting' })\n },\n\n interrupt() {\n audioPlayer.interrupt()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', { messageId: currentMessageId ?? undefined })\n },\n on: realtimeEventEmitterOn,\n getAudioVisualization(): AudioVisualization {\n return {\n get inputLevel() {\n return audioStreamer.inputLevel\n },\n\n get outputLevel() {\n return audioPlayer.outputLevel\n },\n\n getInputFrequencyData() {\n return audioStreamer.inputFrequencyData\n },\n\n getOutputFrequencyData() {\n return audioPlayer.outputFrequencyData\n },\n\n getInputTimeDomainData() {\n return audioStreamer.inputTimeDomainData\n },\n\n getOutputTimeDomainData() {\n return audioPlayer.outputTimeDomainData\n },\n\n get inputSampleRate() {\n return audioStreamer.inputSampleRate\n },\n\n get outputSampleRate() {\n return audioPlayer.outputSampleRate\n },\n }\n },\n }\n\n return connection\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter.js","names":[],"sources":["../../../src/realtime/adapter.ts"],"sourcesContent":["import { createRealtimeEventEmitter } from '@tanstack/ai'\nimport { AudioPlayer, AudioStreamer, base64ToArrayBuffer } from './utils'\nimport { GeminiLiveClient } from './client'\nimport type { LiveResponse } from './client'\nimport type {\n AnyClientTool,\n AudioVisualization,\n RealtimeAdapter,\n RealtimeConnection,\n RealtimeMessage,\n RealtimeMode,\n RealtimeToken,\n} from '@tanstack/ai'\nimport type { GeminiRealtimeModel, GeminiRealtimeOptions } from './types'\n\n/**\n * Creates a Gemini realtime adapter for client-side use.\n *\n * @param options - Optional configuration\n * @returns A RealtimeAdapter for use with RealtimeClient\n *\n * @example\n * ```typescript\n * import { RealtimeClient } from '@tanstack/ai-client'\n * import { geminiRealtime } from '@tanstack/ai-gemini'\n *\n * const client = new RealtimeClient({\n * getToken: () => fetch('/api/realtime-token').then(r => r.json()),\n * adapter: geminiRealtime(),\n * onGoAway: () => client.updateSession({ ... }) // Resume session with new config (available only for Gemini Live adapter)\n * })\n *\n * ```\n */\nexport function geminiRealtime(\n options: GeminiRealtimeOptions = {},\n): RealtimeAdapter {\n return {\n provider: 'gemini',\n\n connect(\n token: RealtimeToken,\n clientTools?: ReadonlyArray<AnyClientTool>,\n ): Promise<RealtimeConnection> {\n return createWebSocketConnection(token, options.model, clientTools)\n },\n }\n}\n\n/**\n * Creates a WebSocket connection to Gemini's realtime API\n */\nasync function createWebSocketConnection(\n token: RealtimeToken,\n model: GeminiRealtimeModel = 'gemini-3.8-live',\n tools?: ReadonlyArray<AnyClientTool>,\n): Promise<RealtimeConnection> {\n const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter()\n\n // Current state\n let currentMode: RealtimeMode = 'idle'\n let currentMessageId: string | null = null\n let messageIdCounter = 0\n\n function generateMessageId(): string {\n return `gemini-msg-${Date.now()}-${++messageIdCounter}`\n }\n\n const client = new GeminiLiveClient(token.token, model, tools)\n\n let message: RealtimeMessage = {\n id: '',\n role: 'assistant',\n timestamp: 0,\n parts: [],\n }\n\n let pendingAssistantResponse = ''\n\n client.onClose = () => {\n emit('status_change', { status: 'idle' })\n emit('mode_change', { mode: 'idle' })\n }\n\n client.onError = (error) => {\n emit('error', { error })\n emit('status_change', { status: 'error' })\n emit('mode_change', { mode: 'idle' })\n }\n\n client.onReceiveResponse = (response: LiveResponse) => {\n switch (response.type) {\n case 'text':\n message.parts.push({\n type: 'text',\n content: response.data,\n })\n break\n case 'audio': {\n // Decode once, then reuse for both the stored message part and playback.\n const pcm = base64ToArrayBuffer(response.data.audioData)\n message.parts.push({\n type: 'audio',\n transcript: response.data.transcript,\n audioData: pcm,\n })\n if (currentMode !== 'speaking') {\n currentMode = 'speaking'\n emit('mode_change', { mode: 'speaking' })\n }\n audioPlayer.play(pcm).catch((error) => emit('error', { error }))\n break\n }\n case 'go_away':\n emit('go_away', { timeLeft: response.data.timeLeft })\n break\n case 'usage_metadata':\n emit('usage', {\n completionTokens: response.data.responseTokenCount ?? 0,\n promptTokens: response.data.promptTokenCount ?? 0,\n totalTokens: response.data.totalTokenCount ?? 0,\n })\n break\n case 'input_transcription':\n if (response.data.finished && currentMode !== 'thinking') {\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n }\n emit('transcript', {\n isFinal: response.data.finished,\n transcript: response.data.text,\n role: 'user',\n })\n break\n case 'output_transcription':\n pendingAssistantResponse += response.data.text\n emit('transcript', {\n isFinal: response.data.finished,\n transcript: pendingAssistantResponse,\n role: 'assistant',\n })\n break\n case 'interrupted':\n audioPlayer.interrupt()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', { messageId: currentMessageId ?? undefined })\n break\n case 'tool_call':\n for (const tool of response.data.functionCalls || []) {\n if (tool.id && tool.name) {\n emit('tool_call', {\n toolCallId: tool.id,\n input: tool.args,\n toolName: tool.name,\n })\n }\n }\n break\n case 'turn_complete':\n currentMessageId = generateMessageId()\n message.id = currentMessageId\n message.timestamp = Date.now()\n message.parts.push({\n type: 'text',\n content: pendingAssistantResponse,\n })\n emit('message_complete', { message })\n\n pendingAssistantResponse = ''\n message = {\n id: '',\n role: 'assistant',\n timestamp: 0,\n parts: [],\n }\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n break\n case 'setup_complete':\n emit('status_change', { status: 'connected' })\n break\n case 'error':\n emit('error', {\n error: new Error(response.data),\n })\n break\n case 'thought':\n case 'session_resumption_update':\n // Handled internally by GeminiLiveClient; not surfaced to the client.\n break\n }\n }\n\n await client.connect()\n\n const audioStreamer = new AudioStreamer(client)\n const audioPlayer = new AudioPlayer()\n try {\n await audioPlayer.init()\n await audioStreamer.start()\n } catch (error) {\n // Tear down the socket + audio graph if mic/worklet setup fails (e.g. the\n // user denied microphone access) rather than leaking an open connection.\n audioStreamer.stop()\n audioPlayer.destroy()\n client.disconnect()\n throw error\n }\n\n const connection: RealtimeConnection = {\n async disconnect() {\n audioStreamer.stop()\n audioPlayer.destroy()\n client.disconnect()\n currentMode = 'idle'\n emit('status_change', { status: 'idle' })\n },\n\n async startAudioCapture() {\n // Audio capture is established during connection setup\n audioStreamer.startAudioCapture()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n },\n\n stopAudioCapture() {\n audioStreamer.stopAudioCapture()\n currentMode = 'idle'\n emit('mode_change', { mode: 'idle' })\n },\n\n sendText(text: string) {\n client.sendTextMessage(text)\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n },\n\n sendImage(imageData: string, mimeType: string) {\n client.sendImageMessage(imageData, mimeType)\n currentMode = 'thinking'\n emit('mode_change', { mode: 'thinking' })\n },\n\n sendToolResult(callId: string, result: string) {\n client.sendToolResponse([\n {\n id: callId,\n response: {\n result,\n },\n },\n ])\n },\n\n updateSession(config) {\n client.updateSession(config).catch((error) => emit('error', { error }))\n emit('status_change', { status: 'reconnecting' })\n },\n\n updateToken(newToken) {\n client.updateToken(newToken)\n emit('status_change', { status: 'reconnecting' })\n },\n\n interrupt() {\n audioPlayer.interrupt()\n currentMode = 'listening'\n emit('mode_change', { mode: 'listening' })\n emit('interrupted', { messageId: currentMessageId ?? undefined })\n },\n on: realtimeEventEmitterOn,\n getAudioVisualization(): AudioVisualization {\n return {\n get inputLevel() {\n return audioStreamer.inputLevel\n },\n\n get outputLevel() {\n return audioPlayer.outputLevel\n },\n\n getInputFrequencyData() {\n return audioStreamer.inputFrequencyData\n },\n\n getOutputFrequencyData() {\n return audioPlayer.outputFrequencyData\n },\n\n getInputTimeDomainData() {\n return audioStreamer.inputTimeDomainData\n },\n\n getOutputTimeDomainData() {\n return audioPlayer.outputTimeDomainData\n },\n\n get inputSampleRate() {\n return audioStreamer.inputSampleRate\n },\n\n get outputSampleRate() {\n return audioPlayer.outputSampleRate\n },\n }\n },\n }\n\n return connection\n}\n"],"mappings":"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"}
|
|
@@ -17,7 +17,7 @@ import { GeminiRealtimeTokenOptions } from './types.js';
|
|
|
17
17
|
* adapter: geminiRealtimeToken({
|
|
18
18
|
* // Optional: constraint model config by token
|
|
19
19
|
* liveConnectConstraints: {
|
|
20
|
-
* model: 'gemini-3.
|
|
20
|
+
* model: 'gemini-3.8-live',
|
|
21
21
|
* },
|
|
22
22
|
* }),
|
|
23
23
|
* })
|
|
@@ -19,7 +19,7 @@ import { GoogleGenAI } from "@google/genai";
|
|
|
19
19
|
* adapter: geminiRealtimeToken({
|
|
20
20
|
* // Optional: constraint model config by token
|
|
21
21
|
* liveConnectConstraints: {
|
|
22
|
-
* model: 'gemini-3.
|
|
22
|
+
* model: 'gemini-3.8-live',
|
|
23
23
|
* },
|
|
24
24
|
* }),
|
|
25
25
|
* })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","names":[],"sources":["../../../src/realtime/token.ts"],"sourcesContent":["import { GoogleGenAI } from '@google/genai'\nimport { getGeminiApiKeyFromEnv } from '../utils'\nimport type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai'\nimport type { GeminiRealtimeTokenOptions } from './types'\n\n/**\n * Creates a Google Gemini realtime token adapter.\n *\n * This adapter generates ephemeral tokens for client-side WebSocket connections.\n *\n * @param options - Configuration options for the realtime session\n * @returns A RealtimeTokenAdapter for use with realtimeToken()\n *\n * @example\n * ```typescript\n * import { realtimeToken } from '@tanstack/ai'\n * import { geminiRealtimeToken } from '@tanstack/ai-gemini'\n *\n * const token = await realtimeToken({\n * adapter: geminiRealtimeToken({\n * // Optional: constraint model config by token\n * liveConnectConstraints: {\n * model: 'gemini-3.
|
|
1
|
+
{"version":3,"file":"token.js","names":[],"sources":["../../../src/realtime/token.ts"],"sourcesContent":["import { GoogleGenAI } from '@google/genai'\nimport { getGeminiApiKeyFromEnv } from '../utils'\nimport type { RealtimeToken, RealtimeTokenAdapter } from '@tanstack/ai'\nimport type { GeminiRealtimeTokenOptions } from './types'\n\n/**\n * Creates a Google Gemini realtime token adapter.\n *\n * This adapter generates ephemeral tokens for client-side WebSocket connections.\n *\n * @param options - Configuration options for the realtime session\n * @returns A RealtimeTokenAdapter for use with realtimeToken()\n *\n * @example\n * ```typescript\n * import { realtimeToken } from '@tanstack/ai'\n * import { geminiRealtimeToken } from '@tanstack/ai-gemini'\n *\n * const token = await realtimeToken({\n * adapter: geminiRealtimeToken({\n * // Optional: constraint model config by token\n * liveConnectConstraints: {\n * model: 'gemini-3.8-live',\n * },\n * }),\n * })\n * ```\n */\nexport function geminiRealtimeToken(\n options: GeminiRealtimeTokenOptions = {},\n): RealtimeTokenAdapter {\n const apiKey = getGeminiApiKeyFromEnv()\n\n const client = new GoogleGenAI({\n apiKey,\n })\n\n return {\n provider: 'gemini',\n async generateToken(): Promise<RealtimeToken> {\n // Computed per call so a reused adapter doesn't mint tokens with a\n // fixed (and eventually past) expiry. Defaults to 30 minutes.\n const expireTime = options.expiresAt ?? Date.now() + 30 * 60 * 1000\n\n const token = await client.authTokens.create({\n config: {\n uses: options.uses ?? 1,\n expireTime: new Date(expireTime).toISOString(),\n liveConnectConstraints: options.liveConnectConstraints,\n httpOptions: {\n apiVersion: 'v1alpha',\n },\n },\n })\n\n if (!token.name) {\n throw new Error('Gemini realtime token creation failed')\n }\n\n return {\n provider: 'gemini',\n token: token.name,\n expiresAt: expireTime,\n config: {\n model: options.liveConnectConstraints?.model,\n },\n }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,oBACd,UAAsC,CAAC,GACjB;CACtB,MAAM,SAAS,uBAAuB;CAEtC,MAAM,SAAS,IAAI,YAAY,EAC7B,OACF,CAAC;CAED,OAAO;EACL,UAAU;EACV,MAAM,gBAAwC;GAG5C,MAAM,aAAa,QAAQ,aAAa,KAAK,IAAI,IAAI;GAErD,MAAM,QAAQ,MAAM,OAAO,WAAW,OAAO,EAC3C,QAAQ;IACN,MAAM,QAAQ,QAAQ;IACtB,YAAY,IAAI,KAAK,UAAU,CAAC,CAAC,YAAY;IAC7C,wBAAwB,QAAQ;IAChC,aAAa,EACX,YAAY,UACd;GACF,EACF,CAAC;GAED,IAAI,CAAC,MAAM,MACT,MAAM,IAAI,MAAM,uCAAuC;GAGzD,OAAO;IACL,UAAU;IACV,OAAO,MAAM;IACb,WAAW;IACX,QAAQ,EACN,OAAO,QAAQ,wBAAwB,MACzC;GACF;EACF;CACF;AACF"}
|
|
@@ -4,9 +4,16 @@ import { ContextWindowCompressionConfig, LiveConnectConstraints, ThinkingConfig
|
|
|
4
4
|
*/
|
|
5
5
|
export type GeminiRealtimeVoice = 'Achernar' | 'Achird' | 'Algenib' | 'Algieba' | 'Alnilam' | 'Aoede' | 'Autonoe' | 'Callirrhoe' | 'Charon' | 'Despina' | 'Enceladus' | 'Erinome' | 'Fenrir' | 'Gacrux' | 'Iapetus' | 'Kore' | 'Laomedeia' | 'Leda' | 'Orus' | 'Pulcherrima' | 'Puck' | 'Rasalgethi' | 'Sadachbia' | 'Sadaltager' | 'Schedar' | 'Sulafat' | 'Umbriel' | 'Vindemiatrix' | 'Zephyr' | 'Zubenelgenubi';
|
|
6
6
|
/**
|
|
7
|
-
* Gemini realtime model options
|
|
7
|
+
* Gemini realtime model options.
|
|
8
|
+
*
|
|
9
|
+
* - `gemini-3.8-live` — default low-latency voice model.
|
|
10
|
+
* - `gemini-3.8-live-extended-thinking` — reasons in the background while it
|
|
11
|
+
* speaks. Set `thinkingConfig.thinkingLevel` to `LOW`, `MEDIUM`, or `HIGH`
|
|
12
|
+
* (`MINIMAL` is not supported). Function calls run asynchronously, so
|
|
13
|
+
* `turnComplete` no longer means the model is idle.
|
|
14
|
+
* - `gemini-3.1-flash-live-preview` — legacy preview model.
|
|
8
15
|
*/
|
|
9
|
-
export type GeminiRealtimeModel = 'gemini-3.1-flash-live-preview';
|
|
16
|
+
export type GeminiRealtimeModel = 'gemini-3.8-live' | 'gemini-3.8-live-extended-thinking' | 'gemini-3.1-flash-live-preview';
|
|
10
17
|
/**
|
|
11
18
|
* Options for the Gemini realtime client adapter
|
|
12
19
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai-gemini",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Google Gemini adapter for TanStack AI chat, images, speech, audio generation, and structured outputs.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,16 +60,16 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@google/genai": "^2.10.0",
|
|
62
62
|
"partial-json": "^0.1.7",
|
|
63
|
-
"@tanstack/ai-utils": "^0.4.
|
|
63
|
+
"@tanstack/ai-utils": "^0.4.1"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@tanstack/ai": "^0.
|
|
66
|
+
"@tanstack/ai": "^0.58.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@vitest/coverage-v8": "4.1.10",
|
|
70
70
|
"vite": "^8.2.1",
|
|
71
71
|
"zod": "^4.2.0",
|
|
72
|
-
"@tanstack/ai": "0.
|
|
72
|
+
"@tanstack/ai": "0.58.0"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "vite build",
|
package/src/realtime/adapter.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function geminiRealtime(
|
|
|
52
52
|
*/
|
|
53
53
|
async function createWebSocketConnection(
|
|
54
54
|
token: RealtimeToken,
|
|
55
|
-
model: GeminiRealtimeModel = 'gemini-3.
|
|
55
|
+
model: GeminiRealtimeModel = 'gemini-3.8-live',
|
|
56
56
|
tools?: ReadonlyArray<AnyClientTool>,
|
|
57
57
|
): Promise<RealtimeConnection> {
|
|
58
58
|
const { emit, on: realtimeEventEmitterOn } = createRealtimeEventEmitter()
|
package/src/realtime/token.ts
CHANGED
|
@@ -20,7 +20,7 @@ import type { GeminiRealtimeTokenOptions } from './types'
|
|
|
20
20
|
* adapter: geminiRealtimeToken({
|
|
21
21
|
* // Optional: constraint model config by token
|
|
22
22
|
* liveConnectConstraints: {
|
|
23
|
-
* model: 'gemini-3.
|
|
23
|
+
* model: 'gemini-3.8-live',
|
|
24
24
|
* },
|
|
25
25
|
* }),
|
|
26
26
|
* })
|
package/src/realtime/types.ts
CHANGED
|
@@ -40,9 +40,19 @@ export type GeminiRealtimeVoice =
|
|
|
40
40
|
| 'Zubenelgenubi'
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Gemini realtime model options
|
|
43
|
+
* Gemini realtime model options.
|
|
44
|
+
*
|
|
45
|
+
* - `gemini-3.8-live` — default low-latency voice model.
|
|
46
|
+
* - `gemini-3.8-live-extended-thinking` — reasons in the background while it
|
|
47
|
+
* speaks. Set `thinkingConfig.thinkingLevel` to `LOW`, `MEDIUM`, or `HIGH`
|
|
48
|
+
* (`MINIMAL` is not supported). Function calls run asynchronously, so
|
|
49
|
+
* `turnComplete` no longer means the model is idle.
|
|
50
|
+
* - `gemini-3.1-flash-live-preview` — legacy preview model.
|
|
44
51
|
*/
|
|
45
|
-
export type GeminiRealtimeModel =
|
|
52
|
+
export type GeminiRealtimeModel =
|
|
53
|
+
| 'gemini-3.8-live'
|
|
54
|
+
| 'gemini-3.8-live-extended-thinking'
|
|
55
|
+
| 'gemini-3.1-flash-live-preview'
|
|
46
56
|
|
|
47
57
|
/**
|
|
48
58
|
* Options for the Gemini realtime client adapter
|