ai 7.0.122 → 7.0.124
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 +29 -0
- package/dist/index.d.ts +7598 -7268
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19555 -22545
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +2235 -2004
- package/dist/internal/index.d.ts.map +1 -0
- package/dist/internal/index.js +3261 -3921
- package/dist/internal/index.js.map +1 -1
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/test/index.d.ts +294 -273
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +325 -469
- package/dist/test/index.js.map +1 -1
- package/docs/02-foundations/03-prompts.mdx +3 -1
- package/docs/02-foundations/06-provider-options.mdx +1 -1
- package/docs/02-getting-started/06-nodejs.mdx +4 -4
- package/docs/02-getting-started/08-tanstack-start.mdx +1 -1
- package/docs/03-agents/05-configuring-call-options.mdx +1 -1
- package/docs/03-ai-sdk-core/10-generating-structured-data.mdx +1 -1
- package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +3 -0
- package/docs/03-ai-sdk-core/17-mcp-apps.mdx +5 -5
- package/docs/03-ai-sdk-core/36-transcription.mdx +36 -0
- package/docs/03-ai-sdk-core/37-speech.mdx +22 -1
- package/docs/03-ai-sdk-core/42-batch.mdx +1 -1
- package/docs/03-ai-sdk-core/60-telemetry.mdx +51 -3
- package/docs/03-ai-sdk-harnesses/02-harness-agent.mdx +24 -0
- package/docs/03-ai-sdk-harnesses/05-harness-adapters.mdx +12 -12
- package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +9 -7
- package/docs/04-ai-sdk-ui/03-chatbot-resume-streams.mdx +2 -2
- package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +7 -9
- package/docs/04-ai-sdk-ui/05-completion.mdx +1 -1
- package/docs/04-ai-sdk-ui/50-stream-protocol.mdx +2 -2
- package/docs/05-ai-sdk-rsc/03-generative-ui-state.mdx +8 -7
- package/docs/05-ai-sdk-rsc/06-loading-state.mdx +19 -10
- package/docs/05-ai-sdk-rsc/08-error-handling.mdx +2 -0
- package/docs/05-ai-sdk-rsc/10-migrating-to-ui.mdx +4 -3
- package/docs/06-advanced/02-stopping-streams.mdx +2 -2
- package/docs/06-advanced/04-caching.mdx +9 -5
- package/docs/06-advanced/05-multiple-streamables.mdx +1 -1
- package/docs/06-advanced/07-rendering-ui-with-language-models.mdx +16 -16
- package/docs/06-advanced/09-multistep-interfaces.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +4 -4
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +4 -4
- 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/14-upload-file.mdx +4 -4
- package/docs/07-reference/01-ai-sdk-core/14-upload-skill.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/15-agent.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +1 -4
- package/docs/07-reference/01-ai-sdk-core/22-dynamic-tool.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/23-create-mcp-client.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/30-model-message.mdx +2 -2
- package/docs/07-reference/01-ai-sdk-core/69-add-tool-input-examples-middleware.mdx +1 -1
- package/docs/07-reference/01-ai-sdk-core/80-smooth-stream.mdx +1 -1
- package/docs/07-reference/02-ai-sdk-ui/31-convert-to-model-messages.mdx +5 -4
- package/docs/07-reference/02-ai-sdk-ui/40-create-ui-message-stream.mdx +3 -2
- package/docs/07-reference/02-ai-sdk-ui/41-create-ui-message-stream-response.mdx +7 -0
- package/docs/07-reference/02-ai-sdk-ui/42-pipe-ui-message-stream-to-response.mdx +7 -0
- package/docs/07-reference/02-ai-sdk-ui/50-direct-chat-transport.mdx +2 -2
- package/docs/07-reference/03-ai-sdk-rsc/01-stream-ui.mdx +2 -2
- package/docs/07-reference/04-ai-sdk-workflow/01-workflow-agent.mdx +1 -1
- package/docs/08-migration-guides/25-migration-guide-5-0-data.mdx +4 -5
- package/docs/08-migration-guides/26-migration-guide-5-0.mdx +2 -1
- package/docs/08-migration-guides/29-migration-guide-4-0.mdx +4 -4
- package/docs/08-migration-guides/38-migration-guide-3-2.mdx +1 -1
- package/docs/08-migration-guides/39-migration-guide-3-1.mdx +1 -1
- package/docs/09-troubleshooting/12-use-chat-an-error-occurred.mdx +8 -6
- package/docs/09-troubleshooting/50-react-maximum-update-depth-exceeded.mdx +6 -6
- package/docs/09-troubleshooting/70-high-memory-usage-with-images.mdx +1 -1
- package/package.json +17 -17
- package/src/agent/create-agent-ui-stream-response.ts +3 -0
- package/src/agent/pipe-agent-ui-stream-to-response.ts +3 -0
- 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/generate-text/prune-messages.ts +1 -1
- package/src/middleware/extract-reasoning-middleware.ts +27 -11
- 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/convert-to-model-messages.ts +18 -6
- package/src/ui-message-stream/create-sse-stream-with-keep-alive.ts +89 -0
- package/src/ui-message-stream/create-ui-message-stream-response.ts +7 -1
- package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +7 -1
- package/src/ui-message-stream/ui-message-stream-response-init.ts +7 -0
|
@@ -82,7 +82,7 @@ export function pruneMessages({
|
|
|
82
82
|
const keptToolCallIds: Set<string> = new Set();
|
|
83
83
|
const keptApprovalIds: Set<string> = new Set();
|
|
84
84
|
|
|
85
|
-
if (keepLastMessagesCount != null) {
|
|
85
|
+
if (keepLastMessagesCount != null && keepLastMessagesCount !== 0) {
|
|
86
86
|
for (const message of messages.slice(-keepLastMessagesCount)) {
|
|
87
87
|
if (
|
|
88
88
|
(message.role === 'assistant' || message.role === 'tool') &&
|
|
@@ -116,20 +116,15 @@ export function extractReasoningMiddleware({
|
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
if (
|
|
120
|
-
chunk.type === 'text-end' &&
|
|
121
|
-
delayedTextStarts[chunk.id] != null
|
|
122
|
-
) {
|
|
123
|
-
controller.enqueue(delayedTextStarts[chunk.id]);
|
|
124
|
-
delete delayedTextStarts[chunk.id];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (chunk.type !== 'text-delta') {
|
|
119
|
+
if (chunk.type !== 'text-delta' && chunk.type !== 'text-end') {
|
|
128
120
|
controller.enqueue(chunk);
|
|
129
121
|
return;
|
|
130
122
|
}
|
|
131
123
|
|
|
132
|
-
if (
|
|
124
|
+
if (
|
|
125
|
+
chunk.type === 'text-delta' &&
|
|
126
|
+
reasoningExtractions[chunk.id] == null
|
|
127
|
+
) {
|
|
133
128
|
reasoningExtractions[chunk.id] = {
|
|
134
129
|
isFirstReasoning: true,
|
|
135
130
|
isFirstText: true,
|
|
@@ -143,7 +138,14 @@ export function extractReasoningMiddleware({
|
|
|
143
138
|
|
|
144
139
|
const activeExtraction = reasoningExtractions[chunk.id];
|
|
145
140
|
|
|
146
|
-
activeExtraction
|
|
141
|
+
if (activeExtraction == null) {
|
|
142
|
+
if (delayedTextStarts[chunk.id] != null) {
|
|
143
|
+
controller.enqueue(delayedTextStarts[chunk.id]);
|
|
144
|
+
delete delayedTextStarts[chunk.id];
|
|
145
|
+
}
|
|
146
|
+
controller.enqueue(chunk);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
147
149
|
|
|
148
150
|
function getReasoningId() {
|
|
149
151
|
return (activeExtraction.reasoningId ??= `reasoning-${reasoningIdCounter++}`);
|
|
@@ -199,6 +201,20 @@ export function extractReasoningMiddleware({
|
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
203
|
|
|
204
|
+
if (chunk.type === 'text-end') {
|
|
205
|
+
publish(activeExtraction.buffer);
|
|
206
|
+
activeExtraction.buffer = '';
|
|
207
|
+
|
|
208
|
+
if (delayedTextStarts[chunk.id] != null) {
|
|
209
|
+
controller.enqueue(delayedTextStarts[chunk.id]);
|
|
210
|
+
delete delayedTextStarts[chunk.id];
|
|
211
|
+
}
|
|
212
|
+
controller.enqueue(chunk);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
activeExtraction.buffer += chunk.delta;
|
|
217
|
+
|
|
202
218
|
do {
|
|
203
219
|
const nextTag = activeExtraction.isReasoning
|
|
204
220
|
? closingTag
|
|
@@ -142,7 +142,7 @@ export function createTelemetryDispatcher({
|
|
|
142
142
|
|
|
143
143
|
const mergeTelemetryCallback = <KEY extends TelemetryCallbackKey>(
|
|
144
144
|
key: KEY,
|
|
145
|
-
): Callback<TelemetryEvent<KEY>> => {
|
|
145
|
+
): Callback<TelemetryEvent<KEY>> | undefined => {
|
|
146
146
|
const integrationCallbacks = (
|
|
147
147
|
integrations
|
|
148
148
|
.map(integration => integration[key]?.bind(integration))
|
|
@@ -157,6 +157,10 @@ export function createTelemetryDispatcher({
|
|
|
157
157
|
>,
|
|
158
158
|
);
|
|
159
159
|
|
|
160
|
+
if (integrationCallbacks.length === 0) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
160
164
|
const mergedIntegrationCallback = mergeCallbacks(...integrationCallbacks);
|
|
161
165
|
|
|
162
166
|
return async (event: TelemetryEvent<KEY>) => {
|
|
@@ -164,6 +168,9 @@ export function createTelemetryDispatcher({
|
|
|
164
168
|
};
|
|
165
169
|
};
|
|
166
170
|
|
|
171
|
+
const onStepEnd = mergeTelemetryCallback('onStepEnd');
|
|
172
|
+
const onStepFinish = mergeTelemetryCallback('onStepFinish');
|
|
173
|
+
|
|
167
174
|
const executeLanguageModelCallWrappers = integrations
|
|
168
175
|
.map(integration => integration.executeLanguageModelCall?.bind(integration))
|
|
169
176
|
.filter(Boolean) as Array<
|
|
@@ -205,10 +212,10 @@ export function createTelemetryDispatcher({
|
|
|
205
212
|
// deprecated `onStepFinish` callback so integrations that still implement
|
|
206
213
|
// only `onStepFinish` keep receiving step-end events during the deprecation
|
|
207
214
|
// window.
|
|
208
|
-
onStepEnd:
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
onStepEnd:
|
|
216
|
+
onStepEnd == null && onStepFinish == null
|
|
217
|
+
? undefined
|
|
218
|
+
: mergeCallbacks(onStepEnd, onStepFinish),
|
|
212
219
|
onObjectStepStart: mergeTelemetryCallback('onObjectStepStart'),
|
|
213
220
|
onObjectStepEnd: mergeTelemetryCallback('onObjectStepEnd'),
|
|
214
221
|
onEmbedStart: mergeTelemetryCallback('onEmbedStart'),
|
|
@@ -227,6 +234,12 @@ export function createTelemetryDispatcher({
|
|
|
227
234
|
experimental_onEvaluateEnd: mergeTelemetryCallback(
|
|
228
235
|
'experimental_onEvaluateEnd',
|
|
229
236
|
),
|
|
237
|
+
experimental_onStreamTranscriptionStart: mergeTelemetryCallback(
|
|
238
|
+
'experimental_onStreamTranscriptionStart',
|
|
239
|
+
),
|
|
240
|
+
experimental_onStreamTranscriptionEnd: mergeTelemetryCallback(
|
|
241
|
+
'experimental_onStreamTranscriptionEnd',
|
|
242
|
+
),
|
|
230
243
|
onEnd: mergeTelemetryCallback('onEnd'),
|
|
231
244
|
onAbort: mergeTelemetryCallback('onAbort'),
|
|
232
245
|
onError: mergeTelemetryCallback('onError'),
|
|
@@ -40,6 +40,16 @@ import type {
|
|
|
40
40
|
RerankingModelCallEndEvent,
|
|
41
41
|
RerankingModelCallStartEvent,
|
|
42
42
|
} from '../rerank/rerank-events';
|
|
43
|
+
import type {
|
|
44
|
+
GenerateSpeechEndEvent,
|
|
45
|
+
GenerateSpeechStartEvent,
|
|
46
|
+
} from '../generate-speech/speech-events';
|
|
47
|
+
import type {
|
|
48
|
+
StreamTranscriptionEndEvent,
|
|
49
|
+
StreamTranscriptionStartEvent,
|
|
50
|
+
TranscriptionEndEvent,
|
|
51
|
+
TranscriptionStartEvent,
|
|
52
|
+
} from '../transcribe/transcription-events';
|
|
43
53
|
import type { Callback } from '../util/callback';
|
|
44
54
|
import type { TelemetryOptions } from '../telemetry/telemetry-options';
|
|
45
55
|
import type { TelemetryTracingEventType } from './tracing-channel';
|
|
@@ -51,17 +61,25 @@ export type InferTelemetryEvent<EVENT> = EVENT &
|
|
|
51
61
|
'integrations' | 'isEnabled' | 'includeRuntimeContext'
|
|
52
62
|
>;
|
|
53
63
|
|
|
64
|
+
type BivariantCallback<EVENT> = {
|
|
65
|
+
bivarianceHack(event: EVENT): PromiseLike<void> | void;
|
|
66
|
+
}['bivarianceHack'];
|
|
67
|
+
|
|
54
68
|
type OperationStartEvent =
|
|
55
69
|
| GenerateTextStartEvent
|
|
56
70
|
| GenerateObjectStartEvent
|
|
57
71
|
| EmbedStartEvent
|
|
58
|
-
| RerankStartEvent
|
|
72
|
+
| RerankStartEvent
|
|
73
|
+
| GenerateSpeechStartEvent
|
|
74
|
+
| TranscriptionStartEvent;
|
|
59
75
|
|
|
60
76
|
type OperationEndEvent =
|
|
61
77
|
| GenerateTextEndEvent<ToolSet>
|
|
62
78
|
| GenerateObjectEndEvent<unknown>
|
|
63
79
|
| EmbedEndEvent
|
|
64
|
-
| RerankEndEvent
|
|
80
|
+
| RerankEndEvent
|
|
81
|
+
| GenerateSpeechEndEvent
|
|
82
|
+
| TranscriptionEndEvent;
|
|
65
83
|
|
|
66
84
|
export interface TelemetryDispatcher {
|
|
67
85
|
/**
|
|
@@ -102,6 +120,8 @@ export interface TelemetryDispatcher {
|
|
|
102
120
|
experimental_onEvaluationModelCallStart?: Callback<EvaluationModelCallStartEvent>;
|
|
103
121
|
experimental_onEvaluationModelCallEnd?: Callback<EvaluationModelCallEndEvent>;
|
|
104
122
|
experimental_onEvaluateEnd?: Callback<EvaluateEndEvent>;
|
|
123
|
+
experimental_onStreamTranscriptionStart?: Callback<StreamTranscriptionStartEvent>;
|
|
124
|
+
experimental_onStreamTranscriptionEnd?: Callback<StreamTranscriptionEndEvent>;
|
|
105
125
|
onEnd?: Callback<OperationEndEvent>;
|
|
106
126
|
onAbort?: Callback<GenerateTextAbortEvent<ToolSet>>;
|
|
107
127
|
onError?: Callback<unknown>;
|
|
@@ -117,11 +137,12 @@ export interface Telemetry {
|
|
|
117
137
|
/**
|
|
118
138
|
* Called when an operation begins. Fired for text generation
|
|
119
139
|
* (generateText/streamText), object generation (generateObject/streamObject),
|
|
120
|
-
* embedding (embed/embedMany),
|
|
140
|
+
* embedding (embed/embedMany), reranking, speech generation, and
|
|
141
|
+
* non-streaming transcription operations.
|
|
121
142
|
*
|
|
122
143
|
* Use the `operationId` field to distinguish between operation types.
|
|
123
144
|
*/
|
|
124
|
-
onStart?:
|
|
145
|
+
onStart?: BivariantCallback<InferTelemetryEvent<OperationStartEvent>>;
|
|
125
146
|
|
|
126
147
|
/**
|
|
127
148
|
* Called when an individual step (single LLM invocation) begins.
|
|
@@ -248,14 +269,25 @@ export interface Telemetry {
|
|
|
248
269
|
/** Called when an experimental evaluation operation completes. */
|
|
249
270
|
experimental_onEvaluateEnd?: Callback<InferTelemetryEvent<EvaluateEndEvent>>;
|
|
250
271
|
|
|
272
|
+
/** Called when an experimental streaming transcription operation begins. */
|
|
273
|
+
experimental_onStreamTranscriptionStart?: Callback<
|
|
274
|
+
InferTelemetryEvent<StreamTranscriptionStartEvent>
|
|
275
|
+
>;
|
|
276
|
+
|
|
277
|
+
/** Called when an experimental streaming transcription operation completes. */
|
|
278
|
+
experimental_onStreamTranscriptionEnd?: Callback<
|
|
279
|
+
InferTelemetryEvent<StreamTranscriptionEndEvent>
|
|
280
|
+
>;
|
|
281
|
+
|
|
251
282
|
/**
|
|
252
283
|
* Called when an operation completes. Fired for text generation
|
|
253
284
|
* (generateText/streamText), object generation (generateObject/streamObject),
|
|
254
|
-
* embedding (embed/embedMany),
|
|
285
|
+
* embedding (embed/embedMany), reranking, speech generation, and
|
|
286
|
+
* non-streaming transcription operations.
|
|
255
287
|
*
|
|
256
288
|
* Use the event shape or `operationId` to distinguish between operation types.
|
|
257
289
|
*/
|
|
258
|
-
onEnd?:
|
|
290
|
+
onEnd?: BivariantCallback<InferTelemetryEvent<OperationEndEvent>>;
|
|
259
291
|
|
|
260
292
|
/**
|
|
261
293
|
* Called when a streaming text generation operation is aborted before it
|
package/src/transcribe/index.ts
CHANGED
|
@@ -8,6 +8,12 @@ export type {
|
|
|
8
8
|
TranscriptionStreamPart,
|
|
9
9
|
} from './stream-transcribe-result';
|
|
10
10
|
export type { TranscriptionResult } from './transcribe-result';
|
|
11
|
+
export type {
|
|
12
|
+
StreamTranscriptionEndEvent as Experimental_StreamTranscriptionEndEvent,
|
|
13
|
+
StreamTranscriptionStartEvent as Experimental_StreamTranscriptionStartEvent,
|
|
14
|
+
TranscriptionEndEvent,
|
|
15
|
+
TranscriptionStartEvent,
|
|
16
|
+
} from './transcription-events';
|
|
11
17
|
|
|
12
18
|
// deprecated exports
|
|
13
19
|
|
|
@@ -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
|
+
}
|