@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.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 {