@tanstack/ai-gemini 0.19.1 → 0.20.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.
Files changed (56) hide show
  1. package/dist/esm/adapters/audio.d.ts +1 -1
  2. package/dist/esm/adapters/audio.js.map +1 -1
  3. package/dist/esm/adapters/image.d.ts +1 -1
  4. package/dist/esm/adapters/image.js +17 -39
  5. package/dist/esm/adapters/image.js.map +1 -1
  6. package/dist/esm/adapters/summarize.d.ts +1 -1
  7. package/dist/esm/adapters/summarize.js.map +1 -1
  8. package/dist/esm/adapters/text.d.ts +1 -1
  9. package/dist/esm/adapters/text.js.map +1 -1
  10. package/dist/esm/adapters/tts.d.ts +1 -1
  11. package/dist/esm/adapters/tts.js.map +1 -1
  12. package/dist/esm/adapters/video.d.ts +60 -11
  13. package/dist/esm/adapters/video.js +205 -6
  14. package/dist/esm/adapters/video.js.map +1 -1
  15. package/dist/esm/experimental/text-interactions/adapter.d.ts +1 -1
  16. package/dist/esm/experimental/text-interactions/adapter.js.map +1 -1
  17. package/dist/esm/index.d.ts +6 -3
  18. package/dist/esm/index.js +9 -3
  19. package/dist/esm/index.js.map +1 -1
  20. package/dist/esm/model-meta.d.ts +11 -3
  21. package/dist/esm/model-meta.js +9 -1
  22. package/dist/esm/model-meta.js.map +1 -1
  23. package/dist/esm/realtime/adapter.d.ts +22 -0
  24. package/dist/esm/realtime/adapter.js +233 -0
  25. package/dist/esm/realtime/adapter.js.map +1 -0
  26. package/dist/esm/realtime/client.d.ts +98 -0
  27. package/dist/esm/realtime/client.js +389 -0
  28. package/dist/esm/realtime/client.js.map +1 -0
  29. package/dist/esm/realtime/index.d.ts +3 -0
  30. package/dist/esm/realtime/token.d.ts +26 -0
  31. package/dist/esm/realtime/token.js +39 -0
  32. package/dist/esm/realtime/token.js.map +1 -0
  33. package/dist/esm/realtime/types.d.ts +51 -0
  34. package/dist/esm/realtime/utils.d.ts +40 -0
  35. package/dist/esm/realtime/utils.js +350 -0
  36. package/dist/esm/realtime/utils.js.map +1 -0
  37. package/dist/esm/video/video-provider-options.d.ts +59 -14
  38. package/dist/esm/video/video-provider-options.js +15 -2
  39. package/dist/esm/video/video-provider-options.js.map +1 -1
  40. package/package.json +4 -4
  41. package/src/adapters/audio.ts +1 -1
  42. package/src/adapters/image.ts +25 -49
  43. package/src/adapters/summarize.ts +1 -1
  44. package/src/adapters/text.ts +1 -1
  45. package/src/adapters/tts.ts +1 -1
  46. package/src/adapters/video.ts +333 -16
  47. package/src/experimental/text-interactions/adapter.ts +2 -2
  48. package/src/index.ts +20 -2
  49. package/src/model-meta.ts +45 -2
  50. package/src/realtime/adapter.ts +311 -0
  51. package/src/realtime/client.ts +547 -0
  52. package/src/realtime/index.ts +14 -0
  53. package/src/realtime/token.ts +70 -0
  54. package/src/realtime/types.ts +94 -0
  55. package/src/realtime/utils.ts +439 -0
  56. package/src/video/video-provider-options.ts +95 -15
@@ -0,0 +1,389 @@
1
+ import { convertSchemaToJsonSchema } from "@tanstack/ai";
2
+ import { Modality, StartSensitivity, EndSensitivity, ActivityHandling, TurnCoverage } from "@google/genai";
3
+ function clientToolToDeclaration(tool) {
4
+ return {
5
+ name: tool.name,
6
+ description: tool.description,
7
+ parametersJsonSchema: convertSchemaToJsonSchema(tool.inputSchema),
8
+ responseJsonSchema: convertSchemaToJsonSchema(tool.outputSchema)
9
+ };
10
+ }
11
+ function toolConfigToDeclaration(tool) {
12
+ return {
13
+ name: tool.name,
14
+ description: tool.description,
15
+ parametersJsonSchema: tool.inputSchema,
16
+ responseJsonSchema: tool.outputSchema
17
+ };
18
+ }
19
+ function parseResponseMessages(data) {
20
+ const responses = [];
21
+ const serverContent = data.serverContent;
22
+ const parts = serverContent?.modelTurn?.parts;
23
+ if (data.setupComplete) {
24
+ responses.push({ type: "setup_complete", data: "", endOfTurn: false });
25
+ return responses;
26
+ }
27
+ if (data.toolCall) {
28
+ responses.push({
29
+ type: "tool_call",
30
+ data: data.toolCall,
31
+ endOfTurn: false
32
+ });
33
+ return responses;
34
+ }
35
+ if (data.sessionResumptionUpdate) {
36
+ responses.push({
37
+ type: "session_resumption_update",
38
+ data: data.sessionResumptionUpdate,
39
+ endOfTurn: false
40
+ });
41
+ }
42
+ if (data.goAway) {
43
+ responses.push({ type: "go_away", data: data.goAway, endOfTurn: false });
44
+ }
45
+ if (data.usageMetadata) {
46
+ responses.push({
47
+ type: "usage_metadata",
48
+ data: data.usageMetadata,
49
+ endOfTurn: false
50
+ });
51
+ }
52
+ if (parts?.length) {
53
+ for (const part of parts) {
54
+ if (part.inlineData?.data) {
55
+ responses.push({
56
+ type: "audio",
57
+ data: {
58
+ audioData: part.inlineData.data,
59
+ // The transcription is independent to the model turn, which means it doesn't imply any ordering between transcription and model turn.
60
+ transcript: ""
61
+ },
62
+ endOfTurn: false
63
+ });
64
+ } else if (part.text) {
65
+ responses.push({
66
+ type: part.thought ? "thought" : "text",
67
+ data: part.text,
68
+ endOfTurn: false
69
+ });
70
+ }
71
+ }
72
+ }
73
+ if (serverContent?.inputTranscription) {
74
+ responses.push({
75
+ type: "input_transcription",
76
+ data: {
77
+ text: serverContent.inputTranscription.text || "",
78
+ finished: serverContent.inputTranscription.finished || false
79
+ },
80
+ endOfTurn: false
81
+ });
82
+ }
83
+ if (serverContent?.outputTranscription) {
84
+ responses.push({
85
+ type: "output_transcription",
86
+ data: {
87
+ text: serverContent.outputTranscription.text || "",
88
+ finished: serverContent.outputTranscription.finished || false
89
+ },
90
+ endOfTurn: false
91
+ });
92
+ }
93
+ if (serverContent?.interrupted) {
94
+ responses.push({ type: "interrupted", data: "", endOfTurn: false });
95
+ }
96
+ if (serverContent?.turnComplete) {
97
+ responses.push({ type: "turn_complete", data: "", endOfTurn: true });
98
+ }
99
+ return responses;
100
+ }
101
+ class GeminiLiveClient {
102
+ token = null;
103
+ model = null;
104
+ responseModalities = [Modality.AUDIO];
105
+ systemInstructions = "";
106
+ googleGrounding = false;
107
+ voiceName = "Puck";
108
+ temperature = 1;
109
+ inputAudioTranscription = false;
110
+ outputAudioTranscription = false;
111
+ contextWindowCompression;
112
+ proactiveAudio = false;
113
+ enableAffectiveDialog = false;
114
+ thinkingConfig;
115
+ speechLanguageCode;
116
+ maxOutputTokens;
117
+ functionDeclarations = [];
118
+ automaticActivityDetection = {
119
+ disabled: false,
120
+ silence_duration_ms: 2e3,
121
+ prefix_padding_ms: 500,
122
+ end_of_speech_sensitivity: EndSensitivity.END_SENSITIVITY_UNSPECIFIED,
123
+ start_of_speech_sensitivity: StartSensitivity.START_SENSITIVITY_UNSPECIFIED
124
+ };
125
+ activityHandling = ActivityHandling.ACTIVITY_HANDLING_UNSPECIFIED;
126
+ webSocket = null;
127
+ lastResumptionUpdate = null;
128
+ setupComplete = false;
129
+ connected = false;
130
+ onReceiveResponse = () => {
131
+ };
132
+ onOpen = () => {
133
+ };
134
+ onClose = () => {
135
+ };
136
+ onError = () => {
137
+ };
138
+ constructor(token, model, tools) {
139
+ this.token = token;
140
+ this.model = model;
141
+ if (tools) {
142
+ this.functionDeclarations = tools.map(clientToolToDeclaration);
143
+ }
144
+ }
145
+ get isConnected() {
146
+ return this.connected;
147
+ }
148
+ get isSetupComplete() {
149
+ return this.setupComplete;
150
+ }
151
+ /**
152
+ * Connection management
153
+ */
154
+ connect() {
155
+ return new Promise((resolve, reject) => {
156
+ const socket = new WebSocket(
157
+ `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained?access_token=${this.token}`
158
+ );
159
+ this.webSocket = socket;
160
+ let errored = false;
161
+ socket.onclose = () => {
162
+ this.connected = false;
163
+ this.setupComplete = false;
164
+ if (!errored) this.onClose();
165
+ reject(new Error("WebSocket closed before setup completed"));
166
+ };
167
+ socket.onerror = () => {
168
+ errored = true;
169
+ this.connected = false;
170
+ this.setupComplete = false;
171
+ const error = new Error("Gemini realtime WebSocket connection error");
172
+ this.onError(error);
173
+ reject(error);
174
+ };
175
+ socket.onopen = () => {
176
+ this.connected = true;
177
+ this.onOpen();
178
+ resolve();
179
+ };
180
+ socket.onmessage = (event) => {
181
+ void this.onReceiveMessage(event);
182
+ };
183
+ });
184
+ }
185
+ disconnect() {
186
+ if (this.webSocket) {
187
+ this.webSocket.onclose = null;
188
+ this.webSocket.onerror = null;
189
+ this.webSocket.onopen = null;
190
+ this.webSocket.onmessage = null;
191
+ this.webSocket.close();
192
+ this.webSocket = null;
193
+ }
194
+ this.connected = false;
195
+ this.setupComplete = false;
196
+ }
197
+ /**
198
+ * Session management
199
+ */
200
+ sendInitialSetupMessage(resume = false) {
201
+ const tools = this.functionDeclarations;
202
+ const setup = {
203
+ model: `models/${this.model}`,
204
+ generationConfig: {
205
+ responseModalities: this.responseModalities,
206
+ temperature: this.temperature,
207
+ speechConfig: {
208
+ languageCode: this.speechLanguageCode,
209
+ voiceConfig: {
210
+ prebuiltVoiceConfig: {
211
+ voiceName: this.voiceName
212
+ }
213
+ }
214
+ },
215
+ enableAffectiveDialog: this.enableAffectiveDialog,
216
+ maxOutputTokens: this.maxOutputTokens,
217
+ thinkingConfig: this.thinkingConfig
218
+ },
219
+ sessionResumption: {
220
+ transparent: true,
221
+ handle: resume ? this.lastResumptionUpdate?.newHandle : void 0
222
+ },
223
+ contextWindowCompression: this.contextWindowCompression,
224
+ proactivity: {
225
+ proactiveAudio: this.proactiveAudio
226
+ },
227
+ systemInstruction: { parts: [{ text: this.systemInstructions }] },
228
+ tools: [{ functionDeclarations: tools }],
229
+ realtimeInputConfig: {
230
+ automaticActivityDetection: {
231
+ disabled: this.automaticActivityDetection.disabled,
232
+ silenceDurationMs: this.automaticActivityDetection.silence_duration_ms,
233
+ prefixPaddingMs: this.automaticActivityDetection.prefix_padding_ms,
234
+ endOfSpeechSensitivity: this.automaticActivityDetection.end_of_speech_sensitivity,
235
+ startOfSpeechSensitivity: this.automaticActivityDetection.start_of_speech_sensitivity
236
+ },
237
+ activityHandling: this.activityHandling,
238
+ turnCoverage: TurnCoverage.TURN_INCLUDES_ONLY_ACTIVITY
239
+ }
240
+ };
241
+ if (this.inputAudioTranscription) {
242
+ setup.inputAudioTranscription = {};
243
+ }
244
+ if (this.outputAudioTranscription) {
245
+ setup.outputAudioTranscription = {};
246
+ }
247
+ if (this.googleGrounding) {
248
+ console.warn(
249
+ "Google Grounding enabled, removing custom function calls if any."
250
+ );
251
+ setup.tools = [{ googleSearch: {} }];
252
+ }
253
+ this.sendMessage({ setup });
254
+ }
255
+ async restartSession(resume = false) {
256
+ this.disconnect();
257
+ await this.connect();
258
+ this.sendInitialSetupMessage(resume);
259
+ }
260
+ updateToken(token) {
261
+ this.token = token.token;
262
+ const resume = !(token.config.model && this.model != token.config.model);
263
+ if (!resume) {
264
+ this.model = token.config.model;
265
+ }
266
+ this.restartSession(resume).catch(
267
+ (err) => this.onError(err instanceof Error ? err : new Error(String(err)))
268
+ );
269
+ }
270
+ async updateSession(config) {
271
+ if (config.model && !this.setupComplete) {
272
+ this.model = config.model;
273
+ }
274
+ if (config.instructions) {
275
+ this.systemInstructions = config.instructions;
276
+ }
277
+ if (config.tools) {
278
+ this.functionDeclarations = config.tools.map(toolConfigToDeclaration);
279
+ }
280
+ if (config.maxOutputTokens) {
281
+ this.maxOutputTokens = typeof config.maxOutputTokens === "number" ? config.maxOutputTokens : void 0;
282
+ }
283
+ if (config.temperature) {
284
+ this.temperature = config.temperature;
285
+ }
286
+ if (config.voice) {
287
+ this.voiceName = config.voice;
288
+ }
289
+ const providerOptions = config.providerOptions;
290
+ if (providerOptions?.googleGrounding) {
291
+ this.googleGrounding = providerOptions.googleGrounding;
292
+ }
293
+ if (providerOptions?.proactiveAudio) {
294
+ this.proactiveAudio = providerOptions.proactiveAudio;
295
+ }
296
+ if (providerOptions?.enableAffectiveDialog) {
297
+ this.enableAffectiveDialog = providerOptions.enableAffectiveDialog;
298
+ }
299
+ if (providerOptions?.contextWindowCompression) {
300
+ this.contextWindowCompression = providerOptions.contextWindowCompression;
301
+ }
302
+ if (providerOptions?.thinkingConfig) {
303
+ this.thinkingConfig = providerOptions.thinkingConfig;
304
+ }
305
+ if (providerOptions?.languageCode) {
306
+ this.speechLanguageCode = providerOptions.languageCode;
307
+ }
308
+ const includeTranscription = config.outputModalities?.includes("text") || false;
309
+ this.inputAudioTranscription = includeTranscription;
310
+ this.outputAudioTranscription = includeTranscription;
311
+ if (!this.setupComplete) {
312
+ this.sendInitialSetupMessage();
313
+ } else {
314
+ return this.restartSession(true);
315
+ }
316
+ }
317
+ /**
318
+ * Message transmission & receiving
319
+ */
320
+ sendMessage(message) {
321
+ if (this.webSocket?.readyState === WebSocket.OPEN) {
322
+ this.webSocket.send(JSON.stringify(message));
323
+ } else {
324
+ this.onError(
325
+ new Error("Cannot send message: Gemini realtime socket is not open")
326
+ );
327
+ }
328
+ }
329
+ async onReceiveMessage(messageEvent) {
330
+ let jsonData;
331
+ if (messageEvent.data instanceof Blob) {
332
+ jsonData = await messageEvent.data.text();
333
+ } else if (messageEvent.data instanceof ArrayBuffer) {
334
+ jsonData = new TextDecoder().decode(messageEvent.data);
335
+ } else {
336
+ jsonData = messageEvent.data;
337
+ }
338
+ try {
339
+ const messageData = JSON.parse(jsonData);
340
+ const responses = parseResponseMessages(messageData);
341
+ for (const response of responses) {
342
+ if (response.type === "session_resumption_update" && response.data.resumable) {
343
+ this.lastResumptionUpdate = response.data;
344
+ }
345
+ if (response.type === "setup_complete") {
346
+ this.setupComplete = true;
347
+ }
348
+ this.onReceiveResponse(response);
349
+ }
350
+ } catch (err) {
351
+ this.onError(err instanceof Error ? err : new Error(String(err)));
352
+ }
353
+ }
354
+ sendRealtimeInputMessage(data, mimeType) {
355
+ const blob = { mimeType, data };
356
+ if (mimeType.startsWith("audio/")) {
357
+ this.sendMessage({ realtimeInput: { audio: blob } });
358
+ } else if (mimeType.startsWith("image/") || mimeType.startsWith("video/")) {
359
+ this.sendMessage({ realtimeInput: { video: blob } });
360
+ }
361
+ }
362
+ sendAudioMessage(base64PCM) {
363
+ this.sendRealtimeInputMessage(base64PCM, "audio/pcm");
364
+ }
365
+ sendImageMessage(base64, mimeType = "image/jpeg") {
366
+ this.sendRealtimeInputMessage(base64, mimeType);
367
+ }
368
+ sendTextMessage(text) {
369
+ const message = {
370
+ realtimeInput: {
371
+ text
372
+ }
373
+ };
374
+ this.sendMessage(message);
375
+ }
376
+ sendToolResponse(functionResponses) {
377
+ const message = {
378
+ toolResponse: {
379
+ functionResponses
380
+ }
381
+ };
382
+ this.sendMessage(message);
383
+ }
384
+ }
385
+ export {
386
+ GeminiLiveClient,
387
+ parseResponseMessages
388
+ };
389
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sources":["../../../src/realtime/client.ts"],"sourcesContent":["import { convertSchemaToJsonSchema } from '@tanstack/ai'\nimport {\n ActivityHandling,\n EndSensitivity,\n Modality,\n StartSensitivity,\n TurnCoverage,\n} from '@google/genai'\nimport type {\n ContextWindowCompressionConfig,\n FunctionDeclaration,\n FunctionResponse,\n LiveClientMessage,\n LiveServerGoAway,\n LiveServerMessage,\n LiveServerSessionResumptionUpdate,\n LiveServerToolCall,\n ThinkingConfig,\n UsageMetadata,\n} from '@google/genai'\nimport type {\n AnyClientTool,\n RealtimeSessionConfig,\n RealtimeToken,\n RealtimeToolConfig,\n} from '@tanstack/ai'\nimport type {\n GeminiRealtimeModel,\n GeminiRealtimeProviderOptions,\n GeminiRealtimeVoice,\n} from './types'\n\n/** Build a Gemini FunctionDeclaration from an isomorphic client tool (Zod). */\nfunction clientToolToDeclaration(tool: AnyClientTool): FunctionDeclaration {\n return {\n name: tool.name,\n description: tool.description,\n parametersJsonSchema: convertSchemaToJsonSchema(tool.inputSchema),\n responseJsonSchema: convertSchemaToJsonSchema(tool.outputSchema),\n }\n}\n\n/** Build a Gemini FunctionDeclaration from an already-serialized tool config. */\nfunction toolConfigToDeclaration(\n tool: RealtimeToolConfig,\n): FunctionDeclaration {\n return {\n name: tool.name,\n description: tool.description,\n parametersJsonSchema: tool.inputSchema,\n responseJsonSchema: tool.outputSchema,\n }\n}\n\ninterface LiveResponsePayloads {\n text: string\n thought: string\n audio: { audioData: string; transcript: string }\n setup_complete: string\n interrupted: string\n turn_complete: string\n tool_call: LiveServerToolCall\n session_resumption_update: LiveServerSessionResumptionUpdate\n go_away: LiveServerGoAway\n usage_metadata: UsageMetadata\n error: string\n input_transcription: { text: string; finished: boolean }\n output_transcription: { text: string; finished: boolean }\n}\n\nexport type MultimodalLiveResponseType = keyof LiveResponsePayloads\n\nexport type LiveResponse = {\n [K in MultimodalLiveResponseType]: {\n type: K\n data: LiveResponsePayloads[K]\n endOfTurn: boolean\n }\n}[MultimodalLiveResponseType]\n\n/**\n * Parses response messages from the Gemini Live API\n */\n/**\n * Parses ALL response types from a single server message.\n * The server can now bundle multiple fields (e.g. audio + transcription)\n * in the same message. Returns an array of response objects.\n */\nexport function parseResponseMessages(\n data: LiveServerMessage,\n): Array<LiveResponse> {\n const responses: Array<LiveResponse> = []\n const serverContent = data.serverContent\n const parts = serverContent?.modelTurn?.parts\n\n // Setup complete (exclusive — no other fields expected)\n if (data.setupComplete) {\n responses.push({ type: 'setup_complete', data: '', endOfTurn: false })\n return responses\n }\n\n // Tool call (exclusive)\n if (data.toolCall) {\n responses.push({\n type: 'tool_call',\n data: data.toolCall,\n endOfTurn: false,\n })\n return responses\n }\n\n if (data.sessionResumptionUpdate) {\n responses.push({\n type: 'session_resumption_update',\n data: data.sessionResumptionUpdate,\n endOfTurn: false,\n })\n }\n\n if (data.goAway) {\n responses.push({ type: 'go_away', data: data.goAway, endOfTurn: false })\n }\n\n if (data.usageMetadata) {\n responses.push({\n type: 'usage_metadata',\n data: data.usageMetadata,\n endOfTurn: false,\n })\n }\n\n // Audio data from model turn parts\n if (parts?.length) {\n for (const part of parts) {\n if (part.inlineData?.data) {\n responses.push({\n type: 'audio',\n data: {\n audioData: part.inlineData.data,\n // The transcription is independent to the model turn, which means it doesn't imply any ordering between transcription and model turn.\n transcript: '',\n },\n endOfTurn: false,\n })\n } else if (part.text) {\n responses.push({\n type: part.thought ? 'thought' : 'text',\n data: part.text,\n endOfTurn: false,\n })\n }\n }\n }\n\n // Transcriptions — checked independently, NOT in else-if with audio\n if (serverContent?.inputTranscription) {\n responses.push({\n type: 'input_transcription',\n data: {\n text: serverContent.inputTranscription.text || '',\n finished: serverContent.inputTranscription.finished || false,\n },\n endOfTurn: false,\n })\n }\n\n if (serverContent?.outputTranscription) {\n responses.push({\n type: 'output_transcription',\n data: {\n text: serverContent.outputTranscription.text || '',\n finished: serverContent.outputTranscription.finished || false,\n },\n endOfTurn: false,\n })\n }\n\n // Interrupted\n if (serverContent?.interrupted) {\n responses.push({ type: 'interrupted', data: '', endOfTurn: false })\n }\n\n // Turn complete\n if (serverContent?.turnComplete) {\n responses.push({ type: 'turn_complete', data: '', endOfTurn: true })\n }\n\n return responses\n}\n\nexport class GeminiLiveClient {\n private token: string | null = null\n private model: GeminiRealtimeModel | null = null\n\n private readonly responseModalities: Array<Modality> = [Modality.AUDIO]\n private systemInstructions = ''\n private googleGrounding = false\n private voiceName: GeminiRealtimeVoice = 'Puck'\n private temperature = 1.0\n private inputAudioTranscription = false\n private outputAudioTranscription = false\n private contextWindowCompression: ContextWindowCompressionConfig | undefined\n private proactiveAudio = false\n private enableAffectiveDialog = false\n private thinkingConfig: ThinkingConfig | undefined\n private speechLanguageCode: string | undefined\n\n private maxOutputTokens: number | undefined\n private functionDeclarations: Array<FunctionDeclaration> = []\n\n private readonly automaticActivityDetection = {\n disabled: false,\n silence_duration_ms: 2000,\n prefix_padding_ms: 500,\n end_of_speech_sensitivity: EndSensitivity.END_SENSITIVITY_UNSPECIFIED,\n start_of_speech_sensitivity: StartSensitivity.START_SENSITIVITY_UNSPECIFIED,\n }\n\n private readonly activityHandling =\n ActivityHandling.ACTIVITY_HANDLING_UNSPECIFIED\n\n private webSocket: WebSocket | null = null\n private lastResumptionUpdate: LiveServerSessionResumptionUpdate | null = null\n private setupComplete = false\n private connected = false\n\n public onReceiveResponse: (response: LiveResponse) => void = () => {}\n public onOpen: () => void = () => {}\n public onClose: () => void = () => {}\n public onError: (error: Error) => void = () => {}\n\n constructor(\n token: string,\n model: GeminiRealtimeModel,\n tools?: ReadonlyArray<AnyClientTool>,\n ) {\n this.token = token\n this.model = model\n\n if (tools) {\n this.functionDeclarations = tools.map(clientToolToDeclaration)\n }\n }\n\n get isConnected() {\n return this.connected\n }\n\n get isSetupComplete() {\n return this.setupComplete\n }\n\n /**\n * Connection management\n */\n connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n const socket = new WebSocket(\n `wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1alpha.GenerativeService.BidiGenerateContentConstrained?access_token=${this.token}`,\n )\n this.webSocket = socket\n\n // The browser fires `onerror` then `onclose` on an abnormal close; this\n // flag stops the close handler from overwriting the surfaced error with a\n // benign \"closed\" signal.\n let errored = false\n\n socket.onclose = () => {\n this.connected = false\n this.setupComplete = false\n if (!errored) this.onClose()\n reject(new Error('WebSocket closed before setup completed'))\n }\n\n socket.onerror = () => {\n errored = true\n this.connected = false\n this.setupComplete = false\n const error = new Error('Gemini realtime WebSocket connection error')\n this.onError(error)\n reject(error)\n }\n\n socket.onopen = () => {\n this.connected = true\n this.onOpen()\n resolve()\n }\n\n socket.onmessage = (event) => {\n void this.onReceiveMessage(event)\n }\n })\n }\n\n disconnect() {\n if (this.webSocket) {\n // Detach handlers first so a deliberate teardown (e.g. during a\n // reconnect) doesn't emit a spurious close/error to the client.\n this.webSocket.onclose = null\n this.webSocket.onerror = null\n this.webSocket.onopen = null\n this.webSocket.onmessage = null\n this.webSocket.close()\n this.webSocket = null\n }\n this.connected = false\n this.setupComplete = false\n }\n\n /**\n * Session management\n */\n sendInitialSetupMessage(resume = false) {\n const tools = this.functionDeclarations\n\n const setup: NonNullable<LiveClientMessage['setup']> = {\n model: `models/${this.model}`,\n generationConfig: {\n responseModalities: this.responseModalities,\n temperature: this.temperature,\n speechConfig: {\n languageCode: this.speechLanguageCode,\n voiceConfig: {\n prebuiltVoiceConfig: {\n voiceName: this.voiceName,\n },\n },\n },\n enableAffectiveDialog: this.enableAffectiveDialog,\n maxOutputTokens: this.maxOutputTokens,\n thinkingConfig: this.thinkingConfig,\n },\n sessionResumption: {\n transparent: true,\n handle: resume ? this.lastResumptionUpdate?.newHandle : undefined,\n },\n contextWindowCompression: this.contextWindowCompression,\n proactivity: {\n proactiveAudio: this.proactiveAudio,\n },\n systemInstruction: { parts: [{ text: this.systemInstructions }] },\n tools: [{ functionDeclarations: tools }],\n realtimeInputConfig: {\n automaticActivityDetection: {\n disabled: this.automaticActivityDetection.disabled,\n silenceDurationMs:\n this.automaticActivityDetection.silence_duration_ms,\n prefixPaddingMs: this.automaticActivityDetection.prefix_padding_ms,\n endOfSpeechSensitivity:\n this.automaticActivityDetection.end_of_speech_sensitivity,\n startOfSpeechSensitivity:\n this.automaticActivityDetection.start_of_speech_sensitivity,\n },\n activityHandling: this.activityHandling,\n turnCoverage: TurnCoverage.TURN_INCLUDES_ONLY_ACTIVITY,\n },\n }\n\n if (this.inputAudioTranscription) {\n setup.inputAudioTranscription = {}\n }\n\n if (this.outputAudioTranscription) {\n setup.outputAudioTranscription = {}\n }\n\n if (this.googleGrounding) {\n // Currently can't have both Google Search with custom tools.\n console.warn(\n 'Google Grounding enabled, removing custom function calls if any.',\n )\n setup.tools = [{ googleSearch: {} }]\n }\n\n this.sendMessage({ setup })\n }\n\n async restartSession(resume = false) {\n this.disconnect()\n await this.connect()\n this.sendInitialSetupMessage(resume)\n }\n\n updateToken(token: RealtimeToken) {\n this.token = token.token\n\n // Restart completely with the new model, or resume the existing session.\n const resume = !(token.config.model && this.model != token.config.model)\n if (!resume) {\n this.model = token.config.model as GeminiRealtimeModel\n }\n this.restartSession(resume).catch((err) =>\n this.onError(err instanceof Error ? err : new Error(String(err))),\n )\n }\n\n async updateSession(config: Partial<RealtimeSessionConfig>) {\n // model can only be set during initial setup\n if (config.model && !this.setupComplete) {\n this.model = config.model as GeminiRealtimeModel\n }\n\n if (config.instructions) {\n this.systemInstructions = config.instructions\n }\n\n if (config.tools) {\n this.functionDeclarations = config.tools.map(toolConfigToDeclaration)\n }\n\n if (config.maxOutputTokens) {\n // Gemini has no \"inf\" sentinel; treat it as \"no explicit limit\".\n this.maxOutputTokens =\n typeof config.maxOutputTokens === 'number'\n ? config.maxOutputTokens\n : undefined\n }\n\n if (config.temperature) {\n this.temperature = config.temperature\n }\n\n if (config.voice) {\n this.voiceName = config.voice as GeminiRealtimeVoice\n }\n\n const providerOptions = config.providerOptions as\n | GeminiRealtimeProviderOptions\n | undefined\n\n if (providerOptions?.googleGrounding) {\n this.googleGrounding = providerOptions.googleGrounding\n }\n\n if (providerOptions?.proactiveAudio) {\n this.proactiveAudio = providerOptions.proactiveAudio\n }\n\n if (providerOptions?.enableAffectiveDialog) {\n this.enableAffectiveDialog = providerOptions.enableAffectiveDialog\n }\n\n if (providerOptions?.contextWindowCompression) {\n this.contextWindowCompression = providerOptions.contextWindowCompression\n }\n\n if (providerOptions?.thinkingConfig) {\n this.thinkingConfig = providerOptions.thinkingConfig\n }\n\n if (providerOptions?.languageCode) {\n this.speechLanguageCode = providerOptions.languageCode\n }\n\n const includeTranscription =\n config.outputModalities?.includes('text') || false\n this.inputAudioTranscription = includeTranscription\n this.outputAudioTranscription = includeTranscription\n\n if (!this.setupComplete) {\n this.sendInitialSetupMessage()\n } else {\n return this.restartSession(true)\n }\n }\n\n /**\n * Message transmission & receiving\n */\n sendMessage(message: LiveClientMessage) {\n if (this.webSocket?.readyState === WebSocket.OPEN) {\n this.webSocket.send(JSON.stringify(message))\n } else {\n this.onError(\n new Error('Cannot send message: Gemini realtime socket is not open'),\n )\n }\n }\n\n async onReceiveMessage(messageEvent: MessageEvent) {\n let jsonData\n if (messageEvent.data instanceof Blob) {\n jsonData = await messageEvent.data.text()\n } else if (messageEvent.data instanceof ArrayBuffer) {\n jsonData = new TextDecoder().decode(messageEvent.data)\n } else {\n jsonData = messageEvent.data\n }\n\n try {\n const messageData = JSON.parse(jsonData)\n // Parse all response types from this message (audio + transcription can coexist)\n const responses = parseResponseMessages(messageData)\n for (const response of responses) {\n if (\n response.type === 'session_resumption_update' &&\n response.data.resumable\n ) {\n this.lastResumptionUpdate = response.data\n }\n if (response.type === 'setup_complete') {\n this.setupComplete = true\n }\n this.onReceiveResponse(response)\n }\n } catch (err) {\n this.onError(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n sendRealtimeInputMessage(data: string, mimeType: string) {\n const blob = { mimeType, data }\n\n if (mimeType.startsWith('audio/')) {\n this.sendMessage({ realtimeInput: { audio: blob } })\n } else if (mimeType.startsWith('image/') || mimeType.startsWith('video/')) {\n this.sendMessage({ realtimeInput: { video: blob } })\n }\n }\n\n sendAudioMessage(base64PCM: string) {\n this.sendRealtimeInputMessage(base64PCM, 'audio/pcm')\n }\n\n sendImageMessage(base64: string, mimeType = 'image/jpeg') {\n this.sendRealtimeInputMessage(base64, mimeType)\n }\n\n sendTextMessage(text: string) {\n const message: LiveClientMessage = {\n realtimeInput: {\n text,\n },\n }\n this.sendMessage(message)\n }\n\n sendToolResponse(functionResponses: Array<FunctionResponse>) {\n const message: LiveClientMessage = {\n toolResponse: {\n functionResponses,\n },\n }\n this.sendMessage(message)\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { geminiRealtimeToken } from './token.js';
2
+ export { geminiRealtime } from './adapter.js';
3
+ export type { GeminiRealtimeModel, GeminiRealtimeVoice, GeminiRealtimeTokenOptions, GeminiRealtimeOptions, GeminiRealtimeProviderOptions, } from './types.js';
@@ -0,0 +1,26 @@
1
+ import { RealtimeTokenAdapter } from '@tanstack/ai';
2
+ import { GeminiRealtimeTokenOptions } from './types.js';
3
+ /**
4
+ * Creates a Google Gemini realtime token adapter.
5
+ *
6
+ * This adapter generates ephemeral tokens for client-side WebSocket connections.
7
+ *
8
+ * @param options - Configuration options for the realtime session
9
+ * @returns A RealtimeTokenAdapter for use with realtimeToken()
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { realtimeToken } from '@tanstack/ai'
14
+ * import { geminiRealtimeToken } from '@tanstack/ai-gemini'
15
+ *
16
+ * const token = await realtimeToken({
17
+ * adapter: geminiRealtimeToken({
18
+ * // Optional: constraint model config by token
19
+ * liveConnectConstraints: {
20
+ * model: 'gemini-3.1-flash-live-preview',
21
+ * },
22
+ * }),
23
+ * })
24
+ * ```
25
+ */
26
+ export declare function geminiRealtimeToken(options?: GeminiRealtimeTokenOptions): RealtimeTokenAdapter;
@@ -0,0 +1,39 @@
1
+ import { GoogleGenAI } from "@google/genai";
2
+ import { getGeminiApiKeyFromEnv } from "../utils/client.js";
3
+ function geminiRealtimeToken(options = {}) {
4
+ const apiKey = getGeminiApiKeyFromEnv();
5
+ const client = new GoogleGenAI({
6
+ apiKey
7
+ });
8
+ return {
9
+ provider: "gemini",
10
+ async generateToken() {
11
+ const expireTime = options.expiresAt ?? Date.now() + 30 * 60 * 1e3;
12
+ const token = await client.authTokens.create({
13
+ config: {
14
+ uses: options.uses ?? 1,
15
+ expireTime: new Date(expireTime).toISOString(),
16
+ liveConnectConstraints: options.liveConnectConstraints,
17
+ httpOptions: {
18
+ apiVersion: "v1alpha"
19
+ }
20
+ }
21
+ });
22
+ if (!token.name) {
23
+ throw new Error("Gemini realtime token creation failed");
24
+ }
25
+ return {
26
+ provider: "gemini",
27
+ token: token.name,
28
+ expiresAt: expireTime,
29
+ config: {
30
+ model: options.liveConnectConstraints?.model
31
+ }
32
+ };
33
+ }
34
+ };
35
+ }
36
+ export {
37
+ geminiRealtimeToken
38
+ };
39
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","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-flash-live-preview',\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"],"names":[],"mappings":";;AA4BO,SAAS,oBACd,UAAsC,IAChB;AACtB,QAAM,SAAS,uBAAA;AAEf,QAAM,SAAS,IAAI,YAAY;AAAA,IAC7B;AAAA,EAAA,CACD;AAED,SAAO;AAAA,IACL,UAAU;AAAA,IACV,MAAM,gBAAwC;AAG5C,YAAM,aAAa,QAAQ,aAAa,KAAK,QAAQ,KAAK,KAAK;AAE/D,YAAM,QAAQ,MAAM,OAAO,WAAW,OAAO;AAAA,QAC3C,QAAQ;AAAA,UACN,MAAM,QAAQ,QAAQ;AAAA,UACtB,YAAY,IAAI,KAAK,UAAU,EAAE,YAAA;AAAA,UACjC,wBAAwB,QAAQ;AAAA,UAChC,aAAa;AAAA,YACX,YAAY;AAAA,UAAA;AAAA,QACd;AAAA,MACF,CACD;AAED,UAAI,CAAC,MAAM,MAAM;AACf,cAAM,IAAI,MAAM,uCAAuC;AAAA,MACzD;AAEA,aAAO;AAAA,QACL,UAAU;AAAA,QACV,OAAO,MAAM;AAAA,QACb,WAAW;AAAA,QACX,QAAQ;AAAA,UACN,OAAO,QAAQ,wBAAwB;AAAA,QAAA;AAAA,MACzC;AAAA,IAEJ;AAAA,EAAA;AAEJ;"}
@@ -0,0 +1,51 @@
1
+ import { ContextWindowCompressionConfig, LiveConnectConstraints, ThinkingConfig } from '@google/genai';
2
+ /**
3
+ * Gemini realtime voice options
4
+ */
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
+ /**
7
+ * Gemini realtime model options
8
+ */
9
+ export type GeminiRealtimeModel = 'gemini-3.1-flash-live-preview';
10
+ /**
11
+ * Options for the Gemini realtime client adapter
12
+ */
13
+ export interface GeminiRealtimeOptions {
14
+ /** Connection mode (default: 'websocket' in browser) */
15
+ connectionMode?: 'websocket';
16
+ model?: GeminiRealtimeModel;
17
+ }
18
+ export interface StrictLiveConnectionConstraints extends Omit<LiveConnectConstraints, 'model'> {
19
+ model?: GeminiRealtimeModel;
20
+ }
21
+ /**
22
+ * Options for the Gemini realtime token adapter
23
+ */
24
+ export interface GeminiRealtimeTokenOptions {
25
+ expiresAt?: number;
26
+ uses?: number;
27
+ /**
28
+ * Config for LiveConnectConstraints for Auth Token creation.
29
+ *
30
+ * NOTE: Adding liveConnectConstraints will cause the API to ignore any config passed later to WebSocket.
31
+ */
32
+ liveConnectConstraints?: StrictLiveConnectionConstraints;
33
+ }
34
+ /**
35
+ * Gemini-specific realtime options, passed through `providerOptions` on a
36
+ * realtime session config.
37
+ */
38
+ export interface GeminiRealtimeProviderOptions {
39
+ /** Enable Google Search grounding (mutually exclusive with custom tools). */
40
+ googleGrounding?: boolean;
41
+ /** Enable proactive audio so the model may choose when to respond. */
42
+ proactiveAudio?: boolean;
43
+ /** Enable affective (emotion-aware) dialog. */
44
+ enableAffectiveDialog?: boolean;
45
+ /** Context window compression configuration. */
46
+ contextWindowCompression?: ContextWindowCompressionConfig;
47
+ /** Thinking configuration. */
48
+ thinkingConfig?: ThinkingConfig;
49
+ /** BCP-47 language code for speech output. */
50
+ languageCode?: string;
51
+ }
@@ -0,0 +1,40 @@
1
+ import { GeminiLiveClient } from './client.js';
2
+ export declare function base64ToArrayBuffer(base64: string): ArrayBuffer;
3
+ export declare class AudioStreamer {
4
+ private audioContext;
5
+ private audioWorklet;
6
+ private mediaStream;
7
+ private analyser;
8
+ private isStreaming;
9
+ private readonly sampleRate;
10
+ private readonly client;
11
+ constructor(client: GeminiLiveClient);
12
+ get inputLevel(): number;
13
+ get inputFrequencyData(): Uint8Array<ArrayBuffer>;
14
+ get inputTimeDomainData(): Uint8Array<ArrayBuffer>;
15
+ get inputSampleRate(): number;
16
+ start(): Promise<void>;
17
+ stop(): void;
18
+ startAudioCapture(): void;
19
+ stopAudioCapture(): void;
20
+ private convertToPCM16;
21
+ private arrayBufferToBase64;
22
+ }
23
+ export declare class AudioPlayer {
24
+ private audioContext;
25
+ private workletNode;
26
+ private gainNode;
27
+ private analyser;
28
+ private isInitialized;
29
+ private volume;
30
+ private readonly sampleRate;
31
+ get outputLevel(): number;
32
+ get outputFrequencyData(): Uint8Array<ArrayBuffer>;
33
+ get outputTimeDomainData(): Uint8Array<ArrayBuffer>;
34
+ get outputSampleRate(): number;
35
+ init(): Promise<void>;
36
+ play(pcmData: ArrayBuffer): Promise<void>;
37
+ interrupt(): void;
38
+ setVolume(volume: number): void;
39
+ destroy(): void;
40
+ }