ai 7.0.136 → 7.0.138
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 +29 -0
- package/dist/index.d.ts +31 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +116 -77
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +73 -4
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +44 -17
- package/dist/internal/index.js.map +1 -1
- package/docs/03-ai-sdk-core/05-generating-text.mdx +70 -2
- 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 +6 -0
- package/docs/04-ai-sdk-ui/21-error-handling.mdx +34 -0
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +4 -2
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +11 -1
- package/docs/07-reference/02-ai-sdk-ui/01-use-chat.mdx +8 -1
- package/docs/07-reference/04-ai-sdk-workflow/02-workflow-chat-transport.mdx +2 -2
- package/package.json +12 -12
- package/src/decide/validate-decision.ts +2 -1
- package/src/generate-text/content-part.ts +7 -1
- package/src/generate-text/is-output-chunk.ts +59 -0
- package/src/generate-text/stream-text-result.ts +2 -0
- package/src/generate-text/stream-text.ts +7 -57
- package/src/global.ts +23 -4
- package/src/middleware/simulate-streaming-middleware.ts +8 -2
- package/src/prompt/convert-to-language-model-prompt.ts +0 -42
- package/src/types/citation.ts +31 -0
- package/src/types/index.ts +1 -0
- package/src/ui/chat.ts +63 -13
- package/src/ui/default-chat-transport.ts +14 -1
- package/src/ui/process-ui-message-stream.ts +23 -11
- package/src/ui/ui-messages.ts +4 -0
- package/src/ui/validate-ui-messages.ts +2 -0
- package/src/ui-message-stream/read-ui-message-stream.ts +1 -0
- package/src/ui-message-stream/to-ui-message-chunk.ts +1 -0
- package/src/ui-message-stream/ui-message-chunks.ts +3 -0
|
@@ -54,38 +54,6 @@ export async function convertToLanguageModelPrompt({
|
|
|
54
54
|
supportedUrls,
|
|
55
55
|
);
|
|
56
56
|
|
|
57
|
-
const approvalIdToToolCallId = new Map<string, string>();
|
|
58
|
-
for (const message of prompt.messages) {
|
|
59
|
-
if (message.role === 'assistant' && Array.isArray(message.content)) {
|
|
60
|
-
for (const part of message.content) {
|
|
61
|
-
if (
|
|
62
|
-
part.type === 'tool-approval-request' &&
|
|
63
|
-
'approvalId' in part &&
|
|
64
|
-
'toolCallId' in part
|
|
65
|
-
) {
|
|
66
|
-
approvalIdToToolCallId.set(
|
|
67
|
-
part.approvalId as string,
|
|
68
|
-
part.toolCallId as string,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const approvedToolCallIds = new Set<string>();
|
|
76
|
-
for (const message of prompt.messages) {
|
|
77
|
-
if (message.role === 'tool') {
|
|
78
|
-
for (const part of message.content) {
|
|
79
|
-
if (part.type === 'tool-approval-response') {
|
|
80
|
-
const toolCallId = approvalIdToToolCallId.get(part.approvalId);
|
|
81
|
-
if (toolCallId) {
|
|
82
|
-
approvedToolCallIds.add(toolCallId);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
57
|
const messages = [
|
|
90
58
|
...(prompt.instructions != null
|
|
91
59
|
? typeof prompt.instructions === 'string'
|
|
@@ -157,11 +125,6 @@ export async function convertToLanguageModelPrompt({
|
|
|
157
125
|
}
|
|
158
126
|
case 'user':
|
|
159
127
|
case 'system':
|
|
160
|
-
// remove approved tool calls from the set before checking:
|
|
161
|
-
for (const id of approvedToolCallIds) {
|
|
162
|
-
toolCallIds.delete(id);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
128
|
if (toolCallIds.size > 0) {
|
|
166
129
|
throw new MissingToolResultsError({
|
|
167
130
|
toolCallIds: Array.from(toolCallIds),
|
|
@@ -171,11 +134,6 @@ export async function convertToLanguageModelPrompt({
|
|
|
171
134
|
}
|
|
172
135
|
}
|
|
173
136
|
|
|
174
|
-
// remove approved tool calls from the set before checking:
|
|
175
|
-
for (const id of approvedToolCallIds) {
|
|
176
|
-
toolCallIds.delete(id);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
137
|
if (toolCallIds.size > 0) {
|
|
180
138
|
throw new MissingToolResultsError({ toolCallIds: Array.from(toolCallIds) });
|
|
181
139
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LanguageModelV4Citation } from '@ai-sdk/provider';
|
|
2
|
+
import { z, type ZodType } from '../util/zod';
|
|
3
|
+
import { providerMetadataSchema } from './provider-metadata';
|
|
4
|
+
|
|
5
|
+
/** A reference supporting a generated text part. */
|
|
6
|
+
export type Citation = LanguageModelV4Citation;
|
|
7
|
+
|
|
8
|
+
export const citationSchema: ZodType<Citation> = z.object({
|
|
9
|
+
source: z.discriminatedUnion('sourceType', [
|
|
10
|
+
z.object({
|
|
11
|
+
type: z.literal('source'),
|
|
12
|
+
sourceType: z.literal('url'),
|
|
13
|
+
id: z.string(),
|
|
14
|
+
url: z.string(),
|
|
15
|
+
title: z.string().optional(),
|
|
16
|
+
providerMetadata: providerMetadataSchema.optional(),
|
|
17
|
+
}),
|
|
18
|
+
z.object({
|
|
19
|
+
type: z.literal('source'),
|
|
20
|
+
sourceType: z.literal('document'),
|
|
21
|
+
id: z.string(),
|
|
22
|
+
mediaType: z.string(),
|
|
23
|
+
title: z.string(),
|
|
24
|
+
filename: z.string().optional(),
|
|
25
|
+
providerMetadata: providerMetadataSchema.optional(),
|
|
26
|
+
}),
|
|
27
|
+
]),
|
|
28
|
+
startIndex: z.number().int().nonnegative().optional(),
|
|
29
|
+
endIndex: z.number().int().nonnegative().optional(),
|
|
30
|
+
citedText: z.string().optional(),
|
|
31
|
+
});
|
package/src/types/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type { JSONSchema7 } from '@ai-sdk/provider';
|
|
2
|
+
export type { Citation } from './citation';
|
|
2
3
|
export type { Embedding, EmbeddingModel } from './embedding-model';
|
|
3
4
|
export type { EmbeddingModelMiddleware } from './embedding-model-middleware';
|
|
4
5
|
export type { ImageModel, ImageModelProviderMetadata } from './image-model';
|
package/src/ui/chat.ts
CHANGED
|
@@ -541,6 +541,33 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
541
541
|
await this.makeRequest({ trigger: 'resume-stream', ...options });
|
|
542
542
|
};
|
|
543
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Resume the stream if the latest response ended with a resumable network
|
|
546
|
+
* disconnect. If a response is still active, wait for it to settle first so
|
|
547
|
+
* disconnects reported shortly after the caller's trigger are handled.
|
|
548
|
+
*
|
|
549
|
+
* @internal
|
|
550
|
+
*/
|
|
551
|
+
'~resumeStreamIfDisconnected' = async ({
|
|
552
|
+
shouldResume = () => true,
|
|
553
|
+
}: {
|
|
554
|
+
shouldResume?: () => boolean;
|
|
555
|
+
} = {}): Promise<void> => {
|
|
556
|
+
const activeResponse = this.activeResponse;
|
|
557
|
+
|
|
558
|
+
if (this.resumableStreamState == null && activeResponse != null) {
|
|
559
|
+
await activeResponse.completionPromise;
|
|
560
|
+
|
|
561
|
+
if (this.resumableStreamState !== activeResponse.state) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (this.resumableStreamState != null && shouldResume()) {
|
|
567
|
+
await this.resumeStream();
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
544
571
|
/**
|
|
545
572
|
* Clear the error state and set the status to ready if the chat is in an error state.
|
|
546
573
|
*/
|
|
@@ -635,7 +662,6 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
635
662
|
}) =>
|
|
636
663
|
this.jobExecutor.run(async () => {
|
|
637
664
|
const messages = this.state.messages;
|
|
638
|
-
const lastMessage = messages[messages.length - 1];
|
|
639
665
|
|
|
640
666
|
const updatePart = (
|
|
641
667
|
part: UIMessagePart<UIDataTypes, UITools>,
|
|
@@ -675,11 +701,21 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
675
701
|
};
|
|
676
702
|
};
|
|
677
703
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
704
|
+
const messageIndex = messages.findIndex(message =>
|
|
705
|
+
message.parts.some(
|
|
706
|
+
part => isToolUIPart(part) && part.toolCallId === toolCallId,
|
|
707
|
+
),
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
if (messageIndex !== -1) {
|
|
711
|
+
const message = messages[messageIndex];
|
|
712
|
+
|
|
713
|
+
// update the message to trigger an immediate UI update
|
|
714
|
+
this.state.replaceMessage(messageIndex, {
|
|
715
|
+
...message,
|
|
716
|
+
parts: message.parts.map(updatePart),
|
|
717
|
+
});
|
|
718
|
+
}
|
|
683
719
|
|
|
684
720
|
// update the active response if it exists
|
|
685
721
|
if (this.activeResponse) {
|
|
@@ -689,6 +725,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
689
725
|
|
|
690
726
|
// automatically send the message if the sendAutomaticallyWhen function returns true
|
|
691
727
|
if (
|
|
728
|
+
messageIndex !== -1 &&
|
|
692
729
|
this.status !== 'streaming' &&
|
|
693
730
|
this.status !== 'submitted' &&
|
|
694
731
|
this.sendAutomaticallyWhen
|
|
@@ -701,7 +738,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
701
738
|
() =>
|
|
702
739
|
this.makeRequest({
|
|
703
740
|
trigger: 'submit-message',
|
|
704
|
-
messageId:
|
|
741
|
+
messageId: messages[messageIndex].id,
|
|
705
742
|
...options,
|
|
706
743
|
}),
|
|
707
744
|
shouldSend,
|
|
@@ -938,6 +975,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
938
975
|
responseMessageIndex === -1
|
|
939
976
|
? lastMessage
|
|
940
977
|
: this.state.messages[responseMessageIndex];
|
|
978
|
+
const originalResponseMessageId = responseMessage?.id;
|
|
941
979
|
// The continued stream can start with input deltas or a tool result.
|
|
942
980
|
// Keep unfinished tool parts so result chunks can find their tool call.
|
|
943
981
|
const resumableResponseMessage =
|
|
@@ -1028,16 +1066,28 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
1028
1066
|
this.setStatus({ status: 'streaming' });
|
|
1029
1067
|
}
|
|
1030
1068
|
|
|
1031
|
-
|
|
1069
|
+
const existingMessageIndex = this.state.messages.findLastIndex(
|
|
1070
|
+
message => message.id === response.state.message.id,
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
if (existingMessageIndex !== -1) {
|
|
1032
1074
|
this.state.replaceMessage(
|
|
1033
|
-
|
|
1075
|
+
existingMessageIndex,
|
|
1034
1076
|
response.state.message,
|
|
1035
1077
|
);
|
|
1036
|
-
} else if (
|
|
1037
|
-
this.state.
|
|
1038
|
-
|
|
1039
|
-
response.state.message,
|
|
1078
|
+
} else if (usesEarlierAssistantMessage) {
|
|
1079
|
+
const originalMessageIndex = this.state.messages.findLastIndex(
|
|
1080
|
+
message => message.id === originalResponseMessageId,
|
|
1040
1081
|
);
|
|
1082
|
+
|
|
1083
|
+
if (originalMessageIndex !== -1) {
|
|
1084
|
+
this.state.replaceMessage(
|
|
1085
|
+
originalMessageIndex,
|
|
1086
|
+
response.state.message,
|
|
1087
|
+
);
|
|
1088
|
+
} else {
|
|
1089
|
+
this.state.pushMessage(response.state.message);
|
|
1090
|
+
}
|
|
1041
1091
|
} else {
|
|
1042
1092
|
this.state.pushMessage(response.state.message);
|
|
1043
1093
|
}
|
|
@@ -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(
|
|
@@ -143,6 +143,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
143
143
|
// A transport can replay a response from the beginning using the same ID.
|
|
144
144
|
resetStateOnFirstMessageStart?: boolean;
|
|
145
145
|
}): ReadableStream<InferUIMessageChunk<UI_MESSAGE>> {
|
|
146
|
+
let isFirstMessageStart = true;
|
|
147
|
+
|
|
146
148
|
return stream.pipeThrough(
|
|
147
149
|
new TransformStream<UIMessageChunk, InferUIMessageChunk<UI_MESSAGE>>({
|
|
148
150
|
async transform(chunk, controller) {
|
|
@@ -508,6 +510,9 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
508
510
|
});
|
|
509
511
|
}
|
|
510
512
|
textPart.state = 'done';
|
|
513
|
+
if (chunk.citations != null) {
|
|
514
|
+
textPart.citations = chunk.citations;
|
|
515
|
+
}
|
|
511
516
|
textPart.providerMetadata =
|
|
512
517
|
chunk.providerMetadata ?? textPart.providerMetadata;
|
|
513
518
|
delete state.activeTextParts[chunk.id];
|
|
@@ -959,20 +964,23 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
959
964
|
}
|
|
960
965
|
|
|
961
966
|
case 'start': {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
(
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
//
|
|
967
|
+
const shouldReset =
|
|
968
|
+
(resetStateOnFirstMessageStart && isFirstMessageStart) ||
|
|
969
|
+
(resetStateOnMessageIdChange &&
|
|
970
|
+
chunk.messageId != null &&
|
|
971
|
+
chunk.messageId !== state.message.id);
|
|
972
|
+
isFirstMessageStart = false;
|
|
973
|
+
|
|
974
|
+
if (shouldReset) {
|
|
975
|
+
// Rebuild a replay or separate response from empty state. Only
|
|
976
|
+
// reset a replay once: merged streams can emit more starts.
|
|
977
|
+
// Keep the state object for its callers and preserve the ID
|
|
978
|
+
// when the replay does not include one.
|
|
971
979
|
Object.assign(
|
|
972
980
|
state,
|
|
973
981
|
createStreamingUIMessageState({
|
|
974
982
|
lastMessage: undefined,
|
|
975
|
-
messageId: chunk.messageId,
|
|
983
|
+
messageId: chunk.messageId ?? state.message.id,
|
|
976
984
|
}),
|
|
977
985
|
{ finishReason: undefined },
|
|
978
986
|
);
|
|
@@ -984,7 +992,11 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
984
992
|
|
|
985
993
|
await updateMessageMetadata(chunk.messageMetadata);
|
|
986
994
|
|
|
987
|
-
if (
|
|
995
|
+
if (
|
|
996
|
+
shouldReset ||
|
|
997
|
+
chunk.messageId != null ||
|
|
998
|
+
chunk.messageMetadata != null
|
|
999
|
+
) {
|
|
988
1000
|
write({ updateStatus: false });
|
|
989
1001
|
}
|
|
990
1002
|
break;
|
package/src/ui/ui-messages.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Citation } from '../types/citation';
|
|
1
2
|
import type { JSONObject } from '@ai-sdk/provider';
|
|
2
3
|
import type {
|
|
3
4
|
InferToolInput,
|
|
@@ -96,6 +97,9 @@ export type UIMessagePart<
|
|
|
96
97
|
export type TextUIPart = {
|
|
97
98
|
type: 'text';
|
|
98
99
|
|
|
100
|
+
/** References supporting this text, separate from retrieved sources. */
|
|
101
|
+
citations?: Array<Citation>;
|
|
102
|
+
|
|
99
103
|
/**
|
|
100
104
|
* The text content.
|
|
101
105
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { citationSchema } from '../types/citation';
|
|
1
2
|
import { TypeValidationError, type JSONObject } from '@ai-sdk/provider';
|
|
2
3
|
import {
|
|
3
4
|
lazySchema,
|
|
@@ -95,6 +96,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
95
96
|
z.object({
|
|
96
97
|
type: z.literal('text'),
|
|
97
98
|
text: z.string(),
|
|
99
|
+
citations: z.array(citationSchema).optional(),
|
|
98
100
|
state: z.enum(['streaming', 'done']).optional(),
|
|
99
101
|
providerMetadata: providerMetadataSchema.optional(),
|
|
100
102
|
}),
|
|
@@ -95,6 +95,7 @@ export function readUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
95
95
|
consumeStream({
|
|
96
96
|
stream: processUIMessageStream({
|
|
97
97
|
stream,
|
|
98
|
+
resetStateOnMessageIdChange: true,
|
|
98
99
|
runUpdateMessageJob(
|
|
99
100
|
job: (options: {
|
|
100
101
|
state: StreamingUIMessageState<UI_MESSAGE>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { citationSchema, type Citation } from '../types/citation';
|
|
1
2
|
import type { JSONObject } from '@ai-sdk/provider';
|
|
2
3
|
import {
|
|
3
4
|
providerMetadataSchema,
|
|
@@ -37,6 +38,7 @@ export const uiMessageChunkSchema = lazySchema(() =>
|
|
|
37
38
|
z.looseObject({
|
|
38
39
|
type: z.literal('text-end'),
|
|
39
40
|
id: z.string(),
|
|
41
|
+
citations: z.array(citationSchema).optional(),
|
|
40
42
|
providerMetadata: providerMetadataSchema.optional(),
|
|
41
43
|
}),
|
|
42
44
|
z.looseObject({
|
|
@@ -246,6 +248,7 @@ export type UIMessageChunk<
|
|
|
246
248
|
}
|
|
247
249
|
| {
|
|
248
250
|
type: 'text-end';
|
|
251
|
+
citations?: Array<Citation>;
|
|
249
252
|
id: string;
|
|
250
253
|
providerMetadata?: ProviderMetadata;
|
|
251
254
|
}
|