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.
- package/CHANGELOG.md +26 -0
- package/dist/index.d.ts +232 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +415 -100
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +199 -8
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +9 -3
- package/dist/internal/index.js.map +1 -1
- package/docs/03-ai-sdk-core/16-mcp-tools.mdx +25 -0
- package/docs/03-ai-sdk-core/36-transcription.mdx +38 -1
- package/docs/03-ai-sdk-core/37-speech.mdx +23 -0
- package/docs/03-ai-sdk-core/60-telemetry.mdx +49 -1
- package/docs/07-reference/01-ai-sdk-core/11-stream-transcribe.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/11-transcribe.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/12-generate-speech.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/17-create-agent-ui-stream.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +7 -0
- package/docs/07-reference/01-ai-sdk-core/18-pipe-agent-ui-stream-to-response.mdx +7 -0
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +60 -3
- package/package.json +12 -12
- package/src/agent/create-agent-ui-stream-response.ts +16 -4
- package/src/agent/create-agent-ui-stream.ts +16 -2
- package/src/agent/pipe-agent-ui-stream-to-response.ts +16 -4
- package/src/generate-speech/generate-speech.ts +163 -38
- package/src/generate-speech/index.ts +4 -0
- package/src/generate-speech/speech-events.ts +91 -0
- package/src/telemetry/create-telemetry-dispatcher.ts +18 -5
- package/src/telemetry/telemetry.ts +38 -6
- package/src/telemetry/tracing-channel.ts +3 -0
- package/src/transcribe/index.ts +6 -0
- package/src/transcribe/stream-transcribe.ts +182 -31
- package/src/transcribe/transcribe.ts +186 -33
- package/src/transcribe/transcription-events.ts +111 -0
- 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
|
-
|
|
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 = (
|
|
155
|
-
|
|
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 (
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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 `
|
|
270
|
-
// so cancel it directly
|
|
271
|
-
//
|
|
272
|
-
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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 {
|