@assistant-ui/react 0.0.13 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
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: