@ai-sdk/google-vertex 4.0.189 → 4.0.193

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.
@@ -0,0 +1,253 @@
1
+ import {
2
+ InvalidArgumentError,
3
+ type JSONObject,
4
+ type SharedV3Warning,
5
+ type TranscriptionModelV3,
6
+ } from '@ai-sdk/provider';
7
+ import {
8
+ combineHeaders,
9
+ convertToBase64,
10
+ createJsonResponseHandler,
11
+ parseProviderOptions,
12
+ postJsonToApi,
13
+ resolve,
14
+ type FetchFunction,
15
+ type Resolvable,
16
+ } from '@ai-sdk/provider-utils';
17
+ import { z } from 'zod/v4';
18
+ import { googleVertexFailedResponseHandler } from '../google-vertex-error';
19
+ import {
20
+ googleVertexGeminiTranscriptionModelOptions,
21
+ type GoogleVertexGeminiTranscriptionModelId,
22
+ type GoogleVertexTranscriptionModelGeminiOptions,
23
+ } from './google-vertex-gemini-transcription-model-options';
24
+
25
+ /**
26
+ * Live transcription (`*-live` model variants) requires streaming support,
27
+ * which is only available in AI SDK v7 (transcription specification v4).
28
+ */
29
+ function isLiveTranscriptionModelId(modelId: string): boolean {
30
+ return modelId.includes('-live');
31
+ }
32
+
33
+ interface GoogleVertexGeminiTranscriptionModelConfig {
34
+ provider: string;
35
+ /** Regional base URL ending in `/publishers/google`. */
36
+ baseURL: string;
37
+ headers?: Resolvable<Record<string, string | undefined>>;
38
+ fetch?: FetchFunction;
39
+ _internal?: {
40
+ currentDate?: () => Date;
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Gemini transcription on Vertex AI (e.g. `gemini-3.5-transcribe`) via
46
+ * `generateContent` with `generationConfig.audioTranscriptionConfig`.
47
+ */
48
+ export class GoogleVertexGeminiTranscriptionModel implements TranscriptionModelV3 {
49
+ readonly specificationVersion = 'v3';
50
+
51
+ get provider(): string {
52
+ return this.config.provider;
53
+ }
54
+
55
+ constructor(
56
+ readonly modelId: GoogleVertexGeminiTranscriptionModelId,
57
+ private readonly config: GoogleVertexGeminiTranscriptionModelConfig,
58
+ ) {}
59
+
60
+ private async parseOptions(
61
+ providerOptions: Record<string, unknown> | undefined,
62
+ ): Promise<GoogleVertexTranscriptionModelGeminiOptions | undefined> {
63
+ // The Vertex provider exposes options under `googleVertex`/`vertex`;
64
+ // accept `google` as a cross-namespace fallback (e.g. via the AI Gateway).
65
+ for (const provider of ['googleVertex', 'vertex', 'google'] as const) {
66
+ const parsed = await parseProviderOptions({
67
+ provider,
68
+ providerOptions,
69
+ schema: googleVertexGeminiTranscriptionModelOptions,
70
+ });
71
+ if (parsed != null) return parsed;
72
+ }
73
+ return undefined;
74
+ }
75
+
76
+ async doGenerate(
77
+ options: Parameters<TranscriptionModelV3['doGenerate']>[0],
78
+ ): Promise<Awaited<ReturnType<TranscriptionModelV3['doGenerate']>>> {
79
+ if (isLiveTranscriptionModelId(this.modelId)) {
80
+ throw new InvalidArgumentError({
81
+ argument: 'modelId',
82
+ message:
83
+ `Model '${this.modelId}' only supports streaming transcription, ` +
84
+ `which requires AI SDK v7. Use a unary model such as 'gemini-3.5-transcribe'.`,
85
+ });
86
+ }
87
+
88
+ const currentDate = this.config._internal?.currentDate?.() ?? new Date();
89
+ const warnings: SharedV3Warning[] = [];
90
+ const googleOptions = await this.parseOptions(options.providerOptions);
91
+ const audioTranscriptionConfig =
92
+ buildAudioTranscriptionConfig(googleOptions);
93
+
94
+ const requestBody = {
95
+ contents: [
96
+ {
97
+ role: 'user',
98
+ parts: [
99
+ {
100
+ inlineData: {
101
+ mimeType: options.mediaType,
102
+ data: convertToBase64(options.audio),
103
+ },
104
+ },
105
+ ],
106
+ },
107
+ ],
108
+ ...(audioTranscriptionConfig != null
109
+ ? { generationConfig: { audioTranscriptionConfig } }
110
+ : {}),
111
+ };
112
+
113
+ const {
114
+ value: response,
115
+ responseHeaders,
116
+ rawValue: rawResponse,
117
+ } = await postJsonToApi({
118
+ url: `${this.config.baseURL}/models/${this.modelId}:generateContent`,
119
+ headers: combineHeaders(
120
+ this.config.headers ? await resolve(this.config.headers) : undefined,
121
+ options.headers,
122
+ ),
123
+ body: requestBody,
124
+ failedResponseHandler: googleVertexFailedResponseHandler,
125
+ successfulResponseHandler: createJsonResponseHandler(
126
+ googleVertexGeminiTranscriptionResponseSchema,
127
+ ),
128
+ abortSignal: options.abortSignal,
129
+ fetch: this.config.fetch,
130
+ });
131
+
132
+ const parts = response.candidates?.[0]?.content?.parts ?? [];
133
+ const plainText = parts.map(part => part.text ?? '').join('');
134
+ const transcriptionText = parts
135
+ .map(part => part.audioTranscription?.text ?? '')
136
+ .join('');
137
+ const text = plainText !== '' ? plainText : transcriptionText;
138
+
139
+ let language: string | undefined;
140
+ const segments: Array<{
141
+ text: string;
142
+ startSecond: number;
143
+ endSecond: number;
144
+ }> = [];
145
+ for (const part of parts) {
146
+ const transcription = part.audioTranscription;
147
+ if (transcription == null) continue;
148
+ language ??= transcription.languageCode ?? undefined;
149
+ for (const word of transcription.words ?? []) {
150
+ const startSecond = parseOffsetSeconds(word.startOffset);
151
+ const endSecond = parseOffsetSeconds(word.endOffset);
152
+ if (word.word == null || startSecond == null || endSecond == null) {
153
+ continue;
154
+ }
155
+ segments.push({ text: word.word, startSecond, endSecond });
156
+ }
157
+ }
158
+
159
+ return {
160
+ text,
161
+ segments,
162
+ language,
163
+ durationInSeconds: undefined,
164
+ warnings,
165
+ response: {
166
+ timestamp: currentDate,
167
+ modelId: this.modelId,
168
+ headers: responseHeaders,
169
+ body: rawResponse,
170
+ },
171
+ ...(response.usageMetadata != null
172
+ ? {
173
+ providerMetadata: {
174
+ google: { usageMetadata: response.usageMetadata as JSONObject },
175
+ },
176
+ }
177
+ : {}),
178
+ };
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Builds Google's `AudioTranscriptionConfig` from provider options; returns
184
+ * undefined when no options are set.
185
+ */
186
+ function buildAudioTranscriptionConfig(
187
+ options: GoogleVertexTranscriptionModelGeminiOptions | undefined,
188
+ ): Record<string, unknown> | undefined {
189
+ if (options == null) return undefined;
190
+ const config: Record<string, unknown> = {};
191
+ if (options.languageCodes != null) {
192
+ config.languageCodes = options.languageCodes;
193
+ }
194
+ if (options.customVocabulary != null) {
195
+ config.customVocabulary = options.customVocabulary;
196
+ }
197
+ if (options.wordTimestamp != null) {
198
+ config.wordTimestamp = options.wordTimestamp;
199
+ }
200
+ if (options.diarization != null) {
201
+ config.diarization = options.diarization;
202
+ }
203
+ if (options.mode != null) {
204
+ config.mode = options.mode;
205
+ }
206
+ return Object.keys(config).length > 0 ? config : undefined;
207
+ }
208
+
209
+ /** Parses a Google duration offset such as `"1s"` or `"9.400s"` to seconds. */
210
+ function parseOffsetSeconds(
211
+ offset: string | undefined | null,
212
+ ): number | undefined {
213
+ if (offset == null) return undefined;
214
+ const parsed = Number.parseFloat(offset);
215
+ return Number.isFinite(parsed) ? parsed : undefined;
216
+ }
217
+
218
+ const googleVertexGeminiTranscriptionWordSchema = z.object({
219
+ word: z.string().nullish(),
220
+ startOffset: z.string().nullish(),
221
+ endOffset: z.string().nullish(),
222
+ });
223
+
224
+ const googleVertexGeminiTranscriptionResponseSchema = z.object({
225
+ candidates: z
226
+ .array(
227
+ z.object({
228
+ content: z
229
+ .object({
230
+ parts: z
231
+ .array(
232
+ z.object({
233
+ text: z.string().nullish(),
234
+ audioTranscription: z
235
+ .object({
236
+ text: z.string().nullish(),
237
+ languageCode: z.string().nullish(),
238
+ speakerLabel: z.string().nullish(),
239
+ words: z
240
+ .array(googleVertexGeminiTranscriptionWordSchema)
241
+ .nullish(),
242
+ })
243
+ .nullish(),
244
+ }),
245
+ )
246
+ .nullish(),
247
+ })
248
+ .nullish(),
249
+ }),
250
+ )
251
+ .nullish(),
252
+ usageMetadata: z.record(z.string(), z.unknown()).nullish(),
253
+ });
@@ -33,6 +33,7 @@ import type { GoogleVertexModelId } from './google-vertex-options';
33
33
  import { googleVertexTools } from './google-vertex-tools';
34
34
  import { GoogleVertexTranscriptionModel } from './google-vertex-transcription-model';
35
35
  import type { GoogleVertexTranscriptionModelId } from './google-vertex-transcription-model-options';
36
+ import { GoogleVertexGeminiTranscriptionModel } from './gemini-transcription/google-vertex-gemini-transcription-model';
36
37
  import { GoogleVertexVideoModel } from './google-vertex-video-model';
37
38
  import type { GoogleVertexVideoModelId } from './google-vertex-video-settings';
38
39
 
@@ -305,6 +306,19 @@ export function createVertex(
305
306
  }
306
307
 
307
308
  const config = createConfig('transcription');
309
+
310
+ // Gemini transcription models (`gemini-3.5-transcribe`) use the Vertex
311
+ // generateContent surface; everything else routes to Cloud
312
+ // Speech-to-Text (Chirp, telephony).
313
+ if (modelId.startsWith('gemini')) {
314
+ return new GoogleVertexGeminiTranscriptionModel(modelId, {
315
+ provider: config.provider,
316
+ baseURL: loadBaseURL(),
317
+ headers: config.headers,
318
+ fetch: config.fetch,
319
+ });
320
+ }
321
+
308
322
  return new GoogleVertexTranscriptionModel(modelId, {
309
323
  provider: config.provider,
310
324
  headers: config.headers,
package/src/index.ts CHANGED
@@ -14,6 +14,11 @@ export type {
14
14
  GoogleVertexVideoModelOptions as GoogleVertexVideoProviderOptions,
15
15
  } from './google-vertex-video-model';
16
16
  export type { GoogleVertexVideoModelId } from './google-vertex-video-settings';
17
+ export { GoogleVertexGeminiTranscriptionModel } from './gemini-transcription/google-vertex-gemini-transcription-model';
18
+ export type {
19
+ GoogleVertexGeminiTranscriptionModelId,
20
+ GoogleVertexTranscriptionModelGeminiOptions,
21
+ } from './gemini-transcription/google-vertex-gemini-transcription-model-options';
17
22
  export { createVertex, vertex } from './google-vertex-provider-node';
18
23
  export type {
19
24
  GoogleVertexProvider,