@firebase/ai 2.6.1-canary.9cf4b7e35 → 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
package/dist/index.cjs.js CHANGED
@@ -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.1-canary.9cf4b7e35";
11
+ var version = "2.6.1-canary.b2827448b";
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
@@ -1445,6 +1458,8 @@ class AIModel {
1445
1458
  * See the License for the specific language governing permissions and
1446
1459
  * limitations under the License.
1447
1460
  */
1461
+ const TIMEOUT_EXPIRED_MESSAGE = 'Timeout has expired.';
1462
+ const ABORT_ERROR_NAME = 'AbortError';
1448
1463
  class RequestURL {
1449
1464
  constructor(params) {
1450
1465
  this.params = params;
@@ -1467,7 +1482,7 @@ class RequestURL {
1467
1482
  }
1468
1483
  }
1469
1484
  get baseUrl() {
1470
- return this.params.requestOptions?.baseUrl ?? `https://${DEFAULT_DOMAIN}`;
1485
+ return (this.params.singleRequestOptions?.baseUrl ?? `https://${DEFAULT_DOMAIN}`);
1471
1486
  }
1472
1487
  get queryParams() {
1473
1488
  const params = new URLSearchParams();
@@ -1535,21 +1550,32 @@ async function getHeaders(url) {
1535
1550
  async function makeRequest(requestUrlParams, body) {
1536
1551
  const url = new RequestURL(requestUrlParams);
1537
1552
  let response;
1538
- let fetchTimeoutId;
1553
+ const externalSignal = requestUrlParams.singleRequestOptions?.signal;
1554
+ const timeoutMillis = requestUrlParams.singleRequestOptions?.timeout != null &&
1555
+ requestUrlParams.singleRequestOptions.timeout >= 0
1556
+ ? requestUrlParams.singleRequestOptions.timeout
1557
+ : DEFAULT_FETCH_TIMEOUT_MS;
1558
+ const internalAbortController = new AbortController();
1559
+ const fetchTimeoutId = setTimeout(() => {
1560
+ internalAbortController.abort(new DOMException(TIMEOUT_EXPIRED_MESSAGE, ABORT_ERROR_NAME));
1561
+ logger.debug(`Aborting request to ${url} due to timeout (${timeoutMillis}ms)`);
1562
+ }, timeoutMillis);
1563
+ // Used to abort the fetch if either the user-defined `externalSignal` is aborted, or if the
1564
+ // internal signal (triggered by timeouts) is aborted.
1565
+ const combinedSignal = AbortSignal.any(externalSignal
1566
+ ? [externalSignal, internalAbortController.signal]
1567
+ : [internalAbortController.signal]);
1568
+ if (externalSignal && externalSignal.aborted) {
1569
+ clearTimeout(fetchTimeoutId);
1570
+ throw new DOMException(externalSignal.reason ?? 'Aborted externally before fetch', ABORT_ERROR_NAME);
1571
+ }
1539
1572
  try {
1540
1573
  const fetchOptions = {
1541
1574
  method: 'POST',
1542
1575
  headers: await getHeaders(url),
1576
+ signal: combinedSignal,
1543
1577
  body
1544
1578
  };
1545
- // Timeout is 180s by default.
1546
- const timeoutMillis = requestUrlParams.requestOptions?.timeout != null &&
1547
- requestUrlParams.requestOptions.timeout >= 0
1548
- ? requestUrlParams.requestOptions.timeout
1549
- : DEFAULT_FETCH_TIMEOUT_MS;
1550
- const abortController = new AbortController();
1551
- fetchTimeoutId = setTimeout(() => abortController.abort(), timeoutMillis);
1552
- fetchOptions.signal = abortController.signal;
1553
1579
  response = await fetch(url.toString(), fetchOptions);
1554
1580
  if (!response.ok) {
1555
1581
  let message = '';
@@ -1592,16 +1618,18 @@ async function makeRequest(requestUrlParams, body) {
1592
1618
  let err = e;
1593
1619
  if (e.code !== AIErrorCode.FETCH_ERROR &&
1594
1620
  e.code !== AIErrorCode.API_NOT_ENABLED &&
1595
- e instanceof Error) {
1621
+ e instanceof Error &&
1622
+ e.name !== ABORT_ERROR_NAME) {
1596
1623
  err = new AIError(AIErrorCode.ERROR, `Error fetching from ${url.toString()}: ${e.message}`);
1597
1624
  err.stack = e.stack;
1598
1625
  }
1599
1626
  throw err;
1600
1627
  }
1601
1628
  finally {
1602
- if (fetchTimeoutId) {
1603
- clearTimeout(fetchTimeoutId);
1604
- }
1629
+ // When doing streaming requests, this will clear the timeout once the stream begins.
1630
+ // If a timeout it 3000ms, and the stream starts after 300ms and ends after 5000ms, the
1631
+ // timeout will be cleared after 300ms, so it won't abort the request.
1632
+ clearTimeout(fetchTimeoutId);
1605
1633
  }
1606
1634
  return response;
1607
1635
  }
@@ -2039,6 +2067,8 @@ const responseLineRE = /^data\: (.*)(?:\n\n|\r\r|\r\n\r\n)/;
2039
2067
  function processStream(response, apiSettings, inferenceSource) {
2040
2068
  const inputStream = response.body.pipeThrough(new TextDecoderStream('utf8', { fatal: true }));
2041
2069
  const responseStream = getResponseStream(inputStream);
2070
+ // We split the stream so the user can iterate over partial results (stream1)
2071
+ // while we aggregate the full result for history/final response (stream2).
2042
2072
  const [stream1, stream2] = responseStream.tee();
2043
2073
  return {
2044
2074
  stream: generateResponseSequence(stream1, apiSettings, inferenceSource),
@@ -2075,7 +2105,6 @@ async function* generateResponseSequence(stream, apiSettings, inferenceSource) {
2075
2105
  enhancedResponse = createEnhancedContentResponse(value, inferenceSource);
2076
2106
  }
2077
2107
  const firstCandidate = enhancedResponse.candidates?.[0];
2078
- // Don't yield a response with no useful data for the developer.
2079
2108
  if (!firstCandidate?.content?.parts &&
2080
2109
  !firstCandidate?.finishReason &&
2081
2110
  !firstCandidate?.citationMetadata &&
@@ -2086,9 +2115,7 @@ async function* generateResponseSequence(stream, apiSettings, inferenceSource) {
2086
2115
  }
2087
2116
  }
2088
2117
  /**
2089
- * Reads a raw stream from the fetch response and join incomplete
2090
- * chunks, returning a new stream that provides a single complete
2091
- * GenerateContentResponse in each iteration.
2118
+ * Reads a raw string stream, buffers incomplete chunks, and yields parsed JSON objects.
2092
2119
  */
2093
2120
  function getResponseStream(inputStream) {
2094
2121
  const reader = inputStream.getReader();
@@ -2107,6 +2134,8 @@ function getResponseStream(inputStream) {
2107
2134
  return;
2108
2135
  }
2109
2136
  currentText += value;
2137
+ // SSE events may span chunk boundaries, so we buffer until we match
2138
+ // the full "data: {json}\n\n" pattern.
2110
2139
  let match = currentText.match(responseLineRE);
2111
2140
  let parsedResponse;
2112
2141
  while (match) {
@@ -2140,8 +2169,7 @@ function aggregateResponses(responses) {
2140
2169
  for (const response of responses) {
2141
2170
  if (response.candidates) {
2142
2171
  for (const candidate of response.candidates) {
2143
- // Index will be undefined if it's the first index (0), so we should use 0 if it's undefined.
2144
- // See: https://github.com/firebase/firebase-js-sdk/issues/8566
2172
+ // Use 0 if index is undefined (protobuf default value omission).
2145
2173
  const i = candidate.index || 0;
2146
2174
  if (!aggregatedResponse.candidates) {
2147
2175
  aggregatedResponse.candidates = [];
@@ -2151,7 +2179,7 @@ function aggregateResponses(responses) {
2151
2179
  index: candidate.index
2152
2180
  };
2153
2181
  }
2154
- // Keep overwriting, the last one will be final
2182
+ // Overwrite with the latest metadata
2155
2183
  aggregatedResponse.candidates[i].citationMetadata =
2156
2184
  candidate.citationMetadata;
2157
2185
  aggregatedResponse.candidates[i].finishReason = candidate.finishReason;
@@ -2172,12 +2200,7 @@ function aggregateResponses(responses) {
2172
2200
  aggregatedResponse.candidates[i].urlContextMetadata =
2173
2201
  urlContextMetadata;
2174
2202
  }
2175
- /**
2176
- * Candidates should always have content and parts, but this handles
2177
- * possible malformed responses.
2178
- */
2179
2203
  if (candidate.content) {
2180
- // Skip a candidate without parts.
2181
2204
  if (!candidate.content.parts) {
2182
2205
  continue;
2183
2206
  }
@@ -2309,7 +2332,7 @@ async function callCloudOrDevice(request, chromeAdapter, onDeviceCall, inCloudCa
2309
2332
  * See the License for the specific language governing permissions and
2310
2333
  * limitations under the License.
2311
2334
  */
2312
- async function generateContentStreamOnCloud(apiSettings, model, params, requestOptions) {
2335
+ async function generateContentStreamOnCloud(apiSettings, model, params, singleRequestOptions) {
2313
2336
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2314
2337
  params = mapGenerateContentRequest(params);
2315
2338
  }
@@ -2318,14 +2341,14 @@ async function generateContentStreamOnCloud(apiSettings, model, params, requestO
2318
2341
  model,
2319
2342
  apiSettings,
2320
2343
  stream: true,
2321
- requestOptions
2344
+ singleRequestOptions
2322
2345
  }, JSON.stringify(params));
2323
2346
  }
2324
- async function generateContentStream(apiSettings, model, params, chromeAdapter, requestOptions) {
2325
- const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContentStream(params), () => generateContentStreamOnCloud(apiSettings, model, params, requestOptions));
2347
+ async function generateContentStream(apiSettings, model, params, chromeAdapter, singleRequestOptions) {
2348
+ const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContentStream(params), () => generateContentStreamOnCloud(apiSettings, model, params, singleRequestOptions));
2326
2349
  return processStream(callResult.response, apiSettings, callResult.inferenceSource);
2327
2350
  }
2328
- async function generateContentOnCloud(apiSettings, model, params, requestOptions) {
2351
+ async function generateContentOnCloud(apiSettings, model, params, singleRequestOptions) {
2329
2352
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2330
2353
  params = mapGenerateContentRequest(params);
2331
2354
  }
@@ -2334,16 +2357,16 @@ async function generateContentOnCloud(apiSettings, model, params, requestOptions
2334
2357
  task: "generateContent" /* Task.GENERATE_CONTENT */,
2335
2358
  apiSettings,
2336
2359
  stream: false,
2337
- requestOptions
2360
+ singleRequestOptions
2338
2361
  }, JSON.stringify(params));
2339
2362
  }
2340
- async function templateGenerateContent(apiSettings, templateId, templateParams, requestOptions) {
2363
+ async function templateGenerateContent(apiSettings, templateId, templateParams, singleRequestOptions) {
2341
2364
  const response = await makeRequest({
2342
2365
  task: "templateGenerateContent" /* ServerPromptTemplateTask.TEMPLATE_GENERATE_CONTENT */,
2343
2366
  templateId,
2344
2367
  apiSettings,
2345
2368
  stream: false,
2346
- requestOptions
2369
+ singleRequestOptions
2347
2370
  }, JSON.stringify(templateParams));
2348
2371
  const generateContentResponse = await processGenerateContentResponse(response, apiSettings);
2349
2372
  const enhancedResponse = createEnhancedContentResponse(generateContentResponse);
@@ -2351,18 +2374,18 @@ async function templateGenerateContent(apiSettings, templateId, templateParams,
2351
2374
  response: enhancedResponse
2352
2375
  };
2353
2376
  }
2354
- async function templateGenerateContentStream(apiSettings, templateId, templateParams, requestOptions) {
2377
+ async function templateGenerateContentStream(apiSettings, templateId, templateParams, singleRequestOptions) {
2355
2378
  const response = await makeRequest({
2356
2379
  task: "templateStreamGenerateContent" /* ServerPromptTemplateTask.TEMPLATE_STREAM_GENERATE_CONTENT */,
2357
2380
  templateId,
2358
2381
  apiSettings,
2359
2382
  stream: true,
2360
- requestOptions
2383
+ singleRequestOptions
2361
2384
  }, JSON.stringify(templateParams));
2362
2385
  return processStream(response, apiSettings);
2363
2386
  }
2364
- async function generateContent(apiSettings, model, params, chromeAdapter, requestOptions) {
2365
- const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContent(params), () => generateContentOnCloud(apiSettings, model, params, requestOptions));
2387
+ async function generateContent(apiSettings, model, params, chromeAdapter, singleRequestOptions) {
2388
+ const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContent(params), () => generateContentOnCloud(apiSettings, model, params, singleRequestOptions));
2366
2389
  const generateContentResponse = await processGenerateContentResponse(callResult.response, apiSettings);
2367
2390
  const enhancedResponse = createEnhancedContentResponse(generateContentResponse, callResult.inferenceSource);
2368
2391
  return {
@@ -2616,7 +2639,8 @@ function validateChatHistory(history) {
2616
2639
  * limitations under the License.
2617
2640
  */
2618
2641
  /**
2619
- * Do not log a message for this error.
2642
+ * Used to break the internal promise chain when an error is already handled
2643
+ * by the user, preventing duplicate console logs.
2620
2644
  */
2621
2645
  const SILENT_ERROR = 'SILENT_ERROR';
2622
2646
  /**
@@ -2632,6 +2656,10 @@ class ChatSession {
2632
2656
  this.params = params;
2633
2657
  this.requestOptions = requestOptions;
2634
2658
  this._history = [];
2659
+ /**
2660
+ * Ensures sequential execution of chat messages to maintain history order.
2661
+ * Each call waits for the previous one to settle before proceeding.
2662
+ */
2635
2663
  this._sendPromise = Promise.resolve();
2636
2664
  this._apiSettings = apiSettings;
2637
2665
  if (params?.history) {
@@ -2652,7 +2680,7 @@ class ChatSession {
2652
2680
  * Sends a chat message and receives a non-streaming
2653
2681
  * {@link GenerateContentResult}
2654
2682
  */
2655
- async sendMessage(request) {
2683
+ async sendMessage(request, singleRequestOptions) {
2656
2684
  await this._sendPromise;
2657
2685
  const newContent = formatNewContent(request);
2658
2686
  const generateContentRequest = {
@@ -2664,16 +2692,20 @@ class ChatSession {
2664
2692
  contents: [...this._history, newContent]
2665
2693
  };
2666
2694
  let finalResult = {};
2667
- // Add onto the chain.
2668
2695
  this._sendPromise = this._sendPromise
2669
- .then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions))
2696
+ .then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, {
2697
+ ...this.requestOptions,
2698
+ ...singleRequestOptions
2699
+ }))
2670
2700
  .then(result => {
2701
+ // TODO: Make this update atomic. If creating `responseContent` throws,
2702
+ // history will contain the user message but not the response, causing
2703
+ // validation errors on the next request.
2671
2704
  if (result.response.candidates &&
2672
2705
  result.response.candidates.length > 0) {
2673
2706
  this._history.push(newContent);
2674
2707
  const responseContent = {
2675
2708
  parts: result.response.candidates?.[0].content.parts || [],
2676
- // Response seems to come back without a role set.
2677
2709
  role: result.response.candidates?.[0].content.role || 'model'
2678
2710
  };
2679
2711
  this._history.push(responseContent);
@@ -2694,7 +2726,7 @@ class ChatSession {
2694
2726
  * {@link GenerateContentStreamResult} containing an iterable stream
2695
2727
  * and a response promise.
2696
2728
  */
2697
- async sendMessageStream(request) {
2729
+ async sendMessageStream(request, singleRequestOptions) {
2698
2730
  await this._sendPromise;
2699
2731
  const newContent = formatNewContent(request);
2700
2732
  const generateContentRequest = {
@@ -2705,21 +2737,29 @@ class ChatSession {
2705
2737
  systemInstruction: this.params?.systemInstruction,
2706
2738
  contents: [...this._history, newContent]
2707
2739
  };
2708
- const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions);
2709
- // Add onto the chain.
2740
+ const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, {
2741
+ ...this.requestOptions,
2742
+ ...singleRequestOptions
2743
+ });
2744
+ // We hook into the chain to update history, but we don't block the
2745
+ // return of `streamPromise` to the user.
2710
2746
  this._sendPromise = this._sendPromise
2711
2747
  .then(() => streamPromise)
2712
- // This must be handled to avoid unhandled rejection, but jump
2713
- // to the final catch block with a label to not log this error.
2714
2748
  .catch(_ignored => {
2749
+ // If the initial fetch fails, the user's `streamPromise` rejects.
2750
+ // We swallow the error here to prevent double logging in the final catch.
2715
2751
  throw new Error(SILENT_ERROR);
2716
2752
  })
2717
2753
  .then(streamResult => streamResult.response)
2718
2754
  .then(response => {
2755
+ // This runs after the stream completes. Runtime errors here cannot be
2756
+ // caught by the user because their promise has likely already resolved.
2757
+ // TODO: Move response validation logic upstream to `stream-reader` so
2758
+ // errors propagate to the user's `result.response` promise.
2719
2759
  if (response.candidates && response.candidates.length > 0) {
2720
2760
  this._history.push(newContent);
2761
+ // TODO: Validate that `response.candidates[0].content` is not null.
2721
2762
  const responseContent = { ...response.candidates[0].content };
2722
- // Response seems to come back without a role set.
2723
2763
  if (!responseContent.role) {
2724
2764
  responseContent.role = 'model';
2725
2765
  }
@@ -2733,12 +2773,8 @@ class ChatSession {
2733
2773
  }
2734
2774
  })
2735
2775
  .catch(e => {
2736
- // Errors in streamPromise are already catchable by the user as
2737
- // streamPromise is returned.
2738
- // Avoid duplicating the error message in logs.
2739
- if (e.message !== SILENT_ERROR) {
2740
- // Users do not have access to _sendPromise to catch errors
2741
- // downstream from streamPromise, so they should not throw.
2776
+ // Filter out errors already handled by the user or initiated by them.
2777
+ if (e.message !== SILENT_ERROR && e.name !== 'AbortError') {
2742
2778
  logger.error(e);
2743
2779
  }
2744
2780
  });
@@ -2762,7 +2798,7 @@ class ChatSession {
2762
2798
  * See the License for the specific language governing permissions and
2763
2799
  * limitations under the License.
2764
2800
  */
2765
- async function countTokensOnCloud(apiSettings, model, params, requestOptions) {
2801
+ async function countTokensOnCloud(apiSettings, model, params, singleRequestOptions) {
2766
2802
  let body = '';
2767
2803
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2768
2804
  const mappedParams = mapCountTokensRequest(params, model);
@@ -2776,7 +2812,7 @@ async function countTokensOnCloud(apiSettings, model, params, requestOptions) {
2776
2812
  task: "countTokens" /* Task.COUNT_TOKENS */,
2777
2813
  apiSettings,
2778
2814
  stream: false,
2779
- requestOptions
2815
+ singleRequestOptions
2780
2816
  }, body);
2781
2817
  return response.json();
2782
2818
  }
@@ -2812,6 +2848,7 @@ class GenerativeModel extends AIModel {
2812
2848
  super(ai, modelParams.model);
2813
2849
  this.chromeAdapter = chromeAdapter;
2814
2850
  this.generationConfig = modelParams.generationConfig || {};
2851
+ validateGenerationConfig(this.generationConfig);
2815
2852
  this.safetySettings = modelParams.safetySettings || [];
2816
2853
  this.tools = modelParams.tools;
2817
2854
  this.toolConfig = modelParams.toolConfig;
@@ -2822,7 +2859,7 @@ class GenerativeModel extends AIModel {
2822
2859
  * Makes a single non-streaming call to the model
2823
2860
  * and returns an object containing a single {@link GenerateContentResponse}.
2824
2861
  */
2825
- async generateContent(request) {
2862
+ async generateContent(request, singleRequestOptions) {
2826
2863
  const formattedParams = formatGenerateContentInput(request);
2827
2864
  return generateContent(this._apiSettings, this.model, {
2828
2865
  generationConfig: this.generationConfig,
@@ -2831,7 +2868,12 @@ class GenerativeModel extends AIModel {
2831
2868
  toolConfig: this.toolConfig,
2832
2869
  systemInstruction: this.systemInstruction,
2833
2870
  ...formattedParams
2834
- }, this.chromeAdapter, this.requestOptions);
2871
+ }, this.chromeAdapter,
2872
+ // Merge request options
2873
+ {
2874
+ ...this.requestOptions,
2875
+ ...singleRequestOptions
2876
+ });
2835
2877
  }
2836
2878
  /**
2837
2879
  * Makes a single streaming call to the model
@@ -2839,7 +2881,7 @@ class GenerativeModel extends AIModel {
2839
2881
  * over all chunks in the streaming response as well as
2840
2882
  * a promise that returns the final aggregated response.
2841
2883
  */
2842
- async generateContentStream(request) {
2884
+ async generateContentStream(request, singleRequestOptions) {
2843
2885
  const formattedParams = formatGenerateContentInput(request);
2844
2886
  return generateContentStream(this._apiSettings, this.model, {
2845
2887
  generationConfig: this.generationConfig,
@@ -2848,7 +2890,12 @@ class GenerativeModel extends AIModel {
2848
2890
  toolConfig: this.toolConfig,
2849
2891
  systemInstruction: this.systemInstruction,
2850
2892
  ...formattedParams
2851
- }, this.chromeAdapter, this.requestOptions);
2893
+ }, this.chromeAdapter,
2894
+ // Merge request options
2895
+ {
2896
+ ...this.requestOptions,
2897
+ ...singleRequestOptions
2898
+ });
2852
2899
  }
2853
2900
  /**
2854
2901
  * Gets a new {@link ChatSession} instance which can be used for
@@ -2872,9 +2919,26 @@ class GenerativeModel extends AIModel {
2872
2919
  /**
2873
2920
  * Counts the tokens in the provided request.
2874
2921
  */
2875
- async countTokens(request) {
2922
+ async countTokens(request, singleRequestOptions) {
2876
2923
  const formattedParams = formatGenerateContentInput(request);
2877
- return countTokens(this._apiSettings, this.model, formattedParams, this.chromeAdapter);
2924
+ return countTokens(this._apiSettings, this.model, formattedParams, this.chromeAdapter,
2925
+ // Merge request options
2926
+ {
2927
+ ...this.requestOptions,
2928
+ ...singleRequestOptions
2929
+ });
2930
+ }
2931
+ }
2932
+ /**
2933
+ * Client-side validation of some common `GenerationConfig` pitfalls, in order
2934
+ * to save the developer a wasted request.
2935
+ */
2936
+ function validateGenerationConfig(generationConfig) {
2937
+ if (
2938
+ // != allows for null and undefined. 0 is considered "set" by the model
2939
+ generationConfig.thinkingConfig?.thinkingBudget != null &&
2940
+ generationConfig.thinkingConfig?.thinkingLevel) {
2941
+ throw new AIError(AIErrorCode.UNSUPPORTED, `Cannot set both thinkingBudget and thinkingLevel in a config.`);
2878
2942
  }
2879
2943
  }
2880
2944
 
@@ -3325,7 +3389,7 @@ class ImagenModel extends AIModel {
3325
3389
  *
3326
3390
  * @public
3327
3391
  */
3328
- async generateImages(prompt) {
3392
+ async generateImages(prompt, singleRequestOptions) {
3329
3393
  const body = createPredictRequestBody(prompt, {
3330
3394
  ...this.generationConfig,
3331
3395
  ...this.safetySettings
@@ -3335,7 +3399,11 @@ class ImagenModel extends AIModel {
3335
3399
  model: this.model,
3336
3400
  apiSettings: this._apiSettings,
3337
3401
  stream: false,
3338
- requestOptions: this.requestOptions
3402
+ // Merge request options. Single request options overwrite the model's request options.
3403
+ singleRequestOptions: {
3404
+ ...this.requestOptions,
3405
+ ...singleRequestOptions
3406
+ }
3339
3407
  }, JSON.stringify(body));
3340
3408
  return handlePredictResponse(response);
3341
3409
  }
@@ -3358,7 +3426,7 @@ class ImagenModel extends AIModel {
3358
3426
  * returned object will have a `filteredReason` property.
3359
3427
  * If all images are filtered, the `images` array will be empty.
3360
3428
  */
3361
- async generateImagesGCS(prompt, gcsURI) {
3429
+ async generateImagesGCS(prompt, gcsURI, singleRequestOptions) {
3362
3430
  const body = createPredictRequestBody(prompt, {
3363
3431
  gcsURI,
3364
3432
  ...this.generationConfig,
@@ -3369,7 +3437,11 @@ class ImagenModel extends AIModel {
3369
3437
  model: this.model,
3370
3438
  apiSettings: this._apiSettings,
3371
3439
  stream: false,
3372
- requestOptions: this.requestOptions
3440
+ // Merge request options. Single request options overwrite the model's request options.
3441
+ singleRequestOptions: {
3442
+ ...this.requestOptions,
3443
+ ...singleRequestOptions
3444
+ }
3373
3445
  }, JSON.stringify(body));
3374
3446
  return handlePredictResponse(response);
3375
3447
  }
@@ -3563,9 +3635,11 @@ class TemplateGenerativeModel {
3563
3635
  *
3564
3636
  * @beta
3565
3637
  */
3566
- async generateContent(templateId, templateVariables // anything!
3567
- ) {
3568
- return templateGenerateContent(this._apiSettings, templateId, { inputs: templateVariables }, this.requestOptions);
3638
+ async generateContent(templateId, templateVariables, singleRequestOptions) {
3639
+ return templateGenerateContent(this._apiSettings, templateId, { inputs: templateVariables }, {
3640
+ ...this.requestOptions,
3641
+ ...singleRequestOptions
3642
+ });
3569
3643
  }
3570
3644
  /**
3571
3645
  * Makes a single streaming call to the model and returns an object
@@ -3579,8 +3653,11 @@ class TemplateGenerativeModel {
3579
3653
  *
3580
3654
  * @beta
3581
3655
  */
3582
- async generateContentStream(templateId, templateVariables) {
3583
- return templateGenerateContentStream(this._apiSettings, templateId, { inputs: templateVariables }, this.requestOptions);
3656
+ async generateContentStream(templateId, templateVariables, singleRequestOptions) {
3657
+ return templateGenerateContentStream(this._apiSettings, templateId, { inputs: templateVariables }, {
3658
+ ...this.requestOptions,
3659
+ ...singleRequestOptions
3660
+ });
3584
3661
  }
3585
3662
  }
3586
3663
 
@@ -3625,13 +3702,16 @@ class TemplateImagenModel {
3625
3702
  *
3626
3703
  * @beta
3627
3704
  */
3628
- async generateImages(templateId, templateVariables) {
3705
+ async generateImages(templateId, templateVariables, singleRequestOptions) {
3629
3706
  const response = await makeRequest({
3630
3707
  task: "templatePredict" /* ServerPromptTemplateTask.TEMPLATE_PREDICT */,
3631
3708
  templateId,
3632
3709
  apiSettings: this._apiSettings,
3633
3710
  stream: false,
3634
- requestOptions: this.requestOptions
3711
+ singleRequestOptions: {
3712
+ ...this.requestOptions,
3713
+ ...singleRequestOptions
3714
+ }
3635
3715
  }, JSON.stringify({ inputs: templateVariables }));
3636
3716
  return handlePredictResponse(response);
3637
3717
  }
@@ -4508,6 +4588,7 @@ exports.SchemaType = SchemaType;
4508
4588
  exports.StringSchema = StringSchema;
4509
4589
  exports.TemplateGenerativeModel = TemplateGenerativeModel;
4510
4590
  exports.TemplateImagenModel = TemplateImagenModel;
4591
+ exports.ThinkingLevel = ThinkingLevel;
4511
4592
  exports.URLRetrievalStatus = URLRetrievalStatus;
4512
4593
  exports.VertexAIBackend = VertexAIBackend;
4513
4594
  exports.getAI = getAI;