@firebase/ai 2.0.0 → 2.1.0-canary.02280d747

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 (51) hide show
  1. package/dist/ai-public.d.ts +217 -9
  2. package/dist/ai.d.ts +220 -10
  3. package/dist/esm/index.esm.js +394 -34
  4. package/dist/esm/index.esm.js.map +1 -1
  5. package/dist/esm/src/api.d.ts +2 -2
  6. package/dist/esm/src/constants.d.ts +4 -0
  7. package/dist/esm/src/index.d.ts +3 -0
  8. package/dist/esm/src/methods/chat-session.d.ts +3 -1
  9. package/dist/esm/src/methods/chrome-adapter.d.ts +120 -0
  10. package/dist/esm/src/methods/count-tokens.d.ts +3 -1
  11. package/dist/esm/src/methods/generate-content.d.ts +3 -2
  12. package/dist/esm/src/models/ai-model.d.ts +1 -1
  13. package/dist/esm/src/models/generative-model.d.ts +3 -1
  14. package/dist/esm/src/public-types.d.ts +10 -1
  15. package/dist/esm/src/service.d.ts +4 -1
  16. package/dist/esm/src/types/chrome-adapter.d.ts +56 -0
  17. package/dist/esm/src/types/enums.d.ts +20 -1
  18. package/dist/esm/src/types/imagen/requests.d.ts +2 -2
  19. package/dist/esm/src/types/imagen/responses.d.ts +1 -0
  20. package/dist/esm/src/types/index.d.ts +2 -0
  21. package/dist/esm/src/types/language-model.d.ts +117 -0
  22. package/dist/esm/src/types/requests.d.ts +35 -2
  23. package/dist/esm/src/types/responses.d.ts +1 -1
  24. package/dist/esm/src/types/schema.d.ts +1 -1
  25. package/dist/index.cjs.js +395 -33
  26. package/dist/index.cjs.js.map +1 -1
  27. package/dist/index.node.cjs.js +382 -22
  28. package/dist/index.node.cjs.js.map +1 -1
  29. package/dist/index.node.mjs +382 -23
  30. package/dist/index.node.mjs.map +1 -1
  31. package/dist/src/api.d.ts +2 -2
  32. package/dist/src/constants.d.ts +4 -0
  33. package/dist/src/index.d.ts +3 -0
  34. package/dist/src/methods/chat-session.d.ts +3 -1
  35. package/dist/src/methods/chrome-adapter.d.ts +120 -0
  36. package/dist/src/methods/count-tokens.d.ts +3 -1
  37. package/dist/src/methods/generate-content.d.ts +3 -2
  38. package/dist/src/models/ai-model.d.ts +1 -1
  39. package/dist/src/models/generative-model.d.ts +3 -1
  40. package/dist/src/public-types.d.ts +10 -1
  41. package/dist/src/service.d.ts +4 -1
  42. package/dist/src/types/chrome-adapter.d.ts +56 -0
  43. package/dist/src/types/enums.d.ts +20 -1
  44. package/dist/src/types/imagen/requests.d.ts +2 -2
  45. package/dist/src/types/imagen/responses.d.ts +1 -0
  46. package/dist/src/types/index.d.ts +2 -0
  47. package/dist/src/types/language-model.d.ts +117 -0
  48. package/dist/src/types/requests.d.ts +35 -2
  49. package/dist/src/types/responses.d.ts +1 -1
  50. package/dist/src/types/schema.d.ts +1 -1
  51. 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.0.0";
11
+ var version = "2.1.0-canary.02280d747";
12
12
 
13
13
  /**
14
14
  * @license
@@ -33,6 +33,10 @@ const DEFAULT_API_VERSION = 'v1beta';
33
33
  const PACKAGE_VERSION = version;
34
34
  const LANGUAGE_TAG = 'gl-js';
35
35
  const DEFAULT_FETCH_TIMEOUT_MS = 180 * 1000;
36
+ /**
37
+ * Defines the name of the default in-cloud model to use for hybrid inference.
38
+ */
39
+ const DEFAULT_HYBRID_IN_CLOUD_MODEL = 'gemini-2.0-flash-lite';
36
40
 
37
41
  /**
38
42
  * @license
@@ -88,7 +92,7 @@ const HarmBlockThreshold = {
88
92
  BLOCK_NONE: 'BLOCK_NONE',
89
93
  /**
90
94
  * All content will be allowed. This is the same as `BLOCK_NONE`, but the metadata corresponding
91
- * to the {@link HarmCategory} will not be present in the response.
95
+ * to the {@link (HarmCategory:type)} will not be present in the response.
92
96
  */
93
97
  OFF: 'OFF'
94
98
  };
@@ -291,6 +295,16 @@ const ResponseModality = {
291
295
  */
292
296
  IMAGE: 'IMAGE'
293
297
  };
298
+ /**
299
+ * <b>(EXPERIMENTAL)</b>
300
+ * Determines whether inference happens on-device or in-cloud.
301
+ * @public
302
+ */
303
+ const InferenceMode = {
304
+ 'PREFER_ON_DEVICE': 'prefer_on_device',
305
+ 'ONLY_ON_DEVICE': 'only_on_device',
306
+ 'ONLY_IN_CLOUD': 'only_in_cloud'
307
+ };
294
308
 
295
309
  /**
296
310
  * @license
@@ -464,7 +478,7 @@ const ImagenPersonFilterLevel = {
464
478
  * To specify an aspect ratio for generated images, set the `aspectRatio` property in your
465
479
  * {@link ImagenGenerationConfig}.
466
480
  *
467
- * See the the {@link http://firebase.google.com/docs/vertex-ai/generate-images | documentation }
481
+ * See the {@link http://firebase.google.com/docs/vertex-ai/generate-images | documentation }
468
482
  * for more details and examples of the supported aspect ratios.
469
483
  *
470
484
  * @beta
@@ -643,6 +657,12 @@ class AIService {
643
657
  _delete() {
644
658
  return Promise.resolve();
645
659
  }
660
+ set options(optionsToSet) {
661
+ this._options = optionsToSet;
662
+ }
663
+ get options() {
664
+ return this._options;
665
+ }
646
666
  }
647
667
 
648
668
  /**
@@ -827,7 +847,12 @@ class AIModel {
827
847
  };
828
848
  }
829
849
  else if (ai.appCheck) {
830
- this._apiSettings.getAppCheckToken = () => ai.appCheck.getToken();
850
+ if (ai.options?.useLimitedUseAppCheckTokens) {
851
+ this._apiSettings.getAppCheckToken = () => ai.appCheck.getLimitedUseToken();
852
+ }
853
+ else {
854
+ this._apiSettings.getAppCheckToken = () => ai.appCheck.getToken();
855
+ }
831
856
  }
832
857
  if (ai.auth) {
833
858
  this._apiSettings.getAuthToken = () => ai.auth.getToken();
@@ -1664,20 +1689,38 @@ function aggregateResponses(responses) {
1664
1689
  * See the License for the specific language governing permissions and
1665
1690
  * limitations under the License.
1666
1691
  */
1667
- async function generateContentStream(apiSettings, model, params, requestOptions) {
1692
+ async function generateContentStreamOnCloud(apiSettings, model, params, requestOptions) {
1668
1693
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
1669
1694
  params = mapGenerateContentRequest(params);
1670
1695
  }
1671
- const response = await makeRequest(model, Task.STREAM_GENERATE_CONTENT, apiSettings,
1696
+ return makeRequest(model, Task.STREAM_GENERATE_CONTENT, apiSettings,
1672
1697
  /* stream */ true, JSON.stringify(params), requestOptions);
1698
+ }
1699
+ async function generateContentStream(apiSettings, model, params, chromeAdapter, requestOptions) {
1700
+ let response;
1701
+ if (chromeAdapter && (await chromeAdapter.isAvailable(params))) {
1702
+ response = await chromeAdapter.generateContentStream(params);
1703
+ }
1704
+ else {
1705
+ response = await generateContentStreamOnCloud(apiSettings, model, params, requestOptions);
1706
+ }
1673
1707
  return processStream(response, apiSettings); // TODO: Map streaming responses
1674
1708
  }
1675
- async function generateContent(apiSettings, model, params, requestOptions) {
1709
+ async function generateContentOnCloud(apiSettings, model, params, requestOptions) {
1676
1710
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
1677
1711
  params = mapGenerateContentRequest(params);
1678
1712
  }
1679
- const response = await makeRequest(model, Task.GENERATE_CONTENT, apiSettings,
1713
+ return makeRequest(model, Task.GENERATE_CONTENT, apiSettings,
1680
1714
  /* stream */ false, JSON.stringify(params), requestOptions);
1715
+ }
1716
+ async function generateContent(apiSettings, model, params, chromeAdapter, requestOptions) {
1717
+ let response;
1718
+ if (chromeAdapter && (await chromeAdapter.isAvailable(params))) {
1719
+ response = await chromeAdapter.generateContent(params);
1720
+ }
1721
+ else {
1722
+ response = await generateContentOnCloud(apiSettings, model, params, requestOptions);
1723
+ }
1681
1724
  const generateContentResponse = await processGenerateContentResponse(response, apiSettings);
1682
1725
  const enhancedResponse = createEnhancedContentResponse(generateContentResponse);
1683
1726
  return {
@@ -1934,8 +1977,9 @@ const SILENT_ERROR = 'SILENT_ERROR';
1934
1977
  * @public
1935
1978
  */
1936
1979
  class ChatSession {
1937
- constructor(apiSettings, model, params, requestOptions) {
1980
+ constructor(apiSettings, model, chromeAdapter, params, requestOptions) {
1938
1981
  this.model = model;
1982
+ this.chromeAdapter = chromeAdapter;
1939
1983
  this.params = params;
1940
1984
  this.requestOptions = requestOptions;
1941
1985
  this._history = [];
@@ -1973,7 +2017,7 @@ class ChatSession {
1973
2017
  let finalResult = {};
1974
2018
  // Add onto the chain.
1975
2019
  this._sendPromise = this._sendPromise
1976
- .then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.requestOptions))
2020
+ .then(() => generateContent(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions))
1977
2021
  .then(result => {
1978
2022
  if (result.response.candidates &&
1979
2023
  result.response.candidates.length > 0) {
@@ -2012,7 +2056,7 @@ class ChatSession {
2012
2056
  systemInstruction: this.params?.systemInstruction,
2013
2057
  contents: [...this._history, newContent]
2014
2058
  };
2015
- const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.requestOptions);
2059
+ const streamPromise = generateContentStream(this._apiSettings, this.model, generateContentRequest, this.chromeAdapter, this.requestOptions);
2016
2060
  // Add onto the chain.
2017
2061
  this._sendPromise = this._sendPromise
2018
2062
  .then(() => streamPromise)
@@ -2069,7 +2113,7 @@ class ChatSession {
2069
2113
  * See the License for the specific language governing permissions and
2070
2114
  * limitations under the License.
2071
2115
  */
2072
- async function countTokens(apiSettings, model, params, requestOptions) {
2116
+ async function countTokensOnCloud(apiSettings, model, params, requestOptions) {
2073
2117
  let body = '';
2074
2118
  if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
2075
2119
  const mappedParams = mapCountTokensRequest(params, model);
@@ -2081,6 +2125,12 @@ async function countTokens(apiSettings, model, params, requestOptions) {
2081
2125
  const response = await makeRequest(model, Task.COUNT_TOKENS, apiSettings, false, body, requestOptions);
2082
2126
  return response.json();
2083
2127
  }
2128
+ async function countTokens(apiSettings, model, params, chromeAdapter, requestOptions) {
2129
+ if (chromeAdapter && (await chromeAdapter.isAvailable(params))) {
2130
+ return (await chromeAdapter.countTokens(params)).json();
2131
+ }
2132
+ return countTokensOnCloud(apiSettings, model, params, requestOptions);
2133
+ }
2084
2134
 
2085
2135
  /**
2086
2136
  * @license
@@ -2103,8 +2153,9 @@ async function countTokens(apiSettings, model, params, requestOptions) {
2103
2153
  * @public
2104
2154
  */
2105
2155
  class GenerativeModel extends AIModel {
2106
- constructor(ai, modelParams, requestOptions) {
2156
+ constructor(ai, modelParams, requestOptions, chromeAdapter) {
2107
2157
  super(ai, modelParams.model);
2158
+ this.chromeAdapter = chromeAdapter;
2108
2159
  this.generationConfig = modelParams.generationConfig || {};
2109
2160
  this.safetySettings = modelParams.safetySettings || [];
2110
2161
  this.tools = modelParams.tools;
@@ -2125,7 +2176,7 @@ class GenerativeModel extends AIModel {
2125
2176
  toolConfig: this.toolConfig,
2126
2177
  systemInstruction: this.systemInstruction,
2127
2178
  ...formattedParams
2128
- }, this.requestOptions);
2179
+ }, this.chromeAdapter, this.requestOptions);
2129
2180
  }
2130
2181
  /**
2131
2182
  * Makes a single streaming call to the model
@@ -2142,14 +2193,14 @@ class GenerativeModel extends AIModel {
2142
2193
  toolConfig: this.toolConfig,
2143
2194
  systemInstruction: this.systemInstruction,
2144
2195
  ...formattedParams
2145
- }, this.requestOptions);
2196
+ }, this.chromeAdapter, this.requestOptions);
2146
2197
  }
2147
2198
  /**
2148
2199
  * Gets a new {@link ChatSession} instance which can be used for
2149
2200
  * multi-turn chats.
2150
2201
  */
2151
2202
  startChat(startChatParams) {
2152
- return new ChatSession(this._apiSettings, this.model, {
2203
+ return new ChatSession(this._apiSettings, this.model, this.chromeAdapter, {
2153
2204
  tools: this.tools,
2154
2205
  toolConfig: this.toolConfig,
2155
2206
  systemInstruction: this.systemInstruction,
@@ -2168,7 +2219,7 @@ class GenerativeModel extends AIModel {
2168
2219
  */
2169
2220
  async countTokens(request) {
2170
2221
  const formattedParams = formatGenerateContentInput(request);
2171
- return countTokens(this._apiSettings, this.model, formattedParams);
2222
+ return countTokens(this._apiSettings, this.model, formattedParams, this.chromeAdapter);
2172
2223
  }
2173
2224
  }
2174
2225
 
@@ -2286,6 +2337,292 @@ class ImagenModel extends AIModel {
2286
2337
  }
2287
2338
  }
2288
2339
 
2340
+ /**
2341
+ * @internal
2342
+ */
2343
+ var Availability;
2344
+ (function (Availability) {
2345
+ Availability["UNAVAILABLE"] = "unavailable";
2346
+ Availability["DOWNLOADABLE"] = "downloadable";
2347
+ Availability["DOWNLOADING"] = "downloading";
2348
+ Availability["AVAILABLE"] = "available";
2349
+ })(Availability || (Availability = {}));
2350
+
2351
+ /**
2352
+ * @license
2353
+ * Copyright 2025 Google LLC
2354
+ *
2355
+ * Licensed under the Apache License, Version 2.0 (the "License");
2356
+ * you may not use this file except in compliance with the License.
2357
+ * You may obtain a copy of the License at
2358
+ *
2359
+ * http://www.apache.org/licenses/LICENSE-2.0
2360
+ *
2361
+ * Unless required by applicable law or agreed to in writing, software
2362
+ * distributed under the License is distributed on an "AS IS" BASIS,
2363
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2364
+ * See the License for the specific language governing permissions and
2365
+ * limitations under the License.
2366
+ */
2367
+ /**
2368
+ * Defines an inference "backend" that uses Chrome's on-device model,
2369
+ * and encapsulates logic for detecting when on-device inference is
2370
+ * possible.
2371
+ */
2372
+ class ChromeAdapterImpl {
2373
+ constructor(languageModelProvider, mode, onDeviceParams = {
2374
+ createOptions: {
2375
+ // Defaults to support image inputs for convenience.
2376
+ expectedInputs: [{ type: 'image' }]
2377
+ }
2378
+ }) {
2379
+ this.languageModelProvider = languageModelProvider;
2380
+ this.mode = mode;
2381
+ this.onDeviceParams = onDeviceParams;
2382
+ this.isDownloading = false;
2383
+ }
2384
+ /**
2385
+ * Checks if a given request can be made on-device.
2386
+ *
2387
+ * Encapsulates a few concerns:
2388
+ * the mode
2389
+ * API existence
2390
+ * prompt formatting
2391
+ * model availability, including triggering download if necessary
2392
+ *
2393
+ *
2394
+ * Pros: callers needn't be concerned with details of on-device availability.</p>
2395
+ * Cons: this method spans a few concerns and splits request validation from usage.
2396
+ * If instance variables weren't already part of the API, we could consider a better
2397
+ * separation of concerns.
2398
+ */
2399
+ async isAvailable(request) {
2400
+ if (!this.mode) {
2401
+ logger.debug(`On-device inference unavailable because mode is undefined.`);
2402
+ return false;
2403
+ }
2404
+ if (this.mode === InferenceMode.ONLY_IN_CLOUD) {
2405
+ logger.debug(`On-device inference unavailable because mode is "only_in_cloud".`);
2406
+ return false;
2407
+ }
2408
+ // Triggers out-of-band download so model will eventually become available.
2409
+ const availability = await this.downloadIfAvailable();
2410
+ if (this.mode === InferenceMode.ONLY_ON_DEVICE) {
2411
+ // If it will never be available due to API inavailability, throw.
2412
+ if (availability === Availability.UNAVAILABLE) {
2413
+ throw new AIError(AIErrorCode.API_NOT_ENABLED, 'Local LanguageModel API not available in this environment.');
2414
+ }
2415
+ else if (availability === Availability.DOWNLOADABLE ||
2416
+ availability === Availability.DOWNLOADING) {
2417
+ // TODO(chholland): Better user experience during download - progress?
2418
+ logger.debug(`Waiting for download of LanguageModel to complete.`);
2419
+ await this.downloadPromise;
2420
+ return true;
2421
+ }
2422
+ return true;
2423
+ }
2424
+ // Applies prefer_on_device logic.
2425
+ if (availability !== Availability.AVAILABLE) {
2426
+ logger.debug(`On-device inference unavailable because availability is "${availability}".`);
2427
+ return false;
2428
+ }
2429
+ if (!ChromeAdapterImpl.isOnDeviceRequest(request)) {
2430
+ logger.debug(`On-device inference unavailable because request is incompatible.`);
2431
+ return false;
2432
+ }
2433
+ return true;
2434
+ }
2435
+ /**
2436
+ * Generates content on device.
2437
+ *
2438
+ * @remarks
2439
+ * This is comparable to {@link GenerativeModel.generateContent} for generating content in
2440
+ * Cloud.
2441
+ * @param request - a standard Firebase AI {@link GenerateContentRequest}
2442
+ * @returns {@link Response}, so we can reuse common response formatting.
2443
+ */
2444
+ async generateContent(request) {
2445
+ const session = await this.createSession();
2446
+ const contents = await Promise.all(request.contents.map(ChromeAdapterImpl.toLanguageModelMessage));
2447
+ const text = await session.prompt(contents, this.onDeviceParams.promptOptions);
2448
+ return ChromeAdapterImpl.toResponse(text);
2449
+ }
2450
+ /**
2451
+ * Generates content stream on device.
2452
+ *
2453
+ * @remarks
2454
+ * This is comparable to {@link GenerativeModel.generateContentStream} for generating content in
2455
+ * Cloud.
2456
+ * @param request - a standard Firebase AI {@link GenerateContentRequest}
2457
+ * @returns {@link Response}, so we can reuse common response formatting.
2458
+ */
2459
+ async generateContentStream(request) {
2460
+ const session = await this.createSession();
2461
+ const contents = await Promise.all(request.contents.map(ChromeAdapterImpl.toLanguageModelMessage));
2462
+ const stream = session.promptStreaming(contents, this.onDeviceParams.promptOptions);
2463
+ return ChromeAdapterImpl.toStreamResponse(stream);
2464
+ }
2465
+ async countTokens(_request) {
2466
+ throw new AIError(AIErrorCode.REQUEST_ERROR, 'Count Tokens is not yet available for on-device model.');
2467
+ }
2468
+ /**
2469
+ * Asserts inference for the given request can be performed by an on-device model.
2470
+ */
2471
+ static isOnDeviceRequest(request) {
2472
+ // Returns false if the prompt is empty.
2473
+ if (request.contents.length === 0) {
2474
+ logger.debug('Empty prompt rejected for on-device inference.');
2475
+ return false;
2476
+ }
2477
+ for (const content of request.contents) {
2478
+ if (content.role === 'function') {
2479
+ logger.debug(`"Function" role rejected for on-device inference.`);
2480
+ return false;
2481
+ }
2482
+ // Returns false if request contains an image with an unsupported mime type.
2483
+ for (const part of content.parts) {
2484
+ if (part.inlineData &&
2485
+ ChromeAdapterImpl.SUPPORTED_MIME_TYPES.indexOf(part.inlineData.mimeType) === -1) {
2486
+ logger.debug(`Unsupported mime type "${part.inlineData.mimeType}" rejected for on-device inference.`);
2487
+ return false;
2488
+ }
2489
+ }
2490
+ }
2491
+ return true;
2492
+ }
2493
+ /**
2494
+ * Encapsulates logic to get availability and download a model if one is downloadable.
2495
+ */
2496
+ async downloadIfAvailable() {
2497
+ const availability = await this.languageModelProvider?.availability(this.onDeviceParams.createOptions);
2498
+ if (availability === Availability.DOWNLOADABLE) {
2499
+ this.download();
2500
+ }
2501
+ return availability;
2502
+ }
2503
+ /**
2504
+ * Triggers out-of-band download of an on-device model.
2505
+ *
2506
+ * Chrome only downloads models as needed. Chrome knows a model is needed when code calls
2507
+ * LanguageModel.create.
2508
+ *
2509
+ * Since Chrome manages the download, the SDK can only avoid redundant download requests by
2510
+ * tracking if a download has previously been requested.
2511
+ */
2512
+ download() {
2513
+ if (this.isDownloading) {
2514
+ return;
2515
+ }
2516
+ this.isDownloading = true;
2517
+ this.downloadPromise = this.languageModelProvider
2518
+ ?.create(this.onDeviceParams.createOptions)
2519
+ .finally(() => {
2520
+ this.isDownloading = false;
2521
+ });
2522
+ }
2523
+ /**
2524
+ * Converts Firebase AI {@link Content} object to a Chrome {@link LanguageModelMessage} object.
2525
+ */
2526
+ static async toLanguageModelMessage(content) {
2527
+ const languageModelMessageContents = await Promise.all(content.parts.map(ChromeAdapterImpl.toLanguageModelMessageContent));
2528
+ return {
2529
+ role: ChromeAdapterImpl.toLanguageModelMessageRole(content.role),
2530
+ content: languageModelMessageContents
2531
+ };
2532
+ }
2533
+ /**
2534
+ * Converts a Firebase AI Part object to a Chrome LanguageModelMessageContent object.
2535
+ */
2536
+ static async toLanguageModelMessageContent(part) {
2537
+ if (part.text) {
2538
+ return {
2539
+ type: 'text',
2540
+ value: part.text
2541
+ };
2542
+ }
2543
+ else if (part.inlineData) {
2544
+ const formattedImageContent = await fetch(`data:${part.inlineData.mimeType};base64,${part.inlineData.data}`);
2545
+ const imageBlob = await formattedImageContent.blob();
2546
+ const imageBitmap = await createImageBitmap(imageBlob);
2547
+ return {
2548
+ type: 'image',
2549
+ value: imageBitmap
2550
+ };
2551
+ }
2552
+ throw new AIError(AIErrorCode.REQUEST_ERROR, `Processing of this Part type is not currently supported.`);
2553
+ }
2554
+ /**
2555
+ * Converts a Firebase AI {@link Role} string to a {@link LanguageModelMessageRole} string.
2556
+ */
2557
+ static toLanguageModelMessageRole(role) {
2558
+ // Assumes 'function' rule has been filtered by isOnDeviceRequest
2559
+ return role === 'model' ? 'assistant' : 'user';
2560
+ }
2561
+ /**
2562
+ * Abstracts Chrome session creation.
2563
+ *
2564
+ * Chrome uses a multi-turn session for all inference. Firebase AI uses single-turn for all
2565
+ * inference. To map the Firebase AI API to Chrome's API, the SDK creates a new session for all
2566
+ * inference.
2567
+ *
2568
+ * Chrome will remove a model from memory if it's no longer in use, so this method ensures a
2569
+ * new session is created before an old session is destroyed.
2570
+ */
2571
+ async createSession() {
2572
+ if (!this.languageModelProvider) {
2573
+ throw new AIError(AIErrorCode.UNSUPPORTED, 'Chrome AI requested for unsupported browser version.');
2574
+ }
2575
+ const newSession = await this.languageModelProvider.create(this.onDeviceParams.createOptions);
2576
+ if (this.oldSession) {
2577
+ this.oldSession.destroy();
2578
+ }
2579
+ // Holds session reference, so model isn't unloaded from memory.
2580
+ this.oldSession = newSession;
2581
+ return newSession;
2582
+ }
2583
+ /**
2584
+ * Formats string returned by Chrome as a {@link Response} returned by Firebase AI.
2585
+ */
2586
+ static toResponse(text) {
2587
+ return {
2588
+ json: async () => ({
2589
+ candidates: [
2590
+ {
2591
+ content: {
2592
+ parts: [{ text }]
2593
+ }
2594
+ }
2595
+ ]
2596
+ })
2597
+ };
2598
+ }
2599
+ /**
2600
+ * Formats string stream returned by Chrome as SSE returned by Firebase AI.
2601
+ */
2602
+ static toStreamResponse(stream) {
2603
+ const encoder = new TextEncoder();
2604
+ return {
2605
+ body: stream.pipeThrough(new TransformStream({
2606
+ transform(chunk, controller) {
2607
+ const json = JSON.stringify({
2608
+ candidates: [
2609
+ {
2610
+ content: {
2611
+ role: 'model',
2612
+ parts: [{ text: chunk }]
2613
+ }
2614
+ }
2615
+ ]
2616
+ });
2617
+ controller.enqueue(encoder.encode(`data: ${json}\n\n`));
2618
+ }
2619
+ }))
2620
+ };
2621
+ }
2622
+ }
2623
+ // Visible for testing
2624
+ ChromeAdapterImpl.SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/png'];
2625
+
2289
2626
  /**
2290
2627
  * @license
2291
2628
  * Copyright 2024 Google LLC
@@ -2637,14 +2974,20 @@ class ImagenImageFormat {
2637
2974
  *
2638
2975
  * @public
2639
2976
  */
2640
- function getAI(app$1 = app.getApp(), options = { backend: new GoogleAIBackend() }) {
2977
+ function getAI(app$1 = app.getApp(), options) {
2641
2978
  app$1 = util.getModularInstance(app$1);
2642
2979
  // Dependencies
2643
2980
  const AIProvider = app._getProvider(app$1, AI_TYPE);
2644
- const identifier = encodeInstanceIdentifier(options.backend);
2645
- return AIProvider.getImmediate({
2981
+ const backend = options?.backend ?? new GoogleAIBackend();
2982
+ const finalOptions = {
2983
+ useLimitedUseAppCheckTokens: options?.useLimitedUseAppCheckTokens ?? false
2984
+ };
2985
+ const identifier = encodeInstanceIdentifier(backend);
2986
+ const aiInstance = AIProvider.getImmediate({
2646
2987
  identifier
2647
2988
  });
2989
+ aiInstance.options = finalOptions;
2990
+ return aiInstance;
2648
2991
  }
2649
2992
  /**
2650
2993
  * Returns a {@link GenerativeModel} class with methods for inference
@@ -2653,10 +2996,26 @@ function getAI(app$1 = app.getApp(), options = { backend: new GoogleAIBackend()
2653
2996
  * @public
2654
2997
  */
2655
2998
  function getGenerativeModel(ai, modelParams, requestOptions) {
2656
- if (!modelParams.model) {
2999
+ // Uses the existence of HybridParams.mode to clarify the type of the modelParams input.
3000
+ const hybridParams = modelParams;
3001
+ let inCloudParams;
3002
+ if (hybridParams.mode) {
3003
+ inCloudParams = hybridParams.inCloudParams || {
3004
+ model: DEFAULT_HYBRID_IN_CLOUD_MODEL
3005
+ };
3006
+ }
3007
+ else {
3008
+ inCloudParams = modelParams;
3009
+ }
3010
+ if (!inCloudParams.model) {
2657
3011
  throw new AIError(AIErrorCode.NO_MODEL, `Must provide a model name. Example: getGenerativeModel({ model: 'my-model-name' })`);
2658
3012
  }
2659
- return new GenerativeModel(ai, modelParams, requestOptions);
3013
+ let chromeAdapter;
3014
+ // Do not initialize a ChromeAdapter if we are not in hybrid mode.
3015
+ if (typeof window !== 'undefined' && hybridParams.mode) {
3016
+ chromeAdapter = new ChromeAdapterImpl(window.LanguageModel, hybridParams.mode, hybridParams.onDeviceParams);
3017
+ }
3018
+ return new GenerativeModel(ai, inCloudParams, requestOptions, chromeAdapter);
2660
3019
  }
2661
3020
  /**
2662
3021
  * Returns an {@link ImagenModel} class with methods for using Imagen.
@@ -2684,18 +3043,19 @@ function getImagenModel(ai, modelParams, requestOptions) {
2684
3043
  *
2685
3044
  * @packageDocumentation
2686
3045
  */
3046
+ function factory(container, { instanceIdentifier }) {
3047
+ if (!instanceIdentifier) {
3048
+ throw new AIError(AIErrorCode.ERROR, 'AIService instance identifier is undefined.');
3049
+ }
3050
+ const backend = decodeInstanceIdentifier(instanceIdentifier);
3051
+ // getImmediate for FirebaseApp will always succeed
3052
+ const app = container.getProvider('app').getImmediate();
3053
+ const auth = container.getProvider('auth-internal');
3054
+ const appCheckProvider = container.getProvider('app-check-internal');
3055
+ return new AIService(app, backend, auth, appCheckProvider);
3056
+ }
2687
3057
  function registerAI() {
2688
- app._registerComponent(new component.Component(AI_TYPE, (container, { instanceIdentifier }) => {
2689
- if (!instanceIdentifier) {
2690
- throw new AIError(AIErrorCode.ERROR, 'AIService instance identifier is undefined.');
2691
- }
2692
- const backend = decodeInstanceIdentifier(instanceIdentifier);
2693
- // getImmediate for FirebaseApp will always succeed
2694
- const app = container.getProvider('app').getImmediate();
2695
- const auth = container.getProvider('auth-internal');
2696
- const appCheckProvider = container.getProvider('app-check-internal');
2697
- return new AIService(app, backend, auth, appCheckProvider);
2698
- }, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
3058
+ app._registerComponent(new component.Component(AI_TYPE, factory, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
2699
3059
  app.registerVersion(name, version);
2700
3060
  // BUILD_TARGET will be replaced by values like esm, cjs, etc during the compilation
2701
3061
  app.registerVersion(name, version, 'cjs2020');
@@ -2726,6 +3086,7 @@ exports.ImagenImageFormat = ImagenImageFormat;
2726
3086
  exports.ImagenModel = ImagenModel;
2727
3087
  exports.ImagenPersonFilterLevel = ImagenPersonFilterLevel;
2728
3088
  exports.ImagenSafetyFilterLevel = ImagenSafetyFilterLevel;
3089
+ exports.InferenceMode = InferenceMode;
2729
3090
  exports.IntegerSchema = IntegerSchema;
2730
3091
  exports.Modality = Modality;
2731
3092
  exports.NumberSchema = NumberSchema;
@@ -2736,6 +3097,7 @@ exports.Schema = Schema;
2736
3097
  exports.SchemaType = SchemaType;
2737
3098
  exports.StringSchema = StringSchema;
2738
3099
  exports.VertexAIBackend = VertexAIBackend;
3100
+ exports.factory = factory;
2739
3101
  exports.getAI = getAI;
2740
3102
  exports.getGenerativeModel = getGenerativeModel;
2741
3103
  exports.getImagenModel = getImagenModel;