@ai-sdk/google 4.0.77 → 4.0.79
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 +25 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +574 -398
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +26 -2
- package/dist/internal/index.js +371 -220
- package/dist/internal/index.js.map +1 -1
- package/docs/15-google.mdx +85 -14
- package/package.json +3 -3
- package/src/convert-to-google-messages.ts +33 -1
- package/src/google-embedding-model.ts +39 -8
- package/src/google-json-accumulator.ts +0 -1
- package/src/google-prompt.ts +12 -0
- package/src/google-speech-api.ts +2 -1
- package/src/google-speech-input.ts +64 -0
- package/src/google-speech-model-options.ts +28 -1
- package/src/google-speech-model.ts +137 -25
- package/src/internal/index.ts +1 -0
- package/src/tool/code-execution.ts +14 -10
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
InvalidArgumentError,
|
|
3
|
+
type SpeechModelV4,
|
|
4
|
+
type SharedV4Warning,
|
|
5
|
+
} from '@ai-sdk/provider';
|
|
2
6
|
import {
|
|
3
7
|
combineHeaders,
|
|
4
8
|
convertBase64ToUint8Array,
|
|
@@ -14,6 +18,7 @@ import {
|
|
|
14
18
|
} from '@ai-sdk/provider-utils';
|
|
15
19
|
import { googleFailedResponseHandler } from './google-error';
|
|
16
20
|
import { googleSpeechResponseSchema } from './google-speech-api';
|
|
21
|
+
import { getGoogleSpeechInput } from './google-speech-input';
|
|
17
22
|
import {
|
|
18
23
|
googleSpeechProviderOptionsSchema,
|
|
19
24
|
type GoogleSpeechModelId,
|
|
@@ -101,18 +106,36 @@ export class GoogleSpeechModel implements SpeechModelV4 {
|
|
|
101
106
|
});
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
// Older Gemini families require prompt-based directions. Default newer and
|
|
110
|
+
// custom model IDs to structured speech without enumerating their aliases.
|
|
111
|
+
const usesStructuredSpeech =
|
|
112
|
+
!this.modelId.startsWith('gemini-2.5-') &&
|
|
113
|
+
!this.modelId.startsWith('gemini-3.1-');
|
|
114
|
+
|
|
115
|
+
const input = getGoogleSpeechInput({
|
|
116
|
+
text,
|
|
117
|
+
voice,
|
|
118
|
+
providerOptions: { google: googleOptions },
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (input.usesCustomVoice) {
|
|
122
|
+
throw new InvalidArgumentError({
|
|
123
|
+
argument: 'voice',
|
|
124
|
+
message:
|
|
125
|
+
'Custom voices are not supported. Use a prebuilt voice instead.',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
104
129
|
// Multi-speaker (provider option) takes precedence over the single voice.
|
|
105
130
|
const multiSpeakerVoiceConfig = googleOptions?.multiSpeakerVoiceConfig;
|
|
106
131
|
const speechConfig = multiSpeakerVoiceConfig
|
|
107
132
|
? { multiSpeakerVoiceConfig }
|
|
108
133
|
: { voiceConfig: { prebuiltVoiceConfig: { voiceName: voice } } };
|
|
109
134
|
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// the transcript starts with speaker labels (e.g. `Joe: ...`), so prepending
|
|
113
|
-
// instructions would corrupt that parsing — ignore them there (with a warning).
|
|
135
|
+
// Older models expect directions in the prompt. Prepending them to a
|
|
136
|
+
// labelled multi-speaker transcript would break speaker parsing.
|
|
114
137
|
let promptText = text;
|
|
115
|
-
if (instructions != null) {
|
|
138
|
+
if (instructions != null && !usesStructuredSpeech) {
|
|
116
139
|
if (multiSpeakerVoiceConfig) {
|
|
117
140
|
warnings.push({
|
|
118
141
|
type: 'unsupported',
|
|
@@ -126,6 +149,65 @@ export class GoogleSpeechModel implements SpeechModelV4 {
|
|
|
126
149
|
}
|
|
127
150
|
}
|
|
128
151
|
|
|
152
|
+
let parts: Array<{
|
|
153
|
+
text: string;
|
|
154
|
+
speechMetadata?: { speaker?: string; style?: string };
|
|
155
|
+
}> = [{ text: promptText }];
|
|
156
|
+
|
|
157
|
+
if (usesStructuredSpeech) {
|
|
158
|
+
if (googleOptions?.turns && googleOptions.speechMetadata) {
|
|
159
|
+
throw new InvalidArgumentError({
|
|
160
|
+
argument: 'providerOptions',
|
|
161
|
+
message: 'Set speechMetadata on each turn when using turns.',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (googleOptions?.turns && text !== '') {
|
|
165
|
+
warnings.push({
|
|
166
|
+
type: 'unsupported',
|
|
167
|
+
feature: 'text',
|
|
168
|
+
details: 'Google TTS turns replace the top-level text.',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
parts = (
|
|
172
|
+
googleOptions?.turns ?? [
|
|
173
|
+
{ text, speechMetadata: googleOptions?.speechMetadata },
|
|
174
|
+
]
|
|
175
|
+
).map(part => {
|
|
176
|
+
const style = part.speechMetadata?.style ?? instructions;
|
|
177
|
+
const speaker = part.speechMetadata?.speaker;
|
|
178
|
+
if (
|
|
179
|
+
multiSpeakerVoiceConfig &&
|
|
180
|
+
!multiSpeakerVoiceConfig.speakerVoiceConfigs.some(
|
|
181
|
+
config => config.speaker === speaker,
|
|
182
|
+
)
|
|
183
|
+
) {
|
|
184
|
+
throw new InvalidArgumentError({
|
|
185
|
+
argument: 'speechMetadata.speaker',
|
|
186
|
+
message:
|
|
187
|
+
'Every multi-speaker turn must specify a speechMetadata.speaker matching a configured speaker.',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
text: part.text,
|
|
192
|
+
...(style != null || speaker != null
|
|
193
|
+
? { speechMetadata: { style, speaker } }
|
|
194
|
+
: {}),
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
} else if (googleOptions?.turns || googleOptions?.speechMetadata) {
|
|
198
|
+
throw new InvalidArgumentError({
|
|
199
|
+
argument: 'providerOptions',
|
|
200
|
+
message: 'Structured speech metadata and turns require Gemini 3.8 TTS.',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (input.text.length === 0) {
|
|
205
|
+
throw new InvalidArgumentError({
|
|
206
|
+
argument: 'text',
|
|
207
|
+
message: 'Speech input must contain a non-empty transcript.',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
129
211
|
if (speed != null) {
|
|
130
212
|
warnings.push({
|
|
131
213
|
type: 'unsupported',
|
|
@@ -145,11 +227,25 @@ export class GoogleSpeechModel implements SpeechModelV4 {
|
|
|
145
227
|
});
|
|
146
228
|
}
|
|
147
229
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
230
|
+
const formats: Record<string, string> = usesStructuredSpeech
|
|
231
|
+
? {
|
|
232
|
+
wav: 'AUDIO_WAV',
|
|
233
|
+
'audio/wav': 'AUDIO_WAV',
|
|
234
|
+
pcm: 'AUDIO_L16',
|
|
235
|
+
'audio/l16': 'AUDIO_L16',
|
|
236
|
+
mulaw: 'AUDIO_MULAW',
|
|
237
|
+
'audio/mulaw': 'AUDIO_MULAW',
|
|
238
|
+
alaw: 'AUDIO_ALAW',
|
|
239
|
+
'audio/alaw': 'AUDIO_ALAW',
|
|
240
|
+
}
|
|
241
|
+
: { wav: 'AUDIO_WAV', pcm: 'AUDIO_L16' };
|
|
242
|
+
let resolvedOutputFormat = 'wav';
|
|
243
|
+
if (
|
|
244
|
+
outputFormat != null &&
|
|
245
|
+
Object.prototype.hasOwnProperty.call(formats, outputFormat)
|
|
246
|
+
) {
|
|
247
|
+
resolvedOutputFormat = outputFormat;
|
|
248
|
+
} else if (outputFormat != null) {
|
|
153
249
|
warnings.push({
|
|
154
250
|
type: 'unsupported',
|
|
155
251
|
feature: 'outputFormat',
|
|
@@ -158,21 +254,34 @@ export class GoogleSpeechModel implements SpeechModelV4 {
|
|
|
158
254
|
}
|
|
159
255
|
|
|
160
256
|
const requestBody = {
|
|
161
|
-
contents: [{ role: 'user', parts
|
|
257
|
+
contents: [{ role: 'user', parts }],
|
|
162
258
|
generationConfig: {
|
|
163
259
|
responseModalities: ['AUDIO'],
|
|
164
260
|
speechConfig,
|
|
261
|
+
...(usesStructuredSpeech && outputFormat != null
|
|
262
|
+
? {
|
|
263
|
+
responseFormat: {
|
|
264
|
+
audio: { mimeType: formats[resolvedOutputFormat] },
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
: {}),
|
|
165
268
|
},
|
|
166
269
|
};
|
|
167
270
|
|
|
168
|
-
return {
|
|
271
|
+
return {
|
|
272
|
+
requestBody,
|
|
273
|
+
warnings,
|
|
274
|
+
outputFormat: formats[resolvedOutputFormat],
|
|
275
|
+
usesStructuredSpeech,
|
|
276
|
+
};
|
|
169
277
|
}
|
|
170
278
|
|
|
171
279
|
async doGenerate(
|
|
172
280
|
options: Parameters<SpeechModelV4['doGenerate']>[0],
|
|
173
281
|
): Promise<Awaited<ReturnType<SpeechModelV4['doGenerate']>>> {
|
|
174
282
|
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
175
|
-
const { requestBody, warnings, outputFormat } =
|
|
283
|
+
const { requestBody, warnings, outputFormat, usesStructuredSpeech } =
|
|
284
|
+
await this.getArgs(options);
|
|
176
285
|
|
|
177
286
|
const {
|
|
178
287
|
value: response,
|
|
@@ -211,23 +320,26 @@ export class GoogleSpeechModel implements SpeechModelV4 {
|
|
|
211
320
|
}
|
|
212
321
|
|
|
213
322
|
const sampleRate = parseSampleRate(mimeType) ?? DEFAULT_SAMPLE_RATE;
|
|
214
|
-
const
|
|
323
|
+
const bytes =
|
|
215
324
|
base64Audio != null
|
|
216
325
|
? convertBase64ToUint8Array(base64Audio)
|
|
217
326
|
: new Uint8Array(0);
|
|
218
327
|
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// Empty audio is returned as-is so the core layer throws NoSpeechGeneratedError.
|
|
328
|
+
// Older models return PCM, which needs a container for default WAV output.
|
|
329
|
+
// Gemini 3.8 returns WAV itself; adding another header corrupts that audio.
|
|
330
|
+
const isPcm =
|
|
331
|
+
/^audio\/(?:l16|pcm)(?:;|$)/i.test(mimeType ?? '') ||
|
|
332
|
+
(mimeType == null && !usesStructuredSpeech);
|
|
225
333
|
const audio =
|
|
226
|
-
outputFormat === '
|
|
227
|
-
?
|
|
228
|
-
:
|
|
334
|
+
outputFormat === 'AUDIO_WAV' && isPcm && bytes.length > 0
|
|
335
|
+
? addWavHeader(bytes, sampleRate)
|
|
336
|
+
: bytes;
|
|
229
337
|
|
|
230
|
-
if (
|
|
338
|
+
if (
|
|
339
|
+
outputFormat === 'AUDIO_L16' &&
|
|
340
|
+
bytes.length > 0 &&
|
|
341
|
+
!usesStructuredSpeech
|
|
342
|
+
) {
|
|
231
343
|
warnings.push({
|
|
232
344
|
type: 'unsupported',
|
|
233
345
|
feature: 'outputFormat',
|
package/src/internal/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from '../google-language-model';
|
|
2
2
|
export * from '../google-speech-model';
|
|
3
|
+
export { getGoogleSpeechInput } from '../google-speech-input';
|
|
3
4
|
export { googleTools } from '../google-tools';
|
|
4
5
|
export type { GoogleModelId } from '../google-language-model-options';
|
|
5
6
|
export {
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import { createProviderExecutedToolFactory } from '@ai-sdk/provider-utils';
|
|
2
2
|
import { z } from 'zod/v4';
|
|
3
3
|
|
|
4
|
+
export const codeExecutionInputSchema = z.object({
|
|
5
|
+
language: z.string().describe('The programming language of the code.'),
|
|
6
|
+
code: z.string().describe('The code to be executed.'),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const codeExecutionOutputSchema = z.object({
|
|
10
|
+
outcome: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The outcome of the execution (e.g., "OUTCOME_OK").'),
|
|
13
|
+
output: z.string().describe('The output from the code execution.'),
|
|
14
|
+
});
|
|
15
|
+
|
|
4
16
|
/**
|
|
5
17
|
* A tool that enables the model to generate and run Python code.
|
|
6
18
|
*
|
|
@@ -22,14 +34,6 @@ export const codeExecution = createProviderExecutedToolFactory<
|
|
|
22
34
|
{}
|
|
23
35
|
>({
|
|
24
36
|
id: 'google.code_execution',
|
|
25
|
-
inputSchema:
|
|
26
|
-
|
|
27
|
-
code: z.string().describe('The code to be executed.'),
|
|
28
|
-
}),
|
|
29
|
-
outputSchema: z.object({
|
|
30
|
-
outcome: z
|
|
31
|
-
.string()
|
|
32
|
-
.describe('The outcome of the execution (e.g., "OUTCOME_OK").'),
|
|
33
|
-
output: z.string().describe('The output from the code execution.'),
|
|
34
|
-
}),
|
|
37
|
+
inputSchema: codeExecutionInputSchema,
|
|
38
|
+
outputSchema: codeExecutionOutputSchema,
|
|
35
39
|
});
|