@firebase/ai 2.3.0-canary.cb3bdd812 → 2.3.0-canary.ccbf7ba36

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.3.0-canary.cb3bdd812";
7
+ var version = "2.3.0-canary.ccbf7ba36";
8
8
 
9
9
  /**
10
10
  * @license
@@ -382,7 +382,7 @@ const InferenceMode = {
382
382
  /**
383
383
  * Represents the result of the code execution.
384
384
  *
385
- * @public
385
+ * @beta
386
386
  */
387
387
  const Outcome = {
388
388
  UNSPECIFIED: 'OUTCOME_UNSPECIFIED',
@@ -393,7 +393,7 @@ const Outcome = {
393
393
  /**
394
394
  * The programming language of the code.
395
395
  *
396
- * @public
396
+ * @beta
397
397
  */
398
398
  const Language = {
399
399
  UNSPECIFIED: 'LANGUAGE_UNSPECIFIED',
@@ -416,6 +416,45 @@ const Language = {
416
416
  * See the License for the specific language governing permissions and
417
417
  * limitations under the License.
418
418
  */
419
+ /**
420
+ * The status of a URL retrieval.
421
+ *
422
+ * @remarks
423
+ * <b>URL_RETRIEVAL_STATUS_UNSPECIFIED:</b> Unspecified retrieval status.
424
+ * <br/>
425
+ * <b>URL_RETRIEVAL_STATUS_SUCCESS:</b> The URL retrieval was successful.
426
+ * <br/>
427
+ * <b>URL_RETRIEVAL_STATUS_ERROR:</b> The URL retrieval failed.
428
+ * <br/>
429
+ * <b>URL_RETRIEVAL_STATUS_PAYWALL:</b> The URL retrieval failed because the content is behind a paywall.
430
+ * <br/>
431
+ * <b>URL_RETRIEVAL_STATUS_UNSAFE:</b> The URL retrieval failed because the content is unsafe.
432
+ * <br/>
433
+ *
434
+ * @beta
435
+ */
436
+ const URLRetrievalStatus = {
437
+ /**
438
+ * Unspecified retrieval status.
439
+ */
440
+ URL_RETRIEVAL_STATUS_UNSPECIFIED: 'URL_RETRIEVAL_STATUS_UNSPECIFIED',
441
+ /**
442
+ * The URL retrieval was successful.
443
+ */
444
+ URL_RETRIEVAL_STATUS_SUCCESS: 'URL_RETRIEVAL_STATUS_SUCCESS',
445
+ /**
446
+ * The URL retrieval failed.
447
+ */
448
+ URL_RETRIEVAL_STATUS_ERROR: 'URL_RETRIEVAL_STATUS_ERROR',
449
+ /**
450
+ * The URL retrieval failed because the content is behind a paywall.
451
+ */
452
+ URL_RETRIEVAL_STATUS_PAYWALL: 'URL_RETRIEVAL_STATUS_PAYWALL',
453
+ /**
454
+ * The URL retrieval failed because the content is unsafe.
455
+ */
456
+ URL_RETRIEVAL_STATUS_UNSAFE: 'URL_RETRIEVAL_STATUS_UNSAFE'
457
+ };
419
458
  /**
420
459
  * The types of responses that can be returned by {@link LiveSession.receive}.
421
460
  *
@@ -1876,7 +1915,7 @@ function mapGenerateContentCandidates(candidates) {
1876
1915
  // videoMetadata is not supported.
1877
1916
  // Throw early since developers may send a long video as input and only expect to pay
1878
1917
  // for inference on a small portion of the video.
1879
- if (candidate.content?.parts.some(part => part?.videoMetadata)) {
1918
+ if (candidate.content?.parts?.some(part => part?.videoMetadata)) {
1880
1919
  throw new AIError(AIErrorCode.UNSUPPORTED, 'Part.videoMetadata is not supported in the Gemini Developer API. Please remove this property.');
1881
1920
  }
1882
1921
  const mappedCandidate = {
@@ -1886,7 +1925,8 @@ function mapGenerateContentCandidates(candidates) {
1886
1925
  finishMessage: candidate.finishMessage,
1887
1926
  safetyRatings: mappedSafetyRatings,
1888
1927
  citationMetadata,
1889
- groundingMetadata: candidate.groundingMetadata
1928
+ groundingMetadata: candidate.groundingMetadata,
1929
+ urlContextMetadata: candidate.urlContextMetadata
1890
1930
  };
1891
1931
  mappedCandidates.push(mappedCandidate);
1892
1932
  });
@@ -1977,6 +2017,14 @@ async function* generateResponseSequence(stream, apiSettings) {
1977
2017
  else {
1978
2018
  enhancedResponse = createEnhancedContentResponse(value);
1979
2019
  }
2020
+ const firstCandidate = enhancedResponse.candidates?.[0];
2021
+ // Don't yield a response with no useful data for the developer.
2022
+ if (!firstCandidate?.content?.parts &&
2023
+ !firstCandidate?.finishReason &&
2024
+ !firstCandidate?.citationMetadata &&
2025
+ !firstCandidate?.urlContextMetadata) {
2026
+ continue;
2027
+ }
1980
2028
  yield enhancedResponse;
1981
2029
  }
1982
2030
  }
@@ -2056,36 +2104,43 @@ function aggregateResponses(responses) {
2056
2104
  candidate.safetyRatings;
2057
2105
  aggregatedResponse.candidates[i].groundingMetadata =
2058
2106
  candidate.groundingMetadata;
2107
+ // The urlContextMetadata object is defined in the first chunk of the response stream.
2108
+ // In all subsequent chunks, the urlContextMetadata object will be undefined. We need to
2109
+ // make sure that we don't overwrite the first value urlContextMetadata object with undefined.
2110
+ // FIXME: What happens if we receive a second, valid urlContextMetadata object?
2111
+ const urlContextMetadata = candidate.urlContextMetadata;
2112
+ if (typeof urlContextMetadata === 'object' &&
2113
+ urlContextMetadata !== null &&
2114
+ Object.keys(urlContextMetadata).length > 0) {
2115
+ aggregatedResponse.candidates[i].urlContextMetadata =
2116
+ urlContextMetadata;
2117
+ }
2059
2118
  /**
2060
2119
  * Candidates should always have content and parts, but this handles
2061
2120
  * possible malformed responses.
2062
2121
  */
2063
- if (candidate.content && candidate.content.parts) {
2122
+ if (candidate.content) {
2123
+ // Skip a candidate without parts.
2124
+ if (!candidate.content.parts) {
2125
+ continue;
2126
+ }
2064
2127
  if (!aggregatedResponse.candidates[i].content) {
2065
2128
  aggregatedResponse.candidates[i].content = {
2066
2129
  role: candidate.content.role || 'user',
2067
2130
  parts: []
2068
2131
  };
2069
2132
  }
2070
- const newPart = {};
2071
2133
  for (const part of candidate.content.parts) {
2072
- if (part.text !== undefined) {
2073
- // The backend can send empty text parts. If these are sent back
2074
- // (e.g. in chat history), the backend will respond with an error.
2075
- // To prevent this, ignore empty text parts.
2076
- if (part.text === '') {
2077
- continue;
2078
- }
2079
- newPart.text = part.text;
2080
- }
2081
- if (part.functionCall) {
2082
- newPart.functionCall = part.functionCall;
2134
+ const newPart = { ...part };
2135
+ // The backend can send empty text parts. If these are sent back
2136
+ // (e.g. in chat history), the backend will respond with an error.
2137
+ // To prevent this, ignore empty text parts.
2138
+ if (part.text === '') {
2139
+ continue;
2083
2140
  }
2084
- if (Object.keys(newPart).length === 0) {
2085
- throw new AIError(AIErrorCode.INVALID_CONTENT, 'Part should have at least one property, but there are none. This is likely caused ' +
2086
- 'by a malformed response from the backend.');
2141
+ if (Object.keys(newPart).length > 0) {
2142
+ aggregatedResponse.candidates[i].content.parts.push(newPart);
2087
2143
  }
2088
- aggregatedResponse.candidates[i].content.parts.push(newPart);
2089
2144
  }
2090
2145
  }
2091
2146
  }
@@ -2791,6 +2846,25 @@ class LiveSession {
2791
2846
  this.webSocketHandler.send(JSON.stringify(message));
2792
2847
  });
2793
2848
  }
2849
+ /**
2850
+ * Sends function responses to the server.
2851
+ *
2852
+ * @param functionResponses - The function responses to send.
2853
+ * @throws If this session has been closed.
2854
+ *
2855
+ * @beta
2856
+ */
2857
+ async sendFunctionResponses(functionResponses) {
2858
+ if (this.isClosed) {
2859
+ throw new AIError(AIErrorCode.REQUEST_ERROR, 'This LiveSession has been closed and cannot be used.');
2860
+ }
2861
+ const message = {
2862
+ toolResponse: {
2863
+ functionResponses
2864
+ }
2865
+ };
2866
+ this.webSocketHandler.send(JSON.stringify(message));
2867
+ }
2794
2868
  /**
2795
2869
  * Sends a stream of {@link GenerativeContentBlob}.
2796
2870
  *
@@ -3772,9 +3846,9 @@ class AudioConversationRunner {
3772
3846
  }
3773
3847
  else {
3774
3848
  try {
3775
- const resultPart = await this.options.functionCallingHandler(message.functionCalls);
3849
+ const functionResponse = await this.options.functionCallingHandler(message.functionCalls);
3776
3850
  if (!this.isStopped) {
3777
- void this.liveSession.send([resultPart]);
3851
+ void this.liveSession.sendFunctionResponses([functionResponse]);
3778
3852
  }
3779
3853
  }
3780
3854
  catch (e) {
@@ -4035,5 +4109,5 @@ function registerAI() {
4035
4109
  }
4036
4110
  registerAI();
4037
4111
 
4038
- export { AIError, AIErrorCode, AIModel, AnyOfSchema, ArraySchema, Backend, BackendType, BlockReason, BooleanSchema, ChatSession, FinishReason, FunctionCallingMode, GenerativeModel, GoogleAIBackend, HarmBlockMethod, HarmBlockThreshold, HarmCategory, HarmProbability, HarmSeverity, ImagenAspectRatio, ImagenImageFormat, ImagenModel, ImagenPersonFilterLevel, ImagenSafetyFilterLevel, InferenceMode, IntegerSchema, Language, LiveGenerativeModel, LiveResponseType, LiveSession, Modality, NumberSchema, ObjectSchema, Outcome, POSSIBLE_ROLES, ResponseModality, Schema, SchemaType, StringSchema, VertexAIBackend, getAI, getGenerativeModel, getImagenModel, getLiveGenerativeModel, startAudioConversation };
4112
+ export { AIError, AIErrorCode, AIModel, AnyOfSchema, ArraySchema, Backend, BackendType, BlockReason, BooleanSchema, ChatSession, FinishReason, FunctionCallingMode, GenerativeModel, GoogleAIBackend, HarmBlockMethod, HarmBlockThreshold, HarmCategory, HarmProbability, HarmSeverity, ImagenAspectRatio, ImagenImageFormat, ImagenModel, ImagenPersonFilterLevel, ImagenSafetyFilterLevel, InferenceMode, IntegerSchema, Language, LiveGenerativeModel, LiveResponseType, LiveSession, Modality, NumberSchema, ObjectSchema, Outcome, POSSIBLE_ROLES, ResponseModality, Schema, SchemaType, StringSchema, URLRetrievalStatus, VertexAIBackend, getAI, getGenerativeModel, getImagenModel, getLiveGenerativeModel, startAudioConversation };
4039
4113
  //# sourceMappingURL=index.esm.js.map