ai 6.0.301 → 6.0.303
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 +25 -0
- package/dist/index.d.mts +26 -4
- package/dist/index.d.ts +26 -4
- package/dist/index.js +189 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +189 -63
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +1 -1
- package/dist/internal/index.mjs.map +1 -1
- package/docs/02-foundations/04-tools.mdx +1 -1
- package/docs/03-agents/06-memory.mdx +1 -1
- package/docs/03-ai-sdk-core/35-image-generation.mdx +2 -0
- package/docs/03-ai-sdk-core/45-provider-management.mdx +2 -2
- package/docs/04-ai-sdk-ui/21-error-handling.mdx +34 -0
- package/package.json +3 -3
- package/src/global.ts +23 -4
- package/src/model/as-language-model-v3.ts +4 -0
- package/src/types/usage.ts +21 -0
- package/src/ui/chat.ts +117 -34
- package/src/ui/convert-to-model-messages.ts +41 -8
- package/src/ui/http-chat-transport.ts +5 -3
- package/src/ui/process-ui-message-stream.ts +21 -0
- package/src/ui/ui-messages.ts +8 -0
- package/src/ui/unavailable-tool.ts +17 -0
- package/src/ui/validate-ui-messages.ts +12 -3
- package/src/ui-message-stream/read-ui-message-stream.ts +1 -0
- package/src/util/serial-job-executor.ts +22 -5
package/src/global.ts
CHANGED
|
@@ -2,8 +2,20 @@ import type { ProviderV3 } from '@ai-sdk/provider';
|
|
|
2
2
|
import type { LogWarningsFunction } from './logger/log-warnings';
|
|
3
3
|
import type { TelemetryIntegration } from './telemetry/telemetry-integration';
|
|
4
4
|
|
|
5
|
-
//
|
|
5
|
+
// Each installed copy contributes its own types without conflicting with other
|
|
6
|
+
// versions. This is a type-only key; no symbol is created at runtime.
|
|
7
|
+
declare const aiSdkGlobalsKey: unique symbol;
|
|
8
|
+
|
|
6
9
|
declare global {
|
|
10
|
+
interface AISDKGlobalTypes {
|
|
11
|
+
[aiSdkGlobalsKey]: {
|
|
12
|
+
provider: ProviderV3;
|
|
13
|
+
logWarnings: LogWarningsFunction;
|
|
14
|
+
telemetry: TelemetryIntegration;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Keep these registry-based declarations identical across major versions.
|
|
7
19
|
/**
|
|
8
20
|
* The default provider to use for the AI SDK.
|
|
9
21
|
* String model ids are resolved to the default provider and model id.
|
|
@@ -12,7 +24,9 @@ declare global {
|
|
|
12
24
|
*
|
|
13
25
|
* @see https://ai-sdk.dev/docs/ai-sdk-core/provider-management#global-provider-configuration
|
|
14
26
|
*/
|
|
15
|
-
var AI_SDK_DEFAULT_PROVIDER:
|
|
27
|
+
var AI_SDK_DEFAULT_PROVIDER:
|
|
28
|
+
| AISDKGlobalTypes[keyof AISDKGlobalTypes]['provider']
|
|
29
|
+
| undefined;
|
|
16
30
|
|
|
17
31
|
/**
|
|
18
32
|
* The warning logger to use for the AI SDK.
|
|
@@ -21,7 +35,10 @@ declare global {
|
|
|
21
35
|
*
|
|
22
36
|
* If set to false, no warnings are logged.
|
|
23
37
|
*/
|
|
24
|
-
var AI_SDK_LOG_WARNINGS:
|
|
38
|
+
var AI_SDK_LOG_WARNINGS:
|
|
39
|
+
| AISDKGlobalTypes[keyof AISDKGlobalTypes]['logWarnings']
|
|
40
|
+
| undefined
|
|
41
|
+
| false;
|
|
25
42
|
|
|
26
43
|
/**
|
|
27
44
|
* Globally registered telemetry integrations for the AI SDK.
|
|
@@ -32,5 +49,7 @@ declare global {
|
|
|
32
49
|
* Prefer using `registerTelemetryIntegration()` from `'ai'` instead of
|
|
33
50
|
* assigning this directly.
|
|
34
51
|
*/
|
|
35
|
-
var AI_SDK_TELEMETRY_INTEGRATIONS:
|
|
52
|
+
var AI_SDK_TELEMETRY_INTEGRATIONS:
|
|
53
|
+
| Array<AISDKGlobalTypes[keyof AISDKGlobalTypes]['telemetry']>
|
|
54
|
+
| undefined;
|
|
36
55
|
}
|
|
@@ -99,5 +99,9 @@ function convertV2UsageToV3(usage: LanguageModelV2Usage): LanguageModelV3Usage {
|
|
|
99
99
|
text: undefined,
|
|
100
100
|
reasoning: usage.reasoningTokens,
|
|
101
101
|
},
|
|
102
|
+
raw:
|
|
103
|
+
usage.totalTokens == null
|
|
104
|
+
? undefined
|
|
105
|
+
: { totalTokens: usage.totalTokens },
|
|
102
106
|
};
|
|
103
107
|
}
|
package/src/types/usage.ts
CHANGED
|
@@ -135,6 +135,12 @@ export function addLanguageModelUsage(
|
|
|
135
135
|
usage1: LanguageModelUsage,
|
|
136
136
|
usage2: LanguageModelUsage,
|
|
137
137
|
): LanguageModelUsage {
|
|
138
|
+
const raw = isNullLanguageModelUsage(usage1)
|
|
139
|
+
? usage2.raw
|
|
140
|
+
: isNullLanguageModelUsage(usage2)
|
|
141
|
+
? usage1.raw
|
|
142
|
+
: undefined;
|
|
143
|
+
|
|
138
144
|
return {
|
|
139
145
|
inputTokens: addTokenCounts(usage1.inputTokens, usage2.inputTokens),
|
|
140
146
|
inputTokenDetails: {
|
|
@@ -171,9 +177,24 @@ export function addLanguageModelUsage(
|
|
|
171
177
|
usage1.cachedInputTokens,
|
|
172
178
|
usage2.cachedInputTokens,
|
|
173
179
|
),
|
|
180
|
+
...(raw == null ? {} : { raw }),
|
|
174
181
|
};
|
|
175
182
|
}
|
|
176
183
|
|
|
184
|
+
function isNullLanguageModelUsage(usage: LanguageModelUsage): boolean {
|
|
185
|
+
return (
|
|
186
|
+
usage.inputTokens == null &&
|
|
187
|
+
usage.inputTokenDetails?.noCacheTokens == null &&
|
|
188
|
+
usage.inputTokenDetails?.cacheReadTokens == null &&
|
|
189
|
+
usage.inputTokenDetails?.cacheWriteTokens == null &&
|
|
190
|
+
usage.outputTokens == null &&
|
|
191
|
+
usage.outputTokenDetails?.textTokens == null &&
|
|
192
|
+
usage.outputTokenDetails?.reasoningTokens == null &&
|
|
193
|
+
usage.totalTokens == null &&
|
|
194
|
+
usage.raw == null
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
177
198
|
function addTokenCounts(
|
|
178
199
|
tokenCount1: number | undefined,
|
|
179
200
|
tokenCount2: number | undefined,
|
package/src/ui/chat.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import type { FinishReason } from '../types/language-model';
|
|
8
8
|
import type { UIMessageChunk } from '../ui-message-stream/ui-message-chunks';
|
|
9
9
|
import { consumeStream } from '../util/consume-stream';
|
|
10
|
+
import { createResolvablePromise } from '../util/create-resolvable-promise';
|
|
10
11
|
import { SerialJobExecutor } from '../util/serial-job-executor';
|
|
11
12
|
import type { ChatTransport } from './chat-transport';
|
|
12
13
|
import { convertFileListToFileUIParts } from './convert-file-list-to-file-ui-parts';
|
|
@@ -135,12 +136,19 @@ export type ChatStatus = 'submitted' | 'streaming' | 'ready' | 'error';
|
|
|
135
136
|
type ActiveResponse<UI_MESSAGE extends UIMessage> = {
|
|
136
137
|
state: StreamingUIMessageState<UI_MESSAGE>;
|
|
137
138
|
abortController: AbortController;
|
|
139
|
+
completionPromise: Promise<void>;
|
|
138
140
|
};
|
|
139
141
|
|
|
140
142
|
type ActiveResumeRequest = {
|
|
141
143
|
abortController: AbortController;
|
|
144
|
+
completionPromise: Promise<void>;
|
|
142
145
|
};
|
|
143
146
|
|
|
147
|
+
type MakeRequestOptions = {
|
|
148
|
+
trigger: 'submit-message' | 'resume-stream' | 'regenerate-message';
|
|
149
|
+
messageId?: string;
|
|
150
|
+
} & ChatRequestOptions;
|
|
151
|
+
|
|
144
152
|
export interface ChatState<UI_MESSAGE extends UIMessage> {
|
|
145
153
|
status: ChatStatus;
|
|
146
154
|
|
|
@@ -266,6 +274,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
266
274
|
| StreamingUIMessageState<UI_MESSAGE>
|
|
267
275
|
| undefined = undefined;
|
|
268
276
|
private jobExecutor = new SerialJobExecutor();
|
|
277
|
+
private activeStopCount = 0;
|
|
278
|
+
private stopGeneration = 0;
|
|
269
279
|
|
|
270
280
|
constructor({
|
|
271
281
|
generateId = generateIdFunc,
|
|
@@ -314,7 +324,12 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
314
324
|
status: ChatStatus;
|
|
315
325
|
error?: Error;
|
|
316
326
|
}) {
|
|
317
|
-
if (this.status === status)
|
|
327
|
+
if (this.status === status) {
|
|
328
|
+
if (this.error !== error) {
|
|
329
|
+
this.state.error = error;
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
318
333
|
|
|
319
334
|
this.state.status = status;
|
|
320
335
|
this.state.error = error;
|
|
@@ -567,21 +582,23 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
567
582
|
this.status !== 'submitted' &&
|
|
568
583
|
this.sendAutomaticallyWhen
|
|
569
584
|
) {
|
|
570
|
-
this.shouldSendAutomatically()
|
|
571
|
-
|
|
572
|
-
|
|
585
|
+
const shouldSend = await this.shouldSendAutomatically();
|
|
586
|
+
|
|
587
|
+
if (shouldSend) {
|
|
588
|
+
// no await to avoid deadlocking
|
|
589
|
+
void this.runAutomaticRequest(() => {
|
|
573
590
|
const messageId =
|
|
574
591
|
messageIndex === -1
|
|
575
592
|
? this.lastMessage?.id
|
|
576
593
|
: messages[messageIndex].id;
|
|
577
594
|
|
|
578
|
-
this.makeRequestForToolApproval({
|
|
595
|
+
return this.makeRequestForToolApproval({
|
|
579
596
|
messageId,
|
|
580
597
|
messageIndex,
|
|
581
598
|
...options,
|
|
582
599
|
});
|
|
583
|
-
}
|
|
584
|
-
}
|
|
600
|
+
}, shouldSend);
|
|
601
|
+
}
|
|
585
602
|
}
|
|
586
603
|
});
|
|
587
604
|
|
|
@@ -621,16 +638,20 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
621
638
|
this.status !== 'submitted' &&
|
|
622
639
|
this.sendAutomaticallyWhen
|
|
623
640
|
) {
|
|
624
|
-
this.shouldSendAutomatically()
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
641
|
+
const shouldSend = await this.shouldSendAutomatically();
|
|
642
|
+
|
|
643
|
+
if (shouldSend) {
|
|
644
|
+
// no await to avoid deadlocking
|
|
645
|
+
void this.runAutomaticRequest(
|
|
646
|
+
() =>
|
|
647
|
+
this.makeRequest({
|
|
648
|
+
trigger: 'submit-message',
|
|
649
|
+
messageId: this.lastMessage?.id,
|
|
650
|
+
...options,
|
|
651
|
+
}),
|
|
652
|
+
shouldSend,
|
|
653
|
+
);
|
|
654
|
+
}
|
|
634
655
|
}
|
|
635
656
|
});
|
|
636
657
|
|
|
@@ -638,11 +659,31 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
638
659
|
addToolResult = this.addToolOutput;
|
|
639
660
|
|
|
640
661
|
/**
|
|
641
|
-
* Abort the current request
|
|
662
|
+
* Abort the current request, keep the generated tokens if any, and wait for
|
|
663
|
+
* the request pipeline to finish.
|
|
642
664
|
*/
|
|
643
665
|
stop = async () => {
|
|
644
|
-
this.
|
|
645
|
-
this.
|
|
666
|
+
this.activeStopCount++;
|
|
667
|
+
this.stopGeneration++;
|
|
668
|
+
|
|
669
|
+
try {
|
|
670
|
+
const activeResumeRequest = this.activeResumeRequest;
|
|
671
|
+
const activeResponse = this.activeResponse;
|
|
672
|
+
|
|
673
|
+
activeResumeRequest?.abortController.abort();
|
|
674
|
+
activeResponse?.abortController.abort();
|
|
675
|
+
|
|
676
|
+
await Promise.all([
|
|
677
|
+
activeResumeRequest?.completionPromise,
|
|
678
|
+
activeResponse?.completionPromise,
|
|
679
|
+
]);
|
|
680
|
+
|
|
681
|
+
// Stream cancellation can complete while a processing job is still
|
|
682
|
+
// blocked in onToolCall. Drain that job and any message update it queued.
|
|
683
|
+
await this.jobExecutor.waitForIdle();
|
|
684
|
+
} finally {
|
|
685
|
+
this.activeStopCount--;
|
|
686
|
+
}
|
|
646
687
|
};
|
|
647
688
|
|
|
648
689
|
private async shouldSendAutomatically(): Promise<boolean> {
|
|
@@ -660,6 +701,30 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
660
701
|
return result as boolean;
|
|
661
702
|
}
|
|
662
703
|
|
|
704
|
+
private async runAutomaticRequest(
|
|
705
|
+
request: () => Promise<void>,
|
|
706
|
+
shouldSend?: boolean,
|
|
707
|
+
): Promise<void> {
|
|
708
|
+
const stopGeneration = this.stopGeneration;
|
|
709
|
+
const startedWhileStopping = this.activeStopCount > 0;
|
|
710
|
+
|
|
711
|
+
if (!(shouldSend ?? (await this.shouldSendAutomatically()))) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// A stop invalidates automatic requests that were pending when it started,
|
|
716
|
+
// including requests queued by callback-driven tool updates.
|
|
717
|
+
if (
|
|
718
|
+
startedWhileStopping ||
|
|
719
|
+
stopGeneration !== this.stopGeneration ||
|
|
720
|
+
this.activeStopCount > 0
|
|
721
|
+
) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
await request();
|
|
726
|
+
}
|
|
727
|
+
|
|
663
728
|
private async makeRequestForToolApproval({
|
|
664
729
|
messageId,
|
|
665
730
|
messageIndex,
|
|
@@ -690,23 +755,38 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
690
755
|
}
|
|
691
756
|
}
|
|
692
757
|
|
|
693
|
-
private async makeRequest({
|
|
758
|
+
private async makeRequest(options: MakeRequestOptions) {
|
|
759
|
+
const completion = createResolvablePromise<void>();
|
|
760
|
+
|
|
761
|
+
try {
|
|
762
|
+
await this.makeRequestImpl({
|
|
763
|
+
...options,
|
|
764
|
+
completionPromise: completion.promise,
|
|
765
|
+
});
|
|
766
|
+
} finally {
|
|
767
|
+
completion.resolve();
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
private async makeRequestImpl({
|
|
694
772
|
trigger,
|
|
695
773
|
metadata,
|
|
696
774
|
headers,
|
|
697
775
|
body,
|
|
698
776
|
messageId,
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
}
|
|
777
|
+
completionPromise,
|
|
778
|
+
}: MakeRequestOptions & {
|
|
779
|
+
completionPromise: Promise<void>;
|
|
780
|
+
}) {
|
|
703
781
|
if (trigger !== 'resume-stream') {
|
|
704
782
|
this.resumableStreamState = undefined;
|
|
705
783
|
}
|
|
706
784
|
|
|
707
785
|
const abortController = new AbortController();
|
|
708
786
|
const activeResumeRequest =
|
|
709
|
-
trigger === 'resume-stream'
|
|
787
|
+
trigger === 'resume-stream'
|
|
788
|
+
? { abortController, completionPromise }
|
|
789
|
+
: undefined;
|
|
710
790
|
|
|
711
791
|
if (activeResumeRequest) {
|
|
712
792
|
this.activeResumeRequest?.abortController.abort();
|
|
@@ -825,6 +905,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
825
905
|
messageId: this.generateId(),
|
|
826
906
|
}),
|
|
827
907
|
abortController,
|
|
908
|
+
completionPromise,
|
|
828
909
|
} as ActiveResponse<UI_MESSAGE>;
|
|
829
910
|
|
|
830
911
|
activeResponse = response;
|
|
@@ -987,14 +1068,16 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
987
1068
|
}
|
|
988
1069
|
|
|
989
1070
|
// automatically send the message if the sendAutomaticallyWhen function returns true
|
|
990
|
-
if (!
|
|
991
|
-
await this.
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1071
|
+
if (!isAbort && !isError) {
|
|
1072
|
+
await this.runAutomaticRequest(() =>
|
|
1073
|
+
this.makeRequest({
|
|
1074
|
+
trigger: 'submit-message',
|
|
1075
|
+
messageId: this.lastMessage?.id,
|
|
1076
|
+
metadata,
|
|
1077
|
+
headers,
|
|
1078
|
+
body,
|
|
1079
|
+
}),
|
|
1080
|
+
);
|
|
998
1081
|
}
|
|
999
1082
|
}
|
|
1000
1083
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import type { ToolSet } from '../generate-text/tool-set';
|
|
11
11
|
import { createToolModelOutput } from '../prompt/create-tool-model-output';
|
|
12
12
|
import { MessageConversionError } from '../prompt/message-conversion-error';
|
|
13
|
+
import { getOwn } from '../util/get-own';
|
|
13
14
|
import {
|
|
14
15
|
getToolName,
|
|
15
16
|
isDataUIPart,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
type ToolUIPart,
|
|
28
29
|
type UIMessage,
|
|
29
30
|
} from './ui-messages';
|
|
31
|
+
import { isToolPartFromUnavailableTool } from './unavailable-tool';
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* Converts an array of UI messages from useChat into an array of ModelMessages that can be used
|
|
@@ -65,6 +67,39 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
65
67
|
}));
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
async function createModelOutput({
|
|
71
|
+
toolPart,
|
|
72
|
+
toolName,
|
|
73
|
+
output,
|
|
74
|
+
errorMode,
|
|
75
|
+
}: {
|
|
76
|
+
toolPart: ToolUIPart<InferUIMessageTools<UI_MESSAGE>> | DynamicToolUIPart;
|
|
77
|
+
toolName: string;
|
|
78
|
+
output: unknown;
|
|
79
|
+
errorMode: 'none' | 'text' | 'json';
|
|
80
|
+
}) {
|
|
81
|
+
const tool = getOwn(options?.tools, toolName);
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
errorMode === 'none' &&
|
|
85
|
+
tool == null &&
|
|
86
|
+
isToolPartFromUnavailableTool(toolPart)
|
|
87
|
+
) {
|
|
88
|
+
return {
|
|
89
|
+
type: 'text' as const,
|
|
90
|
+
value: 'Tool output omitted because the tool is no longer available.',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return createToolModelOutput({
|
|
95
|
+
toolCallId: toolPart.toolCallId,
|
|
96
|
+
input: toolPart.input,
|
|
97
|
+
output,
|
|
98
|
+
tool,
|
|
99
|
+
errorMode,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
68
103
|
for (const message of messages) {
|
|
69
104
|
switch (message.role) {
|
|
70
105
|
case 'system': {
|
|
@@ -231,14 +266,13 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
231
266
|
type: 'tool-result',
|
|
232
267
|
toolCallId: part.toolCallId,
|
|
233
268
|
toolName,
|
|
234
|
-
output: await
|
|
235
|
-
|
|
236
|
-
|
|
269
|
+
output: await createModelOutput({
|
|
270
|
+
toolPart: part,
|
|
271
|
+
toolName,
|
|
237
272
|
output:
|
|
238
273
|
part.state === 'output-error'
|
|
239
274
|
? part.errorText
|
|
240
275
|
: part.output,
|
|
241
|
-
tool: options?.tools?.[toolName],
|
|
242
276
|
errorMode:
|
|
243
277
|
part.state === 'output-error' ? 'json' : 'none',
|
|
244
278
|
}),
|
|
@@ -331,14 +365,13 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
|
|
|
331
365
|
type: 'tool-result',
|
|
332
366
|
toolCallId: toolPart.toolCallId,
|
|
333
367
|
toolName,
|
|
334
|
-
output: await
|
|
335
|
-
|
|
336
|
-
|
|
368
|
+
output: await createModelOutput({
|
|
369
|
+
toolPart,
|
|
370
|
+
toolName,
|
|
337
371
|
output:
|
|
338
372
|
toolPart.state === 'output-error'
|
|
339
373
|
? toolPart.errorText
|
|
340
374
|
: toolPart.output,
|
|
341
|
-
tool: options?.tools?.[toolName],
|
|
342
375
|
errorMode:
|
|
343
376
|
toolPart.state === 'output-error' ? 'text' : 'none',
|
|
344
377
|
}),
|
|
@@ -11,10 +11,12 @@ import type { UIMessage } from './ui-messages';
|
|
|
11
11
|
|
|
12
12
|
function appendPathToUrl(url: string, path: string): string {
|
|
13
13
|
const queryOrFragmentStart = url.search(/[?#]/);
|
|
14
|
+
const urlPath =
|
|
15
|
+
queryOrFragmentStart === -1 ? url : url.slice(0, queryOrFragmentStart);
|
|
16
|
+
const suffix =
|
|
17
|
+
queryOrFragmentStart === -1 ? '' : url.slice(queryOrFragmentStart);
|
|
14
18
|
|
|
15
|
-
return
|
|
16
|
-
? `${url}${path}`
|
|
17
|
-
: `${url.slice(0, queryOrFragmentStart)}${path}${url.slice(queryOrFragmentStart)}`;
|
|
19
|
+
return `${urlPath.endsWith('/') && path.startsWith('/') ? urlPath.slice(0, -1) : urlPath}${path}${suffix}`;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export type PrepareSendMessagesRequest<UI_MESSAGE extends UIMessage> = (
|
|
@@ -120,6 +120,7 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
120
120
|
onError,
|
|
121
121
|
onToolCall,
|
|
122
122
|
onData,
|
|
123
|
+
resetStateOnMessageIdChange = false,
|
|
123
124
|
}: {
|
|
124
125
|
// input stream is not fully typed yet:
|
|
125
126
|
stream: ReadableStream<UIMessageChunk>;
|
|
@@ -136,6 +137,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
136
137
|
}) => Promise<void>,
|
|
137
138
|
) => Promise<void>;
|
|
138
139
|
onError: ErrorHandler;
|
|
140
|
+
// During resume, a different message ID identifies a separate response.
|
|
141
|
+
resetStateOnMessageIdChange?: boolean;
|
|
139
142
|
}): ReadableStream<InferUIMessageChunk<UI_MESSAGE>> {
|
|
140
143
|
return stream.pipeThrough(
|
|
141
144
|
new TransformStream<UIMessageChunk, InferUIMessageChunk<UI_MESSAGE>>({
|
|
@@ -878,6 +881,24 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
|
|
|
878
881
|
}
|
|
879
882
|
|
|
880
883
|
case 'start': {
|
|
884
|
+
if (
|
|
885
|
+
resetStateOnMessageIdChange &&
|
|
886
|
+
chunk.messageId != null &&
|
|
887
|
+
chunk.messageId !== state.message.id
|
|
888
|
+
) {
|
|
889
|
+
// Start the separate response with empty parts and metadata.
|
|
890
|
+
// Reset the active-part maps too, so earlier chunks stay with
|
|
891
|
+
// the previous response. Keep the state object for its callers.
|
|
892
|
+
Object.assign(
|
|
893
|
+
state,
|
|
894
|
+
createStreamingUIMessageState({
|
|
895
|
+
lastMessage: undefined,
|
|
896
|
+
messageId: chunk.messageId,
|
|
897
|
+
}),
|
|
898
|
+
{ finishReason: undefined },
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
|
|
881
902
|
if (chunk.messageId != null) {
|
|
882
903
|
state.message.id = chunk.messageId;
|
|
883
904
|
}
|
package/src/ui/ui-messages.ts
CHANGED
|
@@ -348,6 +348,14 @@ export type ToolUIPart<TOOLS extends UITools = UITools> = ValueOf<{
|
|
|
348
348
|
export type DynamicToolUIPart = {
|
|
349
349
|
type: 'dynamic-tool';
|
|
350
350
|
|
|
351
|
+
/**
|
|
352
|
+
* `false` when a static tool part was normalized to a dynamic tool part
|
|
353
|
+
* because its current tool schema was unavailable or incompatible.
|
|
354
|
+
*
|
|
355
|
+
* This preserves its static origin across persistence boundaries.
|
|
356
|
+
*/
|
|
357
|
+
dynamic?: false;
|
|
358
|
+
|
|
351
359
|
/**
|
|
352
360
|
* Name of the tool that is being called.
|
|
353
361
|
*/
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DynamicToolUIPart, ToolUIPart } from './ui-messages';
|
|
2
|
+
|
|
3
|
+
export function markToolPartAsUnavailable(
|
|
4
|
+
part: DynamicToolUIPart,
|
|
5
|
+
): DynamicToolUIPart {
|
|
6
|
+
// Preserve the static tool's provenance across persistence boundaries so
|
|
7
|
+
// model-message conversion never falls back to its unfiltered raw output.
|
|
8
|
+
part.dynamic = false;
|
|
9
|
+
|
|
10
|
+
return part;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isToolPartFromUnavailableTool(
|
|
14
|
+
part: ToolUIPart | DynamicToolUIPart,
|
|
15
|
+
): boolean {
|
|
16
|
+
return part.type === 'dynamic-tool' && part.dynamic === false;
|
|
17
|
+
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { z } from 'zod/v4';
|
|
16
16
|
import { InvalidArgumentError } from '../error';
|
|
17
17
|
import { jsonValueSchema } from '../types/json-value';
|
|
18
|
+
import { getOwn } from '../util/get-own';
|
|
18
19
|
import { isDeepEqualData } from '../util/is-deep-equal-data';
|
|
19
20
|
import { providerMetadataSchema } from '../types/provider-metadata';
|
|
20
21
|
import type {
|
|
@@ -25,6 +26,7 @@ import type {
|
|
|
25
26
|
ToolUIPart,
|
|
26
27
|
UIMessage,
|
|
27
28
|
} from './ui-messages';
|
|
29
|
+
import { markToolPartAsUnavailable } from './unavailable-tool';
|
|
28
30
|
|
|
29
31
|
const toolMetadataSchema: z.ZodType<JSONObject> = z.record(
|
|
30
32
|
z.string(),
|
|
@@ -43,11 +45,11 @@ function isEmptyObject(value: unknown): value is Record<string, never> {
|
|
|
43
45
|
function asDynamicToolPart(toolPart: ToolUIPart): DynamicToolUIPart {
|
|
44
46
|
const { type, ...part } = toolPart;
|
|
45
47
|
|
|
46
|
-
return {
|
|
48
|
+
return markToolPartAsUnavailable({
|
|
47
49
|
...part,
|
|
48
50
|
type: 'dynamic-tool',
|
|
49
51
|
toolName: type.slice(5),
|
|
50
|
-
} as DynamicToolUIPart;
|
|
52
|
+
} as DynamicToolUIPart);
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
function getToolPartInputSchemaInput(
|
|
@@ -134,6 +136,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
134
136
|
}),
|
|
135
137
|
z.object({
|
|
136
138
|
type: z.literal('dynamic-tool'),
|
|
139
|
+
dynamic: z.literal(false).optional(),
|
|
137
140
|
toolName: z.string(),
|
|
138
141
|
toolCallId: z.string(),
|
|
139
142
|
toolMetadata: toolMetadataSchema.optional(),
|
|
@@ -148,6 +151,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
148
151
|
}),
|
|
149
152
|
z.object({
|
|
150
153
|
type: z.literal('dynamic-tool'),
|
|
154
|
+
dynamic: z.literal(false).optional(),
|
|
151
155
|
toolName: z.string(),
|
|
152
156
|
toolCallId: z.string(),
|
|
153
157
|
toolMetadata: toolMetadataSchema.optional(),
|
|
@@ -161,6 +165,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
161
165
|
}),
|
|
162
166
|
z.object({
|
|
163
167
|
type: z.literal('dynamic-tool'),
|
|
168
|
+
dynamic: z.literal(false).optional(),
|
|
164
169
|
toolName: z.string(),
|
|
165
170
|
toolCallId: z.string(),
|
|
166
171
|
toolMetadata: toolMetadataSchema.optional(),
|
|
@@ -174,6 +179,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
174
179
|
}),
|
|
175
180
|
z.object({
|
|
176
181
|
type: z.literal('dynamic-tool'),
|
|
182
|
+
dynamic: z.literal(false).optional(),
|
|
177
183
|
toolName: z.string(),
|
|
178
184
|
toolCallId: z.string(),
|
|
179
185
|
toolMetadata: toolMetadataSchema.optional(),
|
|
@@ -187,6 +193,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
187
193
|
}),
|
|
188
194
|
z.object({
|
|
189
195
|
type: z.literal('dynamic-tool'),
|
|
196
|
+
dynamic: z.literal(false).optional(),
|
|
190
197
|
toolName: z.string(),
|
|
191
198
|
toolCallId: z.string(),
|
|
192
199
|
toolMetadata: toolMetadataSchema.optional(),
|
|
@@ -202,6 +209,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
202
209
|
}),
|
|
203
210
|
z.object({
|
|
204
211
|
type: z.literal('dynamic-tool'),
|
|
212
|
+
dynamic: z.literal(false).optional(),
|
|
205
213
|
toolName: z.string(),
|
|
206
214
|
toolCallId: z.string(),
|
|
207
215
|
toolMetadata: toolMetadataSchema.optional(),
|
|
@@ -217,6 +225,7 @@ const uiMessagesSchema = lazySchema(() => {
|
|
|
217
225
|
}),
|
|
218
226
|
z.object({
|
|
219
227
|
type: z.literal('dynamic-tool'),
|
|
228
|
+
dynamic: z.literal(false).optional(),
|
|
220
229
|
toolName: z.string(),
|
|
221
230
|
toolCallId: z.string(),
|
|
222
231
|
toolMetadata: toolMetadataSchema.optional(),
|
|
@@ -451,7 +460,7 @@ async function safeValidateUIMessagesInternal<UI_MESSAGE extends UIMessage>(
|
|
|
451
460
|
InferUIMessageTools<UI_MESSAGE>
|
|
452
461
|
>;
|
|
453
462
|
const toolName = toolPart.type.slice(5);
|
|
454
|
-
const tool = tools
|
|
463
|
+
const tool = getOwn(tools, toolName);
|
|
455
464
|
const isTerminal =
|
|
456
465
|
toolPart.state === 'output-available' ||
|
|
457
466
|
toolPart.state === 'output-error' ||
|
|
@@ -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>;
|
|
@@ -3,6 +3,7 @@ import type { Job } from './job';
|
|
|
3
3
|
export class SerialJobExecutor {
|
|
4
4
|
private queue: Array<Job> = [];
|
|
5
5
|
private isProcessing = false;
|
|
6
|
+
private idleWaiters: Array<() => void> = [];
|
|
6
7
|
|
|
7
8
|
private async processQueue() {
|
|
8
9
|
if (this.isProcessing) {
|
|
@@ -11,12 +12,18 @@ export class SerialJobExecutor {
|
|
|
11
12
|
|
|
12
13
|
this.isProcessing = true;
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
try {
|
|
16
|
+
while (this.queue.length > 0) {
|
|
17
|
+
await this.queue[0]();
|
|
18
|
+
this.queue.shift();
|
|
19
|
+
}
|
|
20
|
+
} finally {
|
|
21
|
+
this.isProcessing = false;
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
for (const resolve of this.idleWaiters.splice(0)) {
|
|
24
|
+
resolve();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
async run(job: Job): Promise<void> {
|
|
@@ -33,4 +40,14 @@ export class SerialJobExecutor {
|
|
|
33
40
|
void this.processQueue();
|
|
34
41
|
});
|
|
35
42
|
}
|
|
43
|
+
|
|
44
|
+
async waitForIdle(): Promise<void> {
|
|
45
|
+
if (!this.isProcessing && this.queue.length === 0) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await new Promise<void>(resolve => {
|
|
50
|
+
this.idleWaiters.push(resolve);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
36
53
|
}
|