ai 7.0.25 → 7.0.27
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 +17 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +50 -10
- package/dist/index.js.map +1 -1
- package/dist/internal/index.js +1 -1
- package/docs/02-getting-started/09-coding-agents.mdx +1 -1
- package/docs/03-ai-sdk-core/36-transcription.mdx +6 -0
- package/docs/03-ai-sdk-core/65-devtools.mdx +6 -2
- package/docs/07-reference/01-ai-sdk-core/11-stream-transcribe.mdx +1 -1
- package/package.json +3 -3
- package/src/generate-text/stream-text.ts +14 -2
- package/src/transcribe/stream-transcribe-result.ts +5 -0
- package/src/transcribe/stream-transcribe.ts +46 -7
- package/src/ui/validate-ui-messages.ts +21 -9
package/dist/internal/index.js
CHANGED
|
@@ -84,7 +84,7 @@ const result = await generateText({
|
|
|
84
84
|
Start the DevTools viewer in a separate terminal:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
npx @ai-sdk/devtools
|
|
87
|
+
npx @ai-sdk/devtools@latest
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
Open [http://localhost:4983](http://localhost:4983) to inspect your AI SDK interactions in real time.
|
|
@@ -71,6 +71,12 @@ for await (const part of result.fullStream) {
|
|
|
71
71
|
console.log(await result.text);
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
`fullStream` is a single-consumer live stream and can only be accessed once.
|
|
75
|
+
When you need both stream parts and final results, access `fullStream` first and
|
|
76
|
+
await the result promises while or after consuming it. Accessing a result
|
|
77
|
+
promise first consumes the stream internally, so `fullStream` is no longer
|
|
78
|
+
available. This avoids retaining an unbounded replay buffer for live audio.
|
|
79
|
+
|
|
74
80
|
To access the final transcript metadata:
|
|
75
81
|
|
|
76
82
|
```ts
|
|
@@ -74,7 +74,7 @@ const result = streamText({
|
|
|
74
74
|
Start the DevTools viewer:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
npx @ai-sdk/devtools
|
|
77
|
+
npx @ai-sdk/devtools@latest
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
Open [http://localhost:4983](http://localhost:4983) to view your AI SDK interactions.
|
|
@@ -87,9 +87,13 @@ For example, if your API is in `apps/api`, run:
|
|
|
87
87
|
|
|
88
88
|
```bash
|
|
89
89
|
cd apps/api
|
|
90
|
-
npx @ai-sdk/devtools
|
|
90
|
+
npx @ai-sdk/devtools@latest
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
The explicit `@latest` tag ensures that `npx` installs an executable copy
|
|
94
|
+
instead of selecting a transitive dependency whose binary is not linked into
|
|
95
|
+
the workspace.
|
|
96
|
+
|
|
93
97
|
## Captured data
|
|
94
98
|
|
|
95
99
|
DevTools captures the following information from your AI SDK calls:
|
|
@@ -99,7 +99,7 @@ console.log(await result.text);
|
|
|
99
99
|
name: 'fullStream',
|
|
100
100
|
type: 'AsyncIterableStream<TranscriptionStreamPart>',
|
|
101
101
|
description:
|
|
102
|
-
'A stream of transcription parts: `transcript-delta`, `transcript-partial`, `transcript-final`, `raw`, and `error`.',
|
|
102
|
+
'A single-consumer live stream of transcription parts: `transcript-delta`, `transcript-partial`, `transcript-final`, `raw`, and `error`. Access it once, before any result promise, when both stream parts and final results are needed. Accessing a result promise first consumes the stream internally and makes `fullStream` unavailable.',
|
|
103
103
|
},
|
|
104
104
|
{
|
|
105
105
|
name: 'text',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.27",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI SDK by Vercel - build apps like ChatGPT, Claude, Gemini, and more with a single interface for any model using the Vercel AI Gateway or go direct to OpenAI, Anthropic, Google, or any other model provider.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.20",
|
|
46
46
|
"@ai-sdk/provider": "4.0.3",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.10"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@edge-runtime/vm": "^5.0.0",
|
|
@@ -1589,6 +1589,19 @@ class DefaultStreamTextResult<
|
|
|
1589
1589
|
// Re-enter the streamText tracing context after stream setup returns.
|
|
1590
1590
|
const runInStreamTextTracingChannelContext = <T>(execute: () => T): T =>
|
|
1591
1591
|
streamTextTracingChannelContext?.run(execute) ?? execute();
|
|
1592
|
+
const runInTracingChannelSpanInStreamText =
|
|
1593
|
+
telemetryDispatcher.runInTracingChannelSpan == null
|
|
1594
|
+
? undefined
|
|
1595
|
+
: <T>(
|
|
1596
|
+
options: Parameters<
|
|
1597
|
+
NonNullable<TelemetryDispatcher['runInTracingChannelSpan']>
|
|
1598
|
+
>[0] & {
|
|
1599
|
+
execute: () => PromiseLike<T>;
|
|
1600
|
+
},
|
|
1601
|
+
) =>
|
|
1602
|
+
runInStreamTextTracingChannelContext(() =>
|
|
1603
|
+
telemetryDispatcher.runInTracingChannelSpan!(options),
|
|
1604
|
+
);
|
|
1592
1605
|
|
|
1593
1606
|
await notify({
|
|
1594
1607
|
event: startEvent,
|
|
@@ -1676,8 +1689,7 @@ class DefaultStreamTextResult<
|
|
|
1676
1689
|
telemetryDispatcher.onToolExecutionEnd,
|
|
1677
1690
|
),
|
|
1678
1691
|
executeToolInTelemetryContext: telemetryDispatcher.executeTool,
|
|
1679
|
-
runInTracingChannelSpan:
|
|
1680
|
-
telemetryDispatcher.runInTracingChannelSpan,
|
|
1692
|
+
runInTracingChannelSpan: runInTracingChannelSpanInStreamText,
|
|
1681
1693
|
onPreliminaryToolResult: result => {
|
|
1682
1694
|
toolExecutionStepStreamController?.enqueue(result);
|
|
1683
1695
|
},
|
|
@@ -81,6 +81,11 @@ export interface StreamTranscriptionResult {
|
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
83
|
* Full stream of transcription parts.
|
|
84
|
+
*
|
|
85
|
+
* This is a single-consumer live stream and can only be accessed once.
|
|
86
|
+
* Access it before any result promise when both stream parts and final
|
|
87
|
+
* results are needed; accessing a result promise first consumes the stream
|
|
88
|
+
* internally and makes `fullStream` unavailable.
|
|
84
89
|
*/
|
|
85
90
|
readonly fullStream: AsyncIterableStream<TranscriptionStreamPart>;
|
|
86
91
|
}
|
|
@@ -285,34 +285,73 @@ export function streamTranscribe({
|
|
|
285
285
|
});
|
|
286
286
|
});
|
|
287
287
|
|
|
288
|
+
// Transcription streams can be unbounded (live microphone + raw chunks), so
|
|
289
|
+
// unlike streamText we cannot retain an unread tee branch for replay. The
|
|
290
|
+
// output stream has one owner: either fullStream, or the first result promise
|
|
291
|
+
// getter which claims and drains it internally.
|
|
292
|
+
let streamOwner: 'unclaimed' | 'full-stream' | 'result-promises' =
|
|
293
|
+
'unclaimed';
|
|
294
|
+
|
|
295
|
+
function consumeStream() {
|
|
296
|
+
if (streamOwner === 'full-stream' || streamOwner === 'result-promises') {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
streamOwner = 'result-promises';
|
|
300
|
+
const reader = transform.readable.getReader();
|
|
301
|
+
void (async () => {
|
|
302
|
+
while (!(await reader.read()).done) {
|
|
303
|
+
// drain; results surface via the promises
|
|
304
|
+
}
|
|
305
|
+
})().catch(() => {
|
|
306
|
+
// stream errors reject the promises via the pipe handler above
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function getFullStream() {
|
|
311
|
+
if (streamOwner !== 'unclaimed') {
|
|
312
|
+
throw new Error(
|
|
313
|
+
streamOwner === 'full-stream'
|
|
314
|
+
? 'fullStream can only be accessed once.'
|
|
315
|
+
: 'fullStream cannot be accessed after a result promise.',
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
streamOwner = 'full-stream';
|
|
319
|
+
// Direct ownership preserves backpressure and cancellation: cancelling
|
|
320
|
+
// this stream reaches Transformer.cancel and aborts the model pipe.
|
|
321
|
+
return asAsyncIterableStream(transform.readable);
|
|
322
|
+
}
|
|
323
|
+
|
|
288
324
|
return {
|
|
289
325
|
get text() {
|
|
326
|
+
consumeStream();
|
|
290
327
|
return textPromise.promise;
|
|
291
328
|
},
|
|
292
329
|
get segments() {
|
|
330
|
+
consumeStream();
|
|
293
331
|
return segmentsPromise.promise;
|
|
294
332
|
},
|
|
295
333
|
get language() {
|
|
334
|
+
consumeStream();
|
|
296
335
|
return languagePromise.promise;
|
|
297
336
|
},
|
|
298
337
|
get durationInSeconds() {
|
|
338
|
+
consumeStream();
|
|
299
339
|
return durationInSecondsPromise.promise;
|
|
300
340
|
},
|
|
301
341
|
get warnings() {
|
|
342
|
+
consumeStream();
|
|
302
343
|
return warningsPromise.promise;
|
|
303
344
|
},
|
|
304
345
|
get responses() {
|
|
346
|
+
consumeStream();
|
|
305
347
|
return responsesPromise.promise;
|
|
306
348
|
},
|
|
307
349
|
get providerMetadata() {
|
|
350
|
+
consumeStream();
|
|
308
351
|
return providerMetadataPromise.promise;
|
|
309
352
|
},
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
// transforms fed by the active model pipe below and surfaces a spurious
|
|
314
|
-
// unhandled `undefined` rejection when the consumer cancels early on
|
|
315
|
-
// Node.js 26.
|
|
316
|
-
fullStream: asAsyncIterableStream(transform.readable),
|
|
353
|
+
get fullStream() {
|
|
354
|
+
return getFullStream();
|
|
355
|
+
},
|
|
317
356
|
};
|
|
318
357
|
}
|
|
@@ -30,12 +30,12 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
30
30
|
zodSchema(
|
|
31
31
|
z
|
|
32
32
|
.array(
|
|
33
|
-
z
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.array(
|
|
33
|
+
z
|
|
34
|
+
.object({
|
|
35
|
+
id: z.string(),
|
|
36
|
+
role: z.enum(['system', 'user', 'assistant']),
|
|
37
|
+
metadata: z.unknown().optional(),
|
|
38
|
+
parts: z.array(
|
|
39
39
|
z.union([
|
|
40
40
|
z.object({
|
|
41
41
|
type: z.literal('text'),
|
|
@@ -343,9 +343,21 @@ const uiMessagesSchema = lazySchema(() =>
|
|
|
343
343
|
}),
|
|
344
344
|
}),
|
|
345
345
|
]),
|
|
346
|
-
)
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
),
|
|
347
|
+
})
|
|
348
|
+
.superRefine((message, context) => {
|
|
349
|
+
if (message.role !== 'assistant' && message.parts.length === 0) {
|
|
350
|
+
context.addIssue({
|
|
351
|
+
origin: 'array',
|
|
352
|
+
code: 'too_small',
|
|
353
|
+
minimum: 1,
|
|
354
|
+
inclusive: true,
|
|
355
|
+
input: message.parts,
|
|
356
|
+
path: ['parts'],
|
|
357
|
+
message: 'Message must contain at least one part',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}),
|
|
349
361
|
)
|
|
350
362
|
.nonempty('Messages array must not be empty'),
|
|
351
363
|
),
|