@ai-sdk/google 4.0.82 → 4.0.84

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/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # @ai-sdk/google
2
2
 
3
+ ## 4.0.84
4
+
5
+ ### Patch Changes
6
+
7
+ - d0290cf: fix(google): preserve realtime user utterances with independent IDs and cumulative transcripts, honor text-free transcription completion markers, and advance response IDs after interruption
8
+
9
+ ## 4.0.83
10
+
11
+ ### Patch Changes
12
+
13
+ - 32cf2f6: fix(google): send the default Gemini Live `thinkingLevel` when `thinkingConfig` sets neither `thinkingLevel` nor `thinkingBudget`, and stop overwriting a raw `generationConfig.thinkingConfig`
14
+ - 525efc5: feat(provider): advertise image model file and mask input support
15
+
16
+ Use confirmed model IDs for capability declarations so unrecognized model names
17
+ remain unknown. Include Together AI FLUX.2 Pro and Flex single-image editing, and
18
+ allow asynchronous capability lookups and middleware overrides to resolve to
19
+ unknown.
20
+
21
+ Advertise QuiverAI Arrow 2 and Arrow 2 Telos file-input support, and mark Together
22
+ AI Gemini image inputs unsupported by the current single-image request mapping.
23
+
24
+ - Updated dependencies [e3605f6]
25
+ - Updated dependencies [525efc5]
26
+ - @ai-sdk/provider-utils@5.0.50
27
+ - @ai-sdk/provider@4.0.19
28
+
3
29
  ## 4.0.82
4
30
 
5
31
  ### Patch Changes
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  import { Experimental_EvaluationLanguageModel as EvaluationLanguageModel } from "@ai-sdk/provider-utils/experimental-evaluation";
9
9
 
10
10
  // src/version.ts
11
- var VERSION = true ? "4.0.82" : "0.0.0-test";
11
+ var VERSION = true ? "4.0.84" : "0.0.0-test";
12
12
 
13
13
  // src/google-embedding-model.ts
14
14
  import {
@@ -3912,6 +3912,11 @@ import {
3912
3912
  WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE3,
3913
3913
  WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE3
3914
3914
  } from "@ai-sdk/provider-utils";
3915
+ var googleImageModelsWithFileInputSupport = /* @__PURE__ */ new Set([
3916
+ "gemini-2.5-flash-image",
3917
+ "gemini-3-pro-image-preview",
3918
+ "gemini-3.1-flash-image-preview"
3919
+ ]);
3915
3920
  var GoogleImageModel = class _GoogleImageModel {
3916
3921
  constructor(modelId, settings, config) {
3917
3922
  this.modelId = modelId;
@@ -3934,6 +3939,12 @@ var GoogleImageModel = class _GoogleImageModel {
3934
3939
  }
3935
3940
  return 1;
3936
3941
  }
3942
+ get supportsFileInputs() {
3943
+ return googleImageModelsWithFileInputSupport.has(this.modelId) ? true : void 0;
3944
+ }
3945
+ get supportsMaskInputs() {
3946
+ return this.supportsFileInputs === true ? false : void 0;
3947
+ }
3937
3948
  get provider() {
3938
3949
  return this.config.provider;
3939
3950
  }
@@ -8080,6 +8091,10 @@ import { isRecord, safeParseJSON } from "@ai-sdk/provider-utils";
8080
8091
  var GoogleRealtimeEventMapper = class {
8081
8092
  constructor() {
8082
8093
  this.turnCounter = 0;
8094
+ this.inputTranscriptionCounter = 0;
8095
+ this.inputTranscriptionBuffer = "";
8096
+ this.inputTranscriptionBoundary = "none";
8097
+ this.preserveInputTranscriptionAcrossTurnComplete = false;
8083
8098
  this.hasAudio = false;
8084
8099
  this.hasText = false;
8085
8100
  this.hasTranscript = false;
@@ -8093,12 +8108,11 @@ var GoogleRealtimeEventMapper = class {
8093
8108
  return `google-item-${this.turnCounter}`;
8094
8109
  }
8095
8110
  /**
8096
- * Rolls over to the next turn lazily, only once new model content actually
8097
- * arrives. `turnComplete` merely marks the current turn closed; the counter
8098
- * is not advanced until the next response begins. This keeps a transcript
8099
- * that arrives shortly after `turnComplete` attached to the turn it belongs
8100
- * to, since Google delivers transcription independently with no guaranteed
8101
- * ordering relative to `turnComplete`.
8111
+ * Rolls over to the next turn lazily, once model content for the next turn
8112
+ * arrives. `turnComplete` merely marks the current turn closed.
8113
+ * This keeps an output transcript that arrives shortly after `turnComplete`
8114
+ * attached to the turn it belongs to, since Google delivers transcription
8115
+ * independently with no guaranteed ordering relative to `turnComplete`.
8102
8116
  */
8103
8117
  beginTurnIfClosed() {
8104
8118
  if (!this.turnClosed) return;
@@ -8107,6 +8121,48 @@ var GoogleRealtimeEventMapper = class {
8107
8121
  this.hasText = false;
8108
8122
  this.hasTranscript = false;
8109
8123
  this.turnClosed = false;
8124
+ this.preserveInputTranscriptionAcrossTurnComplete = false;
8125
+ }
8126
+ // Google streams input transcription as a sequence of non-accumulating
8127
+ // fragments for a single user utterance (e.g. "The quick brown fox",
8128
+ // " jumps over the", " lazy dog."). Concatenate consecutive fragments into
8129
+ // one running transcript under a stable synthetic id so the realtime reducer
8130
+ // (which overwrites the message for a given id) surfaces them as a single
8131
+ // coherent user message rather than one message per fragment. Google's
8132
+ // `finished` signal is the authoritative utterance boundary. For older
8133
+ // payloads without that signal, response boundaries remain a fallback.
8134
+ //
8135
+ // Input transcriptions can arrive independently of response turn events, so
8136
+ // their synthetic IDs use a sequence independent of `turnCounter`.
8137
+ accumulateInputTranscription({
8138
+ text,
8139
+ finished
8140
+ }) {
8141
+ if (!text) {
8142
+ if (finished === true) {
8143
+ this.finishInputTranscription();
8144
+ }
8145
+ return void 0;
8146
+ }
8147
+ const hasFinishedSignal = finished != null;
8148
+ if (this.inputTranscriptionBuffer !== "" && (this.inputTranscriptionBoundary === "interrupted" || this.inputTranscriptionBoundary === "turn-complete" && !hasFinishedSignal)) {
8149
+ this.finishInputTranscription();
8150
+ }
8151
+ this.inputTranscriptionBoundary = "none";
8152
+ this.inputTranscriptionBuffer += text;
8153
+ const result = {
8154
+ itemId: `google-input-${this.inputTranscriptionCounter}`,
8155
+ transcript: this.inputTranscriptionBuffer
8156
+ };
8157
+ if (finished === true) {
8158
+ this.finishInputTranscription();
8159
+ }
8160
+ return result;
8161
+ }
8162
+ finishInputTranscription() {
8163
+ if (this.inputTranscriptionBuffer === "") return;
8164
+ this.inputTranscriptionCounter++;
8165
+ this.inputTranscriptionBuffer = "";
8110
8166
  }
8111
8167
  parseServerEvent(raw) {
8112
8168
  const data = raw;
@@ -8163,19 +8219,26 @@ var GoogleRealtimeEventMapper = class {
8163
8219
  if (data.serverContent != null) {
8164
8220
  return this.parseServerContent(data.serverContent, raw);
8165
8221
  }
8166
- if (data.inputTranscription?.text != null) {
8167
- return {
8168
- type: "input-transcription-completed",
8169
- itemId: `google-input-${this.turnCounter}`,
8170
- transcript: data.inputTranscription.text,
8171
- raw
8172
- };
8222
+ if (data.inputTranscription != null) {
8223
+ const transcription = this.accumulateInputTranscription(
8224
+ data.inputTranscription
8225
+ );
8226
+ if (transcription != null) {
8227
+ return {
8228
+ type: "input-transcription-completed",
8229
+ ...transcription,
8230
+ raw
8231
+ };
8232
+ }
8173
8233
  }
8174
8234
  return { type: "custom", rawType: String(Object.keys(data)[0]), raw };
8175
8235
  }
8176
8236
  parseServerContent(serverContent, raw) {
8177
8237
  const events = [];
8178
8238
  if (serverContent.interrupted) {
8239
+ this.inputTranscriptionBoundary = "interrupted";
8240
+ this.preserveInputTranscriptionAcrossTurnComplete = true;
8241
+ this.turnClosed = true;
8179
8242
  events.push({
8180
8243
  type: "speech-started",
8181
8244
  raw
@@ -8216,13 +8279,17 @@ var GoogleRealtimeEventMapper = class {
8216
8279
  raw
8217
8280
  });
8218
8281
  }
8219
- if (serverContent.inputTranscription?.text) {
8220
- events.push({
8221
- type: "input-transcription-completed",
8222
- itemId: `google-input-${this.turnCounter}`,
8223
- transcript: serverContent.inputTranscription.text,
8224
- raw
8225
- });
8282
+ if (serverContent.inputTranscription != null) {
8283
+ const transcription = this.accumulateInputTranscription(
8284
+ serverContent.inputTranscription
8285
+ );
8286
+ if (transcription != null) {
8287
+ events.push({
8288
+ type: "input-transcription-completed",
8289
+ ...transcription,
8290
+ raw
8291
+ });
8292
+ }
8226
8293
  }
8227
8294
  if (serverContent.generationComplete) {
8228
8295
  events.push({
@@ -8277,6 +8344,13 @@ var GoogleRealtimeEventMapper = class {
8277
8344
  raw
8278
8345
  });
8279
8346
  this.turnClosed = true;
8347
+ if (this.preserveInputTranscriptionAcrossTurnComplete) {
8348
+ this.preserveInputTranscriptionAcrossTurnComplete = false;
8349
+ } else {
8350
+ if (this.inputTranscriptionBoundary !== "interrupted") {
8351
+ this.inputTranscriptionBoundary = "turn-complete";
8352
+ }
8353
+ }
8280
8354
  }
8281
8355
  if (events.length === 0) {
8282
8356
  return { type: "custom", rawType: "serverContent", raw };
@@ -8405,7 +8479,9 @@ function buildGoogleSessionConfig(config, modelId) {
8405
8479
  if (config?.outputAudioTranscription != null) {
8406
8480
  setup.outputAudioTranscription = {};
8407
8481
  }
8408
- const thinkingConfig = googleOptions?.thinkingConfig ?? (isThinkingLiveModel(modelId) ? { thinkingLevel: "low" } : void 0);
8482
+ const rawGenerationConfig = restProviderOptions.generationConfig;
8483
+ const explicitThinkingConfig = googleOptions?.thinkingConfig ?? (isRecord(rawGenerationConfig) && isRecord(rawGenerationConfig.thinkingConfig) ? rawGenerationConfig.thinkingConfig : void 0);
8484
+ const thinkingConfig = isThinkingLiveModel(modelId) && explicitThinkingConfig?.thinkingLevel == null && explicitThinkingConfig?.thinkingBudget == null ? { ...explicitThinkingConfig, thinkingLevel: "low" } : googleOptions?.thinkingConfig;
8409
8485
  const applyThinkingConfig = () => {
8410
8486
  if (thinkingConfig == null) return;
8411
8487
  const target = isRecord(setup.generationConfig) ? setup.generationConfig : generationConfig;