@assistant-ui/react 0.0.13 → 0.0.15

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/dist/index.js CHANGED
@@ -75,7 +75,7 @@ var useAssistantContext = () => {
75
75
  const context = (0, import_react2.useContext)(AssistantContext);
76
76
  if (!context)
77
77
  throw new Error(
78
- "useAssistantContext must be used within a AssistantProvider"
78
+ "This component must be used within a AssistantProvider."
79
79
  );
80
80
  return context;
81
81
  };
@@ -84,14 +84,10 @@ var useAssistantContext = () => {
84
84
  var useThreadIf = (props) => {
85
85
  const { useThread } = useAssistantContext();
86
86
  return useThread((thread) => {
87
- if (props.empty === true && thread.messages.length !== 0)
88
- return false;
89
- if (props.empty === false && thread.messages.length === 0)
90
- return false;
91
- if (props.running === true && !thread.isRunning)
92
- return false;
93
- if (props.running === false && thread.isRunning)
94
- return false;
87
+ if (props.empty === true && thread.messages.length !== 0) return false;
88
+ if (props.empty === false && thread.messages.length === 0) return false;
89
+ if (props.running === true && !thread.isRunning) return false;
90
+ if (props.running === false && thread.isRunning) return false;
95
91
  return true;
96
92
  });
97
93
  };
@@ -119,8 +115,7 @@ var useOnResizeContent = (ref, callback) => {
119
115
  callbackRef.current = callback;
120
116
  (0, import_react3.useLayoutEffect)(() => {
121
117
  const el = ref.current;
122
- if (!el)
123
- return;
118
+ if (!el) return;
124
119
  const resizeObserver = new ResizeObserver(() => {
125
120
  callbackRef.current();
126
121
  });
@@ -175,16 +170,14 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
175
170
  const lastScrollTop = (0, import_react5.useRef)(0);
176
171
  const scrollToBottom = () => {
177
172
  const div = messagesEndRef.current;
178
- if (!div || !autoScroll)
179
- return;
173
+ if (!div || !autoScroll) return;
180
174
  const behavior = firstRenderRef.current ? "instant" : "auto";
181
175
  firstRenderRef.current = false;
182
176
  useViewport.setState({ isAtBottom: true });
183
177
  div.scrollIntoView({ behavior });
184
178
  };
185
179
  useOnResizeContent(divRef, () => {
186
- if (!useViewport.getState().isAtBottom)
187
- return;
180
+ if (!useViewport.getState().isAtBottom) return;
188
181
  scrollToBottom();
189
182
  });
190
183
  useOnScrollToBottom(() => {
@@ -192,8 +185,7 @@ var ThreadViewport = (0, import_react5.forwardRef)(({ autoScroll = true, onScrol
192
185
  });
193
186
  const handleScroll = () => {
194
187
  const div = divRef.current;
195
- if (!div)
196
- return;
188
+ if (!div) return;
197
189
  const isAtBottom = useViewport.getState().isAtBottom;
198
190
  const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
199
191
  if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
@@ -225,7 +217,7 @@ var MessageContext = (0, import_react6.createContext)(null);
225
217
  var useMessageContext = () => {
226
218
  const context = (0, import_react6.useContext)(MessageContext);
227
219
  if (!context)
228
- throw new Error("useMessageContext must be used within a MessageProvider");
220
+ throw new Error("This component must be used within a MessageProvider.");
229
221
  return context;
230
222
  };
231
223
 
@@ -243,10 +235,8 @@ var useComposerContext = () => {
243
235
  var useComposerIf = (props) => {
244
236
  const { useComposer } = useComposerContext();
245
237
  return useComposer((composer) => {
246
- if (props.editing === true && !composer.isEditing)
247
- return false;
248
- if (props.editing === false && composer.isEditing)
249
- return false;
238
+ if (props.editing === true && !composer.isEditing) return false;
239
+ if (props.editing === false && composer.isEditing) return false;
250
240
  return true;
251
241
  });
252
242
  };
@@ -268,6 +258,14 @@ __export(message_exports, {
268
258
  var import_react8 = require("react");
269
259
  var import_zustand2 = require("zustand");
270
260
 
261
+ // src/utils/context/getMessageText.tsx
262
+ var getMessageText = (message) => {
263
+ const textParts = message.content.filter(
264
+ (part) => part.type === "text"
265
+ );
266
+ return textParts.map((part) => part.text).join("\n\n");
267
+ };
268
+
271
269
  // src/utils/context/stores/ComposerStore.ts
272
270
  var import_zustand = require("zustand");
273
271
  var makeBaseComposer = (set) => ({
@@ -292,8 +290,7 @@ var makeMessageComposerStore = ({
292
290
  onSend(value);
293
291
  },
294
292
  cancel: () => {
295
- if (!get().isEditing)
296
- return false;
293
+ if (!get().isEditing) return false;
297
294
  set({ isEditing: false });
298
295
  return true;
299
296
  }
@@ -312,8 +309,7 @@ var makeThreadComposerStore = (useThread) => (0, import_zustand.create)()((set,
312
309
  },
313
310
  cancel: () => {
314
311
  const thread = useThread.getState();
315
- if (!thread.isRunning)
316
- return false;
312
+ if (!thread.isRunning) return false;
317
313
  useThread.getState().cancelRun();
318
314
  return true;
319
315
  }
@@ -328,31 +324,42 @@ var getIsLast = (thread, message) => {
328
324
  var useMessageContext2 = () => {
329
325
  const { useThread } = useAssistantContext();
330
326
  const [context] = (0, import_react8.useState)(() => {
331
- const useMessage = (0, import_zustand2.create)(() => ({
327
+ const useMessage = (0, import_zustand2.create)((set) => ({
332
328
  message: null,
329
+ parentId: null,
333
330
  branches: [],
334
331
  isLast: false,
335
332
  isCopied: false,
336
333
  isHovering: false,
337
- setIsCopied: () => {
334
+ setIsCopied: (value) => {
335
+ set({ isCopied: value });
338
336
  },
339
- setIsHovering: () => {
337
+ setIsHovering: (value) => {
338
+ set({ isHovering: value });
340
339
  }
341
340
  }));
342
341
  const useComposer = makeMessageComposerStore({
343
342
  onEdit: () => {
344
343
  const message = useMessage.getState().message;
345
344
  if (message.role !== "user")
346
- throw new Error("Editing is only supported for user messages");
347
- if (message.content[0]?.type !== "text")
348
- throw new Error("Editing is only supported for text-only messages");
349
- return message.content[0].text;
345
+ throw new Error(
346
+ "Tried to edit a non-user message. Editing is only supported for user messages. This is likely an internal bug in assistant-ui."
347
+ );
348
+ const text = getMessageText(message);
349
+ return text;
350
350
  },
351
351
  onSend: (text) => {
352
- const message = useMessage.getState().message;
352
+ const { message, parentId } = useMessage.getState();
353
+ if (message.role !== "user")
354
+ throw new Error(
355
+ "Tried to edit a non-user message. Editing is only supported for user messages. This is likely an internal bug in assistant-ui."
356
+ );
357
+ const nonTextParts = message.content.filter(
358
+ (part) => part.type !== "text" && part.type !== "ui"
359
+ );
353
360
  useThread.getState().append({
354
- parentId: message.parentId,
355
- content: [{ type: "text", text }]
361
+ parentId,
362
+ content: [{ type: "text", text }, ...nonTextParts]
356
363
  });
357
364
  }
358
365
  });
@@ -362,28 +369,21 @@ var useMessageContext2 = () => {
362
369
  };
363
370
  var MessageProvider = ({
364
371
  message,
372
+ parentId,
365
373
  children
366
374
  }) => {
367
375
  const { useThread } = useAssistantContext();
368
376
  const context = useMessageContext2();
369
377
  const isLast = useThread((thread) => getIsLast(thread, message));
370
378
  const branches = useThread((thread) => thread.getBranches(message.id));
371
- const [isCopied, setIsCopied] = (0, import_react8.useState)(false);
372
- const [isHovering, setIsHovering] = (0, import_react8.useState)(false);
373
379
  (0, import_react8.useMemo)(() => {
374
- context.useMessage.setState(
375
- {
376
- message,
377
- branches,
378
- isLast,
379
- isCopied,
380
- isHovering,
381
- setIsCopied,
382
- setIsHovering
383
- },
384
- true
385
- );
386
- }, [context, message, branches, isLast, isCopied, isHovering]);
380
+ context.useMessage.setState({
381
+ message,
382
+ parentId,
383
+ branches,
384
+ isLast
385
+ });
386
+ }, [context, message, parentId, branches, isLast]);
387
387
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MessageContext.Provider, { value: context, children });
388
388
  };
389
389
 
@@ -418,18 +418,12 @@ var MessageRoot = (0, import_react9.forwardRef)(
418
418
  var useMessageIf = (props) => {
419
419
  const { useMessage } = useMessageContext();
420
420
  return useMessage(({ message, branches, isLast, isCopied, isHovering }) => {
421
- if (props.hasBranches === true && branches.length < 2)
422
- return false;
423
- if (props.user && message.role !== "user")
424
- return false;
425
- if (props.assistant && message.role !== "assistant")
426
- return false;
427
- if (props.lastOrHover === true && !isHovering && !isLast)
428
- return false;
429
- if (props.copied === true && !isCopied)
430
- return false;
431
- if (props.copied === false && isCopied)
432
- return false;
421
+ if (props.hasBranches === true && branches.length < 2) return false;
422
+ if (props.user && message.role !== "user") return false;
423
+ if (props.assistant && message.role !== "assistant") return false;
424
+ if (props.lastOrHover === true && !isHovering && !isLast) return false;
425
+ if (props.copied === true && !isCopied) return false;
426
+ if (props.copied === false && isCopied) return false;
433
427
  return true;
434
428
  });
435
429
  };
@@ -491,18 +485,23 @@ var ThreadMessages = ({ components }) => {
491
485
  const thread = useThread();
492
486
  const messages = thread.messages;
493
487
  const { UserMessage, EditComposer, AssistantMessage } = getComponents(components);
494
- if (messages.length === 0)
495
- return null;
488
+ if (messages.length === 0) return null;
496
489
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: messages.map((message, idx) => {
497
- return (
498
- // biome-ignore lint/suspicious/noArrayIndexKey: fixes a11y issues with branch navigation
499
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(MessageProvider, { message, children: [
500
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(MessageIf, { user: true, children: [
501
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ComposerIf, { editing: false, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(UserMessage, {}) }),
502
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ComposerIf, { editing: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EditComposer, {}) })
503
- ] }),
504
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MessageIf, { assistant: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssistantMessage, {}) })
505
- ] }, idx)
490
+ const parentId = messages[idx - 1]?.id ?? null;
491
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
492
+ MessageProvider,
493
+ {
494
+ message,
495
+ parentId,
496
+ children: [
497
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(MessageIf, { user: true, children: [
498
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ComposerIf, { editing: false, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(UserMessage, {}) }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ComposerIf, { editing: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(EditComposer, {}) })
500
+ ] }),
501
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MessageIf, { assistant: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AssistantMessage, {}) })
502
+ ]
503
+ },
504
+ parentId ?? "__ROOT__"
506
505
  );
507
506
  }) });
508
507
  };
@@ -580,8 +579,7 @@ var ComposerRoot = (0, import_react12.forwardRef)(
580
579
  const ref = (0, import_react_compose_refs2.useComposedRefs)(forwardedRef, formRef);
581
580
  const handleSubmit = (e) => {
582
581
  const composerState = useComposer.getState();
583
- if (!composerState.isEditing)
584
- return;
582
+ if (!composerState.isEditing) return;
585
583
  e.preventDefault();
586
584
  composerState.send();
587
585
  useViewport.getState().scrollToBottom();
@@ -605,18 +603,16 @@ var import_react13 = require("react");
605
603
  var import_react_textarea_autosize = __toESM(require("react-textarea-autosize"));
606
604
  var import_jsx_runtime11 = require("react/jsx-runtime");
607
605
  var ComposerInput = (0, import_react13.forwardRef)(
608
- ({ autoFocus, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
606
+ ({ autoFocus = false, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
609
607
  const { useThread, useViewport } = useAssistantContext();
610
608
  const { useComposer, type } = useComposerContext();
611
609
  const value = useComposer((c) => {
612
- if (!c.isEditing)
613
- return "";
610
+ if (!c.isEditing) return "";
614
611
  return c.value;
615
612
  });
616
613
  const Component = asChild ? import_react_slot.Slot : import_react_textarea_autosize.default;
617
614
  const handleKeyPress = (e) => {
618
- if (disabled)
619
- return;
615
+ if (disabled) return;
620
616
  const composer = useComposer.getState();
621
617
  if (e.key === "Escape") {
622
618
  if (useComposer.getState().cancel()) {
@@ -633,11 +629,10 @@ var ComposerInput = (0, import_react13.forwardRef)(
633
629
  };
634
630
  const textareaRef = (0, import_react13.useRef)(null);
635
631
  const ref = (0, import_react_compose_refs3.useComposedRefs)(forwardedRef, textareaRef);
636
- const autoFocusEnabled = autoFocus !== false && !disabled;
632
+ const autoFocusEnabled = autoFocus && !disabled;
637
633
  const focus = (0, import_react13.useCallback)(() => {
638
634
  const textarea = textareaRef.current;
639
- if (!textarea || !autoFocusEnabled)
640
- return;
635
+ if (!textarea || !autoFocusEnabled) return;
641
636
  textarea.focus();
642
637
  textarea.setSelectionRange(
643
638
  textareaRef.current.value.length,
@@ -659,8 +654,7 @@ var ComposerInput = (0, import_react13.forwardRef)(
659
654
  disabled,
660
655
  onChange: (0, import_primitive6.composeEventHandlers)(onChange, (e) => {
661
656
  const composerState = useComposer.getState();
662
- if (!composerState.isEditing)
663
- return;
657
+ if (!composerState.isEditing) return;
664
658
  return composerState.setValue(e.target.value);
665
659
  }),
666
660
  onKeyDown: (0, import_primitive6.composeEventHandlers)(onKeyDown, handleKeyPress)
@@ -754,8 +748,7 @@ var useGoToNextBranch = () => {
754
748
  [useMessage, useComposer],
755
749
  (m, c) => c.isEditing || m.branches.indexOf(m.message.id) + 1 >= m.branches.length
756
750
  );
757
- if (disabled)
758
- return null;
751
+ if (disabled) return null;
759
752
  return () => {
760
753
  const { message, branches } = useMessage.getState();
761
754
  useThread.getState().switchToBranch(branches[branches.indexOf(message.id) + 1]);
@@ -796,8 +789,7 @@ var useGoToPreviousBranch = () => {
796
789
  [useMessage, useComposer],
797
790
  (m, c) => c.isEditing || m.branches.indexOf(m.message.id) <= 0
798
791
  );
799
- if (disabled)
800
- return null;
792
+ if (disabled) return null;
801
793
  return () => {
802
794
  const { message, branches } = useMessage.getState();
803
795
  useThread.getState().switchToBranch(
@@ -853,20 +845,16 @@ var ActionBarRoot = (0, import_react20.forwardRef)(({ hideWhenRunning, autohide,
853
845
  const hideAndfloatStatus = useCombinedStore(
854
846
  [useThread, useMessage],
855
847
  (t, m) => {
856
- if (hideWhenRunning && t.isRunning)
857
- return "hidden" /* Hidden */;
848
+ if (hideWhenRunning && t.isRunning) return "hidden" /* Hidden */;
858
849
  const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
859
- if (!autohideEnabled)
860
- return "normal" /* Normal */;
861
- if (!m.isHovering)
862
- return "hidden" /* Hidden */;
850
+ if (!autohideEnabled) return "normal" /* Normal */;
851
+ if (!m.isHovering) return "hidden" /* Hidden */;
863
852
  if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branches.length <= 1)
864
853
  return "floating" /* Floating */;
865
854
  return "normal" /* Normal */;
866
855
  }
867
856
  );
868
- if (hideAndfloatStatus === "hidden" /* Hidden */)
869
- return null;
857
+ if (hideAndfloatStatus === "hidden" /* Hidden */) return null;
870
858
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
871
859
  import_react_primitive11.Primitive.div,
872
860
  {
@@ -880,14 +868,18 @@ var ActionBarRoot = (0, import_react20.forwardRef)(({ hideWhenRunning, autohide,
880
868
  // src/actions/useCopyMessage.tsx
881
869
  var useCopyMessage = ({ copiedDuration = 3e3 }) => {
882
870
  const { useMessage, useComposer } = useMessageContext();
883
- const isEditing = useComposer((s) => s.isEditing);
884
- if (isEditing)
885
- return null;
871
+ const hasCopyableContent = useCombinedStore(
872
+ [useMessage, useComposer],
873
+ (m, c) => {
874
+ return c.isEditing || m.message.content.some((c2) => c2.type === "text");
875
+ }
876
+ );
877
+ if (!hasCopyableContent) return null;
886
878
  return () => {
879
+ const { isEditing, value: composerValue } = useComposer.getState();
887
880
  const { message, setIsCopied } = useMessage.getState();
888
- if (message.content[0]?.type !== "text")
889
- throw new Error("Copying is only supported for text-only messages");
890
- navigator.clipboard.writeText(message.content[0].text);
881
+ const valueToCopy = isEditing ? composerValue : getMessageText(message);
882
+ navigator.clipboard.writeText(valueToCopy);
891
883
  setIsCopied(true);
892
884
  setTimeout(() => setIsCopied(false), copiedDuration);
893
885
  };
@@ -904,13 +896,10 @@ var useReloadMessage = () => {
904
896
  [useThread, useMessage],
905
897
  (t, m) => t.isRunning || m.message.role !== "assistant"
906
898
  );
907
- if (disabled)
908
- return null;
899
+ if (disabled) return null;
909
900
  return () => {
910
- const message = useMessage.getState().message;
911
- if (message.role !== "assistant")
912
- throw new Error("Reloading is only supported on assistant messages");
913
- useThread.getState().startRun(message.parentId);
901
+ const { parentId } = useMessage.getState();
902
+ useThread.getState().startRun(parentId);
914
903
  useViewport.getState().scrollToBottom();
915
904
  };
916
905
  };
@@ -925,8 +914,7 @@ var useBeginMessageEdit = () => {
925
914
  [useMessage, useComposer],
926
915
  (m, c) => m.message.role !== "user" || c.isEditing
927
916
  );
928
- if (disabled)
929
- return null;
917
+ if (disabled) return null;
930
918
  return () => {
931
919
  const { edit } = useComposer.getState();
932
920
  edit();
@@ -974,13 +962,13 @@ var makeDummyThreadStore = () => {
974
962
  switchToBranch: () => {
975
963
  throw new Error("Not implemented");
976
964
  },
977
- append: async () => {
965
+ append: () => {
978
966
  throw new Error("Not implemented");
979
967
  },
980
- cancelRun: () => {
968
+ startRun: () => {
981
969
  throw new Error("Not implemented");
982
970
  },
983
- startRun: async () => {
971
+ cancelRun: () => {
984
972
  throw new Error("Not implemented");
985
973
  }
986
974
  }));
@@ -995,7 +983,7 @@ var useDummyAIAssistantContext = () => {
995
983
  return context;
996
984
  };
997
985
 
998
- // src/adapters/vercel/useVercelAIBranches.tsx
986
+ // src/adapters/vercel/useVercelAIThreadState.tsx
999
987
  var import_react22 = require("react");
1000
988
 
1001
989
  // src/adapters/MessageRepository.tsx
@@ -1008,8 +996,7 @@ var optimisticPrefix = "__optimistic__";
1008
996
  var generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
1009
997
  var isOptimisticId = (id) => id.startsWith(optimisticPrefix);
1010
998
  var findHead = (message) => {
1011
- if (message.next)
1012
- return findHead(message.next);
999
+ if (message.next) return findHead(message.next);
1013
1000
  return message;
1014
1001
  };
1015
1002
  var MessageRepository = class {
@@ -1024,19 +1011,21 @@ var MessageRepository = class {
1024
1011
  }
1025
1012
  return messages;
1026
1013
  }
1027
- addOrUpdateMessage(message) {
1014
+ addOrUpdateMessage(parentId, message) {
1028
1015
  const item = this.messages.get(message.id);
1029
1016
  if (item) {
1030
- if (item.current.parentId !== message.parentId) {
1017
+ if ((item.prev?.current.id ?? null) !== parentId) {
1031
1018
  this.deleteMessage(message.id);
1032
1019
  } else {
1033
1020
  item.current = message;
1034
1021
  return;
1035
1022
  }
1036
1023
  }
1037
- const prev = message.parentId ? this.messages.get(message.parentId) : null;
1024
+ const prev = parentId ? this.messages.get(parentId) : null;
1038
1025
  if (prev === void 0)
1039
- throw new Error("Unexpected: Parent message not found");
1026
+ throw new Error(
1027
+ "MessageRepository(addOrUpdateMessage): Parent message not found. This is likely an internal bug in assistant-ui."
1028
+ );
1040
1029
  const newItem = {
1041
1030
  prev,
1042
1031
  current: message,
@@ -1058,7 +1047,9 @@ var MessageRepository = class {
1058
1047
  deleteMessage(messageId) {
1059
1048
  const message = this.messages.get(messageId);
1060
1049
  if (!message)
1061
- throw new Error("Unexpected: Message not found");
1050
+ throw new Error(
1051
+ "MessageRepository(deleteMessage): Message not found. This is likely an internal bug in assistant-ui."
1052
+ );
1062
1053
  if (message.children.length > 0) {
1063
1054
  for (const child of message.children) {
1064
1055
  this.deleteMessage(child);
@@ -1073,7 +1064,9 @@ var MessageRepository = class {
1073
1064
  const childId = message.prev.children.at(-1);
1074
1065
  const child = childId ? this.messages.get(childId) : null;
1075
1066
  if (child === void 0)
1076
- throw new Error("Unexpected: Child message not found");
1067
+ throw new Error(
1068
+ "MessageRepository(deleteMessage): Child message not found. This is likely an internal bug in assistant-ui."
1069
+ );
1077
1070
  message.prev.next = child;
1078
1071
  }
1079
1072
  } else {
@@ -1083,16 +1076,16 @@ var MessageRepository = class {
1083
1076
  this.head = message.prev ? findHead(message.prev) : null;
1084
1077
  }
1085
1078
  }
1086
- getOptimisticId = () => {
1079
+ getOptimisticId() {
1087
1080
  let optimisticId;
1088
1081
  do {
1089
1082
  optimisticId = generateOptimisticId();
1090
1083
  } while (this.messages.has(optimisticId));
1091
1084
  return optimisticId;
1092
- };
1085
+ }
1093
1086
  commitOptimisticRun(parentId) {
1094
1087
  const optimisticId = this.getOptimisticId();
1095
- this.addOrUpdateMessage({
1088
+ this.addOrUpdateMessage(parentId, {
1096
1089
  id: optimisticId,
1097
1090
  role: "assistant",
1098
1091
  content: [
@@ -1101,7 +1094,6 @@ var MessageRepository = class {
1101
1094
  text: ""
1102
1095
  }
1103
1096
  ],
1104
- parentId,
1105
1097
  createdAt: /* @__PURE__ */ new Date()
1106
1098
  });
1107
1099
  return optimisticId;
@@ -1109,7 +1101,9 @@ var MessageRepository = class {
1109
1101
  getBranches(messageId) {
1110
1102
  const message = this.messages.get(messageId);
1111
1103
  if (!message)
1112
- throw new Error("Unexpected: Message not found");
1104
+ throw new Error(
1105
+ "MessageRepository(getBranches): Message not found. This is likely an internal bug in assistant-ui."
1106
+ );
1113
1107
  if (message.prev) {
1114
1108
  return message.prev.children;
1115
1109
  }
@@ -1118,7 +1112,9 @@ var MessageRepository = class {
1118
1112
  switchToBranch(messageId) {
1119
1113
  const message = this.messages.get(messageId);
1120
1114
  if (!message)
1121
- throw new Error("Unexpected: Branch not found");
1115
+ throw new Error(
1116
+ "MessageRepository(switchToBranch): Branch not found. This is likely an internal bug in assistant-ui."
1117
+ );
1122
1118
  if (message.prev) {
1123
1119
  message.prev.next = message;
1124
1120
  }
@@ -1128,7 +1124,9 @@ var MessageRepository = class {
1128
1124
  if (messageId) {
1129
1125
  const message = this.messages.get(messageId);
1130
1126
  if (!message)
1131
- throw new Error("Unexpected: Branch not found");
1127
+ throw new Error(
1128
+ "MessageRepository(resetHead): Branch not found. This is likely an internal bug in assistant-ui."
1129
+ );
1132
1130
  this.head = message;
1133
1131
  for (let current = message; current; current = current.prev) {
1134
1132
  if (current.prev) {
@@ -1141,151 +1139,171 @@ var MessageRepository = class {
1141
1139
  }
1142
1140
  };
1143
1141
 
1144
- // src/adapters/vercel/useVercelAIBranches.tsx
1142
+ // src/adapters/ThreadMessageConverter.tsx
1143
+ var ThreadMessageConverter = class {
1144
+ constructor(converter2) {
1145
+ this.converter = converter2;
1146
+ }
1147
+ cache = /* @__PURE__ */ new WeakMap();
1148
+ convertMessages(messages) {
1149
+ return messages.map((m) => {
1150
+ const cached = this.cache.get(m);
1151
+ if (cached) return cached;
1152
+ const newMessage = this.converter(m);
1153
+ this.cache.set(m, newMessage);
1154
+ return newMessage;
1155
+ });
1156
+ }
1157
+ };
1158
+
1159
+ // src/adapters/vercel/useVercelAIThreadState.tsx
1160
+ var vercelToThreadMessage = (message) => {
1161
+ if (message.role !== "user" && message.role !== "assistant")
1162
+ throw new Error(
1163
+ `You have a message with an unsupported role. The role ${message.role} is not supported.`
1164
+ );
1165
+ return {
1166
+ id: message.id,
1167
+ role: message.role,
1168
+ content: [
1169
+ ...message.content ? [{ type: "text", text: message.content }] : [],
1170
+ ...message.toolInvocations?.map((t) => ({
1171
+ type: "tool-call",
1172
+ name: t.toolName,
1173
+ args: t.args,
1174
+ result: "result" in t ? t.result : void 0
1175
+ })) ?? []
1176
+ ],
1177
+ // ignore type mismatch for now
1178
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1179
+ innerMessage: message
1180
+ };
1181
+ };
1182
+ var converter = new ThreadMessageConverter(vercelToThreadMessage);
1145
1183
  var sliceMessagesUntil = (messages, messageId) => {
1146
- if (messageId == null)
1147
- return [];
1148
- if (isOptimisticId(messageId))
1149
- return messages;
1184
+ if (messageId == null) return [];
1185
+ if (isOptimisticId(messageId)) return messages;
1150
1186
  const messageIdx = messages.findIndex((m) => m.id === messageId);
1151
1187
  if (messageIdx === -1)
1152
- throw new Error("Unexpected: Message not found");
1188
+ throw new Error(
1189
+ "useVercelAIThreadState: Message not found. This is liekly an internal bug in assistant-ui."
1190
+ );
1153
1191
  return messages.slice(0, messageIdx + 1);
1154
1192
  };
1155
1193
  var hasUpcomingMessage = (isRunning, messages) => {
1156
1194
  return isRunning && messages[messages.length - 1]?.role !== "assistant";
1157
1195
  };
1158
- var useVercelAIBranches = (chat, messages) => {
1196
+ var getIsRunning = (vercel) => {
1197
+ if ("isLoading" in vercel) return vercel.isLoading;
1198
+ return vercel.status === "in_progress";
1199
+ };
1200
+ var useVercelAIThreadState = (vercel) => {
1159
1201
  const [data] = (0, import_react22.useState)(() => new MessageRepository());
1160
- const isRunning = "isLoading" in chat ? chat.isLoading : chat.status === "in_progress";
1202
+ const vercelRef = (0, import_react22.useRef)(vercel);
1203
+ vercelRef.current = vercel;
1204
+ const isRunning = getIsRunning(vercelRef.current);
1161
1205
  const assistantOptimisticIdRef = (0, import_react22.useRef)(null);
1162
- const messagesEx = (0, import_react22.useMemo)(() => {
1163
- for (const message of messages) {
1164
- data.addOrUpdateMessage(message);
1206
+ const messages = (0, import_react22.useMemo)(() => {
1207
+ const vm = converter.convertMessages(vercel.messages);
1208
+ for (let i = 0; i < vm.length; i++) {
1209
+ const message = vm[i];
1210
+ const parent = vm[i - 1];
1211
+ data.addOrUpdateMessage(parent?.id ?? null, message);
1165
1212
  }
1166
1213
  if (assistantOptimisticIdRef.current) {
1167
1214
  data.deleteMessage(assistantOptimisticIdRef.current);
1168
1215
  assistantOptimisticIdRef.current = null;
1169
1216
  }
1170
- if (hasUpcomingMessage(isRunning, messages)) {
1217
+ if (hasUpcomingMessage(isRunning, vm)) {
1171
1218
  assistantOptimisticIdRef.current = data.commitOptimisticRun(
1172
- messages.at(-1)?.id ?? null
1219
+ vm.at(-1)?.id ?? null
1173
1220
  );
1174
1221
  }
1175
- data.resetHead(
1176
- assistantOptimisticIdRef.current ?? messages.at(-1)?.id ?? null
1177
- );
1222
+ data.resetHead(assistantOptimisticIdRef.current ?? vm.at(-1)?.id ?? null);
1178
1223
  return data.getMessages();
1179
- }, [data, isRunning, messages]);
1180
- const getBranches = (0, import_react22.useCallback)(
1224
+ }, [data, isRunning, vercel.messages]);
1225
+ const getBranches2 = (0, import_react22.useCallback)(
1181
1226
  (messageId) => {
1182
1227
  return data.getBranches(messageId);
1183
1228
  },
1184
1229
  [data]
1185
1230
  );
1186
- const switchToBranch = (0, import_react22.useCallback)(
1231
+ const switchToBranch2 = (0, import_react22.useCallback)(
1187
1232
  (messageId) => {
1188
1233
  data.switchToBranch(messageId);
1189
- chat.setMessages(
1234
+ vercelRef.current.setMessages(
1190
1235
  data.getMessages().filter((m) => !isOptimisticId(m.id)).map((m) => m.innerMessage)
1191
1236
  );
1192
1237
  },
1193
- [data, chat.setMessages]
1194
- );
1195
- const reloadMaybe = "reload" in chat ? chat.reload : void 0;
1196
- const startRun = (0, import_react22.useCallback)(
1197
- async (parentId) => {
1198
- if (!reloadMaybe)
1199
- throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
1200
- const newMessages = sliceMessagesUntil(chat.messages, parentId);
1201
- chat.setMessages(newMessages);
1202
- await reloadMaybe();
1203
- },
1204
- [chat.messages, chat.setMessages, reloadMaybe]
1205
- );
1206
- const append = (0, import_react22.useCallback)(
1207
- async (message) => {
1208
- if (message.content.length !== 1 || message.content[0]?.type !== "text")
1209
- throw new Error("Only text content is supported by Vercel AI SDK");
1210
- const newMessages = sliceMessagesUntil(chat.messages, message.parentId);
1211
- chat.setMessages(newMessages);
1212
- await chat.append({
1213
- role: "user",
1214
- content: message.content[0].text
1215
- });
1216
- },
1217
- [chat.messages, chat.setMessages, chat.append]
1238
+ [data]
1218
1239
  );
1240
+ const startRun = (0, import_react22.useCallback)(async (parentId) => {
1241
+ const reloadMaybe = "reload" in vercelRef.current ? vercelRef.current.reload : void 0;
1242
+ if (!reloadMaybe)
1243
+ throw new Error(
1244
+ "Reload is not supported by Vercel AI SDK's useAssistant."
1245
+ );
1246
+ const newMessages = sliceMessagesUntil(
1247
+ vercelRef.current.messages,
1248
+ parentId
1249
+ );
1250
+ vercelRef.current.setMessages(newMessages);
1251
+ await reloadMaybe();
1252
+ }, []);
1253
+ const append = (0, import_react22.useCallback)(async (message) => {
1254
+ if (message.content.length !== 1 || message.content[0]?.type !== "text")
1255
+ throw new Error("Only text content is supported by Vercel AI SDK.");
1256
+ const newMessages = sliceMessagesUntil(
1257
+ vercelRef.current.messages,
1258
+ message.parentId
1259
+ );
1260
+ vercelRef.current.setMessages(newMessages);
1261
+ await vercelRef.current.append({
1262
+ role: "user",
1263
+ content: message.content[0].text
1264
+ });
1265
+ }, []);
1266
+ const cancelRun2 = (0, import_react22.useCallback)(() => {
1267
+ const lastMessage = vercelRef.current.messages.at(-1);
1268
+ vercelRef.current.stop();
1269
+ if (lastMessage?.role === "user") {
1270
+ vercelRef.current.setInput(lastMessage.content);
1271
+ }
1272
+ }, []);
1219
1273
  return (0, import_react22.useMemo)(
1220
1274
  () => ({
1221
- messages: messagesEx,
1222
- getBranches,
1223
- switchToBranch,
1275
+ isRunning,
1276
+ messages,
1277
+ getBranches: getBranches2,
1278
+ switchToBranch: switchToBranch2,
1224
1279
  append,
1225
- startRun
1280
+ startRun,
1281
+ cancelRun: cancelRun2
1226
1282
  }),
1227
- [messagesEx, getBranches, switchToBranch, append, startRun]
1283
+ [
1284
+ isRunning,
1285
+ messages,
1286
+ getBranches2,
1287
+ switchToBranch2,
1288
+ append,
1289
+ startRun,
1290
+ cancelRun2
1291
+ ]
1228
1292
  );
1229
1293
  };
1230
1294
 
1231
1295
  // src/adapters/vercel/VercelAIAssistantProvider.tsx
1232
1296
  var import_jsx_runtime19 = require("react/jsx-runtime");
1233
- var ThreadMessageCache = /* @__PURE__ */ new WeakMap();
1234
- var vercelToThreadMessage = (message, parentId) => {
1235
- if (message.role !== "user" && message.role !== "assistant")
1236
- throw new Error("Unsupported role");
1237
- return {
1238
- parentId,
1239
- id: message.id,
1240
- role: message.role,
1241
- content: [{ type: "text", text: message.content }],
1242
- createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1243
- innerMessage: message
1244
- };
1245
- };
1246
- var vercelToCachedThreadMessages = (messages) => {
1247
- return messages.map((m, idx) => {
1248
- const cached = ThreadMessageCache.get(m);
1249
- const parentId = messages[idx - 1]?.id ?? null;
1250
- if (cached && cached.parentId === parentId)
1251
- return cached;
1252
- const newMessage = vercelToThreadMessage(m, parentId);
1253
- ThreadMessageCache.set(m, newMessage);
1254
- return newMessage;
1255
- });
1256
- };
1257
1297
  var VercelAIAssistantProvider = ({
1258
1298
  children,
1259
1299
  ...rest
1260
1300
  }) => {
1261
1301
  const context = useDummyAIAssistantContext();
1262
1302
  const vercel = "chat" in rest ? rest.chat : rest.assistant;
1263
- const messages = (0, import_react23.useMemo)(() => {
1264
- return vercelToCachedThreadMessages(vercel.messages);
1265
- }, [vercel.messages]);
1266
- const branches = useVercelAIBranches(vercel, messages);
1267
- const cancelRun = (0, import_react23.useCallback)(() => {
1268
- const lastMessage = vercel.messages.at(-1);
1269
- vercel.stop();
1270
- if (lastMessage?.role === "user") {
1271
- vercel.setInput(lastMessage.content);
1272
- }
1273
- }, [vercel.messages, vercel.stop, vercel.setInput]);
1274
- const isRunning = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1303
+ const threadState = useVercelAIThreadState(vercel);
1275
1304
  (0, import_react23.useMemo)(() => {
1276
- context.useThread.setState(
1277
- {
1278
- messages: branches.messages,
1279
- isRunning,
1280
- getBranches: branches.getBranches,
1281
- switchToBranch: branches.switchToBranch,
1282
- append: branches.append,
1283
- startRun: branches.startRun,
1284
- cancelRun
1285
- },
1286
- true
1287
- );
1288
- }, [context, isRunning, cancelRun, branches]);
1305
+ context.useThread.setState(threadState, true);
1306
+ }, [context, threadState]);
1289
1307
  (0, import_react23.useMemo)(() => {
1290
1308
  context.useComposer.setState({
1291
1309
  value: vercel.input,
@@ -1298,51 +1316,91 @@ var VercelAIAssistantProvider = ({
1298
1316
  // src/adapters/vercel/VercelRSCAssistantProvider.tsx
1299
1317
  var import_react24 = require("react");
1300
1318
  var import_jsx_runtime20 = require("react/jsx-runtime");
1301
- var ThreadMessageCache2 = /* @__PURE__ */ new WeakMap();
1302
- var vercelToThreadMessage2 = (parentId, message) => {
1303
- if (message.role !== "user" && message.role !== "assistant")
1304
- throw new Error("Unsupported role");
1319
+ var vercelToThreadMessage2 = (message) => {
1305
1320
  return {
1306
- parentId,
1307
1321
  id: message.id,
1308
1322
  role: message.role,
1309
1323
  content: [{ type: "ui", display: message.display }],
1310
1324
  createdAt: message.createdAt ?? /* @__PURE__ */ new Date()
1311
1325
  };
1312
1326
  };
1313
- var vercelToCachedThreadMessages2 = (messages) => {
1314
- return messages.map((m, idx) => {
1315
- const cached = ThreadMessageCache2.get(m);
1316
- const parentId = messages[idx - 1]?.id ?? null;
1317
- if (cached && cached.parentId === parentId)
1318
- return cached;
1319
- const newMessage = vercelToThreadMessage2(parentId, m);
1320
- ThreadMessageCache2.set(m, newMessage);
1321
- return newMessage;
1322
- });
1327
+ var EMPTY_BRANCHES = [];
1328
+ var getBranches = () => {
1329
+ return EMPTY_BRANCHES;
1323
1330
  };
1324
- var VercelRSCAssistantProvider = ({ children, messages: vercelMessages, append: vercelAppend }) => {
1331
+ var switchToBranch = () => {
1332
+ throw new Error(
1333
+ "Branch switching is not supported by VercelRSCAssistantProvider."
1334
+ );
1335
+ };
1336
+ var cancelRun = () => {
1337
+ if (process.env["NODE_ENV"] === "development") {
1338
+ console.warn(
1339
+ "Run cancellation is not supported by VercelRSCAssistantProvider."
1340
+ );
1341
+ }
1342
+ };
1343
+ var VercelRSCAssistantProvider = ({
1344
+ children,
1345
+ convertMessage,
1346
+ messages: vercelMessages,
1347
+ append: appendCallback,
1348
+ edit,
1349
+ reload
1350
+ }) => {
1325
1351
  const context = useDummyAIAssistantContext();
1352
+ const [isRunning, setIsRunning] = (0, import_react24.useState)(false);
1353
+ const withRunning = (0, import_react24.useCallback)((callback) => {
1354
+ setIsRunning(true);
1355
+ return callback.finally(() => setIsRunning(false));
1356
+ }, []);
1357
+ const converter2 = (0, import_react24.useMemo)(() => {
1358
+ const rscConverter = convertMessage ?? ((m) => m);
1359
+ return new ThreadMessageConverter((m) => {
1360
+ return vercelToThreadMessage2(rscConverter(m));
1361
+ });
1362
+ }, [convertMessage]);
1326
1363
  const messages = (0, import_react24.useMemo)(() => {
1327
- return vercelToCachedThreadMessages2(vercelMessages);
1328
- }, [vercelMessages]);
1364
+ return converter2.convertMessages(vercelMessages);
1365
+ }, [converter2, vercelMessages]);
1329
1366
  const append = (0, import_react24.useCallback)(
1330
1367
  async (message) => {
1331
- if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? null))
1332
- throw new Error("Unexpected: Message editing is not supported");
1333
- if (message.content[0]?.type !== "text") {
1334
- throw new Error("Only text content is currently supported");
1368
+ if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? null)) {
1369
+ if (!edit)
1370
+ throw new Error(
1371
+ "Message editing is not enabled, please provide an edit callback to VercelRSCAssistantProvider."
1372
+ );
1373
+ await withRunning(edit(message));
1374
+ } else {
1375
+ await withRunning(appendCallback(message));
1335
1376
  }
1336
- await vercelAppend(message);
1337
1377
  },
1338
- [context, vercelAppend]
1378
+ [context, withRunning, appendCallback, edit]
1379
+ );
1380
+ const startRun = (0, import_react24.useCallback)(
1381
+ async (parentId) => {
1382
+ if (!reload)
1383
+ throw new Error(
1384
+ "Message reloading is not enabled, please provide a reload callback to VercelRSCAssistantProvider."
1385
+ );
1386
+ await withRunning(reload(parentId));
1387
+ },
1388
+ [withRunning, reload]
1339
1389
  );
1340
1390
  (0, import_react24.useMemo)(() => {
1341
- context.useThread.setState({
1342
- messages,
1343
- append
1344
- });
1345
- }, [context, messages, append]);
1391
+ context.useThread.setState(
1392
+ {
1393
+ messages,
1394
+ isRunning,
1395
+ getBranches,
1396
+ switchToBranch,
1397
+ append,
1398
+ startRun,
1399
+ cancelRun
1400
+ },
1401
+ true
1402
+ );
1403
+ }, [context, messages, isRunning, append, startRun]);
1346
1404
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(AssistantContext.Provider, { value: context, children });
1347
1405
  };
1348
1406
  // Annotate the CommonJS export names for ESM import in node: