ai 7.0.123 → 7.0.126

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/index.d.ts +232 -34
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +415 -100
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal/index.d.ts +199 -8
  7. package/dist/internal/index.d.ts.map +1 -1
  8. package/dist/internal/index.js +9 -3
  9. package/dist/internal/index.js.map +1 -1
  10. package/docs/03-ai-sdk-core/16-mcp-tools.mdx +25 -0
  11. package/docs/03-ai-sdk-core/36-transcription.mdx +38 -1
  12. package/docs/03-ai-sdk-core/37-speech.mdx +23 -0
  13. package/docs/03-ai-sdk-core/60-telemetry.mdx +49 -1
  14. package/docs/07-reference/01-ai-sdk-core/11-stream-transcribe.mdx +7 -0
  15. package/docs/07-reference/01-ai-sdk-core/11-transcribe.mdx +7 -0
  16. package/docs/07-reference/01-ai-sdk-core/12-generate-speech.mdx +7 -0
  17. package/docs/07-reference/01-ai-sdk-core/17-create-agent-ui-stream.mdx +7 -0
  18. package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +7 -0
  19. package/docs/07-reference/01-ai-sdk-core/18-pipe-agent-ui-stream-to-response.mdx +7 -0
  20. package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +60 -3
  21. package/package.json +12 -12
  22. package/src/agent/create-agent-ui-stream-response.ts +16 -4
  23. package/src/agent/create-agent-ui-stream.ts +16 -2
  24. package/src/agent/pipe-agent-ui-stream-to-response.ts +16 -4
  25. package/src/generate-speech/generate-speech.ts +163 -38
  26. package/src/generate-speech/index.ts +4 -0
  27. package/src/generate-speech/speech-events.ts +91 -0
  28. package/src/telemetry/create-telemetry-dispatcher.ts +18 -5
  29. package/src/telemetry/telemetry.ts +38 -6
  30. package/src/telemetry/tracing-channel.ts +3 -0
  31. package/src/transcribe/index.ts +6 -0
  32. package/src/transcribe/stream-transcribe.ts +182 -31
  33. package/src/transcribe/transcribe.ts +186 -33
  34. package/src/transcribe/transcription-events.ts +111 -0
  35. package/src/ui/chat.ts +36 -5
@@ -5,6 +5,7 @@ import {
5
5
  type SharedV4AudioFormat,
6
6
  } from '@ai-sdk/provider';
7
7
  import {
8
+ createIdGenerator,
8
9
  DelayedPromise,
9
10
  withUserAgentSuffix,
10
11
  type ProviderOptions,
@@ -12,16 +13,23 @@ import {
12
13
  import { NoTranscriptGeneratedError } from '../error/no-transcript-generated-error';
13
14
  import { logWarnings } from '../logger/log-warnings';
14
15
  import { resolveTranscriptionModel } from '../model/resolve-model';
16
+ import { createTelemetryDispatcher } from '../telemetry/create-telemetry-dispatcher';
17
+ import type { TelemetryOptions } from '../telemetry/telemetry-options';
15
18
  import type { TranscriptionModel } from '../types/transcription-model';
16
19
  import type { TranscriptionModelResponseMetadata } from '../types/transcription-model-response-metadata';
17
20
  import type { Warning } from '../types/warning';
18
21
  import { asAsyncIterableStream } from '../util/async-iterable-stream';
19
22
  import { mergeAbortSignals } from '../util/merge-abort-signals';
23
+ import { notify } from '../util/notify';
20
24
  import { VERSION } from '../version';
21
25
  import type {
22
26
  StreamTranscriptionResult,
23
27
  TranscriptionStreamPart,
24
28
  } from './stream-transcribe-result';
29
+ import type {
30
+ StreamTranscriptionEndEvent,
31
+ StreamTranscriptionStartEvent,
32
+ } from './transcription-events';
25
33
 
26
34
  type TranscriptSegment = {
27
35
  text: string;
@@ -29,6 +37,11 @@ type TranscriptSegment = {
29
37
  endSecond: number;
30
38
  };
31
39
 
40
+ const originalGenerateCallId = createIdGenerator({
41
+ prefix: 'call',
42
+ size: 24,
43
+ });
44
+
32
45
  /**
33
46
  * Streams transcripts using a transcription model.
34
47
  *
@@ -38,6 +51,7 @@ type TranscriptSegment = {
38
51
  * @param providerOptions - Additional provider-specific options.
39
52
  * @param abortSignal - An optional abort signal that can be used to cancel the call.
40
53
  * @param headers - Additional HTTP/WebSocket headers to send when supported by the provider.
54
+ * @param telemetry - Optional telemetry configuration.
41
55
  *
42
56
  * @returns A result object that contains the streaming transcript and final transcript metadata.
43
57
  */
@@ -49,7 +63,11 @@ export function streamTranscribe({
49
63
  abortSignal,
50
64
  headers,
51
65
  includeRawChunks,
52
- _internal: { currentDate = () => new Date() } = {},
66
+ telemetry,
67
+ _internal: {
68
+ currentDate = () => new Date(),
69
+ generateCallId = originalGenerateCallId,
70
+ } = {},
53
71
  }: {
54
72
  /**
55
73
  * The transcription model to use.
@@ -86,11 +104,17 @@ export function streamTranscribe({
86
104
  */
87
105
  includeRawChunks?: boolean;
88
106
 
107
+ /**
108
+ * Optional telemetry configuration.
109
+ */
110
+ telemetry?: TelemetryOptions;
111
+
89
112
  /**
90
113
  * Internal test hooks.
91
114
  */
92
115
  _internal?: {
93
116
  currentDate?: () => Date;
117
+ generateCallId?: () => string;
94
118
  };
95
119
  }): StreamTranscriptionResult {
96
120
  const resolvedModel = resolveTranscriptionModel(model);
@@ -118,6 +142,34 @@ export function streamTranscribe({
118
142
  `ai/${VERSION}`,
119
143
  );
120
144
 
145
+ const callId = generateCallId();
146
+ const telemetryDispatcher = createTelemetryDispatcher({ telemetry });
147
+ const runInTracingChannelSpan =
148
+ telemetryDispatcher.runInTracingChannelSpan ??
149
+ (async <T>({ execute }: { execute: () => PromiseLike<T> }) =>
150
+ await execute());
151
+ let audioByteLength = 0;
152
+ const countedAudio =
153
+ telemetryDispatcher.experimental_onStreamTranscriptionEnd != null
154
+ ? createByteCountingStream(audio, byteLength => {
155
+ audioByteLength += byteLength;
156
+ })
157
+ : audio;
158
+ const startEvent: StreamTranscriptionStartEvent = {
159
+ callId,
160
+ operationId: 'ai.streamTranscribe',
161
+ provider: resolvedModel.provider,
162
+ modelId: resolvedModel.modelId,
163
+ audio: {
164
+ byteLength: undefined,
165
+ mediaType: inputAudioFormat.type,
166
+ },
167
+ inputAudioFormat,
168
+ maxRetries: undefined,
169
+ headers,
170
+ providerOptions,
171
+ };
172
+
121
173
  const textPromise = new DelayedPromise<string>();
122
174
  const segmentsPromise = new DelayedPromise<Array<TranscriptSegment>>();
123
175
  const languagePromise = new DelayedPromise<string | undefined>();
@@ -129,6 +181,8 @@ export function streamTranscribe({
129
181
  const providerMetadataPromise = new DelayedPromise<
130
182
  Record<string, JSONObject>
131
183
  >();
184
+ let warnings: Array<Warning> = [];
185
+ let telemetrySettled = false;
132
186
 
133
187
  const rejectPendingPromises = (error: unknown) => {
134
188
  for (const promise of [
@@ -151,10 +205,11 @@ export function streamTranscribe({
151
205
  const currentResponseMetadata = () =>
152
206
  response ?? { timestamp: startedAt, modelId: resolvedModel.modelId };
153
207
 
154
- const resolveWarnings = (warnings: Array<Warning>) => {
155
- warningsPromise.resolve(warnings);
208
+ const resolveWarnings = (resolvedWarnings: Array<Warning>) => {
209
+ warnings = resolvedWarnings;
210
+ warningsPromise.resolve(resolvedWarnings);
156
211
  logWarnings({
157
- warnings,
212
+ warnings: resolvedWarnings,
158
213
  provider: resolvedModel.provider,
159
214
  model: resolvedModel.modelId,
160
215
  });
@@ -172,7 +227,7 @@ export function streamTranscribe({
172
227
  Experimental_TranscriptionModelV4StreamPart,
173
228
  TranscriptionStreamPart
174
229
  > & { cancel?: (reason?: unknown) => void } = {
175
- transform(value, controller) {
230
+ async transform(value, controller) {
176
231
  switch (value.type) {
177
232
  case 'stream-start': {
178
233
  resolveWarnings(value.warnings);
@@ -214,6 +269,37 @@ export function streamTranscribe({
214
269
  durationInSecondsPromise.resolve(value.durationInSeconds);
215
270
  responsesPromise.resolve([currentResponseMetadata()]);
216
271
  providerMetadataPromise.resolve(value.providerMetadata ?? {});
272
+
273
+ telemetrySettled = true;
274
+ if (
275
+ telemetryDispatcher.experimental_onStreamTranscriptionEnd != null
276
+ ) {
277
+ const endEvent: StreamTranscriptionEndEvent = {
278
+ callId,
279
+ operationId: 'ai.streamTranscribe',
280
+ provider: resolvedModel.provider,
281
+ modelId: resolvedModel.modelId,
282
+ audio: {
283
+ byteLength: audioByteLength,
284
+ mediaType: inputAudioFormat.type,
285
+ },
286
+ text: value.text,
287
+ segments: value.segments,
288
+ language: value.language,
289
+ durationInSeconds: value.durationInSeconds,
290
+ usage: value.usage,
291
+ warnings,
292
+ providerMetadata: value.providerMetadata,
293
+ response: currentResponseMetadata(),
294
+ };
295
+
296
+ await notify({
297
+ event: endEvent,
298
+ callbacks: [
299
+ telemetryDispatcher.experimental_onStreamTranscriptionEnd,
300
+ ],
301
+ });
302
+ }
217
303
  break;
218
304
  }
219
305
  }
@@ -241,36 +327,50 @@ export function streamTranscribe({
241
327
 
242
328
  // Piping (instead of an eager read loop) preserves consumer backpressure
243
329
  // and propagates cancellation of `fullStream` to the model stream.
244
- void (async () => {
245
- const result = await doStream({
246
- audio,
247
- inputAudioFormat,
248
- providerOptions,
249
- // merged so cancelling fullStream also aborts a still-pending doStream
250
- abortSignal: mergeAbortSignals(abortSignal, pipeAbortController.signal),
251
- headers: headersWithUserAgent,
252
- includeRawChunks,
253
- });
254
-
255
- response = {
256
- timestamp: result.response?.timestamp ?? startedAt,
257
- modelId: result.response?.modelId ?? resolvedModel.modelId,
258
- headers: result.response?.headers,
259
- };
260
-
261
- await result.stream.pipeTo(transform.writable, {
262
- signal: pipeAbortController.signal,
263
- });
264
- })().catch(error => {
330
+ void runInTracingChannelSpan({
331
+ type: 'streamTranscribe',
332
+ event: startEvent,
333
+ execute: async () => {
334
+ await notify({
335
+ event: startEvent,
336
+ callbacks: [
337
+ telemetryDispatcher.experimental_onStreamTranscriptionStart,
338
+ ],
339
+ });
340
+
341
+ const result = await doStream({
342
+ audio: countedAudio,
343
+ inputAudioFormat,
344
+ providerOptions,
345
+ // merged so cancelling fullStream also aborts a still-pending doStream
346
+ abortSignal: mergeAbortSignals(abortSignal, pipeAbortController.signal),
347
+ headers: headersWithUserAgent,
348
+ includeRawChunks,
349
+ });
350
+
351
+ response = {
352
+ timestamp: result.response?.timestamp ?? startedAt,
353
+ modelId: result.response?.modelId ?? resolvedModel.modelId,
354
+ headers: result.response?.headers,
355
+ };
356
+
357
+ await result.stream.pipeTo(transform.writable, {
358
+ signal: pipeAbortController.signal,
359
+ });
360
+ },
361
+ }).catch(async error => {
265
362
  const reason =
266
363
  error ?? new Error('Transcription stream was cancelled or errored.');
267
364
  rejectPendingPromises(reason);
365
+ if (!telemetrySettled) {
366
+ telemetrySettled = true;
367
+ await telemetryDispatcher.onError?.({ callId, error: reason });
368
+ }
268
369
  // When `doStream` rejects before the model stream exists (e.g. auth or
269
- // header resolution failure), nothing has taken ownership of `audio` yet,
270
- // so cancel it directly — otherwise an upstream producer piping into it
271
- // hangs forever. When the model did take a reader, `audio` is locked and
272
- // the cancel rejects, which is fine: the model's cleanup owns it then.
273
- audio.cancel(reason).catch(() => {});
370
+ // header resolution failure), nothing has taken ownership of `countedAudio`
371
+ // yet, so cancel it directly. When the model did take a reader, the cancel
372
+ // rejects and the model's cleanup owns it.
373
+ countedAudio.cancel(reason).catch(() => {});
274
374
  transform.writable.abort(reason).catch(() => {
275
375
  // the writable is already errored when the model stream failed mid-pipe
276
376
  });
@@ -346,3 +446,54 @@ export function streamTranscribe({
346
446
  },
347
447
  };
348
448
  }
449
+
450
+ function createByteCountingStream(
451
+ audio: ReadableStream<Uint8Array | string>,
452
+ onChunk: (byteLength: number) => void,
453
+ ): ReadableStream<Uint8Array | string> {
454
+ let audioReader: ReadableStreamDefaultReader<Uint8Array | string> | undefined;
455
+
456
+ return new ReadableStream<Uint8Array | string>({
457
+ async pull(controller) {
458
+ audioReader ??= audio.getReader();
459
+ const { done, value } = await audioReader.read();
460
+ if (done) {
461
+ controller.close();
462
+ return;
463
+ }
464
+
465
+ onChunk(
466
+ typeof value === 'string'
467
+ ? getBase64ByteLength(value)
468
+ : value.byteLength,
469
+ );
470
+ controller.enqueue(value);
471
+ },
472
+ async cancel(reason) {
473
+ if (audioReader != null) {
474
+ await audioReader.cancel(reason);
475
+ } else {
476
+ await audio.cancel(reason);
477
+ }
478
+ },
479
+ });
480
+ }
481
+
482
+ function getBase64ByteLength(value: string): number {
483
+ let characterCount = 0;
484
+ let lastCharacter = '';
485
+ let secondLastCharacter = '';
486
+
487
+ for (const character of value) {
488
+ if (/\s/.test(character)) {
489
+ continue;
490
+ }
491
+ characterCount++;
492
+ secondLastCharacter = lastCharacter;
493
+ lastCharacter = character;
494
+ }
495
+
496
+ const padding =
497
+ lastCharacter === '=' ? (secondLastCharacter === '=' ? 2 : 1) : 0;
498
+ return Math.floor((characterCount * 3) / 4) - padding;
499
+ }
@@ -1,21 +1,34 @@
1
1
  import type { JSONObject } from '@ai-sdk/provider';
2
2
  import {
3
+ createIdGenerator,
3
4
  detectMediaType,
4
5
  withUserAgentSuffix,
5
6
  type ProviderOptions,
6
7
  } from '@ai-sdk/provider-utils';
7
8
  import { NoTranscriptGeneratedError } from '../error/no-transcript-generated-error';
8
9
  import { logWarnings } from '../logger/log-warnings';
10
+ import { resolveTranscriptionModel } from '../model/resolve-model';
9
11
  import type { DataContent } from '../prompt';
10
12
  import { convertDataContentToUint8Array } from '../prompt/data-content';
13
+ import { createTelemetryDispatcher } from '../telemetry/create-telemetry-dispatcher';
14
+ import type { TelemetryOptions } from '../telemetry/telemetry-options';
11
15
  import type { TranscriptionModel } from '../types/transcription-model';
12
16
  import type { TranscriptionModelResponseMetadata } from '../types/transcription-model-response-metadata';
13
17
  import { createDownload } from '../util/download/create-download';
18
+ import { notify } from '../util/notify';
14
19
  import { prepareRetries } from '../util/prepare-retries';
15
20
  import type { TranscriptionResult } from './transcribe-result';
16
21
  import { VERSION } from '../version';
17
- import { resolveTranscriptionModel } from '../model/resolve-model';
18
22
  import type { Warning } from '../types';
23
+ import type {
24
+ TranscriptionEndEvent,
25
+ TranscriptionStartEvent,
26
+ } from './transcription-events';
27
+
28
+ const originalGenerateCallId = createIdGenerator({
29
+ prefix: 'call',
30
+ size: 24,
31
+ });
19
32
  /**
20
33
  * Generates transcripts using a transcription model.
21
34
  *
@@ -26,6 +39,7 @@ import type { Warning } from '../types';
26
39
  * @param maxRetries - Maximum number of retries. Set to 0 to disable retries. Default: 2.
27
40
  * @param abortSignal - An optional abort signal that can be used to cancel the call.
28
41
  * @param headers - Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers.
42
+ * @param telemetry - Optional telemetry configuration.
29
43
  *
30
44
  * @returns A result object that contains the generated transcript.
31
45
  */
@@ -38,7 +52,9 @@ export async function transcribe({
38
52
  maxRetries: maxRetriesArg,
39
53
  abortSignal,
40
54
  headers,
55
+ telemetry,
41
56
  download: downloadFn = defaultDownload,
57
+ _internal: { generateCallId = originalGenerateCallId } = {},
42
58
  }: {
43
59
  /**
44
60
  * The transcription model to use.
@@ -84,6 +100,11 @@ export async function transcribe({
84
100
  */
85
101
  headers?: Record<string, string>;
86
102
 
103
+ /**
104
+ * Optional telemetry configuration.
105
+ */
106
+ telemetry?: TelemetryOptions;
107
+
87
108
  /**
88
109
  * Custom download function for fetching audio from URLs.
89
110
  * Use `createDownload()` from `ai` to create a download function with custom size limits.
@@ -94,13 +115,20 @@ export async function transcribe({
94
115
  url: URL;
95
116
  abortSignal?: AbortSignal;
96
117
  }) => Promise<{ data: Uint8Array; mediaType: string | undefined }>;
118
+
119
+ /**
120
+ * Internal. For test use only. May change without notice.
121
+ */
122
+ _internal?: {
123
+ generateCallId?: () => string;
124
+ };
97
125
  }): Promise<TranscriptionResult> {
98
126
  const resolvedModel = resolveTranscriptionModel(model);
99
127
  if (!resolvedModel) {
100
128
  throw new Error('Model could not be resolved');
101
129
  }
102
130
 
103
- const { retry } = prepareRetries({
131
+ const { maxRetries, retry } = prepareRetries({
104
132
  maxRetries: maxRetriesArg,
105
133
  abortSignal,
106
134
  });
@@ -110,44 +138,169 @@ export async function transcribe({
110
138
  `ai/${VERSION}`,
111
139
  );
112
140
 
113
- const audioData =
114
- audio instanceof URL
115
- ? (await downloadFn({ url: audio, abortSignal })).data
116
- : convertDataContentToUint8Array(audio);
117
-
118
- const result = await retry(() =>
119
- resolvedModel.doGenerate({
120
- audio: audioData,
121
- abortSignal,
122
- headers: headersWithUserAgent,
123
- providerOptions,
124
- mediaType:
125
- detectMediaType({
126
- data: audioData,
127
- topLevelType: 'audio',
128
- }) ?? 'audio/wav',
129
- }),
130
- );
141
+ const callId = generateCallId();
142
+ const telemetryDispatcher = createTelemetryDispatcher({ telemetry });
143
+ const runInTracingChannelSpan =
144
+ telemetryDispatcher.runInTracingChannelSpan ??
145
+ (async <T>({ execute }: { execute: () => PromiseLike<T> }) =>
146
+ await execute());
131
147
 
132
- logWarnings({
133
- warnings: result.warnings,
148
+ const createStartEvent = ({
149
+ byteLength,
150
+ mediaType,
151
+ }: {
152
+ byteLength: number | undefined;
153
+ mediaType: string | undefined;
154
+ }): TranscriptionStartEvent => ({
155
+ callId,
156
+ operationId: 'ai.transcribe',
134
157
  provider: resolvedModel.provider,
135
- model: resolvedModel.modelId,
158
+ modelId: resolvedModel.modelId,
159
+ audio: {
160
+ byteLength,
161
+ mediaType,
162
+ },
163
+ inputAudioFormat: undefined,
164
+ maxRetries,
165
+ headers,
166
+ providerOptions,
136
167
  });
137
168
 
138
- if (!result.text) {
139
- throw new NoTranscriptGeneratedError({ responses: [result.response] });
169
+ const executeWithStartEvent = async <T>({
170
+ startEvent,
171
+ execute,
172
+ }: {
173
+ startEvent: TranscriptionStartEvent;
174
+ execute: () => PromiseLike<T>;
175
+ }) =>
176
+ await runInTracingChannelSpan({
177
+ type: 'transcribe',
178
+ event: startEvent,
179
+ execute: async () => {
180
+ await notify({
181
+ event: startEvent,
182
+ callbacks: [telemetryDispatcher.onStart],
183
+ });
184
+
185
+ return await execute();
186
+ },
187
+ });
188
+
189
+ let preparationError: unknown;
190
+ let preparationFailed = false;
191
+ let audioData: Uint8Array;
192
+ let downloadedMediaType: string | undefined;
193
+
194
+ try {
195
+ if (audio instanceof URL) {
196
+ const downloadResult = await downloadFn({ url: audio, abortSignal });
197
+ audioData = downloadResult.data;
198
+ downloadedMediaType = downloadResult.mediaType;
199
+ } else {
200
+ audioData = convertDataContentToUint8Array(audio);
201
+ }
202
+ } catch (error) {
203
+ preparationFailed = true;
204
+ preparationError = error;
205
+ audioData = new Uint8Array();
206
+ }
207
+
208
+ if (preparationFailed) {
209
+ const startEvent = createStartEvent({
210
+ byteLength: undefined,
211
+ mediaType: undefined,
212
+ });
213
+
214
+ try {
215
+ return await executeWithStartEvent({
216
+ startEvent,
217
+ execute: () => {
218
+ throw preparationError;
219
+ },
220
+ });
221
+ } catch (error) {
222
+ await telemetryDispatcher.onError?.({ callId, error });
223
+ throw error;
224
+ }
140
225
  }
141
226
 
142
- return new DefaultTranscriptionResult({
143
- text: result.text,
144
- segments: result.segments,
145
- language: result.language,
146
- durationInSeconds: result.durationInSeconds,
147
- warnings: result.warnings,
148
- responses: [result.response],
149
- providerMetadata: result.providerMetadata,
227
+ const mediaType =
228
+ downloadedMediaType ??
229
+ detectMediaType({
230
+ data: audioData,
231
+ topLevelType: 'audio',
232
+ }) ??
233
+ 'audio/wav';
234
+ const startEvent = createStartEvent({
235
+ byteLength: audioData.byteLength,
236
+ mediaType,
150
237
  });
238
+
239
+ try {
240
+ return await executeWithStartEvent({
241
+ startEvent,
242
+ execute: async () => {
243
+ const result = await retry(() =>
244
+ resolvedModel.doGenerate({
245
+ audio: audioData,
246
+ abortSignal,
247
+ headers: headersWithUserAgent,
248
+ providerOptions,
249
+ mediaType,
250
+ }),
251
+ );
252
+
253
+ logWarnings({
254
+ warnings: result.warnings,
255
+ provider: resolvedModel.provider,
256
+ model: resolvedModel.modelId,
257
+ });
258
+
259
+ if (!result.text) {
260
+ throw new NoTranscriptGeneratedError({
261
+ responses: [result.response],
262
+ });
263
+ }
264
+
265
+ const endEvent: TranscriptionEndEvent = {
266
+ callId,
267
+ operationId: 'ai.transcribe',
268
+ provider: resolvedModel.provider,
269
+ modelId: resolvedModel.modelId,
270
+ audio: {
271
+ byteLength: audioData.byteLength,
272
+ mediaType,
273
+ },
274
+ text: result.text,
275
+ segments: result.segments,
276
+ language: result.language,
277
+ durationInSeconds: result.durationInSeconds,
278
+ usage: result.usage,
279
+ warnings: result.warnings,
280
+ providerMetadata: result.providerMetadata,
281
+ response: result.response,
282
+ };
283
+
284
+ await notify({
285
+ event: endEvent,
286
+ callbacks: [telemetryDispatcher.onEnd],
287
+ });
288
+
289
+ return new DefaultTranscriptionResult({
290
+ text: result.text,
291
+ segments: result.segments,
292
+ language: result.language,
293
+ durationInSeconds: result.durationInSeconds,
294
+ warnings: result.warnings,
295
+ responses: [result.response],
296
+ providerMetadata: result.providerMetadata,
297
+ });
298
+ },
299
+ });
300
+ } catch (error) {
301
+ await telemetryDispatcher.onError?.({ callId, error });
302
+ throw error;
303
+ }
151
304
  }
152
305
 
153
306
  class DefaultTranscriptionResult implements TranscriptionResult {