@assistant-ui/react 0.0.16 → 0.0.18

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