ai 6.0.302 → 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.
@@ -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.302" : "0.0.0-test";
167
+ var VERSION = true ? "6.0.303" : "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.302" : "0.0.0-test";
147
+ var VERSION = true ? "6.0.303" : "0.0.0-test";
148
148
 
149
149
  // src/util/download/download.ts
150
150
  var download = async ({
@@ -49,6 +49,40 @@ globalThis.AI_SDK_LOG_WARNINGS = ({ warnings, provider, model }) => {
49
49
  };
50
50
  ```
51
51
 
52
+ If your application loads multiple AI SDK versions in the same runtime, they all
53
+ share `globalThis.AI_SDK_LOG_WARNINGS`. Each version calls the logger with its own
54
+ arguments and warning types. The merged global type declarations prevent
55
+ TypeScript declaration conflicts, but do not convert these arguments at runtime.
56
+ A handler typed for only one version may therefore fail when another version
57
+ calls it.
58
+
59
+ Import `LogWarningsFunction` from every installed version that can call the logger
60
+ and accept a union of their parameter types. For example, with AI SDK 7 installed
61
+ as `ai` and AI SDK 6 installed under the package alias `ai-v6` (`npm:ai@6`):
62
+
63
+ ```ts
64
+ import type { LogWarningsFunction as LogWarningsFunctionV7 } from 'ai';
65
+ import type { LogWarningsFunction as LogWarningsFunctionV6 } from 'ai-v6';
66
+
67
+ type LoggerOptions =
68
+ | Parameters<LogWarningsFunctionV7>[0]
69
+ | Parameters<LogWarningsFunctionV6>[0];
70
+
71
+ const logger = ({ warnings, provider, model }: LoggerOptions) => {
72
+ for (const warning of warnings) {
73
+ console.warn('AI SDK Warning:', warning, { provider, model });
74
+ }
75
+ };
76
+
77
+ globalThis.AI_SDK_LOG_WARNINGS = logger;
78
+ ```
79
+
80
+ Check the argument shape and warning type before accessing version-specific
81
+ fields. If AI SDK 5 is also installed, include its
82
+ `Experimental_LogWarningsFunction` parameter type and use `Array.isArray` to
83
+ handle its warning array before accessing the options object used by AI SDK 6
84
+ and 7.
85
+
52
86
  ## Error Handling
53
87
 
54
88
  ### Error Helper Object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "6.0.302",
3
+ "version": "6.0.303",
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.211",
48
+ "@ai-sdk/gateway": "3.0.212",
49
49
  "@ai-sdk/provider": "3.0.18",
50
50
  "@ai-sdk/provider-utils": "4.0.57"
51
51
  },
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
- // add AI SDK default provider to the globalThis object
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: ProviderV3 | undefined;
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: LogWarningsFunction | undefined | false;
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: TelemetryIntegration[] | undefined;
52
+ var AI_SDK_TELEMETRY_INTEGRATIONS:
53
+ | Array<AISDKGlobalTypes[keyof AISDKGlobalTypes]['telemetry']>
54
+ | undefined;
36
55
  }
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,
@@ -572,21 +582,23 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
572
582
  this.status !== 'submitted' &&
573
583
  this.sendAutomaticallyWhen
574
584
  ) {
575
- this.shouldSendAutomatically().then(shouldSend => {
576
- if (shouldSend) {
577
- // no await to avoid deadlocking
585
+ const shouldSend = await this.shouldSendAutomatically();
586
+
587
+ if (shouldSend) {
588
+ // no await to avoid deadlocking
589
+ void this.runAutomaticRequest(() => {
578
590
  const messageId =
579
591
  messageIndex === -1
580
592
  ? this.lastMessage?.id
581
593
  : messages[messageIndex].id;
582
594
 
583
- this.makeRequestForToolApproval({
595
+ return this.makeRequestForToolApproval({
584
596
  messageId,
585
597
  messageIndex,
586
598
  ...options,
587
599
  });
588
- }
589
- });
600
+ }, shouldSend);
601
+ }
590
602
  }
591
603
  });
592
604
 
@@ -626,16 +638,20 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
626
638
  this.status !== 'submitted' &&
627
639
  this.sendAutomaticallyWhen
628
640
  ) {
629
- this.shouldSendAutomatically().then(shouldSend => {
630
- if (shouldSend) {
631
- // no await to avoid deadlocking
632
- this.makeRequest({
633
- trigger: 'submit-message',
634
- messageId: this.lastMessage?.id,
635
- ...options,
636
- });
637
- }
638
- });
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
+ }
639
655
  }
640
656
  });
641
657
 
@@ -643,11 +659,31 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
643
659
  addToolResult = this.addToolOutput;
644
660
 
645
661
  /**
646
- * Abort the current request immediately, keep the generated tokens if any.
662
+ * Abort the current request, keep the generated tokens if any, and wait for
663
+ * the request pipeline to finish.
647
664
  */
648
665
  stop = async () => {
649
- this.activeResumeRequest?.abortController.abort();
650
- this.activeResponse?.abortController.abort();
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
+ }
651
687
  };
652
688
 
653
689
  private async shouldSendAutomatically(): Promise<boolean> {
@@ -665,6 +701,30 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
665
701
  return result as boolean;
666
702
  }
667
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
+
668
728
  private async makeRequestForToolApproval({
669
729
  messageId,
670
730
  messageIndex,
@@ -695,23 +755,38 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
695
755
  }
696
756
  }
697
757
 
698
- 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({
699
772
  trigger,
700
773
  metadata,
701
774
  headers,
702
775
  body,
703
776
  messageId,
704
- }: {
705
- trigger: 'submit-message' | 'resume-stream' | 'regenerate-message';
706
- messageId?: string;
707
- } & ChatRequestOptions) {
777
+ completionPromise,
778
+ }: MakeRequestOptions & {
779
+ completionPromise: Promise<void>;
780
+ }) {
708
781
  if (trigger !== 'resume-stream') {
709
782
  this.resumableStreamState = undefined;
710
783
  }
711
784
 
712
785
  const abortController = new AbortController();
713
786
  const activeResumeRequest =
714
- trigger === 'resume-stream' ? { abortController } : undefined;
787
+ trigger === 'resume-stream'
788
+ ? { abortController, completionPromise }
789
+ : undefined;
715
790
 
716
791
  if (activeResumeRequest) {
717
792
  this.activeResumeRequest?.abortController.abort();
@@ -830,6 +905,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
830
905
  messageId: this.generateId(),
831
906
  }),
832
907
  abortController,
908
+ completionPromise,
833
909
  } as ActiveResponse<UI_MESSAGE>;
834
910
 
835
911
  activeResponse = response;
@@ -992,14 +1068,16 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
992
1068
  }
993
1069
 
994
1070
  // automatically send the message if the sendAutomaticallyWhen function returns true
995
- if (!isError && (await this.shouldSendAutomatically())) {
996
- await this.makeRequest({
997
- trigger: 'submit-message',
998
- messageId: this.lastMessage?.id,
999
- metadata,
1000
- headers,
1001
- body,
1002
- });
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
+ );
1003
1081
  }
1004
1082
  }
1005
1083
  }
@@ -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
  }
@@ -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
- while (this.queue.length > 0) {
15
- await this.queue[0]();
16
- this.queue.shift();
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
- this.isProcessing = false;
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
  }