ai 6.0.248 → 6.0.249

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.
@@ -164,7 +164,7 @@ function detectMediaType({
164
164
  var import_provider_utils2 = require("@ai-sdk/provider-utils");
165
165
 
166
166
  // src/version.ts
167
- var VERSION = true ? "6.0.248" : "0.0.0-test";
167
+ var VERSION = true ? "6.0.249" : "0.0.0-test";
168
168
 
169
169
  // src/util/download/download.ts
170
170
  var download = async ({
@@ -144,7 +144,7 @@ import {
144
144
  } from "@ai-sdk/provider-utils";
145
145
 
146
146
  // src/version.ts
147
- var VERSION = true ? "6.0.248" : "0.0.0-test";
147
+ var VERSION = true ? "6.0.249" : "0.0.0-test";
148
148
 
149
149
  // src/util/download/download.ts
150
150
  var download = async ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "6.0.248",
3
+ "version": "6.0.249",
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,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@opentelemetry/api": "^1.9.0",
48
- "@ai-sdk/gateway": "3.0.168",
48
+ "@ai-sdk/gateway": "3.0.169",
49
49
  "@ai-sdk/provider": "3.0.15",
50
50
  "@ai-sdk/provider-utils": "4.0.44"
51
51
  },
@@ -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
@@ -136,6 +136,10 @@ type ActiveResponse<UI_MESSAGE extends UIMessage> = {
136
136
  abortController: AbortController;
137
137
  };
138
138
 
139
+ type ActiveResumeRequest = {
140
+ abortController: AbortController;
141
+ };
142
+
139
143
  export interface ChatState<UI_MESSAGE extends UIMessage> {
140
144
  status: ChatStatus;
141
145
 
@@ -255,6 +259,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
255
259
  private sendAutomaticallyWhen?: ChatInit<UI_MESSAGE>['sendAutomaticallyWhen'];
256
260
 
257
261
  private activeResponse: ActiveResponse<UI_MESSAGE> | undefined = undefined;
262
+ private activeResumeRequest: ActiveResumeRequest | undefined = undefined;
258
263
  private jobExecutor = new SerialJobExecutor();
259
264
 
260
265
  constructor({
@@ -585,11 +590,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
585
590
  * Abort the current request immediately, keep the generated tokens if any.
586
591
  */
587
592
  stop = async () => {
588
- if (this.status !== 'streaming' && this.status !== 'submitted') return;
589
-
590
- if (this.activeResponse?.abortController) {
591
- this.activeResponse.abortController.abort();
592
- }
593
+ this.activeResumeRequest?.abortController.abort();
594
+ this.activeResponse?.abortController.abort();
593
595
  };
594
596
 
595
597
  private async shouldSendAutomatically(): Promise<boolean> {
@@ -617,6 +619,25 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
617
619
  trigger: 'submit-message' | 'resume-stream' | 'regenerate-message';
618
620
  messageId?: string;
619
621
  } & ChatRequestOptions) {
622
+ const abortController = new AbortController();
623
+ const activeResumeRequest =
624
+ trigger === 'resume-stream' ? { abortController } : undefined;
625
+
626
+ if (activeResumeRequest) {
627
+ this.activeResumeRequest?.abortController.abort();
628
+ this.activeResumeRequest = activeResumeRequest;
629
+ }
630
+
631
+ const isCurrentRequest = () =>
632
+ activeResumeRequest == null ||
633
+ this.activeResumeRequest === activeResumeRequest;
634
+
635
+ const clearActiveResumeRequest = () => {
636
+ if (this.activeResumeRequest === activeResumeRequest) {
637
+ this.activeResumeRequest = undefined;
638
+ }
639
+ };
640
+
620
641
  // For resume-stream, check if there's an active stream before
621
642
  // changing status. This avoids a brief flash of 'submitted' status
622
643
  // when there is no stream to resume (e.g. on page load).
@@ -625,21 +646,49 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
625
646
  try {
626
647
  const reconnect = await this.transport.reconnectToStream({
627
648
  chatId: this.id,
649
+ abortSignal: abortController.signal,
628
650
  metadata,
629
651
  headers,
630
652
  body,
631
653
  });
632
654
 
655
+ if (abortController.signal.aborted || !isCurrentRequest()) {
656
+ await reconnect?.cancel().catch(() => {});
657
+ if (isCurrentRequest()) {
658
+ this.setStatus({ status: 'ready' });
659
+ }
660
+ clearActiveResumeRequest();
661
+ return;
662
+ }
663
+
633
664
  if (reconnect == null) {
665
+ this.setStatus({ status: 'ready' });
666
+ clearActiveResumeRequest();
634
667
  return; // no active stream found, so we do not resume
635
668
  }
636
669
 
637
670
  resumeStream = reconnect;
638
671
  } catch (err) {
672
+ if (
673
+ abortController.signal.aborted ||
674
+ (err as { name?: string }).name === 'AbortError'
675
+ ) {
676
+ if (isCurrentRequest()) {
677
+ this.setStatus({ status: 'ready' });
678
+ }
679
+ clearActiveResumeRequest();
680
+ return;
681
+ }
682
+
683
+ if (!isCurrentRequest()) {
684
+ return;
685
+ }
686
+
639
687
  if (this.onError && err instanceof Error) {
640
688
  this.onError(err);
641
689
  }
642
690
  this.setStatus({ status: 'error', error: err as Error });
691
+ clearActiveResumeRequest();
643
692
  return;
644
693
  }
645
694
  }
@@ -662,7 +711,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
662
711
  : this.state.snapshot(lastMessage),
663
712
  messageId: this.generateId(),
664
713
  }),
665
- abortController: new AbortController(),
714
+ abortController,
666
715
  } as ActiveResponse<UI_MESSAGE>;
667
716
 
668
717
  activeResponse = response;
@@ -697,10 +746,18 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
697
746
  }) => Promise<void>,
698
747
  ) =>
699
748
  // serialize the job execution to avoid race conditions:
700
- this.jobExecutor.run(() =>
701
- job({
749
+ this.jobExecutor.run(() => {
750
+ if (response.abortController.signal.aborted) {
751
+ return Promise.resolve();
752
+ }
753
+
754
+ return job({
702
755
  state: response.state,
703
756
  write: () => {
757
+ if (response.abortController.signal.aborted) {
758
+ return;
759
+ }
760
+
704
761
  // streaming is set on first write (before it should be "submitted")
705
762
  this.setStatus({ status: 'streaming' });
706
763
 
@@ -716,8 +773,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
716
773
  this.state.pushMessage(response.state.message);
717
774
  }
718
775
  },
719
- }),
720
- );
776
+ });
777
+ });
721
778
 
722
779
  await consumeStream({
723
780
  stream: processUIMessageStream({
@@ -731,17 +788,33 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
731
788
  throw error;
732
789
  },
733
790
  }),
791
+ abortSignal: response.abortController.signal,
734
792
  onError: error => {
735
793
  throw error;
736
794
  },
737
795
  });
738
796
 
739
- this.setStatus({ status: 'ready' });
797
+ if (isAbort) {
798
+ if (isCurrentRequest()) {
799
+ this.setStatus({ status: 'ready' });
800
+ }
801
+ return null;
802
+ }
803
+
804
+ if (isCurrentRequest()) {
805
+ this.setStatus({ status: 'ready' });
806
+ }
740
807
  } catch (err) {
741
808
  // Ignore abort errors as they are expected.
742
809
  if (isAbort || (err as any).name === 'AbortError') {
743
810
  isAbort = true;
744
- this.setStatus({ status: 'ready' });
811
+ if (isCurrentRequest()) {
812
+ this.setStatus({ status: 'ready' });
813
+ }
814
+ return null;
815
+ }
816
+
817
+ if (!isCurrentRequest()) {
745
818
  return null;
746
819
  }
747
820
 
@@ -780,6 +853,8 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
780
853
  if (this.activeResponse === activeResponse) {
781
854
  this.activeResponse = undefined;
782
855
  }
856
+
857
+ clearActiveResumeRequest();
783
858
  }
784
859
 
785
860
  // automatically send the message if the sendAutomaticallyWhen function returns true
@@ -247,6 +247,7 @@ export abstract class HttpChatTransport<
247
247
  method: 'GET',
248
248
  headers,
249
249
  credentials,
250
+ signal: options.abortSignal,
250
251
  });
251
252
 
252
253
  // no active stream found, so we do not resume
@@ -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
  }