@firebase/ai 2.6.0 → 2.6.1-canary.5579b387a

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 +159 -79
  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 +159 -78
  19. package/dist/index.cjs.js.map +1 -1
  20. package/dist/index.node.cjs.js +159 -78
  21. package/dist/index.node.cjs.js.map +1 -1
  22. package/dist/index.node.mjs +159 -79
  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
@@ -8,7 +8,7 @@ var util = require('@firebase/util');
8
8
  var logger$1 = require('@firebase/logger');
9
9
 
10
10
  var name = "@firebase/ai";
11
- var version = "2.6.0";
11
+ var version = "2.6.1-canary.5579b387a";
12
12
 
13
13
  /**
14
14
  * @license
@@ -36,7 +36,7 @@ const DEFAULT_FETCH_TIMEOUT_MS = 180 * 1000;
36
36
  /**
37
37
  * Defines the name of the default in-cloud model to use for hybrid inference.
38
38
  */
39
- const DEFAULT_HYBRID_IN_CLOUD_MODEL = 'gemini-2.0-flash-lite';
39
+ const DEFAULT_HYBRID_IN_CLOUD_MODEL = 'gemini-2.5-flash-lite';
40
40
 
41
41
  /**
42
42
  * @license
@@ -412,6 +412,19 @@ const Language = {
412
412
  UNSPECIFIED: 'LANGUAGE_UNSPECIFIED',
413
413
  PYTHON: 'PYTHON'
414
414
  };
415
+ /**
416
+ * A preset that controls the model's "thinking" process. Use
417
+ * `ThinkingLevel.LOW` for faster responses on less complex tasks, and
418
+ * `ThinkingLevel.HIGH` for better reasoning on more complex tasks.
419
+ *
420
+ * @public
421
+ */
422
+ const ThinkingLevel = {
423
+ MINIMAL: 'MINIMAL',
424
+ LOW: 'LOW',
425
+ MEDIUM: 'MEDIUM',
426
+ HIGH: 'HIGH'
427
+ };
415
428
 
416
429
  /**
417
430
  * @license
@@ -1137,6 +1150,8 @@ const logger = new logger$1.Logger('@firebase/vertexai');
1137
1150
  * See the License for the specific language governing permissions and
1138
1151
  * limitations under the License.
1139
1152
  */
1153
+ const TIMEOUT_EXPIRED_MESSAGE = 'Timeout has expired.';
1154
+ const ABORT_ERROR_NAME = 'AbortError';
1140
1155
  class RequestURL {
1141
1156
  constructor(params) {
1142
1157
  this.params = params;
@@ -1159,7 +1174,7 @@ class RequestURL {
1159
1174
  }
1160
1175
  }
1161
1176
  get baseUrl() {
1162
- return this.params.requestOptions?.baseUrl ?? `https://${DEFAULT_DOMAIN}`;
1177
+ return (this.params.singleRequestOptions?.baseUrl ?? `https://${DEFAULT_DOMAIN}`);
1163
1178
  }
1164
1179
  get queryParams() {
1165
1180
  const params = new URLSearchParams();
@@ -1227,21 +1242,32 @@ async function getHeaders(url) {
1227
1242
  async function makeRequest(requestUrlParams, body) {
1228
1243
  const url = new RequestURL(requestUrlParams);
1229
1244
  let response;
1230
- let fetchTimeoutId;
1245
+ const externalSignal = requestUrlParams.singleRequestOptions?.signal;
1246
+ const timeoutMillis = requestUrlParams.singleRequestOptions?.timeout != null &&
1247
+ requestUrlParams.singleRequestOptions.timeout >= 0
1248
+ ? requestUrlParams.singleRequestOptions.timeout
1249
+ : DEFAULT_FETCH_TIMEOUT_MS;
1250
+ const internalAbortController = new AbortController();
1251
+ const fetchTimeoutId = setTimeout(() => {
1252
+ internalAbortController.abort(new DOMException(TIMEOUT_EXPIRED_MESSAGE, ABORT_ERROR_NAME));
1253
+ logger.debug(`Aborting request to ${url} due to timeout (${timeoutMillis}ms)`);
1254
+ }, timeoutMillis);
1255
+ // Used to abort the fetch if either the user-defined `externalSignal` is aborted, or if the
1256
+ // internal signal (triggered by timeouts) is aborted.
1257
+ const combinedSignal = AbortSignal.any(externalSignal
1258
+ ? [externalSignal, internalAbortController.signal]
1259
+ : [internalAbortController.signal]);
1260
+ if (externalSignal && externalSignal.aborted) {
1261
+ clearTimeout(fetchTimeoutId);
1262
+ throw new DOMException(externalSignal.reason ?? 'Aborted externally before fetch', ABORT_ERROR_NAME);
1263
+ }
1231
1264
  try {
1232
1265
  const fetchOptions = {
1233
1266
  method: 'POST',
1234
1267
  headers: await getHeaders(url),
1268
+ signal: combinedSignal,
1235
1269
  body
1236
1270
  };
1237
- // Timeout is 180s by default.
1238
- const timeoutMillis = requestUrlParams.requestOptions?.timeout != null &&
1239
- requestUrlParams.requestOptions.timeout >= 0
1240
- ? requestUrlParams.requestOptions.timeout
1241
- : DEFAULT_FETCH_TIMEOUT_MS;
1242
- const abortController = new AbortController();
1243
- fetchTimeoutId = setTimeout(() => abortController.abort(), timeoutMillis);
1244
- fetchOptions.signal = abortController.signal;
1245
1271
  response = await fetch(url.toString(), fetchOptions);
1246
1272
  if (!response.ok) {
1247
1273
  let message = '';
@@ -1264,7 +1290,7 @@ async function makeRequest(requestUrlParams, body) {
1264
1290
  throw new AIError(AIErrorCode.API_NOT_ENABLED, `The Firebase AI SDK requires the Firebase AI ` +
1265
1291
  `API ('firebasevertexai.googleapis.com') to be enabled in your ` +
1266
1292
  `Firebase project. Enable this API by visiting the Firebase Console ` +
1267
- `at https://console.firebase.google.com/project/${url.params.apiSettings.project}/genai/ ` +
1293
+ `at https://console.firebase.google.com/project/${url.params.apiSettings.project}/ailogic/ ` +
1268
1294
  `and clicking "Get started". If you enabled this API recently, ` +
1269
1295
  `wait a few minutes for the action to propagate to our systems and ` +
1270
1296
  `then retry.`, {
@@ -1284,16 +1310,18 @@ async function makeRequest(requestUrlParams, body) {
1284
1310
  let err = e;
1285
1311
  if (e.code !== AIErrorCode.FETCH_ERROR &&
1286
1312
  e.code !== AIErrorCode.API_NOT_ENABLED &&
1287
- e instanceof Error) {
1313
+ e instanceof Error &&
1314
+ e.name !== ABORT_ERROR_NAME) {
1288
1315
  err = new AIError(AIErrorCode.ERROR, `Error fetching from ${url.toString()}: ${e.message}`);
1289
1316
  err.stack = e.stack;
1290
1317
  }
1291
1318
  throw err;
1292
1319
  }
1293
1320
  finally {
1294
- if (fetchTimeoutId) {
1295
- clearTimeout(fetchTimeoutId);
1296
- }
1321
+ // When doing streaming requests, this will clear the timeout once the stream begins.
1322
+ // If a timeout it 3000ms, and the stream starts after 300ms and ends after 5000ms, the
1323
+ // timeout will be cleared after 300ms, so it won't abort the request.
1324
+ clearTimeout(fetchTimeoutId);
1297
1325
  }
1298
1326
  return response;
1299
1327
  }
@@ -1731,6 +1759,8 @@ const responseLineRE = /^data\: (.*)(?:\n\n|\r\r|\r\n\r\n)/;
1731
1759
  function processStream(response, apiSettings, inferenceSource) {
1732
1760
  const inputStream = response.body.pipeThrough(new TextDecoderStream('utf8', { fatal: true }));
1733
1761
  const responseStream = getResponseStream(inputStream);
1762
+ // We split the stream so the user can iterate over partial results (stream1)
1763
+ // while we aggregate the full result for history/final response (stream2).
1734
1764
  const [stream1, stream2] = responseStream.tee();
1735
1765
  return {
1736
1766
  stream: generateResponseSequence(stream1, apiSettings, inferenceSource),
@@ -1767,7 +1797,6 @@ async function* generateResponseSequence(stream, apiSettings, inferenceSource) {
1767
1797
  enhancedResponse = createEnhancedContentResponse(value, inferenceSource);
1768
1798
  }
1769
1799
  const firstCandidate = enhancedResponse.candidates?.[0];
1770
- // Don't yield a response with no useful data for the developer.
1771
1800
  if (!firstCandidate?.content?.parts &&
1772
1801
  !firstCandidate?.finishReason &&
1773
1802
  !firstCandidate?.citationMetadata &&
@@ -1778,9 +1807,7 @@ async function* generateResponseSequence(stream, apiSettings, inferenceSource) {
1778
1807
  }
1779
1808
  }
1780
1809
  /**
1781
- * Reads a raw stream from the fetch response and join incomplete
1782
- * chunks, returning a new stream that provides a single complete
1783
- * GenerateContentResponse in each iteration.
1810
+ * Reads a raw string stream, buffers incomplete chunks, and yields parsed JSON objects.
1784
1811
  */
1785
1812
  function getResponseStream(inputStream) {
1786
1813
  const reader = inputStream.getReader();
@@ -1799,6 +1826,8 @@ function getResponseStream(inputStream) {
1799
1826
  return;
1800
1827
  }
1801
1828
  currentText += value;
1829
+ // SSE events may span chunk boundaries, so we buffer until we match
1830
+ // the full "data: {json}\n\n" pattern.
1802
1831
  let match = currentText.match(responseLineRE);
1803
1832
  let parsedResponse;
1804
1833
  while (match) {
@@ -1832,8 +1861,7 @@ function aggregateResponses(responses) {
1832
1861
  for (const response of responses) {
1833
1862
  if (response.candidates) {
1834
1863
  for (const candidate of response.candidates) {
1835
- // Index will be undefined if it's the first index (0), so we should use 0 if it's undefined.
1836
- // See: https://github.com/firebase/firebase-js-sdk/issues/8566
1864
+ // Use 0 if index is undefined (protobuf default value omission).
1837
1865
  const i = candidate.index || 0;
1838
1866
  if (!aggregatedResponse.candidates) {
1839
1867
  aggregatedResponse.candidates = [];
@@ -1843,7 +1871,7 @@ function aggregateResponses(responses) {
1843
1871
  index: candidate.index
1844
1872
  };
1845
1873
  }
1846
- // Keep overwriting, the last one will be final
1874
+ // Overwrite with the latest metadata
1847
1875
  aggregatedResponse.candidates[i].citationMetadata =
1848
1876
  candidate.citationMetadata;
1849
1877
  aggregatedResponse.candidates[i].finishReason = candidate.finishReason;
@@ -1864,12 +1892,7 @@ function aggregateResponses(responses) {
1864
1892
  aggregatedResponse.candidates[i].urlContextMetadata =
1865
1893
  urlContextMetadata;
1866
1894
  }
1867
- /**
1868
- * Candidates should always have content and parts, but this handles
1869
- * possible malformed responses.
1870
- */
1871
1895
  if (candidate.content) {
1872
- // Skip a candidate without parts.
1873
1896
  if (!candidate.content.parts) {
1874
1897
  continue;
1875
1898
  }
@@ -2001,7 +2024,7 @@ async function callCloudOrDevice(request, chromeAdapter, onDeviceCall, inCloudCa
2001
2024
  * See the License for the specific language governing permissions and
2002
2025
  * limitations under the License.
2003
2026
  */
2004
- async function generateContentStreamOnCloud(apiSettings, model, params, requestOptions) {
2027
+ async function generateContentStreamOnCloud(apiSettings, model, params, singleRequestOptions) {
2005
2028
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2006
2029
  params = mapGenerateContentRequest(params);
2007
2030
  }
@@ -2010,14 +2033,14 @@ async function generateContentStreamOnCloud(apiSettings, model, params, requestO
2010
2033
  model,
2011
2034
  apiSettings,
2012
2035
  stream: true,
2013
- requestOptions
2036
+ singleRequestOptions
2014
2037
  }, JSON.stringify(params));
2015
2038
  }
2016
- async function generateContentStream(apiSettings, model, params, chromeAdapter, requestOptions) {
2017
- const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContentStream(params), () => generateContentStreamOnCloud(apiSettings, model, params, requestOptions));
2018
- return processStream(callResult.response, apiSettings); // TODO: Map streaming responses
2039
+ async function generateContentStream(apiSettings, model, params, chromeAdapter, singleRequestOptions) {
2040
+ const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContentStream(params), () => generateContentStreamOnCloud(apiSettings, model, params, singleRequestOptions));
2041
+ return processStream(callResult.response, apiSettings, callResult.inferenceSource);
2019
2042
  }
2020
- async function generateContentOnCloud(apiSettings, model, params, requestOptions) {
2043
+ async function generateContentOnCloud(apiSettings, model, params, singleRequestOptions) {
2021
2044
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2022
2045
  params = mapGenerateContentRequest(params);
2023
2046
  }
@@ -2026,16 +2049,16 @@ async function generateContentOnCloud(apiSettings, model, params, requestOptions
2026
2049
  task: "generateContent" /* Task.GENERATE_CONTENT */,
2027
2050
  apiSettings,
2028
2051
  stream: false,
2029
- requestOptions
2052
+ singleRequestOptions
2030
2053
  }, JSON.stringify(params));
2031
2054
  }
2032
- async function templateGenerateContent(apiSettings, templateId, templateParams, requestOptions) {
2055
+ async function templateGenerateContent(apiSettings, templateId, templateParams, singleRequestOptions) {
2033
2056
  const response = await makeRequest({
2034
2057
  task: "templateGenerateContent" /* ServerPromptTemplateTask.TEMPLATE_GENERATE_CONTENT */,
2035
2058
  templateId,
2036
2059
  apiSettings,
2037
2060
  stream: false,
2038
- requestOptions
2061
+ singleRequestOptions
2039
2062
  }, JSON.stringify(templateParams));
2040
2063
  const generateContentResponse = await processGenerateContentResponse(response, apiSettings);
2041
2064
  const enhancedResponse = createEnhancedContentResponse(generateContentResponse);
@@ -2043,18 +2066,18 @@ async function templateGenerateContent(apiSettings, templateId, templateParams,
2043
2066
  response: enhancedResponse
2044
2067
  };
2045
2068
  }
2046
- async function templateGenerateContentStream(apiSettings, templateId, templateParams, requestOptions) {
2069
+ async function templateGenerateContentStream(apiSettings, templateId, templateParams, singleRequestOptions) {
2047
2070
  const response = await makeRequest({
2048
2071
  task: "templateStreamGenerateContent" /* ServerPromptTemplateTask.TEMPLATE_STREAM_GENERATE_CONTENT */,
2049
2072
  templateId,
2050
2073
  apiSettings,
2051
2074
  stream: true,
2052
- requestOptions
2075
+ singleRequestOptions
2053
2076
  }, JSON.stringify(templateParams));
2054
2077
  return processStream(response, apiSettings);
2055
2078
  }
2056
- async function generateContent(apiSettings, model, params, chromeAdapter, requestOptions) {
2057
- const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContent(params), () => generateContentOnCloud(apiSettings, model, params, requestOptions));
2079
+ async function generateContent(apiSettings, model, params, chromeAdapter, singleRequestOptions) {
2080
+ const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContent(params), () => generateContentOnCloud(apiSettings, model, params, singleRequestOptions));
2058
2081
  const generateContentResponse = await processGenerateContentResponse(callResult.response, apiSettings);
2059
2082
  const enhancedResponse = createEnhancedContentResponse(generateContentResponse, callResult.inferenceSource);
2060
2083
  return {
@@ -2308,7 +2331,8 @@ function validateChatHistory(history) {
2308
2331
  * limitations under the License.
2309
2332
  */
2310
2333
  /**
2311
- * Do not log a message for this error.
2334
+ * Used to break the internal promise chain when an error is already handled
2335
+ * by the user, preventing duplicate console logs.
2312
2336
  */
2313
2337
  const SILENT_ERROR = 'SILENT_ERROR';
2314
2338
  /**
@@ -2324,6 +2348,10 @@ class ChatSession {
2324
2348
  this.params = params;
2325
2349
  this.requestOptions = requestOptions;
2326
2350
  this._history = [];
2351
+ /**
2352
+ * Ensures sequential execution of chat messages to maintain history order.
2353
+ * Each call waits for the previous one to settle before proceeding.
2354
+ */
2327
2355
  this._sendPromise = Promise.resolve();
2328
2356
  this._apiSettings = apiSettings;
2329
2357
  if (params?.history) {
@@ -2344,7 +2372,7 @@ class ChatSession {
2344
2372
  * Sends a chat message and receives a non-streaming
2345
2373
  * {@link GenerateContentResult}
2346
2374
  */
2347
- async sendMessage(request) {
2375
+ async sendMessage(request, singleRequestOptions) {
2348
2376
  await this._sendPromise;
2349
2377
  const newContent = formatNewContent(request);
2350
2378
  const generateContentRequest = {
@@ -2356,16 +2384,20 @@ class ChatSession {
2356
2384
  contents: [...this._history, newContent]
2357
2385
  };
2358
2386
  let finalResult = {};
2359
- // Add onto the chain.
2360
2387
  this._sendPromise = this._sendPromise
2361
- .then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions))
2388
+ .then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, {
2389
+ ...this.requestOptions,
2390
+ ...singleRequestOptions
2391
+ }))
2362
2392
  .then(result => {
2393
+ // TODO: Make this update atomic. If creating `responseContent` throws,
2394
+ // history will contain the user message but not the response, causing
2395
+ // validation errors on the next request.
2363
2396
  if (result.response.candidates &&
2364
2397
  result.response.candidates.length > 0) {
2365
2398
  this._history.push(newContent);
2366
2399
  const responseContent = {
2367
2400
  parts: result.response.candidates?.[0].content.parts || [],
2368
- // Response seems to come back without a role set.
2369
2401
  role: result.response.candidates?.[0].content.role || 'model'
2370
2402
  };
2371
2403
  this._history.push(responseContent);
@@ -2386,7 +2418,7 @@ class ChatSession {
2386
2418
  * {@link GenerateContentStreamResult} containing an iterable stream
2387
2419
  * and a response promise.
2388
2420
  */
2389
- async sendMessageStream(request) {
2421
+ async sendMessageStream(request, singleRequestOptions) {
2390
2422
  await this._sendPromise;
2391
2423
  const newContent = formatNewContent(request);
2392
2424
  const generateContentRequest = {
@@ -2397,21 +2429,29 @@ class ChatSession {
2397
2429
  systemInstruction: this.params?.systemInstruction,
2398
2430
  contents: [...this._history, newContent]
2399
2431
  };
2400
- const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions);
2401
- // Add onto the chain.
2432
+ const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, {
2433
+ ...this.requestOptions,
2434
+ ...singleRequestOptions
2435
+ });
2436
+ // We hook into the chain to update history, but we don't block the
2437
+ // return of `streamPromise` to the user.
2402
2438
  this._sendPromise = this._sendPromise
2403
2439
  .then(() => streamPromise)
2404
- // This must be handled to avoid unhandled rejection, but jump
2405
- // to the final catch block with a label to not log this error.
2406
2440
  .catch(_ignored => {
2441
+ // If the initial fetch fails, the user's `streamPromise` rejects.
2442
+ // We swallow the error here to prevent double logging in the final catch.
2407
2443
  throw new Error(SILENT_ERROR);
2408
2444
  })
2409
2445
  .then(streamResult => streamResult.response)
2410
2446
  .then(response => {
2447
+ // This runs after the stream completes. Runtime errors here cannot be
2448
+ // caught by the user because their promise has likely already resolved.
2449
+ // TODO: Move response validation logic upstream to `stream-reader` so
2450
+ // errors propagate to the user's `result.response` promise.
2411
2451
  if (response.candidates && response.candidates.length > 0) {
2412
2452
  this._history.push(newContent);
2453
+ // TODO: Validate that `response.candidates[0].content` is not null.
2413
2454
  const responseContent = { ...response.candidates[0].content };
2414
- // Response seems to come back without a role set.
2415
2455
  if (!responseContent.role) {
2416
2456
  responseContent.role = 'model';
2417
2457
  }
@@ -2425,12 +2465,8 @@ class ChatSession {
2425
2465
  }
2426
2466
  })
2427
2467
  .catch(e => {
2428
- // Errors in streamPromise are already catchable by the user as
2429
- // streamPromise is returned.
2430
- // Avoid duplicating the error message in logs.
2431
- if (e.message !== SILENT_ERROR) {
2432
- // Users do not have access to _sendPromise to catch errors
2433
- // downstream from streamPromise, so they should not throw.
2468
+ // Filter out errors already handled by the user or initiated by them.
2469
+ if (e.message !== SILENT_ERROR && e.name !== 'AbortError') {
2434
2470
  logger.error(e);
2435
2471
  }
2436
2472
  });
@@ -2454,7 +2490,7 @@ class ChatSession {
2454
2490
  * See the License for the specific language governing permissions and
2455
2491
  * limitations under the License.
2456
2492
  */
2457
- async function countTokensOnCloud(apiSettings, model, params, requestOptions) {
2493
+ async function countTokensOnCloud(apiSettings, model, params, singleRequestOptions) {
2458
2494
  let body = '';
2459
2495
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2460
2496
  const mappedParams = mapCountTokensRequest(params, model);
@@ -2468,7 +2504,7 @@ async function countTokensOnCloud(apiSettings, model, params, requestOptions) {
2468
2504
  task: "countTokens" /* Task.COUNT_TOKENS */,
2469
2505
  apiSettings,
2470
2506
  stream: false,
2471
- requestOptions
2507
+ singleRequestOptions
2472
2508
  }, body);
2473
2509
  return response.json();
2474
2510
  }
@@ -2504,6 +2540,7 @@ class GenerativeModel extends AIModel {
2504
2540
  super(ai, modelParams.model);
2505
2541
  this.chromeAdapter = chromeAdapter;
2506
2542
  this.generationConfig = modelParams.generationConfig || {};
2543
+ validateGenerationConfig(this.generationConfig);
2507
2544
  this.safetySettings = modelParams.safetySettings || [];
2508
2545
  this.tools = modelParams.tools;
2509
2546
  this.toolConfig = modelParams.toolConfig;
@@ -2514,7 +2551,7 @@ class GenerativeModel extends AIModel {
2514
2551
  * Makes a single non-streaming call to the model
2515
2552
  * and returns an object containing a single {@link GenerateContentResponse}.
2516
2553
  */
2517
- async generateContent(request) {
2554
+ async generateContent(request, singleRequestOptions) {
2518
2555
  const formattedParams = formatGenerateContentInput(request);
2519
2556
  return generateContent(this._apiSettings, this.model, {
2520
2557
  generationConfig: this.generationConfig,
@@ -2523,7 +2560,12 @@ class GenerativeModel extends AIModel {
2523
2560
  toolConfig: this.toolConfig,
2524
2561
  systemInstruction: this.systemInstruction,
2525
2562
  ...formattedParams
2526
- }, this.chromeAdapter, this.requestOptions);
2563
+ }, this.chromeAdapter,
2564
+ // Merge request options
2565
+ {
2566
+ ...this.requestOptions,
2567
+ ...singleRequestOptions
2568
+ });
2527
2569
  }
2528
2570
  /**
2529
2571
  * Makes a single streaming call to the model
@@ -2531,7 +2573,7 @@ class GenerativeModel extends AIModel {
2531
2573
  * over all chunks in the streaming response as well as
2532
2574
  * a promise that returns the final aggregated response.
2533
2575
  */
2534
- async generateContentStream(request) {
2576
+ async generateContentStream(request, singleRequestOptions) {
2535
2577
  const formattedParams = formatGenerateContentInput(request);
2536
2578
  return generateContentStream(this._apiSettings, this.model, {
2537
2579
  generationConfig: this.generationConfig,
@@ -2540,7 +2582,12 @@ class GenerativeModel extends AIModel {
2540
2582
  toolConfig: this.toolConfig,
2541
2583
  systemInstruction: this.systemInstruction,
2542
2584
  ...formattedParams
2543
- }, this.chromeAdapter, this.requestOptions);
2585
+ }, this.chromeAdapter,
2586
+ // Merge request options
2587
+ {
2588
+ ...this.requestOptions,
2589
+ ...singleRequestOptions
2590
+ });
2544
2591
  }
2545
2592
  /**
2546
2593
  * Gets a new {@link ChatSession} instance which can be used for
@@ -2564,9 +2611,26 @@ class GenerativeModel extends AIModel {
2564
2611
  /**
2565
2612
  * Counts the tokens in the provided request.
2566
2613
  */
2567
- async countTokens(request) {
2614
+ async countTokens(request, singleRequestOptions) {
2568
2615
  const formattedParams = formatGenerateContentInput(request);
2569
- return countTokens(this._apiSettings, this.model, formattedParams, this.chromeAdapter);
2616
+ return countTokens(this._apiSettings, this.model, formattedParams, this.chromeAdapter,
2617
+ // Merge request options
2618
+ {
2619
+ ...this.requestOptions,
2620
+ ...singleRequestOptions
2621
+ });
2622
+ }
2623
+ }
2624
+ /**
2625
+ * Client-side validation of some common `GenerationConfig` pitfalls, in order
2626
+ * to save the developer a wasted request.
2627
+ */
2628
+ function validateGenerationConfig(generationConfig) {
2629
+ if (
2630
+ // != allows for null and undefined. 0 is considered "set" by the model
2631
+ generationConfig.thinkingConfig?.thinkingBudget != null &&
2632
+ generationConfig.thinkingConfig?.thinkingLevel) {
2633
+ throw new AIError(AIErrorCode.UNSUPPORTED, `Cannot set both thinkingBudget and thinkingLevel in a config.`);
2570
2634
  }
2571
2635
  }
2572
2636
 
@@ -3017,7 +3081,7 @@ class ImagenModel extends AIModel {
3017
3081
  *
3018
3082
  * @public
3019
3083
  */
3020
- async generateImages(prompt) {
3084
+ async generateImages(prompt, singleRequestOptions) {
3021
3085
  const body = createPredictRequestBody(prompt, {
3022
3086
  ...this.generationConfig,
3023
3087
  ...this.safetySettings
@@ -3027,7 +3091,11 @@ class ImagenModel extends AIModel {
3027
3091
  model: this.model,
3028
3092
  apiSettings: this._apiSettings,
3029
3093
  stream: false,
3030
- requestOptions: this.requestOptions
3094
+ // Merge request options. Single request options overwrite the model's request options.
3095
+ singleRequestOptions: {
3096
+ ...this.requestOptions,
3097
+ ...singleRequestOptions
3098
+ }
3031
3099
  }, JSON.stringify(body));
3032
3100
  return handlePredictResponse(response);
3033
3101
  }
@@ -3050,7 +3118,7 @@ class ImagenModel extends AIModel {
3050
3118
  * returned object will have a `filteredReason` property.
3051
3119
  * If all images are filtered, the `images` array will be empty.
3052
3120
  */
3053
- async generateImagesGCS(prompt, gcsURI) {
3121
+ async generateImagesGCS(prompt, gcsURI, singleRequestOptions) {
3054
3122
  const body = createPredictRequestBody(prompt, {
3055
3123
  gcsURI,
3056
3124
  ...this.generationConfig,
@@ -3061,7 +3129,11 @@ class ImagenModel extends AIModel {
3061
3129
  model: this.model,
3062
3130
  apiSettings: this._apiSettings,
3063
3131
  stream: false,
3064
- requestOptions: this.requestOptions
3132
+ // Merge request options. Single request options overwrite the model's request options.
3133
+ singleRequestOptions: {
3134
+ ...this.requestOptions,
3135
+ ...singleRequestOptions
3136
+ }
3065
3137
  }, JSON.stringify(body));
3066
3138
  return handlePredictResponse(response);
3067
3139
  }
@@ -3255,9 +3327,11 @@ class TemplateGenerativeModel {
3255
3327
  *
3256
3328
  * @beta
3257
3329
  */
3258
- async generateContent(templateId, templateVariables // anything!
3259
- ) {
3260
- return templateGenerateContent(this._apiSettings, templateId, { inputs: templateVariables }, this.requestOptions);
3330
+ async generateContent(templateId, templateVariables, singleRequestOptions) {
3331
+ return templateGenerateContent(this._apiSettings, templateId, { inputs: templateVariables }, {
3332
+ ...this.requestOptions,
3333
+ ...singleRequestOptions
3334
+ });
3261
3335
  }
3262
3336
  /**
3263
3337
  * Makes a single streaming call to the model and returns an object
@@ -3271,8 +3345,11 @@ class TemplateGenerativeModel {
3271
3345
  *
3272
3346
  * @beta
3273
3347
  */
3274
- async generateContentStream(templateId, templateVariables) {
3275
- return templateGenerateContentStream(this._apiSettings, templateId, { inputs: templateVariables }, this.requestOptions);
3348
+ async generateContentStream(templateId, templateVariables, singleRequestOptions) {
3349
+ return templateGenerateContentStream(this._apiSettings, templateId, { inputs: templateVariables }, {
3350
+ ...this.requestOptions,
3351
+ ...singleRequestOptions
3352
+ });
3276
3353
  }
3277
3354
  }
3278
3355
 
@@ -3317,13 +3394,16 @@ class TemplateImagenModel {
3317
3394
  *
3318
3395
  * @beta
3319
3396
  */
3320
- async generateImages(templateId, templateVariables) {
3397
+ async generateImages(templateId, templateVariables, singleRequestOptions) {
3321
3398
  const response = await makeRequest({
3322
3399
  task: "templatePredict" /* ServerPromptTemplateTask.TEMPLATE_PREDICT */,
3323
3400
  templateId,
3324
3401
  apiSettings: this._apiSettings,
3325
3402
  stream: false,
3326
- requestOptions: this.requestOptions
3403
+ singleRequestOptions: {
3404
+ ...this.requestOptions,
3405
+ ...singleRequestOptions
3406
+ }
3327
3407
  }, JSON.stringify({ inputs: templateVariables }));
3328
3408
  return handlePredictResponse(response);
3329
3409
  }
@@ -4200,6 +4280,7 @@ exports.SchemaType = SchemaType;
4200
4280
  exports.StringSchema = StringSchema;
4201
4281
  exports.TemplateGenerativeModel = TemplateGenerativeModel;
4202
4282
  exports.TemplateImagenModel = TemplateImagenModel;
4283
+ exports.ThinkingLevel = ThinkingLevel;
4203
4284
  exports.URLRetrievalStatus = URLRetrievalStatus;
4204
4285
  exports.VertexAIBackend = VertexAIBackend;
4205
4286
  exports.getAI = getAI;