@ai-sdk/google 4.0.71 → 4.0.73
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 +32 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +84 -50
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +50 -45
- package/dist/internal/index.js.map +1 -1
- package/docs/15-google.mdx +44 -0
- package/package.json +3 -3
- package/src/google-language-model.ts +61 -47
- package/src/realtime/google-realtime-event-mapper.ts +63 -6
- package/src/realtime/google-realtime-model-options.ts +39 -0
package/docs/15-google.mdx
CHANGED
|
@@ -1186,6 +1186,50 @@ Gemini Live Translation accepts audio input and produces translated audio
|
|
|
1186
1186
|
output. Text input, tools, and custom instructions are not supported by this
|
|
1187
1187
|
model.
|
|
1188
1188
|
|
|
1189
|
+
### Gemini 3.8 Live
|
|
1190
|
+
|
|
1191
|
+
`gemini-3.8-live` and `gemini-3.8-live-extended-thinking` are audio-only
|
|
1192
|
+
realtime models: request `outputModalities: ['audio']`, and add
|
|
1193
|
+
`outputAudioTranscription: {}` if you want the assistant's transcript as
|
|
1194
|
+
`audio-transcript-delta` events. Server-side voice activity detection and
|
|
1195
|
+
proactive audio are always on.
|
|
1196
|
+
|
|
1197
|
+
`gemini-3.8-live-extended-thinking` reasons in the background while streaming
|
|
1198
|
+
audio. Google requires exactly one of `thinkingLevel` (`low`, `medium`, `high`)
|
|
1199
|
+
or `thinkingBudget` for it; the provider sends `thinkingLevel: 'low'` when you
|
|
1200
|
+
set neither. Override it through `providerOptions.google.thinkingConfig`:
|
|
1201
|
+
|
|
1202
|
+
```ts
|
|
1203
|
+
import {
|
|
1204
|
+
google,
|
|
1205
|
+
type Experimental_GoogleRealtimeModelOptions as GoogleRealtimeModelOptions,
|
|
1206
|
+
} from '@ai-sdk/google';
|
|
1207
|
+
|
|
1208
|
+
const token = await google.experimental_realtime.getToken({
|
|
1209
|
+
model: 'gemini-3.8-live-extended-thinking',
|
|
1210
|
+
sessionConfig: {
|
|
1211
|
+
outputModalities: ['audio'],
|
|
1212
|
+
outputAudioTranscription: {},
|
|
1213
|
+
providerOptions: {
|
|
1214
|
+
google: {
|
|
1215
|
+
thinkingConfig: { thinkingLevel: 'high' },
|
|
1216
|
+
} satisfies GoogleRealtimeModelOptions,
|
|
1217
|
+
},
|
|
1218
|
+
},
|
|
1219
|
+
});
|
|
1220
|
+
```
|
|
1221
|
+
|
|
1222
|
+
Tools declared in the session config run asynchronously (`NON_BLOCKING`) by
|
|
1223
|
+
default on these models, so the model keeps talking while a tool call is in
|
|
1224
|
+
flight. Set `providerOptions.google.defaultToolBehavior: 'BLOCKING'` on
|
|
1225
|
+
`gemini-3.8-live` to make it wait for the result instead.
|
|
1226
|
+
|
|
1227
|
+
On the extended thinking model, `turnComplete` (the `response-done` event) does
|
|
1228
|
+
not mean the model is idle. Watch the `interactionStatus` custom event: its
|
|
1229
|
+
`raw.interactionStatus` is `IN_PROGRESS`, `IDLE`, or `WAITING_FOR_INPUT`. A
|
|
1230
|
+
`waitingForInput` custom event is emitted when the model has stopped and needs
|
|
1231
|
+
user input to continue.
|
|
1232
|
+
|
|
1189
1233
|
## Translation Models
|
|
1190
1234
|
|
|
1191
1235
|
<Note type="warning">Speech translation is an experimental feature.</Note>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/google",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.73",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@ai-sdk/provider": "4.0.
|
|
39
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
38
|
+
"@ai-sdk/provider": "4.0.16",
|
|
39
|
+
"@ai-sdk/provider-utils": "5.0.42"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@ai-sdk/test-server": "2.0.1",
|
|
@@ -726,9 +726,12 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
726
726
|
raw: undefined,
|
|
727
727
|
};
|
|
728
728
|
let usage: GoogleUsageMetadata | undefined = undefined;
|
|
729
|
-
let
|
|
729
|
+
let promptFeedback: PromptFeedbackSchema | null = null;
|
|
730
730
|
let lastGroundingMetadata: GroundingMetadataSchema | null = null;
|
|
731
731
|
let lastUrlContextMetadata: UrlContextMetadataSchema | null = null;
|
|
732
|
+
let lastSafetyRatings: SafetyRatingSchema[] | null = null;
|
|
733
|
+
let lastFinishMessage: string | null = null;
|
|
734
|
+
let confirmedPromptBlockReason: string | undefined;
|
|
732
735
|
|
|
733
736
|
const generateId = this.config.generateId;
|
|
734
737
|
let hasToolCalls = false;
|
|
@@ -825,38 +828,48 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
825
828
|
usage = usageMetadata;
|
|
826
829
|
}
|
|
827
830
|
|
|
828
|
-
|
|
831
|
+
if (
|
|
832
|
+
value.promptFeedback != null &&
|
|
833
|
+
confirmedPromptBlockReason == null
|
|
834
|
+
) {
|
|
835
|
+
promptFeedback = value.promptFeedback;
|
|
829
836
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
837
|
+
if (
|
|
838
|
+
isConfirmedPromptBlockReason(value.promptFeedback.blockReason)
|
|
839
|
+
) {
|
|
840
|
+
confirmedPromptBlockReason = value.promptFeedback.blockReason;
|
|
834
841
|
finishReason = {
|
|
835
842
|
unified: 'content-filter',
|
|
836
|
-
raw:
|
|
843
|
+
raw: confirmedPromptBlockReason,
|
|
837
844
|
};
|
|
838
|
-
providerMetadata = wrapProviderMetadata({
|
|
839
|
-
promptFeedback: value.promptFeedback ?? null,
|
|
840
|
-
groundingMetadata: lastGroundingMetadata,
|
|
841
|
-
urlContextMetadata: lastUrlContextMetadata,
|
|
842
|
-
safetyRatings: null,
|
|
843
|
-
usageMetadata: usageMetadata ?? null,
|
|
844
|
-
finishMessage: null,
|
|
845
|
-
serviceTier: usage?.serviceTier ?? null,
|
|
846
|
-
} satisfies GoogleProviderMetadata);
|
|
847
845
|
}
|
|
848
|
-
return;
|
|
849
846
|
}
|
|
850
847
|
|
|
851
|
-
const
|
|
848
|
+
const candidate = value.candidates?.[0];
|
|
852
849
|
|
|
853
|
-
if (candidate
|
|
854
|
-
|
|
850
|
+
if (candidate != null) {
|
|
851
|
+
if (candidate.groundingMetadata != null) {
|
|
852
|
+
lastGroundingMetadata = candidate.groundingMetadata;
|
|
853
|
+
}
|
|
854
|
+
if (candidate.urlContextMetadata != null) {
|
|
855
|
+
lastUrlContextMetadata = candidate.urlContextMetadata;
|
|
856
|
+
}
|
|
857
|
+
if (candidate.safetyRatings != null) {
|
|
858
|
+
lastSafetyRatings = candidate.safetyRatings;
|
|
859
|
+
}
|
|
860
|
+
if (candidate.finishMessage != null) {
|
|
861
|
+
lastFinishMessage = candidate.finishMessage;
|
|
862
|
+
}
|
|
855
863
|
}
|
|
856
|
-
|
|
857
|
-
|
|
864
|
+
|
|
865
|
+
// A confirmed prompt block is terminal for generated content, but
|
|
866
|
+
// later chunks can still contribute usage and provider metadata.
|
|
867
|
+
if (confirmedPromptBlockReason != null || candidate == null) {
|
|
868
|
+
return;
|
|
858
869
|
}
|
|
859
870
|
|
|
871
|
+
const content = candidate.content;
|
|
872
|
+
|
|
860
873
|
const sources = extractSources({
|
|
861
874
|
groundingMetadata: candidate.groundingMetadata,
|
|
862
875
|
generateId,
|
|
@@ -1222,32 +1235,14 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
1222
1235
|
}
|
|
1223
1236
|
}
|
|
1224
1237
|
|
|
1225
|
-
|
|
1226
|
-
const isPromptBlocked =
|
|
1227
|
-
candidate.finishReason == null && promptBlockReason != null;
|
|
1228
|
-
const rawFinishReason =
|
|
1229
|
-
candidate.finishReason ?? promptBlockReason ?? undefined;
|
|
1230
|
-
|
|
1231
|
-
if (rawFinishReason != null) {
|
|
1238
|
+
if (candidate.finishReason != null) {
|
|
1232
1239
|
finishReason = {
|
|
1233
|
-
unified:
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
}),
|
|
1239
|
-
raw: rawFinishReason,
|
|
1240
|
+
unified: mapGoogleFinishReason({
|
|
1241
|
+
finishReason: candidate.finishReason,
|
|
1242
|
+
hasToolCalls,
|
|
1243
|
+
}),
|
|
1244
|
+
raw: candidate.finishReason,
|
|
1240
1245
|
};
|
|
1241
|
-
|
|
1242
|
-
providerMetadata = wrapProviderMetadata({
|
|
1243
|
-
promptFeedback: value.promptFeedback ?? null,
|
|
1244
|
-
groundingMetadata: lastGroundingMetadata,
|
|
1245
|
-
urlContextMetadata: lastUrlContextMetadata,
|
|
1246
|
-
safetyRatings: candidate.safetyRatings ?? null,
|
|
1247
|
-
usageMetadata: usageMetadata ?? null,
|
|
1248
|
-
finishMessage: candidate.finishMessage ?? null,
|
|
1249
|
-
serviceTier: usage?.serviceTier ?? null,
|
|
1250
|
-
} satisfies GoogleProviderMetadata);
|
|
1251
1246
|
}
|
|
1252
1247
|
},
|
|
1253
1248
|
|
|
@@ -1269,7 +1264,15 @@ export class GoogleLanguageModel implements LanguageModelV4 {
|
|
|
1269
1264
|
type: 'finish',
|
|
1270
1265
|
finishReason,
|
|
1271
1266
|
usage: convertGoogleUsage(usage),
|
|
1272
|
-
providerMetadata
|
|
1267
|
+
providerMetadata: wrapProviderMetadata({
|
|
1268
|
+
promptFeedback,
|
|
1269
|
+
groundingMetadata: lastGroundingMetadata,
|
|
1270
|
+
urlContextMetadata: lastUrlContextMetadata,
|
|
1271
|
+
safetyRatings: lastSafetyRatings,
|
|
1272
|
+
usageMetadata: usage ?? null,
|
|
1273
|
+
finishMessage: lastFinishMessage,
|
|
1274
|
+
serviceTier: usage?.serviceTier ?? null,
|
|
1275
|
+
} satisfies GoogleProviderMetadata),
|
|
1273
1276
|
});
|
|
1274
1277
|
},
|
|
1275
1278
|
}),
|
|
@@ -1794,3 +1797,14 @@ const chunkSchema = lazySchema(() =>
|
|
|
1794
1797
|
);
|
|
1795
1798
|
|
|
1796
1799
|
type ChunkSchema = InferSchema<typeof chunkSchema>;
|
|
1800
|
+
|
|
1801
|
+
function isConfirmedPromptBlockReason(
|
|
1802
|
+
blockReason: string | null | undefined,
|
|
1803
|
+
): blockReason is string {
|
|
1804
|
+
return (
|
|
1805
|
+
blockReason != null &&
|
|
1806
|
+
blockReason !== '' &&
|
|
1807
|
+
blockReason !== 'BLOCK_REASON_UNSPECIFIED' &&
|
|
1808
|
+
blockReason !== 'BLOCKED_REASON_UNSPECIFIED'
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
@@ -17,6 +17,7 @@ type GoogleRealtimeFunctionCall = {
|
|
|
17
17
|
|
|
18
18
|
type GoogleRealtimeServerContent = {
|
|
19
19
|
generationComplete?: boolean;
|
|
20
|
+
interactionStatus?: string;
|
|
20
21
|
interrupted?: boolean;
|
|
21
22
|
modelTurn?: {
|
|
22
23
|
parts?: Array<{
|
|
@@ -27,6 +28,7 @@ type GoogleRealtimeServerContent = {
|
|
|
27
28
|
outputTranscription?: { text?: string };
|
|
28
29
|
inputTranscription?: { text?: string };
|
|
29
30
|
turnComplete?: boolean;
|
|
31
|
+
waitingForInput?: boolean;
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
type GoogleRealtimeWireEvent = {
|
|
@@ -232,6 +234,30 @@ export class GoogleRealtimeEventMapper {
|
|
|
232
234
|
});
|
|
233
235
|
}
|
|
234
236
|
|
|
237
|
+
// `interactionStatus` (IN_PROGRESS | IDLE | WAITING_FOR_INPUT) is the definitive
|
|
238
|
+
// session-activity signal for background-reasoning models: `turnComplete`
|
|
239
|
+
// no longer implies the model is idle, since asynchronous tool calls and
|
|
240
|
+
// audio may still follow. Surface it as a custom event so clients can
|
|
241
|
+
// coordinate state on it.
|
|
242
|
+
if (serverContent.interactionStatus != null) {
|
|
243
|
+
events.push({
|
|
244
|
+
type: 'custom',
|
|
245
|
+
rawType: 'interactionStatus',
|
|
246
|
+
raw,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// `waitingForInput` is the always-on Proactive Audio turn-taking signal:
|
|
251
|
+
// the model has yielded the floor and is not generating because it
|
|
252
|
+
// expects the user to continue.
|
|
253
|
+
if (serverContent.waitingForInput) {
|
|
254
|
+
events.push({
|
|
255
|
+
type: 'custom',
|
|
256
|
+
rawType: 'waitingForInput',
|
|
257
|
+
raw,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
235
261
|
if (serverContent.turnComplete) {
|
|
236
262
|
if (this.hasAudio) {
|
|
237
263
|
events.push({
|
|
@@ -377,6 +403,16 @@ async function serializeFunctionCallOutput(
|
|
|
377
403
|
};
|
|
378
404
|
}
|
|
379
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Live models that reason in the background (e.g. `gemini-3.8-live-extended-thinking`).
|
|
408
|
+
* Google requires exactly one of `thinkingLevel` / `thinkingBudget` in their setup and
|
|
409
|
+
* rejects `thinkingConfig` on every other Live model.
|
|
410
|
+
*/
|
|
411
|
+
function isThinkingLiveModel(modelId: string): boolean {
|
|
412
|
+
const modelName = modelId.split('/').at(-1)?.toLowerCase() ?? '';
|
|
413
|
+
return /^gemini-\d+\.\d+-live\b.*thinking/.test(modelName);
|
|
414
|
+
}
|
|
415
|
+
|
|
380
416
|
/**
|
|
381
417
|
* Builds a Google-specific session configuration from a normalized config.
|
|
382
418
|
* Used to construct the `bidiGenerateContentSetup` payload for auth token creation.
|
|
@@ -389,6 +425,11 @@ export function buildGoogleSessionConfig(
|
|
|
389
425
|
model: getModelPath(modelId),
|
|
390
426
|
};
|
|
391
427
|
|
|
428
|
+
const { google, ...restProviderOptions } = config?.providerOptions ?? {};
|
|
429
|
+
const googleOptions = isRecord(google)
|
|
430
|
+
? (google as GoogleRealtimeModelOptions)
|
|
431
|
+
: undefined;
|
|
432
|
+
|
|
392
433
|
const generationConfig: Record<string, unknown> = {};
|
|
393
434
|
|
|
394
435
|
if (config?.outputModalities != null) {
|
|
@@ -424,6 +465,9 @@ export function buildGoogleSessionConfig(
|
|
|
424
465
|
name: tool.name,
|
|
425
466
|
description: tool.description,
|
|
426
467
|
parametersJsonSchema: tool.parameters,
|
|
468
|
+
...(googleOptions?.defaultToolBehavior != null
|
|
469
|
+
? { behavior: googleOptions.defaultToolBehavior }
|
|
470
|
+
: {}),
|
|
427
471
|
})),
|
|
428
472
|
},
|
|
429
473
|
];
|
|
@@ -437,16 +481,28 @@ export function buildGoogleSessionConfig(
|
|
|
437
481
|
setup.outputAudioTranscription = {};
|
|
438
482
|
}
|
|
439
483
|
|
|
484
|
+
// Default to the lowest-latency thinking level so a session on a
|
|
485
|
+
// background-reasoning model works without provider options. Merged last so
|
|
486
|
+
// it survives a raw `providerOptions.generationConfig`.
|
|
487
|
+
const thinkingConfig =
|
|
488
|
+
googleOptions?.thinkingConfig ??
|
|
489
|
+
(isThinkingLiveModel(modelId)
|
|
490
|
+
? { thinkingLevel: 'low' as const }
|
|
491
|
+
: undefined);
|
|
492
|
+
const applyThinkingConfig = () => {
|
|
493
|
+
if (thinkingConfig == null) return;
|
|
494
|
+
const target = isRecord(setup.generationConfig)
|
|
495
|
+
? setup.generationConfig
|
|
496
|
+
: generationConfig;
|
|
497
|
+
setup.generationConfig = { ...target, thinkingConfig };
|
|
498
|
+
};
|
|
499
|
+
|
|
440
500
|
if (config?.providerOptions == null) {
|
|
501
|
+
applyThinkingConfig();
|
|
441
502
|
return setup;
|
|
442
503
|
}
|
|
443
504
|
|
|
444
|
-
|
|
445
|
-
Object.assign(setup, providerOptions);
|
|
446
|
-
|
|
447
|
-
const googleOptions = isRecord(google)
|
|
448
|
-
? (google as GoogleRealtimeModelOptions)
|
|
449
|
-
: undefined;
|
|
505
|
+
Object.assign(setup, restProviderOptions);
|
|
450
506
|
|
|
451
507
|
if (googleOptions?.translationConfig != null) {
|
|
452
508
|
const target = isRecord(setup.generationConfig)
|
|
@@ -458,5 +514,6 @@ export function buildGoogleSessionConfig(
|
|
|
458
514
|
};
|
|
459
515
|
}
|
|
460
516
|
|
|
517
|
+
applyThinkingConfig();
|
|
461
518
|
return setup;
|
|
462
519
|
}
|
|
@@ -20,4 +20,43 @@ export type GoogleRealtimeModelOptions = {
|
|
|
20
20
|
*/
|
|
21
21
|
echoTargetLanguage?: boolean;
|
|
22
22
|
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Gemini Live thinking configuration.
|
|
26
|
+
*
|
|
27
|
+
* Supported by Live models with background reasoning (e.g.
|
|
28
|
+
* `gemini-3.8-live-extended-thinking`), which can process multi-step
|
|
29
|
+
* reasoning and function calls while streaming audio responses. Not
|
|
30
|
+
* supported by latency-optimized models (e.g. `gemini-3.8-live`).
|
|
31
|
+
*/
|
|
32
|
+
thinkingConfig?: {
|
|
33
|
+
/**
|
|
34
|
+
* Thinking effort level. Background-reasoning Live models require exactly
|
|
35
|
+
* one of `thinkingLevel` or `thinkingBudget`; when neither is set, the
|
|
36
|
+
* provider sends `thinkingLevel: 'low'` on those models.
|
|
37
|
+
*/
|
|
38
|
+
thinkingLevel?: 'low' | 'medium' | 'high';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Token budget for background thinking. Mutually exclusive with
|
|
42
|
+
* `thinkingLevel`; setting it suppresses the default level.
|
|
43
|
+
*/
|
|
44
|
+
thinkingBudget?: number;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Whether thought summaries should be included in the response.
|
|
48
|
+
*/
|
|
49
|
+
includeThoughts?: boolean;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default `behavior` stamped onto every function declaration in the
|
|
54
|
+
* session setup.
|
|
55
|
+
*
|
|
56
|
+
* Gemini 3.8 Live models default to `NON_BLOCKING` (asynchronous) function
|
|
57
|
+
* calling. Set to `BLOCKING` for synchronous calls on models that support it
|
|
58
|
+
* (e.g. `gemini-3.8-live`); background-reasoning models accept only
|
|
59
|
+
* `NON_BLOCKING`.
|
|
60
|
+
*/
|
|
61
|
+
defaultToolBehavior?: 'BLOCKING' | 'NON_BLOCKING';
|
|
23
62
|
};
|