ai 7.0.34 → 7.0.35

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.
@@ -5,7 +5,8 @@ import type { ToolSet } from '@ai-sdk/provider-utils';
5
5
  * - A number representing milliseconds
6
6
  * - An object with `totalMs` property for the total timeout in milliseconds
7
7
  * - An object with `stepMs` property for the timeout of each step in milliseconds
8
- * - An object with `chunkMs` property for the timeout between stream chunks (streaming only)
8
+ * - An object with `firstChunkMs` property for the timeout until the first content chunk of each step (streaming only)
9
+ * - An object with `chunkMs` property for the timeout between content chunks (streaming only)
9
10
  * - An object with `toolMs` property for the default timeout for all tool executions
10
11
  * - An object with `tools` property for per-tool timeout overrides using `{toolName}Ms` keys
11
12
  */
@@ -14,6 +15,7 @@ export type TimeoutConfiguration<TOOLS extends ToolSet> =
14
15
  | {
15
16
  totalMs?: number;
16
17
  stepMs?: number;
18
+ firstChunkMs?: number;
17
19
  chunkMs?: number;
18
20
  toolMs?: number;
19
21
  tools?: Partial<Record<`${keyof TOOLS & string}Ms`, number>>;
@@ -52,9 +54,25 @@ export function getStepTimeoutMs(
52
54
  return timeout.stepMs;
53
55
  }
54
56
 
57
+ /**
58
+ * Extracts the first chunk timeout value in milliseconds from a TimeoutConfiguration.
59
+ * This timeout is for streaming only - it aborts if no content chunk is received within the specified duration.
60
+ *
61
+ * @param timeout - The timeout configuration.
62
+ * @returns The first chunk timeout in milliseconds, or undefined if no first chunk timeout is configured.
63
+ */
64
+ export function getFirstChunkTimeoutMs(
65
+ timeout: TimeoutConfiguration<any> | undefined,
66
+ ): number | undefined {
67
+ if (timeout == null || typeof timeout === 'number') {
68
+ return undefined;
69
+ }
70
+ return timeout.firstChunkMs;
71
+ }
72
+
55
73
  /**
56
74
  * Extracts the chunk timeout value in milliseconds from a TimeoutConfiguration.
57
- * This timeout is for streaming only - it aborts if no new chunk is received within the specified duration.
75
+ * This timeout is for streaming only - it aborts if no new content chunk is received within the specified duration.
58
76
  *
59
77
  * @param timeout - The timeout configuration.
60
78
  * @returns The chunk timeout in milliseconds, or undefined if no chunk timeout is configured.
@@ -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.stream - 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
  stream: 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,
@@ -8,11 +8,21 @@ import { createResolvablePromise } from './create-resolvable-promise';
8
8
  */
9
9
  export function createStitchableStream<T>(): {
10
10
  stream: ReadableStream<T>;
11
- addStream: (innerStream: ReadableStream<T>) => void;
11
+ addStream: (
12
+ innerStream: ReadableStream<T>,
13
+ callbacks?: {
14
+ onError?: (error: unknown) => void;
15
+ onCancel?: () => void;
16
+ },
17
+ ) => void;
12
18
  close: () => void;
13
19
  terminate: () => void;
14
20
  } {
15
- let innerStreamReaders: ReadableStreamDefaultReader<T>[] = [];
21
+ let innerStreams: Array<{
22
+ reader: ReadableStreamDefaultReader<T>;
23
+ onError?: (error: unknown) => void;
24
+ onCancel?: () => void;
25
+ }> = [];
16
26
  let controller: ReadableStreamDefaultController<T> | null = null;
17
27
  let isClosed = false;
18
28
  let waitForNewStream = createResolvablePromise<void>();
@@ -21,34 +31,39 @@ export function createStitchableStream<T>(): {
21
31
  isClosed = true;
22
32
  waitForNewStream.resolve();
23
33
 
24
- innerStreamReaders.forEach(reader => reader.cancel());
25
- innerStreamReaders = [];
34
+ innerStreams.forEach(({ reader, onCancel }) => {
35
+ onCancel?.();
36
+ reader.cancel();
37
+ });
38
+ innerStreams = [];
26
39
  controller?.close();
27
40
  };
28
41
 
29
42
  const processPull = async () => {
30
43
  // Case 1: Outer stream is closed and no more inner streams
31
- if (isClosed && innerStreamReaders.length === 0) {
44
+ if (isClosed && innerStreams.length === 0) {
32
45
  controller?.close();
33
46
  return;
34
47
  }
35
48
 
36
49
  // Case 2: No inner streams available, but outer stream is open
37
50
  // wait for a new inner stream to be added or the outer stream to close
38
- if (innerStreamReaders.length === 0) {
51
+ if (innerStreams.length === 0) {
39
52
  waitForNewStream = createResolvablePromise<void>();
40
53
  await waitForNewStream.promise;
41
54
  return await processPull();
42
55
  }
43
56
 
57
+ const currentStream = innerStreams[0];
58
+
44
59
  try {
45
- const { value, done } = await innerStreamReaders[0].read();
60
+ const { value, done } = await currentStream.reader.read();
46
61
 
47
62
  if (done) {
48
63
  // Case 3: Current inner stream is done
49
- innerStreamReaders.shift(); // Remove the finished stream
64
+ innerStreams.shift(); // Remove the finished stream
50
65
 
51
- if (innerStreamReaders.length === 0 && isClosed) {
66
+ if (innerStreams.length === 0 && isClosed) {
52
67
  // when closed and no more inner streams, stop pulling
53
68
  controller?.close();
54
69
  } else {
@@ -61,8 +76,9 @@ export function createStitchableStream<T>(): {
61
76
  }
62
77
  } catch (error) {
63
78
  // Case 5: Current inner stream throws an error
79
+ currentStream.onError?.(error);
64
80
  controller?.error(error);
65
- innerStreamReaders.shift(); // Remove the errored stream
81
+ innerStreams.shift(); // Remove the errored stream
66
82
  terminate(); // we have errored, terminate all streams
67
83
  }
68
84
  };
@@ -74,19 +90,29 @@ export function createStitchableStream<T>(): {
74
90
  },
75
91
  pull: processPull,
76
92
  async cancel() {
77
- for (const reader of innerStreamReaders) {
93
+ for (const { reader, onCancel } of innerStreams) {
94
+ onCancel?.();
78
95
  await reader.cancel();
79
96
  }
80
- innerStreamReaders = [];
97
+ innerStreams = [];
81
98
  isClosed = true;
82
99
  },
83
100
  }),
84
- addStream: (innerStream: ReadableStream<T>) => {
101
+ addStream: (
102
+ innerStream: ReadableStream<T>,
103
+ callbacks?: {
104
+ onError?: (error: unknown) => void;
105
+ onCancel?: () => void;
106
+ },
107
+ ) => {
85
108
  if (isClosed) {
86
109
  throw new Error('Cannot add inner stream: outer stream is closed');
87
110
  }
88
111
 
89
- innerStreamReaders.push(innerStream.getReader());
112
+ innerStreams.push({
113
+ reader: innerStream.getReader(),
114
+ ...callbacks,
115
+ });
90
116
  waitForNewStream.resolve();
91
117
  },
92
118
 
@@ -98,7 +124,7 @@ export function createStitchableStream<T>(): {
98
124
  isClosed = true;
99
125
  waitForNewStream.resolve();
100
126
 
101
- if (innerStreamReaders.length === 0) {
127
+ if (innerStreams.length === 0) {
102
128
  controller?.close();
103
129
  }
104
130
  },
@@ -19,7 +19,7 @@ export function writeToServerResponse({
19
19
  statusText?: string;
20
20
  headers?: Record<string, string | number | string[]>;
21
21
  stream: ReadableStream<Uint8Array>;
22
- }): void {
22
+ }): Promise<void> {
23
23
  const statusCode = status ?? 200;
24
24
  if (statusText !== undefined) {
25
25
  response.writeHead(statusCode, statusText, headers);
@@ -54,5 +54,5 @@ export function writeToServerResponse({
54
54
  }
55
55
  };
56
56
 
57
- read();
57
+ return read();
58
58
  }