ai 6.0.199 → 6.0.200

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.
@@ -152,7 +152,7 @@ function detectMediaType({
152
152
  var import_provider_utils2 = require("@ai-sdk/provider-utils");
153
153
 
154
154
  // src/version.ts
155
- var VERSION = true ? "6.0.199" : "0.0.0-test";
155
+ var VERSION = true ? "6.0.200" : "0.0.0-test";
156
156
 
157
157
  // src/util/download/download.ts
158
158
  var download = async ({
@@ -131,7 +131,7 @@ import {
131
131
  } from "@ai-sdk/provider-utils";
132
132
 
133
133
  // src/version.ts
134
- var VERSION = true ? "6.0.199" : "0.0.0-test";
134
+ var VERSION = true ? "6.0.200" : "0.0.0-test";
135
135
 
136
136
  // src/util/download/download.ts
137
137
  var download = async ({
@@ -34,7 +34,7 @@ Here is a comparison of the supported functions across these frameworks:
34
34
 
35
35
  Explore these example implementations for different frameworks:
36
36
 
37
- - [**Next.js**](https://github.com/vercel/ai/tree/main/examples/next-openai)
37
+ - [**Next.js**](https://github.com/vercel/ai/tree/main/examples/ai-e2e-next)
38
38
  - [**Nuxt**](https://github.com/vercel/ai/tree/main/examples/nuxt-openai)
39
39
  - [**SvelteKit**](https://github.com/vercel/ai/tree/main/examples/sveltekit-openai)
40
40
  - [**Angular**](https://github.com/vercel/ai/tree/main/examples/angular)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "6.0.199",
3
+ "version": "6.0.200",
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.127",
49
48
  "@ai-sdk/provider": "3.0.10",
50
- "@ai-sdk/provider-utils": "4.0.27"
49
+ "@ai-sdk/provider-utils": "4.0.27",
50
+ "@ai-sdk/gateway": "3.0.127"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@edge-runtime/vm": "^5.0.0",
@@ -128,6 +128,35 @@ const originalGenerateId = createIdGenerator({
128
128
  size: 24,
129
129
  });
130
130
 
131
+ // chunk types that count as model output; used to distinguish empty
132
+ // incomplete streams from incomplete streams with partial results.
133
+ // exhaustive so that new chunk types must be classified explicitly:
134
+ const isOutputChunkType = {
135
+ file: true,
136
+ source: true,
137
+ 'text-start': true,
138
+ 'text-end': true,
139
+ 'text-delta': true,
140
+ 'reasoning-start': true,
141
+ 'reasoning-end': true,
142
+ 'reasoning-delta': true,
143
+ 'tool-input-start': true,
144
+ 'tool-input-end': true,
145
+ 'tool-input-delta': true,
146
+ 'tool-approval-request': true,
147
+ 'tool-call': true,
148
+ 'tool-result': true,
149
+ 'tool-error': true,
150
+ 'stream-start': false,
151
+ 'response-metadata': false,
152
+ finish: false,
153
+ error: false,
154
+ raw: false,
155
+ } as const satisfies Record<
156
+ SingleRequestTextStreamPart<ToolSet>['type'],
157
+ boolean
158
+ >;
159
+
131
160
  /**
132
161
  * A transformation that is applied to the stream.
133
162
  *
@@ -836,6 +865,7 @@ class DefaultStreamTextResult<
836
865
  let recordedRequest: LanguageModelRequestMetadata = {};
837
866
  let recordedWarnings: Array<CallWarning> = [];
838
867
  const recordedSteps: StepResult<TOOLS>[] = [];
868
+ let recordedNoOutputError: NoOutputGeneratedError | undefined;
839
869
 
840
870
  // Track provider-executed tool calls that support deferred results
841
871
  // (e.g., code_execution in programmatic tool calling scenarios).
@@ -885,7 +915,13 @@ class DefaultStreamTextResult<
885
915
  }
886
916
 
887
917
  if (part.type === 'error') {
888
- await onError({ error: wrapGatewayError(part.error) });
918
+ const error = wrapGatewayError(part.error);
919
+
920
+ if (NoOutputGeneratedError.isInstance(error)) {
921
+ recordedNoOutputError = error;
922
+ }
923
+
924
+ await onError({ error });
889
925
  }
890
926
 
891
927
  if (part.type === 'text-start') {
@@ -1080,12 +1116,13 @@ class DefaultStreamTextResult<
1080
1116
 
1081
1117
  async flush(controller) {
1082
1118
  try {
1083
- if (recordedSteps.length === 0) {
1119
+ if (recordedSteps.length === 0 || recordedNoOutputError != null) {
1084
1120
  const error = abortSignal?.aborted
1085
1121
  ? abortSignal.reason
1086
- : new NoOutputGeneratedError({
1122
+ : (recordedNoOutputError ??
1123
+ new NoOutputGeneratedError({
1087
1124
  message: 'No output generated. Check the stream for errors.',
1088
- });
1125
+ }));
1089
1126
 
1090
1127
  self._finishReason.reject(error);
1091
1128
  self._rawFinishReason.reject(error);
@@ -1721,6 +1758,14 @@ class DefaultStreamTextResult<
1721
1758
  let stepFinishReason: FinishReason = 'other';
1722
1759
  let stepRawFinishReason: string | undefined = undefined;
1723
1760
 
1761
+ // terminal chunk = 'finish' or 'error'; absence on stream
1762
+ // end means the model stream is incomplete:
1763
+ let hasReceivedTerminalChunk = false;
1764
+
1765
+ // output chunk = any content chunk (text, tool calls, etc.);
1766
+ // used to distinguish empty incomplete streams from partial results:
1767
+ let hasReceivedOutputChunk = false;
1768
+
1724
1769
  let stepUsage: LanguageModelUsage = createNullLanguageModelUsage();
1725
1770
  let stepProviderMetadata: ProviderMetadata | undefined;
1726
1771
  let stepFirstChunk = true;
@@ -1771,6 +1816,11 @@ class DefaultStreamTextResult<
1771
1816
  }
1772
1817
 
1773
1818
  const chunkType = chunk.type;
1819
+
1820
+ if (isOutputChunkType[chunkType]) {
1821
+ hasReceivedOutputChunk = true;
1822
+ }
1823
+
1774
1824
  switch (chunkType) {
1775
1825
  case 'tool-approval-request':
1776
1826
  case 'text-start':
@@ -1841,6 +1891,8 @@ class DefaultStreamTextResult<
1841
1891
  }
1842
1892
 
1843
1893
  case 'finish': {
1894
+ hasReceivedTerminalChunk = true;
1895
+
1844
1896
  // Note: tool executions might not be finished yet when the finish event is emitted.
1845
1897
  // store usage and finish reason for promises and onFinish callback:
1846
1898
  stepUsage = chunk.usage;
@@ -1917,6 +1969,7 @@ class DefaultStreamTextResult<
1917
1969
  }
1918
1970
 
1919
1971
  case 'error': {
1972
+ hasReceivedTerminalChunk = true;
1920
1973
  controller.enqueue(chunk);
1921
1974
  stepFinishReason = 'error';
1922
1975
  break;
@@ -1940,6 +1993,25 @@ class DefaultStreamTextResult<
1940
1993
 
1941
1994
  // invoke onFinish callback and resolve toolResults promise when the stream is about to close:
1942
1995
  async flush(controller) {
1996
+ // emit an error when an incomplete model stream produced no
1997
+ // output instead of recording an empty step. incomplete
1998
+ // streams with partial output retain the partial result:
1999
+ if (!hasReceivedTerminalChunk && !hasReceivedOutputChunk) {
2000
+ controller.enqueue({
2001
+ type: 'error',
2002
+ error: new NoOutputGeneratedError({
2003
+ message:
2004
+ 'No output generated. The model stream ended without a finish chunk.',
2005
+ }),
2006
+ });
2007
+
2008
+ doStreamSpan.end();
2009
+ clearStepTimeout();
2010
+ clearChunkTimeout();
2011
+ self.closeStream();
2012
+ return;
2013
+ }
2014
+
1943
2015
  const stepToolCallsJson =
1944
2016
  stepToolCalls.length > 0
1945
2017
  ? JSON.stringify(stepToolCalls)
@@ -2316,13 +2388,24 @@ class DefaultStreamTextResult<
2316
2388
  );
2317
2389
  }
2318
2390
 
2391
+ private rejectResultPromises(error: unknown) {
2392
+ if (this._finishReason.isPending()) this._finishReason.reject(error);
2393
+ if (this._rawFinishReason.isPending()) this._rawFinishReason.reject(error);
2394
+ if (this._totalUsage.isPending()) this._totalUsage.reject(error);
2395
+ if (this._steps.isPending()) this._steps.reject(error);
2396
+ }
2397
+
2319
2398
  async consumeStream(options?: ConsumeStreamOptions): Promise<void> {
2320
2399
  try {
2321
2400
  await consumeStream({
2322
2401
  stream: this.fullStream,
2323
- onError: options?.onError,
2402
+ onError: error => {
2403
+ this.rejectResultPromises(error);
2404
+ options?.onError?.(error);
2405
+ },
2324
2406
  });
2325
2407
  } catch (error) {
2408
+ this.rejectResultPromises(error);
2326
2409
  options?.onError?.(error);
2327
2410
  }
2328
2411
  }
@@ -9,6 +9,7 @@ import {
9
9
  type InferUIMessageChunk,
10
10
  type UIMessageChunk,
11
11
  } from '../ui-message-stream/ui-message-chunks';
12
+ import { createIdMap } from '../util/create-id-map';
12
13
  import type { ErrorHandler } from '../util/error-handler';
13
14
  import { mergeObjects } from '../util/merge-objects';
14
15
  import { parsePartialJson } from '../util/parse-partial-json';
@@ -68,9 +69,9 @@ export function createStreamingUIMessageState<UI_MESSAGE extends UIMessage>({
68
69
  InferUIMessageTools<UI_MESSAGE>
69
70
  >[],
70
71
  } as UI_MESSAGE),
71
- activeTextParts: {},
72
- activeReasoningParts: {},
73
- partialToolCalls: {},
72
+ activeTextParts: createIdMap(),
73
+ activeReasoningParts: createIdMap(),
74
+ partialToolCalls: createIdMap(),
74
75
  };
75
76
  }
76
77
 
@@ -763,8 +764,8 @@ export function processUIMessageStream<UI_MESSAGE extends UIMessage>({
763
764
 
764
765
  case 'finish-step': {
765
766
  // reset the current text and reasoning parts
766
- state.activeTextParts = {};
767
- state.activeReasoningParts = {};
767
+ state.activeTextParts = createIdMap();
768
+ state.activeReasoningParts = createIdMap();
768
769
  break;
769
770
  }
770
771
 
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Creates a string-keyed map without an object prototype.
3
+ *
4
+ * Use this for lookup tables keyed by IDs or names that may come from outside
5
+ * the SDK, such as streamed chunk IDs or tool call IDs. Unlike `{}`, these maps
6
+ * do not inherit `__proto__`, `constructor`, or other Object prototype members,
7
+ * so a missing untrusted key cannot resolve to a shared prototype object.
8
+ */
9
+ export function createIdMap<T>(): Record<string, T> {
10
+ return Object.create(null);
11
+ }