ai 7.0.55 → 7.0.57
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 +8 -1
- package/dist/index.js +134 -57
- package/dist/index.js.map +1 -1
- package/dist/internal/index.d.ts +2 -0
- package/dist/internal/index.js +1 -1
- package/docs/03-ai-sdk-core/05-generating-text.mdx +2 -2
- package/docs/03-ai-sdk-core/60-telemetry.mdx +2 -2
- package/docs/03-ai-sdk-core/65-lifecycle-callbacks.mdx +18 -1
- package/docs/07-reference/01-ai-sdk-core/01-generate-text.mdx +6 -0
- package/docs/07-reference/01-ai-sdk-core/02-stream-text.mdx +6 -0
- package/package.json +3 -3
- package/src/generate-text/generate-text.ts +5 -0
- package/src/generate-text/language-model-events.ts +4 -0
- package/src/generate-text/stream-language-model-call.ts +3 -0
- package/src/generate-video/generate-video.ts +17 -6
- package/src/ui/chat-transport.ts +3 -0
- package/src/ui/chat.ts +87 -12
- package/src/ui/http-chat-transport.ts +1 -0
- package/src/util/consume-stream.ts +13 -0
package/dist/internal/index.d.ts
CHANGED
|
@@ -1399,6 +1399,8 @@ type LanguageModelCallEndEvent<TOOLS extends ToolSet = ToolSet> = ModelInfo & {
|
|
|
1399
1399
|
readonly content: ReadonlyArray<ContentPart<TOOLS>>;
|
|
1400
1400
|
/** The provider-returned response id for this model call. */
|
|
1401
1401
|
readonly responseId: string;
|
|
1402
|
+
/** Optional provider-specific metadata for this model call. */
|
|
1403
|
+
readonly providerMetadata?: ProviderMetadata;
|
|
1402
1404
|
/** Performance metrics for the model call. */
|
|
1403
1405
|
readonly performance: {
|
|
1404
1406
|
/** Time spent waiting for the language model response in milliseconds. */
|
package/dist/internal/index.js
CHANGED
|
@@ -187,7 +187,7 @@ The available lifecycle callbacks are:
|
|
|
187
187
|
- **`onStart`**: Called once when the `generateText` operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`.
|
|
188
188
|
- **`onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
|
|
189
189
|
- **`onLanguageModelCallStart`**: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution.
|
|
190
|
-
- **`onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, and
|
|
190
|
+
- **`onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, finish reason, and provider metadata.
|
|
191
191
|
- **`onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`.
|
|
192
192
|
- **`onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call object, `toolExecutionMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`).
|
|
193
193
|
- **`onStepEnd`**: Called after each step finishes. Includes `stepNumber` (zero-based index of the completed step).
|
|
@@ -417,7 +417,7 @@ The available lifecycle callbacks are:
|
|
|
417
417
|
- **`onStart`**: Called once when the `streamText` operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`.
|
|
418
418
|
- **`onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps.
|
|
419
419
|
- **`onLanguageModelCallStart`**: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution.
|
|
420
|
-
- **`onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, and
|
|
420
|
+
- **`onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, finish reason, and provider metadata.
|
|
421
421
|
- **`onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`.
|
|
422
422
|
- **`onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call object, `toolExecutionMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`).
|
|
423
423
|
- **`onStepEnd`**: Called after each step finishes. Receives the finish reason, usage, and other step details.
|
|
@@ -356,7 +356,7 @@ export function myIntegration(): Telemetry {
|
|
|
356
356
|
name: 'onLanguageModelCallEnd',
|
|
357
357
|
type: '(event: LanguageModelCallEndEvent) => void | PromiseLike<void>',
|
|
358
358
|
description:
|
|
359
|
-
'Called after the model response has been normalized and parsed, but before any client-side tool execution begins.',
|
|
359
|
+
'Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Includes provider-specific metadata when available.',
|
|
360
360
|
},
|
|
361
361
|
{
|
|
362
362
|
name: 'onToolExecutionStart',
|
|
@@ -628,7 +628,7 @@ registerTelemetry(
|
|
|
628
628
|
The available options are:
|
|
629
629
|
|
|
630
630
|
- `usage`: detailed usage attributes that are not covered by GenAI usage attributes, such as uncached input tokens and output text/reasoning token details.
|
|
631
|
-
- `providerMetadata`: `ai.response.providerMetadata
|
|
631
|
+
- `providerMetadata`: `ai.response.providerMetadata` on operation, step, and model-call spans.
|
|
632
632
|
- `embedding`: embedding inputs and outputs.
|
|
633
633
|
- `reranking`: rerank input documents and ranking output.
|
|
634
634
|
- `runtimeContext`: `ai.settings.context.*`.
|
|
@@ -97,7 +97,13 @@ const result = streamText({
|
|
|
97
97
|
model: __MODEL__,
|
|
98
98
|
prompt: 'Explain partial prerendering in two paragraphs.',
|
|
99
99
|
|
|
100
|
-
onLanguageModelCallEnd({
|
|
100
|
+
onLanguageModelCallEnd({
|
|
101
|
+
callId,
|
|
102
|
+
modelId,
|
|
103
|
+
usage,
|
|
104
|
+
performance,
|
|
105
|
+
providerMetadata,
|
|
106
|
+
}) {
|
|
101
107
|
metrics.histogram('ai.model.response_time_ms', performance.responseTimeMs, {
|
|
102
108
|
callId,
|
|
103
109
|
modelId,
|
|
@@ -108,6 +114,11 @@ const result = streamText({
|
|
|
108
114
|
total: performance.effectiveTotalTokensPerSecond,
|
|
109
115
|
tokens: usage.totalTokens,
|
|
110
116
|
});
|
|
117
|
+
|
|
118
|
+
logger.info('ai.model.provider_metadata', {
|
|
119
|
+
callId,
|
|
120
|
+
providerMetadata,
|
|
121
|
+
});
|
|
111
122
|
},
|
|
112
123
|
});
|
|
113
124
|
|
|
@@ -675,6 +686,12 @@ Called after the provider response has been normalized and parsed, before local
|
|
|
675
686
|
type: 'string',
|
|
676
687
|
description: 'Provider-returned response ID for this model call.',
|
|
677
688
|
},
|
|
689
|
+
{
|
|
690
|
+
name: 'providerMetadata',
|
|
691
|
+
type: 'ProviderMetadata | undefined',
|
|
692
|
+
description:
|
|
693
|
+
'Provider-specific metadata for this model call, when returned by the provider.',
|
|
694
|
+
},
|
|
678
695
|
{
|
|
679
696
|
name: 'performance',
|
|
680
697
|
type: 'LanguageModelCallPerformance',
|
|
@@ -1438,6 +1438,12 @@ To see `generateText` in action, check out [these examples](#examples).
|
|
|
1438
1438
|
description:
|
|
1439
1439
|
'The provider-returned response ID for this model call.',
|
|
1440
1440
|
},
|
|
1441
|
+
{
|
|
1442
|
+
name: 'providerMetadata',
|
|
1443
|
+
type: 'ProviderMetadata | undefined',
|
|
1444
|
+
description:
|
|
1445
|
+
'Provider-specific metadata for this model call, when returned by the provider.',
|
|
1446
|
+
},
|
|
1441
1447
|
{
|
|
1442
1448
|
name: 'performance',
|
|
1443
1449
|
type: '{ responseTimeMs: number; effectiveOutputTokensPerSecond: number; outputTokensPerSecond: number | undefined; inputTokensPerSecond: number | undefined; effectiveTotalTokensPerSecond: number; timeToFirstOutputMs: number | undefined; timeBetweenOutputChunksMs?: OutputChunkTimingStats }',
|
|
@@ -2422,6 +2422,12 @@ To see `streamText` in action, check out [these examples](#examples).
|
|
|
2422
2422
|
description:
|
|
2423
2423
|
'The provider-returned response ID for this model call.',
|
|
2424
2424
|
},
|
|
2425
|
+
{
|
|
2426
|
+
name: 'providerMetadata',
|
|
2427
|
+
type: 'ProviderMetadata | undefined',
|
|
2428
|
+
description:
|
|
2429
|
+
'Provider-specific metadata for this model call, when returned by the provider.',
|
|
2430
|
+
},
|
|
2425
2431
|
{
|
|
2426
2432
|
name: 'performance',
|
|
2427
2433
|
type: '{ responseTimeMs: number; effectiveOutputTokensPerSecond: number; outputTokensPerSecond: number | undefined; inputTokensPerSecond: number | undefined; effectiveTotalTokensPerSecond: number; timeToFirstOutputMs: number | undefined; timeBetweenOutputChunksMs?: OutputChunkTimingStats }',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.57",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"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.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ai-sdk/gateway": "4.0.
|
|
45
|
+
"@ai-sdk/gateway": "4.0.45",
|
|
46
46
|
"@ai-sdk/provider": "4.0.6",
|
|
47
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
47
|
+
"@ai-sdk/provider-utils": "5.0.24"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@edge-runtime/vm": "^5.0.0",
|
|
@@ -1085,6 +1085,11 @@ export async function generateText<
|
|
|
1085
1085
|
usage: stepUsage,
|
|
1086
1086
|
content: modelCallContent,
|
|
1087
1087
|
responseId: currentModelResponse.response.id,
|
|
1088
|
+
...(currentModelResponse.providerMetadata != null
|
|
1089
|
+
? {
|
|
1090
|
+
providerMetadata: currentModelResponse.providerMetadata,
|
|
1091
|
+
}
|
|
1092
|
+
: {}),
|
|
1088
1093
|
performance: {
|
|
1089
1094
|
responseTimeMs,
|
|
1090
1095
|
effectiveOutputTokensPerSecond: calculateTokensPerSecond({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ToolSet } from '@ai-sdk/provider-utils';
|
|
2
2
|
import type { Callback } from '../util/callback';
|
|
3
3
|
import type { FinishReason } from '../types/language-model';
|
|
4
|
+
import type { ProviderMetadata } from '../types/provider-metadata';
|
|
4
5
|
import type { LanguageModelUsage } from '../types/usage';
|
|
5
6
|
import type { ContentPart } from './content-part';
|
|
6
7
|
import type { StandardizedPrompt } from '../prompt/standardize-prompt';
|
|
@@ -55,6 +56,9 @@ export type LanguageModelCallEndEvent<TOOLS extends ToolSet = ToolSet> =
|
|
|
55
56
|
/** The provider-returned response id for this model call. */
|
|
56
57
|
readonly responseId: string;
|
|
57
58
|
|
|
59
|
+
/** Optional provider-specific metadata for this model call. */
|
|
60
|
+
readonly providerMetadata?: ProviderMetadata;
|
|
61
|
+
|
|
58
62
|
/** Performance metrics for the model call. */
|
|
59
63
|
readonly performance: {
|
|
60
64
|
/** Time spent waiting for the language model response in milliseconds. */
|
|
@@ -596,6 +596,9 @@ function createLanguageModelV4StreamPartToLanguageModelStreamPartTransform<
|
|
|
596
596
|
usage,
|
|
597
597
|
content: modelCallContent,
|
|
598
598
|
responseId,
|
|
599
|
+
...(chunk.providerMetadata != null
|
|
600
|
+
? { providerMetadata: chunk.providerMetadata }
|
|
601
|
+
: {}),
|
|
599
602
|
performance,
|
|
600
603
|
},
|
|
601
604
|
callbacks: onLanguageModelCallEnd,
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import {
|
|
12
12
|
convertBase64ToUint8Array,
|
|
13
13
|
delay as defaultDelay,
|
|
14
|
+
generateId,
|
|
14
15
|
withUserAgentSuffix,
|
|
15
16
|
type DataContent,
|
|
16
17
|
detectMediaType,
|
|
@@ -553,13 +554,23 @@ async function executeStartStatusFlow({
|
|
|
553
554
|
}
|
|
554
555
|
}
|
|
555
556
|
|
|
556
|
-
// 2. Start the generation
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}),
|
|
557
|
+
// 2. Start the generation. `doStart` is billable: mint one idempotency token
|
|
558
|
+
// per logical start, outside the retry closure; a caller-supplied key wins.
|
|
559
|
+
const callerIdempotencyKey = Object.entries(callOptions.headers ?? {}).find(
|
|
560
|
+
([key, value]) =>
|
|
561
|
+
key.toLowerCase() === 'idempotency-key' && value !== undefined,
|
|
562
562
|
);
|
|
563
|
+
const startCallOptions = {
|
|
564
|
+
...callOptions,
|
|
565
|
+
headers: {
|
|
566
|
+
...callOptions.headers,
|
|
567
|
+
...(callerIdempotencyKey
|
|
568
|
+
? {}
|
|
569
|
+
: { 'idempotency-key': `aisdk_vid_${generateId()}` }),
|
|
570
|
+
},
|
|
571
|
+
webhookUrl,
|
|
572
|
+
};
|
|
573
|
+
const startResult = await retry(() => model.doStart!(startCallOptions));
|
|
563
574
|
|
|
564
575
|
const allWarnings = [...earlyWarnings, ...startResult.warnings];
|
|
565
576
|
let operationProviderMetadata =
|
package/src/ui/chat-transport.ts
CHANGED
|
@@ -64,6 +64,7 @@ export interface ChatTransport<UI_MESSAGE extends UIMessage> {
|
|
|
64
64
|
*
|
|
65
65
|
* @param options - Configuration object containing:
|
|
66
66
|
* @param options.chatId - Unique identifier for the chat session to reconnect to
|
|
67
|
+
* @param options.abortSignal - Signal to abort the reconnection request if needed
|
|
67
68
|
* @param options.headers - Additional HTTP headers to include in the reconnection request
|
|
68
69
|
* @param options.body - Additional JSON properties to include in the request body
|
|
69
70
|
* @param options.metadata - Custom metadata to attach to the request
|
|
@@ -78,6 +79,8 @@ export interface ChatTransport<UI_MESSAGE extends UIMessage> {
|
|
|
78
79
|
options: {
|
|
79
80
|
/** Unique identifier for the chat session to reconnect to */
|
|
80
81
|
chatId: string;
|
|
82
|
+
/** Signal to abort the reconnection request if needed */
|
|
83
|
+
abortSignal?: AbortSignal;
|
|
81
84
|
} & ChatRequestOptions,
|
|
82
85
|
) => Promise<ReadableStream<UIMessageChunk> | null>;
|
|
83
86
|
}
|
package/src/ui/chat.ts
CHANGED
|
@@ -135,6 +135,10 @@ type ActiveResponse<UI_MESSAGE extends UIMessage> = {
|
|
|
135
135
|
abortController: AbortController;
|
|
136
136
|
};
|
|
137
137
|
|
|
138
|
+
type ActiveResumeRequest = {
|
|
139
|
+
abortController: AbortController;
|
|
140
|
+
};
|
|
141
|
+
|
|
138
142
|
export interface ChatState<UI_MESSAGE extends UIMessage> {
|
|
139
143
|
status: ChatStatus;
|
|
140
144
|
|
|
@@ -254,6 +258,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
254
258
|
private sendAutomaticallyWhen?: ChatInit<UI_MESSAGE>['sendAutomaticallyWhen'];
|
|
255
259
|
|
|
256
260
|
private activeResponse: ActiveResponse<UI_MESSAGE> | undefined = undefined;
|
|
261
|
+
private activeResumeRequest: ActiveResumeRequest | undefined = undefined;
|
|
257
262
|
private jobExecutor = new SerialJobExecutor();
|
|
258
263
|
|
|
259
264
|
constructor({
|
|
@@ -584,11 +589,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
584
589
|
* Abort the current request immediately, keep the generated tokens if any.
|
|
585
590
|
*/
|
|
586
591
|
stop = async () => {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
if (this.activeResponse?.abortController) {
|
|
590
|
-
this.activeResponse.abortController.abort();
|
|
591
|
-
}
|
|
592
|
+
this.activeResumeRequest?.abortController.abort();
|
|
593
|
+
this.activeResponse?.abortController.abort();
|
|
592
594
|
};
|
|
593
595
|
|
|
594
596
|
private async shouldSendAutomatically(): Promise<boolean> {
|
|
@@ -616,6 +618,25 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
616
618
|
trigger: 'submit-message' | 'resume-stream' | 'regenerate-message';
|
|
617
619
|
messageId?: string;
|
|
618
620
|
} & ChatRequestOptions) {
|
|
621
|
+
const abortController = new AbortController();
|
|
622
|
+
const activeResumeRequest =
|
|
623
|
+
trigger === 'resume-stream' ? { abortController } : undefined;
|
|
624
|
+
|
|
625
|
+
if (activeResumeRequest) {
|
|
626
|
+
this.activeResumeRequest?.abortController.abort();
|
|
627
|
+
this.activeResumeRequest = activeResumeRequest;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const isCurrentRequest = () =>
|
|
631
|
+
activeResumeRequest == null ||
|
|
632
|
+
this.activeResumeRequest === activeResumeRequest;
|
|
633
|
+
|
|
634
|
+
const clearActiveResumeRequest = () => {
|
|
635
|
+
if (this.activeResumeRequest === activeResumeRequest) {
|
|
636
|
+
this.activeResumeRequest = undefined;
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
|
|
619
640
|
// For resume-stream, check if there's an active stream before
|
|
620
641
|
// changing status. This avoids a brief flash of 'submitted' status
|
|
621
642
|
// when there is no stream to resume (e.g. on page load).
|
|
@@ -624,21 +645,49 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
624
645
|
try {
|
|
625
646
|
const reconnect = await this.transport.reconnectToStream({
|
|
626
647
|
chatId: this.id,
|
|
648
|
+
abortSignal: abortController.signal,
|
|
627
649
|
metadata,
|
|
628
650
|
headers,
|
|
629
651
|
body,
|
|
630
652
|
});
|
|
631
653
|
|
|
654
|
+
if (abortController.signal.aborted || !isCurrentRequest()) {
|
|
655
|
+
await reconnect?.cancel().catch(() => {});
|
|
656
|
+
if (isCurrentRequest()) {
|
|
657
|
+
this.setStatus({ status: 'ready' });
|
|
658
|
+
}
|
|
659
|
+
clearActiveResumeRequest();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
|
|
632
663
|
if (reconnect == null) {
|
|
664
|
+
this.setStatus({ status: 'ready' });
|
|
665
|
+
clearActiveResumeRequest();
|
|
633
666
|
return; // no active stream found, so we do not resume
|
|
634
667
|
}
|
|
635
668
|
|
|
636
669
|
resumeStream = reconnect;
|
|
637
670
|
} catch (err) {
|
|
671
|
+
if (
|
|
672
|
+
abortController.signal.aborted ||
|
|
673
|
+
(err as { name?: string }).name === 'AbortError'
|
|
674
|
+
) {
|
|
675
|
+
if (isCurrentRequest()) {
|
|
676
|
+
this.setStatus({ status: 'ready' });
|
|
677
|
+
}
|
|
678
|
+
clearActiveResumeRequest();
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (!isCurrentRequest()) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
638
686
|
if (this.onError && err instanceof Error) {
|
|
639
687
|
this.onError(err);
|
|
640
688
|
}
|
|
641
689
|
this.setStatus({ status: 'error', error: err as Error });
|
|
690
|
+
clearActiveResumeRequest();
|
|
642
691
|
return;
|
|
643
692
|
}
|
|
644
693
|
}
|
|
@@ -661,7 +710,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
661
710
|
: this.state.snapshot(lastMessage),
|
|
662
711
|
messageId: this.generateId(),
|
|
663
712
|
}),
|
|
664
|
-
abortController
|
|
713
|
+
abortController,
|
|
665
714
|
} as ActiveResponse<UI_MESSAGE>;
|
|
666
715
|
|
|
667
716
|
activeResponse = response;
|
|
@@ -696,10 +745,18 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
696
745
|
}) => Promise<void>,
|
|
697
746
|
) =>
|
|
698
747
|
// serialize the job execution to avoid race conditions:
|
|
699
|
-
this.jobExecutor.run(() =>
|
|
700
|
-
|
|
748
|
+
this.jobExecutor.run(() => {
|
|
749
|
+
if (response.abortController.signal.aborted) {
|
|
750
|
+
return Promise.resolve();
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return job({
|
|
701
754
|
state: response.state,
|
|
702
755
|
write: () => {
|
|
756
|
+
if (response.abortController.signal.aborted) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
703
760
|
// streaming is set on first write (before it should be "submitted")
|
|
704
761
|
this.setStatus({ status: 'streaming' });
|
|
705
762
|
|
|
@@ -715,8 +772,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
715
772
|
this.state.pushMessage(response.state.message);
|
|
716
773
|
}
|
|
717
774
|
},
|
|
718
|
-
})
|
|
719
|
-
);
|
|
775
|
+
});
|
|
776
|
+
});
|
|
720
777
|
|
|
721
778
|
await consumeStream({
|
|
722
779
|
stream: processUIMessageStream({
|
|
@@ -730,17 +787,33 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
730
787
|
throw error;
|
|
731
788
|
},
|
|
732
789
|
}),
|
|
790
|
+
abortSignal: response.abortController.signal,
|
|
733
791
|
onError: error => {
|
|
734
792
|
throw error;
|
|
735
793
|
},
|
|
736
794
|
});
|
|
737
795
|
|
|
738
|
-
|
|
796
|
+
if (isAbort) {
|
|
797
|
+
if (isCurrentRequest()) {
|
|
798
|
+
this.setStatus({ status: 'ready' });
|
|
799
|
+
}
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (isCurrentRequest()) {
|
|
804
|
+
this.setStatus({ status: 'ready' });
|
|
805
|
+
}
|
|
739
806
|
} catch (err) {
|
|
740
807
|
// Ignore abort errors as they are expected.
|
|
741
808
|
if (isAbort || (err as any).name === 'AbortError') {
|
|
742
809
|
isAbort = true;
|
|
743
|
-
|
|
810
|
+
if (isCurrentRequest()) {
|
|
811
|
+
this.setStatus({ status: 'ready' });
|
|
812
|
+
}
|
|
813
|
+
return null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (!isCurrentRequest()) {
|
|
744
817
|
return null;
|
|
745
818
|
}
|
|
746
819
|
|
|
@@ -779,6 +852,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
|
|
|
779
852
|
if (this.activeResponse === activeResponse) {
|
|
780
853
|
this.activeResponse = undefined;
|
|
781
854
|
}
|
|
855
|
+
|
|
856
|
+
clearActiveResumeRequest();
|
|
782
857
|
}
|
|
783
858
|
|
|
784
859
|
// automatically send the message if the sendAutomaticallyWhen function returns true
|
|
@@ -13,11 +13,23 @@
|
|
|
13
13
|
export async function consumeStream({
|
|
14
14
|
stream,
|
|
15
15
|
onError,
|
|
16
|
+
abortSignal,
|
|
16
17
|
}: {
|
|
17
18
|
stream: ReadableStream;
|
|
18
19
|
onError?: (error: unknown) => void;
|
|
20
|
+
abortSignal?: AbortSignal;
|
|
19
21
|
}): Promise<void> {
|
|
20
22
|
const reader = stream.getReader();
|
|
23
|
+
const cancelOnAbort = () => {
|
|
24
|
+
reader.cancel().catch(() => {});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (abortSignal?.aborted) {
|
|
28
|
+
cancelOnAbort();
|
|
29
|
+
} else {
|
|
30
|
+
abortSignal?.addEventListener('abort', cancelOnAbort, { once: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
try {
|
|
22
34
|
while (true) {
|
|
23
35
|
const { done } = await reader.read();
|
|
@@ -26,6 +38,7 @@ export async function consumeStream({
|
|
|
26
38
|
} catch (error) {
|
|
27
39
|
onError?.(error);
|
|
28
40
|
} finally {
|
|
41
|
+
abortSignal?.removeEventListener('abort', cancelOnAbort);
|
|
29
42
|
reader.releaseLock();
|
|
30
43
|
}
|
|
31
44
|
}
|