ai 6.0.233 → 6.0.235

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.
@@ -525,6 +525,34 @@ The `unknown` finish reason has been removed. It is now returned as `other`.
525
525
 
526
526
  ## AI SDK UI
527
527
 
528
+ ### Tool UI Part Approval States
529
+
530
+ AI SDK 6 adds `approval-requested`, `approval-responded`, and `output-denied`
531
+ to the tool UI part `state` union. Update exhaustive `switch` statements and
532
+ other state handling to cover the three approval states.
533
+
534
+ ```tsx filename="AI SDK 6"
535
+ switch (part.state) {
536
+ case 'input-streaming':
537
+ return 'Loading input';
538
+ case 'input-available':
539
+ return 'Input ready';
540
+ case 'approval-requested':
541
+ return 'Approval requested';
542
+ case 'approval-responded':
543
+ return 'Approval response received';
544
+ case 'output-available':
545
+ return 'Output ready';
546
+ case 'output-error':
547
+ return part.errorText;
548
+ case 'output-denied':
549
+ return 'Tool call denied';
550
+ }
551
+ ```
552
+
553
+ See [Tool execution approval](/docs/ai-sdk-ui/chatbot-tool-usage#tool-execution-approval)
554
+ for handling approval requests and responses in a chat UI.
555
+
528
556
  ### Tool UI Part Helper Functions Rename
529
557
 
530
558
  The tool UI part helper functions have been renamed to better reflect their purpose and to accommodate both static and dynamic tool parts ([PR #XXXX](https://github.com/vercel/ai/pull/XXXX)).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "6.0.233",
3
+ "version": "6.0.235",
4
4
  "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.",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@opentelemetry/api": "^1.9.0",
48
- "@ai-sdk/gateway": "3.0.155",
48
+ "@ai-sdk/gateway": "3.0.157",
49
49
  "@ai-sdk/provider": "3.0.14",
50
50
  "@ai-sdk/provider-utils": "4.0.40"
51
51
  },
@@ -56,7 +56,7 @@ export async function pipeAgentUIStreamToResponse<
56
56
  UIMessageStreamOptions<
57
57
  UIMessage<MESSAGE_METADATA, never, InferUITools<TOOLS>>
58
58
  >): Promise<void> {
59
- pipeUIMessageStreamToResponse({
59
+ return pipeUIMessageStreamToResponse({
60
60
  response,
61
61
  headers,
62
62
  status,
@@ -85,7 +85,10 @@ export interface StreamObjectResult<PARTIAL, RESULT, ELEMENT_STREAM> {
85
85
  * @param response A Node.js response-like object (ServerResponse).
86
86
  * @param init Optional headers, status code, and status text.
87
87
  */
88
- pipeTextStreamToResponse(response: ServerResponse, init?: ResponseInit): void;
88
+ pipeTextStreamToResponse(
89
+ response: ServerResponse,
90
+ init?: ResponseInit,
91
+ ): Promise<void>;
89
92
 
90
93
  /**
91
94
  * Creates a simple text stream response.
@@ -954,7 +954,7 @@ class DefaultStreamObjectResult<
954
954
  }
955
955
 
956
956
  pipeTextStreamToResponse(response: ServerResponse, init?: ResponseInit) {
957
- pipeTextStreamToResponse({
957
+ return pipeTextStreamToResponse({
958
958
  response,
959
959
  textStream: this.textStream,
960
960
  ...init,
@@ -342,7 +342,7 @@ export interface StreamTextResult<
342
342
  pipeUIMessageStreamToResponse<UI_MESSAGE extends UIMessage>(
343
343
  response: ServerResponse,
344
344
  options?: UIMessageStreamResponseInit & UIMessageStreamOptions<UI_MESSAGE>,
345
- ): void;
345
+ ): Promise<void>;
346
346
 
347
347
  /**
348
348
  * Writes text delta output to a Node.js response-like object.
@@ -352,7 +352,10 @@ export interface StreamTextResult<
352
352
  * @param response A Node.js response-like object (ServerResponse).
353
353
  * @param init Optional headers, status code, and status text.
354
354
  */
355
- pipeTextStreamToResponse(response: ServerResponse, init?: ResponseInit): void;
355
+ pipeTextStreamToResponse(
356
+ response: ServerResponse,
357
+ init?: ResponseInit,
358
+ ): Promise<void>;
356
359
 
357
360
  /**
358
361
  * Converts the result to a streamed response object with a stream data part stream.
@@ -2914,7 +2914,7 @@ class DefaultStreamTextResult<
2914
2914
  ...init
2915
2915
  }: UIMessageStreamResponseInit & UIMessageStreamOptions<UI_MESSAGE> = {},
2916
2916
  ) {
2917
- pipeUIMessageStreamToResponse({
2917
+ return pipeUIMessageStreamToResponse({
2918
2918
  response,
2919
2919
  stream: this.toUIMessageStream({
2920
2920
  originalMessages,
@@ -2932,7 +2932,7 @@ class DefaultStreamTextResult<
2932
2932
  }
2933
2933
 
2934
2934
  pipeTextStreamToResponse(response: ServerResponse, init?: ResponseInit) {
2935
- pipeTextStreamToResponse({
2935
+ return pipeTextStreamToResponse({
2936
2936
  response,
2937
2937
  textStream: this.textStream,
2938
2938
  ...init,
@@ -13,6 +13,7 @@ import { writeToServerResponse } from '../util/write-to-server-response';
13
13
  * @param options.statusText - Optional HTTP status text.
14
14
  * @param options.headers - Optional response headers.
15
15
  * @param options.textStream - The text stream to pipe.
16
+ * @returns A promise that resolves when the stream has been written.
16
17
  */
17
18
  export function pipeTextStreamToResponse({
18
19
  response,
@@ -23,8 +24,8 @@ export function pipeTextStreamToResponse({
23
24
  }: {
24
25
  response: ServerResponse;
25
26
  textStream: ReadableStream<string>;
26
- } & ResponseInit): void {
27
- writeToServerResponse({
27
+ } & ResponseInit): Promise<void> {
28
+ return writeToServerResponse({
28
29
  response,
29
30
  status,
30
31
  statusText,
@@ -16,6 +16,7 @@ import type { UIMessageStreamResponseInit } from './ui-message-stream-response-i
16
16
  * @param options.headers - Additional HTTP headers to include in the response.
17
17
  * @param options.stream - The UI message chunk stream to send.
18
18
  * @param options.consumeSseStream - Optional callback to consume a copy of the SSE stream independently.
19
+ * @returns A promise that resolves when the stream has been written.
19
20
  */
20
21
  export function pipeUIMessageStreamToResponse({
21
22
  response,
@@ -27,7 +28,7 @@ export function pipeUIMessageStreamToResponse({
27
28
  }: {
28
29
  response: ServerResponse;
29
30
  stream: ReadableStream<UIMessageChunk>;
30
- } & UIMessageStreamResponseInit): void {
31
+ } & UIMessageStreamResponseInit): Promise<void> {
31
32
  let sseStream = stream.pipeThrough(new JsonToSseTransformStream());
32
33
 
33
34
  // when the consumeSseStream is provided, we need to tee the stream
@@ -39,7 +40,7 @@ export function pipeUIMessageStreamToResponse({
39
40
  consumeSseStream({ stream: stream2 }); // no await (do not block the response)
40
41
  }
41
42
 
42
- writeToServerResponse({
43
+ return writeToServerResponse({
43
44
  response,
44
45
  status,
45
46
  statusText,
@@ -168,31 +168,54 @@ export const videoMediaTypeSignatures = [
168
168
  },
169
169
  ] as const;
170
170
 
171
- const stripID3 = (data: Uint8Array | string) => {
172
- const bytes =
173
- typeof data === 'string' ? convertBase64ToUint8Array(data) : data;
171
+ const DEFAULT_SNIFF_BYTES = 18;
172
+
173
+ // Longest signature prefix in the tables above (e.g. image/avif = 12 bytes).
174
+ const MAX_SIGNATURE_BYTES = 12;
175
+
176
+ // Largest ID3v2 tag (10-byte header + body) skipped to reach the audio frame.
177
+ // Covers typical tags including embedded cover art while keeping the decode
178
+ // bounded and O(1) in the attachment size. Exported for boundary tests.
179
+ export const MAX_ID3_TAG_BYTES = 128 * 1024;
180
+
181
+ // Total prefix decoded when an ID3 tag is present: the tag plus room for the
182
+ // trailing signature, so a tag right at the size limit stays detectable.
183
+ const ID3_SCAN_BYTES = MAX_ID3_TAG_BYTES + MAX_SIGNATURE_BYTES;
184
+
185
+ // Decode/view exactly the first `maxBytes` bytes from the front of the input.
186
+ // The base64 and raw-byte paths yield the same length, so detection does not
187
+ // depend on the input's representation.
188
+ function decodePrefix(data: Uint8Array | string, maxBytes: number): Uint8Array {
189
+ if (typeof data !== 'string') {
190
+ return data.length > maxBytes ? data.subarray(0, maxBytes) : data;
191
+ }
192
+ // base64: 4 chars -> 3 bytes. Decode whole 4-char groups, then trim the 0-2
193
+ // extra bytes so the result matches the raw-byte path exactly.
194
+ const maxChars = Math.ceil(maxBytes / 3) * 4;
195
+ const bytes = convertBase64ToUint8Array(
196
+ data.substring(0, Math.min(data.length, maxChars)),
197
+ );
198
+ return bytes.length > maxBytes ? bytes.subarray(0, maxBytes) : bytes;
199
+ }
200
+
201
+ function hasID3(bytes: Uint8Array): boolean {
202
+ return (
203
+ bytes.length > 10 &&
204
+ bytes[0] === 0x49 && // 'I'
205
+ bytes[1] === 0x44 && // 'D'
206
+ bytes[2] === 0x33 // '3'
207
+ );
208
+ }
209
+
210
+ const stripID3 = (bytes: Uint8Array): Uint8Array => {
174
211
  const id3Size =
175
212
  ((bytes[6] & 0x7f) << 21) |
176
213
  ((bytes[7] & 0x7f) << 14) |
177
214
  ((bytes[8] & 0x7f) << 7) |
178
215
  (bytes[9] & 0x7f);
179
-
180
- // The raw MP3 starts here
181
- return bytes.slice(id3Size + 10);
216
+ return bytes.subarray(id3Size + 10);
182
217
  };
183
218
 
184
- function stripID3TagsIfPresent(data: Uint8Array | string): Uint8Array | string {
185
- const hasId3 =
186
- (typeof data === 'string' && data.startsWith('SUQz')) ||
187
- (typeof data !== 'string' &&
188
- data.length > 10 &&
189
- data[0] === 0x49 && // 'I'
190
- data[1] === 0x44 && // 'D'
191
- data[2] === 0x33); // '3'
192
-
193
- return hasId3 ? stripID3(data) : data;
194
- }
195
-
196
219
  /**
197
220
  * Detect the media IANA media type of a file using a list of signatures.
198
221
  *
@@ -210,15 +233,13 @@ export function detectMediaType({
210
233
  | typeof imageMediaTypeSignatures
211
234
  | typeof videoMediaTypeSignatures;
212
235
  }): (typeof signatures)[number]['mediaType'] | undefined {
213
- const processedData = stripID3TagsIfPresent(data);
236
+ let bytes = decodePrefix(data, DEFAULT_SNIFF_BYTES);
214
237
 
215
- // Convert the first ~18 bytes (24 base64 chars) for consistent detection logic:
216
- const bytes =
217
- typeof processedData === 'string'
218
- ? convertBase64ToUint8Array(
219
- processedData.substring(0, Math.min(processedData.length, 24)),
220
- )
221
- : processedData;
238
+ // ID3v2-tagged MP3s carry the audio frame after the tag; scan a bounded
239
+ // prefix past it rather than decoding the whole input.
240
+ if (hasID3(bytes)) {
241
+ bytes = stripID3(decodePrefix(data, ID3_SCAN_BYTES));
242
+ }
222
243
 
223
244
  for (const signature of signatures) {
224
245
  if (
@@ -15,7 +15,7 @@ export function writeToServerResponse({
15
15
  statusText?: string;
16
16
  headers?: Record<string, string | number | string[]>;
17
17
  stream: ReadableStream<Uint8Array>;
18
- }): void {
18
+ }): Promise<void> {
19
19
  const statusCode = status ?? 200;
20
20
  if (statusText !== undefined) {
21
21
  response.writeHead(statusCode, statusText, headers);
@@ -45,5 +45,5 @@ export function writeToServerResponse({
45
45
  }
46
46
  };
47
47
 
48
- read();
48
+ return read();
49
49
  }