@firebase/ai 2.3.0-canary.7a7634f79 → 2.3.0-canary.ea8512812

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.7a7634f79";
7
+ var version = "2.3.0-canary.ea8512812";
8
8
 
9
9
  /**
10
10
  * @license
@@ -1915,7 +1915,7 @@ function mapGenerateContentCandidates(candidates) {
1915
1915
  // videoMetadata is not supported.
1916
1916
  // Throw early since developers may send a long video as input and only expect to pay
1917
1917
  // for inference on a small portion of the video.
1918
- if (candidate.content?.parts.some(part => part?.videoMetadata)) {
1918
+ if (candidate.content?.parts?.some(part => part?.videoMetadata)) {
1919
1919
  throw new AIError(AIErrorCode.UNSUPPORTED, 'Part.videoMetadata is not supported in the Gemini Developer API. Please remove this property.');
1920
1920
  }
1921
1921
  const mappedCandidate = {
@@ -2017,6 +2017,14 @@ async function* generateResponseSequence(stream, apiSettings) {
2017
2017
  else {
2018
2018
  enhancedResponse = createEnhancedContentResponse(value);
2019
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
+ }
2020
2028
  yield enhancedResponse;
2021
2029
  }
2022
2030
  }
@@ -2111,32 +2119,28 @@ function aggregateResponses(responses) {
2111
2119
  * Candidates should always have content and parts, but this handles
2112
2120
  * possible malformed responses.
2113
2121
  */
2114
- if (candidate.content && candidate.content.parts) {
2122
+ if (candidate.content) {
2123
+ // Skip a candidate without parts.
2124
+ if (!candidate.content.parts) {
2125
+ continue;
2126
+ }
2115
2127
  if (!aggregatedResponse.candidates[i].content) {
2116
2128
  aggregatedResponse.candidates[i].content = {
2117
2129
  role: candidate.content.role || 'user',
2118
2130
  parts: []
2119
2131
  };
2120
2132
  }
2121
- const newPart = {};
2122
2133
  for (const part of candidate.content.parts) {
2123
- if (part.text !== undefined) {
2124
- // The backend can send empty text parts. If these are sent back
2125
- // (e.g. in chat history), the backend will respond with an error.
2126
- // To prevent this, ignore empty text parts.
2127
- if (part.text === '') {
2128
- continue;
2129
- }
2130
- newPart.text = part.text;
2131
- }
2132
- if (part.functionCall) {
2133
- 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;
2134
2140
  }
2135
- if (Object.keys(newPart).length === 0) {
2136
- throw new AIError(AIErrorCode.INVALID_CONTENT, 'Part should have at least one property, but there are none. This is likely caused ' +
2137
- 'by a malformed response from the backend.');
2141
+ if (Object.keys(newPart).length > 0) {
2142
+ aggregatedResponse.candidates[i].content.parts.push(newPart);
2138
2143
  }
2139
- aggregatedResponse.candidates[i].content.parts.push(newPart);
2140
2144
  }
2141
2145
  }
2142
2146
  }