ai 7.0.26 → 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.
@@ -85,7 +85,7 @@ import {
85
85
  } from "@ai-sdk/provider-utils";
86
86
 
87
87
  // src/version.ts
88
- var VERSION = true ? "7.0.26" : "0.0.0-test";
88
+ var VERSION = true ? "7.0.27" : "0.0.0-test";
89
89
 
90
90
  // src/util/download/download.ts
91
91
  var download = async ({
@@ -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.26",
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.19",
45
+ "@ai-sdk/gateway": "4.0.20",
46
46
  "@ai-sdk/provider": "4.0.3",
47
- "@ai-sdk/provider-utils": "5.0.9"
47
+ "@ai-sdk/provider-utils": "5.0.10"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@edge-runtime/vm": "^5.0.0",
@@ -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
- // `transform.readable` is fresh and exclusively owned here, so attach the
311
- // async iterator in place rather than piping through another transform.
312
- // The extra transform (as `createAsyncIterableStream` would add) chains two
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.object({
34
- id: z.string(),
35
- role: z.enum(['system', 'user', 'assistant']),
36
- metadata: z.unknown().optional(),
37
- parts: z
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
- .nonempty('Message must contain at least one part'),
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
  ),