ai 6.0.295 → 6.0.297

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/index.d.mts +42 -11
  3. package/dist/index.d.ts +42 -11
  4. package/dist/index.js +210 -43
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +210 -43
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/internal/index.js +1 -1
  9. package/dist/internal/index.mjs +1 -1
  10. package/docs/04-ai-sdk-ui/02-chatbot.mdx +10 -1
  11. package/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx +8 -0
  12. package/docs/04-ai-sdk-ui/03-chatbot-resume-streams.mdx +10 -3
  13. package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +10 -1
  14. package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +1 -1
  15. package/docs/07-reference/01-ai-sdk-core/17-create-agent-ui-stream.mdx +3 -3
  16. package/docs/07-reference/01-ai-sdk-core/18-create-agent-ui-stream-response.mdx +2 -2
  17. package/docs/07-reference/01-ai-sdk-core/18-pipe-agent-ui-stream-to-response.mdx +3 -3
  18. package/docs/07-reference/02-ai-sdk-ui/41-create-ui-message-stream-response.mdx +7 -0
  19. package/docs/07-reference/02-ai-sdk-ui/42-pipe-ui-message-stream-to-response.mdx +7 -0
  20. package/package.json +2 -2
  21. package/src/agent/create-agent-ui-stream-response.ts +5 -2
  22. package/src/agent/create-agent-ui-stream.ts +5 -3
  23. package/src/agent/pipe-agent-ui-stream-to-response.ts +5 -2
  24. package/src/embed/embed-many.ts +5 -3
  25. package/src/generate-image/generate-image.ts +7 -2
  26. package/src/generate-text/smooth-stream.ts +24 -9
  27. package/src/generate-text/stream-text-result.ts +5 -0
  28. package/src/generate-text/stream-text.ts +4 -1
  29. package/src/generate-video/generate-video.ts +6 -4
  30. package/src/registry/provider-registry.ts +1 -1
  31. package/src/ui/chat.ts +13 -4
  32. package/src/ui/http-chat-transport.ts +27 -1
  33. package/src/ui/last-assistant-message-is-complete-with-tool-calls.ts +15 -3
  34. package/src/ui/process-ui-message-stream.ts +59 -18
  35. package/src/ui/ui-messages.ts +14 -0
  36. package/src/ui/validate-ui-messages.ts +2 -0
  37. package/src/ui-message-stream/create-sse-stream-with-keep-alive.ts +89 -0
  38. package/src/ui-message-stream/create-ui-message-stream-response.ts +7 -1
  39. package/src/ui-message-stream/pipe-ui-message-stream-to-response.ts +7 -1
  40. package/src/ui-message-stream/ui-message-stream-response-init.ts +7 -0
  41. package/src/util/get-own.ts +18 -0
  42. package/src/util/set-own.ts +16 -0
@@ -60,22 +60,55 @@ export function createStreamingUIMessageState<UI_MESSAGE extends UIMessage>({
60
60
  lastMessage: UI_MESSAGE | undefined;
61
61
  messageId: string;
62
62
  }): StreamingUIMessageState<UI_MESSAGE> {
63
+ const message =
64
+ lastMessage?.role === 'assistant'
65
+ ? lastMessage
66
+ : ({
67
+ id: messageId,
68
+ metadata: undefined,
69
+ role: 'assistant',
70
+ parts: [] as UIMessagePart<
71
+ InferUIMessageData<UI_MESSAGE>,
72
+ InferUIMessageTools<UI_MESSAGE>
73
+ >[],
74
+ } as UI_MESSAGE);
75
+ const partialToolCalls: StreamingUIMessageState<UI_MESSAGE>['partialToolCalls'] =
76
+ createIdMap();
77
+ const lastStepStartIndex = message.parts.findLastIndex(
78
+ part => part.type === 'step-start',
79
+ );
80
+ let staticToolIndex = 0;
81
+
82
+ for (const part of message.parts.slice(lastStepStartIndex + 1)) {
83
+ if (!isToolUIPart(part)) {
84
+ continue;
85
+ }
86
+
87
+ const index = staticToolIndex;
88
+ if (isStaticToolUIPart(part)) {
89
+ staticToolIndex++;
90
+ }
91
+
92
+ if (part.state !== 'input-streaming') {
93
+ continue;
94
+ }
95
+
96
+ partialToolCalls[part.toolCallId] = {
97
+ text: part.rawInput ?? '',
98
+ index,
99
+ toolName:
100
+ part.type === 'dynamic-tool' ? part.toolName : getStaticToolName(part),
101
+ dynamic: part.type === 'dynamic-tool',
102
+ title: part.title,
103
+ toolMetadata: part.toolMetadata,
104
+ };
105
+ }
106
+
63
107
  return {
64
- message:
65
- lastMessage?.role === 'assistant'
66
- ? lastMessage
67
- : ({
68
- id: messageId,
69
- metadata: undefined,
70
- role: 'assistant',
71
- parts: [] as UIMessagePart<
72
- InferUIMessageData<UI_MESSAGE>,
73
- InferUIMessageTools<UI_MESSAGE>
74
- >[],
75
- } as UI_MESSAGE),
108
+ message,
76
109
  activeTextParts: createIdMap(),
77
110
  activeReasoningParts: createIdMap(),
78
- partialToolCalls: createIdMap(),
111
+ partialToolCalls,
79
112
  };
80
113
  }
81
114
 
@@ -167,6 +200,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
167
200
  | {
168
201
  state: 'input-streaming';
169
202
  input: unknown;
203
+ rawInput?: string;
170
204
  providerExecuted?: boolean;
171
205
  providerMetadata?: ProviderMetadata;
172
206
  }
@@ -282,6 +316,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
282
316
  | {
283
317
  state: 'input-streaming';
284
318
  input: unknown;
319
+ rawInput?: string;
285
320
  providerMetadata?: ProviderMetadata;
286
321
  }
287
322
  | {
@@ -322,7 +357,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
322
357
  anyPart.input = anyOptions.input;
323
358
  anyPart.output = anyOptions.output;
324
359
  anyPart.errorText = anyOptions.errorText;
325
- anyPart.rawInput = anyOptions.rawInput ?? anyPart.rawInput;
360
+ anyPart.rawInput = anyOptions.rawInput;
326
361
  anyPart.preliminary = anyOptions.preliminary;
327
362
  if (options.title !== undefined) {
328
363
  anyPart.title = options.title;
@@ -615,6 +650,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
615
650
  toolName: partialToolCall.toolName,
616
651
  state: 'input-streaming',
617
652
  input: partialArgs,
653
+ rawInput: partialToolCall.text,
618
654
  title: partialToolCall.title,
619
655
  toolMetadata: partialToolCall.toolMetadata,
620
656
  });
@@ -624,6 +660,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
624
660
  toolName: partialToolCall.toolName,
625
661
  state: 'input-streaming',
626
662
  input: partialArgs,
663
+ rawInput: partialToolCall.text,
627
664
  title: partialToolCall.title,
628
665
  toolMetadata: partialToolCall.toolMetadata,
629
666
  });
@@ -757,7 +794,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
757
794
  providerExecuted: chunk.providerExecuted,
758
795
  providerMetadata: chunk.providerMetadata,
759
796
  title: toolInvocation.title,
760
- toolMetadata: toolInvocation.toolMetadata,
797
+ toolMetadata:
798
+ chunk.toolMetadata ?? toolInvocation.toolMetadata,
761
799
  },
762
800
  toolInvocation,
763
801
  );
@@ -773,7 +811,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
773
811
  preliminary: chunk.preliminary,
774
812
  providerMetadata: chunk.providerMetadata,
775
813
  title: toolInvocation.title,
776
- toolMetadata: toolInvocation.toolMetadata,
814
+ toolMetadata:
815
+ chunk.toolMetadata ?? toolInvocation.toolMetadata,
777
816
  },
778
817
  toolInvocation as ToolUIPart<InferUIMessageTools<UI_MESSAGE>>,
779
818
  );
@@ -797,7 +836,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
797
836
  providerExecuted: chunk.providerExecuted,
798
837
  providerMetadata: chunk.providerMetadata,
799
838
  title: toolInvocation.title,
800
- toolMetadata: toolInvocation.toolMetadata,
839
+ toolMetadata:
840
+ chunk.toolMetadata ?? toolInvocation.toolMetadata,
801
841
  },
802
842
  toolInvocation,
803
843
  );
@@ -813,7 +853,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
813
853
  providerExecuted: chunk.providerExecuted,
814
854
  providerMetadata: chunk.providerMetadata,
815
855
  title: toolInvocation.title,
816
- toolMetadata: toolInvocation.toolMetadata,
856
+ toolMetadata:
857
+ chunk.toolMetadata ?? toolInvocation.toolMetadata,
817
858
  },
818
859
  toolInvocation as ToolUIPart<InferUIMessageTools<UI_MESSAGE>>,
819
860
  );
@@ -238,6 +238,13 @@ export type UIToolInvocation<TOOL extends UITool | Tool> = {
238
238
  | {
239
239
  state: 'input-streaming';
240
240
  input?: DeepPartial<asUITool<TOOL>['input']> | undefined;
241
+ /**
242
+ * The raw tool input text received so far.
243
+ *
244
+ * This is used to continue input streaming when a message is persisted
245
+ * and later resumed.
246
+ */
247
+ rawInput?: string;
241
248
  output?: never;
242
249
  errorText?: never;
243
250
  callProviderMetadata?: ProviderMetadata;
@@ -361,6 +368,13 @@ export type DynamicToolUIPart = {
361
368
  | {
362
369
  state: 'input-streaming';
363
370
  input?: unknown;
371
+ /**
372
+ * The raw tool input text received so far.
373
+ *
374
+ * This is used to continue input streaming when a message is persisted
375
+ * and later resumed.
376
+ */
377
+ rawInput?: string;
364
378
  output?: never;
365
379
  errorText?: never;
366
380
  callProviderMetadata?: ProviderMetadata;
@@ -139,6 +139,7 @@ const uiMessagesSchema = lazySchema(() => {
139
139
  toolMetadata: toolMetadataSchema.optional(),
140
140
  state: z.literal('input-streaming'),
141
141
  input: z.unknown().optional(),
142
+ rawInput: z.string().optional(),
142
143
  providerExecuted: z.boolean().optional(),
143
144
  callProviderMetadata: providerMetadataSchema.optional(),
144
145
  output: z.never().optional(),
@@ -235,6 +236,7 @@ const uiMessagesSchema = lazySchema(() => {
235
236
  providerExecuted: z.boolean().optional(),
236
237
  callProviderMetadata: providerMetadataSchema.optional(),
237
238
  input: z.unknown().optional(),
239
+ rawInput: z.string().optional(),
238
240
  output: z.never().optional(),
239
241
  errorText: z.never().optional(),
240
242
  approval: z.never().optional(),
@@ -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 = stream.pipeThrough(new JsonToSseTransformStream());
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 = stream.pipeThrough(new JsonToSseTransformStream());
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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Reads a property by an untrusted key, ignoring inherited prototype members.
3
+ *
4
+ * Tool sets, tool contexts, and similar lookup objects are indexed by names
5
+ * that can come from model output or client-supplied message history. Plain
6
+ * bracket access (`obj[name]`) resolves names such as `constructor`,
7
+ * `toString`, or `__proto__` to values on `Object.prototype`, which would slip
8
+ * past the `== null` / `!value` guards that treat an unknown name as "not
9
+ * present". This helper returns `undefined` unless `key` is an own property.
10
+ */
11
+ export function getOwn<T extends object>(
12
+ obj: T | undefined | null,
13
+ key: string,
14
+ ): T[keyof T] | undefined {
15
+ return obj != null && Object.hasOwn(obj, key)
16
+ ? obj[key as keyof T]
17
+ : undefined;
18
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Writes an enumerable own property without invoking inherited setters such as
3
+ * `Object.prototype.__proto__`. Keeps the target object's prototype unchanged.
4
+ */
5
+ export function setOwn<T>(
6
+ object: Record<string, T>,
7
+ key: string,
8
+ value: T,
9
+ ): void {
10
+ Object.defineProperty(object, key, {
11
+ value,
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ });
16
+ }