ai 7.0.120 → 7.0.122

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # ai
2
2
 
3
+ ## 7.0.122
4
+
5
+ ### Patch Changes
6
+
7
+ - f3575f8: Clarify that provider-executed tool execution errors bypass the UI stream's `onError` callback to preserve provider error data and harness runtime messages. Stream errors and invalid tool calls still use the callback. Runtime behavior is unchanged.
8
+ - 27ab8d4: Encode chat IDs in default stream reconnection URLs so slashes, query delimiters, and fragments stay within the ID. Reject standalone `.` and `..` IDs before fetching. Custom URLs returned by `prepareReconnectToStreamRequest` remain unchanged.
9
+ - Updated dependencies [870f509]
10
+ - Updated dependencies [a75f1fd]
11
+ - @ai-sdk/gateway@4.0.100
12
+
13
+ ## 7.0.121
14
+
15
+ ### Patch Changes
16
+
17
+ - c5e90bb: fix(ai): preserve hydrated partial static tool input across stream resumptions
18
+ - 868c475: Preserve prototype-named tools, providers, and provider metadata as own properties without changing lookup object prototypes. Prevent inherited names from resolving as registered providers or causing image metadata aggregation to fail.
19
+ - 119536f: Preserve provider metadata on corresponding `smoothStream` chunks without carrying it into subsequent metadata-free deltas.
20
+ - 9941f32: Prevent automatic chat resumption when completed tool output is followed by terminal text without a completed stream state, while preserving resumption after completed model text.
21
+ - Updated dependencies [dbddb5b]
22
+ - Updated dependencies [c2511c1]
23
+ - @ai-sdk/gateway@4.0.99
24
+ - @ai-sdk/provider-utils@5.0.51
25
+
3
26
  ## 7.0.120
4
27
 
5
28
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -2161,6 +2161,13 @@ type UIToolInvocation<TOOL extends UITool | Tool> = {
2161
2161
  } & ({
2162
2162
  state: 'input-streaming';
2163
2163
  input?: DeepPartial<asUITool<TOOL>['input']> | undefined;
2164
+ /**
2165
+ * The raw tool input text received so far.
2166
+ *
2167
+ * This is used to continue input streaming when a message is persisted
2168
+ * and later resumed.
2169
+ */
2170
+ rawInput?: string;
2164
2171
  output?: never;
2165
2172
  errorText?: never;
2166
2173
  callProviderMetadata?: ProviderMetadata;
@@ -2285,6 +2292,13 @@ type DynamicToolUIPart = {
2285
2292
  } & ({
2286
2293
  state: 'input-streaming';
2287
2294
  input?: unknown;
2295
+ /**
2296
+ * The raw tool input text received so far.
2297
+ *
2298
+ * This is used to continue input streaming when a message is persisted
2299
+ * and later resumed.
2300
+ */
2301
+ rawInput?: string;
2288
2302
  output?: never;
2289
2303
  errorText?: never;
2290
2304
  callProviderMetadata?: ProviderMetadata;
@@ -2778,6 +2792,11 @@ type UIMessageStreamOptions<UI_MESSAGE extends UIMessage> = {
2778
2792
  /**
2779
2793
  * Process an error, e.g. to log it. Default to `() => 'An error occurred.'`.
2780
2794
  *
2795
+ * Tool execution errors marked `providerExecuted: true` bypass this callback.
2796
+ * Their strings pass through unchanged; other values are JSON-stringified to
2797
+ * preserve provider error data for model-message round trips and harness
2798
+ * runtime error messages. Stream errors and invalid tool calls still use it.
2799
+ *
2781
2800
  * @returns error message to include in the data stream.
2782
2801
  */
2783
2802
  onError?: (error: unknown) => string;
@@ -6074,7 +6093,9 @@ declare function lastAssistantMessageIsCompleteWithApprovalResponses({ messages,
6074
6093
  /**
6075
6094
  * Check if the last message is an assistant message with completed tool calls.
6076
6095
  * The last step of the message must have at least one tool invocation and
6077
- * all tool invocations must have a result.
6096
+ * all tool invocations must have a result. Completed model text may follow
6097
+ * the tool invocation because client-side tool results update parts in place,
6098
+ * but trailing text without a completed stream state prevents resubmission.
6078
6099
  */
6079
6100
  declare function lastAssistantMessageIsCompleteWithToolCalls({ messages, }: {
6080
6101
  messages: UIMessage[];
@@ -6333,6 +6354,11 @@ type ToUIMessageChunkOptions<TOOLS extends ToolSet = ToolSet, UI_MESSAGE extends
6333
6354
  sendSources?: boolean;
6334
6355
  sendStart?: boolean;
6335
6356
  sendFinish?: boolean;
6357
+ /**
6358
+ * Formats stream errors, invalid tool calls, and application-executed tool
6359
+ * errors. Provider-executed tool execution errors bypass this callback so
6360
+ * their original error data survives model-message round trips.
6361
+ */
6336
6362
  onError?: (error: unknown) => string;
6337
6363
  messageMetadata?: InferUIMessageMetadata<UI_MESSAGE>;
6338
6364
  responseMessageId?: string;
@@ -6343,7 +6369,7 @@ type ToUIMessageChunkOptions<TOOLS extends ToolSet = ToolSet, UI_MESSAGE extends
6343
6369
  *
6344
6370
  * Returns `undefined` for stream parts that do not produce UI message chunks.
6345
6371
  */
6346
- declare function toUIMessageChunk<TOOLS extends ToolSet = ToolSet, UI_MESSAGE extends UIMessage = UIMessage>(part: TextStreamPart<TOOLS>, { tools, sendReasoning, sendSources, sendStart, sendFinish, onError, // prevent leaking server error details to the client by default
6372
+ declare function toUIMessageChunk<TOOLS extends ToolSet = ToolSet, UI_MESSAGE extends UIMessage = UIMessage>(part: TextStreamPart<TOOLS>, { tools, sendReasoning, sendSources, sendStart, sendFinish, onError, // masks errors except provider-executed tool execution errors
6347
6373
  messageMetadata, responseMessageId, }?: ToUIMessageChunkOptions<TOOLS, UI_MESSAGE>): InferUIMessageChunk<UI_MESSAGE> | undefined;
6348
6374
 
6349
6375
  /**
@@ -6352,7 +6378,7 @@ messageMetadata, responseMessageId, }?: ToUIMessageChunkOptions<TOOLS, UI_MESSAG
6352
6378
  * UI message streaming, including response message ID injection and
6353
6379
  * `onEnd` handling.
6354
6380
  */
6355
- declare function toUIMessageStream<TOOLS extends ToolSet = ToolSet, UI_MESSAGE extends UIMessage = UIMessage>({ stream, tools, sendReasoning, sendSources, sendStart, sendFinish, onError, // prevent leaking server error details to the client by default
6381
+ declare function toUIMessageStream<TOOLS extends ToolSet = ToolSet, UI_MESSAGE extends UIMessage = UIMessage>({ stream, tools, sendReasoning, sendSources, sendStart, sendFinish, onError, // masks errors except provider-executed tool execution errors
6356
6382
  messageMetadata, originalMessages, generateMessageId, onEnd, onFinish, }: {
6357
6383
  stream: ReadableStream<TextStreamPart<TOOLS>>;
6358
6384
  tools?: TOOLS;
package/dist/index.js CHANGED
@@ -1328,7 +1328,7 @@ import {
1328
1328
  } from "@ai-sdk/provider-utils";
1329
1329
 
1330
1330
  // src/version.ts
1331
- var VERSION = true ? "7.0.120" : "0.0.0-test";
1331
+ var VERSION = true ? "7.0.122" : "0.0.0-test";
1332
1332
 
1333
1333
  // src/util/download/download.ts
1334
1334
  var download = async ({
@@ -7476,16 +7476,42 @@ function createStreamingUIMessageState({
7476
7476
  lastMessage,
7477
7477
  messageId
7478
7478
  }) {
7479
+ const message = lastMessage?.role === "assistant" ? lastMessage : {
7480
+ id: messageId,
7481
+ metadata: void 0,
7482
+ role: "assistant",
7483
+ parts: []
7484
+ };
7485
+ const partialToolCalls = createIdMap();
7486
+ const lastStepStartIndex = message.parts.findLastIndex(
7487
+ (part) => part.type === "step-start"
7488
+ );
7489
+ let staticToolIndex = 0;
7490
+ for (const part of message.parts.slice(lastStepStartIndex + 1)) {
7491
+ if (!isToolUIPart(part)) {
7492
+ continue;
7493
+ }
7494
+ const index = staticToolIndex;
7495
+ if (isStaticToolUIPart(part)) {
7496
+ staticToolIndex++;
7497
+ }
7498
+ if (part.state !== "input-streaming") {
7499
+ continue;
7500
+ }
7501
+ partialToolCalls[part.toolCallId] = {
7502
+ text: part.rawInput ?? "",
7503
+ index,
7504
+ toolName: part.type === "dynamic-tool" ? part.toolName : getStaticToolName(part),
7505
+ dynamic: part.type === "dynamic-tool",
7506
+ title: part.title,
7507
+ toolMetadata: part.toolMetadata
7508
+ };
7509
+ }
7479
7510
  return {
7480
- message: lastMessage?.role === "assistant" ? lastMessage : {
7481
- id: messageId,
7482
- metadata: void 0,
7483
- role: "assistant",
7484
- parts: []
7485
- },
7511
+ message,
7486
7512
  activeTextParts: createIdMap(),
7487
7513
  activeReasoningParts: createIdMap(),
7488
- partialToolCalls: createIdMap()
7514
+ partialToolCalls
7489
7515
  };
7490
7516
  }
7491
7517
  function processUIMessageStream({
@@ -7609,7 +7635,7 @@ function processUIMessageStream({
7609
7635
  anyPart.input = anyOptions.input;
7610
7636
  anyPart.output = anyOptions.output;
7611
7637
  anyPart.errorText = anyOptions.errorText;
7612
- anyPart.rawInput = anyOptions.rawInput ?? anyPart.rawInput;
7638
+ anyPart.rawInput = anyOptions.rawInput;
7613
7639
  anyPart.preliminary = anyOptions.preliminary;
7614
7640
  if (options.title !== void 0) {
7615
7641
  anyPart.title = options.title;
@@ -7844,6 +7870,7 @@ function processUIMessageStream({
7844
7870
  toolName: partialToolCall.toolName,
7845
7871
  state: "input-streaming",
7846
7872
  input: partialArgs,
7873
+ rawInput: partialToolCall.text,
7847
7874
  title: partialToolCall.title,
7848
7875
  toolMetadata: partialToolCall.toolMetadata
7849
7876
  });
@@ -7853,6 +7880,7 @@ function processUIMessageStream({
7853
7880
  toolName: partialToolCall.toolName,
7854
7881
  state: "input-streaming",
7855
7882
  input: partialArgs,
7883
+ rawInput: partialToolCall.text,
7856
7884
  title: partialToolCall.title,
7857
7885
  toolMetadata: partialToolCall.toolMetadata
7858
7886
  });
@@ -8275,7 +8303,7 @@ function toUIMessageChunk(part, {
8275
8303
  sendStart = true,
8276
8304
  sendFinish = true,
8277
8305
  onError = () => "An error occurred.",
8278
- // prevent leaking server error details to the client by default
8306
+ // masks errors except provider-executed tool execution errors
8279
8307
  messageMetadata,
8280
8308
  responseMessageId
8281
8309
  } = {}) {
@@ -8533,7 +8561,7 @@ function toUIMessageStream({
8533
8561
  sendStart = true,
8534
8562
  sendFinish = true,
8535
8563
  onError = () => "An error occurred.",
8536
- // prevent leaking server error details to the client by default
8564
+ // masks errors except provider-executed tool execution errors
8537
8565
  messageMetadata,
8538
8566
  originalMessages,
8539
8567
  generateMessageId,
@@ -12632,6 +12660,7 @@ var uiMessagesSchema = lazySchema2(() => {
12632
12660
  toolMetadata: toolMetadataSchema2.optional(),
12633
12661
  state: z.literal("input-streaming"),
12634
12662
  input: z.unknown().optional(),
12663
+ rawInput: z.string().optional(),
12635
12664
  providerExecuted: z.boolean().optional(),
12636
12665
  callProviderMetadata: providerMetadataSchema.optional(),
12637
12666
  output: z.never().optional(),
@@ -12735,6 +12764,7 @@ var uiMessagesSchema = lazySchema2(() => {
12735
12764
  providerExecuted: z.boolean().optional(),
12736
12765
  callProviderMetadata: providerMetadataSchema.optional(),
12737
12766
  input: z.unknown().optional(),
12767
+ rawInput: z.string().optional(),
12738
12768
  output: z.never().optional(),
12739
12769
  errorText: z.never().optional(),
12740
12770
  approval: z.never().optional()
@@ -13130,6 +13160,16 @@ import {
13130
13160
  withUserAgentSuffix as withUserAgentSuffix4
13131
13161
  } from "@ai-sdk/provider-utils";
13132
13162
 
13163
+ // src/util/set-own.ts
13164
+ function setOwn(object3, key, value) {
13165
+ Object.defineProperty(object3, key, {
13166
+ value,
13167
+ enumerable: true,
13168
+ configurable: true,
13169
+ writable: true
13170
+ });
13171
+ }
13172
+
13133
13173
  // src/prompt/data-content.ts
13134
13174
  import {
13135
13175
  convertBase64ToUint8Array as convertBase64ToUint8Array3,
@@ -13316,8 +13356,11 @@ async function generateImage({
13316
13356
  delete providerMetadata[providerName].images;
13317
13357
  }
13318
13358
  } else {
13319
- providerMetadata[providerName] ??= { images: [] };
13320
- providerMetadata[providerName].images.push(...metadata.images);
13359
+ const currentEntry = getOwn(providerMetadata, providerName) ?? {
13360
+ images: []
13361
+ };
13362
+ currentEntry.images.push(...metadata.images);
13363
+ setOwn(providerMetadata, providerName, currentEntry);
13321
13364
  }
13322
13365
  }
13323
13366
  }
@@ -13357,7 +13400,7 @@ function getImageProviderMetadata(providerMetadata, imageIndex) {
13357
13400
  for (const [providerName, metadata] of Object.entries(providerMetadata)) {
13358
13401
  const value = metadata.images?.[imageIndex];
13359
13402
  if (isJSONObject(value) && !Array.isArray(value)) {
13360
- (imageMetadata ??= {})[providerName] = value;
13403
+ setOwn(imageMetadata ??= {}, providerName, value);
13361
13404
  }
13362
13405
  }
13363
13406
  return imageMetadata;
@@ -14386,10 +14429,10 @@ async function embedMany({
14386
14429
  for (const [providerName, metadata] of Object.entries(
14387
14430
  result.providerMetadata
14388
14431
  )) {
14389
- providerMetadata[providerName] = {
14390
- ...providerMetadata[providerName],
14432
+ setOwn(providerMetadata, providerName, {
14433
+ ...getOwn(providerMetadata, providerName),
14391
14434
  ...metadata
14392
- };
14435
+ });
14393
14436
  }
14394
14437
  }
14395
14438
  }
@@ -16615,21 +16658,32 @@ function smoothStream({
16615
16658
  controller.enqueue(chunk);
16616
16659
  return;
16617
16660
  }
16618
- if ((chunk.type !== type || chunk.id !== id) && (buffer.length > 0 || providerMetadata != null)) {
16661
+ if (chunk.text.length === 0 && chunk.providerMetadata != null) {
16662
+ flushBuffer(controller);
16663
+ controller.enqueue(chunk);
16664
+ return;
16665
+ }
16666
+ if (buffer.length > 0 && (chunk.type !== type || chunk.id !== id || providerMetadata != null || chunk.providerMetadata != null)) {
16619
16667
  flushBuffer(controller);
16620
16668
  }
16621
16669
  buffer += chunk.text;
16622
16670
  id = chunk.id;
16623
16671
  type = chunk.type;
16624
- if (chunk.providerMetadata != null) {
16625
- providerMetadata = chunk.providerMetadata;
16626
- }
16672
+ providerMetadata = chunk.providerMetadata;
16627
16673
  let match;
16628
16674
  while ((match = detectChunk(buffer)) != null) {
16629
- controller.enqueue({ type, text: match, id });
16675
+ controller.enqueue({
16676
+ type,
16677
+ text: match,
16678
+ id,
16679
+ ...providerMetadata != null ? { providerMetadata } : {}
16680
+ });
16630
16681
  buffer = buffer.slice(match.length);
16631
16682
  await delay(isDocumentHidden() ? null : delayInMs);
16632
16683
  }
16684
+ if (buffer.length === 0) {
16685
+ providerMetadata = void 0;
16686
+ }
16633
16687
  }
16634
16688
  });
16635
16689
  };
@@ -17028,12 +17082,12 @@ async function waitForWebhook({
17028
17082
  }
17029
17083
  function mergeProviderMetadata(target, source) {
17030
17084
  for (const [providerName, metadataValue] of Object.entries(source)) {
17031
- const existingMetadata = target[providerName];
17085
+ const existingMetadata = getOwn(target, providerName);
17032
17086
  if (existingMetadata != null && typeof existingMetadata === "object" && metadataValue != null && typeof metadataValue === "object") {
17033
- target[providerName] = {
17087
+ setOwn(target, providerName, {
17034
17088
  ...existingMetadata,
17035
17089
  ...metadataValue
17036
- };
17090
+ });
17037
17091
  if ("videos" in existingMetadata && Array.isArray(existingMetadata.videos) && "videos" in metadataValue && Array.isArray(metadataValue.videos)) {
17038
17092
  target[providerName].videos = [
17039
17093
  ...existingMetadata.videos,
@@ -17041,7 +17095,7 @@ function mergeProviderMetadata(target, source) {
17041
17095
  ];
17042
17096
  }
17043
17097
  } else {
17044
- target[providerName] = metadataValue;
17098
+ setOwn(target, providerName, metadataValue);
17045
17099
  }
17046
17100
  }
17047
17101
  }
@@ -20710,7 +20764,7 @@ var DefaultProviderRegistry = class {
20710
20764
  languageModelMiddleware,
20711
20765
  imageModelMiddleware
20712
20766
  }) {
20713
- this.providers = {};
20767
+ this.providers = /* @__PURE__ */ Object.create(null);
20714
20768
  this.separator = separator;
20715
20769
  this.languageModelMiddleware = languageModelMiddleware;
20716
20770
  this.imageModelMiddleware = imageModelMiddleware;
@@ -21938,7 +21992,20 @@ var HttpChatTransport = class {
21938
21992
  credentials: resolvedCredentials,
21939
21993
  requestMetadata: options.metadata
21940
21994
  });
21941
- const api = preparedRequest?.api ?? appendPathToUrl(this.api, `/${options.chatId}/stream`);
21995
+ let api = preparedRequest?.api;
21996
+ if (api == null) {
21997
+ if (options.chatId === "." || options.chatId === "..") {
21998
+ throw new InvalidArgumentError({
21999
+ parameter: "chatId",
22000
+ value: options.chatId,
22001
+ message: 'Chat IDs must not be "." or ".." when using the default reconnect URL.'
22002
+ });
22003
+ }
22004
+ api = appendPathToUrl(
22005
+ this.api,
22006
+ `/${encodeURIComponent(options.chatId)}/stream`
22007
+ );
22008
+ }
21942
22009
  const headers = preparedRequest?.headers !== void 0 ? normalizeHeaders(preparedRequest.headers) : baseHeaders;
21943
22010
  const credentials = preparedRequest?.credentials ?? resolvedCredentials;
21944
22011
  const fetch2 = this.fetch ?? globalThis.fetch;
@@ -22368,6 +22435,9 @@ var AbstractChat = class {
22368
22435
  const lastMessage = this.lastMessage;
22369
22436
  const responseMessageIndex = trigger === "submit-message" && messageId != null ? this.state.messages.findIndex((message) => message.id === messageId) : this.state.messages.length - 1;
22370
22437
  const responseMessage = responseMessageIndex === -1 ? lastMessage : this.state.messages[responseMessageIndex];
22438
+ const resumableResponseMessage = trigger === "resume-stream" && responseMessage?.role === "assistant" && responseMessage.parts.some(
22439
+ (part) => isToolUIPart(part) && part.state === "input-streaming"
22440
+ ) ? this.state.snapshot(responseMessage) : void 0;
22371
22441
  const usesEarlierAssistantMessage = responseMessageIndex !== -1 && responseMessageIndex < this.state.messages.length - 1 && responseMessage?.role === "assistant";
22372
22442
  let isAbort = false;
22373
22443
  let isDisconnect = false;
@@ -22376,7 +22446,7 @@ var AbstractChat = class {
22376
22446
  try {
22377
22447
  const response = {
22378
22448
  state: trigger === "resume-stream" && this.resumableStreamState != null ? this.resumableStreamState : createStreamingUIMessageState({
22379
- lastMessage: trigger === "resume-stream" || trigger === "regenerate-message" ? void 0 : this.state.snapshot(responseMessage),
22449
+ lastMessage: trigger === "resume-stream" ? resumableResponseMessage : trigger === "regenerate-message" ? void 0 : this.state.snapshot(responseMessage),
22380
22450
  messageId: this.generateId()
22381
22451
  }),
22382
22452
  abortController
@@ -22608,8 +22678,13 @@ function lastAssistantMessageIsCompleteWithToolCalls({
22608
22678
  const lastStepStartIndex = message.parts.reduce((lastIndex, part, index) => {
22609
22679
  return part.type === "step-start" ? index : lastIndex;
22610
22680
  }, -1);
22611
- const lastStepToolInvocations = message.parts.slice(lastStepStartIndex + 1).filter(isToolUIPart).filter((part) => !part.providerExecuted);
22612
- return lastStepToolInvocations.length > 0 && lastStepToolInvocations.every(
22681
+ const lastStepParts = message.parts.slice(lastStepStartIndex + 1);
22682
+ const lastStepToolInvocations = lastStepParts.filter(isToolUIPart).filter((part) => !part.providerExecuted);
22683
+ const lastToolInvocationIndex = lastStepParts.reduce(
22684
+ (lastIndex, part, index) => isToolUIPart(part) && !part.providerExecuted ? index : lastIndex,
22685
+ -1
22686
+ );
22687
+ return lastStepToolInvocations.length > 0 && !lastStepParts.slice(lastToolInvocationIndex + 1).some((part) => part.type === "text" && part.state !== "done") && lastStepToolInvocations.every(
22613
22688
  (part) => part.state === "output-available" && part.preliminary !== true || part.state === "output-error"
22614
22689
  );
22615
22690
  }