@firebase/ai 2.11.1 → 2.12.0-20260505131936

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.
@@ -4,7 +4,7 @@ import { FirebaseError, Deferred, getModularInstance } from '@firebase/util';
4
4
  import { Logger } from '@firebase/logger';
5
5
 
6
6
  var name = "@firebase/ai";
7
- var version = "2.11.1";
7
+ var version = "2.12.0-20260505131936";
8
8
 
9
9
  /**
10
10
  * @license
@@ -277,7 +277,78 @@ const FinishReason = {
277
277
  /**
278
278
  * The function call generated by the model was invalid.
279
279
  */
280
- MALFORMED_FUNCTION_CALL: 'MALFORMED_FUNCTION_CALL'
280
+ MALFORMED_FUNCTION_CALL: 'MALFORMED_FUNCTION_CALL',
281
+ /**
282
+ * Token generation stopped because generated images contain safety violations.
283
+ */
284
+ IMAGE_SAFETY: 'IMAGE_SAFETY',
285
+ /**
286
+ * Image generation stopped because generated images have other prohibited content.
287
+ */
288
+ IMAGE_PROHIBITED_CONTENT: 'IMAGE_PROHIBITED_CONTENT',
289
+ /**
290
+ * Image generation stopped because of other miscellaneous issue.
291
+ */
292
+ IMAGE_OTHER: 'IMAGE_OTHER',
293
+ /**
294
+ * The model was expected to generate an image, but none was generated.
295
+ */
296
+ NO_IMAGE: 'NO_IMAGE',
297
+ /**
298
+ * Image generation stopped due to recitation.
299
+ */
300
+ IMAGE_RECITATION: 'IMAGE_RECITATION',
301
+ /**
302
+ * The response candidate content was flagged for using an unsupported language.
303
+ */
304
+ LANGUAGE: 'LANGUAGE',
305
+ /**
306
+ * Model generated a tool call but no tools were enabled in the request.
307
+ */
308
+ UNEXPECTED_TOOL_CALL: 'UNEXPECTED_TOOL_CALL',
309
+ /**
310
+ * Model called too many tools consecutively, thus the system exited execution.
311
+ */
312
+ TOO_MANY_TOOL_CALLS: 'TOO_MANY_TOOL_CALLS',
313
+ /**
314
+ * Request has at least one thought signature missing.
315
+ */
316
+ MISSING_THOUGHT_SIGNATURE: 'MISSING_THOUGHT_SIGNATURE',
317
+ /**
318
+ * Finished due to malformed response.
319
+ */
320
+ MALFORMED_RESPONSE: 'MALFORMED_RESPONSE'
321
+ };
322
+ /**
323
+ * Aspect ratios for generated images.
324
+ * @public
325
+ */
326
+ /* eslint-disable camelcase */
327
+ const ImageConfigAspectRatio = {
328
+ SQUARE_1x1: '1:1',
329
+ PORTRAIT_9x16: '9:16',
330
+ LANDSCAPE_16x9: '16:9',
331
+ PORTRAIT_3x4: '3:4',
332
+ LANDSCAPE_4x3: '4:3',
333
+ PORTRAIT_2x3: '2:3',
334
+ LANDSCAPE_3x2: '3:2',
335
+ PORTRAIT_4x5: '4:5',
336
+ LANDSCAPE_5x4: '5:4',
337
+ PORTRAIT_1x4: '1:4',
338
+ LANDSCAPE_4x1: '4:1',
339
+ PORTRAIT_1x8: '1:8',
340
+ LANDSCAPE_8x1: '8:1',
341
+ ULTRAWIDE_21x9: '21:9'
342
+ };
343
+ /**
344
+ * Sizes for generated images. For example, '1K' is 1024px, '2K' is 2048px, and '4K' is 4096px.
345
+ * @public
346
+ */
347
+ const ImageConfigImageSize = {
348
+ SIZE_512: '512',
349
+ SIZE_1K: '1K',
350
+ SIZE_2K: '2K',
351
+ SIZE_4K: '4K'
281
352
  };
282
353
  /**
283
354
  * @public
@@ -487,7 +558,8 @@ const LiveResponseType = {
487
558
  SERVER_CONTENT: 'serverContent',
488
559
  TOOL_CALL: 'toolCall',
489
560
  TOOL_CALL_CANCELLATION: 'toolCallCancellation',
490
- GOING_AWAY_NOTICE: 'goingAwayNotice'
561
+ GOING_AWAY_NOTICE: 'goingAwayNotice',
562
+ SESSION_RESUMPTION_UPDATE: 'sessionResumptionUpdate'
491
563
  };
492
564
 
493
565
  /**
@@ -1522,7 +1594,24 @@ function getInlineDataParts(response) {
1522
1594
  return undefined;
1523
1595
  }
1524
1596
  }
1525
- const badFinishReasons = [FinishReason.RECITATION, FinishReason.SAFETY];
1597
+ const badFinishReasons = [
1598
+ FinishReason.RECITATION,
1599
+ FinishReason.SAFETY,
1600
+ FinishReason.BLOCKLIST,
1601
+ FinishReason.PROHIBITED_CONTENT,
1602
+ FinishReason.SPII,
1603
+ FinishReason.MALFORMED_FUNCTION_CALL,
1604
+ FinishReason.IMAGE_SAFETY,
1605
+ FinishReason.IMAGE_PROHIBITED_CONTENT,
1606
+ FinishReason.IMAGE_OTHER,
1607
+ FinishReason.NO_IMAGE,
1608
+ FinishReason.IMAGE_RECITATION,
1609
+ FinishReason.LANGUAGE,
1610
+ FinishReason.UNEXPECTED_TOOL_CALL,
1611
+ FinishReason.TOO_MANY_TOOL_CALLS,
1612
+ FinishReason.MISSING_THOUGHT_SIGNATURE,
1613
+ FinishReason.MALFORMED_RESPONSE
1614
+ ];
1526
1615
  function hadBadFinishReason(candidate) {
1527
1616
  return (!!candidate.finishReason &&
1528
1617
  badFinishReasons.some(reason => reason === candidate.finishReason));
@@ -2924,6 +3013,153 @@ function validateGenerationConfig(generationConfig) {
2924
3013
  }
2925
3014
  }
2926
3015
 
3016
+ /**
3017
+ * @license
3018
+ * Copyright 2025 Google LLC
3019
+ *
3020
+ * Licensed under the Apache License, Version 2.0 (the "License");
3021
+ * you may not use this file except in compliance with the License.
3022
+ * You may obtain a copy of the License at
3023
+ *
3024
+ * http://www.apache.org/licenses/LICENSE-2.0
3025
+ *
3026
+ * Unless required by applicable law or agreed to in writing, software
3027
+ * distributed under the License is distributed on an "AS IS" BASIS,
3028
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3029
+ * See the License for the specific language governing permissions and
3030
+ * limitations under the License.
3031
+ */
3032
+ /**
3033
+ * A wrapper for the native `WebSocket` available in both Browsers and Node >= 22.
3034
+ *
3035
+ * @internal
3036
+ */
3037
+ class WebSocketHandlerImpl {
3038
+ constructor() {
3039
+ if (typeof WebSocket === 'undefined') {
3040
+ throw new AIError(AIErrorCode.UNSUPPORTED, 'The WebSocket API is not available in this environment. ' +
3041
+ 'The "Live" feature is not supported here. It is supported in ' +
3042
+ 'modern browser windows, Web Workers with WebSocket support, and Node >= 22.');
3043
+ }
3044
+ }
3045
+ connect(url) {
3046
+ return new Promise((resolve, reject) => {
3047
+ this.ws = new WebSocket(url);
3048
+ this.ws.binaryType = 'blob'; // Only important to set in Node
3049
+ this.ws.addEventListener('open', () => resolve(), { once: true });
3050
+ this.ws.addEventListener('error', () => reject(new AIError(AIErrorCode.FETCH_ERROR, `Error event raised on WebSocket`)), { once: true });
3051
+ this.ws.addEventListener('close', (closeEvent) => {
3052
+ if (closeEvent.reason) {
3053
+ logger.warn(`WebSocket connection closed by server. Reason: '${closeEvent.reason}'`);
3054
+ }
3055
+ });
3056
+ });
3057
+ }
3058
+ send(data) {
3059
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
3060
+ throw new AIError(AIErrorCode.REQUEST_ERROR, 'WebSocket is not open.');
3061
+ }
3062
+ this.ws.send(data);
3063
+ }
3064
+ async *listen() {
3065
+ if (!this.ws) {
3066
+ throw new AIError(AIErrorCode.REQUEST_ERROR, 'WebSocket is not connected.');
3067
+ }
3068
+ const messageQueue = [];
3069
+ const errorQueue = [];
3070
+ let resolvePromise = null;
3071
+ let isClosed = false;
3072
+ const messageListener = async (event) => {
3073
+ let data;
3074
+ if (event.data instanceof Blob) {
3075
+ data = await event.data.text();
3076
+ }
3077
+ else if (typeof event.data === 'string') {
3078
+ data = event.data;
3079
+ }
3080
+ else {
3081
+ errorQueue.push(new AIError(AIErrorCode.PARSE_FAILED, `Failed to parse WebSocket response. Expected data to be a Blob or string, but was ${typeof event.data}.`));
3082
+ if (resolvePromise) {
3083
+ resolvePromise();
3084
+ resolvePromise = null;
3085
+ }
3086
+ return;
3087
+ }
3088
+ try {
3089
+ const obj = JSON.parse(data);
3090
+ messageQueue.push(obj);
3091
+ }
3092
+ catch (e) {
3093
+ const err = e;
3094
+ errorQueue.push(new AIError(AIErrorCode.PARSE_FAILED, `Error parsing WebSocket message to JSON: ${err.message}`));
3095
+ }
3096
+ if (resolvePromise) {
3097
+ resolvePromise();
3098
+ resolvePromise = null;
3099
+ }
3100
+ };
3101
+ const errorListener = () => {
3102
+ errorQueue.push(new AIError(AIErrorCode.FETCH_ERROR, 'WebSocket connection error.'));
3103
+ if (resolvePromise) {
3104
+ resolvePromise();
3105
+ resolvePromise = null;
3106
+ }
3107
+ };
3108
+ const closeListener = (event) => {
3109
+ if (event.reason) {
3110
+ logger.warn(`WebSocket connection closed by the server with reason: ${event.reason}`);
3111
+ }
3112
+ isClosed = true;
3113
+ if (resolvePromise) {
3114
+ resolvePromise();
3115
+ resolvePromise = null;
3116
+ }
3117
+ // Clean up listeners to prevent memory leaks
3118
+ this.ws?.removeEventListener('message', messageListener);
3119
+ this.ws?.removeEventListener('close', closeListener);
3120
+ this.ws?.removeEventListener('error', errorListener);
3121
+ };
3122
+ this.ws.addEventListener('message', messageListener);
3123
+ this.ws.addEventListener('close', closeListener);
3124
+ this.ws.addEventListener('error', errorListener);
3125
+ while (!isClosed) {
3126
+ if (errorQueue.length > 0) {
3127
+ const error = errorQueue.shift();
3128
+ throw error;
3129
+ }
3130
+ if (messageQueue.length > 0) {
3131
+ yield messageQueue.shift();
3132
+ }
3133
+ else {
3134
+ await new Promise(resolve => {
3135
+ resolvePromise = resolve;
3136
+ });
3137
+ }
3138
+ }
3139
+ // If the loop terminated because isClosed is true, check for any final errors
3140
+ if (errorQueue.length > 0) {
3141
+ const error = errorQueue.shift();
3142
+ throw error;
3143
+ }
3144
+ }
3145
+ close(code, reason) {
3146
+ return new Promise(resolve => {
3147
+ if (!this.ws) {
3148
+ return resolve();
3149
+ }
3150
+ this.ws.addEventListener('close', () => resolve(), { once: true });
3151
+ // Calling 'close' during these states results in an error.
3152
+ if (this.ws.readyState === WebSocket.CLOSED ||
3153
+ this.ws.readyState === WebSocket.CONNECTING) {
3154
+ return resolve();
3155
+ }
3156
+ if (this.ws.readyState !== WebSocket.CLOSING) {
3157
+ this.ws.close(code, reason);
3158
+ }
3159
+ });
3160
+ }
3161
+ }
3162
+
2927
3163
  /**
2928
3164
  * @license
2929
3165
  * Copyright 2025 Google LLC
@@ -2951,9 +3187,10 @@ class LiveSession {
2951
3187
  /**
2952
3188
  * @internal
2953
3189
  */
2954
- constructor(webSocketHandler, serverMessages) {
2955
- this.webSocketHandler = webSocketHandler;
2956
- this.serverMessages = serverMessages;
3190
+ constructor(_setupMessage, _apiSettings, _sessionResumption, webSocketHandler) {
3191
+ this._setupMessage = _setupMessage;
3192
+ this._apiSettings = _apiSettings;
3193
+ this._sessionResumption = _sessionResumption;
2957
3194
  /**
2958
3195
  * Indicates whether this Live session is closed.
2959
3196
  *
@@ -2966,6 +3203,64 @@ class LiveSession {
2966
3203
  * @beta
2967
3204
  */
2968
3205
  this.inConversation = false;
3206
+ /**
3207
+ * Generator yielding WebSocket messages from the server.
3208
+ */
3209
+ this._serverMessages = null;
3210
+ this._webSocketHandler = webSocketHandler || new WebSocketHandlerImpl();
3211
+ this.connectionPromise = this._connectSession(this._sessionResumption);
3212
+ }
3213
+ /**
3214
+ * Initializes connection to the WebSocket. Should be called immediately
3215
+ * after instantiation.
3216
+ *
3217
+ * @internal
3218
+ */
3219
+ async _connectSession(sessionResumption) {
3220
+ const url = new WebSocketUrl(this._apiSettings);
3221
+ await this._webSocketHandler.connect(url.toString());
3222
+ try {
3223
+ // Begin listening for server messages, and begin the handshake by sending the 'setupMessage'
3224
+ this._serverMessages = this._webSocketHandler.listen();
3225
+ const setupMessage = { ...this._setupMessage };
3226
+ if (sessionResumption) {
3227
+ setupMessage.setup.sessionResumption = sessionResumption;
3228
+ }
3229
+ this._webSocketHandler.send(JSON.stringify(setupMessage));
3230
+ // Verify we received the handshake response 'setupComplete'
3231
+ const firstMessage = (await this._serverMessages.next()).value;
3232
+ if (!firstMessage ||
3233
+ !(typeof firstMessage === 'object') ||
3234
+ !('setupComplete' in firstMessage)) {
3235
+ await this._webSocketHandler.close(1011, 'Handshake failure');
3236
+ throw new AIError(AIErrorCode.RESPONSE_ERROR, 'Server connection handshake failed. The server did not respond with a setupComplete message.');
3237
+ }
3238
+ this.isClosed = false;
3239
+ }
3240
+ catch (e) {
3241
+ // Ensure connection is closed on any setup error
3242
+ await this._webSocketHandler.close();
3243
+ throw e;
3244
+ }
3245
+ }
3246
+ /**
3247
+ * Resumes an existing live session with the server.
3248
+ *
3249
+ * This closes the current WebSocket connection and establishes a new one using
3250
+ * the same configuration (URI, headers, model, system instruction, tools, etc.)
3251
+ * as the original session.
3252
+ *
3253
+ * @param sessionResumption - The configuration for session resumption, such as the handle to the previous session state to restore.
3254
+ * @throws If the session resumption configuration is unsupported.
3255
+ *
3256
+ * @beta
3257
+ */
3258
+ async resumeSession(sessionResumption) {
3259
+ if (!this._sessionResumption) {
3260
+ throw new AIError(AIErrorCode.UNSUPPORTED, 'Cannot resume session: no sessionResumption config provided');
3261
+ }
3262
+ await this.close();
3263
+ await this._connectSession(sessionResumption);
2969
3264
  }
2970
3265
  /**
2971
3266
  * Sends content to the server.
@@ -2987,7 +3282,7 @@ class LiveSession {
2987
3282
  turnComplete
2988
3283
  }
2989
3284
  };
2990
- this.webSocketHandler.send(JSON.stringify(message));
3285
+ this._webSocketHandler.send(JSON.stringify(message));
2991
3286
  }
2992
3287
  /**
2993
3288
  * Sends text to the server in realtime.
@@ -3011,7 +3306,7 @@ class LiveSession {
3011
3306
  text
3012
3307
  }
3013
3308
  };
3014
- this.webSocketHandler.send(JSON.stringify(message));
3309
+ this._webSocketHandler.send(JSON.stringify(message));
3015
3310
  }
3016
3311
  /**
3017
3312
  * Sends audio data to the server in realtime.
@@ -3040,7 +3335,7 @@ class LiveSession {
3040
3335
  audio: blob
3041
3336
  }
3042
3337
  };
3043
- this.webSocketHandler.send(JSON.stringify(message));
3338
+ this._webSocketHandler.send(JSON.stringify(message));
3044
3339
  }
3045
3340
  /**
3046
3341
  * Sends video data to the server in realtime.
@@ -3068,7 +3363,7 @@ class LiveSession {
3068
3363
  video: blob
3069
3364
  }
3070
3365
  };
3071
- this.webSocketHandler.send(JSON.stringify(message));
3366
+ this._webSocketHandler.send(JSON.stringify(message));
3072
3367
  }
3073
3368
  /**
3074
3369
  * Sends function responses to the server.
@@ -3087,7 +3382,7 @@ class LiveSession {
3087
3382
  functionResponses
3088
3383
  }
3089
3384
  };
3090
- this.webSocketHandler.send(JSON.stringify(message));
3385
+ this._webSocketHandler.send(JSON.stringify(message));
3091
3386
  }
3092
3387
  /**
3093
3388
  * Yields messages received from the server.
@@ -3102,42 +3397,50 @@ class LiveSession {
3102
3397
  if (this.isClosed) {
3103
3398
  throw new AIError(AIErrorCode.SESSION_CLOSED, 'Cannot read from a Live session that is closed. Try starting a new Live session.');
3104
3399
  }
3105
- for await (const message of this.serverMessages) {
3106
- if (message && typeof message === 'object') {
3107
- if (LiveResponseType.SERVER_CONTENT in message) {
3108
- yield {
3109
- type: 'serverContent',
3110
- ...message
3111
- .serverContent
3112
- };
3113
- }
3114
- else if (LiveResponseType.TOOL_CALL in message) {
3115
- yield {
3116
- type: 'toolCall',
3117
- ...message
3118
- .toolCall
3119
- };
3120
- }
3121
- else if (LiveResponseType.TOOL_CALL_CANCELLATION in message) {
3122
- yield {
3123
- type: 'toolCallCancellation',
3124
- ...message.toolCallCancellation
3125
- };
3126
- }
3127
- else if ('goAway' in message) {
3128
- const notice = message.goAway;
3129
- yield {
3130
- type: LiveResponseType.GOING_AWAY_NOTICE,
3131
- timeLeft: parseDuration(notice.timeLeft)
3132
- };
3400
+ if (this._serverMessages) {
3401
+ for await (const message of this._serverMessages) {
3402
+ if (message && typeof message === 'object') {
3403
+ if (LiveResponseType.SERVER_CONTENT in message) {
3404
+ yield {
3405
+ type: 'serverContent',
3406
+ ...message
3407
+ .serverContent
3408
+ };
3409
+ }
3410
+ else if (LiveResponseType.TOOL_CALL in message) {
3411
+ yield {
3412
+ type: 'toolCall',
3413
+ ...message
3414
+ .toolCall
3415
+ };
3416
+ }
3417
+ else if (LiveResponseType.TOOL_CALL_CANCELLATION in message) {
3418
+ yield {
3419
+ type: 'toolCallCancellation',
3420
+ ...message.toolCallCancellation
3421
+ };
3422
+ }
3423
+ else if ('goAway' in message) {
3424
+ const notice = message.goAway;
3425
+ yield {
3426
+ type: LiveResponseType.GOING_AWAY_NOTICE,
3427
+ timeLeft: parseDuration(notice.timeLeft)
3428
+ };
3429
+ }
3430
+ else if (LiveResponseType.SESSION_RESUMPTION_UPDATE in message) {
3431
+ yield {
3432
+ type: LiveResponseType.SESSION_RESUMPTION_UPDATE,
3433
+ ...message.sessionResumptionUpdate
3434
+ };
3435
+ }
3436
+ else {
3437
+ logger.warn(`Received an unknown message type from the server: ${JSON.stringify(message)}`);
3438
+ }
3133
3439
  }
3134
3440
  else {
3135
- logger.warn(`Received an unknown message type from the server: ${JSON.stringify(message)}`);
3441
+ logger.warn(`Received an invalid message from the server: ${JSON.stringify(message)}`);
3136
3442
  }
3137
3443
  }
3138
- else {
3139
- logger.warn(`Received an invalid message from the server: ${JSON.stringify(message)}`);
3140
- }
3141
3444
  }
3142
3445
  }
3143
3446
  /**
@@ -3149,7 +3452,7 @@ class LiveSession {
3149
3452
  async close() {
3150
3453
  if (!this.isClosed) {
3151
3454
  this.isClosed = true;
3152
- await this.webSocketHandler.close(1000, 'Client closed session.');
3455
+ await this._webSocketHandler.close(1000, 'Client closed session.');
3153
3456
  }
3154
3457
  }
3155
3458
  /**
@@ -3172,7 +3475,7 @@ class LiveSession {
3172
3475
  const message = {
3173
3476
  realtimeInput: { mediaChunks: [mediaChunk] }
3174
3477
  };
3175
- this.webSocketHandler.send(JSON.stringify(message));
3478
+ this._webSocketHandler.send(JSON.stringify(message));
3176
3479
  });
3177
3480
  }
3178
3481
  /**
@@ -3252,6 +3555,7 @@ class LiveGenerativeModel extends AIModel {
3252
3555
  */
3253
3556
  constructor(ai, modelParams,
3254
3557
  /**
3558
+ * For testing injection
3255
3559
  * @internal
3256
3560
  */
3257
3561
  _webSocketHandler) {
@@ -3270,9 +3574,7 @@ class LiveGenerativeModel extends AIModel {
3270
3574
  *
3271
3575
  * @beta
3272
3576
  */
3273
- async connect() {
3274
- const url = new WebSocketUrl(this._apiSettings);
3275
- await this._webSocketHandler.connect(url.toString());
3577
+ async connect(sessionResumption) {
3276
3578
  let fullModelPath;
3277
3579
  if (this._apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
3278
3580
  fullModelPath = `projects/${this._apiSettings.project}/${this.model}`;
@@ -3283,36 +3585,24 @@ class LiveGenerativeModel extends AIModel {
3283
3585
  // inputAudioTranscription and outputAudioTranscription are on the generation config in the public API,
3284
3586
  // but the backend expects them to be in the `setup` message.
3285
3587
  const { inputAudioTranscription, outputAudioTranscription, ...generationConfig } = this.generationConfig;
3588
+ const contextWindowCompression = generationConfig.contextWindowCompression;
3589
+ delete generationConfig.contextWindowCompression;
3286
3590
  const setupMessage = {
3287
3591
  setup: {
3288
3592
  model: fullModelPath,
3289
3593
  generationConfig,
3594
+ contextWindowCompression,
3290
3595
  tools: this.tools,
3291
3596
  toolConfig: this.toolConfig,
3292
3597
  systemInstruction: this.systemInstruction,
3293
3598
  inputAudioTranscription,
3294
- outputAudioTranscription
3599
+ outputAudioTranscription,
3600
+ sessionResumption
3295
3601
  }
3296
3602
  };
3297
- try {
3298
- // Begin listening for server messages, and begin the handshake by sending the 'setupMessage'
3299
- const serverMessages = this._webSocketHandler.listen();
3300
- this._webSocketHandler.send(JSON.stringify(setupMessage));
3301
- // Verify we received the handshake response 'setupComplete'
3302
- const firstMessage = (await serverMessages.next()).value;
3303
- if (!firstMessage ||
3304
- !(typeof firstMessage === 'object') ||
3305
- !('setupComplete' in firstMessage)) {
3306
- await this._webSocketHandler.close(1011, 'Handshake failure');
3307
- throw new AIError(AIErrorCode.RESPONSE_ERROR, 'Server connection handshake failed. The server did not respond with a setupComplete message.');
3308
- }
3309
- return new LiveSession(this._webSocketHandler, serverMessages);
3310
- }
3311
- catch (e) {
3312
- // Ensure connection is closed on any setup error
3313
- await this._webSocketHandler.close();
3314
- throw e;
3315
- }
3603
+ const session = new LiveSession(setupMessage, this._apiSettings, sessionResumption, this._webSocketHandler);
3604
+ await session.connectionPromise;
3605
+ return session;
3316
3606
  }
3317
3607
  }
3318
3608
 
@@ -3453,153 +3743,6 @@ class ImagenModel extends AIModel {
3453
3743
  }
3454
3744
  }
3455
3745
 
3456
- /**
3457
- * @license
3458
- * Copyright 2025 Google LLC
3459
- *
3460
- * Licensed under the Apache License, Version 2.0 (the "License");
3461
- * you may not use this file except in compliance with the License.
3462
- * You may obtain a copy of the License at
3463
- *
3464
- * http://www.apache.org/licenses/LICENSE-2.0
3465
- *
3466
- * Unless required by applicable law or agreed to in writing, software
3467
- * distributed under the License is distributed on an "AS IS" BASIS,
3468
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3469
- * See the License for the specific language governing permissions and
3470
- * limitations under the License.
3471
- */
3472
- /**
3473
- * A wrapper for the native `WebSocket` available in both Browsers and Node >= 22.
3474
- *
3475
- * @internal
3476
- */
3477
- class WebSocketHandlerImpl {
3478
- constructor() {
3479
- if (typeof WebSocket === 'undefined') {
3480
- throw new AIError(AIErrorCode.UNSUPPORTED, 'The WebSocket API is not available in this environment. ' +
3481
- 'The "Live" feature is not supported here. It is supported in ' +
3482
- 'modern browser windows, Web Workers with WebSocket support, and Node >= 22.');
3483
- }
3484
- }
3485
- connect(url) {
3486
- return new Promise((resolve, reject) => {
3487
- this.ws = new WebSocket(url);
3488
- this.ws.binaryType = 'blob'; // Only important to set in Node
3489
- this.ws.addEventListener('open', () => resolve(), { once: true });
3490
- this.ws.addEventListener('error', () => reject(new AIError(AIErrorCode.FETCH_ERROR, `Error event raised on WebSocket`)), { once: true });
3491
- this.ws.addEventListener('close', (closeEvent) => {
3492
- if (closeEvent.reason) {
3493
- logger.warn(`WebSocket connection closed by server. Reason: '${closeEvent.reason}'`);
3494
- }
3495
- });
3496
- });
3497
- }
3498
- send(data) {
3499
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
3500
- throw new AIError(AIErrorCode.REQUEST_ERROR, 'WebSocket is not open.');
3501
- }
3502
- this.ws.send(data);
3503
- }
3504
- async *listen() {
3505
- if (!this.ws) {
3506
- throw new AIError(AIErrorCode.REQUEST_ERROR, 'WebSocket is not connected.');
3507
- }
3508
- const messageQueue = [];
3509
- const errorQueue = [];
3510
- let resolvePromise = null;
3511
- let isClosed = false;
3512
- const messageListener = async (event) => {
3513
- let data;
3514
- if (event.data instanceof Blob) {
3515
- data = await event.data.text();
3516
- }
3517
- else if (typeof event.data === 'string') {
3518
- data = event.data;
3519
- }
3520
- else {
3521
- errorQueue.push(new AIError(AIErrorCode.PARSE_FAILED, `Failed to parse WebSocket response. Expected data to be a Blob or string, but was ${typeof event.data}.`));
3522
- if (resolvePromise) {
3523
- resolvePromise();
3524
- resolvePromise = null;
3525
- }
3526
- return;
3527
- }
3528
- try {
3529
- const obj = JSON.parse(data);
3530
- messageQueue.push(obj);
3531
- }
3532
- catch (e) {
3533
- const err = e;
3534
- errorQueue.push(new AIError(AIErrorCode.PARSE_FAILED, `Error parsing WebSocket message to JSON: ${err.message}`));
3535
- }
3536
- if (resolvePromise) {
3537
- resolvePromise();
3538
- resolvePromise = null;
3539
- }
3540
- };
3541
- const errorListener = () => {
3542
- errorQueue.push(new AIError(AIErrorCode.FETCH_ERROR, 'WebSocket connection error.'));
3543
- if (resolvePromise) {
3544
- resolvePromise();
3545
- resolvePromise = null;
3546
- }
3547
- };
3548
- const closeListener = (event) => {
3549
- if (event.reason) {
3550
- logger.warn(`WebSocket connection closed by the server with reason: ${event.reason}`);
3551
- }
3552
- isClosed = true;
3553
- if (resolvePromise) {
3554
- resolvePromise();
3555
- resolvePromise = null;
3556
- }
3557
- // Clean up listeners to prevent memory leaks
3558
- this.ws?.removeEventListener('message', messageListener);
3559
- this.ws?.removeEventListener('close', closeListener);
3560
- this.ws?.removeEventListener('error', errorListener);
3561
- };
3562
- this.ws.addEventListener('message', messageListener);
3563
- this.ws.addEventListener('close', closeListener);
3564
- this.ws.addEventListener('error', errorListener);
3565
- while (!isClosed) {
3566
- if (errorQueue.length > 0) {
3567
- const error = errorQueue.shift();
3568
- throw error;
3569
- }
3570
- if (messageQueue.length > 0) {
3571
- yield messageQueue.shift();
3572
- }
3573
- else {
3574
- await new Promise(resolve => {
3575
- resolvePromise = resolve;
3576
- });
3577
- }
3578
- }
3579
- // If the loop terminated because isClosed is true, check for any final errors
3580
- if (errorQueue.length > 0) {
3581
- const error = errorQueue.shift();
3582
- throw error;
3583
- }
3584
- }
3585
- close(code, reason) {
3586
- return new Promise(resolve => {
3587
- if (!this.ws) {
3588
- return resolve();
3589
- }
3590
- this.ws.addEventListener('close', () => resolve(), { once: true });
3591
- // Calling 'close' during these states results in an error.
3592
- if (this.ws.readyState === WebSocket.CLOSED ||
3593
- this.ws.readyState === WebSocket.CONNECTING) {
3594
- return resolve();
3595
- }
3596
- if (this.ws.readyState !== WebSocket.CLOSING) {
3597
- this.ws.close(code, reason);
3598
- }
3599
- });
3600
- }
3601
- }
3602
-
3603
3746
  /**
3604
3747
  * @license
3605
3748
  * Copyright 2026 Google LLC
@@ -3744,11 +3887,16 @@ class TemplateGenerativeModel {
3744
3887
  * @param templateId - The ID of the server-side template to execute.
3745
3888
  * @param templateVariables - A key-value map of variables to populate the
3746
3889
  * template with.
3890
+ * @param singleRequestOptions - Optional. Options to use for this request.
3891
+ * @param templateToolConfig - Optional. Configuration for tools to use with this request.
3747
3892
  *
3748
3893
  * @beta
3749
3894
  */
3750
- async generateContent(templateId, templateVariables, singleRequestOptions) {
3751
- return templateGenerateContent(this._apiSettings, templateId, { inputs: templateVariables }, {
3895
+ async generateContent(templateId, templateVariables, singleRequestOptions, templateToolConfig) {
3896
+ return templateGenerateContent(this._apiSettings, templateId, {
3897
+ inputs: templateVariables,
3898
+ ...(templateToolConfig && { toolConfig: templateToolConfig })
3899
+ }, {
3752
3900
  ...this.requestOptions,
3753
3901
  ...singleRequestOptions
3754
3902
  });
@@ -3762,11 +3910,16 @@ class TemplateGenerativeModel {
3762
3910
  * @param templateId - The ID of the server-side template to execute.
3763
3911
  * @param templateVariables - A key-value map of variables to populate the
3764
3912
  * template with.
3913
+ * @param singleRequestOptions - Optional.Options to use for this request.
3914
+ * @param templateToolConfig - Optional. Configuration for tools to use with this request.
3765
3915
  *
3766
3916
  * @beta
3767
3917
  */
3768
- async generateContentStream(templateId, templateVariables, singleRequestOptions) {
3769
- return templateGenerateContentStream(this._apiSettings, templateId, { inputs: templateVariables }, {
3918
+ async generateContentStream(templateId, templateVariables, singleRequestOptions, templateToolConfig) {
3919
+ return templateGenerateContentStream(this._apiSettings, templateId, {
3920
+ inputs: templateVariables,
3921
+ ...(templateToolConfig && { toolConfig: templateToolConfig })
3922
+ }, {
3770
3923
  ...this.requestOptions,
3771
3924
  ...singleRequestOptions
3772
3925
  });
@@ -4659,8 +4812,7 @@ function getLiveGenerativeModel(ai, modelParams) {
4659
4812
  if (!modelParams.model) {
4660
4813
  throw new AIError(AIErrorCode.NO_MODEL, `Must provide a model name for getLiveGenerativeModel. Example: getLiveGenerativeModel(ai, { model: 'my-model-name' })`);
4661
4814
  }
4662
- const webSocketHandler = new WebSocketHandlerImpl();
4663
- return new LiveGenerativeModel(ai, modelParams, webSocketHandler);
4815
+ return new LiveGenerativeModel(ai, modelParams);
4664
4816
  }
4665
4817
  /**
4666
4818
  * Returns a {@link TemplateGenerativeModel} class for executing server-side
@@ -4703,5 +4855,5 @@ function registerAI() {
4703
4855
  }
4704
4856
  registerAI();
4705
4857
 
4706
- export { AIError, AIErrorCode, AIModel, AnyOfSchema, ArraySchema, Backend, BackendType, BlockReason, BooleanSchema, ChatSession, ChatSessionBase, FinishReason, FunctionCallingMode, GenerativeModel, GoogleAIBackend, HarmBlockMethod, HarmBlockThreshold, HarmCategory, HarmProbability, HarmSeverity, ImagenAspectRatio, ImagenImageFormat, ImagenModel, ImagenPersonFilterLevel, ImagenSafetyFilterLevel, InferenceMode, InferenceSource, IntegerSchema, Language, LiveGenerativeModel, LiveResponseType, LiveSession, Modality, NumberSchema, ObjectSchema, Outcome, POSSIBLE_ROLES, ResponseModality, Schema, SchemaType, StringSchema, TemplateGenerativeModel, TemplateImagenModel, ThinkingLevel, URLRetrievalStatus, VertexAIBackend, getAI, getGenerativeModel, getImagenModel, getLiveGenerativeModel, getTemplateGenerativeModel, getTemplateImagenModel, startAudioConversation };
4858
+ export { AIError, AIErrorCode, AIModel, AnyOfSchema, ArraySchema, Backend, BackendType, BlockReason, BooleanSchema, ChatSession, ChatSessionBase, FinishReason, FunctionCallingMode, GenerativeModel, GoogleAIBackend, HarmBlockMethod, HarmBlockThreshold, HarmCategory, HarmProbability, HarmSeverity, ImageConfigAspectRatio, ImageConfigImageSize, ImagenAspectRatio, ImagenImageFormat, ImagenModel, ImagenPersonFilterLevel, ImagenSafetyFilterLevel, InferenceMode, InferenceSource, IntegerSchema, Language, LiveGenerativeModel, LiveResponseType, LiveSession, Modality, NumberSchema, ObjectSchema, Outcome, POSSIBLE_ROLES, ResponseModality, Schema, SchemaType, StringSchema, TemplateGenerativeModel, TemplateImagenModel, ThinkingLevel, URLRetrievalStatus, VertexAIBackend, getAI, getGenerativeModel, getImagenModel, getLiveGenerativeModel, getTemplateGenerativeModel, getTemplateImagenModel, startAudioConversation };
4707
4859
  //# sourceMappingURL=index.node.mjs.map