@firebase/ai 2.4.0 → 2.5.0-canary.0800a8bed
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.
- package/dist/ai-public.d.ts +134 -9
- package/dist/ai.d.ts +137 -9
- package/dist/esm/index.esm.js +202 -68
- package/dist/esm/index.esm.js.map +1 -1
- package/dist/esm/src/factory-node.d.ts +19 -0
- package/dist/esm/src/methods/chrome-adapter.d.ts +1 -1
- package/dist/esm/src/methods/live-session.d.ts +64 -9
- package/dist/esm/src/requests/hybrid-helpers.d.ts +7 -2
- package/dist/esm/src/requests/response-helpers.d.ts +2 -2
- package/dist/esm/src/requests/stream-reader.d.ts +2 -1
- package/dist/esm/src/service.d.ts +3 -4
- package/dist/esm/src/types/chrome-adapter.d.ts +5 -0
- package/dist/esm/src/types/enums.d.ts +15 -0
- package/dist/esm/src/types/live-responses.d.ts +21 -3
- package/dist/esm/src/types/requests.d.ts +23 -0
- package/dist/esm/src/types/responses.d.ts +28 -1
- package/dist/index.cjs.js +202 -67
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.node.cjs.js +306 -166
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +306 -167
- package/dist/index.node.mjs.map +1 -1
- package/dist/src/factory-node.d.ts +19 -0
- package/dist/src/methods/chrome-adapter.d.ts +1 -1
- package/dist/src/methods/live-session.d.ts +64 -9
- package/dist/src/requests/hybrid-helpers.d.ts +7 -2
- package/dist/src/requests/response-helpers.d.ts +2 -2
- package/dist/src/requests/stream-reader.d.ts +2 -1
- package/dist/src/service.d.ts +3 -4
- package/dist/src/types/chrome-adapter.d.ts +5 -0
- package/dist/src/types/enums.d.ts +15 -0
- package/dist/src/types/live-responses.d.ts +21 -3
- package/dist/src/types/requests.d.ts +23 -0
- package/dist/src/types/responses.d.ts +28 -1
- package/package.json +8 -8
package/dist/index.node.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.
|
|
11
|
+
var version = "2.5.0-canary.0800a8bed";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @license
|
|
@@ -38,6 +38,62 @@ const DEFAULT_FETCH_TIMEOUT_MS = 180 * 1000;
|
|
|
38
38
|
*/
|
|
39
39
|
const DEFAULT_HYBRID_IN_CLOUD_MODEL = 'gemini-2.0-flash-lite';
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* @license
|
|
43
|
+
* Copyright 2024 Google LLC
|
|
44
|
+
*
|
|
45
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
46
|
+
* you may not use this file except in compliance with the License.
|
|
47
|
+
* You may obtain a copy of the License at
|
|
48
|
+
*
|
|
49
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
50
|
+
*
|
|
51
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
52
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
53
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
54
|
+
* See the License for the specific language governing permissions and
|
|
55
|
+
* limitations under the License.
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Error class for the Firebase AI SDK.
|
|
59
|
+
*
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
class AIError extends util.FirebaseError {
|
|
63
|
+
/**
|
|
64
|
+
* Constructs a new instance of the `AIError` class.
|
|
65
|
+
*
|
|
66
|
+
* @param code - The error code from {@link (AIErrorCode:type)}.
|
|
67
|
+
* @param message - A human-readable message describing the error.
|
|
68
|
+
* @param customErrorData - Optional error data.
|
|
69
|
+
*/
|
|
70
|
+
constructor(code, message, customErrorData) {
|
|
71
|
+
// Match error format used by FirebaseError from ErrorFactory
|
|
72
|
+
const service = AI_TYPE;
|
|
73
|
+
const fullCode = `${service}/${code}`;
|
|
74
|
+
const fullMessage = `${service}: ${message} (${fullCode})`;
|
|
75
|
+
super(code, fullMessage);
|
|
76
|
+
this.code = code;
|
|
77
|
+
this.customErrorData = customErrorData;
|
|
78
|
+
// FirebaseError initializes a stack trace, but it assumes the error is created from the error
|
|
79
|
+
// factory. Since we break this assumption, we set the stack trace to be originating from this
|
|
80
|
+
// constructor.
|
|
81
|
+
// This is only supported in V8.
|
|
82
|
+
if (Error.captureStackTrace) {
|
|
83
|
+
// Allows us to initialize the stack trace without including the constructor itself at the
|
|
84
|
+
// top level of the stack trace.
|
|
85
|
+
Error.captureStackTrace(this, AIError);
|
|
86
|
+
}
|
|
87
|
+
// Allows instanceof AIError in ES5/ES6
|
|
88
|
+
// https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
|
89
|
+
// TODO(dlarocque): Replace this with `new.target`: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
|
|
90
|
+
// which we can now use since we no longer target ES5.
|
|
91
|
+
Object.setPrototypeOf(this, AIError.prototype);
|
|
92
|
+
// Since Error is an interface, we don't inherit toString and so we define it ourselves.
|
|
93
|
+
this.toString = () => fullMessage;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
41
97
|
/**
|
|
42
98
|
* @license
|
|
43
99
|
* Copyright 2024 Google LLC
|
|
@@ -327,6 +383,15 @@ const InferenceMode = {
|
|
|
327
383
|
'ONLY_IN_CLOUD': 'only_in_cloud',
|
|
328
384
|
'PREFER_IN_CLOUD': 'prefer_in_cloud'
|
|
329
385
|
};
|
|
386
|
+
/**
|
|
387
|
+
* Indicates whether inference happened on-device or in-cloud.
|
|
388
|
+
*
|
|
389
|
+
* @beta
|
|
390
|
+
*/
|
|
391
|
+
const InferenceSource = {
|
|
392
|
+
'ON_DEVICE': 'on_device',
|
|
393
|
+
'IN_CLOUD': 'in_cloud'
|
|
394
|
+
};
|
|
330
395
|
/**
|
|
331
396
|
* Represents the result of the code execution.
|
|
332
397
|
*
|
|
@@ -733,6 +798,64 @@ class VertexAIBackend extends Backend {
|
|
|
733
798
|
}
|
|
734
799
|
}
|
|
735
800
|
|
|
801
|
+
/**
|
|
802
|
+
* @license
|
|
803
|
+
* Copyright 2025 Google LLC
|
|
804
|
+
*
|
|
805
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
806
|
+
* you may not use this file except in compliance with the License.
|
|
807
|
+
* You may obtain a copy of the License at
|
|
808
|
+
*
|
|
809
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
810
|
+
*
|
|
811
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
812
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
813
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
814
|
+
* See the License for the specific language governing permissions and
|
|
815
|
+
* limitations under the License.
|
|
816
|
+
*/
|
|
817
|
+
/**
|
|
818
|
+
* Encodes a {@link Backend} into a string that will be used to uniquely identify {@link AI}
|
|
819
|
+
* instances by backend type.
|
|
820
|
+
*
|
|
821
|
+
* @internal
|
|
822
|
+
*/
|
|
823
|
+
function encodeInstanceIdentifier(backend) {
|
|
824
|
+
if (backend instanceof GoogleAIBackend) {
|
|
825
|
+
return `${AI_TYPE}/googleai`;
|
|
826
|
+
}
|
|
827
|
+
else if (backend instanceof VertexAIBackend) {
|
|
828
|
+
return `${AI_TYPE}/vertexai/${backend.location}`;
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
throw new AIError(AIErrorCode.ERROR, `Invalid backend: ${JSON.stringify(backend.backendType)}`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Decodes an instance identifier string into a {@link Backend}.
|
|
836
|
+
*
|
|
837
|
+
* @internal
|
|
838
|
+
*/
|
|
839
|
+
function decodeInstanceIdentifier(instanceIdentifier) {
|
|
840
|
+
const identifierParts = instanceIdentifier.split('/');
|
|
841
|
+
if (identifierParts[0] !== AI_TYPE) {
|
|
842
|
+
throw new AIError(AIErrorCode.ERROR, `Invalid instance identifier, unknown prefix '${identifierParts[0]}'`);
|
|
843
|
+
}
|
|
844
|
+
const backendType = identifierParts[1];
|
|
845
|
+
switch (backendType) {
|
|
846
|
+
case 'vertexai':
|
|
847
|
+
const location = identifierParts[2];
|
|
848
|
+
if (!location) {
|
|
849
|
+
throw new AIError(AIErrorCode.ERROR, `Invalid instance identifier, unknown location '${instanceIdentifier}'`);
|
|
850
|
+
}
|
|
851
|
+
return new VertexAIBackend(location);
|
|
852
|
+
case 'googleai':
|
|
853
|
+
return new GoogleAIBackend();
|
|
854
|
+
default:
|
|
855
|
+
throw new AIError(AIErrorCode.ERROR, `Invalid instance identifier string: '${instanceIdentifier}'`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
736
859
|
/**
|
|
737
860
|
* @license
|
|
738
861
|
* Copyright 2024 Google LLC
|
|
@@ -776,62 +899,6 @@ class AIService {
|
|
|
776
899
|
}
|
|
777
900
|
}
|
|
778
901
|
|
|
779
|
-
/**
|
|
780
|
-
* @license
|
|
781
|
-
* Copyright 2024 Google LLC
|
|
782
|
-
*
|
|
783
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
784
|
-
* you may not use this file except in compliance with the License.
|
|
785
|
-
* You may obtain a copy of the License at
|
|
786
|
-
*
|
|
787
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
788
|
-
*
|
|
789
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
790
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
791
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
792
|
-
* See the License for the specific language governing permissions and
|
|
793
|
-
* limitations under the License.
|
|
794
|
-
*/
|
|
795
|
-
/**
|
|
796
|
-
* Error class for the Firebase AI SDK.
|
|
797
|
-
*
|
|
798
|
-
* @public
|
|
799
|
-
*/
|
|
800
|
-
class AIError extends util.FirebaseError {
|
|
801
|
-
/**
|
|
802
|
-
* Constructs a new instance of the `AIError` class.
|
|
803
|
-
*
|
|
804
|
-
* @param code - The error code from {@link (AIErrorCode:type)}.
|
|
805
|
-
* @param message - A human-readable message describing the error.
|
|
806
|
-
* @param customErrorData - Optional error data.
|
|
807
|
-
*/
|
|
808
|
-
constructor(code, message, customErrorData) {
|
|
809
|
-
// Match error format used by FirebaseError from ErrorFactory
|
|
810
|
-
const service = AI_TYPE;
|
|
811
|
-
const fullCode = `${service}/${code}`;
|
|
812
|
-
const fullMessage = `${service}: ${message} (${fullCode})`;
|
|
813
|
-
super(code, fullMessage);
|
|
814
|
-
this.code = code;
|
|
815
|
-
this.customErrorData = customErrorData;
|
|
816
|
-
// FirebaseError initializes a stack trace, but it assumes the error is created from the error
|
|
817
|
-
// factory. Since we break this assumption, we set the stack trace to be originating from this
|
|
818
|
-
// constructor.
|
|
819
|
-
// This is only supported in V8.
|
|
820
|
-
if (Error.captureStackTrace) {
|
|
821
|
-
// Allows us to initialize the stack trace without including the constructor itself at the
|
|
822
|
-
// top level of the stack trace.
|
|
823
|
-
Error.captureStackTrace(this, AIError);
|
|
824
|
-
}
|
|
825
|
-
// Allows instanceof AIError in ES5/ES6
|
|
826
|
-
// https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
|
|
827
|
-
// TODO(dlarocque): Replace this with `new.target`: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#support-for-newtarget
|
|
828
|
-
// which we can now use since we no longer target ES5.
|
|
829
|
-
Object.setPrototypeOf(this, AIError.prototype);
|
|
830
|
-
// Since Error is an interface, we don't inherit toString and so we define it ourselves.
|
|
831
|
-
this.toString = () => fullMessage;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
902
|
/**
|
|
836
903
|
* @license
|
|
837
904
|
* Copyright 2025 Google LLC
|
|
@@ -848,46 +915,16 @@ class AIError extends util.FirebaseError {
|
|
|
848
915
|
* See the License for the specific language governing permissions and
|
|
849
916
|
* limitations under the License.
|
|
850
917
|
*/
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
*
|
|
855
|
-
* @internal
|
|
856
|
-
*/
|
|
857
|
-
function encodeInstanceIdentifier(backend) {
|
|
858
|
-
if (backend instanceof GoogleAIBackend) {
|
|
859
|
-
return `${AI_TYPE}/googleai`;
|
|
860
|
-
}
|
|
861
|
-
else if (backend instanceof VertexAIBackend) {
|
|
862
|
-
return `${AI_TYPE}/vertexai/${backend.location}`;
|
|
863
|
-
}
|
|
864
|
-
else {
|
|
865
|
-
throw new AIError(AIErrorCode.ERROR, `Invalid backend: ${JSON.stringify(backend.backendType)}`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* Decodes an instance identifier string into a {@link Backend}.
|
|
870
|
-
*
|
|
871
|
-
* @internal
|
|
872
|
-
*/
|
|
873
|
-
function decodeInstanceIdentifier(instanceIdentifier) {
|
|
874
|
-
const identifierParts = instanceIdentifier.split('/');
|
|
875
|
-
if (identifierParts[0] !== AI_TYPE) {
|
|
876
|
-
throw new AIError(AIErrorCode.ERROR, `Invalid instance identifier, unknown prefix '${identifierParts[0]}'`);
|
|
877
|
-
}
|
|
878
|
-
const backendType = identifierParts[1];
|
|
879
|
-
switch (backendType) {
|
|
880
|
-
case 'vertexai':
|
|
881
|
-
const location = identifierParts[2];
|
|
882
|
-
if (!location) {
|
|
883
|
-
throw new AIError(AIErrorCode.ERROR, `Invalid instance identifier, unknown location '${instanceIdentifier}'`);
|
|
884
|
-
}
|
|
885
|
-
return new VertexAIBackend(location);
|
|
886
|
-
case 'googleai':
|
|
887
|
-
return new GoogleAIBackend();
|
|
888
|
-
default:
|
|
889
|
-
throw new AIError(AIErrorCode.ERROR, `Invalid instance identifier string: '${instanceIdentifier}'`);
|
|
918
|
+
function factory(container, { instanceIdentifier }) {
|
|
919
|
+
if (!instanceIdentifier) {
|
|
920
|
+
throw new AIError(AIErrorCode.ERROR, 'AIService instance identifier is undefined.');
|
|
890
921
|
}
|
|
922
|
+
const backend = decodeInstanceIdentifier(instanceIdentifier);
|
|
923
|
+
// getImmediate for FirebaseApp will always succeed
|
|
924
|
+
const app = container.getProvider('app').getImmediate();
|
|
925
|
+
const auth = container.getProvider('auth-internal');
|
|
926
|
+
const appCheckProvider = container.getProvider('app-check-internal');
|
|
927
|
+
return new AIService(app, backend, auth, appCheckProvider);
|
|
891
928
|
}
|
|
892
929
|
|
|
893
930
|
/**
|
|
@@ -1273,7 +1310,7 @@ function hasValidCandidates(response) {
|
|
|
1273
1310
|
* Creates an EnhancedGenerateContentResponse object that has helper functions and
|
|
1274
1311
|
* other modifications that improve usability.
|
|
1275
1312
|
*/
|
|
1276
|
-
function createEnhancedContentResponse(response) {
|
|
1313
|
+
function createEnhancedContentResponse(response, inferenceSource = InferenceSource.IN_CLOUD) {
|
|
1277
1314
|
/**
|
|
1278
1315
|
* The Vertex AI backend omits default values.
|
|
1279
1316
|
* This causes the `index` property to be omitted from the first candidate in the
|
|
@@ -1284,6 +1321,7 @@ function createEnhancedContentResponse(response) {
|
|
|
1284
1321
|
response.candidates[0].index = 0;
|
|
1285
1322
|
}
|
|
1286
1323
|
const responseWithHelpers = addHelpers(response);
|
|
1324
|
+
responseWithHelpers.inferenceSource = inferenceSource;
|
|
1287
1325
|
return responseWithHelpers;
|
|
1288
1326
|
}
|
|
1289
1327
|
/**
|
|
@@ -1660,16 +1698,16 @@ const responseLineRE = /^data\: (.*)(?:\n\n|\r\r|\r\n\r\n)/;
|
|
|
1660
1698
|
*
|
|
1661
1699
|
* @param response - Response from a fetch call
|
|
1662
1700
|
*/
|
|
1663
|
-
function processStream(response, apiSettings) {
|
|
1701
|
+
function processStream(response, apiSettings, inferenceSource) {
|
|
1664
1702
|
const inputStream = response.body.pipeThrough(new TextDecoderStream('utf8', { fatal: true }));
|
|
1665
1703
|
const responseStream = getResponseStream(inputStream);
|
|
1666
1704
|
const [stream1, stream2] = responseStream.tee();
|
|
1667
1705
|
return {
|
|
1668
|
-
stream: generateResponseSequence(stream1, apiSettings),
|
|
1669
|
-
response: getResponsePromise(stream2, apiSettings)
|
|
1706
|
+
stream: generateResponseSequence(stream1, apiSettings, inferenceSource),
|
|
1707
|
+
response: getResponsePromise(stream2, apiSettings, inferenceSource)
|
|
1670
1708
|
};
|
|
1671
1709
|
}
|
|
1672
|
-
async function getResponsePromise(stream, apiSettings) {
|
|
1710
|
+
async function getResponsePromise(stream, apiSettings, inferenceSource) {
|
|
1673
1711
|
const allResponses = [];
|
|
1674
1712
|
const reader = stream.getReader();
|
|
1675
1713
|
while (true) {
|
|
@@ -1679,12 +1717,12 @@ async function getResponsePromise(stream, apiSettings) {
|
|
|
1679
1717
|
if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
|
|
1680
1718
|
generateContentResponse = mapGenerateContentResponse(generateContentResponse);
|
|
1681
1719
|
}
|
|
1682
|
-
return createEnhancedContentResponse(generateContentResponse);
|
|
1720
|
+
return createEnhancedContentResponse(generateContentResponse, inferenceSource);
|
|
1683
1721
|
}
|
|
1684
1722
|
allResponses.push(value);
|
|
1685
1723
|
}
|
|
1686
1724
|
}
|
|
1687
|
-
async function* generateResponseSequence(stream, apiSettings) {
|
|
1725
|
+
async function* generateResponseSequence(stream, apiSettings, inferenceSource) {
|
|
1688
1726
|
const reader = stream.getReader();
|
|
1689
1727
|
while (true) {
|
|
1690
1728
|
const { value, done } = await reader.read();
|
|
@@ -1693,10 +1731,10 @@ async function* generateResponseSequence(stream, apiSettings) {
|
|
|
1693
1731
|
}
|
|
1694
1732
|
let enhancedResponse;
|
|
1695
1733
|
if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
|
|
1696
|
-
enhancedResponse = createEnhancedContentResponse(mapGenerateContentResponse(value));
|
|
1734
|
+
enhancedResponse = createEnhancedContentResponse(mapGenerateContentResponse(value), inferenceSource);
|
|
1697
1735
|
}
|
|
1698
1736
|
else {
|
|
1699
|
-
enhancedResponse = createEnhancedContentResponse(value);
|
|
1737
|
+
enhancedResponse = createEnhancedContentResponse(value, inferenceSource);
|
|
1700
1738
|
}
|
|
1701
1739
|
const firstCandidate = enhancedResponse.candidates?.[0];
|
|
1702
1740
|
// Don't yield a response with no useful data for the developer.
|
|
@@ -1866,31 +1904,52 @@ const errorsCausingFallback = [
|
|
|
1866
1904
|
*/
|
|
1867
1905
|
async function callCloudOrDevice(request, chromeAdapter, onDeviceCall, inCloudCall) {
|
|
1868
1906
|
if (!chromeAdapter) {
|
|
1869
|
-
return
|
|
1907
|
+
return {
|
|
1908
|
+
response: await inCloudCall(),
|
|
1909
|
+
inferenceSource: InferenceSource.IN_CLOUD
|
|
1910
|
+
};
|
|
1870
1911
|
}
|
|
1871
1912
|
switch (chromeAdapter.mode) {
|
|
1872
1913
|
case InferenceMode.ONLY_ON_DEVICE:
|
|
1873
1914
|
if (await chromeAdapter.isAvailable(request)) {
|
|
1874
|
-
return
|
|
1915
|
+
return {
|
|
1916
|
+
response: await onDeviceCall(),
|
|
1917
|
+
inferenceSource: InferenceSource.ON_DEVICE
|
|
1918
|
+
};
|
|
1875
1919
|
}
|
|
1876
1920
|
throw new AIError(AIErrorCode.UNSUPPORTED, 'Inference mode is ONLY_ON_DEVICE, but an on-device model is not available.');
|
|
1877
1921
|
case InferenceMode.ONLY_IN_CLOUD:
|
|
1878
|
-
return
|
|
1922
|
+
return {
|
|
1923
|
+
response: await inCloudCall(),
|
|
1924
|
+
inferenceSource: InferenceSource.IN_CLOUD
|
|
1925
|
+
};
|
|
1879
1926
|
case InferenceMode.PREFER_IN_CLOUD:
|
|
1880
1927
|
try {
|
|
1881
|
-
return
|
|
1928
|
+
return {
|
|
1929
|
+
response: await inCloudCall(),
|
|
1930
|
+
inferenceSource: InferenceSource.IN_CLOUD
|
|
1931
|
+
};
|
|
1882
1932
|
}
|
|
1883
1933
|
catch (e) {
|
|
1884
1934
|
if (e instanceof AIError && errorsCausingFallback.includes(e.code)) {
|
|
1885
|
-
return
|
|
1935
|
+
return {
|
|
1936
|
+
response: await onDeviceCall(),
|
|
1937
|
+
inferenceSource: InferenceSource.ON_DEVICE
|
|
1938
|
+
};
|
|
1886
1939
|
}
|
|
1887
1940
|
throw e;
|
|
1888
1941
|
}
|
|
1889
1942
|
case InferenceMode.PREFER_ON_DEVICE:
|
|
1890
1943
|
if (await chromeAdapter.isAvailable(request)) {
|
|
1891
|
-
return
|
|
1944
|
+
return {
|
|
1945
|
+
response: await onDeviceCall(),
|
|
1946
|
+
inferenceSource: InferenceSource.ON_DEVICE
|
|
1947
|
+
};
|
|
1892
1948
|
}
|
|
1893
|
-
return
|
|
1949
|
+
return {
|
|
1950
|
+
response: await inCloudCall(),
|
|
1951
|
+
inferenceSource: InferenceSource.IN_CLOUD
|
|
1952
|
+
};
|
|
1894
1953
|
default:
|
|
1895
1954
|
throw new AIError(AIErrorCode.ERROR, `Unexpected infererence mode: ${chromeAdapter.mode}`);
|
|
1896
1955
|
}
|
|
@@ -1920,8 +1979,8 @@ async function generateContentStreamOnCloud(apiSettings, model, params, requestO
|
|
|
1920
1979
|
/* stream */ true, JSON.stringify(params), requestOptions);
|
|
1921
1980
|
}
|
|
1922
1981
|
async function generateContentStream(apiSettings, model, params, chromeAdapter, requestOptions) {
|
|
1923
|
-
const
|
|
1924
|
-
return processStream(response, apiSettings); // TODO: Map streaming responses
|
|
1982
|
+
const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContentStream(params), () => generateContentStreamOnCloud(apiSettings, model, params, requestOptions));
|
|
1983
|
+
return processStream(callResult.response, apiSettings); // TODO: Map streaming responses
|
|
1925
1984
|
}
|
|
1926
1985
|
async function generateContentOnCloud(apiSettings, model, params, requestOptions) {
|
|
1927
1986
|
if (apiSettings.backend.backendType === BackendType.GOOGLE_AI) {
|
|
@@ -1931,9 +1990,9 @@ async function generateContentOnCloud(apiSettings, model, params, requestOptions
|
|
|
1931
1990
|
/* stream */ false, JSON.stringify(params), requestOptions);
|
|
1932
1991
|
}
|
|
1933
1992
|
async function generateContent(apiSettings, model, params, chromeAdapter, requestOptions) {
|
|
1934
|
-
const
|
|
1935
|
-
const generateContentResponse = await processGenerateContentResponse(response, apiSettings);
|
|
1936
|
-
const enhancedResponse = createEnhancedContentResponse(generateContentResponse);
|
|
1993
|
+
const callResult = await callCloudOrDevice(params, chromeAdapter, () => chromeAdapter.generateContent(params), () => generateContentOnCloud(apiSettings, model, params, requestOptions));
|
|
1994
|
+
const generateContentResponse = await processGenerateContentResponse(callResult.response, apiSettings);
|
|
1995
|
+
const enhancedResponse = createEnhancedContentResponse(generateContentResponse, callResult.inferenceSource);
|
|
1937
1996
|
return {
|
|
1938
1997
|
response: enhancedResponse
|
|
1939
1998
|
};
|
|
@@ -2507,75 +2566,104 @@ class LiveSession {
|
|
|
2507
2566
|
this.webSocketHandler.send(JSON.stringify(message));
|
|
2508
2567
|
}
|
|
2509
2568
|
/**
|
|
2510
|
-
* Sends
|
|
2569
|
+
* Sends text to the server in realtime.
|
|
2511
2570
|
*
|
|
2512
|
-
* @
|
|
2571
|
+
* @example
|
|
2572
|
+
* ```javascript
|
|
2573
|
+
* liveSession.sendTextRealtime("Hello, how are you?");
|
|
2574
|
+
* ```
|
|
2575
|
+
*
|
|
2576
|
+
* @param text - The text data to send.
|
|
2513
2577
|
* @throws If this session has been closed.
|
|
2514
2578
|
*
|
|
2515
2579
|
* @beta
|
|
2516
2580
|
*/
|
|
2517
|
-
async
|
|
2581
|
+
async sendTextRealtime(text) {
|
|
2518
2582
|
if (this.isClosed) {
|
|
2519
2583
|
throw new AIError(AIErrorCode.REQUEST_ERROR, 'This LiveSession has been closed and cannot be used.');
|
|
2520
2584
|
}
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
this.webSocketHandler.send(JSON.stringify(message));
|
|
2528
|
-
});
|
|
2585
|
+
const message = {
|
|
2586
|
+
realtimeInput: {
|
|
2587
|
+
text
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
this.webSocketHandler.send(JSON.stringify(message));
|
|
2529
2591
|
}
|
|
2530
2592
|
/**
|
|
2531
|
-
* Sends
|
|
2593
|
+
* Sends audio data to the server in realtime.
|
|
2532
2594
|
*
|
|
2533
|
-
* @
|
|
2595
|
+
* @remarks The server requires that the audio data is base64-encoded 16-bit PCM at 16kHz
|
|
2596
|
+
* little-endian.
|
|
2597
|
+
*
|
|
2598
|
+
* @example
|
|
2599
|
+
* ```javascript
|
|
2600
|
+
* // const pcmData = ... base64-encoded 16-bit PCM at 16kHz little-endian.
|
|
2601
|
+
* const blob = { mimeType: "audio/pcm", data: pcmData };
|
|
2602
|
+
* liveSession.sendAudioRealtime(blob);
|
|
2603
|
+
* ```
|
|
2604
|
+
*
|
|
2605
|
+
* @param blob - The base64-encoded PCM data to send to the server in realtime.
|
|
2534
2606
|
* @throws If this session has been closed.
|
|
2535
2607
|
*
|
|
2536
2608
|
* @beta
|
|
2537
2609
|
*/
|
|
2538
|
-
async
|
|
2610
|
+
async sendAudioRealtime(blob) {
|
|
2539
2611
|
if (this.isClosed) {
|
|
2540
2612
|
throw new AIError(AIErrorCode.REQUEST_ERROR, 'This LiveSession has been closed and cannot be used.');
|
|
2541
2613
|
}
|
|
2542
2614
|
const message = {
|
|
2543
|
-
|
|
2544
|
-
|
|
2615
|
+
realtimeInput: {
|
|
2616
|
+
audio: blob
|
|
2545
2617
|
}
|
|
2546
2618
|
};
|
|
2547
2619
|
this.webSocketHandler.send(JSON.stringify(message));
|
|
2548
2620
|
}
|
|
2549
2621
|
/**
|
|
2550
|
-
* Sends
|
|
2622
|
+
* Sends video data to the server in realtime.
|
|
2551
2623
|
*
|
|
2552
|
-
* @
|
|
2624
|
+
* @remarks The server requires that the video is sent as individual video frames at 1 FPS. It
|
|
2625
|
+
* is recommended to set `mimeType` to `image/jpeg`.
|
|
2626
|
+
*
|
|
2627
|
+
* @example
|
|
2628
|
+
* ```javascript
|
|
2629
|
+
* // const videoFrame = ... base64-encoded JPEG data
|
|
2630
|
+
* const blob = { mimeType: "image/jpeg", data: videoFrame };
|
|
2631
|
+
* liveSession.sendVideoRealtime(blob);
|
|
2632
|
+
* ```
|
|
2633
|
+
* @param blob - The base64-encoded video data to send to the server in realtime.
|
|
2553
2634
|
* @throws If this session has been closed.
|
|
2554
2635
|
*
|
|
2555
2636
|
* @beta
|
|
2556
2637
|
*/
|
|
2557
|
-
async
|
|
2638
|
+
async sendVideoRealtime(blob) {
|
|
2558
2639
|
if (this.isClosed) {
|
|
2559
2640
|
throw new AIError(AIErrorCode.REQUEST_ERROR, 'This LiveSession has been closed and cannot be used.');
|
|
2560
2641
|
}
|
|
2561
|
-
const
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
const { done, value } = await reader.read();
|
|
2565
|
-
if (done) {
|
|
2566
|
-
break;
|
|
2567
|
-
}
|
|
2568
|
-
else if (!value) {
|
|
2569
|
-
throw new Error('Missing chunk in reader, but reader is not done.');
|
|
2570
|
-
}
|
|
2571
|
-
await this.sendMediaChunks([value]);
|
|
2572
|
-
}
|
|
2573
|
-
catch (e) {
|
|
2574
|
-
// Re-throw any errors that occur during stream consumption or sending.
|
|
2575
|
-
const message = e instanceof Error ? e.message : 'Error processing media stream.';
|
|
2576
|
-
throw new AIError(AIErrorCode.REQUEST_ERROR, message);
|
|
2642
|
+
const message = {
|
|
2643
|
+
realtimeInput: {
|
|
2644
|
+
video: blob
|
|
2577
2645
|
}
|
|
2646
|
+
};
|
|
2647
|
+
this.webSocketHandler.send(JSON.stringify(message));
|
|
2648
|
+
}
|
|
2649
|
+
/**
|
|
2650
|
+
* Sends function responses to the server.
|
|
2651
|
+
*
|
|
2652
|
+
* @param functionResponses - The function responses to send.
|
|
2653
|
+
* @throws If this session has been closed.
|
|
2654
|
+
*
|
|
2655
|
+
* @beta
|
|
2656
|
+
*/
|
|
2657
|
+
async sendFunctionResponses(functionResponses) {
|
|
2658
|
+
if (this.isClosed) {
|
|
2659
|
+
throw new AIError(AIErrorCode.REQUEST_ERROR, 'This LiveSession has been closed and cannot be used.');
|
|
2578
2660
|
}
|
|
2661
|
+
const message = {
|
|
2662
|
+
toolResponse: {
|
|
2663
|
+
functionResponses
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
this.webSocketHandler.send(JSON.stringify(message));
|
|
2579
2667
|
}
|
|
2580
2668
|
/**
|
|
2581
2669
|
* Yields messages received from the server.
|
|
@@ -2633,6 +2721,62 @@ class LiveSession {
|
|
|
2633
2721
|
await this.webSocketHandler.close(1000, 'Client closed session.');
|
|
2634
2722
|
}
|
|
2635
2723
|
}
|
|
2724
|
+
/**
|
|
2725
|
+
* Sends realtime input to the server.
|
|
2726
|
+
*
|
|
2727
|
+
* @deprecated Use `sendTextRealtime()`, `sendAudioRealtime()`, and `sendVideoRealtime()` instead.
|
|
2728
|
+
*
|
|
2729
|
+
* @param mediaChunks - The media chunks to send.
|
|
2730
|
+
* @throws If this session has been closed.
|
|
2731
|
+
*
|
|
2732
|
+
* @beta
|
|
2733
|
+
*/
|
|
2734
|
+
async sendMediaChunks(mediaChunks) {
|
|
2735
|
+
if (this.isClosed) {
|
|
2736
|
+
throw new AIError(AIErrorCode.REQUEST_ERROR, 'This LiveSession has been closed and cannot be used.');
|
|
2737
|
+
}
|
|
2738
|
+
// The backend does not support sending more than one mediaChunk in one message.
|
|
2739
|
+
// Work around this limitation by sending mediaChunks in separate messages.
|
|
2740
|
+
mediaChunks.forEach(mediaChunk => {
|
|
2741
|
+
const message = {
|
|
2742
|
+
realtimeInput: { mediaChunks: [mediaChunk] }
|
|
2743
|
+
};
|
|
2744
|
+
this.webSocketHandler.send(JSON.stringify(message));
|
|
2745
|
+
});
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* @deprecated Use `sendTextRealtime()`, `sendAudioRealtime()`, and `sendVideoRealtime()` instead.
|
|
2749
|
+
*
|
|
2750
|
+
* Sends a stream of {@link GenerativeContentBlob}.
|
|
2751
|
+
*
|
|
2752
|
+
* @param mediaChunkStream - The stream of {@link GenerativeContentBlob} to send.
|
|
2753
|
+
* @throws If this session has been closed.
|
|
2754
|
+
*
|
|
2755
|
+
* @beta
|
|
2756
|
+
*/
|
|
2757
|
+
async sendMediaStream(mediaChunkStream) {
|
|
2758
|
+
if (this.isClosed) {
|
|
2759
|
+
throw new AIError(AIErrorCode.REQUEST_ERROR, 'This LiveSession has been closed and cannot be used.');
|
|
2760
|
+
}
|
|
2761
|
+
const reader = mediaChunkStream.getReader();
|
|
2762
|
+
while (true) {
|
|
2763
|
+
try {
|
|
2764
|
+
const { done, value } = await reader.read();
|
|
2765
|
+
if (done) {
|
|
2766
|
+
break;
|
|
2767
|
+
}
|
|
2768
|
+
else if (!value) {
|
|
2769
|
+
throw new Error('Missing chunk in reader, but reader is not done.');
|
|
2770
|
+
}
|
|
2771
|
+
await this.sendMediaChunks([value]);
|
|
2772
|
+
}
|
|
2773
|
+
catch (e) {
|
|
2774
|
+
// Re-throw any errors that occur during stream consumption or sending.
|
|
2775
|
+
const message = e instanceof Error ? e.message : 'Error processing media stream.';
|
|
2776
|
+
throw new AIError(AIErrorCode.REQUEST_ERROR, message);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2636
2780
|
}
|
|
2637
2781
|
|
|
2638
2782
|
/**
|
|
@@ -2693,13 +2837,18 @@ class LiveGenerativeModel extends AIModel {
|
|
|
2693
2837
|
else {
|
|
2694
2838
|
fullModelPath = `projects/${this._apiSettings.project}/locations/${this._apiSettings.location}/${this.model}`;
|
|
2695
2839
|
}
|
|
2840
|
+
// inputAudioTranscription and outputAudioTranscription are on the generation config in the public API,
|
|
2841
|
+
// but the backend expects them to be in the `setup` message.
|
|
2842
|
+
const { inputAudioTranscription, outputAudioTranscription, ...generationConfig } = this.generationConfig;
|
|
2696
2843
|
const setupMessage = {
|
|
2697
2844
|
setup: {
|
|
2698
2845
|
model: fullModelPath,
|
|
2699
|
-
generationConfig
|
|
2846
|
+
generationConfig,
|
|
2700
2847
|
tools: this.tools,
|
|
2701
2848
|
toolConfig: this.toolConfig,
|
|
2702
|
-
systemInstruction: this.systemInstruction
|
|
2849
|
+
systemInstruction: this.systemInstruction,
|
|
2850
|
+
inputAudioTranscription,
|
|
2851
|
+
outputAudioTranscription
|
|
2703
2852
|
}
|
|
2704
2853
|
};
|
|
2705
2854
|
try {
|
|
@@ -3405,7 +3554,7 @@ class AudioConversationRunner {
|
|
|
3405
3554
|
mimeType: 'audio/pcm',
|
|
3406
3555
|
data: base64
|
|
3407
3556
|
};
|
|
3408
|
-
void this.liveSession.
|
|
3557
|
+
void this.liveSession.sendAudioRealtime(chunk);
|
|
3409
3558
|
};
|
|
3410
3559
|
}
|
|
3411
3560
|
/**
|
|
@@ -3783,17 +3932,7 @@ function getLiveGenerativeModel(ai, modelParams) {
|
|
|
3783
3932
|
* @packageDocumentation
|
|
3784
3933
|
*/
|
|
3785
3934
|
function registerAI() {
|
|
3786
|
-
app._registerComponent(new component.Component(AI_TYPE,
|
|
3787
|
-
if (!instanceIdentifier) {
|
|
3788
|
-
throw new AIError(AIErrorCode.ERROR, 'AIService instance identifier is undefined.');
|
|
3789
|
-
}
|
|
3790
|
-
const backend = decodeInstanceIdentifier(instanceIdentifier);
|
|
3791
|
-
// getImmediate for FirebaseApp will always succeed
|
|
3792
|
-
const app = container.getProvider('app').getImmediate();
|
|
3793
|
-
const auth = container.getProvider('auth-internal');
|
|
3794
|
-
const appCheckProvider = container.getProvider('app-check-internal');
|
|
3795
|
-
return new AIService(app, backend, auth, appCheckProvider);
|
|
3796
|
-
}, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
|
|
3935
|
+
app._registerComponent(new component.Component(AI_TYPE, factory, "PUBLIC" /* ComponentType.PUBLIC */).setMultipleInstances(true));
|
|
3797
3936
|
app.registerVersion(name, version, 'node');
|
|
3798
3937
|
// BUILD_TARGET will be replaced by values like esm, cjs, etc during the compilation
|
|
3799
3938
|
app.registerVersion(name, version, 'cjs2020');
|
|
@@ -3825,6 +3964,7 @@ exports.ImagenModel = ImagenModel;
|
|
|
3825
3964
|
exports.ImagenPersonFilterLevel = ImagenPersonFilterLevel;
|
|
3826
3965
|
exports.ImagenSafetyFilterLevel = ImagenSafetyFilterLevel;
|
|
3827
3966
|
exports.InferenceMode = InferenceMode;
|
|
3967
|
+
exports.InferenceSource = InferenceSource;
|
|
3828
3968
|
exports.IntegerSchema = IntegerSchema;
|
|
3829
3969
|
exports.Language = Language;
|
|
3830
3970
|
exports.LiveGenerativeModel = LiveGenerativeModel;
|