@firebase/ai 2.6.1-canary.8e26a5590 → 2.6.1-canary.b2827448b

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 (37) hide show
  1. package/dist/ai-public.d.ts +104 -11
  2. package/dist/ai.d.ts +105 -12
  3. package/dist/esm/index.esm.js +157 -77
  4. package/dist/esm/index.esm.js.map +1 -1
  5. package/dist/esm/src/constants.d.ts +1 -1
  6. package/dist/esm/src/methods/chat-session.d.ts +7 -3
  7. package/dist/esm/src/methods/count-tokens.d.ts +2 -2
  8. package/dist/esm/src/methods/generate-content.d.ts +5 -5
  9. package/dist/esm/src/models/generative-model.d.ts +4 -4
  10. package/dist/esm/src/models/imagen-model.d.ts +3 -3
  11. package/dist/esm/src/models/template-generative-model.d.ts +3 -3
  12. package/dist/esm/src/models/template-imagen-model.d.ts +2 -2
  13. package/dist/esm/src/requests/request.d.ts +4 -2
  14. package/dist/esm/src/requests/stream-reader.d.ts +1 -3
  15. package/dist/esm/src/types/enums.d.ts +21 -0
  16. package/dist/esm/src/types/imagen/internal.d.ts +1 -1
  17. package/dist/esm/src/types/requests.d.ts +68 -3
  18. package/dist/index.cjs.js +157 -76
  19. package/dist/index.cjs.js.map +1 -1
  20. package/dist/index.node.cjs.js +157 -76
  21. package/dist/index.node.cjs.js.map +1 -1
  22. package/dist/index.node.mjs +157 -77
  23. package/dist/index.node.mjs.map +1 -1
  24. package/dist/src/constants.d.ts +1 -1
  25. package/dist/src/methods/chat-session.d.ts +7 -3
  26. package/dist/src/methods/count-tokens.d.ts +2 -2
  27. package/dist/src/methods/generate-content.d.ts +5 -5
  28. package/dist/src/models/generative-model.d.ts +4 -4
  29. package/dist/src/models/imagen-model.d.ts +3 -3
  30. package/dist/src/models/template-generative-model.d.ts +3 -3
  31. package/dist/src/models/template-imagen-model.d.ts +2 -2
  32. package/dist/src/requests/request.d.ts +4 -2
  33. package/dist/src/requests/stream-reader.d.ts +1 -3
  34. package/dist/src/types/enums.d.ts +21 -0
  35. package/dist/src/types/imagen/internal.d.ts +1 -1
  36. package/dist/src/types/requests.d.ts +68 -3
  37. package/package.json +8 -8
@@ -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.6.1-canary.8e26a5590";
7
+ var version = "2.6.1-canary.b2827448b";
8
8
 
9
9
  /**
10
10
  * @license
@@ -32,7 +32,7 @@ const DEFAULT_FETCH_TIMEOUT_MS = 180 * 1000;
32
32
  /**
33
33
  * Defines the name of the default in-cloud model to use for hybrid inference.
34
34
  */
35
- const DEFAULT_HYBRID_IN_CLOUD_MODEL = 'gemini-2.0-flash-lite';
35
+ const DEFAULT_HYBRID_IN_CLOUD_MODEL = 'gemini-2.5-flash-lite';
36
36
 
37
37
  /**
38
38
  * @license
@@ -408,6 +408,19 @@ const Language = {
408
408
  UNSPECIFIED: 'LANGUAGE_UNSPECIFIED',
409
409
  PYTHON: 'PYTHON'
410
410
  };
411
+ /**
412
+ * A preset that controls the model's "thinking" process. Use
413
+ * `ThinkingLevel.LOW` for faster responses on less complex tasks, and
414
+ * `ThinkingLevel.HIGH` for better reasoning on more complex tasks.
415
+ *
416
+ * @public
417
+ */
418
+ const ThinkingLevel = {
419
+ MINIMAL: 'MINIMAL',
420
+ LOW: 'LOW',
421
+ MEDIUM: 'MEDIUM',
422
+ HIGH: 'HIGH'
423
+ };
411
424
 
412
425
  /**
413
426
  * @license
@@ -1133,6 +1146,8 @@ const logger = new Logger('@firebase/vertexai');
1133
1146
  * See the License for the specific language governing permissions and
1134
1147
  * limitations under the License.
1135
1148
  */
1149
+ const TIMEOUT_EXPIRED_MESSAGE = 'Timeout has expired.';
1150
+ const ABORT_ERROR_NAME = 'AbortError';
1136
1151
  class RequestURL {
1137
1152
  constructor(params) {
1138
1153
  this.params = params;
@@ -1155,7 +1170,7 @@ class RequestURL {
1155
1170
  }
1156
1171
  }
1157
1172
  get baseUrl() {
1158
- return this.params.requestOptions?.baseUrl ?? `https://${DEFAULT_DOMAIN}`;
1173
+ return (this.params.singleRequestOptions?.baseUrl ?? `https://${DEFAULT_DOMAIN}`);
1159
1174
  }
1160
1175
  get queryParams() {
1161
1176
  const params = new URLSearchParams();
@@ -1223,21 +1238,32 @@ async function getHeaders(url) {
1223
1238
  async function makeRequest(requestUrlParams, body) {
1224
1239
  const url = new RequestURL(requestUrlParams);
1225
1240
  let response;
1226
- let fetchTimeoutId;
1241
+ const externalSignal = requestUrlParams.singleRequestOptions?.signal;
1242
+ const timeoutMillis = requestUrlParams.singleRequestOptions?.timeout != null &&
1243
+ requestUrlParams.singleRequestOptions.timeout >= 0
1244
+ ? requestUrlParams.singleRequestOptions.timeout
1245
+ : DEFAULT_FETCH_TIMEOUT_MS;
1246
+ const internalAbortController = new AbortController();
1247
+ const fetchTimeoutId = setTimeout(() => {
1248
+ internalAbortController.abort(new DOMException(TIMEOUT_EXPIRED_MESSAGE, ABORT_ERROR_NAME));
1249
+ logger.debug(`Aborting request to ${url} due to timeout (${timeoutMillis}ms)`);
1250
+ }, timeoutMillis);
1251
+ // Used to abort the fetch if either the user-defined `externalSignal` is aborted, or if the
1252
+ // internal signal (triggered by timeouts) is aborted.
1253
+ const combinedSignal = AbortSignal.any(externalSignal
1254
+ ? [externalSignal, internalAbortController.signal]
1255
+ : [internalAbortController.signal]);
1256
+ if (externalSignal && externalSignal.aborted) {
1257
+ clearTimeout(fetchTimeoutId);
1258
+ throw new DOMException(externalSignal.reason ?? 'Aborted externally before fetch', ABORT_ERROR_NAME);
1259
+ }
1227
1260
  try {
1228
1261
  const fetchOptions = {
1229
1262
  method: 'POST',
1230
1263
  headers: await getHeaders(url),
1264
+ signal: combinedSignal,
1231
1265
  body
1232
1266
  };
1233
- // Timeout is 180s by default.
1234
- const timeoutMillis = requestUrlParams.requestOptions?.timeout != null &&
1235
- requestUrlParams.requestOptions.timeout >= 0
1236
- ? requestUrlParams.requestOptions.timeout
1237
- : DEFAULT_FETCH_TIMEOUT_MS;
1238
- const abortController = new AbortController();
1239
- fetchTimeoutId = setTimeout(() => abortController.abort(), timeoutMillis);
1240
- fetchOptions.signal = abortController.signal;
1241
1267
  response = await fetch(url.toString(), fetchOptions);
1242
1268
  if (!response.ok) {
1243
1269
  let message = '';
@@ -1280,16 +1306,18 @@ async function makeRequest(requestUrlParams, body) {
1280
1306
  let err = e;
1281
1307
  if (e.code !== AIErrorCode.FETCH_ERROR &&
1282
1308
  e.code !== AIErrorCode.API_NOT_ENABLED &&
1283
- e instanceof Error) {
1309
+ e instanceof Error &&
1310
+ e.name !== ABORT_ERROR_NAME) {
1284
1311
  err = new AIError(AIErrorCode.ERROR, `Error fetching from ${url.toString()}: ${e.message}`);
1285
1312
  err.stack = e.stack;
1286
1313
  }
1287
1314
  throw err;
1288
1315
  }
1289
1316
  finally {
1290
- if (fetchTimeoutId) {
1291
- clearTimeout(fetchTimeoutId);
1292
- }
1317
+ // When doing streaming requests, this will clear the timeout once the stream begins.
1318
+ // If a timeout it 3000ms, and the stream starts after 300ms and ends after 5000ms, the
1319
+ // timeout will be cleared after 300ms, so it won't abort the request.
1320
+ clearTimeout(fetchTimeoutId);
1293
1321
  }
1294
1322
  return response;
1295
1323
  }
@@ -1727,6 +1755,8 @@ const responseLineRE = /^data\: (.*)(?:\n\n|\r\r|\r\n\r\n)/;
1727
1755
  function processStream(response, apiSettings, inferenceSource) {
1728
1756
  const inputStream = response.body.pipeThrough(new TextDecoderStream('utf8', { fatal: true }));
1729
1757
  const responseStream = getResponseStream(inputStream);
1758
+ // We split the stream so the user can iterate over partial results (stream1)
1759
+ // while we aggregate the full result for history/final response (stream2).
1730
1760
  const [stream1, stream2] = responseStream.tee();
1731
1761
  return {
1732
1762
  stream: generateResponseSequence(stream1, apiSettings, inferenceSource),
@@ -1763,7 +1793,6 @@ async function* generateResponseSequence(stream, apiSettings, inferenceSource) {
1763
1793
  enhancedResponse = createEnhancedContentResponse(value, inferenceSource);
1764
1794
  }
1765
1795
  const firstCandidate = enhancedResponse.candidates?.[0];
1766
- // Don't yield a response with no useful data for the developer.
1767
1796
  if (!firstCandidate?.content?.parts &&
1768
1797
  !firstCandidate?.finishReason &&
1769
1798
  !firstCandidate?.citationMetadata &&
@@ -1774,9 +1803,7 @@ async function* generateResponseSequence(stream, apiSettings, inferenceSource) {
1774
1803
  }
1775
1804
  }
1776
1805
  /**
1777
- * Reads a raw stream from the fetch response and join incomplete
1778
- * chunks, returning a new stream that provides a single complete
1779
- * GenerateContentResponse in each iteration.
1806
+ * Reads a raw string stream, buffers incomplete chunks, and yields parsed JSON objects.
1780
1807
  */
1781
1808
  function getResponseStream(inputStream) {
1782
1809
  const reader = inputStream.getReader();
@@ -1795,6 +1822,8 @@ function getResponseStream(inputStream) {
1795
1822
  return;
1796
1823
  }
1797
1824
  currentText += value;
1825
+ // SSE events may span chunk boundaries, so we buffer until we match
1826
+ // the full "data: {json}\n\n" pattern.
1798
1827
  let match = currentText.match(responseLineRE);
1799
1828
  let parsedResponse;
1800
1829
  while (match) {
@@ -1828,8 +1857,7 @@ function aggregateResponses(responses) {
1828
1857
  for (const response of responses) {
1829
1858
  if (response.candidates) {
1830
1859
  for (const candidate of response.candidates) {
1831
- // Index will be undefined if it's the first index (0), so we should use 0 if it's undefined.
1832
- // See: https://github.com/firebase/firebase-js-sdk/issues/8566
1860
+ // Use 0 if index is undefined (protobuf default value omission).
1833
1861
  const i = candidate.index || 0;
1834
1862
  if (!aggregatedResponse.candidates) {
1835
1863
  aggregatedResponse.candidates = [];
@@ -1839,7 +1867,7 @@ function aggregateResponses(responses) {
1839
1867
  index: candidate.index
1840
1868
  };
1841
1869
  }
1842
- // Keep overwriting, the last one will be final
1870
+ // Overwrite with the latest metadata
1843
1871
  aggregatedResponse.candidates[i].citationMetadata =
1844
1872
  candidate.citationMetadata;
1845
1873
  aggregatedResponse.candidates[i].finishReason = candidate.finishReason;
@@ -1860,12 +1888,7 @@ function aggregateResponses(responses) {
1860
1888
  aggregatedResponse.candidates[i].urlContextMetadata =
1861
1889
  urlContextMetadata;
1862
1890
  }
1863
- /**
1864
- * Candidates should always have content and parts, but this handles
1865
- * possible malformed responses.
1866
- */
1867
1891
  if (candidate.content) {
1868
- // Skip a candidate without parts.
1869
1892
  if (!candidate.content.parts) {
1870
1893
  continue;
1871
1894
  }
@@ -1997,7 +2020,7 @@ async function callCloudOrDevice(request, chromeAdapter, onDeviceCall, inCloudCa
1997
2020
  * See the License for the specific language governing permissions and
1998
2021
  * limitations under the License.
1999
2022
  */
2000
- async function generateContentStreamOnCloud(apiSettings, model, params, requestOptions) {
2023
+ async function generateContentStreamOnCloud(apiSettings, model, params, singleRequestOptions) {
2001
2024
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2002
2025
  params = mapGenerateContentRequest(params);
2003
2026
  }
@@ -2006,14 +2029,14 @@ async function generateContentStreamOnCloud(apiSettings, model, params, requestO
2006
2029
  model,
2007
2030
  apiSettings,
2008
2031
  stream: true,
2009
- requestOptions
2032
+ singleRequestOptions
2010
2033
  }, JSON.stringify(params));
2011
2034
  }
2012
- async function generateContentStream(apiSettings, model, params, chromeAdapter, requestOptions) {
2013
- const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContentStream(params), () => generateContentStreamOnCloud(apiSettings, model, params, requestOptions));
2035
+ async function generateContentStream(apiSettings, model, params, chromeAdapter, singleRequestOptions) {
2036
+ const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContentStream(params), () => generateContentStreamOnCloud(apiSettings, model, params, singleRequestOptions));
2014
2037
  return processStream(callResult.response, apiSettings, callResult.inferenceSource);
2015
2038
  }
2016
- async function generateContentOnCloud(apiSettings, model, params, requestOptions) {
2039
+ async function generateContentOnCloud(apiSettings, model, params, singleRequestOptions) {
2017
2040
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2018
2041
  params = mapGenerateContentRequest(params);
2019
2042
  }
@@ -2022,16 +2045,16 @@ async function generateContentOnCloud(apiSettings, model, params, requestOptions
2022
2045
  task: "generateContent" /* Task.GENERATE_CONTENT */,
2023
2046
  apiSettings,
2024
2047
  stream: false,
2025
- requestOptions
2048
+ singleRequestOptions
2026
2049
  }, JSON.stringify(params));
2027
2050
  }
2028
- async function templateGenerateContent(apiSettings, templateId, templateParams, requestOptions) {
2051
+ async function templateGenerateContent(apiSettings, templateId, templateParams, singleRequestOptions) {
2029
2052
  const response = await makeRequest({
2030
2053
  task: "templateGenerateContent" /* ServerPromptTemplateTask.TEMPLATE_GENERATE_CONTENT */,
2031
2054
  templateId,
2032
2055
  apiSettings,
2033
2056
  stream: false,
2034
- requestOptions
2057
+ singleRequestOptions
2035
2058
  }, JSON.stringify(templateParams));
2036
2059
  const generateContentResponse = await processGenerateContentResponse(response, apiSettings);
2037
2060
  const enhancedResponse = createEnhancedContentResponse(generateContentResponse);
@@ -2039,18 +2062,18 @@ async function templateGenerateContent(apiSettings, templateId, templateParams,
2039
2062
  response: enhancedResponse
2040
2063
  };
2041
2064
  }
2042
- async function templateGenerateContentStream(apiSettings, templateId, templateParams, requestOptions) {
2065
+ async function templateGenerateContentStream(apiSettings, templateId, templateParams, singleRequestOptions) {
2043
2066
  const response = await makeRequest({
2044
2067
  task: "templateStreamGenerateContent" /* ServerPromptTemplateTask.TEMPLATE_STREAM_GENERATE_CONTENT */,
2045
2068
  templateId,
2046
2069
  apiSettings,
2047
2070
  stream: true,
2048
- requestOptions
2071
+ singleRequestOptions
2049
2072
  }, JSON.stringify(templateParams));
2050
2073
  return processStream(response, apiSettings);
2051
2074
  }
2052
- async function generateContent(apiSettings, model, params, chromeAdapter, requestOptions) {
2053
- const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContent(params), () => generateContentOnCloud(apiSettings, model, params, requestOptions));
2075
+ async function generateContent(apiSettings, model, params, chromeAdapter, singleRequestOptions) {
2076
+ const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContent(params), () => generateContentOnCloud(apiSettings, model, params, singleRequestOptions));
2054
2077
  const generateContentResponse = await processGenerateContentResponse(callResult.response, apiSettings);
2055
2078
  const enhancedResponse = createEnhancedContentResponse(generateContentResponse, callResult.inferenceSource);
2056
2079
  return {
@@ -2304,7 +2327,8 @@ function validateChatHistory(history) {
2304
2327
  * limitations under the License.
2305
2328
  */
2306
2329
  /**
2307
- * Do not log a message for this error.
2330
+ * Used to break the internal promise chain when an error is already handled
2331
+ * by the user, preventing duplicate console logs.
2308
2332
  */
2309
2333
  const SILENT_ERROR = 'SILENT_ERROR';
2310
2334
  /**
@@ -2320,6 +2344,10 @@ class ChatSession {
2320
2344
  this.params = params;
2321
2345
  this.requestOptions = requestOptions;
2322
2346
  this._history = [];
2347
+ /**
2348
+ * Ensures sequential execution of chat messages to maintain history order.
2349
+ * Each call waits for the previous one to settle before proceeding.
2350
+ */
2323
2351
  this._sendPromise = Promise.resolve();
2324
2352
  this._apiSettings = apiSettings;
2325
2353
  if (params?.history) {
@@ -2340,7 +2368,7 @@ class ChatSession {
2340
2368
  * Sends a chat message and receives a non-streaming
2341
2369
  * {@link GenerateContentResult}
2342
2370
  */
2343
- async sendMessage(request) {
2371
+ async sendMessage(request, singleRequestOptions) {
2344
2372
  await this._sendPromise;
2345
2373
  const newContent = formatNewContent(request);
2346
2374
  const generateContentRequest = {
@@ -2352,16 +2380,20 @@ class ChatSession {
2352
2380
  contents: [...this._history, newContent]
2353
2381
  };
2354
2382
  let finalResult = {};
2355
- // Add onto the chain.
2356
2383
  this._sendPromise = this._sendPromise
2357
- .then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions))
2384
+ .then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, {
2385
+ ...this.requestOptions,
2386
+ ...singleRequestOptions
2387
+ }))
2358
2388
  .then(result => {
2389
+ // TODO: Make this update atomic. If creating `responseContent` throws,
2390
+ // history will contain the user message but not the response, causing
2391
+ // validation errors on the next request.
2359
2392
  if (result.response.candidates &&
2360
2393
  result.response.candidates.length > 0) {
2361
2394
  this._history.push(newContent);
2362
2395
  const responseContent = {
2363
2396
  parts: result.response.candidates?.[0].content.parts || [],
2364
- // Response seems to come back without a role set.
2365
2397
  role: result.response.candidates?.[0].content.role || 'model'
2366
2398
  };
2367
2399
  this._history.push(responseContent);
@@ -2382,7 +2414,7 @@ class ChatSession {
2382
2414
  * {@link GenerateContentStreamResult} containing an iterable stream
2383
2415
  * and a response promise.
2384
2416
  */
2385
- async sendMessageStream(request) {
2417
+ async sendMessageStream(request, singleRequestOptions) {
2386
2418
  await this._sendPromise;
2387
2419
  const newContent = formatNewContent(request);
2388
2420
  const generateContentRequest = {
@@ -2393,21 +2425,29 @@ class ChatSession {
2393
2425
  systemInstruction: this.params?.systemInstruction,
2394
2426
  contents: [...this._history, newContent]
2395
2427
  };
2396
- const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions);
2397
- // Add onto the chain.
2428
+ const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, {
2429
+ ...this.requestOptions,
2430
+ ...singleRequestOptions
2431
+ });
2432
+ // We hook into the chain to update history, but we don't block the
2433
+ // return of `streamPromise` to the user.
2398
2434
  this._sendPromise = this._sendPromise
2399
2435
  .then(() => streamPromise)
2400
- // This must be handled to avoid unhandled rejection, but jump
2401
- // to the final catch block with a label to not log this error.
2402
2436
  .catch(_ignored => {
2437
+ // If the initial fetch fails, the user's `streamPromise` rejects.
2438
+ // We swallow the error here to prevent double logging in the final catch.
2403
2439
  throw new Error(SILENT_ERROR);
2404
2440
  })
2405
2441
  .then(streamResult => streamResult.response)
2406
2442
  .then(response => {
2443
+ // This runs after the stream completes. Runtime errors here cannot be
2444
+ // caught by the user because their promise has likely already resolved.
2445
+ // TODO: Move response validation logic upstream to `stream-reader` so
2446
+ // errors propagate to the user's `result.response` promise.
2407
2447
  if (response.candidates && response.candidates.length > 0) {
2408
2448
  this._history.push(newContent);
2449
+ // TODO: Validate that `response.candidates[0].content` is not null.
2409
2450
  const responseContent = { ...response.candidates[0].content };
2410
- // Response seems to come back without a role set.
2411
2451
  if (!responseContent.role) {
2412
2452
  responseContent.role = 'model';
2413
2453
  }
@@ -2421,12 +2461,8 @@ class ChatSession {
2421
2461
  }
2422
2462
  })
2423
2463
  .catch(e => {
2424
- // Errors in streamPromise are already catchable by the user as
2425
- // streamPromise is returned.
2426
- // Avoid duplicating the error message in logs.
2427
- if (e.message !== SILENT_ERROR) {
2428
- // Users do not have access to _sendPromise to catch errors
2429
- // downstream from streamPromise, so they should not throw.
2464
+ // Filter out errors already handled by the user or initiated by them.
2465
+ if (e.message !== SILENT_ERROR && e.name !== 'AbortError') {
2430
2466
  logger.error(e);
2431
2467
  }
2432
2468
  });
@@ -2450,7 +2486,7 @@ class ChatSession {
2450
2486
  * See the License for the specific language governing permissions and
2451
2487
  * limitations under the License.
2452
2488
  */
2453
- async function countTokensOnCloud(apiSettings, model, params, requestOptions) {
2489
+ async function countTokensOnCloud(apiSettings, model, params, singleRequestOptions) {
2454
2490
  let body = '';
2455
2491
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2456
2492
  const mappedParams = mapCountTokensRequest(params, model);
@@ -2464,7 +2500,7 @@ async function countTokensOnCloud(apiSettings, model, params, requestOptions) {
2464
2500
  task: "countTokens" /* Task.COUNT_TOKENS */,
2465
2501
  apiSettings,
2466
2502
  stream: false,
2467
- requestOptions
2503
+ singleRequestOptions
2468
2504
  }, body);
2469
2505
  return response.json();
2470
2506
  }
@@ -2500,6 +2536,7 @@ class GenerativeModel extends AIModel {
2500
2536
  super(ai, modelParams.model);
2501
2537
  this.chromeAdapter = chromeAdapter;
2502
2538
  this.generationConfig = modelParams.generationConfig || {};
2539
+ validateGenerationConfig(this.generationConfig);
2503
2540
  this.safetySettings = modelParams.safetySettings || [];
2504
2541
  this.tools = modelParams.tools;
2505
2542
  this.toolConfig = modelParams.toolConfig;
@@ -2510,7 +2547,7 @@ class GenerativeModel extends AIModel {
2510
2547
  * Makes a single non-streaming call to the model
2511
2548
  * and returns an object containing a single {@link GenerateContentResponse}.
2512
2549
  */
2513
- async generateContent(request) {
2550
+ async generateContent(request, singleRequestOptions) {
2514
2551
  const formattedParams = formatGenerateContentInput(request);
2515
2552
  return generateContent(this._apiSettings, this.model, {
2516
2553
  generationConfig: this.generationConfig,
@@ -2519,7 +2556,12 @@ class GenerativeModel extends AIModel {
2519
2556
  toolConfig: this.toolConfig,
2520
2557
  systemInstruction: this.systemInstruction,
2521
2558
  ...formattedParams
2522
- }, this.chromeAdapter, this.requestOptions);
2559
+ }, this.chromeAdapter,
2560
+ // Merge request options
2561
+ {
2562
+ ...this.requestOptions,
2563
+ ...singleRequestOptions
2564
+ });
2523
2565
  }
2524
2566
  /**
2525
2567
  * Makes a single streaming call to the model
@@ -2527,7 +2569,7 @@ class GenerativeModel extends AIModel {
2527
2569
  * over all chunks in the streaming response as well as
2528
2570
  * a promise that returns the final aggregated response.
2529
2571
  */
2530
- async generateContentStream(request) {
2572
+ async generateContentStream(request, singleRequestOptions) {
2531
2573
  const formattedParams = formatGenerateContentInput(request);
2532
2574
  return generateContentStream(this._apiSettings, this.model, {
2533
2575
  generationConfig: this.generationConfig,
@@ -2536,7 +2578,12 @@ class GenerativeModel extends AIModel {
2536
2578
  toolConfig: this.toolConfig,
2537
2579
  systemInstruction: this.systemInstruction,
2538
2580
  ...formattedParams
2539
- }, this.chromeAdapter, this.requestOptions);
2581
+ }, this.chromeAdapter,
2582
+ // Merge request options
2583
+ {
2584
+ ...this.requestOptions,
2585
+ ...singleRequestOptions
2586
+ });
2540
2587
  }
2541
2588
  /**
2542
2589
  * Gets a new {@link ChatSession} instance which can be used for
@@ -2560,9 +2607,26 @@ class GenerativeModel extends AIModel {
2560
2607
  /**
2561
2608
  * Counts the tokens in the provided request.
2562
2609
  */
2563
- async countTokens(request) {
2610
+ async countTokens(request, singleRequestOptions) {
2564
2611
  const formattedParams = formatGenerateContentInput(request);
2565
- return countTokens(this._apiSettings, this.model, formattedParams, this.chromeAdapter);
2612
+ return countTokens(this._apiSettings, this.model, formattedParams, this.chromeAdapter,
2613
+ // Merge request options
2614
+ {
2615
+ ...this.requestOptions,
2616
+ ...singleRequestOptions
2617
+ });
2618
+ }
2619
+ }
2620
+ /**
2621
+ * Client-side validation of some common `GenerationConfig` pitfalls, in order
2622
+ * to save the developer a wasted request.
2623
+ */
2624
+ function validateGenerationConfig(generationConfig) {
2625
+ if (
2626
+ // != allows for null and undefined. 0 is considered "set" by the model
2627
+ generationConfig.thinkingConfig?.thinkingBudget != null &&
2628
+ generationConfig.thinkingConfig?.thinkingLevel) {
2629
+ throw new AIError(AIErrorCode.UNSUPPORTED, `Cannot set both thinkingBudget and thinkingLevel in a config.`);
2566
2630
  }
2567
2631
  }
2568
2632
 
@@ -3013,7 +3077,7 @@ class ImagenModel extends AIModel {
3013
3077
  *
3014
3078
  * @public
3015
3079
  */
3016
- async generateImages(prompt) {
3080
+ async generateImages(prompt, singleRequestOptions) {
3017
3081
  const body = createPredictRequestBody(prompt, {
3018
3082
  ...this.generationConfig,
3019
3083
  ...this.safetySettings
@@ -3023,7 +3087,11 @@ class ImagenModel extends AIModel {
3023
3087
  model: this.model,
3024
3088
  apiSettings: this._apiSettings,
3025
3089
  stream: false,
3026
- requestOptions: this.requestOptions
3090
+ // Merge request options. Single request options overwrite the model's request options.
3091
+ singleRequestOptions: {
3092
+ ...this.requestOptions,
3093
+ ...singleRequestOptions
3094
+ }
3027
3095
  }, JSON.stringify(body));
3028
3096
  return handlePredictResponse(response);
3029
3097
  }
@@ -3046,7 +3114,7 @@ class ImagenModel extends AIModel {
3046
3114
  * returned object will have a `filteredReason` property.
3047
3115
  * If all images are filtered, the `images` array will be empty.
3048
3116
  */
3049
- async generateImagesGCS(prompt, gcsURI) {
3117
+ async generateImagesGCS(prompt, gcsURI, singleRequestOptions) {
3050
3118
  const body = createPredictRequestBody(prompt, {
3051
3119
  gcsURI,
3052
3120
  ...this.generationConfig,
@@ -3057,7 +3125,11 @@ class ImagenModel extends AIModel {
3057
3125
  model: this.model,
3058
3126
  apiSettings: this._apiSettings,
3059
3127
  stream: false,
3060
- requestOptions: this.requestOptions
3128
+ // Merge request options. Single request options overwrite the model's request options.
3129
+ singleRequestOptions: {
3130
+ ...this.requestOptions,
3131
+ ...singleRequestOptions
3132
+ }
3061
3133
  }, JSON.stringify(body));
3062
3134
  return handlePredictResponse(response);
3063
3135
  }
@@ -3251,9 +3323,11 @@ class TemplateGenerativeModel {
3251
3323
  *
3252
3324
  * @beta
3253
3325
  */
3254
- async generateContent(templateId, templateVariables // anything!
3255
- ) {
3256
- return templateGenerateContent(this._apiSettings, templateId, { inputs: templateVariables }, this.requestOptions);
3326
+ async generateContent(templateId, templateVariables, singleRequestOptions) {
3327
+ return templateGenerateContent(this._apiSettings, templateId, { inputs: templateVariables }, {
3328
+ ...this.requestOptions,
3329
+ ...singleRequestOptions
3330
+ });
3257
3331
  }
3258
3332
  /**
3259
3333
  * Makes a single streaming call to the model and returns an object
@@ -3267,8 +3341,11 @@ class TemplateGenerativeModel {
3267
3341
  *
3268
3342
  * @beta
3269
3343
  */
3270
- async generateContentStream(templateId, templateVariables) {
3271
- return templateGenerateContentStream(this._apiSettings, templateId, { inputs: templateVariables }, this.requestOptions);
3344
+ async generateContentStream(templateId, templateVariables, singleRequestOptions) {
3345
+ return templateGenerateContentStream(this._apiSettings, templateId, { inputs: templateVariables }, {
3346
+ ...this.requestOptions,
3347
+ ...singleRequestOptions
3348
+ });
3272
3349
  }
3273
3350
  }
3274
3351
 
@@ -3313,13 +3390,16 @@ class TemplateImagenModel {
3313
3390
  *
3314
3391
  * @beta
3315
3392
  */
3316
- async generateImages(templateId, templateVariables) {
3393
+ async generateImages(templateId, templateVariables, singleRequestOptions) {
3317
3394
  const response = await makeRequest({
3318
3395
  task: "templatePredict" /* ServerPromptTemplateTask.TEMPLATE_PREDICT */,
3319
3396
  templateId,
3320
3397
  apiSettings: this._apiSettings,
3321
3398
  stream: false,
3322
- requestOptions: this.requestOptions
3399
+ singleRequestOptions: {
3400
+ ...this.requestOptions,
3401
+ ...singleRequestOptions
3402
+ }
3323
3403
  }, JSON.stringify({ inputs: templateVariables }));
3324
3404
  return handlePredictResponse(response);
3325
3405
  }
@@ -4154,5 +4234,5 @@ function registerAI() {
4154
4234
  }
4155
4235
  registerAI();
4156
4236
 
4157
- export { AIError, AIErrorCode, AIModel, AnyOfSchema, ArraySchema, Backend, BackendType, BlockReason, BooleanSchema, ChatSession, 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, URLRetrievalStatus, VertexAIBackend, getAI, getGenerativeModel, getImagenModel, getLiveGenerativeModel, getTemplateGenerativeModel, getTemplateImagenModel, startAudioConversation };
4237
+ export { AIError, AIErrorCode, AIModel, AnyOfSchema, ArraySchema, Backend, BackendType, BlockReason, BooleanSchema, ChatSession, 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 };
4158
4238
  //# sourceMappingURL=index.node.mjs.map