ai 6.0.303 → 6.0.305
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 +23 -0
- package/dist/index.d.mts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +73 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +73 -60
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +1 -33
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +1 -33
- package/dist/internal/index.mjs.map +1 -1
- package/docs/03-ai-sdk-core/15-tools-and-tool-calling.mdx +14 -1
- package/docs/04-ai-sdk-ui/03-chatbot-resume-streams.mdx +23 -1
- package/docs/04-ai-sdk-ui/03-chatbot-tool-usage.mdx +8 -0
- package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +8 -1
- package/package.json +4 -4
- package/src/prompt/convert-to-language-model-prompt.ts +0 -42
- package/src/ui/chat-transport.ts +7 -0
- package/src/ui/chat.ts +66 -13
- package/src/ui/default-chat-transport.ts +14 -1
- package/src/ui/process-ui-message-stream.ts +23 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.305",
|
|
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,9 +45,9 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@opentelemetry/api": "^1.9.0",
|
|
48
|
-
"@ai-sdk/gateway": "3.0.
|
|
48
|
+
"@ai-sdk/gateway": "3.0.213",
|
|
49
49
|
"@ai-sdk/provider": "3.0.18",
|
|
50
|
-
"@ai-sdk/provider-utils": "4.0.
|
|
50
|
+
"@ai-sdk/provider-utils": "4.0.58"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@edge-runtime/vm": "^5.0.0",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"tsx": "^4.19.2",
|
|
59
59
|
"typescript": "5.8.3",
|
|
60
60
|
"zod": "3.25.76",
|
|
61
|
-
"@ai-sdk/google": "3.0.
|
|
61
|
+
"@ai-sdk/google": "3.0.132",
|
|
62
62
|
"@ai-sdk/test-server": "1.0.6",
|
|
63
63
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
64
64
|
},
|
|
@@ -52,38 +52,6 @@ export async function convertToLanguageModelPrompt({
|
|
|
52
52
|
supportedUrls,
|
|
53
53
|
);
|
|
54
54
|
|
|
55
|
-
const approvalIdToToolCallId = new Map<string, string>();
|
|
56
|
-
for (const message of prompt.messages) {
|
|
57
|
-
if (message.role === 'assistant' && Array.isArray(message.content)) {
|
|
58
|
-
for (const part of message.content) {
|
|
59
|
-
if (
|
|
60
|
-
part.type === 'tool-approval-request' &&
|
|
61
|
-
'approvalId' in part &&
|
|
62
|
-
'toolCallId' in part
|
|
63
|
-
) {
|
|
64
|
-
approvalIdToToolCallId.set(
|
|
65
|
-
part.approvalId as string,
|
|
66
|
-
part.toolCallId as string,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const approvedToolCallIds = new Set<string>();
|
|
74
|
-
for (const message of prompt.messages) {
|
|
75
|
-
if (message.role === 'tool') {
|
|
76
|
-
for (const part of message.content) {
|
|
77
|
-
if (part.type === 'tool-approval-response') {
|
|
78
|
-
const toolCallId = approvalIdToToolCallId.get(part.approvalId);
|
|
79
|
-
if (toolCallId) {
|
|
80
|
-
approvedToolCallIds.add(toolCallId);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
55
|
const messages = [
|
|
88
56
|
...(prompt.system != null
|
|
89
57
|
? typeof prompt.system === 'string'
|
|
@@ -149,11 +117,6 @@ export async function convertToLanguageModelPrompt({
|
|
|
149
117
|
}
|
|
150
118
|
case 'user':
|
|
151
119
|
case 'system':
|
|
152
|
-
// remove approved tool calls from the set before checking:
|
|
153
|
-
for (const id of approvedToolCallIds) {
|
|
154
|
-
toolCallIds.delete(id);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
120
|
if (toolCallIds.size > 0) {
|
|
158
121
|
throw new MissingToolResultsError({
|
|
159
122
|
toolCallIds: Array.from(toolCallIds),
|
|
@@ -163,11 +126,6 @@ export async function convertToLanguageModelPrompt({
|
|
|
163
126
|
}
|
|
164
127
|
}
|
|
165
128
|
|
|
166
|
-
// remove approved tool calls from the set before checking:
|
|
167
|
-
for (const id of approvedToolCallIds) {
|
|
168
|
-
toolCallIds.delete(id);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
129
|
if (toolCallIds.size > 0) {
|
|
172
130
|
throw new MissingToolResultsError({ toolCallIds: Array.from(toolCallIds) });
|
|
173
131
|
}
|
package/src/ui/chat-transport.ts
CHANGED
|
@@ -13,6 +13,13 @@ import type { UIMessage } from './ui-messages';
|
|
|
13
13
|
* @template UI_MESSAGE - The UI message type extending UIMessage
|
|
14
14
|
*/
|
|
15
15
|
export interface ChatTransport<UI_MESSAGE extends UIMessage> {
|
|
16
|
+
/**
|
|
17
|
+
* Whether reconnecting returns the complete response from its beginning.
|
|
18
|
+
* Complete replays must reset the streaming message state before processing
|
|
19
|
+
* the first start chunk.
|
|
20
|
+
*/
|
|
21
|
+
readonly resumeStreamIsReplay?: boolean;
|
|
22
|
+
|
|
16
23
|
/**
|
|
17
24
|
* Sends messages to the chat API endpoint and returns a streaming response.
|
|
18
25
|
*
|
package/src/ui/chat.ts
CHANGED
|
@@ -518,6 +518,33 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
518
518
|
await this.makeRequest({ trigger: 'resume-stream', ...options });
|
|
519
519
|
};
|
|
520
520
|
|
|
521
|
+
/**
|
|
522
|
+
* Resume the stream if the latest response ended with a resumable network
|
|
523
|
+
* disconnect. If a response is still active, wait for it to settle first so
|
|
524
|
+
* disconnects reported shortly after the caller's trigger are handled.
|
|
525
|
+
*
|
|
526
|
+
* @internal
|
|
527
|
+
*/
|
|
528
|
+
'~resumeStreamIfDisconnected' = async ({
|
|
529
|
+
shouldResume = () => true,
|
|
530
|
+
}: {
|
|
531
|
+
shouldResume?: () => boolean;
|
|
532
|
+
} = {}): Promise<void> => {
|
|
533
|
+
const activeResponse = this.activeResponse;
|
|
534
|
+
|
|
535
|
+
if (this.resumableStreamState == null && activeResponse != null) {
|
|
536
|
+
await activeResponse.completionPromise;
|
|
537
|
+
|
|
538
|
+
if (this.resumableStreamState !== activeResponse.state) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (this.resumableStreamState != null && shouldResume()) {
|
|
544
|
+
await this.resumeStream();
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
521
548
|
/**
|
|
522
549
|
* Clear the error state and set the status to ready if the chat is in an error state.
|
|
523
550
|
*/
|
|
@@ -611,7 +638,6 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
611
638
|
}) =>
|
|
612
639
|
this.jobExecutor.run(async () => {
|
|
613
640
|
const messages = this.state.messages;
|
|
614
|
-
const lastMessage = messages[messages.length - 1];
|
|
615
641
|
|
|
616
642
|
const updatePart = (
|
|
617
643
|
part: UIMessagePart<UIDataTypes, UITools>,
|
|
@@ -620,11 +646,21 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
620
646
|
? ({ ...part, state, output, errorText } as typeof part)
|
|
621
647
|
: part;
|
|
622
648
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
649
|
+
const messageIndex = messages.findIndex(message =>
|
|
650
|
+
message.parts.some(
|
|
651
|
+
part => isToolUIPart(part) && part.toolCallId === toolCallId,
|
|
652
|
+
),
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
if (messageIndex !== -1) {
|
|
656
|
+
const message = messages[messageIndex];
|
|
657
|
+
|
|
658
|
+
// update the message to trigger an immediate UI update
|
|
659
|
+
this.state.replaceMessage(messageIndex, {
|
|
660
|
+
...message,
|
|
661
|
+
parts: message.parts.map(updatePart),
|
|
662
|
+
});
|
|
663
|
+
}
|
|
628
664
|
|
|
629
665
|
// update the active response if it exists
|
|
630
666
|
if (this.activeResponse) {
|
|
@@ -634,6 +670,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
634
670
|
|
|
635
671
|
// automatically send the message if the sendAutomaticallyWhen function returns true
|
|
636
672
|
if (
|
|
673
|
+
messageIndex !== -1 &&
|
|
637
674
|
this.status !== 'streaming' &&
|
|
638
675
|
this.status !== 'submitted' &&
|
|
639
676
|
this.sendAutomaticallyWhen
|
|
@@ -646,7 +683,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
646
683
|
() =>
|
|
647
684
|
this.makeRequest({
|
|
648
685
|
trigger: 'submit-message',
|
|
649
|
-
messageId:
|
|
686
|
+
messageId: messages[messageIndex].id,
|
|
650
687
|
...options,
|
|
651
688
|
}),
|
|
652
689
|
shouldSend,
|
|
@@ -872,6 +909,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
872
909
|
responseMessageIndex === -1
|
|
873
910
|
? lastMessage
|
|
874
911
|
: this.state.messages[responseMessageIndex];
|
|
912
|
+
const originalResponseMessageId = responseMessage?.id;
|
|
875
913
|
const resumableResponseMessage =
|
|
876
914
|
trigger === 'resume-stream' &&
|
|
877
915
|
responseMessage?.role === 'assistant' &&
|
|
@@ -956,16 +994,28 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
956
994
|
this.setStatus({ status: 'streaming' });
|
|
957
995
|
}
|
|
958
996
|
|
|
959
|
-
|
|
997
|
+
const existingMessageIndex = this.state.messages.findLastIndex(
|
|
998
|
+
message => message.id === response.state.message.id,
|
|
999
|
+
);
|
|
1000
|
+
|
|
1001
|
+
if (existingMessageIndex !== -1) {
|
|
960
1002
|
this.state.replaceMessage(
|
|
961
|
-
|
|
1003
|
+
existingMessageIndex,
|
|
962
1004
|
response.state.message,
|
|
963
1005
|
);
|
|
964
|
-
} else if (
|
|
965
|
-
this.state.
|
|
966
|
-
|
|
967
|
-
response.state.message,
|
|
1006
|
+
} else if (usesEarlierAssistantMessage) {
|
|
1007
|
+
const originalMessageIndex = this.state.messages.findLastIndex(
|
|
1008
|
+
message => message.id === originalResponseMessageId,
|
|
968
1009
|
);
|
|
1010
|
+
|
|
1011
|
+
if (originalMessageIndex !== -1) {
|
|
1012
|
+
this.state.replaceMessage(
|
|
1013
|
+
originalMessageIndex,
|
|
1014
|
+
response.state.message,
|
|
1015
|
+
);
|
|
1016
|
+
} else {
|
|
1017
|
+
this.state.pushMessage(response.state.message);
|
|
1018
|
+
}
|
|
969
1019
|
} else {
|
|
970
1020
|
this.state.pushMessage(response.state.message);
|
|
971
1021
|
}
|
|
@@ -976,6 +1026,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
976
1026
|
await consumeStream({
|
|
977
1027
|
stream: processUIMessageStream({
|
|
978
1028
|
stream,
|
|
1029
|
+
resetStateOnMessageIdChange: trigger === 'resume-stream',
|
|
1030
|
+
resetStateOnFirstMessageStart:
|
|
1031
|
+
trigger === 'resume-stream' && this.transport.resumeStreamIsReplay,
|
|
979
1032
|
onToolCall: this.onToolCall,
|
|
980
1033
|
onData: this.onData,
|
|
981
1034
|
messageMetadataSchema: this.messageMetadataSchema,
|
|
@@ -12,8 +12,21 @@ import type { UIMessage } from './ui-messages';
|
|
|
12
12
|
export class DefaultChatTransport<
|
|
13
13
|
UI_MESSAGE extends UIMessage,
|
|
14
14
|
> extends HttpChatTransport<UI_MESSAGE> {
|
|
15
|
-
|
|
15
|
+
readonly resumeStreamIsReplay: boolean;
|
|
16
|
+
|
|
17
|
+
constructor({
|
|
18
|
+
resumeStreamIsReplay = true,
|
|
19
|
+
...options
|
|
20
|
+
}: HttpChatTransportInitOptions<UI_MESSAGE> & {
|
|
21
|
+
/**
|
|
22
|
+
* Whether resumed streams replay the response from the beginning.
|
|
23
|
+
* Defaults to true. Set to false for endpoints that only continue the
|
|
24
|
+
* interrupted response, even when they emit a start chunk.
|
|
25
|
+
*/
|
|
26
|
+
resumeStreamIsReplay?: boolean;
|
|
27
|
+
} = {}) {
|
|
16
28
|
super(options);
|
|
29
|
+
this.resumeStreamIsReplay = resumeStreamIsReplay;
|
|
17
30
|
}
|
|
18
31
|
|
|
19
32
|
protected processResponseStream(
|
|
@@ -121,6 +121,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
121
121
|
onToolCall,
|
|
122
122
|
onData,
|
|
123
123
|
resetStateOnMessageIdChange = false,
|
|
124
|
+
resetStateOnFirstMessageStart = false,
|
|
124
125
|
}: {
|
|
125
126
|
// input stream is not fully typed yet:
|
|
126
127
|
stream: ReadableStream<UIMessageChunk>;
|
|
@@ -139,7 +140,11 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
139
140
|
onError: ErrorHandler;
|
|
140
141
|
// During resume, a different message ID identifies a separate response.
|
|
141
142
|
resetStateOnMessageIdChange?: boolean;
|
|
143
|
+
// A transport can replay a response from the beginning using the same ID.
|
|
144
|
+
resetStateOnFirstMessageStart?: boolean;
|
|
142
145
|
}): ReadableStream<InferUIMessageChunk<UI_MESSAGE>> {
|
|
146
|
+
let isFirstMessageStart = true;
|
|
147
|
+
|
|
143
148
|
return stream.pipeThrough(
|
|
144
149
|
new TransformStream<UIMessageChunk, InferUIMessageChunk<UI_MESSAGE>>({
|
|
145
150
|
async transform(chunk, controller) {
|
|
@@ -881,19 +886,23 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
881
886
|
}
|
|
882
887
|
|
|
883
888
|
case 'start': {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
889
|
+
const shouldReset =
|
|
890
|
+
(resetStateOnFirstMessageStart && isFirstMessageStart) ||
|
|
891
|
+
(resetStateOnMessageIdChange &&
|
|
892
|
+
chunk.messageId != null &&
|
|
893
|
+
chunk.messageId !== state.message.id);
|
|
894
|
+
isFirstMessageStart = false;
|
|
895
|
+
|
|
896
|
+
if (shouldReset) {
|
|
897
|
+
// Rebuild a replay or separate response from empty state. Only
|
|
898
|
+
// reset a replay once: merged streams can emit more starts.
|
|
899
|
+
// Keep the state object for its callers and preserve the ID
|
|
900
|
+
// when the replay does not include one.
|
|
892
901
|
Object.assign(
|
|
893
902
|
state,
|
|
894
903
|
createStreamingUIMessageState({
|
|
895
904
|
lastMessage: undefined,
|
|
896
|
-
messageId: chunk.messageId,
|
|
905
|
+
messageId: chunk.messageId ?? state.message.id,
|
|
897
906
|
}),
|
|
898
907
|
{ finishReason: undefined },
|
|
899
908
|
);
|
|
@@ -905,7 +914,11 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
905
914
|
|
|
906
915
|
await updateMessageMetadata(chunk.messageMetadata);
|
|
907
916
|
|
|
908
|
-
if (
|
|
917
|
+
if (
|
|
918
|
+
shouldReset ||
|
|
919
|
+
chunk.messageId != null ||
|
|
920
|
+
chunk.messageMetadata != null
|
|
921
|
+
) {
|
|
909
922
|
write({ updateStatus: false });
|
|
910
923
|
}
|
|
911
924
|
break;
|