ai 6.0.248 → 6.0.250

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.250" : "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.250" : "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.250",
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.168",
48
+ "@ai-sdk/gateway": "3.0.170",
49
49
  "@ai-sdk/provider": "3.0.15",
50
- "@ai-sdk/provider-utils": "4.0.44"
50
+ "@ai-sdk/provider-utils": "4.0.45"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@edge-runtime/vm": "^5.0.0",
@@ -488,6 +488,7 @@ class DefaultStreamObjectResult<
488
488
  }),
489
489
  tracer,
490
490
  endWhenDone: false,
491
+ endOnError: true,
491
492
  fn: async rootSpan => {
492
493
  const standardizedPrompt = await standardizePrompt({
493
494
  system,
@@ -567,6 +568,7 @@ class DefaultStreamObjectResult<
567
568
  }),
568
569
  tracer,
569
570
  endWhenDone: false,
571
+ endOnError: true,
570
572
  fn: async doStreamSpan => ({
571
573
  startTimestampMs: now(),
572
574
  doStreamSpan,
@@ -1760,6 +1760,7 @@ class DefaultStreamTextResult<
1760
1760
  }),
1761
1761
  tracer,
1762
1762
  endWhenDone: false,
1763
+ endOnError: true,
1763
1764
  fn: async doStreamSpan => ({
1764
1765
  startTimestampMs: now(), // get before the call
1765
1766
  doStreamSpan,
@@ -12,12 +12,14 @@ export async function recordSpan<T>({
12
12
  attributes,
13
13
  fn,
14
14
  endWhenDone = true,
15
+ endOnError = endWhenDone,
15
16
  }: {
16
17
  name: string;
17
18
  tracer: Tracer;
18
19
  attributes: Attributes | PromiseLike<Attributes>;
19
20
  fn: (span: Span) => Promise<T>;
20
21
  endWhenDone?: boolean;
22
+ endOnError?: boolean;
21
23
  }) {
22
24
  return tracer.startActiveSpan(
23
25
  name,
@@ -40,8 +42,9 @@ export async function recordSpan<T>({
40
42
  try {
41
43
  recordErrorOnSpan(span, error);
42
44
  } finally {
43
- // always stop the span when there is an error:
44
- span.end();
45
+ if (endOnError) {
46
+ span.end();
47
+ }
45
48
  }
46
49
 
47
50
  throw error;
@@ -77,6 +77,7 @@ class MockSpan implements Span {
77
77
  time?: [number, number];
78
78
  }> = [];
79
79
  status?: SpanStatus;
80
+ endCalls = 0;
80
81
 
81
82
  readonly _spanContext: SpanContext = new MockSpanContext();
82
83
 
@@ -128,6 +129,7 @@ class MockSpan implements Span {
128
129
  return this;
129
130
  }
130
131
  end() {
132
+ this.endCalls++;
131
133
  return this;
132
134
  }
133
135
  isRecording() {
@@ -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
  }
@@ -657,12 +706,12 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
657
706
  const response = {
658
707
  state: createStreamingUIMessageState({
659
708
  lastMessage:
660
- trigger === 'regenerate-message'
709
+ trigger === 'resume-stream' || trigger === 'regenerate-message'
661
710
  ? undefined
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
  }