@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.mjs CHANGED
@@ -35,7 +35,7 @@ var useAssistantContext = () => {
35
35
  const context = useContext(AssistantContext);
36
36
  if (!context)
37
37
  throw new Error(
38
- "useAssistantContext must be used within a AssistantProvider"
38
+ "This component must be used within a AssistantProvider."
39
39
  );
40
40
  return context;
41
41
  };
@@ -44,14 +44,10 @@ var useAssistantContext = () => {
44
44
  var useThreadIf = (props) => {
45
45
  const { useThread } = useAssistantContext();
46
46
  return useThread((thread) => {
47
- if (props.empty === true && thread.messages.length !== 0)
48
- return false;
49
- if (props.empty === false && thread.messages.length === 0)
50
- return false;
51
- if (props.running === true && !thread.isRunning)
52
- return false;
53
- if (props.running === false && thread.isRunning)
54
- return false;
47
+ if (props.empty === true && thread.messages.length !== 0) return false;
48
+ if (props.empty === false && thread.messages.length === 0) return false;
49
+ if (props.running === true && !thread.isRunning) return false;
50
+ if (props.running === false && thread.isRunning) return false;
55
51
  return true;
56
52
  });
57
53
  };
@@ -81,8 +77,7 @@ var useOnResizeContent = (ref, callback) => {
81
77
  callbackRef.current = callback;
82
78
  useLayoutEffect(() => {
83
79
  const el = ref.current;
84
- if (!el)
85
- return;
80
+ if (!el) return;
86
81
  const resizeObserver = new ResizeObserver(() => {
87
82
  callbackRef.current();
88
83
  });
@@ -137,16 +132,14 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
137
132
  const lastScrollTop = useRef3(0);
138
133
  const scrollToBottom = () => {
139
134
  const div = messagesEndRef.current;
140
- if (!div || !autoScroll)
141
- return;
135
+ if (!div || !autoScroll) return;
142
136
  const behavior = firstRenderRef.current ? "instant" : "auto";
143
137
  firstRenderRef.current = false;
144
138
  useViewport.setState({ isAtBottom: true });
145
139
  div.scrollIntoView({ behavior });
146
140
  };
147
141
  useOnResizeContent(divRef, () => {
148
- if (!useViewport.getState().isAtBottom)
149
- return;
142
+ if (!useViewport.getState().isAtBottom) return;
150
143
  scrollToBottom();
151
144
  });
152
145
  useOnScrollToBottom(() => {
@@ -154,8 +147,7 @@ var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...re
154
147
  });
155
148
  const handleScroll = () => {
156
149
  const div = divRef.current;
157
- if (!div)
158
- return;
150
+ if (!div) return;
159
151
  const isAtBottom = useViewport.getState().isAtBottom;
160
152
  const newIsAtBottom = div.scrollHeight - div.scrollTop <= div.clientHeight;
161
153
  if (!newIsAtBottom && lastScrollTop.current < div.scrollTop) {
@@ -187,7 +179,7 @@ var MessageContext = createContext2(null);
187
179
  var useMessageContext = () => {
188
180
  const context = useContext2(MessageContext);
189
181
  if (!context)
190
- throw new Error("useMessageContext must be used within a MessageProvider");
182
+ throw new Error("This component must be used within a MessageProvider.");
191
183
  return context;
192
184
  };
193
185
 
@@ -205,10 +197,8 @@ var useComposerContext = () => {
205
197
  var useComposerIf = (props) => {
206
198
  const { useComposer } = useComposerContext();
207
199
  return useComposer((composer) => {
208
- if (props.editing === true && !composer.isEditing)
209
- return false;
210
- if (props.editing === false && composer.isEditing)
211
- return false;
200
+ if (props.editing === true && !composer.isEditing) return false;
201
+ if (props.editing === false && composer.isEditing) return false;
212
202
  return true;
213
203
  });
214
204
  };
@@ -230,6 +220,14 @@ __export(message_exports, {
230
220
  import { useMemo, useState } from "react";
231
221
  import { create as create2 } from "zustand";
232
222
 
223
+ // src/utils/context/getMessageText.tsx
224
+ var getMessageText = (message) => {
225
+ const textParts = message.content.filter(
226
+ (part) => part.type === "text"
227
+ );
228
+ return textParts.map((part) => part.text).join("\n\n");
229
+ };
230
+
233
231
  // src/utils/context/stores/ComposerStore.ts
234
232
  import {
235
233
  create
@@ -256,8 +254,7 @@ var makeMessageComposerStore = ({
256
254
  onSend(value);
257
255
  },
258
256
  cancel: () => {
259
- if (!get().isEditing)
260
- return false;
257
+ if (!get().isEditing) return false;
261
258
  set({ isEditing: false });
262
259
  return true;
263
260
  }
@@ -276,8 +273,7 @@ var makeThreadComposerStore = (useThread) => create()((set, get, store) => {
276
273
  },
277
274
  cancel: () => {
278
275
  const thread = useThread.getState();
279
- if (!thread.isRunning)
280
- return false;
276
+ if (!thread.isRunning) return false;
281
277
  useThread.getState().cancelRun();
282
278
  return true;
283
279
  }
@@ -292,31 +288,42 @@ var getIsLast = (thread, message) => {
292
288
  var useMessageContext2 = () => {
293
289
  const { useThread } = useAssistantContext();
294
290
  const [context] = useState(() => {
295
- const useMessage = create2(() => ({
291
+ const useMessage = create2((set) => ({
296
292
  message: null,
293
+ parentId: null,
297
294
  branches: [],
298
295
  isLast: false,
299
296
  isCopied: false,
300
297
  isHovering: false,
301
- setIsCopied: () => {
298
+ setIsCopied: (value) => {
299
+ set({ isCopied: value });
302
300
  },
303
- setIsHovering: () => {
301
+ setIsHovering: (value) => {
302
+ set({ isHovering: value });
304
303
  }
305
304
  }));
306
305
  const useComposer = makeMessageComposerStore({
307
306
  onEdit: () => {
308
307
  const message = useMessage.getState().message;
309
308
  if (message.role !== "user")
310
- throw new Error("Editing is only supported for user messages");
311
- if (message.content[0]?.type !== "text")
312
- throw new Error("Editing is only supported for text-only messages");
313
- return message.content[0].text;
309
+ throw new Error(
310
+ "Tried to edit a non-user message. Editing is only supported for user messages. This is likely an internal bug in assistant-ui."
311
+ );
312
+ const text = getMessageText(message);
313
+ return text;
314
314
  },
315
315
  onSend: (text) => {
316
- const message = useMessage.getState().message;
316
+ const { message, parentId } = useMessage.getState();
317
+ if (message.role !== "user")
318
+ throw new Error(
319
+ "Tried to edit a non-user message. Editing is only supported for user messages. This is likely an internal bug in assistant-ui."
320
+ );
321
+ const nonTextParts = message.content.filter(
322
+ (part) => part.type !== "text" && part.type !== "ui"
323
+ );
317
324
  useThread.getState().append({
318
- parentId: message.parentId,
319
- content: [{ type: "text", text }]
325
+ parentId,
326
+ content: [{ type: "text", text }, ...nonTextParts]
320
327
  });
321
328
  }
322
329
  });
@@ -326,28 +333,21 @@ var useMessageContext2 = () => {
326
333
  };
327
334
  var MessageProvider = ({
328
335
  message,
336
+ parentId,
329
337
  children
330
338
  }) => {
331
339
  const { useThread } = useAssistantContext();
332
340
  const context = useMessageContext2();
333
341
  const isLast = useThread((thread) => getIsLast(thread, message));
334
342
  const branches = useThread((thread) => thread.getBranches(message.id));
335
- const [isCopied, setIsCopied] = useState(false);
336
- const [isHovering, setIsHovering] = useState(false);
337
343
  useMemo(() => {
338
- context.useMessage.setState(
339
- {
340
- message,
341
- branches,
342
- isLast,
343
- isCopied,
344
- isHovering,
345
- setIsCopied,
346
- setIsHovering
347
- },
348
- true
349
- );
350
- }, [context, message, branches, isLast, isCopied, isHovering]);
344
+ context.useMessage.setState({
345
+ message,
346
+ parentId,
347
+ branches,
348
+ isLast
349
+ });
350
+ }, [context, message, parentId, branches, isLast]);
351
351
  return /* @__PURE__ */ jsx4(MessageContext.Provider, { value: context, children });
352
352
  };
353
353
 
@@ -384,18 +384,12 @@ var MessageRoot = forwardRef3(
384
384
  var useMessageIf = (props) => {
385
385
  const { useMessage } = useMessageContext();
386
386
  return useMessage(({ message, branches, isLast, isCopied, isHovering }) => {
387
- if (props.hasBranches === true && branches.length < 2)
388
- return false;
389
- if (props.user && message.role !== "user")
390
- return false;
391
- if (props.assistant && message.role !== "assistant")
392
- return false;
393
- if (props.lastOrHover === true && !isHovering && !isLast)
394
- return false;
395
- if (props.copied === true && !isCopied)
396
- return false;
397
- if (props.copied === false && isCopied)
398
- return false;
387
+ if (props.hasBranches === true && branches.length < 2) return false;
388
+ if (props.user && message.role !== "user") return false;
389
+ if (props.assistant && message.role !== "assistant") return false;
390
+ if (props.lastOrHover === true && !isHovering && !isLast) return false;
391
+ if (props.copied === true && !isCopied) return false;
392
+ if (props.copied === false && isCopied) return false;
399
393
  return true;
400
394
  });
401
395
  };
@@ -457,18 +451,23 @@ var ThreadMessages = ({ components }) => {
457
451
  const thread = useThread();
458
452
  const messages = thread.messages;
459
453
  const { UserMessage, EditComposer, AssistantMessage } = getComponents(components);
460
- if (messages.length === 0)
461
- return null;
454
+ if (messages.length === 0) return null;
462
455
  return /* @__PURE__ */ jsx7(Fragment2, { children: messages.map((message, idx) => {
463
- return (
464
- // biome-ignore lint/suspicious/noArrayIndexKey: fixes a11y issues with branch navigation
465
- /* @__PURE__ */ jsxs2(MessageProvider, { message, children: [
466
- /* @__PURE__ */ jsxs2(MessageIf, { user: true, children: [
467
- /* @__PURE__ */ jsx7(ComposerIf, { editing: false, children: /* @__PURE__ */ jsx7(UserMessage, {}) }),
468
- /* @__PURE__ */ jsx7(ComposerIf, { editing: true, children: /* @__PURE__ */ jsx7(EditComposer, {}) })
469
- ] }),
470
- /* @__PURE__ */ jsx7(MessageIf, { assistant: true, children: /* @__PURE__ */ jsx7(AssistantMessage, {}) })
471
- ] }, idx)
456
+ const parentId = messages[idx - 1]?.id ?? null;
457
+ return /* @__PURE__ */ jsxs2(
458
+ MessageProvider,
459
+ {
460
+ message,
461
+ parentId,
462
+ children: [
463
+ /* @__PURE__ */ jsxs2(MessageIf, { user: true, children: [
464
+ /* @__PURE__ */ jsx7(ComposerIf, { editing: false, children: /* @__PURE__ */ jsx7(UserMessage, {}) }),
465
+ /* @__PURE__ */ jsx7(ComposerIf, { editing: true, children: /* @__PURE__ */ jsx7(EditComposer, {}) })
466
+ ] }),
467
+ /* @__PURE__ */ jsx7(MessageIf, { assistant: true, children: /* @__PURE__ */ jsx7(AssistantMessage, {}) })
468
+ ]
469
+ },
470
+ parentId ?? "__ROOT__"
472
471
  );
473
472
  }) });
474
473
  };
@@ -552,8 +551,7 @@ var ComposerRoot = forwardRef6(
552
551
  const ref = useComposedRefs2(forwardedRef, formRef);
553
552
  const handleSubmit = (e) => {
554
553
  const composerState = useComposer.getState();
555
- if (!composerState.isEditing)
556
- return;
554
+ if (!composerState.isEditing) return;
557
555
  e.preventDefault();
558
556
  composerState.send();
559
557
  useViewport.getState().scrollToBottom();
@@ -582,18 +580,16 @@ import {
582
580
  import TextareaAutosize from "react-textarea-autosize";
583
581
  import { jsx as jsx11 } from "react/jsx-runtime";
584
582
  var ComposerInput = forwardRef7(
585
- ({ autoFocus, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
583
+ ({ autoFocus = false, asChild, disabled, onChange, onKeyDown, ...rest }, forwardedRef) => {
586
584
  const { useThread, useViewport } = useAssistantContext();
587
585
  const { useComposer, type } = useComposerContext();
588
586
  const value = useComposer((c) => {
589
- if (!c.isEditing)
590
- return "";
587
+ if (!c.isEditing) return "";
591
588
  return c.value;
592
589
  });
593
590
  const Component = asChild ? Slot : TextareaAutosize;
594
591
  const handleKeyPress = (e) => {
595
- if (disabled)
596
- return;
592
+ if (disabled) return;
597
593
  const composer = useComposer.getState();
598
594
  if (e.key === "Escape") {
599
595
  if (useComposer.getState().cancel()) {
@@ -610,11 +606,10 @@ var ComposerInput = forwardRef7(
610
606
  };
611
607
  const textareaRef = useRef5(null);
612
608
  const ref = useComposedRefs3(forwardedRef, textareaRef);
613
- const autoFocusEnabled = autoFocus !== false && !disabled;
609
+ const autoFocusEnabled = autoFocus && !disabled;
614
610
  const focus = useCallback(() => {
615
611
  const textarea = textareaRef.current;
616
- if (!textarea || !autoFocusEnabled)
617
- return;
612
+ if (!textarea || !autoFocusEnabled) return;
618
613
  textarea.focus();
619
614
  textarea.setSelectionRange(
620
615
  textareaRef.current.value.length,
@@ -636,8 +631,7 @@ var ComposerInput = forwardRef7(
636
631
  disabled,
637
632
  onChange: composeEventHandlers6(onChange, (e) => {
638
633
  const composerState = useComposer.getState();
639
- if (!composerState.isEditing)
640
- return;
634
+ if (!composerState.isEditing) return;
641
635
  return composerState.setValue(e.target.value);
642
636
  }),
643
637
  onKeyDown: composeEventHandlers6(onKeyDown, handleKeyPress)
@@ -735,8 +729,7 @@ var useGoToNextBranch = () => {
735
729
  [useMessage, useComposer],
736
730
  (m, c) => c.isEditing || m.branches.indexOf(m.message.id) + 1 >= m.branches.length
737
731
  );
738
- if (disabled)
739
- return null;
732
+ if (disabled) return null;
740
733
  return () => {
741
734
  const { message, branches } = useMessage.getState();
742
735
  useThread.getState().switchToBranch(branches[branches.indexOf(message.id) + 1]);
@@ -779,8 +772,7 @@ var useGoToPreviousBranch = () => {
779
772
  [useMessage, useComposer],
780
773
  (m, c) => c.isEditing || m.branches.indexOf(m.message.id) <= 0
781
774
  );
782
- if (disabled)
783
- return null;
775
+ if (disabled) return null;
784
776
  return () => {
785
777
  const { message, branches } = useMessage.getState();
786
778
  useThread.getState().switchToBranch(
@@ -840,20 +832,16 @@ var ActionBarRoot = forwardRef12(({ hideWhenRunning, autohide, autohideFloat, ..
840
832
  const hideAndfloatStatus = useCombinedStore(
841
833
  [useThread, useMessage],
842
834
  (t, m) => {
843
- if (hideWhenRunning && t.isRunning)
844
- return "hidden" /* Hidden */;
835
+ if (hideWhenRunning && t.isRunning) return "hidden" /* Hidden */;
845
836
  const autohideEnabled = autohide === "always" || autohide === "not-last" && !m.isLast;
846
- if (!autohideEnabled)
847
- return "normal" /* Normal */;
848
- if (!m.isHovering)
849
- return "hidden" /* Hidden */;
837
+ if (!autohideEnabled) return "normal" /* Normal */;
838
+ if (!m.isHovering) return "hidden" /* Hidden */;
850
839
  if (autohideFloat === "always" || autohideFloat === "single-branch" && m.branches.length <= 1)
851
840
  return "floating" /* Floating */;
852
841
  return "normal" /* Normal */;
853
842
  }
854
843
  );
855
- if (hideAndfloatStatus === "hidden" /* Hidden */)
856
- return null;
844
+ if (hideAndfloatStatus === "hidden" /* Hidden */) return null;
857
845
  return /* @__PURE__ */ jsx18(
858
846
  Primitive11.div,
859
847
  {
@@ -867,14 +855,18 @@ var ActionBarRoot = forwardRef12(({ hideWhenRunning, autohide, autohideFloat, ..
867
855
  // src/actions/useCopyMessage.tsx
868
856
  var useCopyMessage = ({ copiedDuration = 3e3 }) => {
869
857
  const { useMessage, useComposer } = useMessageContext();
870
- const isEditing = useComposer((s) => s.isEditing);
871
- if (isEditing)
872
- return null;
858
+ const hasCopyableContent = useCombinedStore(
859
+ [useMessage, useComposer],
860
+ (m, c) => {
861
+ return c.isEditing || m.message.content.some((c2) => c2.type === "text");
862
+ }
863
+ );
864
+ if (!hasCopyableContent) return null;
873
865
  return () => {
866
+ const { isEditing, value: composerValue } = useComposer.getState();
874
867
  const { message, setIsCopied } = useMessage.getState();
875
- if (message.content[0]?.type !== "text")
876
- throw new Error("Copying is only supported for text-only messages");
877
- navigator.clipboard.writeText(message.content[0].text);
868
+ const valueToCopy = isEditing ? composerValue : getMessageText(message);
869
+ navigator.clipboard.writeText(valueToCopy);
878
870
  setIsCopied(true);
879
871
  setTimeout(() => setIsCopied(false), copiedDuration);
880
872
  };
@@ -891,13 +883,10 @@ var useReloadMessage = () => {
891
883
  [useThread, useMessage],
892
884
  (t, m) => t.isRunning || m.message.role !== "assistant"
893
885
  );
894
- if (disabled)
895
- return null;
886
+ if (disabled) return null;
896
887
  return () => {
897
- const message = useMessage.getState().message;
898
- if (message.role !== "assistant")
899
- throw new Error("Reloading is only supported on assistant messages");
900
- useThread.getState().startRun(message.parentId);
888
+ const { parentId } = useMessage.getState();
889
+ useThread.getState().startRun(parentId);
901
890
  useViewport.getState().scrollToBottom();
902
891
  };
903
892
  };
@@ -912,8 +901,7 @@ var useBeginMessageEdit = () => {
912
901
  [useMessage, useComposer],
913
902
  (m, c) => m.message.role !== "user" || c.isEditing
914
903
  );
915
- if (disabled)
916
- return null;
904
+ if (disabled) return null;
917
905
  return () => {
918
906
  const { edit } = useComposer.getState();
919
907
  edit();
@@ -924,7 +912,7 @@ var useBeginMessageEdit = () => {
924
912
  var ActionBarEdit = createActionButton(useBeginMessageEdit);
925
913
 
926
914
  // src/adapters/vercel/VercelAIAssistantProvider.tsx
927
- import { useCallback as useCallback3, useMemo as useMemo4 } from "react";
915
+ import { useMemo as useMemo4 } from "react";
928
916
 
929
917
  // src/adapters/vercel/useDummyAIAssistantContext.tsx
930
918
  import { useState as useState2 } from "react";
@@ -961,13 +949,13 @@ var makeDummyThreadStore = () => {
961
949
  switchToBranch: () => {
962
950
  throw new Error("Not implemented");
963
951
  },
964
- append: async () => {
952
+ append: () => {
965
953
  throw new Error("Not implemented");
966
954
  },
967
- cancelRun: () => {
955
+ startRun: () => {
968
956
  throw new Error("Not implemented");
969
957
  },
970
- startRun: async () => {
958
+ cancelRun: () => {
971
959
  throw new Error("Not implemented");
972
960
  }
973
961
  }));
@@ -982,7 +970,7 @@ var useDummyAIAssistantContext = () => {
982
970
  return context;
983
971
  };
984
972
 
985
- // src/adapters/vercel/useVercelAIBranches.tsx
973
+ // src/adapters/vercel/useVercelAIThreadState.tsx
986
974
  import { useCallback as useCallback2, useMemo as useMemo3, useRef as useRef6, useState as useState3 } from "react";
987
975
 
988
976
  // src/adapters/MessageRepository.tsx
@@ -995,8 +983,7 @@ var optimisticPrefix = "__optimistic__";
995
983
  var generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
996
984
  var isOptimisticId = (id) => id.startsWith(optimisticPrefix);
997
985
  var findHead = (message) => {
998
- if (message.next)
999
- return findHead(message.next);
986
+ if (message.next) return findHead(message.next);
1000
987
  return message;
1001
988
  };
1002
989
  var MessageRepository = class {
@@ -1011,19 +998,21 @@ var MessageRepository = class {
1011
998
  }
1012
999
  return messages;
1013
1000
  }
1014
- addOrUpdateMessage(message) {
1001
+ addOrUpdateMessage(parentId, message) {
1015
1002
  const item = this.messages.get(message.id);
1016
1003
  if (item) {
1017
- if (item.current.parentId !== message.parentId) {
1004
+ if ((item.prev?.current.id ?? null) !== parentId) {
1018
1005
  this.deleteMessage(message.id);
1019
1006
  } else {
1020
1007
  item.current = message;
1021
1008
  return;
1022
1009
  }
1023
1010
  }
1024
- const prev = message.parentId ? this.messages.get(message.parentId) : null;
1011
+ const prev = parentId ? this.messages.get(parentId) : null;
1025
1012
  if (prev === void 0)
1026
- throw new Error("Unexpected: Parent message not found");
1013
+ throw new Error(
1014
+ "MessageRepository(addOrUpdateMessage): Parent message not found. This is likely an internal bug in assistant-ui."
1015
+ );
1027
1016
  const newItem = {
1028
1017
  prev,
1029
1018
  current: message,
@@ -1045,7 +1034,9 @@ var MessageRepository = class {
1045
1034
  deleteMessage(messageId) {
1046
1035
  const message = this.messages.get(messageId);
1047
1036
  if (!message)
1048
- throw new Error("Unexpected: Message not found");
1037
+ throw new Error(
1038
+ "MessageRepository(deleteMessage): Message not found. This is likely an internal bug in assistant-ui."
1039
+ );
1049
1040
  if (message.children.length > 0) {
1050
1041
  for (const child of message.children) {
1051
1042
  this.deleteMessage(child);
@@ -1060,7 +1051,9 @@ var MessageRepository = class {
1060
1051
  const childId = message.prev.children.at(-1);
1061
1052
  const child = childId ? this.messages.get(childId) : null;
1062
1053
  if (child === void 0)
1063
- throw new Error("Unexpected: Child message not found");
1054
+ throw new Error(
1055
+ "MessageRepository(deleteMessage): Child message not found. This is likely an internal bug in assistant-ui."
1056
+ );
1064
1057
  message.prev.next = child;
1065
1058
  }
1066
1059
  } else {
@@ -1070,16 +1063,16 @@ var MessageRepository = class {
1070
1063
  this.head = message.prev ? findHead(message.prev) : null;
1071
1064
  }
1072
1065
  }
1073
- getOptimisticId = () => {
1066
+ getOptimisticId() {
1074
1067
  let optimisticId;
1075
1068
  do {
1076
1069
  optimisticId = generateOptimisticId();
1077
1070
  } while (this.messages.has(optimisticId));
1078
1071
  return optimisticId;
1079
- };
1072
+ }
1080
1073
  commitOptimisticRun(parentId) {
1081
1074
  const optimisticId = this.getOptimisticId();
1082
- this.addOrUpdateMessage({
1075
+ this.addOrUpdateMessage(parentId, {
1083
1076
  id: optimisticId,
1084
1077
  role: "assistant",
1085
1078
  content: [
@@ -1088,7 +1081,6 @@ var MessageRepository = class {
1088
1081
  text: ""
1089
1082
  }
1090
1083
  ],
1091
- parentId,
1092
1084
  createdAt: /* @__PURE__ */ new Date()
1093
1085
  });
1094
1086
  return optimisticId;
@@ -1096,7 +1088,9 @@ var MessageRepository = class {
1096
1088
  getBranches(messageId) {
1097
1089
  const message = this.messages.get(messageId);
1098
1090
  if (!message)
1099
- throw new Error("Unexpected: Message not found");
1091
+ throw new Error(
1092
+ "MessageRepository(getBranches): Message not found. This is likely an internal bug in assistant-ui."
1093
+ );
1100
1094
  if (message.prev) {
1101
1095
  return message.prev.children;
1102
1096
  }
@@ -1105,7 +1099,9 @@ var MessageRepository = class {
1105
1099
  switchToBranch(messageId) {
1106
1100
  const message = this.messages.get(messageId);
1107
1101
  if (!message)
1108
- throw new Error("Unexpected: Branch not found");
1102
+ throw new Error(
1103
+ "MessageRepository(switchToBranch): Branch not found. This is likely an internal bug in assistant-ui."
1104
+ );
1109
1105
  if (message.prev) {
1110
1106
  message.prev.next = message;
1111
1107
  }
@@ -1115,7 +1111,9 @@ var MessageRepository = class {
1115
1111
  if (messageId) {
1116
1112
  const message = this.messages.get(messageId);
1117
1113
  if (!message)
1118
- throw new Error("Unexpected: Branch not found");
1114
+ throw new Error(
1115
+ "MessageRepository(resetHead): Branch not found. This is likely an internal bug in assistant-ui."
1116
+ );
1119
1117
  this.head = message;
1120
1118
  for (let current = message; current; current = current.prev) {
1121
1119
  if (current.prev) {
@@ -1128,151 +1126,171 @@ var MessageRepository = class {
1128
1126
  }
1129
1127
  };
1130
1128
 
1131
- // src/adapters/vercel/useVercelAIBranches.tsx
1129
+ // src/adapters/ThreadMessageConverter.tsx
1130
+ var ThreadMessageConverter = class {
1131
+ constructor(converter2) {
1132
+ this.converter = converter2;
1133
+ }
1134
+ cache = /* @__PURE__ */ new WeakMap();
1135
+ convertMessages(messages) {
1136
+ return messages.map((m) => {
1137
+ const cached = this.cache.get(m);
1138
+ if (cached) return cached;
1139
+ const newMessage = this.converter(m);
1140
+ this.cache.set(m, newMessage);
1141
+ return newMessage;
1142
+ });
1143
+ }
1144
+ };
1145
+
1146
+ // src/adapters/vercel/useVercelAIThreadState.tsx
1147
+ var vercelToThreadMessage = (message) => {
1148
+ if (message.role !== "user" && message.role !== "assistant")
1149
+ throw new Error(
1150
+ `You have a message with an unsupported role. The role ${message.role} is not supported.`
1151
+ );
1152
+ return {
1153
+ id: message.id,
1154
+ role: message.role,
1155
+ content: [
1156
+ ...message.content ? [{ type: "text", text: message.content }] : [],
1157
+ ...message.toolInvocations?.map((t) => ({
1158
+ type: "tool-call",
1159
+ name: t.toolName,
1160
+ args: t.args,
1161
+ result: "result" in t ? t.result : void 0
1162
+ })) ?? []
1163
+ ],
1164
+ // ignore type mismatch for now
1165
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1166
+ innerMessage: message
1167
+ };
1168
+ };
1169
+ var converter = new ThreadMessageConverter(vercelToThreadMessage);
1132
1170
  var sliceMessagesUntil = (messages, messageId) => {
1133
- if (messageId == null)
1134
- return [];
1135
- if (isOptimisticId(messageId))
1136
- return messages;
1171
+ if (messageId == null) return [];
1172
+ if (isOptimisticId(messageId)) return messages;
1137
1173
  const messageIdx = messages.findIndex((m) => m.id === messageId);
1138
1174
  if (messageIdx === -1)
1139
- throw new Error("Unexpected: Message not found");
1175
+ throw new Error(
1176
+ "useVercelAIThreadState: Message not found. This is liekly an internal bug in assistant-ui."
1177
+ );
1140
1178
  return messages.slice(0, messageIdx + 1);
1141
1179
  };
1142
1180
  var hasUpcomingMessage = (isRunning, messages) => {
1143
1181
  return isRunning && messages[messages.length - 1]?.role !== "assistant";
1144
1182
  };
1145
- var useVercelAIBranches = (chat, messages) => {
1183
+ var getIsRunning = (vercel) => {
1184
+ if ("isLoading" in vercel) return vercel.isLoading;
1185
+ return vercel.status === "in_progress";
1186
+ };
1187
+ var useVercelAIThreadState = (vercel) => {
1146
1188
  const [data] = useState3(() => new MessageRepository());
1147
- const isRunning = "isLoading" in chat ? chat.isLoading : chat.status === "in_progress";
1189
+ const vercelRef = useRef6(vercel);
1190
+ vercelRef.current = vercel;
1191
+ const isRunning = getIsRunning(vercelRef.current);
1148
1192
  const assistantOptimisticIdRef = useRef6(null);
1149
- const messagesEx = useMemo3(() => {
1150
- for (const message of messages) {
1151
- data.addOrUpdateMessage(message);
1193
+ const messages = useMemo3(() => {
1194
+ const vm = converter.convertMessages(vercel.messages);
1195
+ for (let i = 0; i < vm.length; i++) {
1196
+ const message = vm[i];
1197
+ const parent = vm[i - 1];
1198
+ data.addOrUpdateMessage(parent?.id ?? null, message);
1152
1199
  }
1153
1200
  if (assistantOptimisticIdRef.current) {
1154
1201
  data.deleteMessage(assistantOptimisticIdRef.current);
1155
1202
  assistantOptimisticIdRef.current = null;
1156
1203
  }
1157
- if (hasUpcomingMessage(isRunning, messages)) {
1204
+ if (hasUpcomingMessage(isRunning, vm)) {
1158
1205
  assistantOptimisticIdRef.current = data.commitOptimisticRun(
1159
- messages.at(-1)?.id ?? null
1206
+ vm.at(-1)?.id ?? null
1160
1207
  );
1161
1208
  }
1162
- data.resetHead(
1163
- assistantOptimisticIdRef.current ?? messages.at(-1)?.id ?? null
1164
- );
1209
+ data.resetHead(assistantOptimisticIdRef.current ?? vm.at(-1)?.id ?? null);
1165
1210
  return data.getMessages();
1166
- }, [data, isRunning, messages]);
1167
- const getBranches = useCallback2(
1211
+ }, [data, isRunning, vercel.messages]);
1212
+ const getBranches2 = useCallback2(
1168
1213
  (messageId) => {
1169
1214
  return data.getBranches(messageId);
1170
1215
  },
1171
1216
  [data]
1172
1217
  );
1173
- const switchToBranch = useCallback2(
1218
+ const switchToBranch2 = useCallback2(
1174
1219
  (messageId) => {
1175
1220
  data.switchToBranch(messageId);
1176
- chat.setMessages(
1221
+ vercelRef.current.setMessages(
1177
1222
  data.getMessages().filter((m) => !isOptimisticId(m.id)).map((m) => m.innerMessage)
1178
1223
  );
1179
1224
  },
1180
- [data, chat.setMessages]
1181
- );
1182
- const reloadMaybe = "reload" in chat ? chat.reload : void 0;
1183
- const startRun = useCallback2(
1184
- async (parentId) => {
1185
- if (!reloadMaybe)
1186
- throw new Error("Reload not supported by Vercel AI SDK's useAssistant");
1187
- const newMessages = sliceMessagesUntil(chat.messages, parentId);
1188
- chat.setMessages(newMessages);
1189
- await reloadMaybe();
1190
- },
1191
- [chat.messages, chat.setMessages, reloadMaybe]
1192
- );
1193
- const append = useCallback2(
1194
- async (message) => {
1195
- if (message.content.length !== 1 || message.content[0]?.type !== "text")
1196
- throw new Error("Only text content is supported by Vercel AI SDK");
1197
- const newMessages = sliceMessagesUntil(chat.messages, message.parentId);
1198
- chat.setMessages(newMessages);
1199
- await chat.append({
1200
- role: "user",
1201
- content: message.content[0].text
1202
- });
1203
- },
1204
- [chat.messages, chat.setMessages, chat.append]
1225
+ [data]
1205
1226
  );
1227
+ const startRun = useCallback2(async (parentId) => {
1228
+ const reloadMaybe = "reload" in vercelRef.current ? vercelRef.current.reload : void 0;
1229
+ if (!reloadMaybe)
1230
+ throw new Error(
1231
+ "Reload is not supported by Vercel AI SDK's useAssistant."
1232
+ );
1233
+ const newMessages = sliceMessagesUntil(
1234
+ vercelRef.current.messages,
1235
+ parentId
1236
+ );
1237
+ vercelRef.current.setMessages(newMessages);
1238
+ await reloadMaybe();
1239
+ }, []);
1240
+ const append = useCallback2(async (message) => {
1241
+ if (message.content.length !== 1 || message.content[0]?.type !== "text")
1242
+ throw new Error("Only text content is supported by Vercel AI SDK.");
1243
+ const newMessages = sliceMessagesUntil(
1244
+ vercelRef.current.messages,
1245
+ message.parentId
1246
+ );
1247
+ vercelRef.current.setMessages(newMessages);
1248
+ await vercelRef.current.append({
1249
+ role: "user",
1250
+ content: message.content[0].text
1251
+ });
1252
+ }, []);
1253
+ const cancelRun2 = useCallback2(() => {
1254
+ const lastMessage = vercelRef.current.messages.at(-1);
1255
+ vercelRef.current.stop();
1256
+ if (lastMessage?.role === "user") {
1257
+ vercelRef.current.setInput(lastMessage.content);
1258
+ }
1259
+ }, []);
1206
1260
  return useMemo3(
1207
1261
  () => ({
1208
- messages: messagesEx,
1209
- getBranches,
1210
- switchToBranch,
1262
+ isRunning,
1263
+ messages,
1264
+ getBranches: getBranches2,
1265
+ switchToBranch: switchToBranch2,
1211
1266
  append,
1212
- startRun
1267
+ startRun,
1268
+ cancelRun: cancelRun2
1213
1269
  }),
1214
- [messagesEx, getBranches, switchToBranch, append, startRun]
1270
+ [
1271
+ isRunning,
1272
+ messages,
1273
+ getBranches2,
1274
+ switchToBranch2,
1275
+ append,
1276
+ startRun,
1277
+ cancelRun2
1278
+ ]
1215
1279
  );
1216
1280
  };
1217
1281
 
1218
1282
  // src/adapters/vercel/VercelAIAssistantProvider.tsx
1219
1283
  import { jsx as jsx19 } from "react/jsx-runtime";
1220
- var ThreadMessageCache = /* @__PURE__ */ new WeakMap();
1221
- var vercelToThreadMessage = (message, parentId) => {
1222
- if (message.role !== "user" && message.role !== "assistant")
1223
- throw new Error("Unsupported role");
1224
- return {
1225
- parentId,
1226
- id: message.id,
1227
- role: message.role,
1228
- content: [{ type: "text", text: message.content }],
1229
- createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1230
- innerMessage: message
1231
- };
1232
- };
1233
- var vercelToCachedThreadMessages = (messages) => {
1234
- return messages.map((m, idx) => {
1235
- const cached = ThreadMessageCache.get(m);
1236
- const parentId = messages[idx - 1]?.id ?? null;
1237
- if (cached && cached.parentId === parentId)
1238
- return cached;
1239
- const newMessage = vercelToThreadMessage(m, parentId);
1240
- ThreadMessageCache.set(m, newMessage);
1241
- return newMessage;
1242
- });
1243
- };
1244
1284
  var VercelAIAssistantProvider = ({
1245
1285
  children,
1246
1286
  ...rest
1247
1287
  }) => {
1248
1288
  const context = useDummyAIAssistantContext();
1249
1289
  const vercel = "chat" in rest ? rest.chat : rest.assistant;
1250
- const messages = useMemo4(() => {
1251
- return vercelToCachedThreadMessages(vercel.messages);
1252
- }, [vercel.messages]);
1253
- const branches = useVercelAIBranches(vercel, messages);
1254
- const cancelRun = useCallback3(() => {
1255
- const lastMessage = vercel.messages.at(-1);
1256
- vercel.stop();
1257
- if (lastMessage?.role === "user") {
1258
- vercel.setInput(lastMessage.content);
1259
- }
1260
- }, [vercel.messages, vercel.stop, vercel.setInput]);
1261
- const isRunning = "isLoading" in vercel ? vercel.isLoading : vercel.status === "in_progress";
1290
+ const threadState = useVercelAIThreadState(vercel);
1262
1291
  useMemo4(() => {
1263
- context.useThread.setState(
1264
- {
1265
- messages: branches.messages,
1266
- isRunning,
1267
- getBranches: branches.getBranches,
1268
- switchToBranch: branches.switchToBranch,
1269
- append: branches.append,
1270
- startRun: branches.startRun,
1271
- cancelRun
1272
- },
1273
- true
1274
- );
1275
- }, [context, isRunning, cancelRun, branches]);
1292
+ context.useThread.setState(threadState, true);
1293
+ }, [context, threadState]);
1276
1294
  useMemo4(() => {
1277
1295
  context.useComposer.setState({
1278
1296
  value: vercel.input,
@@ -1284,55 +1302,96 @@ var VercelAIAssistantProvider = ({
1284
1302
 
1285
1303
  // src/adapters/vercel/VercelRSCAssistantProvider.tsx
1286
1304
  import {
1287
- useCallback as useCallback4,
1288
- useMemo as useMemo5
1305
+ useCallback as useCallback3,
1306
+ useMemo as useMemo5,
1307
+ useState as useState4
1289
1308
  } from "react";
1290
1309
  import { jsx as jsx20 } from "react/jsx-runtime";
1291
- var ThreadMessageCache2 = /* @__PURE__ */ new WeakMap();
1292
- var vercelToThreadMessage2 = (parentId, message) => {
1293
- if (message.role !== "user" && message.role !== "assistant")
1294
- throw new Error("Unsupported role");
1310
+ var vercelToThreadMessage2 = (message) => {
1295
1311
  return {
1296
- parentId,
1297
1312
  id: message.id,
1298
1313
  role: message.role,
1299
1314
  content: [{ type: "ui", display: message.display }],
1300
1315
  createdAt: message.createdAt ?? /* @__PURE__ */ new Date()
1301
1316
  };
1302
1317
  };
1303
- var vercelToCachedThreadMessages2 = (messages) => {
1304
- return messages.map((m, idx) => {
1305
- const cached = ThreadMessageCache2.get(m);
1306
- const parentId = messages[idx - 1]?.id ?? null;
1307
- if (cached && cached.parentId === parentId)
1308
- return cached;
1309
- const newMessage = vercelToThreadMessage2(parentId, m);
1310
- ThreadMessageCache2.set(m, newMessage);
1311
- return newMessage;
1312
- });
1318
+ var EMPTY_BRANCHES = [];
1319
+ var getBranches = () => {
1320
+ return EMPTY_BRANCHES;
1313
1321
  };
1314
- var VercelRSCAssistantProvider = ({ children, messages: vercelMessages, append: vercelAppend }) => {
1322
+ var switchToBranch = () => {
1323
+ throw new Error(
1324
+ "Branch switching is not supported by VercelRSCAssistantProvider."
1325
+ );
1326
+ };
1327
+ var cancelRun = () => {
1328
+ if (process.env["NODE_ENV"] === "development") {
1329
+ console.warn(
1330
+ "Run cancellation is not supported by VercelRSCAssistantProvider."
1331
+ );
1332
+ }
1333
+ };
1334
+ var VercelRSCAssistantProvider = ({
1335
+ children,
1336
+ convertMessage,
1337
+ messages: vercelMessages,
1338
+ append: appendCallback,
1339
+ edit,
1340
+ reload
1341
+ }) => {
1315
1342
  const context = useDummyAIAssistantContext();
1343
+ const [isRunning, setIsRunning] = useState4(false);
1344
+ const withRunning = useCallback3((callback) => {
1345
+ setIsRunning(true);
1346
+ return callback.finally(() => setIsRunning(false));
1347
+ }, []);
1348
+ const converter2 = useMemo5(() => {
1349
+ const rscConverter = convertMessage ?? ((m) => m);
1350
+ return new ThreadMessageConverter((m) => {
1351
+ return vercelToThreadMessage2(rscConverter(m));
1352
+ });
1353
+ }, [convertMessage]);
1316
1354
  const messages = useMemo5(() => {
1317
- return vercelToCachedThreadMessages2(vercelMessages);
1318
- }, [vercelMessages]);
1319
- const append = useCallback4(
1355
+ return converter2.convertMessages(vercelMessages);
1356
+ }, [converter2, vercelMessages]);
1357
+ const append = useCallback3(
1320
1358
  async (message) => {
1321
- if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? null))
1322
- throw new Error("Unexpected: Message editing is not supported");
1323
- if (message.content[0]?.type !== "text") {
1324
- throw new Error("Only text content is currently supported");
1359
+ if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? null)) {
1360
+ if (!edit)
1361
+ throw new Error(
1362
+ "Message editing is not enabled, please provide an edit callback to VercelRSCAssistantProvider."
1363
+ );
1364
+ await withRunning(edit(message));
1365
+ } else {
1366
+ await withRunning(appendCallback(message));
1325
1367
  }
1326
- await vercelAppend(message);
1327
1368
  },
1328
- [context, vercelAppend]
1369
+ [context, withRunning, appendCallback, edit]
1370
+ );
1371
+ const startRun = useCallback3(
1372
+ async (parentId) => {
1373
+ if (!reload)
1374
+ throw new Error(
1375
+ "Message reloading is not enabled, please provide a reload callback to VercelRSCAssistantProvider."
1376
+ );
1377
+ await withRunning(reload(parentId));
1378
+ },
1379
+ [withRunning, reload]
1329
1380
  );
1330
1381
  useMemo5(() => {
1331
- context.useThread.setState({
1332
- messages,
1333
- append
1334
- });
1335
- }, [context, messages, append]);
1382
+ context.useThread.setState(
1383
+ {
1384
+ messages,
1385
+ isRunning,
1386
+ getBranches,
1387
+ switchToBranch,
1388
+ append,
1389
+ startRun,
1390
+ cancelRun
1391
+ },
1392
+ true
1393
+ );
1394
+ }, [context, messages, isRunning, append, startRun]);
1336
1395
  return /* @__PURE__ */ jsx20(AssistantContext.Provider, { value: context, children });
1337
1396
  };
1338
1397
  export {