ai 6.0.303 → 6.0.304

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,18 @@
1
1
  # ai
2
2
 
3
+ ## 6.0.304
4
+
5
+ ### Patch Changes
6
+
7
+ - fade070: fix: resume interrupted chat streams when the page becomes visible again
8
+
9
+ `useChat` with `resume: true` no longer starts a second resume request on mount when the chat is already submitted or streaming, e.g. when remounting with a shared `Chat` instance mid-stream.
10
+
11
+ - 8e74de0: fix(ai): update tool outputs in the message containing the matching tool call
12
+ - a5c9cca: fix(ai): prevent duplicate message parts when resuming replayed streams
13
+ - 7888841: fix(ai): update streaming chat messages by stable ID
14
+ - e466fb0: fix(ai): reject approved tool calls without results before sending later context to providers
15
+
3
16
  ## 6.0.303
4
17
 
5
18
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -3677,6 +3677,12 @@ declare function callCompletionApi({ api, prompt, credentials, headers, body, st
3677
3677
  * @template UI_MESSAGE - The UI message type extending UIMessage
3678
3678
  */
3679
3679
  interface ChatTransport<UI_MESSAGE extends UIMessage> {
3680
+ /**
3681
+ * Whether reconnecting returns the complete response from its beginning.
3682
+ * Complete replays must reset the streaming message state before processing
3683
+ * the first start chunk.
3684
+ */
3685
+ readonly resumeStreamIsReplay?: boolean;
3680
3686
  /**
3681
3687
  * Sends messages to the chat API endpoint and returns a streaming response.
3682
3688
  *
@@ -4104,7 +4110,15 @@ declare abstract class HttpChatTransport<UI_MESSAGE extends UIMessage> implement
4104
4110
  }
4105
4111
 
4106
4112
  declare class DefaultChatTransport<UI_MESSAGE extends UIMessage> extends HttpChatTransport<UI_MESSAGE> {
4107
- constructor(options?: HttpChatTransportInitOptions<UI_MESSAGE>);
4113
+ readonly resumeStreamIsReplay: boolean;
4114
+ constructor({ resumeStreamIsReplay, ...options }?: HttpChatTransportInitOptions<UI_MESSAGE> & {
4115
+ /**
4116
+ * Whether resumed streams replay the response from the beginning.
4117
+ * Defaults to true. Set to false for endpoints that only continue the
4118
+ * interrupted response, even when they emit a start chunk.
4119
+ */
4120
+ resumeStreamIsReplay?: boolean;
4121
+ });
4108
4122
  protected processResponseStream(stream: ReadableStream<Uint8Array<ArrayBufferLike>>): ReadableStream<UIMessageChunk>;
4109
4123
  }
4110
4124
 
package/dist/index.d.ts CHANGED
@@ -3677,6 +3677,12 @@ declare function callCompletionApi({ api, prompt, credentials, headers, body, st
3677
3677
  * @template UI_MESSAGE - The UI message type extending UIMessage
3678
3678
  */
3679
3679
  interface ChatTransport<UI_MESSAGE extends UIMessage> {
3680
+ /**
3681
+ * Whether reconnecting returns the complete response from its beginning.
3682
+ * Complete replays must reset the streaming message state before processing
3683
+ * the first start chunk.
3684
+ */
3685
+ readonly resumeStreamIsReplay?: boolean;
3680
3686
  /**
3681
3687
  * Sends messages to the chat API endpoint and returns a streaming response.
3682
3688
  *
@@ -4104,7 +4110,15 @@ declare abstract class HttpChatTransport<UI_MESSAGE extends UIMessage> implement
4104
4110
  }
4105
4111
 
4106
4112
  declare class DefaultChatTransport<UI_MESSAGE extends UIMessage> extends HttpChatTransport<UI_MESSAGE> {
4107
- constructor(options?: HttpChatTransportInitOptions<UI_MESSAGE>);
4113
+ readonly resumeStreamIsReplay: boolean;
4114
+ constructor({ resumeStreamIsReplay, ...options }?: HttpChatTransportInitOptions<UI_MESSAGE> & {
4115
+ /**
4116
+ * Whether resumed streams replay the response from the beginning.
4117
+ * Defaults to true. Set to false for endpoints that only continue the
4118
+ * interrupted response, even when they emit a start chunk.
4119
+ */
4120
+ resumeStreamIsReplay?: boolean;
4121
+ });
4108
4122
  protected processResponseStream(stream: ReadableStream<Uint8Array<ArrayBufferLike>>): ReadableStream<UIMessageChunk>;
4109
4123
  }
4110
4124
 
package/dist/index.js CHANGED
@@ -1335,7 +1335,7 @@ function detectMediaType({
1335
1335
  var import_provider_utils3 = require("@ai-sdk/provider-utils");
1336
1336
 
1337
1337
  // src/version.ts
1338
- var VERSION = true ? "6.0.303" : "0.0.0-test";
1338
+ var VERSION = true ? "6.0.304" : "0.0.0-test";
1339
1339
 
1340
1340
  // src/util/download/download.ts
1341
1341
  var download = async ({
@@ -1535,32 +1535,6 @@ async function convertToLanguageModelPrompt({
1535
1535
  download2 != null ? download2 : createDefaultDownloadFunction(void 0, abortSignal),
1536
1536
  supportedUrls
1537
1537
  );
1538
- const approvalIdToToolCallId = /* @__PURE__ */ new Map();
1539
- for (const message of prompt.messages) {
1540
- if (message.role === "assistant" && Array.isArray(message.content)) {
1541
- for (const part of message.content) {
1542
- if (part.type === "tool-approval-request" && "approvalId" in part && "toolCallId" in part) {
1543
- approvalIdToToolCallId.set(
1544
- part.approvalId,
1545
- part.toolCallId
1546
- );
1547
- }
1548
- }
1549
- }
1550
- }
1551
- const approvedToolCallIds = /* @__PURE__ */ new Set();
1552
- for (const message of prompt.messages) {
1553
- if (message.role === "tool") {
1554
- for (const part of message.content) {
1555
- if (part.type === "tool-approval-response") {
1556
- const toolCallId = approvalIdToToolCallId.get(part.approvalId);
1557
- if (toolCallId) {
1558
- approvedToolCallIds.add(toolCallId);
1559
- }
1560
- }
1561
- }
1562
- }
1563
- }
1564
1538
  const messages = [
1565
1539
  ...prompt.system != null ? typeof prompt.system === "string" ? [{ role: "system", content: prompt.system }] : asArray(prompt.system).map((message) => ({
1566
1540
  role: "system",
@@ -1613,9 +1587,6 @@ async function convertToLanguageModelPrompt({
1613
1587
  }
1614
1588
  case "user":
1615
1589
  case "system":
1616
- for (const id of approvedToolCallIds) {
1617
- toolCallIds.delete(id);
1618
- }
1619
1590
  if (toolCallIds.size > 0) {
1620
1591
  throw new MissingToolResultsError({
1621
1592
  toolCallIds: Array.from(toolCallIds)
@@ -1624,9 +1595,6 @@ async function convertToLanguageModelPrompt({
1624
1595
  break;
1625
1596
  }
1626
1597
  }
1627
- for (const id of approvedToolCallIds) {
1628
- toolCallIds.delete(id);
1629
- }
1630
1598
  if (toolCallIds.size > 0) {
1631
1599
  throw new MissingToolResultsError({ toolCallIds: Array.from(toolCallIds) });
1632
1600
  }
@@ -6125,13 +6093,15 @@ function processUIMessageStream({
6125
6093
  onError,
6126
6094
  onToolCall,
6127
6095
  onData,
6128
- resetStateOnMessageIdChange = false
6096
+ resetStateOnMessageIdChange = false,
6097
+ resetStateOnFirstMessageStart = false
6129
6098
  }) {
6099
+ let isFirstMessageStart = true;
6130
6100
  return stream.pipeThrough(
6131
6101
  new TransformStream({
6132
6102
  async transform(chunk, controller) {
6133
6103
  await runUpdateMessageJob(async ({ state, write }) => {
6134
- var _a23, _b, _c, _d, _e, _f, _g, _h;
6104
+ var _a23, _b, _c, _d, _e, _f, _g, _h, _i;
6135
6105
  function getCurrentStepParts() {
6136
6106
  const parts = state.message.parts;
6137
6107
  let currentStepStartIndex = parts.length - 1;
@@ -6638,12 +6608,14 @@ function processUIMessageStream({
6638
6608
  break;
6639
6609
  }
6640
6610
  case "start": {
6641
- if (resetStateOnMessageIdChange && chunk.messageId != null && chunk.messageId !== state.message.id) {
6611
+ const shouldReset = resetStateOnFirstMessageStart && isFirstMessageStart || resetStateOnMessageIdChange && chunk.messageId != null && chunk.messageId !== state.message.id;
6612
+ isFirstMessageStart = false;
6613
+ if (shouldReset) {
6642
6614
  Object.assign(
6643
6615
  state,
6644
6616
  createStreamingUIMessageState({
6645
6617
  lastMessage: void 0,
6646
- messageId: chunk.messageId
6618
+ messageId: (_i = chunk.messageId) != null ? _i : state.message.id
6647
6619
  }),
6648
6620
  { finishReason: void 0 }
6649
6621
  );
@@ -6652,7 +6624,7 @@ function processUIMessageStream({
6652
6624
  state.message.id = chunk.messageId;
6653
6625
  }
6654
6626
  await updateMessageMetadata(chunk.messageMetadata);
6655
- if (chunk.messageId != null || chunk.messageMetadata != null) {
6627
+ if (shouldReset || chunk.messageId != null || chunk.messageMetadata != null) {
6656
6628
  write({ updateStatus: false });
6657
6629
  }
6658
6630
  break;
@@ -14645,8 +14617,12 @@ var HttpChatTransport = class {
14645
14617
 
14646
14618
  // src/ui/default-chat-transport.ts
14647
14619
  var DefaultChatTransport = class extends HttpChatTransport {
14648
- constructor(options = {}) {
14620
+ constructor({
14621
+ resumeStreamIsReplay = true,
14622
+ ...options
14623
+ } = {}) {
14649
14624
  super(options);
14625
+ this.resumeStreamIsReplay = resumeStreamIsReplay;
14650
14626
  }
14651
14627
  processResponseStream(stream) {
14652
14628
  return (0, import_provider_utils42.parseJsonEventStream)({
@@ -14791,6 +14767,27 @@ var AbstractChat = class {
14791
14767
  this.resumeStream = async (options = {}) => {
14792
14768
  await this.makeRequest({ trigger: "resume-stream", ...options });
14793
14769
  };
14770
+ /**
14771
+ * Resume the stream if the latest response ended with a resumable network
14772
+ * disconnect. If a response is still active, wait for it to settle first so
14773
+ * disconnects reported shortly after the caller's trigger are handled.
14774
+ *
14775
+ * @internal
14776
+ */
14777
+ this["~resumeStreamIfDisconnected"] = async ({
14778
+ shouldResume = () => true
14779
+ } = {}) => {
14780
+ const activeResponse = this.activeResponse;
14781
+ if (this.resumableStreamState == null && activeResponse != null) {
14782
+ await activeResponse.completionPromise;
14783
+ if (this.resumableStreamState !== activeResponse.state) {
14784
+ return;
14785
+ }
14786
+ }
14787
+ if (this.resumableStreamState != null && shouldResume()) {
14788
+ await this.resumeStream();
14789
+ }
14790
+ };
14794
14791
  /**
14795
14792
  * Clear the error state and set the status to ready if the chat is in an error state.
14796
14793
  */
@@ -14851,27 +14848,31 @@ var AbstractChat = class {
14851
14848
  options
14852
14849
  }) => this.jobExecutor.run(async () => {
14853
14850
  const messages = this.state.messages;
14854
- const lastMessage = messages[messages.length - 1];
14855
14851
  const updatePart = (part) => isToolUIPart(part) && part.toolCallId === toolCallId ? { ...part, state, output, errorText } : part;
14856
- this.state.replaceMessage(messages.length - 1, {
14857
- ...lastMessage,
14858
- parts: lastMessage.parts.map(updatePart)
14859
- });
14852
+ const messageIndex = messages.findIndex(
14853
+ (message) => message.parts.some(
14854
+ (part) => isToolUIPart(part) && part.toolCallId === toolCallId
14855
+ )
14856
+ );
14857
+ if (messageIndex !== -1) {
14858
+ const message = messages[messageIndex];
14859
+ this.state.replaceMessage(messageIndex, {
14860
+ ...message,
14861
+ parts: message.parts.map(updatePart)
14862
+ });
14863
+ }
14860
14864
  if (this.activeResponse) {
14861
14865
  this.activeResponse.state.message.parts = this.activeResponse.state.message.parts.map(updatePart);
14862
14866
  }
14863
- if (this.status !== "streaming" && this.status !== "submitted" && this.sendAutomaticallyWhen) {
14867
+ if (messageIndex !== -1 && this.status !== "streaming" && this.status !== "submitted" && this.sendAutomaticallyWhen) {
14864
14868
  const shouldSend = await this.shouldSendAutomatically();
14865
14869
  if (shouldSend) {
14866
14870
  void this.runAutomaticRequest(
14867
- () => {
14868
- var _a23;
14869
- return this.makeRequest({
14870
- trigger: "submit-message",
14871
- messageId: (_a23 = this.lastMessage) == null ? void 0 : _a23.id,
14872
- ...options
14873
- });
14874
- },
14871
+ () => this.makeRequest({
14872
+ trigger: "submit-message",
14873
+ messageId: messages[messageIndex].id,
14874
+ ...options
14875
+ }),
14875
14876
  shouldSend
14876
14877
  );
14877
14878
  }
@@ -15075,6 +15076,7 @@ var AbstractChat = class {
15075
15076
  const lastMessage = this.lastMessage;
15076
15077
  const responseMessageIndex = trigger === "submit-message" && messageId != null ? this.state.messages.findIndex((message) => message.id === messageId) : this.state.messages.length - 1;
15077
15078
  const responseMessage = responseMessageIndex === -1 ? lastMessage : this.state.messages[responseMessageIndex];
15079
+ const originalResponseMessageId = responseMessage == null ? void 0 : responseMessage.id;
15078
15080
  const resumableResponseMessage = trigger === "resume-stream" && (responseMessage == null ? void 0 : responseMessage.role) === "assistant" && responseMessage.parts.some(
15079
15081
  (part) => isToolUIPart(part) && part.state === "input-streaming"
15080
15082
  ) ? this.state.snapshot(responseMessage) : void 0;
@@ -15121,23 +15123,32 @@ var AbstractChat = class {
15121
15123
  return job({
15122
15124
  state: response.state,
15123
15125
  write: ({ updateStatus = true } = {}) => {
15124
- var _a24;
15125
15126
  if (response.abortController.signal.aborted) {
15126
15127
  return;
15127
15128
  }
15128
15129
  if (updateStatus) {
15129
15130
  this.setStatus({ status: "streaming" });
15130
15131
  }
15131
- if (usesEarlierAssistantMessage) {
15132
+ const existingMessageIndex = this.state.messages.findLastIndex(
15133
+ (message) => message.id === response.state.message.id
15134
+ );
15135
+ if (existingMessageIndex !== -1) {
15132
15136
  this.state.replaceMessage(
15133
- responseMessageIndex,
15137
+ existingMessageIndex,
15134
15138
  response.state.message
15135
15139
  );
15136
- } else if (response.state.message.id === ((_a24 = this.lastMessage) == null ? void 0 : _a24.id)) {
15137
- this.state.replaceMessage(
15138
- this.state.messages.length - 1,
15139
- response.state.message
15140
+ } else if (usesEarlierAssistantMessage) {
15141
+ const originalMessageIndex = this.state.messages.findLastIndex(
15142
+ (message) => message.id === originalResponseMessageId
15140
15143
  );
15144
+ if (originalMessageIndex !== -1) {
15145
+ this.state.replaceMessage(
15146
+ originalMessageIndex,
15147
+ response.state.message
15148
+ );
15149
+ } else {
15150
+ this.state.pushMessage(response.state.message);
15151
+ }
15141
15152
  } else {
15142
15153
  this.state.pushMessage(response.state.message);
15143
15154
  }
@@ -15148,6 +15159,8 @@ var AbstractChat = class {
15148
15159
  await consumeStream({
15149
15160
  stream: processUIMessageStream({
15150
15161
  stream,
15162
+ resetStateOnMessageIdChange: trigger === "resume-stream",
15163
+ resetStateOnFirstMessageStart: trigger === "resume-stream" && this.transport.resumeStreamIsReplay,
15151
15164
  onToolCall: this.onToolCall,
15152
15165
  onData: this.onData,
15153
15166
  messageMetadataSchema: this.messageMetadataSchema,