@firebase/ai 2.12.0 → 2.13.0-20260526192810

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import { FirebaseError, Deferred, getModularInstance } from '@firebase/util';
4
4
  import { Logger } from '@firebase/logger';
5
5
 
6
6
  var name = "@firebase/ai";
7
- var version = "2.12.0";
7
+ var version = "2.13.0-20260526192810";
8
8
 
9
9
  /**
10
10
  * @license
@@ -443,7 +443,7 @@ const ResponseModality = {
443
443
  * cloud-hosted model. If not available, the SDK will fall back to an
444
444
  * on-device model.
445
445
  *
446
- * @beta
446
+ * @public
447
447
  */
448
448
  const InferenceMode = {
449
449
  'PREFER_ON_DEVICE': 'prefer_on_device',
@@ -454,7 +454,7 @@ const InferenceMode = {
454
454
  /**
455
455
  * Indicates whether inference happened on-device or in-cloud.
456
456
  *
457
- * @beta
457
+ * @public
458
458
  */
459
459
  const InferenceSource = {
460
460
  'ON_DEVICE': 'on_device',
@@ -1023,8 +1023,18 @@ var Availability;
1023
1023
  * See the License for the specific language governing permissions and
1024
1024
  * limitations under the License.
1025
1025
  */
1026
- // Defaults to support image inputs for convenience.
1027
- const defaultExpectedInputs = [{ type: 'image' }];
1026
+ // Defaults to English text. This can be overriden by user config.
1027
+ const defaultExpectedText = {
1028
+ type: 'text',
1029
+ languages: ['en']
1030
+ };
1031
+ const defaultExpectedInputs = [
1032
+ defaultExpectedText,
1033
+ { type: 'image' }
1034
+ ];
1035
+ const defaultExpectedOutputs = [
1036
+ defaultExpectedText
1037
+ ];
1028
1038
  /**
1029
1039
  * Defines an inference "backend" that uses Chrome's on-device model,
1030
1040
  * and encapsulates logic for detecting when on-device inference is
@@ -1034,22 +1044,31 @@ class ChromeAdapterImpl {
1034
1044
  constructor(languageModelProvider, mode, onDeviceParams) {
1035
1045
  this.languageModelProvider = languageModelProvider;
1036
1046
  this.mode = mode;
1037
- this.isDownloading = false;
1047
+ // Promise for on-device model download completion
1048
+ this.downloadPromise = null;
1038
1049
  this.onDeviceParams = {
1039
1050
  createOptions: {
1040
- expectedInputs: defaultExpectedInputs
1051
+ expectedInputs: defaultExpectedInputs,
1052
+ expectedOutputs: defaultExpectedOutputs
1041
1053
  }
1042
1054
  };
1043
1055
  if (onDeviceParams) {
1044
1056
  this.onDeviceParams = onDeviceParams;
1045
1057
  if (!this.onDeviceParams.createOptions) {
1046
1058
  this.onDeviceParams.createOptions = {
1047
- expectedInputs: defaultExpectedInputs
1059
+ expectedInputs: defaultExpectedInputs,
1060
+ expectedOutputs: defaultExpectedOutputs
1048
1061
  };
1049
1062
  }
1050
- else if (!this.onDeviceParams.createOptions.expectedInputs) {
1051
- this.onDeviceParams.createOptions.expectedInputs =
1052
- defaultExpectedInputs;
1063
+ else {
1064
+ if (!this.onDeviceParams.createOptions.expectedInputs) {
1065
+ this.onDeviceParams.createOptions.expectedInputs =
1066
+ defaultExpectedInputs;
1067
+ }
1068
+ if (!this.onDeviceParams.createOptions.expectedOutputs) {
1069
+ this.onDeviceParams.createOptions.expectedOutputs =
1070
+ defaultExpectedOutputs;
1071
+ }
1053
1072
  }
1054
1073
  }
1055
1074
  }
@@ -1060,7 +1079,7 @@ class ChromeAdapterImpl {
1060
1079
  * the mode
1061
1080
  * API existence
1062
1081
  * prompt formatting
1063
- * model availability, including triggering download if necessary
1082
+ * model availability
1064
1083
  *
1065
1084
  *
1066
1085
  * Pros: callers needn't be concerned with details of on-device availability.</p>
@@ -1077,8 +1096,7 @@ class ChromeAdapterImpl {
1077
1096
  logger.debug(`On-device inference unavailable because mode is "only_in_cloud".`);
1078
1097
  return false;
1079
1098
  }
1080
- // Triggers out-of-band download so model will eventually become available.
1081
- const availability = await this.downloadIfAvailable();
1099
+ const availability = await this.languageModelProvider?.availability(this.onDeviceParams.createOptions);
1082
1100
  if (this.mode === InferenceMode.ONLY_ON_DEVICE) {
1083
1101
  // If it will never be available due to API inavailability, throw.
1084
1102
  if (availability === Availability.UNAVAILABLE) {
@@ -1086,9 +1104,13 @@ class ChromeAdapterImpl {
1086
1104
  }
1087
1105
  else if (availability === Availability.DOWNLOADABLE ||
1088
1106
  availability === Availability.DOWNLOADING) {
1089
- // TODO(chholland): Better user experience during download - progress?
1090
1107
  logger.debug(`Waiting for download of LanguageModel to complete.`);
1091
- await this.downloadPromise;
1108
+ try {
1109
+ await this.downloadPromise;
1110
+ }
1111
+ catch (e) {
1112
+ throw new AIError(AIErrorCode.ERROR, e.message);
1113
+ }
1092
1114
  return true;
1093
1115
  }
1094
1116
  return true;
@@ -1165,31 +1187,44 @@ class ChromeAdapterImpl {
1165
1187
  /**
1166
1188
  * Encapsulates logic to get availability and download a model if one is downloadable.
1167
1189
  */
1168
- async downloadIfAvailable() {
1190
+ async downloadIfAvailable(onDownloadProgress) {
1169
1191
  const availability = await this.languageModelProvider?.availability(this.onDeviceParams.createOptions);
1170
- if (availability === Availability.DOWNLOADABLE) {
1171
- this.download();
1192
+ /**
1193
+ * Download may have been automatically started by Chrome or other
1194
+ * code, in which case we should still call create() to get a promise
1195
+ * for when the download completes.
1196
+ */
1197
+ if (availability === Availability.DOWNLOADABLE ||
1198
+ availability === Availability.DOWNLOADING) {
1199
+ this.download(onDownloadProgress);
1172
1200
  }
1173
1201
  return availability;
1174
1202
  }
1175
1203
  /**
1176
1204
  * Triggers out-of-band download of an on-device model.
1177
1205
  *
1178
- * Chrome only downloads models as needed. Chrome knows a model is needed when code calls
1179
- * LanguageModel.create.
1206
+ * Chrome may automatically begin a download on startup or
1207
+ * it may trigger a download when code calls LanguageModel.create.
1180
1208
  *
1181
1209
  * Since Chrome manages the download, the SDK can only avoid redundant download requests by
1182
1210
  * tracking if a download has previously been requested.
1183
1211
  */
1184
- download() {
1185
- if (this.isDownloading) {
1212
+ download(onDownloadProgress) {
1213
+ if (this.downloadPromise) {
1186
1214
  return;
1187
1215
  }
1188
- this.isDownloading = true;
1216
+ const options = { ...this.onDeviceParams.createOptions };
1217
+ if (options && !options.monitor && onDownloadProgress) {
1218
+ options.monitor = m => {
1219
+ m.addEventListener('downloadprogress', e => {
1220
+ onDownloadProgress(e.loaded);
1221
+ });
1222
+ };
1223
+ }
1189
1224
  this.downloadPromise = this.languageModelProvider
1190
- ?.create(this.onDeviceParams.createOptions)
1225
+ ?.create(options)
1191
1226
  .finally(() => {
1192
- this.isDownloading = false;
1227
+ this.downloadPromise = null;
1193
1228
  });
1194
1229
  }
1195
1230
  /**
@@ -1244,6 +1279,13 @@ class ChromeAdapterImpl {
1244
1279
  if (!this.languageModelProvider) {
1245
1280
  throw new AIError(AIErrorCode.UNSUPPORTED, 'Chrome AI requested for unsupported browser version.');
1246
1281
  }
1282
+ /**
1283
+ * Note: `create()` will trigger a download as a side effect if
1284
+ * a model is not downloaded or downloading, but this specific
1285
+ * create() call should not be relied on for the download. The download
1286
+ * should be triggered explicitly by the user in
1287
+ * GenerativeModel.initializeDeviceModel().
1288
+ */
1247
1289
  const newSession = await this.languageModelProvider.create(this.onDeviceParams.createOptions);
1248
1290
  if (this.oldSession) {
1249
1291
  this.oldSession.destroy();
@@ -2435,7 +2477,9 @@ async function callCloudOrDevice(request, chromeAdapter, onDeviceCall, inCloudCa
2435
2477
  };
2436
2478
  }
2437
2479
  catch (e) {
2438
- if (e instanceof AIError && errorsCausingFallback.includes(e.code)) {
2480
+ if (e instanceof AIError &&
2481
+ errorsCausingFallback.includes(e.code) &&
2482
+ (await chromeAdapter.isAvailable(request))) {
2439
2483
  return {
2440
2484
  response: await onDeviceCall(),
2441
2485
  inferenceSource: InferenceSource.ON_DEVICE
@@ -2762,7 +2806,9 @@ class ChatSessionBase {
2762
2806
  else {
2763
2807
  formattedContent = formatNewContent(request);
2764
2808
  }
2765
- const formattedRequest = this._formatRequest(formattedContent, tempHistory);
2809
+ const formattedRequest = this._formatRequest(formattedContent, [
2810
+ ...tempHistory
2811
+ ]);
2766
2812
  tempHistory.push(formattedContent);
2767
2813
  const result = await this._callGenerateContent(formattedRequest, singleRequestOptions);
2768
2814
  if (result) {
@@ -2832,8 +2878,10 @@ class ChatSessionBase {
2832
2878
  else {
2833
2879
  formattedContent = formatNewContent(request);
2834
2880
  }
2881
+ const formattedRequest = this._formatRequest(formattedContent, [
2882
+ ...tempHistory
2883
+ ]);
2835
2884
  tempHistory.push(formattedContent);
2836
- const formattedRequest = this._formatRequest(formattedContent, tempHistory);
2837
2885
  result = await this._callGenerateContentStream(formattedRequest, singleRequestOptions);
2838
2886
  functionCalls = this._getCallableFunctionCalls(result.firstValue);
2839
2887
  if (functionCalls &&
@@ -3221,6 +3269,52 @@ class GenerativeModel extends AIModel {
3221
3269
  this.systemInstruction = formatSystemInstruction(modelParams.systemInstruction);
3222
3270
  this.requestOptions = requestOptions || {};
3223
3271
  }
3272
+ /**
3273
+ * Initializes on-device models.
3274
+ *
3275
+ * @remarks
3276
+ * This may trigger a download on first
3277
+ * use. Wait for this promise to complete before calling inference
3278
+ * methods if you want to ensure the device models are ready before
3279
+ * any calls. Calling inference methods before the device is ready
3280
+ * will result in a cloud fallback if `inferenceMode` is set to
3281
+ * `PREFER_ON_DEVICE`, and an error if set to `ONLY_ON_DEVICE`.
3282
+ *
3283
+ * IMPORTANT: This call must be made on or after a user has interacted
3284
+ * with the page (for example, through a button click or key press).
3285
+ * If it is called without a user interaction, and it requires a download,
3286
+ * this will cause an error.
3287
+ *
3288
+ * See the
3289
+ * {@link https://developer.chrome.com/docs/ai/prompt-api#use_the_prompt_api | Prompt API docs }
3290
+ * for more details on this requirement.
3291
+ *
3292
+ * @param onDownloadProgress A callback called repeatedly as the
3293
+ * download progresses that provides a `progressValue` between 0
3294
+ * and 1 representing how much of the download is complete. This
3295
+ * will be ignored if `monitor` was populated in
3296
+ * {@link LanguageModelCreateOptions}.
3297
+ *
3298
+ * @public
3299
+ */
3300
+ async initializeDeviceModel(onDownloadProgress) {
3301
+ if (!this.chromeAdapter ||
3302
+ this.chromeAdapter.mode === InferenceMode.ONLY_IN_CLOUD) {
3303
+ return;
3304
+ }
3305
+ const availability = await this.chromeAdapter.downloadIfAvailable(onDownloadProgress);
3306
+ if (availability === Availability.UNAVAILABLE) {
3307
+ const notEnabledError = new AIError(AIErrorCode.API_NOT_ENABLED, 'Local LanguageModel API not available in this environment.');
3308
+ if (this.chromeAdapter.mode === InferenceMode.ONLY_ON_DEVICE) {
3309
+ throw notEnabledError;
3310
+ }
3311
+ else {
3312
+ // No reason to throw if not in ONLY_ON_DEVICE mode.
3313
+ logger.debug(notEnabledError.message);
3314
+ }
3315
+ }
3316
+ await this.chromeAdapter.downloadPromise;
3317
+ }
3224
3318
  /**
3225
3319
  * Makes a single non-streaming call to the model
3226
3320
  * and returns an object containing a single {@link GenerateContentResponse}.