@assistant-ui/react 0.14.28 → 0.15.0

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.
Files changed (190) hide show
  1. package/dist/client/ExternalThread.d.ts +11 -1
  2. package/dist/client/ExternalThread.d.ts.map +1 -1
  3. package/dist/client/ExternalThread.js +762 -516
  4. package/dist/client/ExternalThread.js.map +1 -1
  5. package/dist/client/InMemoryThreadList.js +3 -3
  6. package/dist/client/InMemoryThreadList.js.map +1 -1
  7. package/dist/context/index.d.ts +1 -8
  8. package/dist/context/index.js +1 -8
  9. package/dist/index.d.ts +1 -8
  10. package/dist/index.js +1 -8
  11. package/dist/legacy-runtime/cloud/auiV0.d.ts +12 -1
  12. package/dist/legacy-runtime/cloud/auiV0.d.ts.map +1 -1
  13. package/dist/legacy-runtime/cloud/auiV0.js +2 -1
  14. package/dist/legacy-runtime/cloud/auiV0.js.map +1 -1
  15. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +1 -1
  16. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +7 -6
  17. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +1 -1
  18. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +1 -1
  19. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +30 -15
  20. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +1 -1
  21. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  22. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +11 -7
  23. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  24. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +1 -0
  25. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -1
  26. package/dist/mcp-apps/McpAppRenderer.js +1 -1
  27. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  28. package/dist/model-context/makeAssistantVisible.js +26 -25
  29. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  30. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
  31. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  32. package/dist/primitives/actionBar/ActionBarRoot.js +3 -3
  33. package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
  34. package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts +7 -6
  35. package/dist/primitives/actionBar/useActionBarFloatStatus.d.ts.map +1 -1
  36. package/dist/primitives/actionBar/useActionBarFloatStatus.js +10 -11
  37. package/dist/primitives/actionBar/useActionBarFloatStatus.js.map +1 -1
  38. package/dist/primitives/attachment/AttachmentRemove.js +4 -3
  39. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  40. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +4 -3
  41. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  42. package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
  43. package/dist/primitives/composer/ComposerAddAttachment.js +11 -5
  44. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  45. package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts.map +1 -1
  46. package/dist/primitives/composer/ComposerAttachmentDropzone.js +2 -4
  47. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  48. package/dist/primitives/composer/ComposerInput.js +18 -16
  49. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  50. package/dist/primitives/composer/ComposerQuote.js +1 -1
  51. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  52. package/dist/primitives/composer/ComposerSend.js +1 -0
  53. package/dist/primitives/composer/ComposerSend.js.map +1 -1
  54. package/dist/primitives/composer/ComposerStopDictation.js +4 -3
  55. package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
  56. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  57. package/dist/primitives/composer/trigger/triggerSelectionResource.js +3 -3
  58. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  59. package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
  60. package/dist/primitives/message/MessagePartsGrouped.js +67 -89
  61. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  62. package/dist/primitives/message/MessageRoot.js +3 -3
  63. package/dist/primitives/message/MessageRoot.js.map +1 -1
  64. package/dist/primitives/queueItem/QueueItemRemove.js +4 -3
  65. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  66. package/dist/primitives/queueItem/QueueItemSteer.js +4 -3
  67. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  68. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  69. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  70. package/dist/primitives/suggestion/SuggestionTrigger.js +17 -15
  71. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  72. package/dist/primitives/thread/ThreadScrollToBottom.js +1 -0
  73. package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
  74. package/dist/unstable/useComposerInput.js +2 -2
  75. package/dist/unstable/useComposerInput.js.map +1 -1
  76. package/dist/unstable/useComposerInputHistory.js +6 -6
  77. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  78. package/dist/unstable/useMentionAdapter.js +1 -1
  79. package/dist/unstable/useMentionAdapter.js.map +1 -1
  80. package/dist/utils/hooks/useOnResizeContent.js +1 -0
  81. package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
  82. package/dist/utils/hooks/useSizeHandle.js +1 -0
  83. package/dist/utils/hooks/useSizeHandle.js.map +1 -1
  84. package/dist/utils/smooth/SmoothContext.js +3 -3
  85. package/dist/utils/smooth/SmoothContext.js.map +1 -1
  86. package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
  87. package/dist/utils/smooth/useSmooth.js +3 -3
  88. package/dist/utils/smooth/useSmooth.js.map +1 -1
  89. package/package.json +9 -9
  90. package/src/client/ExternalThread.ts +345 -56
  91. package/src/client/InMemoryThreadList.ts +3 -3
  92. package/src/context/index.ts +0 -44
  93. package/src/legacy-runtime/cloud/auiV0.ts +14 -0
  94. package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +4 -1
  95. package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +37 -10
  96. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +79 -0
  97. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +97 -0
  98. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +113 -2
  99. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +10 -2
  100. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +185 -0
  101. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +21 -5
  102. package/src/mcp-apps/McpAppRenderer.tsx +1 -1
  103. package/src/model-context/makeAssistantVisible.test.tsx +1 -1
  104. package/src/model-context/makeAssistantVisible.tsx +1 -1
  105. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
  106. package/src/primitives/actionBar/useActionBarFloatStatus.ts +7 -5
  107. package/src/primitives/attachment/AttachmentRemove.ts +1 -1
  108. package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.ts +1 -1
  109. package/src/primitives/composer/ComposerAddAttachment.test.tsx +108 -0
  110. package/src/primitives/composer/ComposerAddAttachment.ts +11 -5
  111. package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +5 -4
  112. package/src/primitives/composer/ComposerAttachmentDropzone.tsx +4 -3
  113. package/src/primitives/composer/ComposerInput.test.tsx +104 -7
  114. package/src/primitives/composer/ComposerInput.tsx +22 -20
  115. package/src/primitives/composer/ComposerQuote.tsx +1 -1
  116. package/src/primitives/composer/ComposerStopDictation.ts +1 -1
  117. package/src/primitives/composer/trigger/triggerSelectionResource.ts +7 -9
  118. package/src/primitives/message/MessagePartsGrouped.tsx +6 -8
  119. package/src/primitives/message/MessageRoot.tsx +1 -1
  120. package/src/primitives/queueItem/QueueItemRemove.ts +1 -1
  121. package/src/primitives/queueItem/QueueItemSteer.ts +1 -1
  122. package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
  123. package/src/primitives/suggestion/SuggestionTrigger.ts +9 -9
  124. package/src/tests/DataRenderers.test.tsx +212 -0
  125. package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +1 -11
  126. package/src/tests/RemoteThreadListRuntime.deferredProvider.test.tsx +3 -3
  127. package/src/tests/external-thread-attachments.test.tsx +300 -11
  128. package/src/tests/external-thread-branches.test.tsx +10 -10
  129. package/src/tests/external-thread-parity.test.tsx +230 -0
  130. package/src/tests/generative-ui.test.tsx +1 -46
  131. package/src/tests/local-runtime-queue.test.tsx +29 -34
  132. package/src/tests/messagePartSwitchRace.test.tsx +46 -15
  133. package/src/tests/messagePartTypeMismatch.test.tsx +2 -2
  134. package/src/tests/threadMessageById.test.tsx +0 -7
  135. package/src/unstable/useComposerInput.test.tsx +2 -2
  136. package/src/unstable/useComposerInput.ts +2 -2
  137. package/src/unstable/useComposerInputHistory.test.tsx +3 -3
  138. package/src/unstable/useComposerInputHistory.ts +6 -6
  139. package/src/unstable/useMentionAdapter.ts +1 -1
  140. package/src/utils/smooth/SmoothContext.tsx +1 -1
  141. package/src/utils/smooth/useSmooth.test.tsx +1 -1
  142. package/src/utils/smooth/useSmooth.ts +8 -5
  143. package/dist/context/react/utils/createStateHookForRuntime.d.ts +0 -34
  144. package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +0 -1
  145. package/dist/context/react/utils/createStateHookForRuntime.js +0 -21
  146. package/dist/context/react/utils/createStateHookForRuntime.js.map +0 -1
  147. package/dist/legacy-runtime/hooks/AssistantContext.d.ts +0 -74
  148. package/dist/legacy-runtime/hooks/AssistantContext.d.ts.map +0 -1
  149. package/dist/legacy-runtime/hooks/AssistantContext.js +0 -29
  150. package/dist/legacy-runtime/hooks/AssistantContext.js.map +0 -1
  151. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts +0 -1007
  152. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts.map +0 -1
  153. package/dist/legacy-runtime/hooks/AttachmentContext.js +0 -47
  154. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +0 -1
  155. package/dist/legacy-runtime/hooks/ComposerContext.d.ts +0 -141
  156. package/dist/legacy-runtime/hooks/ComposerContext.d.ts.map +0 -1
  157. package/dist/legacy-runtime/hooks/ComposerContext.js +0 -65
  158. package/dist/legacy-runtime/hooks/ComposerContext.js.map +0 -1
  159. package/dist/legacy-runtime/hooks/MessageContext.d.ts +0 -155
  160. package/dist/legacy-runtime/hooks/MessageContext.d.ts.map +0 -1
  161. package/dist/legacy-runtime/hooks/MessageContext.js +0 -68
  162. package/dist/legacy-runtime/hooks/MessageContext.js.map +0 -1
  163. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts +0 -44
  164. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts.map +0 -1
  165. package/dist/legacy-runtime/hooks/MessagePartContext.js +0 -26
  166. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +0 -1
  167. package/dist/legacy-runtime/hooks/ThreadContext.d.ts +0 -139
  168. package/dist/legacy-runtime/hooks/ThreadContext.d.ts.map +0 -1
  169. package/dist/legacy-runtime/hooks/ThreadContext.js +0 -84
  170. package/dist/legacy-runtime/hooks/ThreadContext.js.map +0 -1
  171. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts +0 -44
  172. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts.map +0 -1
  173. package/dist/legacy-runtime/hooks/ThreadListItemContext.js +0 -26
  174. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +0 -1
  175. package/src/context/react/utils/createStateHookForRuntime.ts +0 -82
  176. package/src/legacy-runtime/hooks/AssistantContext.ts +0 -67
  177. package/src/legacy-runtime/hooks/AttachmentContext.ts +0 -99
  178. package/src/legacy-runtime/hooks/ComposerContext.ts +0 -131
  179. package/src/legacy-runtime/hooks/MessageContext.ts +0 -127
  180. package/src/legacy-runtime/hooks/MessagePartContext.ts +0 -32
  181. package/src/legacy-runtime/hooks/ThreadContext.ts +0 -120
  182. package/src/legacy-runtime/hooks/ThreadListItemContext.ts +0 -36
  183. package/src/tests/BaseComposerRuntimeCore.test.ts +0 -621
  184. package/src/tests/BaseSubscribable.test.ts +0 -54
  185. package/src/tests/DataRenderers.test.ts +0 -355
  186. package/src/tests/ExternalStoreThreadRuntimeCore.test.ts +0 -685
  187. package/src/tests/LocalThreadRuntimeCore.test.ts +0 -324
  188. package/src/tests/auiV0Encode.test.ts +0 -188
  189. package/src/tests/external-message-converter.test.ts +0 -346
  190. package/src/tests/shouldContinue.test.ts +0 -122
@@ -208,7 +208,7 @@ export const ComposerPrimitiveInput = forwardRef<
208
208
 
209
209
  if (!cancelOnEscape) return;
210
210
 
211
- const composer = aui.composer();
211
+ const composer = aui.composer;
212
212
  if (composer.getState().canCancel) {
213
213
  composer.cancel();
214
214
  e.preventDefault();
@@ -229,7 +229,7 @@ export const ComposerPrimitiveInput = forwardRef<
229
229
  }
230
230
 
231
231
  if (e.key === "Enter") {
232
- const threadState = aui.thread().getState();
232
+ const threadState = aui.thread.getState();
233
233
  const hasQueue = threadState.capabilities.queue;
234
234
 
235
235
  // Steer hotkey: Cmd/Ctrl+Shift+Enter (respects submitMode="none" and canSend)
@@ -238,10 +238,10 @@ export const ComposerPrimitiveInput = forwardRef<
238
238
  (e.ctrlKey || e.metaKey) &&
239
239
  hasQueue &&
240
240
  declaredSubmitMode !== "none" &&
241
- aui.composer().getState().canSend
241
+ aui.composer.getState().canSend
242
242
  ) {
243
243
  e.preventDefault();
244
- aui.composer().send({ steer: true });
244
+ aui.composer.send({ steer: true });
245
245
  return;
246
246
  }
247
247
 
@@ -267,18 +267,20 @@ export const ComposerPrimitiveInput = forwardRef<
267
267
 
268
268
  const handlePaste = async (e: ClipboardEvent<HTMLTextAreaElement>) => {
269
269
  if (!addAttachmentOnPaste) return;
270
- const threadCapabilities = aui.thread().getState().capabilities;
270
+ const threadCapabilities = aui.thread.getState().capabilities;
271
271
  const files = Array.from(e.clipboardData?.files || []);
272
272
 
273
273
  if (threadCapabilities.attachments && files.length > 0) {
274
- try {
275
- e.preventDefault();
276
- await Promise.all(
277
- files.map((file) => aui.composer().addAttachment(file)),
278
- );
279
- } catch (error) {
280
- console.error("Error adding attachment:", error);
281
- }
274
+ e.preventDefault();
275
+ await Promise.all(
276
+ files.map(async (file) => {
277
+ try {
278
+ await aui.composer.addAttachment(file);
279
+ } catch {
280
+ // The composer runtime emits composer.attachmentAddError before rejecting.
281
+ }
282
+ }),
283
+ );
282
284
  }
283
285
  };
284
286
 
@@ -317,7 +319,7 @@ export const ComposerPrimitiveInput = forwardRef<
317
319
 
318
320
  useOnScrollToBottom(() => {
319
321
  if (
320
- aui.composer().getState().type === "thread" &&
322
+ aui.composer.getState().type === "thread" &&
321
323
  unstable_focusOnScrollToBottom
322
324
  ) {
323
325
  focus();
@@ -326,7 +328,7 @@ export const ComposerPrimitiveInput = forwardRef<
326
328
 
327
329
  useEffect(() => {
328
330
  if (
329
- aui.composer().getState().type !== "thread" ||
331
+ aui.composer.getState().type !== "thread" ||
330
332
  !unstable_focusOnRunStart
331
333
  )
332
334
  return undefined;
@@ -336,7 +338,7 @@ export const ComposerPrimitiveInput = forwardRef<
336
338
 
337
339
  useEffect(() => {
338
340
  if (
339
- aui.composer().getState().type !== "thread" ||
341
+ aui.composer.getState().type !== "thread" ||
340
342
  !unstable_focusOnThreadSwitched
341
343
  )
342
344
  return undefined;
@@ -360,7 +362,7 @@ export const ComposerPrimitiveInput = forwardRef<
360
362
  onChange: composeEventHandlers(
361
363
  onChange,
362
364
  (e: React.ChangeEvent<HTMLTextAreaElement>) => {
363
- if (!aui.composer().getState().isEditing) return;
365
+ if (!aui.composer.getState().isEditing) return;
364
366
  const nativeIsComposing =
365
367
  (e.nativeEvent as { isComposing?: boolean }).isComposing === true;
366
368
  // recover stuck compositionRef when the browser drops compositionend
@@ -370,7 +372,7 @@ export const ComposerPrimitiveInput = forwardRef<
370
372
  const isComposing = nativeIsComposing || compositionRef.current;
371
373
  // keep controlled value in sync mid-IME so react does not reset the textarea to a stale value
372
374
  flushTapSync(() => {
373
- aui.composer().setText(e.target.value);
375
+ aui.composer.setText(e.target.value);
374
376
  });
375
377
  if (isComposing) return;
376
378
  const pos = e.target.selectionStart ?? e.target.value.length;
@@ -394,10 +396,10 @@ export const ComposerPrimitiveInput = forwardRef<
394
396
  .onCompositionEnd,
395
397
  (e: React.CompositionEvent<HTMLTextAreaElement>) => {
396
398
  compositionRef.current = false;
397
- if (!aui.composer().getState().isEditing) return;
399
+ if (!aui.composer.getState().isEditing) return;
398
400
  const target = e.target as HTMLTextAreaElement;
399
401
  flushTapSync(() => {
400
- aui.composer().setText(target.value);
402
+ aui.composer.setText(target.value);
401
403
  });
402
404
  const pos = target.selectionStart ?? target.value.length;
403
405
  if (pluginRegistry) {
@@ -93,7 +93,7 @@ export const ComposerPrimitiveQuoteDismiss = forwardRef<
93
93
  >(({ onClick, ...props }, forwardedRef) => {
94
94
  const aui = useAui();
95
95
  const handleDismiss = useCallback(() => {
96
- aui.composer().setQuote(undefined);
96
+ aui.composer.setQuote(undefined);
97
97
  }, [aui]);
98
98
 
99
99
  return (
@@ -14,7 +14,7 @@ const useComposerStopDictation = () => {
14
14
  const isDictating = useAuiState((s) => s.composer.dictation != null);
15
15
 
16
16
  const callback = useCallback(() => {
17
- aui.composer().stopDictation();
17
+ aui.composer.stopDictation();
18
18
  }, [aui]);
19
19
 
20
20
  if (!isDictating) return null;
@@ -72,7 +72,7 @@ const useTriggerSelectionResource = ({
72
72
  return;
73
73
  }
74
74
 
75
- const currentText = aui.composer().getState().text;
75
+ const currentText = aui.composer.getState().text;
76
76
  const before = currentText.slice(0, trigger.offset);
77
77
  const after = currentText.slice(
78
78
  trigger.offset + triggerChar.length + trigger.query.length,
@@ -80,11 +80,9 @@ const useTriggerSelectionResource = ({
80
80
 
81
81
  const insertDirective = () => {
82
82
  const directive = behavior.formatter.serialize(item);
83
- aui
84
- .composer()
85
- .setText(
86
- before + directive + (after.startsWith(" ") ? after : ` ${after}`),
87
- );
83
+ aui.composer.setText(
84
+ before + directive + (after.startsWith(" ") ? after : ` ${after}`),
85
+ );
88
86
  };
89
87
 
90
88
  if (behavior.kind === "directive") {
@@ -92,9 +90,9 @@ const useTriggerSelectionResource = ({
92
90
  behavior.onInserted?.(item);
93
91
  } else {
94
92
  if (behavior.removeOnExecute) {
95
- aui
96
- .composer()
97
- .setText(before + (after.startsWith(" ") ? after.slice(1) : after));
93
+ aui.composer.setText(
94
+ before + (after.startsWith(" ") ? after.slice(1) : after),
95
+ );
98
96
  } else {
99
97
  // Leave directive chip in the composer as an audit trail
100
98
  insertDirective();
@@ -230,11 +230,9 @@ const ToolUIDisplay = ({
230
230
  }: {
231
231
  Fallback: ToolCallMessagePartComponent | undefined;
232
232
  } & ToolCallMessagePartProps) => {
233
- const Render = useAuiState((s) => {
234
- const Render = s.tools.tools[props.toolName] ?? Fallback;
235
- if (Array.isArray(Render)) return Render[0] ?? Fallback;
236
- return Render;
237
- });
233
+ const Render = useAuiState(
234
+ (s) => s.tools.toolUIs[props.toolName]?.[0]?.render ?? Fallback,
235
+ );
238
236
  if (!Render) return null;
239
237
  return <Render {...props} />;
240
238
  };
@@ -292,9 +290,9 @@ const MessagePartComponent: FC<MessagePartComponentProps> = ({
292
290
 
293
291
  const type = part.type;
294
292
  if (type === "tool-call") {
295
- const addResult = aui.part().addToolResult;
296
- const resume = aui.part().resumeToolCall;
297
- const respondToApproval = aui.part().respondToToolApproval;
293
+ const addResult = aui.part.addToolResult;
294
+ const resume = aui.part.resumeToolCall;
295
+ const respondToApproval = aui.part.respondToToolApproval;
298
296
  if ("Override" in tools)
299
297
  return (
300
298
  <tools.Override
@@ -23,7 +23,7 @@ type ThreadViewportStore = NonNullable<
23
23
 
24
24
  const useIsHoveringRef = () => {
25
25
  const aui = useAui();
26
- const message = useAuiState(() => aui.message());
26
+ const message = useAuiState(() => aui.message);
27
27
 
28
28
  const callbackRef = useCallback(
29
29
  (el: HTMLElement) => {
@@ -12,7 +12,7 @@ const useQueueItemRemove = () => {
12
12
  const aui = useAui();
13
13
 
14
14
  const callback = useCallback(() => {
15
- aui.queueItem().remove();
15
+ aui.queueItem.remove();
16
16
  }, [aui]);
17
17
 
18
18
  return callback;
@@ -12,7 +12,7 @@ const useQueueItemSteer = () => {
12
12
  const aui = useAui();
13
13
 
14
14
  const callback = useCallback(() => {
15
- aui.queueItem().steer();
15
+ aui.queueItem.steer();
16
16
  }, [aui]);
17
17
 
18
18
  return callback;
@@ -40,7 +40,7 @@ export const SelectionToolbarPrimitiveQuote = forwardRef<
40
40
  const handleClick = useCallback(() => {
41
41
  if (!info) return;
42
42
 
43
- aui.thread().composer().setQuote({
43
+ aui.thread.composer().setQuote({
44
44
  text: info.text,
45
45
  messageId: info.messageId,
46
46
  });
@@ -34,24 +34,24 @@ const useSuggestionTrigger = ({
34
34
  const resolvedSend = send ?? false;
35
35
 
36
36
  const callback = useCallback(() => {
37
- const isRunning = aui.thread().getState().isRunning;
37
+ const isRunning = aui.thread.getState().isRunning;
38
38
 
39
39
  if (resolvedSend && !isRunning) {
40
- aui.thread().append({
40
+ aui.thread.append({
41
41
  content: [{ type: "text", text: prompt }],
42
- runConfig: aui.composer().getState().runConfig,
42
+ runConfig: aui.composer.getState().runConfig,
43
43
  });
44
44
  if (clearComposer) {
45
- aui.composer().setText("");
45
+ aui.composer.setText("");
46
46
  }
47
47
  } else {
48
48
  if (clearComposer) {
49
- aui.composer().setText(prompt);
49
+ aui.composer.setText(prompt);
50
50
  } else {
51
- const currentText = aui.composer().getState().text;
52
- aui
53
- .composer()
54
- .setText(currentText.trim() ? `${currentText} ${prompt}` : prompt);
51
+ const currentText = aui.composer.getState().text;
52
+ aui.composer.setText(
53
+ currentText.trim() ? `${currentText} ${prompt}` : prompt,
54
+ );
55
55
  }
56
56
  }
57
57
  }, [aui, resolvedSend, clearComposer, prompt]);
@@ -0,0 +1,212 @@
1
+ // @vitest-environment jsdom
2
+
3
+ // Data part rendering contract: global renderers > global fallbacks >
4
+ // inline `components.data` config, exercised through the real store.
5
+
6
+ import { render, screen, waitFor } from "@testing-library/react";
7
+ import {
8
+ useEffect,
9
+ type FC,
10
+ type PropsWithChildren,
11
+ type ReactNode,
12
+ } from "react";
13
+ import { describe, expect, it } from "vitest";
14
+ import { useAui } from "@assistant-ui/store";
15
+ import { AssistantRuntimeProvider } from "../context";
16
+ import * as MessagePrimitive from "../primitives/message";
17
+ import * as ThreadPrimitive from "../primitives/thread";
18
+ import { useLocalRuntime } from "../legacy-runtime/runtime-cores/local/useLocalRuntime";
19
+ import {
20
+ useAssistantDataUI,
21
+ type ChatModelAdapter,
22
+ type DataMessagePartComponent,
23
+ type ThreadMessageLike,
24
+ } from "../index";
25
+
26
+ const noOpAdapter: ChatModelAdapter = {
27
+ async *run() {},
28
+ };
29
+
30
+ const dataMessages = (name: string): ThreadMessageLike[] => [
31
+ {
32
+ role: "assistant",
33
+ content: [{ type: "data", name, data: { value: 42 } }],
34
+ status: { type: "complete", reason: "stop" },
35
+ },
36
+ ];
37
+
38
+ const labeled =
39
+ (label: string): DataMessagePartComponent =>
40
+ ({ name, data }) => (
41
+ <span data-testid={label}>
42
+ {name}:{(data as { value: number }).value}
43
+ </span>
44
+ );
45
+
46
+ const RuntimeProvider: FC<
47
+ PropsWithChildren<{ messages: ThreadMessageLike[] }>
48
+ > = ({ children, messages }) => {
49
+ const runtime = useLocalRuntime(noOpAdapter, { initialMessages: messages });
50
+ return (
51
+ <AssistantRuntimeProvider runtime={runtime}>
52
+ {children}
53
+ </AssistantRuntimeProvider>
54
+ );
55
+ };
56
+
57
+ const GlobalDataUI: FC<{ name: string; render: DataMessagePartComponent }> = ({
58
+ name,
59
+ render,
60
+ }) => {
61
+ useAssistantDataUI({ name, render });
62
+ return null;
63
+ };
64
+
65
+ const GlobalFallbackDataUI: FC<{ render: DataMessagePartComponent }> = ({
66
+ render,
67
+ }) => {
68
+ const aui = useAui();
69
+ useEffect(() => aui.dataRenderers.setFallbackDataUI(render), [aui, render]);
70
+ return null;
71
+ };
72
+
73
+ const renderThread = (
74
+ messages: ThreadMessageLike[],
75
+ Message: FC,
76
+ extra?: ReactNode,
77
+ ) =>
78
+ render(
79
+ <RuntimeProvider messages={messages}>
80
+ {extra}
81
+ <ThreadPrimitive.Messages components={{ Message }} />
82
+ </RuntimeProvider>,
83
+ );
84
+
85
+ describe("data part rendering", () => {
86
+ it("renders a data part via the inline by_name config with its name and data", async () => {
87
+ const Message: FC = () => (
88
+ <MessagePrimitive.Parts
89
+ components={{ data: { by_name: { chart: labeled("inline") } } }}
90
+ />
91
+ );
92
+
93
+ renderThread(dataMessages("chart"), Message);
94
+
95
+ await waitFor(() => {
96
+ expect(screen.getByTestId("inline").textContent).toBe("chart:42");
97
+ });
98
+ });
99
+
100
+ it("uses the inline Fallback for unmatched names and renders nothing without one", async () => {
101
+ const Message: FC = () => (
102
+ <div data-testid="host">
103
+ <MessagePrimitive.Parts
104
+ components={{
105
+ data: { by_name: { other: labeled("named") } },
106
+ }}
107
+ />
108
+ </div>
109
+ );
110
+
111
+ renderThread(dataMessages("chart"), Message);
112
+ await waitFor(() => {
113
+ expect(screen.getByTestId("host").textContent).toBe("");
114
+ });
115
+
116
+ const MessageWithFallback: FC = () => (
117
+ <MessagePrimitive.Parts
118
+ components={{
119
+ data: {
120
+ by_name: { other: labeled("named") },
121
+ Fallback: labeled("inline-fallback"),
122
+ },
123
+ }}
124
+ />
125
+ );
126
+ renderThread(dataMessages("chart"), MessageWithFallback);
127
+ await waitFor(() => {
128
+ expect(screen.getByTestId("inline-fallback").textContent).toBe(
129
+ "chart:42",
130
+ );
131
+ });
132
+ });
133
+
134
+ it("prefers a globally registered renderer over the inline by_name config", async () => {
135
+ const Message: FC = () => (
136
+ <MessagePrimitive.Parts
137
+ components={{ data: { by_name: { chart: labeled("inline") } } }}
138
+ />
139
+ );
140
+
141
+ renderThread(
142
+ dataMessages("chart"),
143
+ Message,
144
+ <GlobalDataUI name="chart" render={labeled("global")} />,
145
+ );
146
+
147
+ await waitFor(() => {
148
+ expect(screen.getByTestId("global").textContent).toBe("chart:42");
149
+ });
150
+ expect(screen.queryByTestId("inline")).toBeNull();
151
+ });
152
+
153
+ it("resolves global fallbacks above inline fallbacks but below named matches", async () => {
154
+ const Message: FC = () => (
155
+ <MessagePrimitive.Parts
156
+ components={{ data: { Fallback: labeled("inline-fallback") } }}
157
+ />
158
+ );
159
+
160
+ renderThread(
161
+ dataMessages("chart"),
162
+ Message,
163
+ <GlobalFallbackDataUI render={labeled("global-fallback")} />,
164
+ );
165
+ await waitFor(() => {
166
+ expect(screen.getByTestId("global-fallback").textContent).toBe(
167
+ "chart:42",
168
+ );
169
+ });
170
+ expect(screen.queryByTestId("inline-fallback")).toBeNull();
171
+
172
+ renderThread(
173
+ dataMessages("chart"),
174
+ Message,
175
+ <>
176
+ <GlobalFallbackDataUI render={labeled("global-fallback-2")} />
177
+ <GlobalDataUI name="chart" render={labeled("global-named")} />
178
+ </>,
179
+ );
180
+ await waitFor(() => {
181
+ expect(screen.getByTestId("global-named").textContent).toBe("chart:42");
182
+ });
183
+ });
184
+
185
+ it("restores the inline renderer when the registering component unmounts", async () => {
186
+ const Message: FC = () => (
187
+ <MessagePrimitive.Parts
188
+ components={{ data: { by_name: { chart: labeled("inline") } } }}
189
+ />
190
+ );
191
+
192
+ const view = render(
193
+ <RuntimeProvider messages={dataMessages("chart")}>
194
+ <GlobalDataUI name="chart" render={labeled("global")} />
195
+ <ThreadPrimitive.Messages components={{ Message }} />
196
+ </RuntimeProvider>,
197
+ );
198
+ await waitFor(() => {
199
+ expect(screen.getByTestId("global")).toBeTruthy();
200
+ });
201
+
202
+ view.rerender(
203
+ <RuntimeProvider messages={dataMessages("chart")}>
204
+ <ThreadPrimitive.Messages components={{ Message }} />
205
+ </RuntimeProvider>,
206
+ );
207
+ await waitFor(() => {
208
+ expect(screen.getByTestId("inline").textContent).toBe("chart:42");
209
+ });
210
+ expect(screen.queryByTestId("global")).toBeNull();
211
+ });
212
+ });
@@ -67,17 +67,7 @@ const wrapInRuntimeAdapterProvider = (
67
67
  };
68
68
 
69
69
  describe("RemoteThreadListAdapter.unstable_Provider", () => {
70
- it("makes RuntimeAdapterProvider context visible to the runtime hook", async () => {
71
- const capture: { adapters: CapturedAdapters } = { adapters: null };
72
- const adapter = makeAdapter({
73
- unstable_Provider: wrapInRuntimeAdapterProvider(dummyHistory),
74
- });
75
- await renderAndWaitForBinder(adapter, capture);
76
-
77
- expect(capture.adapters?.history).toBe(dummyHistory);
78
- });
79
-
80
- it("preserves modelContext from the outer runtime-core RuntimeAdapterProvider", async () => {
70
+ it("makes Provider context visible to the runtime hook while preserving outer modelContext", async () => {
81
71
  const capture: { adapters: CapturedAdapters } = { adapters: null };
82
72
  const adapter = makeAdapter({
83
73
  unstable_Provider: wrapInRuntimeAdapterProvider(dummyHistory),
@@ -6,9 +6,9 @@ import { afterEach, describe, expect, it, vi } from "vitest";
6
6
  import { useRemoteThreadListRuntime } from "@assistant-ui/core/react";
7
7
  import { makeAdapter } from "./remote-thread-list-test-helpers";
8
8
  import type { AssistantRuntime } from "@assistant-ui/core";
9
+ import { useAui } from "@assistant-ui/store";
9
10
  import { AssistantRuntimeProvider } from "../context";
10
11
  import { useLocalRuntime } from "../legacy-runtime/runtime-cores/local/useLocalRuntime";
11
- import { useAssistantRuntime } from "../legacy-runtime/hooks/AssistantContext";
12
12
  import type { ChatModelAdapter, RemoteThreadListAdapter } from "../index";
13
13
 
14
14
  const noOpAdapter: ChatModelAdapter = {
@@ -34,8 +34,8 @@ const RuntimeProvider: FC<
34
34
  const RuntimeCapture: FC<{
35
35
  runtimeRef: { current: AssistantRuntime | null };
36
36
  }> = ({ runtimeRef }) => {
37
- const runtime = useAssistantRuntime();
38
- runtimeRef.current = runtime;
37
+ const aui = useAui();
38
+ runtimeRef.current = aui.threads.__internal_getAssistantRuntime?.() ?? null;
39
39
  return null;
40
40
  };
41
41