ai 6.0.293 → 6.0.295

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.293" : "0.0.0-test";
167
+ var VERSION = true ? "6.0.295" : "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.293" : "0.0.0-test";
147
+ var VERSION = true ? "6.0.295" : "0.0.0-test";
148
148
 
149
149
  // src/util/download/download.ts
150
150
  var download = async ({
@@ -142,6 +142,7 @@ Here are the capabilities of popular models:
142
142
  | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5-chat-latest` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
143
143
  | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-fable-5-1` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
144
144
  | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-5-5` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
145
+ | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-5-5` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
145
146
  | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-5` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
146
147
  | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-fable-5` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
147
148
  | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-8` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
@@ -208,6 +208,7 @@ try {
208
208
  | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3-turbo` |
209
209
  | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3` |
210
210
  | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `whisper-1` |
211
+ | [Azure Speech](/providers/ai-sdk-providers/azure#mai-transcribe-2) | `mai-transcribe-2` |
211
212
  | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-transcribe` |
212
213
  | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-mini-transcribe` |
213
214
  | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `machine` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "6.0.293",
3
+ "version": "6.0.295",
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.203",
48
+ "@ai-sdk/gateway": "3.0.205",
49
49
  "@ai-sdk/provider": "3.0.18",
50
- "@ai-sdk/provider-utils": "4.0.55"
50
+ "@ai-sdk/provider-utils": "4.0.56"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@edge-runtime/vm": "^5.0.0",
@@ -58,7 +58,7 @@
58
58
  "tsx": "^4.19.2",
59
59
  "typescript": "5.8.3",
60
60
  "zod": "3.25.76",
61
- "@ai-sdk/google": "3.0.128",
61
+ "@ai-sdk/google": "3.0.129",
62
62
  "@ai-sdk/test-server": "1.0.6",
63
63
  "@vercel/ai-tsconfig": "0.0.0"
64
64
  },
package/src/ui/chat.ts CHANGED
@@ -262,6 +262,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
262
262
  private pendingApprovalMessageId: string | undefined;
263
263
  private activeResponse: ActiveResponse<UI_MESSAGE> | undefined = undefined;
264
264
  private activeResumeRequest: ActiveResumeRequest | undefined = undefined;
265
+ private resumableStreamState:
266
+ | StreamingUIMessageState<UI_MESSAGE>
267
+ | undefined = undefined;
265
268
  private jobExecutor = new SerialJobExecutor();
266
269
 
267
270
  constructor({
@@ -697,6 +700,10 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
697
700
  trigger: 'submit-message' | 'resume-stream' | 'regenerate-message';
698
701
  messageId?: string;
699
702
  } & ChatRequestOptions) {
703
+ if (trigger !== 'resume-stream') {
704
+ this.resumableStreamState = undefined;
705
+ }
706
+
700
707
  const abortController = new AbortController();
701
708
  const activeResumeRequest =
702
709
  trigger === 'resume-stream' ? { abortController } : undefined;
@@ -734,6 +741,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
734
741
  await reconnect?.cancel().catch(() => {});
735
742
  if (isCurrentRequest()) {
736
743
  this.setStatus({ status: 'ready' });
744
+ this.resumableStreamState = undefined;
737
745
  }
738
746
  clearActiveResumeRequest();
739
747
  return;
@@ -741,6 +749,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
741
749
 
742
750
  if (reconnect == null) {
743
751
  this.setStatus({ status: 'ready' });
752
+ this.resumableStreamState = undefined;
744
753
  clearActiveResumeRequest();
745
754
  return; // no active stream found, so we do not resume
746
755
  }
@@ -753,6 +762,7 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
753
762
  ) {
754
763
  if (isCurrentRequest()) {
755
764
  this.setStatus({ status: 'ready' });
765
+ this.resumableStreamState = undefined;
756
766
  }
757
767
  clearActiveResumeRequest();
758
768
  return;
@@ -794,13 +804,17 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
794
804
 
795
805
  try {
796
806
  const response = {
797
- state: createStreamingUIMessageState({
798
- lastMessage:
799
- trigger === 'resume-stream' || trigger === 'regenerate-message'
800
- ? undefined
801
- : this.state.snapshot(responseMessage),
802
- messageId: this.generateId(),
803
- }),
807
+ state:
808
+ trigger === 'resume-stream' && this.resumableStreamState != null
809
+ ? this.resumableStreamState
810
+ : createStreamingUIMessageState({
811
+ lastMessage:
812
+ trigger === 'resume-stream' ||
813
+ trigger === 'regenerate-message'
814
+ ? undefined
815
+ : this.state.snapshot(responseMessage),
816
+ messageId: this.generateId(),
817
+ }),
804
818
  abortController,
805
819
  } as ActiveResponse<UI_MESSAGE>;
806
820
 
@@ -890,12 +904,18 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
890
904
  if (isAbort) {
891
905
  if (isCurrentRequest()) {
892
906
  this.setStatus({ status: 'ready' });
907
+ if (this.resumableStreamState === response.state) {
908
+ this.resumableStreamState = undefined;
909
+ }
893
910
  }
894
911
  return null;
895
912
  }
896
913
 
897
914
  if (isCurrentRequest()) {
898
915
  this.setStatus({ status: 'ready' });
916
+ if (this.resumableStreamState === response.state) {
917
+ this.resumableStreamState = undefined;
918
+ }
899
919
  }
900
920
  } catch (err) {
901
921
  // Ignore abort errors as they are expected.
@@ -903,6 +923,9 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
903
923
  isAbort = true;
904
924
  if (isCurrentRequest()) {
905
925
  this.setStatus({ status: 'ready' });
926
+ if (this.resumableStreamState === activeResponse?.state) {
927
+ this.resumableStreamState = undefined;
928
+ }
906
929
  }
907
930
  return null;
908
931
  }
@@ -922,6 +945,12 @@ export abstract class AbstractChat<UI_MESSAGE extends UIMessage> {
922
945
  isDisconnect = true;
923
946
  }
924
947
 
948
+ if (isDisconnect) {
949
+ this.resumableStreamState = activeResponse?.state;
950
+ } else if (this.resumableStreamState === activeResponse?.state) {
951
+ this.resumableStreamState = undefined;
952
+ }
953
+
925
954
  if (this.onError && err instanceof Error) {
926
955
  this.onError(err);
927
956
  }
@@ -2,6 +2,10 @@ import type { ServerResponse } from 'node:http';
2
2
 
3
3
  /**
4
4
  * Writes the content of a stream to a server response.
5
+ *
6
+ * When the client disconnects before the stream has been fully written
7
+ * (premature `close`), the stream is cancelled so that upstream resources
8
+ * can be released, and no further chunks are written.
5
9
  */
6
10
  export function writeToServerResponse({
7
11
  response,
@@ -24,22 +28,57 @@ export function writeToServerResponse({
24
28
  }
25
29
 
26
30
  const reader = stream.getReader();
31
+
32
+ // Detect client disconnects. `close` also fires after a regular `end()`;
33
+ // `writableFinished` distinguishes the two cases.
34
+ let clientDisconnected = false;
35
+ let onDisconnect: (() => void) | undefined;
36
+ const handleClose = () => {
37
+ if (response.writableFinished) {
38
+ return;
39
+ }
40
+
41
+ clientDisconnected = true;
42
+ onDisconnect?.();
43
+
44
+ // cancelling the reader resolves a pending read with `done: true`:
45
+ reader.cancel(new Error('Client disconnected.')).catch(() => {});
46
+ };
47
+ response.once('close', handleClose);
48
+
49
+ // The response may have been destroyed before the close listener was
50
+ // registered. Cancel immediately instead of waiting indefinitely for the
51
+ // next stream chunk.
52
+ if (response.destroyed) {
53
+ handleClose();
54
+ }
55
+
27
56
  const read = async () => {
28
57
  try {
29
58
  while (true) {
30
59
  const { done, value } = await reader.read();
31
- if (done) break;
60
+ if (done || clientDisconnected || response.destroyed) break;
32
61
 
33
62
  // Respect backpressure: if write() returns false, wait for 'drain' event
34
63
  const canContinue = response.write(value);
35
64
  if (!canContinue) {
36
65
  await new Promise<void>(resolve => {
66
+ // don't wait for `drain` on a disconnected response:
67
+ onDisconnect = resolve;
37
68
  response.once('drain', resolve);
38
69
  });
70
+ onDisconnect = undefined;
39
71
  }
40
72
  }
41
73
  } finally {
42
- response.end();
74
+ response.off('close', handleClose);
75
+
76
+ if (clientDisconnected || response.destroyed) {
77
+ // release the stream; ending a destroyed response is not possible:
78
+ reader.cancel().catch(() => {});
79
+ } else {
80
+ response.end();
81
+ }
43
82
  }
44
83
  };
45
84