@firebase/ai 2.1.0-canary.c5f08a9bc → 2.1.0-canary.cbef6c6e5

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.
@@ -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.1.0-canary.c5f08a9bc";
11
+ var version = "2.1.0-canary.cbef6c6e5";
12
12
 
13
13
  /**
14
14
  * @license
@@ -640,9 +640,10 @@ class VertexAIBackend extends Backend {
640
640
  * limitations under the License.
641
641
  */
642
642
  class AIService {
643
- constructor(app, backend, authProvider, appCheckProvider) {
643
+ constructor(app, backend, authProvider, appCheckProvider, chromeAdapterFactory) {
644
644
  this.app = app;
645
645
  this.backend = backend;
646
+ this.chromeAdapterFactory = chromeAdapterFactory;
646
647
  const appCheck = appCheckProvider?.getImmediate({ optional: true });
647
648
  const auth = authProvider?.getImmediate({ optional: true });
648
649
  this.auth = auth || null;
@@ -2351,292 +2352,6 @@ class ImagenModel extends AIModel {
2351
2352
  }
2352
2353
  }
2353
2354
 
2354
- /**
2355
- * @internal
2356
- */
2357
- var Availability;
2358
- (function (Availability) {
2359
- Availability["UNAVAILABLE"] = "unavailable";
2360
- Availability["DOWNLOADABLE"] = "downloadable";
2361
- Availability["DOWNLOADING"] = "downloading";
2362
- Availability["AVAILABLE"] = "available";
2363
- })(Availability || (Availability = {}));
2364
-
2365
- /**
2366
- * @license
2367
- * Copyright 2025 Google LLC
2368
- *
2369
- * Licensed under the Apache License, Version 2.0 (the "License");
2370
- * you may not use this file except in compliance with the License.
2371
- * You may obtain a copy of the License at
2372
- *
2373
- * http://www.apache.org/licenses/LICENSE-2.0
2374
- *
2375
- * Unless required by applicable law or agreed to in writing, software
2376
- * distributed under the License is distributed on an "AS IS" BASIS,
2377
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2378
- * See the License for the specific language governing permissions and
2379
- * limitations under the License.
2380
- */
2381
- /**
2382
- * Defines an inference "backend" that uses Chrome's on-device model,
2383
- * and encapsulates logic for detecting when on-device inference is
2384
- * possible.
2385
- */
2386
- class ChromeAdapterImpl {
2387
- constructor(languageModelProvider, mode, onDeviceParams = {
2388
- createOptions: {
2389
- // Defaults to support image inputs for convenience.
2390
- expectedInputs: [{ type: 'image' }]
2391
- }
2392
- }) {
2393
- this.languageModelProvider = languageModelProvider;
2394
- this.mode = mode;
2395
- this.onDeviceParams = onDeviceParams;
2396
- this.isDownloading = false;
2397
- }
2398
- /**
2399
- * Checks if a given request can be made on-device.
2400
- *
2401
- * Encapsulates a few concerns:
2402
- * the mode
2403
- * API existence
2404
- * prompt formatting
2405
- * model availability, including triggering download if necessary
2406
- *
2407
- *
2408
- * Pros: callers needn't be concerned with details of on-device availability.</p>
2409
- * Cons: this method spans a few concerns and splits request validation from usage.
2410
- * If instance variables weren't already part of the API, we could consider a better
2411
- * separation of concerns.
2412
- */
2413
- async isAvailable(request) {
2414
- if (!this.mode) {
2415
- logger.debug(`On-device inference unavailable because mode is undefined.`);
2416
- return false;
2417
- }
2418
- if (this.mode === InferenceMode.ONLY_IN_CLOUD) {
2419
- logger.debug(`On-device inference unavailable because mode is "only_in_cloud".`);
2420
- return false;
2421
- }
2422
- // Triggers out-of-band download so model will eventually become available.
2423
- const availability = await this.downloadIfAvailable();
2424
- if (this.mode === InferenceMode.ONLY_ON_DEVICE) {
2425
- // If it will never be available due to API inavailability, throw.
2426
- if (availability === Availability.UNAVAILABLE) {
2427
- throw new AIError(AIErrorCode.API_NOT_ENABLED, 'Local LanguageModel API not available in this environment.');
2428
- }
2429
- else if (availability === Availability.DOWNLOADABLE ||
2430
- availability === Availability.DOWNLOADING) {
2431
- // TODO(chholland): Better user experience during download - progress?
2432
- logger.debug(`Waiting for download of LanguageModel to complete.`);
2433
- await this.downloadPromise;
2434
- return true;
2435
- }
2436
- return true;
2437
- }
2438
- // Applies prefer_on_device logic.
2439
- if (availability !== Availability.AVAILABLE) {
2440
- logger.debug(`On-device inference unavailable because availability is "${availability}".`);
2441
- return false;
2442
- }
2443
- if (!ChromeAdapterImpl.isOnDeviceRequest(request)) {
2444
- logger.debug(`On-device inference unavailable because request is incompatible.`);
2445
- return false;
2446
- }
2447
- return true;
2448
- }
2449
- /**
2450
- * Generates content on device.
2451
- *
2452
- * @remarks
2453
- * This is comparable to {@link GenerativeModel.generateContent} for generating content in
2454
- * Cloud.
2455
- * @param request - a standard Firebase AI {@link GenerateContentRequest}
2456
- * @returns {@link Response}, so we can reuse common response formatting.
2457
- */
2458
- async generateContent(request) {
2459
- const session = await this.createSession();
2460
- const contents = await Promise.all(request.contents.map(ChromeAdapterImpl.toLanguageModelMessage));
2461
- const text = await session.prompt(contents, this.onDeviceParams.promptOptions);
2462
- return ChromeAdapterImpl.toResponse(text);
2463
- }
2464
- /**
2465
- * Generates content stream on device.
2466
- *
2467
- * @remarks
2468
- * This is comparable to {@link GenerativeModel.generateContentStream} for generating content in
2469
- * Cloud.
2470
- * @param request - a standard Firebase AI {@link GenerateContentRequest}
2471
- * @returns {@link Response}, so we can reuse common response formatting.
2472
- */
2473
- async generateContentStream(request) {
2474
- const session = await this.createSession();
2475
- const contents = await Promise.all(request.contents.map(ChromeAdapterImpl.toLanguageModelMessage));
2476
- const stream = session.promptStreaming(contents, this.onDeviceParams.promptOptions);
2477
- return ChromeAdapterImpl.toStreamResponse(stream);
2478
- }
2479
- async countTokens(_request) {
2480
- throw new AIError(AIErrorCode.REQUEST_ERROR, 'Count Tokens is not yet available for on-device model.');
2481
- }
2482
- /**
2483
- * Asserts inference for the given request can be performed by an on-device model.
2484
- */
2485
- static isOnDeviceRequest(request) {
2486
- // Returns false if the prompt is empty.
2487
- if (request.contents.length === 0) {
2488
- logger.debug('Empty prompt rejected for on-device inference.');
2489
- return false;
2490
- }
2491
- for (const content of request.contents) {
2492
- if (content.role === 'function') {
2493
- logger.debug(`"Function" role rejected for on-device inference.`);
2494
- return false;
2495
- }
2496
- // Returns false if request contains an image with an unsupported mime type.
2497
- for (const part of content.parts) {
2498
- if (part.inlineData &&
2499
- ChromeAdapterImpl.SUPPORTED_MIME_TYPES.indexOf(part.inlineData.mimeType) === -1) {
2500
- logger.debug(`Unsupported mime type "${part.inlineData.mimeType}" rejected for on-device inference.`);
2501
- return false;
2502
- }
2503
- }
2504
- }
2505
- return true;
2506
- }
2507
- /**
2508
- * Encapsulates logic to get availability and download a model if one is downloadable.
2509
- */
2510
- async downloadIfAvailable() {
2511
- const availability = await this.languageModelProvider?.availability(this.onDeviceParams.createOptions);
2512
- if (availability === Availability.DOWNLOADABLE) {
2513
- this.download();
2514
- }
2515
- return availability;
2516
- }
2517
- /**
2518
- * Triggers out-of-band download of an on-device model.
2519
- *
2520
- * Chrome only downloads models as needed. Chrome knows a model is needed when code calls
2521
- * LanguageModel.create.
2522
- *
2523
- * Since Chrome manages the download, the SDK can only avoid redundant download requests by
2524
- * tracking if a download has previously been requested.
2525
- */
2526
- download() {
2527
- if (this.isDownloading) {
2528
- return;
2529
- }
2530
- this.isDownloading = true;
2531
- this.downloadPromise = this.languageModelProvider
2532
- ?.create(this.onDeviceParams.createOptions)
2533
- .finally(() => {
2534
- this.isDownloading = false;
2535
- });
2536
- }
2537
- /**
2538
- * Converts Firebase AI {@link Content} object to a Chrome {@link LanguageModelMessage} object.
2539
- */
2540
- static async toLanguageModelMessage(content) {
2541
- const languageModelMessageContents = await Promise.all(content.parts.map(ChromeAdapterImpl.toLanguageModelMessageContent));
2542
- return {
2543
- role: ChromeAdapterImpl.toLanguageModelMessageRole(content.role),
2544
- content: languageModelMessageContents
2545
- };
2546
- }
2547
- /**
2548
- * Converts a Firebase AI Part object to a Chrome LanguageModelMessageContent object.
2549
- */
2550
- static async toLanguageModelMessageContent(part) {
2551
- if (part.text) {
2552
- return {
2553
- type: 'text',
2554
- value: part.text
2555
- };
2556
- }
2557
- else if (part.inlineData) {
2558
- const formattedImageContent = await fetch(`data:${part.inlineData.mimeType};base64,${part.inlineData.data}`);
2559
- const imageBlob = await formattedImageContent.blob();
2560
- const imageBitmap = await createImageBitmap(imageBlob);
2561
- return {
2562
- type: 'image',
2563
- value: imageBitmap
2564
- };
2565
- }
2566
- throw new AIError(AIErrorCode.REQUEST_ERROR, `Processing of this Part type is not currently supported.`);
2567
- }
2568
- /**
2569
- * Converts a Firebase AI {@link Role} string to a {@link LanguageModelMessageRole} string.
2570
- */
2571
- static toLanguageModelMessageRole(role) {
2572
- // Assumes 'function' rule has been filtered by isOnDeviceRequest
2573
- return role === 'model' ? 'assistant' : 'user';
2574
- }
2575
- /**
2576
- * Abstracts Chrome session creation.
2577
- *
2578
- * Chrome uses a multi-turn session for all inference. Firebase AI uses single-turn for all
2579
- * inference. To map the Firebase AI API to Chrome's API, the SDK creates a new session for all
2580
- * inference.
2581
- *
2582
- * Chrome will remove a model from memory if it's no longer in use, so this method ensures a
2583
- * new session is created before an old session is destroyed.
2584
- */
2585
- async createSession() {
2586
- if (!this.languageModelProvider) {
2587
- throw new AIError(AIErrorCode.UNSUPPORTED, 'Chrome AI requested for unsupported browser version.');
2588
- }
2589
- const newSession = await this.languageModelProvider.create(this.onDeviceParams.createOptions);
2590
- if (this.oldSession) {
2591
- this.oldSession.destroy();
2592
- }
2593
- // Holds session reference, so model isn't unloaded from memory.
2594
- this.oldSession = newSession;
2595
- return newSession;
2596
- }
2597
- /**
2598
- * Formats string returned by Chrome as a {@link Response} returned by Firebase AI.
2599
- */
2600
- static toResponse(text) {
2601
- return {
2602
- json: async () => ({
2603
- candidates: [
2604
- {
2605
- content: {
2606
- parts: [{ text }]
2607
- }
2608
- }
2609
- ]
2610
- })
2611
- };
2612
- }
2613
- /**
2614
- * Formats string stream returned by Chrome as SSE returned by Firebase AI.
2615
- */
2616
- static toStreamResponse(stream) {
2617
- const encoder = new TextEncoder();
2618
- return {
2619
- body: stream.pipeThrough(new TransformStream({
2620
- transform(chunk, controller) {
2621
- const json = JSON.stringify({
2622
- candidates: [
2623
- {
2624
- content: {
2625
- role: 'model',
2626
- parts: [{ text: chunk }]
2627
- }
2628
- }
2629
- ]
2630
- });
2631
- controller.enqueue(encoder.encode(`data: ${json}\n\n`));
2632
- }
2633
- }))
2634
- };
2635
- }
2636
- }
2637
- // Visible for testing
2638
- ChromeAdapterImpl.SUPPORTED_MIME_TYPES = ['image/jpeg', 'image/png'];
2639
-
2640
2355
  /**
2641
2356
  * @license
2642
2357
  * Copyright 2024 Google LLC
@@ -3024,11 +2739,11 @@ function getGenerativeModel(ai, modelParams, requestOptions) {
3024
2739
  if (!inCloudParams.model) {
3025
2740
  throw new AIError(AIErrorCode.NO_MODEL, `Must provide a model name. Example: getGenerativeModel({ model: 'my-model-name' })`);
3026
2741
  }
3027
- let chromeAdapter;
3028
- // Do not initialize a ChromeAdapter if we are not in hybrid mode.
3029
- if (typeof window !== 'undefined' && hybridParams.mode) {
3030
- chromeAdapter = new ChromeAdapterImpl(window.LanguageModel, hybridParams.mode, hybridParams.onDeviceParams);
3031
- }
2742
+ /**
2743
+ * An AIService registered by index.node.ts will not have a
2744
+ * chromeAdapterFactory() method.
2745
+ */
2746
+ const chromeAdapter = ai.chromeAdapterFactory?.(hybridParams.mode, typeof window === 'undefined' ? undefined : window, hybridParams.onDeviceParams);
3032
2747
  return new GenerativeModel(ai, inCloudParams, requestOptions, chromeAdapter);
3033
2748
  }
3034
2749
  /**