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
|
@@ -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 {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { JSONObject, SharedV4AudioFormat } from '@ai-sdk/provider';
|
|
2
|
+
import type { ProviderOptions } from '@ai-sdk/provider-utils';
|
|
3
|
+
import type { ProviderMetadata } from '../types';
|
|
4
|
+
import type { TranscriptionModelResponseMetadata } from '../types/transcription-model-response-metadata';
|
|
5
|
+
import type { Warning } from '../types/warning';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Event passed to telemetry integrations when transcription begins.
|
|
9
|
+
*/
|
|
10
|
+
type TranscriptionStartEventFor<OPERATION_ID extends string> = {
|
|
11
|
+
/** Unique identifier for this transcription call. */
|
|
12
|
+
readonly callId: string;
|
|
13
|
+
|
|
14
|
+
/** Identifies the operation type (`ai.transcribe` or `ai.streamTranscribe`). */
|
|
15
|
+
readonly operationId: OPERATION_ID;
|
|
16
|
+
|
|
17
|
+
/** The provider identifier. */
|
|
18
|
+
readonly provider: string;
|
|
19
|
+
|
|
20
|
+
/** The specific model identifier. */
|
|
21
|
+
readonly modelId: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Metadata about the input audio. The byte length is known immediately for
|
|
25
|
+
* non-streaming transcription and is reported on the end event for streams.
|
|
26
|
+
*/
|
|
27
|
+
readonly audio: {
|
|
28
|
+
readonly byteLength: number | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The media type, when it is known before input preparation completes.
|
|
31
|
+
* URL downloads and invalid inline inputs can begin without this metadata.
|
|
32
|
+
*/
|
|
33
|
+
readonly mediaType: string | undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** The raw input format for streaming transcription. */
|
|
37
|
+
readonly inputAudioFormat: SharedV4AudioFormat | undefined;
|
|
38
|
+
|
|
39
|
+
/** Maximum number of retries for non-streaming transcription. */
|
|
40
|
+
readonly maxRetries: number | undefined;
|
|
41
|
+
|
|
42
|
+
/** Additional HTTP or WebSocket headers sent with the request. */
|
|
43
|
+
readonly headers: Record<string, string> | undefined;
|
|
44
|
+
|
|
45
|
+
/** Additional provider-specific options. */
|
|
46
|
+
readonly providerOptions: ProviderOptions;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type TranscriptionStartEvent =
|
|
50
|
+
TranscriptionStartEventFor<'ai.transcribe'>;
|
|
51
|
+
|
|
52
|
+
export type StreamTranscriptionStartEvent =
|
|
53
|
+
TranscriptionStartEventFor<'ai.streamTranscribe'>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Event passed to telemetry integrations when transcription completes.
|
|
57
|
+
*
|
|
58
|
+
* Raw audio is intentionally not included.
|
|
59
|
+
*/
|
|
60
|
+
type TranscriptionEndEventFor<OPERATION_ID extends string> = {
|
|
61
|
+
/** Unique identifier for this transcription call. */
|
|
62
|
+
readonly callId: string;
|
|
63
|
+
|
|
64
|
+
/** Identifies the operation type (`ai.transcribe` or `ai.streamTranscribe`). */
|
|
65
|
+
readonly operationId: OPERATION_ID;
|
|
66
|
+
|
|
67
|
+
/** The provider identifier. */
|
|
68
|
+
readonly provider: string;
|
|
69
|
+
|
|
70
|
+
/** The specific model identifier. */
|
|
71
|
+
readonly modelId: string;
|
|
72
|
+
|
|
73
|
+
/** Metadata about the input audio. */
|
|
74
|
+
readonly audio: {
|
|
75
|
+
readonly byteLength: number;
|
|
76
|
+
readonly mediaType: string;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** The complete transcript. */
|
|
80
|
+
readonly text: string;
|
|
81
|
+
|
|
82
|
+
/** Transcript segments with timing information. */
|
|
83
|
+
readonly segments: Array<{
|
|
84
|
+
readonly text: string;
|
|
85
|
+
readonly startSecond: number;
|
|
86
|
+
readonly endSecond: number;
|
|
87
|
+
}>;
|
|
88
|
+
|
|
89
|
+
/** The detected language. */
|
|
90
|
+
readonly language: string | undefined;
|
|
91
|
+
|
|
92
|
+
/** The duration of the input audio, when available. */
|
|
93
|
+
readonly durationInSeconds: number | undefined;
|
|
94
|
+
|
|
95
|
+
/** Usage reported by the provider, when available. */
|
|
96
|
+
readonly usage: JSONObject | undefined;
|
|
97
|
+
|
|
98
|
+
/** Warnings from the transcription model. */
|
|
99
|
+
readonly warnings: Array<Warning>;
|
|
100
|
+
|
|
101
|
+
/** Optional provider-specific metadata. */
|
|
102
|
+
readonly providerMetadata: ProviderMetadata | undefined;
|
|
103
|
+
|
|
104
|
+
/** Response metadata from the provider. */
|
|
105
|
+
readonly response: TranscriptionModelResponseMetadata;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type TranscriptionEndEvent = TranscriptionEndEventFor<'ai.transcribe'>;
|
|
109
|
+
|
|
110
|
+
export type StreamTranscriptionEndEvent =
|
|
111
|
+
TranscriptionEndEventFor<'ai.streamTranscribe'>;
|
|
@@ -59,16 +59,28 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
59
59
|
|
|
60
60
|
warnIfUIMessageHasDeprecatedRawInput(messages);
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
// A later user message supersedes unresolved approval requests. Keeping
|
|
63
|
+
// those requests would leave unmatched tool calls at the user boundary.
|
|
64
|
+
const lastUserMessageIndex = messages.reduce(
|
|
65
|
+
(lastIndex, message, index) =>
|
|
66
|
+
message.role === 'user' ? index : lastIndex,
|
|
67
|
+
-1,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (options?.ignoreIncompleteToolCalls || lastUserMessageIndex > 0) {
|
|
71
|
+
messages = messages.map((message, messageIndex) => ({
|
|
64
72
|
...message,
|
|
65
73
|
parts: message.parts.filter(
|
|
66
74
|
part =>
|
|
67
75
|
!isToolUIPart(part) ||
|
|
68
|
-
part.state
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
((part.state !== 'approval-requested' ||
|
|
77
|
+
messageIndex >= lastUserMessageIndex) &&
|
|
78
|
+
(!options?.ignoreIncompleteToolCalls ||
|
|
79
|
+
part.state === 'approval-responded' ||
|
|
80
|
+
(part.state === 'output-available' &&
|
|
81
|
+
part.preliminary !== true) ||
|
|
82
|
+
part.state === 'output-error' ||
|
|
83
|
+
part.state === 'output-denied')),
|
|
72
84
|
),
|
|
73
85
|
}));
|
|
74
86
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const STREAM_OPEN_COMMENT = ': stream-open\n\n';
|
|
2
|
+
const KEEP_ALIVE_COMMENT = ': keep-alive\n\n';
|
|
3
|
+
|
|
4
|
+
export function createSseStreamWithKeepAlive({
|
|
5
|
+
stream,
|
|
6
|
+
keepAliveMs,
|
|
7
|
+
}: {
|
|
8
|
+
stream: ReadableStream<string>;
|
|
9
|
+
keepAliveMs: number | undefined;
|
|
10
|
+
}): ReadableStream<string> {
|
|
11
|
+
if (keepAliveMs == null) {
|
|
12
|
+
return stream;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
!Number.isFinite(keepAliveMs) ||
|
|
17
|
+
keepAliveMs <= 0 ||
|
|
18
|
+
keepAliveMs > 2_147_483_647
|
|
19
|
+
) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'keepAliveMs must be a positive finite timer duration no greater than 2147483647',
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const reader = stream.getReader();
|
|
26
|
+
let keepAliveTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
27
|
+
let isCancelled = false;
|
|
28
|
+
|
|
29
|
+
const clearKeepAliveTimeout = () => {
|
|
30
|
+
clearTimeout(keepAliveTimeout);
|
|
31
|
+
keepAliveTimeout = undefined;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const scheduleKeepAlive = (
|
|
35
|
+
controller: ReadableStreamDefaultController<string>,
|
|
36
|
+
) => {
|
|
37
|
+
clearKeepAliveTimeout();
|
|
38
|
+
keepAliveTimeout = setTimeout(() => {
|
|
39
|
+
if (isCancelled) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (controller.desiredSize != null && controller.desiredSize > 0) {
|
|
44
|
+
controller.enqueue(KEEP_ALIVE_COMMENT);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
scheduleKeepAlive(controller);
|
|
48
|
+
}, keepAliveMs);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return new ReadableStream<string>({
|
|
52
|
+
start(controller) {
|
|
53
|
+
controller.enqueue(STREAM_OPEN_COMMENT);
|
|
54
|
+
scheduleKeepAlive(controller);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
pull(controller) {
|
|
58
|
+
return reader.read().then(
|
|
59
|
+
result => {
|
|
60
|
+
clearKeepAliveTimeout();
|
|
61
|
+
|
|
62
|
+
if (isCancelled) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (result.done) {
|
|
67
|
+
controller.close();
|
|
68
|
+
} else {
|
|
69
|
+
controller.enqueue(result.value);
|
|
70
|
+
scheduleKeepAlive(controller);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
error => {
|
|
74
|
+
clearKeepAliveTimeout();
|
|
75
|
+
|
|
76
|
+
if (!isCancelled) {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async cancel(reason) {
|
|
84
|
+
isCancelled = true;
|
|
85
|
+
clearKeepAliveTimeout();
|
|
86
|
+
await reader.cancel(reason);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { prepareHeaders } from '../util/prepare-headers';
|
|
2
|
+
import { createSseStreamWithKeepAlive } from './create-sse-stream-with-keep-alive';
|
|
2
3
|
import { JsonToSseTransformStream } from './json-to-sse-transform-stream';
|
|
3
4
|
import { UI_MESSAGE_STREAM_HEADERS } from './ui-message-stream-headers';
|
|
4
5
|
import type { UIMessageChunk } from './ui-message-chunks';
|
|
@@ -12,6 +13,7 @@ import type { UIMessageStreamResponseInit } from './ui-message-stream-response-i
|
|
|
12
13
|
* @param options.statusText - The HTTP status text for the response.
|
|
13
14
|
* @param options.headers - Additional HTTP headers to include in the response.
|
|
14
15
|
* @param options.stream - The UI message chunk stream to send.
|
|
16
|
+
* @param options.keepAliveMs - Optional interval for sending SSE keep-alive comments.
|
|
15
17
|
* @param options.consumeSseStream - Optional callback to consume a copy of the SSE stream independently.
|
|
16
18
|
*
|
|
17
19
|
* @returns A `Response` object with the UI message stream as the body.
|
|
@@ -21,11 +23,15 @@ export function createUIMessageStreamResponse({
|
|
|
21
23
|
statusText,
|
|
22
24
|
headers,
|
|
23
25
|
stream,
|
|
26
|
+
keepAliveMs,
|
|
24
27
|
consumeSseStream,
|
|
25
28
|
}: UIMessageStreamResponseInit & {
|
|
26
29
|
stream: ReadableStream<UIMessageChunk>;
|
|
27
30
|
}): Response {
|
|
28
|
-
let sseStream =
|
|
31
|
+
let sseStream = createSseStreamWithKeepAlive({
|
|
32
|
+
stream: stream.pipeThrough(new JsonToSseTransformStream()),
|
|
33
|
+
keepAliveMs,
|
|
34
|
+
});
|
|
29
35
|
|
|
30
36
|
// when the consumeSseStream is provided, we need to tee the stream
|
|
31
37
|
// and send the second part to the consumeSseStream function
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ServerResponse } from 'node:http';
|
|
2
2
|
import { prepareHeaders } from '../util/prepare-headers';
|
|
3
3
|
import { writeToServerResponse } from '../util/write-to-server-response';
|
|
4
|
+
import { createSseStreamWithKeepAlive } from './create-sse-stream-with-keep-alive';
|
|
4
5
|
import { JsonToSseTransformStream } from './json-to-sse-transform-stream';
|
|
5
6
|
import { UI_MESSAGE_STREAM_HEADERS } from './ui-message-stream-headers';
|
|
6
7
|
import type { UIMessageChunk } from './ui-message-chunks';
|
|
@@ -15,6 +16,7 @@ import type { UIMessageStreamResponseInit } from './ui-message-stream-response-i
|
|
|
15
16
|
* @param options.statusText - The HTTP status text for the response.
|
|
16
17
|
* @param options.headers - Additional HTTP headers to include in the response.
|
|
17
18
|
* @param options.stream - The UI message chunk stream to send.
|
|
19
|
+
* @param options.keepAliveMs - Optional interval for sending SSE keep-alive comments.
|
|
18
20
|
* @param options.consumeSseStream - Optional callback to consume a copy of the SSE stream independently.
|
|
19
21
|
* @returns A promise that resolves when the stream has been written.
|
|
20
22
|
*/
|
|
@@ -24,12 +26,16 @@ export function pipeUIMessageStreamToResponse({
|
|
|
24
26
|
statusText,
|
|
25
27
|
headers,
|
|
26
28
|
stream,
|
|
29
|
+
keepAliveMs,
|
|
27
30
|
consumeSseStream,
|
|
28
31
|
}: {
|
|
29
32
|
response: ServerResponse;
|
|
30
33
|
stream: ReadableStream<UIMessageChunk>;
|
|
31
34
|
} & UIMessageStreamResponseInit): Promise<void> {
|
|
32
|
-
let sseStream =
|
|
35
|
+
let sseStream = createSseStreamWithKeepAlive({
|
|
36
|
+
stream: stream.pipeThrough(new JsonToSseTransformStream()),
|
|
37
|
+
keepAliveMs,
|
|
38
|
+
});
|
|
33
39
|
|
|
34
40
|
// when the consumeSseStream is provided, we need to tee the stream
|
|
35
41
|
// and send the second part to the consumeSseStream function
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
* Extends the standard `ResponseInit` with additional streaming options.
|
|
4
4
|
*/
|
|
5
5
|
export type UIMessageStreamResponseInit = ResponseInit & {
|
|
6
|
+
/**
|
|
7
|
+
* Optional interval in milliseconds for sending SSE keep-alive comments.
|
|
8
|
+
* When set, an opening comment is sent immediately and additional comments
|
|
9
|
+
* are sent after the stream has been idle for the configured interval.
|
|
10
|
+
*/
|
|
11
|
+
keepAliveMs?: number;
|
|
12
|
+
|
|
6
13
|
/**
|
|
7
14
|
* Optional callback to consume a copy of the SSE stream independently.
|
|
8
15
|
* This is useful for logging, debugging, or processing the stream in parallel.
|