@assistant-ui/react 0.0.16 → 0.0.18

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
@@ -68,18 +68,18 @@ import { useComposedRefs } from "@radix-ui/react-compose-refs";
68
68
  import {
69
69
  Primitive as Primitive2
70
70
  } from "@radix-ui/react-primitive";
71
- import { forwardRef as forwardRef2, useRef as useRef3 } from "react";
71
+ import { forwardRef as forwardRef2, useRef } from "react";
72
72
 
73
73
  // src/utils/hooks/useOnResizeContent.tsx
74
- import { useEffect, useRef } from "react";
74
+ import { useCallbackRef } from "@radix-ui/react-use-callback-ref";
75
+ import { useEffect } from "react";
75
76
  var useOnResizeContent = (ref, callback) => {
76
- const callbackRef = useRef(callback);
77
- callbackRef.current = callback;
77
+ const callbackRef = useCallbackRef(callback);
78
78
  useEffect(() => {
79
79
  const el = ref.current;
80
80
  if (!el) return;
81
81
  const resizeObserver = new ResizeObserver(() => {
82
- callbackRef.current();
82
+ callbackRef();
83
83
  });
84
84
  const mutationObserver = new MutationObserver((mutations) => {
85
85
  for (const mutation of mutations) {
@@ -94,7 +94,7 @@ var useOnResizeContent = (ref, callback) => {
94
94
  }
95
95
  }
96
96
  }
97
- callbackRef.current();
97
+ callbackRef();
98
98
  });
99
99
  resizeObserver.observe(el);
100
100
  mutationObserver.observe(el, { childList: true });
@@ -105,31 +105,31 @@ var useOnResizeContent = (ref, callback) => {
105
105
  resizeObserver.disconnect();
106
106
  mutationObserver.disconnect();
107
107
  };
108
- }, [ref.current]);
108
+ }, [ref.current, callbackRef]);
109
109
  };
110
110
 
111
111
  // src/utils/hooks/useOnScrollToBottom.tsx
112
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
112
+ import { useCallbackRef as useCallbackRef2 } from "@radix-ui/react-use-callback-ref";
113
+ import { useEffect as useEffect2 } from "react";
113
114
  var useOnScrollToBottom = (callback) => {
114
- const callbackRef = useRef2(callback);
115
- callbackRef.current = callback;
115
+ const callbackRef = useCallbackRef2(callback);
116
116
  const { useViewport } = useAssistantContext();
117
117
  useEffect2(() => {
118
118
  return useViewport.getState().onScrollToBottom(() => {
119
- callbackRef.current();
119
+ callbackRef();
120
120
  });
121
- }, [useViewport]);
121
+ }, [useViewport, callbackRef]);
122
122
  };
123
123
 
124
124
  // src/primitives/thread/ThreadViewport.tsx
125
125
  import { jsx as jsx3, jsxs } from "react/jsx-runtime";
126
126
  var ThreadViewport = forwardRef2(({ autoScroll = true, onScroll, children, ...rest }, forwardedRef) => {
127
- const messagesEndRef = useRef3(null);
128
- const divRef = useRef3(null);
127
+ const messagesEndRef = useRef(null);
128
+ const divRef = useRef(null);
129
129
  const ref = useComposedRefs(forwardedRef, divRef);
130
130
  const { useViewport } = useAssistantContext();
131
- const firstRenderRef = useRef3(true);
132
- const lastScrollTop = useRef3(0);
131
+ const firstRenderRef = useRef(true);
132
+ const lastScrollTop = useRef(0);
133
133
  const scrollToBottom = () => {
134
134
  const div = messagesEndRef.current;
135
135
  if (!div || !autoScroll) return;
@@ -214,7 +214,7 @@ var message_exports = {};
214
214
  __export(message_exports, {
215
215
  Content: () => MessageContent,
216
216
  If: () => MessageIf,
217
- Loading: () => MessageLoading,
217
+ InProgress: () => MessageInProgress,
218
218
  Provider: () => MessageProvider,
219
219
  Root: () => MessageRoot
220
220
  });
@@ -296,11 +296,11 @@ var useMessageContext2 = () => {
296
296
  parentId: null,
297
297
  branches: [],
298
298
  isLast: false,
299
- loadingIndicator: null,
299
+ inProgressIndicator: null,
300
300
  isCopied: false,
301
301
  isHovering: false,
302
- setLoadingIndicator: (value) => {
303
- set({ loadingIndicator: value });
302
+ setInProgressIndicator: (value) => {
303
+ set({ inProgressIndicator: value });
304
304
  },
305
305
  setIsCopied: (value) => {
306
306
  set({ isCopied: value });
@@ -443,15 +443,15 @@ var useContentPartContext = () => {
443
443
  return context;
444
444
  };
445
445
 
446
- // src/primitives/contentPart/ContentPartLoadingIndicator.tsx
447
- var ContentPartLoadingIndicator = () => {
446
+ // src/primitives/contentPart/ContentPartInProgressIndicator.tsx
447
+ var ContentPartInProgressIndicator = () => {
448
448
  const { useMessage } = useMessageContext();
449
449
  const { useContentPart } = useContentPartContext();
450
- const loadingIndicator = useCombinedStore(
450
+ const indicator = useCombinedStore(
451
451
  [useMessage, useContentPart],
452
- (m, c) => c.isLoading ? m.loadingIndicator : null
452
+ (m, c) => c.status === "in_progress" ? m.inProgressIndicator : null
453
453
  );
454
- return loadingIndicator;
454
+ return indicator;
455
455
  };
456
456
 
457
457
  // src/primitives/contentPart/ContentPartProvider.tsx
@@ -462,7 +462,7 @@ var useContentPartContext2 = () => {
462
462
  const [context] = useState2(() => {
463
463
  const useContentPart = create3(() => ({
464
464
  part: null,
465
- isLoading: false
465
+ status: "done"
466
466
  }));
467
467
  return { useContentPart };
468
468
  });
@@ -470,7 +470,7 @@ var useContentPartContext2 = () => {
470
470
  };
471
471
  var ContentPartProvider = ({
472
472
  part,
473
- isLoading,
473
+ status,
474
474
  children
475
475
  }) => {
476
476
  const context = useContentPartContext2();
@@ -478,11 +478,11 @@ var ContentPartProvider = ({
478
478
  context.useContentPart.setState(
479
479
  {
480
480
  part,
481
- isLoading
481
+ status
482
482
  },
483
483
  true
484
484
  );
485
- }, [context, part, isLoading]);
485
+ }, [context, part, status]);
486
486
  return /* @__PURE__ */ jsx6(ContentPartContext.Provider, { value: context, children });
487
487
  };
488
488
 
@@ -491,7 +491,7 @@ import { Fragment, jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
491
491
  var defaultComponents = {
492
492
  Text: ({ part }) => /* @__PURE__ */ jsxs2(Fragment, { children: [
493
493
  part.text,
494
- /* @__PURE__ */ jsx7(ContentPartLoadingIndicator, {})
494
+ /* @__PURE__ */ jsx7(ContentPartInProgressIndicator, {})
495
495
  ] }),
496
496
  Image: () => null,
497
497
  UI: ({ part }) => part.display,
@@ -507,13 +507,10 @@ var MessageContent = ({
507
507
  tools: { by_name = {}, Fallback = defaultComponents.tools.Fallback } = {}
508
508
  } = {}
509
509
  }) => {
510
- const { useThread } = useAssistantContext();
511
510
  const { useMessage } = useMessageContext();
512
- const content = useMessage((s) => s.message.content);
513
- const isLoading = useCombinedStore(
514
- [useThread, useMessage],
515
- (t, s) => s.isLast && t.isRunning
516
- );
511
+ const message = useMessage((s) => s.message);
512
+ const content = message.content;
513
+ const status = message.role === "assistant" ? message.status : "done";
517
514
  return /* @__PURE__ */ jsx7(Fragment, { children: content.map((part, i) => {
518
515
  const key = i;
519
516
  const type = part.type;
@@ -540,7 +537,7 @@ var MessageContent = ({
540
537
  ContentPartProvider,
541
538
  {
542
539
  part,
543
- isLoading: i === content.length - 1 && isLoading,
540
+ status: i === content.length - 1 ? status : "done",
544
541
  children: component
545
542
  },
546
543
  key
@@ -548,16 +545,16 @@ var MessageContent = ({
548
545
  }) });
549
546
  };
550
547
 
551
- // src/primitives/message/MessageLoading.tsx
548
+ // src/primitives/message/MessageInProgress.tsx
552
549
  import {
553
550
  Primitive as Primitive4
554
551
  } from "@radix-ui/react-primitive";
555
552
  import { forwardRef as forwardRef4, useMemo as useMemo4 } from "react";
556
553
  import { jsx as jsx8 } from "react/jsx-runtime";
557
- var MessageLoading = forwardRef4((props, ref) => {
554
+ var MessageInProgress = forwardRef4((props, ref) => {
558
555
  const { useMessage } = useMessageContext();
559
556
  useMemo4(() => {
560
- useMessage.getState().setLoadingIndicator(/* @__PURE__ */ jsx8(Primitive4.div, { ...props, ref }));
557
+ useMessage.getState().setInProgressIndicator(/* @__PURE__ */ jsx8(Primitive4.div, { ...props, ref }));
561
558
  }, [useMessage, props, ref]);
562
559
  return null;
563
560
  });
@@ -666,13 +663,13 @@ import { useComposedRefs as useComposedRefs2 } from "@radix-ui/react-compose-ref
666
663
  import {
667
664
  Primitive as Primitive7
668
665
  } from "@radix-ui/react-primitive";
669
- import { forwardRef as forwardRef7, useRef as useRef4 } from "react";
666
+ import { forwardRef as forwardRef7, useRef as useRef2 } from "react";
670
667
  import { jsx as jsx12 } from "react/jsx-runtime";
671
668
  var ComposerRoot = forwardRef7(
672
669
  ({ onSubmit, ...rest }, forwardedRef) => {
673
670
  const { useViewport } = useAssistantContext();
674
671
  const { useComposer } = useComposerContext();
675
- const formRef = useRef4(null);
672
+ const formRef = useRef2(null);
676
673
  const ref = useComposedRefs2(forwardedRef, formRef);
677
674
  const handleSubmit = (e) => {
678
675
  const composerState = useComposer.getState();
@@ -700,7 +697,7 @@ import {
700
697
  forwardRef as forwardRef8,
701
698
  useCallback,
702
699
  useEffect as useEffect3,
703
- useRef as useRef5
700
+ useRef as useRef3
704
701
  } from "react";
705
702
  import TextareaAutosize from "react-textarea-autosize";
706
703
  import { jsx as jsx13 } from "react/jsx-runtime";
@@ -729,7 +726,7 @@ var ComposerInput = forwardRef8(
729
726
  }
730
727
  }
731
728
  };
732
- const textareaRef = useRef5(null);
729
+ const textareaRef = useRef3(null);
733
730
  const ref = useComposedRefs3(forwardedRef, textareaRef);
734
731
  const autoFocusEnabled = autoFocus && !disabled;
735
732
  const focus = useCallback(() => {
@@ -876,7 +873,6 @@ var useGoToPreviousBranch = () => {
876
873
  const { message, branches } = useMessage.getState();
877
874
  useThread.getState().switchToBranch(
878
875
  branches[branches.indexOf(message.id) - 1]
879
- // TODO probably there's a more elegant way to do this
880
876
  );
881
877
  };
882
878
  };
@@ -1013,7 +1009,7 @@ var ActionBarEdit = createActionButton(useBeginMessageEdit);
1013
1009
  // src/primitives/contentPart/index.ts
1014
1010
  var contentPart_exports = {};
1015
1011
  __export(contentPart_exports, {
1016
- LoadingIndicator: () => ContentPartLoadingIndicator,
1012
+ InProgressIndicator: () => ContentPartInProgressIndicator,
1017
1013
  Provider: () => ContentPartProvider
1018
1014
  });
1019
1015
 
@@ -1077,9 +1073,10 @@ var useDummyAIAssistantContext = () => {
1077
1073
  };
1078
1074
 
1079
1075
  // src/adapters/vercel/useVercelAIThreadState.tsx
1080
- import { useCallback as useCallback2, useMemo as useMemo5, useRef as useRef6, useState as useState4 } from "react";
1076
+ import { useCallbackRef as useCallbackRef3 } from "@radix-ui/react-use-callback-ref";
1077
+ import { useCallback as useCallback2, useMemo as useMemo5, useRef as useRef4, useState as useState4 } from "react";
1081
1078
 
1082
- // src/adapters/MessageRepository.tsx
1079
+ // src/adapters/idUtils.tsx
1083
1080
  import { customAlphabet } from "nanoid/non-secure";
1084
1081
  var generateId = customAlphabet(
1085
1082
  "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
@@ -1087,7 +1084,8 @@ var generateId = customAlphabet(
1087
1084
  );
1088
1085
  var optimisticPrefix = "__optimistic__";
1089
1086
  var generateOptimisticId = () => `${optimisticPrefix}${generateId()}`;
1090
- var isOptimisticId = (id) => id.startsWith(optimisticPrefix);
1087
+
1088
+ // src/adapters/MessageRepository.tsx
1091
1089
  var findHead = (message) => {
1092
1090
  if (message.next) return findHead(message.next);
1093
1091
  return message;
@@ -1099,15 +1097,6 @@ var MessageRepository = class {
1099
1097
  root = {
1100
1098
  children: []
1101
1099
  };
1102
- getFallbackChild(p) {
1103
- const childId = p.children.at(-1);
1104
- const child = childId ? this.messages.get(childId) : null;
1105
- if (child === void 0)
1106
- throw new Error(
1107
- "MessageRepository(getFallbackChild): Child message not found. This is likely an internal bug in assistant-ui."
1108
- );
1109
- return child;
1110
- }
1111
1100
  performOp(newParent, child, operation) {
1112
1101
  const parentOrRoot = child.prev ?? this.root;
1113
1102
  const newParentOrRoot = newParent ?? this.root;
@@ -1117,7 +1106,14 @@ var MessageRepository = class {
1117
1106
  (m) => m !== child.current.id
1118
1107
  );
1119
1108
  if (child.prev?.next === child) {
1120
- child.prev.next = this.getFallbackChild(child.prev);
1109
+ const fallbackId = child.prev.children.at(-1);
1110
+ const fallback = fallbackId ? this.messages.get(fallbackId) : null;
1111
+ if (fallback === void 0) {
1112
+ throw new Error(
1113
+ "MessageRepository(performOp/cut): Fallback sibling message not found. This is likely an internal bug in assistant-ui."
1114
+ );
1115
+ }
1116
+ child.prev.next = fallback;
1121
1117
  }
1122
1118
  }
1123
1119
  if (operation !== "cut") {
@@ -1171,18 +1167,19 @@ var MessageRepository = class {
1171
1167
  this.addOrUpdateMessage(parentId, {
1172
1168
  ...message,
1173
1169
  id: optimisticId,
1174
- createdAt: /* @__PURE__ */ new Date()
1170
+ createdAt: /* @__PURE__ */ new Date(),
1171
+ ...message.role === "assistant" ? { status: "in_progress" } : void 0
1175
1172
  });
1176
1173
  return optimisticId;
1177
1174
  }
1178
- deleteMessage(messageId, newParentId) {
1175
+ deleteMessage(messageId, replacementId) {
1179
1176
  const message = this.messages.get(messageId);
1180
- const newParent = newParentId ? this.messages.get(newParentId) : null;
1177
+ const replacement = replacementId ? this.messages.get(replacementId) : null;
1181
1178
  if (!message)
1182
1179
  throw new Error(
1183
1180
  "MessageRepository(deleteMessage): Optimistic message not found. This is likely an internal bug in assistant-ui."
1184
1181
  );
1185
- if (newParent === void 0)
1182
+ if (replacement === void 0)
1186
1183
  throw new Error(
1187
1184
  "MessageRepository(deleteMessage): New message not found. This is likely an internal bug in assistant-ui."
1188
1185
  );
@@ -1192,11 +1189,11 @@ var MessageRepository = class {
1192
1189
  throw new Error(
1193
1190
  "MessageRepository(deleteMessage): Child message not found. This is likely an internal bug in assistant-ui."
1194
1191
  );
1195
- this.performOp(newParent, childMessage, "relink");
1192
+ this.performOp(replacement, childMessage, "relink");
1196
1193
  }
1197
1194
  this.messages.delete(messageId);
1198
1195
  if (this.head === message) {
1199
- this.head = this.getFallbackChild(message.prev ?? this.root);
1196
+ this.head = replacement;
1200
1197
  }
1201
1198
  this.performOp(null, message, "cut");
1202
1199
  }
@@ -1239,50 +1236,68 @@ var MessageRepository = class {
1239
1236
  }
1240
1237
  };
1241
1238
 
1242
- // src/adapters/ThreadMessageConverter.tsx
1239
+ // src/adapters/ThreadMessageConverter.ts
1243
1240
  var ThreadMessageConverter = class {
1244
- constructor(converter2) {
1245
- this.converter = converter2;
1246
- }
1247
1241
  cache = /* @__PURE__ */ new WeakMap();
1248
- convertMessages(messages) {
1242
+ convertMessages(converter, messages) {
1249
1243
  return messages.map((m) => {
1250
1244
  const cached = this.cache.get(m);
1251
- if (cached) return cached;
1252
- const newMessage = this.converter(m);
1245
+ const newMessage = converter(m, cached);
1253
1246
  this.cache.set(m, newMessage);
1254
1247
  return newMessage;
1255
1248
  });
1256
1249
  }
1257
1250
  };
1258
1251
 
1252
+ // src/adapters/vercel/VercelThreadMessage.tsx
1253
+ var symbolInnerMessage = Symbol("innerMessage");
1254
+ var symbolInnerRSCMessage = Symbol("innerRSCMessage");
1255
+ var getVercelMessage = (message) => {
1256
+ return message[symbolInnerMessage];
1257
+ };
1258
+ var getVercelRSCMessage = (message) => {
1259
+ return message[symbolInnerRSCMessage];
1260
+ };
1261
+
1259
1262
  // src/adapters/vercel/useVercelAIThreadState.tsx
1260
- var vercelToThreadMessage = (message) => {
1261
- if (message.role !== "user" && message.role !== "assistant")
1262
- throw new Error(
1263
- `You have a message with an unsupported role. The role ${message.role} is not supported.`
1264
- );
1265
- return {
1263
+ var vercelToThreadMessage = (message, status) => {
1264
+ const common = {
1266
1265
  id: message.id,
1267
- role: message.role,
1268
- content: [
1269
- ...message.content ? [{ type: "text", text: message.content }] : [],
1270
- ...message.toolInvocations?.map((t) => ({
1271
- type: "tool-call",
1272
- name: t.toolName,
1273
- args: t.args,
1274
- result: "result" in t ? t.result : void 0
1275
- })) ?? []
1276
- ],
1277
- // ignore type mismatch for now
1278
1266
  createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1279
- innerMessage: message
1267
+ [symbolInnerMessage]: message
1280
1268
  };
1269
+ switch (message.role) {
1270
+ case "user":
1271
+ return {
1272
+ ...common,
1273
+ role: "user",
1274
+ content: [{ type: "text", text: message.content }]
1275
+ };
1276
+ case "assistant":
1277
+ return {
1278
+ ...common,
1279
+ role: "assistant",
1280
+ content: [
1281
+ ...message.content ? [{ type: "text", text: message.content }] : [],
1282
+ ...message.toolInvocations?.map(
1283
+ (t) => ({
1284
+ type: "tool-call",
1285
+ name: t.toolName,
1286
+ args: t.args,
1287
+ result: "result" in t ? t.result : void 0
1288
+ })
1289
+ ) ?? []
1290
+ ],
1291
+ status
1292
+ };
1293
+ default:
1294
+ throw new Error(
1295
+ `You have a message with an unsupported role. The role ${message.role} is not supported.`
1296
+ );
1297
+ }
1281
1298
  };
1282
- var converter = new ThreadMessageConverter(vercelToThreadMessage);
1283
1299
  var sliceMessagesUntil = (messages, messageId) => {
1284
1300
  if (messageId == null) return [];
1285
- if (isOptimisticId(messageId)) return messages;
1286
1301
  const messageIdx = messages.findIndex((m) => m.id === messageId);
1287
1302
  if (messageIdx === -1)
1288
1303
  throw new Error(
@@ -1299,12 +1314,18 @@ var getIsRunning = (vercel) => {
1299
1314
  };
1300
1315
  var useVercelAIThreadState = (vercel) => {
1301
1316
  const [data] = useState4(() => new MessageRepository());
1302
- const vercelRef = useRef6(vercel);
1303
- vercelRef.current = vercel;
1304
- const isRunning = getIsRunning(vercelRef.current);
1305
- const assistantOptimisticIdRef = useRef6(null);
1317
+ const isRunning = getIsRunning(vercel);
1318
+ const converter = useMemo5(() => new ThreadMessageConverter(), []);
1319
+ const assistantOptimisticIdRef = useRef4(null);
1306
1320
  const messages = useMemo5(() => {
1307
- const vm = converter.convertMessages(vercel.messages);
1321
+ const lastMessageId = vercel.messages.at(-1)?.id;
1322
+ const convertCallback = (message, cache) => {
1323
+ const status = lastMessageId === message.id && isRunning ? "in_progress" : "done";
1324
+ if (cache && (cache.role === "user" || cache.status === status))
1325
+ return cache;
1326
+ return vercelToThreadMessage(message, status);
1327
+ };
1328
+ const vm = converter.convertMessages(convertCallback, vercel.messages);
1308
1329
  for (let i = 0; i < vm.length; i++) {
1309
1330
  const message = vm[i];
1310
1331
  const parent = vm[i - 1];
@@ -1325,55 +1346,46 @@ var useVercelAIThreadState = (vercel) => {
1325
1346
  }
1326
1347
  data.resetHead(assistantOptimisticIdRef.current ?? vm.at(-1)?.id ?? null);
1327
1348
  return data.getMessages();
1328
- }, [data, isRunning, vercel.messages]);
1349
+ }, [converter, data, isRunning, vercel.messages]);
1329
1350
  const getBranches2 = useCallback2(
1330
1351
  (messageId) => {
1331
1352
  return data.getBranches(messageId);
1332
1353
  },
1333
1354
  [data]
1334
1355
  );
1335
- const switchToBranch2 = useCallback2(
1336
- (messageId) => {
1337
- data.switchToBranch(messageId);
1338
- vercelRef.current.setMessages(
1339
- data.getMessages().filter((m) => !isOptimisticId(m.id)).map((m) => m.innerMessage)
1340
- );
1341
- },
1342
- [data]
1343
- );
1344
- const startRun = useCallback2(async (parentId) => {
1345
- const reloadMaybe = "reload" in vercelRef.current ? vercelRef.current.reload : void 0;
1356
+ const switchToBranch2 = useCallbackRef3((messageId) => {
1357
+ data.switchToBranch(messageId);
1358
+ vercel.setMessages(
1359
+ data.getMessages().map(getVercelMessage).filter((m) => m != null)
1360
+ );
1361
+ });
1362
+ const startRun = useCallbackRef3(async (parentId) => {
1363
+ const reloadMaybe = "reload" in vercel ? vercel.reload : void 0;
1346
1364
  if (!reloadMaybe)
1347
1365
  throw new Error(
1348
1366
  "Reload is not supported by Vercel AI SDK's useAssistant."
1349
1367
  );
1350
- const newMessages = sliceMessagesUntil(
1351
- vercelRef.current.messages,
1352
- parentId
1353
- );
1354
- vercelRef.current.setMessages(newMessages);
1368
+ const newMessages = sliceMessagesUntil(vercel.messages, parentId);
1369
+ vercel.setMessages(newMessages);
1355
1370
  await reloadMaybe();
1356
- }, []);
1357
- const append = useCallback2(async (message) => {
1371
+ });
1372
+ const append = useCallbackRef3(async (message) => {
1358
1373
  if (message.content.length !== 1 || message.content[0]?.type !== "text")
1359
1374
  throw new Error("Only text content is supported by Vercel AI SDK.");
1360
- const newMessages = sliceMessagesUntil(
1361
- vercelRef.current.messages,
1362
- message.parentId
1363
- );
1364
- vercelRef.current.setMessages(newMessages);
1365
- await vercelRef.current.append({
1375
+ const newMessages = sliceMessagesUntil(vercel.messages, message.parentId);
1376
+ vercel.setMessages(newMessages);
1377
+ await vercel.append({
1366
1378
  role: "user",
1367
1379
  content: message.content[0].text
1368
1380
  });
1369
- }, []);
1370
- const cancelRun2 = useCallback2(() => {
1371
- const lastMessage = vercelRef.current.messages.at(-1);
1372
- vercelRef.current.stop();
1381
+ });
1382
+ const cancelRun2 = useCallbackRef3(() => {
1383
+ const lastMessage = vercel.messages.at(-1);
1384
+ vercel.stop();
1373
1385
  if (lastMessage?.role === "user") {
1374
- vercelRef.current.setInput(lastMessage.content);
1386
+ vercel.setInput(lastMessage.content);
1375
1387
  }
1376
- }, []);
1388
+ });
1377
1389
  return useMemo5(
1378
1390
  () => ({
1379
1391
  isRunning,
@@ -1424,12 +1436,15 @@ import {
1424
1436
  useState as useState5
1425
1437
  } from "react";
1426
1438
  import { jsx as jsx22 } from "react/jsx-runtime";
1427
- var vercelToThreadMessage2 = (message) => {
1439
+ var vercelToThreadMessage2 = (converter, rawMessage) => {
1440
+ const message = converter(rawMessage);
1428
1441
  return {
1429
1442
  id: message.id,
1430
1443
  role: message.role,
1431
1444
  content: [{ type: "ui", display: message.display }],
1432
- createdAt: message.createdAt ?? /* @__PURE__ */ new Date()
1445
+ createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
1446
+ ...{ status: "done" },
1447
+ [symbolInnerRSCMessage]: rawMessage
1433
1448
  };
1434
1449
  };
1435
1450
  var EMPTY_BRANCHES = [];
@@ -1462,15 +1477,17 @@ var VercelRSCAssistantProvider = ({
1462
1477
  setIsRunning(true);
1463
1478
  return callback.finally(() => setIsRunning(false));
1464
1479
  }, []);
1465
- const converter2 = useMemo7(() => {
1480
+ const [converter, convertCallback] = useMemo7(() => {
1466
1481
  const rscConverter = convertMessage ?? ((m) => m);
1467
- return new ThreadMessageConverter((m) => {
1468
- return vercelToThreadMessage2(rscConverter(m));
1469
- });
1482
+ const convertCallback2 = (m, cache) => {
1483
+ if (cache) return cache;
1484
+ return vercelToThreadMessage2(rscConverter, m);
1485
+ };
1486
+ return [new ThreadMessageConverter(), convertCallback2];
1470
1487
  }, [convertMessage]);
1471
1488
  const messages = useMemo7(() => {
1472
- return converter2.convertMessages(vercelMessages);
1473
- }, [converter2, vercelMessages]);
1489
+ return converter.convertMessages(convertCallback, vercelMessages);
1490
+ }, [converter, convertCallback, vercelMessages]);
1474
1491
  const append = useCallback3(
1475
1492
  async (message) => {
1476
1493
  if (message.parentId !== (context.useThread.getState().messages.at(-1)?.id ?? null)) {
@@ -1511,6 +1528,259 @@ var VercelRSCAssistantProvider = ({
1511
1528
  }, [context, messages, isRunning, append, startRun]);
1512
1529
  return /* @__PURE__ */ jsx22(AssistantContext.Provider, { value: context, children });
1513
1530
  };
1531
+
1532
+ // src/adapters/core/utils/useAssistantContext.tsx
1533
+ import { useEffect as useEffect4, useRef as useRef5, useState as useState6 } from "react";
1534
+ import { create as create6 } from "zustand";
1535
+
1536
+ // src/adapters/core/utils/AssistantMessageRepository.tsx
1537
+ var AssistantMessageRepository = class {
1538
+ constructor(flushCallback) {
1539
+ this.flushCallback = flushCallback;
1540
+ }
1541
+ repository = new MessageRepository();
1542
+ getBranches(messageId) {
1543
+ return this.repository.getBranches(messageId);
1544
+ }
1545
+ withModifications(callback) {
1546
+ const res = callback(this.repository);
1547
+ this.flushCallback(this.repository.getMessages());
1548
+ return res;
1549
+ }
1550
+ };
1551
+
1552
+ // src/adapters/core/utils/useAssistantContext.tsx
1553
+ var makeThreadStore = (runtimeRef) => {
1554
+ const repository = new AssistantMessageRepository((messages) => {
1555
+ useThread.setState({ messages });
1556
+ });
1557
+ const useThread = create6(() => ({
1558
+ messages: [],
1559
+ isRunning: false,
1560
+ getBranches: (messageId) => repository.getBranches(messageId),
1561
+ switchToBranch: (branchId) => {
1562
+ repository.withModifications((repository2) => {
1563
+ repository2.switchToBranch(branchId);
1564
+ });
1565
+ },
1566
+ startRun: async (parentId) => {
1567
+ const optimisticId = repository.withModifications((repository2) => {
1568
+ const optimisticId2 = repository2.appendOptimisticMessage(parentId, {
1569
+ role: "assistant",
1570
+ content: [{ type: "text", text: "" }]
1571
+ });
1572
+ repository2.resetHead(optimisticId2);
1573
+ return optimisticId2;
1574
+ });
1575
+ const { id } = await runtimeRef.current.startRun(parentId);
1576
+ repository.withModifications((repository2) => {
1577
+ repository2.deleteMessage(optimisticId, id);
1578
+ });
1579
+ },
1580
+ append: async (message) => {
1581
+ const [parentOptimisticId, optimisticId] = repository.withModifications(
1582
+ (repository2) => {
1583
+ const parentOptimisticId2 = repository2.appendOptimisticMessage(
1584
+ message.parentId,
1585
+ {
1586
+ role: "user",
1587
+ content: message.content
1588
+ }
1589
+ );
1590
+ const optimisticId2 = repository2.appendOptimisticMessage(
1591
+ parentOptimisticId2,
1592
+ {
1593
+ role: "assistant",
1594
+ content: [{ type: "text", text: "" }]
1595
+ }
1596
+ );
1597
+ repository2.resetHead(optimisticId2);
1598
+ return [parentOptimisticId2, optimisticId2];
1599
+ }
1600
+ );
1601
+ const { parentId, id } = await runtimeRef.current.append(message);
1602
+ repository.withModifications((repository2) => {
1603
+ repository2.deleteMessage(parentOptimisticId, parentId);
1604
+ repository2.deleteMessage(optimisticId, id);
1605
+ });
1606
+ },
1607
+ cancelRun: () => runtimeRef.current.cancelRun()
1608
+ }));
1609
+ const onNewMessage = (parentId, message) => {
1610
+ repository.withModifications((repository2) => {
1611
+ repository2.addOrUpdateMessage(parentId, message);
1612
+ });
1613
+ };
1614
+ const onRunningChange = (isRunning) => {
1615
+ useThread.setState({ isRunning });
1616
+ };
1617
+ return {
1618
+ useThread,
1619
+ onNewMessage,
1620
+ onRunningChange
1621
+ };
1622
+ };
1623
+ var useAssistantContext2 = (runtime) => {
1624
+ const runtimeRef = useRef5(runtime);
1625
+ runtimeRef.current = runtime;
1626
+ const [{ context, onNewMessage, onRunningChange }] = useState6(() => {
1627
+ const { useThread, onNewMessage: onNewMessage2, onRunningChange: onRunningChange2 } = makeThreadStore(runtimeRef);
1628
+ const useViewport = makeViewportStore();
1629
+ const useComposer = makeThreadComposerStore(useThread);
1630
+ return {
1631
+ context: { useViewport, useThread, useComposer },
1632
+ onNewMessage: onNewMessage2,
1633
+ onRunningChange: onRunningChange2
1634
+ };
1635
+ });
1636
+ useEffect4(() => {
1637
+ return runtime.subscribeToMessageUpdates(onNewMessage);
1638
+ }, [runtime, onNewMessage]);
1639
+ useEffect4(() => {
1640
+ return runtime.subscribeToStatusUpdates(onRunningChange);
1641
+ }, [runtime, onRunningChange]);
1642
+ return context;
1643
+ };
1644
+
1645
+ // src/adapters/core/AssistantProvider.tsx
1646
+ import { jsx as jsx23 } from "react/jsx-runtime";
1647
+ var AssistantProvider = ({ children, runtime }) => {
1648
+ const context = useAssistantContext2(runtime);
1649
+ return /* @__PURE__ */ jsx23(AssistantContext.Provider, { value: context, children });
1650
+ };
1651
+
1652
+ // src/adapters/core/local/useLocalRuntime.tsx
1653
+ import { useState as useState7 } from "react";
1654
+
1655
+ // src/adapters/core/local/LocalRuntime.tsx
1656
+ var LocalRuntime = class {
1657
+ constructor(adapter) {
1658
+ this.adapter = adapter;
1659
+ }
1660
+ _messageUpdateCallbacks = /* @__PURE__ */ new Set();
1661
+ _statusUpdateCallbacks = /* @__PURE__ */ new Set();
1662
+ abortController = null;
1663
+ repository = new MessageRepository();
1664
+ async append(message) {
1665
+ const userMessageId = generateId();
1666
+ const userMessage = {
1667
+ id: userMessageId,
1668
+ role: "user",
1669
+ content: message.content,
1670
+ createdAt: /* @__PURE__ */ new Date()
1671
+ };
1672
+ this.addOrUpdateMessage(message.parentId, userMessage);
1673
+ const { id } = await this.startRun(userMessageId);
1674
+ return { parentId: userMessageId, id };
1675
+ }
1676
+ async startRun(parentId) {
1677
+ const id = generateId();
1678
+ this.repository.resetHead(parentId);
1679
+ const messages = this.repository.getMessages();
1680
+ const message = {
1681
+ id,
1682
+ role: "assistant",
1683
+ status: "in_progress",
1684
+ content: [{ type: "text", text: "" }],
1685
+ createdAt: /* @__PURE__ */ new Date()
1686
+ };
1687
+ this.addOrUpdateMessage(parentId, message);
1688
+ void this.run(parentId, messages, message);
1689
+ return { id };
1690
+ }
1691
+ addOrUpdateMessage(parentId, message) {
1692
+ const clone = { ...message };
1693
+ this.repository.addOrUpdateMessage(parentId, clone);
1694
+ for (const callback of this._messageUpdateCallbacks)
1695
+ callback(parentId, clone);
1696
+ }
1697
+ async run(parentId, messages, message) {
1698
+ this.cancelRun();
1699
+ for (const callback of this._statusUpdateCallbacks) callback(true);
1700
+ this.abortController = new AbortController();
1701
+ try {
1702
+ await this.adapter.run({
1703
+ messages,
1704
+ abortSignal: this.abortController.signal,
1705
+ onUpdate: ({ content }) => {
1706
+ message.content = content;
1707
+ this.addOrUpdateMessage(parentId, message);
1708
+ }
1709
+ });
1710
+ message.status = "done";
1711
+ this.addOrUpdateMessage(parentId, message);
1712
+ } catch (e) {
1713
+ message.status = "error";
1714
+ this.addOrUpdateMessage(parentId, message);
1715
+ console.error(e);
1716
+ } finally {
1717
+ this.cancelRun();
1718
+ }
1719
+ }
1720
+ cancelRun() {
1721
+ if (!this.abortController) return;
1722
+ this.abortController.abort();
1723
+ this.abortController = null;
1724
+ for (const callback of this._statusUpdateCallbacks) callback(false);
1725
+ }
1726
+ subscribeToMessageUpdates(callback) {
1727
+ this._messageUpdateCallbacks.add(callback);
1728
+ return () => this._messageUpdateCallbacks.delete(callback);
1729
+ }
1730
+ subscribeToStatusUpdates(callback) {
1731
+ this._statusUpdateCallbacks.add(callback);
1732
+ return () => this._statusUpdateCallbacks.delete(callback);
1733
+ }
1734
+ };
1735
+
1736
+ // src/adapters/core/local/useLocalRuntime.tsx
1737
+ var useLocalRuntime = (adapter) => {
1738
+ const [runtime] = useState7(() => new LocalRuntime(adapter));
1739
+ runtime.adapter = adapter;
1740
+ return runtime;
1741
+ };
1742
+
1743
+ // src/adapters/core/local/vercel/VercelModelAdapter.tsx
1744
+ import { streamText } from "ai";
1745
+ var VercelModelAdapter = class {
1746
+ constructor(model) {
1747
+ this.model = model;
1748
+ }
1749
+ async run({ messages, abortSignal, onUpdate }) {
1750
+ const { fullStream } = await streamText({
1751
+ model: this.model,
1752
+ abortSignal,
1753
+ messages: messages.map((m) => ({
1754
+ role: m.role,
1755
+ content: m.content.filter((c) => c.type !== "ui")
1756
+ }))
1757
+ });
1758
+ const content = [];
1759
+ for await (const aiPart of fullStream) {
1760
+ switch (aiPart.type) {
1761
+ case "text-delta": {
1762
+ let part = content.at(-1);
1763
+ if (!part || part.type !== "text") {
1764
+ part = { type: "text", text: "" };
1765
+ content.push(part);
1766
+ }
1767
+ part.text += aiPart.textDelta;
1768
+ break;
1769
+ }
1770
+ case "tool-call": {
1771
+ content.push({
1772
+ type: "tool-call",
1773
+ name: aiPart.toolName,
1774
+ args: aiPart.args
1775
+ });
1776
+ break;
1777
+ }
1778
+ }
1779
+ onUpdate({ content });
1780
+ }
1781
+ return { content };
1782
+ }
1783
+ };
1514
1784
  export {
1515
1785
  actionBar_exports as ActionBarPrimitive,
1516
1786
  branchPicker_exports as BranchPickerPrimitive,
@@ -1520,6 +1790,11 @@ export {
1520
1790
  thread_exports as ThreadPrimitive,
1521
1791
  VercelAIAssistantProvider,
1522
1792
  VercelRSCAssistantProvider,
1793
+ AssistantProvider as unstable_AssistantProvider,
1794
+ VercelModelAdapter as unstable_VercelModelAdapter,
1795
+ getVercelMessage as unstable_getVercelMessage,
1796
+ getVercelRSCMessage as unstable_getVercelRSCMessage,
1797
+ useLocalRuntime as unstable_useLocalRuntime,
1523
1798
  useMessageContext as unstable_useMessageContext,
1524
1799
  useBeginMessageEdit,
1525
1800
  useCopyMessage,