@assistant-ui/react 0.14.16 → 0.14.19

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 (380) hide show
  1. package/dist/client/ExternalThread.d.ts +5 -3
  2. package/dist/client/ExternalThread.d.ts.map +1 -1
  3. package/dist/client/ExternalThread.js +745 -255
  4. package/dist/client/ExternalThread.js.map +1 -1
  5. package/dist/client/InMemoryThreadList.d.ts +1 -1
  6. package/dist/client/InMemoryThreadList.d.ts.map +1 -1
  7. package/dist/client/InMemoryThreadList.js +299 -113
  8. package/dist/client/InMemoryThreadList.js.map +1 -1
  9. package/dist/client/SingleThreadList.d.ts +1 -6
  10. package/dist/client/SingleThreadList.d.ts.map +1 -1
  11. package/dist/client/SingleThreadList.js +143 -55
  12. package/dist/client/SingleThreadList.js.map +1 -1
  13. package/dist/context/ReadonlyStore.js.map +1 -1
  14. package/dist/context/providers/MessageProvider.js +38 -5
  15. package/dist/context/providers/MessageProvider.js.map +1 -1
  16. package/dist/context/providers/ThreadViewportProvider.js +76 -20
  17. package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
  18. package/dist/context/react/ThreadViewportContext.js.map +1 -1
  19. package/dist/context/react/utils/createContextHook.js.map +1 -1
  20. package/dist/context/react/utils/createContextStoreHook.js +17 -2
  21. package/dist/context/react/utils/createContextStoreHook.js.map +1 -1
  22. package/dist/context/react/utils/createStateHookForRuntime.js.map +1 -1
  23. package/dist/context/react/utils/ensureBinding.js.map +1 -1
  24. package/dist/context/react/utils/useRuntimeState.js +18 -2
  25. package/dist/context/react/utils/useRuntimeState.js.map +1 -1
  26. package/dist/context/stores/ThreadViewport.js.map +1 -1
  27. package/dist/devtools/DevToolsHooks.js.map +1 -1
  28. package/dist/hooks/useMessageQuote.js.map +1 -1
  29. package/dist/hooks/useMessageTiming.js +4 -1
  30. package/dist/hooks/useMessageTiming.js.map +1 -1
  31. package/dist/hooks/useToolCallElapsed.d.ts +23 -0
  32. package/dist/hooks/useToolCallElapsed.d.ts.map +1 -0
  33. package/dist/hooks/useToolCallElapsed.js +72 -0
  34. package/dist/hooks/useToolCallElapsed.js.map +1 -0
  35. package/dist/index.d.ts +6 -2
  36. package/dist/index.js +5 -1
  37. package/dist/internal.js.map +1 -1
  38. package/dist/legacy-runtime/AssistantRuntimeProvider.js +46 -10
  39. package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
  40. package/dist/legacy-runtime/cloud/auiV0.js.map +1 -1
  41. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js +27 -6
  42. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js.map +1 -1
  43. package/dist/legacy-runtime/hooks/AssistantContext.js +13 -2
  44. package/dist/legacy-runtime/hooks/AssistantContext.js.map +1 -1
  45. package/dist/legacy-runtime/hooks/AttachmentContext.js +9 -1
  46. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +1 -1
  47. package/dist/legacy-runtime/hooks/ComposerContext.js +9 -1
  48. package/dist/legacy-runtime/hooks/ComposerContext.js.map +1 -1
  49. package/dist/legacy-runtime/hooks/MessageContext.js +12 -2
  50. package/dist/legacy-runtime/hooks/MessageContext.js.map +1 -1
  51. package/dist/legacy-runtime/hooks/MessagePartContext.js +9 -1
  52. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +1 -1
  53. package/dist/legacy-runtime/hooks/ThreadContext.js +33 -5
  54. package/dist/legacy-runtime/hooks/ThreadContext.js.map +1 -1
  55. package/dist/legacy-runtime/hooks/ThreadListItemContext.js +9 -1
  56. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +1 -1
  57. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +3 -3
  58. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +1 -1
  59. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +71 -31
  60. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +1 -1
  61. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +1 -1
  62. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +24 -16
  63. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  64. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +17 -12
  65. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -1
  66. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +17 -3
  67. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js.map +1 -1
  68. package/dist/mcp-apps/McpAppRenderer.d.ts +2 -10
  69. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  70. package/dist/mcp-apps/McpAppRenderer.js +9 -8
  71. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  72. package/dist/mcp-apps/McpAppsRemoteHost.d.ts +1 -8
  73. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  74. package/dist/mcp-apps/McpAppsRemoteHost.js +6 -5
  75. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  76. package/dist/mcp-apps/app-frame.js +33 -14
  77. package/dist/mcp-apps/app-frame.js.map +1 -1
  78. package/dist/mcp-apps/bridge.js.map +1 -1
  79. package/dist/mcp-apps/types.js.map +1 -1
  80. package/dist/mcp-apps/utils.js.map +1 -1
  81. package/dist/model-context/frame/useAssistantFrameHost.js +32 -14
  82. package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
  83. package/dist/model-context/makeAssistantVisible.js +64 -26
  84. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  85. package/dist/primitives/actionBar/ActionBarCopy.js +94 -20
  86. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  87. package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
  88. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +105 -37
  89. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  90. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +60 -11
  91. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  92. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +60 -11
  93. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  94. package/dist/primitives/actionBar/ActionBarInteractionContext.js +3 -1
  95. package/dist/primitives/actionBar/ActionBarInteractionContext.js.map +1 -1
  96. package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
  97. package/dist/primitives/actionBar/ActionBarRoot.js +84 -25
  98. package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
  99. package/dist/primitives/actionBar/ActionBarSpeak.js.map +1 -1
  100. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +45 -14
  101. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
  102. package/dist/primitives/actionBar/useActionBarFloatStatus.js +22 -10
  103. package/dist/primitives/actionBar/useActionBarFloatStatus.js.map +1 -1
  104. package/dist/primitives/actionBar.js.map +1 -1
  105. package/dist/primitives/actionBarMore/ActionBarMoreContent.js +44 -7
  106. package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
  107. package/dist/primitives/actionBarMore/ActionBarMoreItem.js +28 -6
  108. package/dist/primitives/actionBarMore/ActionBarMoreItem.js.map +1 -1
  109. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js +103 -36
  110. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
  111. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js +28 -6
  112. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js.map +1 -1
  113. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js +28 -6
  114. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js.map +1 -1
  115. package/dist/primitives/actionBarMore/scope.js.map +1 -1
  116. package/dist/primitives/actionBarMore.js.map +1 -1
  117. package/dist/primitives/assistantModal/AssistantModalAnchor.js +27 -6
  118. package/dist/primitives/assistantModal/AssistantModalAnchor.js.map +1 -1
  119. package/dist/primitives/assistantModal/AssistantModalContent.js +71 -10
  120. package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
  121. package/dist/primitives/assistantModal/AssistantModalRoot.js +93 -26
  122. package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
  123. package/dist/primitives/assistantModal/AssistantModalTrigger.js +27 -6
  124. package/dist/primitives/assistantModal/AssistantModalTrigger.js.map +1 -1
  125. package/dist/primitives/assistantModal/scope.js.map +1 -1
  126. package/dist/primitives/assistantModal.js.map +1 -1
  127. package/dist/primitives/attachment/AttachmentName.js +13 -1
  128. package/dist/primitives/attachment/AttachmentName.js.map +1 -1
  129. package/dist/primitives/attachment/AttachmentRemove.js +11 -4
  130. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  131. package/dist/primitives/attachment/AttachmentRoot.js +13 -4
  132. package/dist/primitives/attachment/AttachmentRoot.js.map +1 -1
  133. package/dist/primitives/attachment/AttachmentThumb.js +20 -9
  134. package/dist/primitives/attachment/AttachmentThumb.js.map +1 -1
  135. package/dist/primitives/attachment.js.map +1 -1
  136. package/dist/primitives/branchPicker/BranchPickerCount.js +14 -2
  137. package/dist/primitives/branchPicker/BranchPickerCount.js.map +1 -1
  138. package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
  139. package/dist/primitives/branchPicker/BranchPickerNumber.js +14 -2
  140. package/dist/primitives/branchPicker/BranchPickerNumber.js.map +1 -1
  141. package/dist/primitives/branchPicker/BranchPickerPrevious.js.map +1 -1
  142. package/dist/primitives/branchPicker/BranchPickerRoot.js +34 -6
  143. package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
  144. package/dist/primitives/branchPicker.js.map +1 -1
  145. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +16 -5
  146. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  147. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js +13 -4
  148. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js.map +1 -1
  149. package/dist/primitives/chainOfThought.js.map +1 -1
  150. package/dist/primitives/composer/ComposerAddAttachment.js +37 -24
  151. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  152. package/dist/primitives/composer/ComposerAttachmentDropzone.js +124 -49
  153. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  154. package/dist/primitives/composer/ComposerCancel.js.map +1 -1
  155. package/dist/primitives/composer/ComposerDictate.js.map +1 -1
  156. package/dist/primitives/composer/ComposerDictationTranscript.js +32 -7
  157. package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
  158. package/dist/primitives/composer/ComposerInput.js +29 -29
  159. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  160. package/dist/primitives/composer/ComposerInputPluginContext.js +71 -25
  161. package/dist/primitives/composer/ComposerInputPluginContext.js.map +1 -1
  162. package/dist/primitives/composer/ComposerQuote.js +92 -23
  163. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  164. package/dist/primitives/composer/ComposerRoot.js +45 -11
  165. package/dist/primitives/composer/ComposerRoot.js.map +1 -1
  166. package/dist/primitives/composer/ComposerSend.js +9 -2
  167. package/dist/primitives/composer/ComposerSend.js.map +1 -1
  168. package/dist/primitives/composer/ComposerStopDictation.js +15 -5
  169. package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
  170. package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
  171. package/dist/primitives/composer/trigger/TriggerPopover.js +215 -75
  172. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  173. package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
  174. package/dist/primitives/composer/trigger/TriggerPopoverBack.js +35 -7
  175. package/dist/primitives/composer/trigger/TriggerPopoverBack.js.map +1 -1
  176. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +134 -28
  177. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
  178. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
  179. package/dist/primitives/composer/trigger/TriggerPopoverItems.js +132 -28
  180. package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
  181. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts +2 -10
  182. package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
  183. package/dist/primitives/composer/trigger/TriggerPopoverResource.js +126 -53
  184. package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
  185. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +181 -78
  186. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
  187. package/dist/primitives/composer/trigger/detectTrigger.js.map +1 -1
  188. package/dist/primitives/composer/trigger/index.js.map +1 -1
  189. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts +2 -6
  190. package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
  191. package/dist/primitives/composer/trigger/triggerDetectionResource.js +30 -15
  192. package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
  193. package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts +2 -17
  194. package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
  195. package/dist/primitives/composer/trigger/triggerKeyboardResource.js +117 -59
  196. package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
  197. package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts +2 -10
  198. package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
  199. package/dist/primitives/composer/trigger/triggerNavigationResource.js +204 -71
  200. package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
  201. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts +2 -10
  202. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  203. package/dist/primitives/composer/trigger/triggerSelectionResource.js +51 -14
  204. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  205. package/dist/primitives/composer.js.map +1 -1
  206. package/dist/primitives/dropdownMenuRenderPrimitives.js.map +1 -1
  207. package/dist/primitives/error/ErrorMessage.js +28 -6
  208. package/dist/primitives/error/ErrorMessage.js.map +1 -1
  209. package/dist/primitives/error/ErrorRoot.js +14 -5
  210. package/dist/primitives/error/ErrorRoot.js.map +1 -1
  211. package/dist/primitives/error.js.map +1 -1
  212. package/dist/primitives/message/MessageError.js +2 -1
  213. package/dist/primitives/message/MessageError.js.map +1 -1
  214. package/dist/primitives/message/MessageIf.js +50 -20
  215. package/dist/primitives/message/MessageIf.js.map +1 -1
  216. package/dist/primitives/message/MessageParts.js +41 -7
  217. package/dist/primitives/message/MessageParts.js.map +1 -1
  218. package/dist/primitives/message/MessagePartsGrouped.js +399 -94
  219. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  220. package/dist/primitives/message/MessageRoot.js +197 -65
  221. package/dist/primitives/message/MessageRoot.js.map +1 -1
  222. package/dist/primitives/message.js.map +1 -1
  223. package/dist/primitives/messagePart/MessagePartImage.js +15 -5
  224. package/dist/primitives/messagePart/MessagePartImage.js.map +1 -1
  225. package/dist/primitives/messagePart/MessagePartText.d.ts +5 -2
  226. package/dist/primitives/messagePart/MessagePartText.d.ts.map +1 -1
  227. package/dist/primitives/messagePart/MessagePartText.js +35 -7
  228. package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
  229. package/dist/primitives/messagePart/useMessagePartData.js +5 -4
  230. package/dist/primitives/messagePart/useMessagePartData.js.map +1 -1
  231. package/dist/primitives/messagePart/useMessagePartFile.js +5 -4
  232. package/dist/primitives/messagePart/useMessagePartFile.js.map +1 -1
  233. package/dist/primitives/messagePart/useMessagePartImage.js +5 -4
  234. package/dist/primitives/messagePart/useMessagePartImage.js.map +1 -1
  235. package/dist/primitives/messagePart/useMessagePartReasoning.js +5 -4
  236. package/dist/primitives/messagePart/useMessagePartReasoning.js.map +1 -1
  237. package/dist/primitives/messagePart/useMessagePartSource.js +5 -4
  238. package/dist/primitives/messagePart/useMessagePartSource.js.map +1 -1
  239. package/dist/primitives/messagePart/useMessagePartText.js +5 -4
  240. package/dist/primitives/messagePart/useMessagePartText.js.map +1 -1
  241. package/dist/primitives/messagePart.js.map +1 -1
  242. package/dist/primitives/queueItem/QueueItemRemove.js +11 -4
  243. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  244. package/dist/primitives/queueItem/QueueItemSteer.js +11 -4
  245. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  246. package/dist/primitives/queueItem/QueueItemText.js +20 -6
  247. package/dist/primitives/queueItem/QueueItemText.js.map +1 -1
  248. package/dist/primitives/queueItem.js.map +1 -1
  249. package/dist/primitives/reasoning/useScrollLock.js +61 -34
  250. package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
  251. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +56 -16
  252. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  253. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +120 -59
  254. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  255. package/dist/primitives/selectionToolbar.js.map +1 -1
  256. package/dist/primitives/suggestion/SuggestionDescription.js +20 -6
  257. package/dist/primitives/suggestion/SuggestionDescription.js.map +1 -1
  258. package/dist/primitives/suggestion/SuggestionTitle.js +20 -6
  259. package/dist/primitives/suggestion/SuggestionTitle.js.map +1 -1
  260. package/dist/primitives/suggestion/SuggestionTrigger.js +39 -26
  261. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  262. package/dist/primitives/suggestion.js.map +1 -1
  263. package/dist/primitives/thread/ThreadEmpty.js +6 -2
  264. package/dist/primitives/thread/ThreadEmpty.js.map +1 -1
  265. package/dist/primitives/thread/ThreadIf.js +32 -10
  266. package/dist/primitives/thread/ThreadIf.js.map +1 -1
  267. package/dist/primitives/thread/ThreadRoot.js +13 -4
  268. package/dist/primitives/thread/ThreadRoot.js.map +1 -1
  269. package/dist/primitives/thread/ThreadScrollToBottom.js +24 -6
  270. package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
  271. package/dist/primitives/thread/ThreadSuggestion.js +18 -6
  272. package/dist/primitives/thread/ThreadSuggestion.js.map +1 -1
  273. package/dist/primitives/thread/ThreadViewport.js +185 -47
  274. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  275. package/dist/primitives/thread/ThreadViewportFooter.js +22 -9
  276. package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
  277. package/dist/primitives/thread/topAnchor/computeTopAnchorSlack.js.map +1 -1
  278. package/dist/primitives/thread/topAnchor/createReserveObservers.js.map +1 -1
  279. package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.js.map +1 -1
  280. package/dist/primitives/thread/topAnchor/topAnchorTurn.js.map +1 -1
  281. package/dist/primitives/thread/topAnchor/topAnchorUtils.js.map +1 -1
  282. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +19 -4
  283. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js.map +1 -1
  284. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  285. package/dist/primitives/thread/useThreadViewportAutoScroll.js +21 -16
  286. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  287. package/dist/primitives/thread.js.map +1 -1
  288. package/dist/primitives/threadList/ThreadListLoadMore.js.map +1 -1
  289. package/dist/primitives/threadList/ThreadListNew.js +53 -11
  290. package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
  291. package/dist/primitives/threadList/ThreadListRoot.js +13 -4
  292. package/dist/primitives/threadList/ThreadListRoot.js.map +1 -1
  293. package/dist/primitives/threadList.js.map +1 -1
  294. package/dist/primitives/threadListItem/ThreadListItemArchive.js.map +1 -1
  295. package/dist/primitives/threadListItem/ThreadListItemDelete.js.map +1 -1
  296. package/dist/primitives/threadListItem/ThreadListItemRoot.js +26 -7
  297. package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
  298. package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
  299. package/dist/primitives/threadListItem/ThreadListItemUnarchive.js.map +1 -1
  300. package/dist/primitives/threadListItem.js.map +1 -1
  301. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +44 -7
  302. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
  303. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js +28 -6
  304. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js.map +1 -1
  305. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +25 -5
  306. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
  307. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js +28 -6
  308. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js.map +1 -1
  309. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +28 -6
  310. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js.map +1 -1
  311. package/dist/primitives/threadListItemMore/scope.js.map +1 -1
  312. package/dist/primitives/threadListItemMore.js.map +1 -1
  313. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  314. package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
  315. package/dist/tests/setup.js.map +1 -1
  316. package/dist/unstable/useComposerInputHistory.d.ts +30 -0
  317. package/dist/unstable/useComposerInputHistory.d.ts.map +1 -0
  318. package/dist/unstable/useComposerInputHistory.js +117 -0
  319. package/dist/unstable/useComposerInputHistory.js.map +1 -0
  320. package/dist/unstable/useMentionAdapter.js.map +1 -1
  321. package/dist/unstable/useMessageStallDetection.d.ts +29 -0
  322. package/dist/unstable/useMessageStallDetection.d.ts.map +1 -0
  323. package/dist/unstable/useMessageStallDetection.js +69 -0
  324. package/dist/unstable/useMessageStallDetection.js.map +1 -0
  325. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  326. package/dist/utils/Primitive.js +57 -12
  327. package/dist/utils/Primitive.js.map +1 -1
  328. package/dist/utils/createActionButton.js +23 -7
  329. package/dist/utils/createActionButton.js.map +1 -1
  330. package/dist/utils/getSelectionMessageId.js.map +1 -1
  331. package/dist/utils/hooks/useManagedRef.js +16 -8
  332. package/dist/utils/hooks/useManagedRef.js.map +1 -1
  333. package/dist/utils/hooks/useMediaQuery.js +25 -10
  334. package/dist/utils/hooks/useMediaQuery.js.map +1 -1
  335. package/dist/utils/hooks/useOnResizeContent.js +29 -19
  336. package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
  337. package/dist/utils/hooks/useOnScrollToBottom.js +20 -4
  338. package/dist/utils/hooks/useOnScrollToBottom.js.map +1 -1
  339. package/dist/utils/hooks/useSizeHandle.js +23 -15
  340. package/dist/utils/hooks/useSizeHandle.js.map +1 -1
  341. package/dist/utils/json/is-json-equal.js.map +1 -1
  342. package/dist/utils/json/is-json.js.map +1 -1
  343. package/dist/utils/smooth/SmoothContext.js +41 -11
  344. package/dist/utils/smooth/SmoothContext.js.map +1 -1
  345. package/dist/utils/smooth/useSmooth.d.ts +40 -2
  346. package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
  347. package/dist/utils/smooth/useSmooth.js +52 -13
  348. package/dist/utils/smooth/useSmooth.js.map +1 -1
  349. package/dist/utils/useToolArgsFieldStatus.d.ts +2 -2
  350. package/dist/utils/useToolArgsFieldStatus.d.ts.map +1 -1
  351. package/dist/utils/useToolArgsFieldStatus.js +13 -5
  352. package/dist/utils/useToolArgsFieldStatus.js.map +1 -1
  353. package/package.json +6 -6
  354. package/src/client/ExternalThread.ts +146 -74
  355. package/src/client/InMemoryThreadList.ts +23 -21
  356. package/src/client/SingleThreadList.ts +29 -27
  357. package/src/hooks/useToolCallElapsed.ts +52 -0
  358. package/src/index.ts +19 -0
  359. package/src/mcp-apps/McpAppRenderer.tsx +5 -3
  360. package/src/mcp-apps/McpAppsRemoteHost.ts +5 -3
  361. package/src/primitives/composer/ComposerInput.test.tsx +1 -1
  362. package/src/primitives/composer/ComposerInput.tsx +3 -3
  363. package/src/primitives/composer/trigger/TriggerPopover.tsx +4 -5
  364. package/src/primitives/composer/trigger/TriggerPopoverResource.ts +5 -3
  365. package/src/primitives/composer/trigger/triggerDetectionResource.ts +21 -21
  366. package/src/primitives/composer/trigger/triggerKeyboardResource.test.ts +5 -4
  367. package/src/primitives/composer/trigger/triggerKeyboardResource.ts +99 -101
  368. package/src/primitives/composer/trigger/triggerNavigationResource.ts +92 -98
  369. package/src/primitives/composer/trigger/triggerSelectionResource.ts +76 -76
  370. package/src/primitives/messagePart/MessagePartText.tsx +3 -2
  371. package/src/primitives/reasoning/useScrollLock.ts +25 -2
  372. package/src/primitives/thread/useThreadViewportAutoScroll.ts +8 -0
  373. package/src/tests/external-thread-branches.test.tsx +160 -0
  374. package/src/tests/shouldContinue.test.ts +33 -0
  375. package/src/tests/toolCallTiming.test.tsx +221 -0
  376. package/src/unstable/useComposerInputHistory.test.tsx +201 -0
  377. package/src/unstable/useComposerInputHistory.ts +160 -0
  378. package/src/unstable/useMessageStallDetection.ts +91 -0
  379. package/src/utils/smooth/useSmooth.test.tsx +95 -0
  380. package/src/utils/smooth/useSmooth.ts +82 -10
@@ -0,0 +1,221 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, render, screen } from "@testing-library/react";
4
+ import type { FC, PropsWithChildren } from "react";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
+ import { AssistantRuntimeProvider } from "../context";
7
+ import * as MessagePrimitive from "../primitives/message";
8
+ import * as ThreadPrimitive from "../primitives/thread";
9
+ import { useLocalRuntime } from "../legacy-runtime/runtime-cores/local/useLocalRuntime";
10
+ import type { ChatModelAdapter, ThreadMessageLike } from "../index";
11
+ import {
12
+ useToolCallElapsed,
13
+ unstable_useMessageStallDetection,
14
+ } from "../index";
15
+
16
+ const noOpAdapter: ChatModelAdapter = {
17
+ async *run() {},
18
+ };
19
+
20
+ const toolMessage = (timing?: {
21
+ startedAt: number;
22
+ completedAt?: number;
23
+ }): ThreadMessageLike => ({
24
+ role: "assistant",
25
+ content: [
26
+ {
27
+ type: "tool-call",
28
+ toolCallId: "tc-1",
29
+ toolName: "search",
30
+ args: {},
31
+ argsText: "{}",
32
+ ...(timing !== undefined && { timing }),
33
+ },
34
+ ],
35
+ status: { type: "running" },
36
+ });
37
+
38
+ const ElapsedProbe: FC = () => {
39
+ const elapsedMs = useToolCallElapsed();
40
+ return (
41
+ <span data-testid="elapsed">
42
+ {elapsedMs === undefined ? "none" : String(elapsedMs)}
43
+ </span>
44
+ );
45
+ };
46
+
47
+ const StallProbe: FC = () => {
48
+ const { stalled } = unstable_useMessageStallDetection({ thresholdMs: 2000 });
49
+ return <span data-testid="stalled">{String(stalled)}</span>;
50
+ };
51
+
52
+ const RuntimeProvider: FC<
53
+ PropsWithChildren<{ messages: ThreadMessageLike[] }>
54
+ > = ({ messages, children }) => {
55
+ const runtime = useLocalRuntime(noOpAdapter, { initialMessages: messages });
56
+ return (
57
+ <AssistantRuntimeProvider runtime={runtime}>
58
+ {children}
59
+ </AssistantRuntimeProvider>
60
+ );
61
+ };
62
+
63
+ const renderHarness = async (messages: ThreadMessageLike[], probe: FC) => {
64
+ render(<Harness messages={messages} probe={probe} />);
65
+ // Flush the runtime's deferred initialization, which fake timers hold back.
66
+ await act(async () => {
67
+ await vi.advanceTimersByTimeAsync(0);
68
+ });
69
+ };
70
+
71
+ const Harness: FC<{ messages: ThreadMessageLike[]; probe: FC }> = ({
72
+ messages,
73
+ probe: Probe,
74
+ }) => (
75
+ <RuntimeProvider messages={messages}>
76
+ <ThreadPrimitive.Messages
77
+ components={{
78
+ Message: () => (
79
+ <MessagePrimitive.Parts components={{ tools: { Fallback: Probe } }} />
80
+ ),
81
+ }}
82
+ />
83
+ </RuntimeProvider>
84
+ );
85
+
86
+ describe("useToolCallElapsed", () => {
87
+ beforeEach(() => {
88
+ vi.useFakeTimers();
89
+ vi.setSystemTime(new Date("2026-01-01T00:00:10.000Z"));
90
+ });
91
+
92
+ afterEach(() => {
93
+ vi.useRealTimers();
94
+ });
95
+
96
+ it("returns undefined when the part carries no timing", async () => {
97
+ await renderHarness([toolMessage()], ElapsedProbe);
98
+ expect(screen.getByTestId("elapsed").textContent).toBe("none");
99
+ });
100
+
101
+ it("returns the settled duration for completed calls", async () => {
102
+ const startedAt = Date.now() - 5000;
103
+ await renderHarness(
104
+ [toolMessage({ startedAt, completedAt: startedAt + 1500 })],
105
+ ElapsedProbe,
106
+ );
107
+ expect(screen.getByTestId("elapsed").textContent).toBe("1500");
108
+ });
109
+
110
+ it("ticks while the call is running", async () => {
111
+ const startedAt = Date.now() - 1000;
112
+ await renderHarness([toolMessage({ startedAt })], ElapsedProbe);
113
+
114
+ expect(screen.getByTestId("elapsed").textContent).toBe("1000");
115
+
116
+ await act(async () => {
117
+ await vi.advanceTimersByTimeAsync(3000);
118
+ });
119
+
120
+ expect(screen.getByTestId("elapsed").textContent).toBe("4000");
121
+ });
122
+ });
123
+
124
+ describe("useToolCallElapsed on terminated calls", () => {
125
+ beforeEach(() => {
126
+ vi.useFakeTimers();
127
+ vi.setSystemTime(new Date("2026-01-01T00:00:10.000Z"));
128
+ });
129
+
130
+ afterEach(() => {
131
+ vi.useRealTimers();
132
+ });
133
+
134
+ it("claims no duration when the call ends without a result (cancelled run)", async () => {
135
+ const startedAt = Date.now() - 1000;
136
+ const cancelled: ThreadMessageLike = {
137
+ role: "assistant",
138
+ content: [
139
+ {
140
+ type: "tool-call",
141
+ toolCallId: "tc-1",
142
+ toolName: "search",
143
+ args: {},
144
+ argsText: "{}",
145
+ timing: { startedAt },
146
+ },
147
+ ],
148
+ status: { type: "incomplete", reason: "cancelled" },
149
+ };
150
+ await renderHarness([cancelled], ElapsedProbe);
151
+
152
+ expect(screen.getByTestId("elapsed").textContent).toBe("none");
153
+
154
+ await act(async () => {
155
+ await vi.advanceTimersByTimeAsync(3000);
156
+ });
157
+
158
+ expect(screen.getByTestId("elapsed").textContent).toBe("none");
159
+ });
160
+ });
161
+
162
+ describe("useToolCallElapsed outside any scope", () => {
163
+ it("returns undefined without a provider instead of throwing", () => {
164
+ const Standalone: FC = () => {
165
+ const elapsedMs = useToolCallElapsed();
166
+ return (
167
+ <span data-testid="standalone">
168
+ {elapsedMs === undefined ? "none" : String(elapsedMs)}
169
+ </span>
170
+ );
171
+ };
172
+ render(<Standalone />);
173
+ expect(screen.getByTestId("standalone").textContent).toBe("none");
174
+ });
175
+ });
176
+
177
+ describe("unstable_useMessageStallDetection", () => {
178
+ beforeEach(() => {
179
+ vi.useFakeTimers();
180
+ });
181
+
182
+ afterEach(() => {
183
+ vi.useRealTimers();
184
+ });
185
+
186
+ it("reports a stall after the threshold with no content changes", async () => {
187
+ await renderHarness([toolMessage({ startedAt: 0 })], StallProbe);
188
+
189
+ expect(screen.getByTestId("stalled").textContent).toBe("false");
190
+
191
+ await act(async () => {
192
+ await vi.advanceTimersByTimeAsync(2500);
193
+ });
194
+
195
+ expect(screen.getByTestId("stalled").textContent).toBe("true");
196
+ });
197
+
198
+ it("never stalls on settled messages", async () => {
199
+ const settled: ThreadMessageLike = {
200
+ role: "assistant",
201
+ content: [
202
+ {
203
+ type: "tool-call",
204
+ toolCallId: "tc-1",
205
+ toolName: "search",
206
+ args: {},
207
+ argsText: "{}",
208
+ result: "done",
209
+ },
210
+ ],
211
+ status: { type: "complete", reason: "stop" },
212
+ };
213
+ await renderHarness([settled], StallProbe);
214
+
215
+ await act(async () => {
216
+ await vi.advanceTimersByTimeAsync(5000);
217
+ });
218
+
219
+ expect(screen.getByTestId("stalled").textContent).toBe("false");
220
+ });
221
+ });
@@ -0,0 +1,201 @@
1
+ /** @vitest-environment jsdom */
2
+ import { createEvent, fireEvent, render, screen } from "@testing-library/react";
3
+ import { beforeEach, describe, expect, it, vi } from "vitest";
4
+
5
+ const fixture = {
6
+ messages: [] as { role: string; content: { type: string; text: string }[] }[],
7
+ composerType: "thread",
8
+ activeAria: null as object | null,
9
+ switchedToHandlers: [] as (() => void)[],
10
+ };
11
+ const setText = vi.fn();
12
+
13
+ vi.mock("@assistant-ui/store", () => ({
14
+ useAui: () => ({
15
+ composer: () => ({
16
+ getState: () => ({ type: fixture.composerType }),
17
+ setText,
18
+ }),
19
+ thread: () => ({ getState: () => ({ messages: fixture.messages }) }),
20
+ on: (_event: string, cb: () => void) => {
21
+ fixture.switchedToHandlers.push(cb);
22
+ return () => {};
23
+ },
24
+ }),
25
+ }));
26
+ vi.mock("@assistant-ui/tap", () => ({
27
+ flushTapSync: (fn: () => void) => fn(),
28
+ }));
29
+ vi.mock("../primitives/composer/trigger/TriggerPopoverRootContext", () => ({
30
+ useTriggerPopoverRootContextOptional: () => ({
31
+ getActiveAria: () => fixture.activeAria,
32
+ }),
33
+ }));
34
+
35
+ vi.stubGlobal("requestAnimationFrame", (cb: FrameRequestCallback) => {
36
+ cb(0);
37
+ return 0;
38
+ });
39
+
40
+ import { unstable_useComposerInputHistory } from "./useComposerInputHistory";
41
+
42
+ const user = (text: string) => ({
43
+ role: "user",
44
+ content: [{ type: "text", text }],
45
+ });
46
+ const assistant = (text: string) => ({
47
+ role: "assistant",
48
+ content: [{ type: "text", text }],
49
+ });
50
+
51
+ function Harness() {
52
+ const history = unstable_useComposerInputHistory();
53
+ return <textarea data-testid="input" {...history} />;
54
+ }
55
+
56
+ const setup = (value = "") => {
57
+ const utils = render(<Harness />);
58
+ const textarea = screen.getByTestId("input") as HTMLTextAreaElement;
59
+ textarea.value = value;
60
+ textarea.setSelectionRange(value.length, value.length);
61
+ return { ...utils, textarea };
62
+ };
63
+
64
+ const arrow = (
65
+ textarea: HTMLTextAreaElement,
66
+ key: "ArrowUp" | "ArrowDown",
67
+ init: Record<string, unknown> = {},
68
+ ) => fireEvent.keyDown(textarea, { key, ...init });
69
+
70
+ beforeEach(() => {
71
+ fixture.messages = [user("first"), assistant("reply"), user("second")];
72
+ fixture.composerType = "thread";
73
+ fixture.activeAria = null;
74
+ fixture.switchedToHandlers = [];
75
+ setText.mockClear();
76
+ });
77
+
78
+ describe("unstable_useComposerInputHistory", () => {
79
+ it("recalls the newest user message on ArrowUp in an empty composer", () => {
80
+ const { textarea } = setup("");
81
+ const notPrevented = arrow(textarea, "ArrowUp");
82
+ expect(notPrevented).toBe(false);
83
+ expect(setText).toHaveBeenCalledWith("second");
84
+ });
85
+
86
+ it("steps to older entries and consumes the key at the oldest", () => {
87
+ const { textarea } = setup("");
88
+ arrow(textarea, "ArrowUp");
89
+ textarea.value = "second";
90
+ arrow(textarea, "ArrowUp");
91
+ expect(setText).toHaveBeenLastCalledWith("first");
92
+
93
+ textarea.value = "first";
94
+ const notPrevented = arrow(textarea, "ArrowUp");
95
+ expect(notPrevented).toBe(false);
96
+ expect(setText).toHaveBeenCalledTimes(2);
97
+ });
98
+
99
+ it("restores the draft snapshot on ArrowDown past the newest", () => {
100
+ const { textarea } = setup(" ");
101
+ arrow(textarea, "ArrowUp");
102
+ textarea.value = "second";
103
+ arrow(textarea, "ArrowDown");
104
+ expect(setText).toHaveBeenLastCalledWith(" ");
105
+
106
+ textarea.value = " ";
107
+ const notPrevented = arrow(textarea, "ArrowDown");
108
+ expect(notPrevented).toBe(true);
109
+ expect(setText).toHaveBeenCalledTimes(2);
110
+ });
111
+
112
+ it("ignores ArrowUp when the composer holds a typed draft", () => {
113
+ const { textarea } = setup("draft in progress");
114
+ const notPrevented = arrow(textarea, "ArrowUp");
115
+ expect(notPrevented).toBe(true);
116
+ expect(setText).not.toHaveBeenCalled();
117
+ });
118
+
119
+ it("keeps native arrows when the caret is not on the first line", () => {
120
+ fixture.messages = [user("other"), user("line1\nline2")];
121
+ const { textarea } = setup("");
122
+ arrow(textarea, "ArrowUp");
123
+ expect(setText).toHaveBeenLastCalledWith("line1\nline2");
124
+ textarea.value = "line1\nline2";
125
+ textarea.setSelectionRange(8, 8);
126
+ const notPrevented = arrow(textarea, "ArrowUp");
127
+ expect(notPrevented).toBe(true);
128
+ expect(setText).toHaveBeenCalledTimes(1);
129
+ });
130
+
131
+ it("yields to an open trigger popover", () => {
132
+ fixture.activeAria = {};
133
+ const { textarea } = setup("");
134
+ const notPrevented = arrow(textarea, "ArrowUp");
135
+ expect(notPrevented).toBe(true);
136
+ expect(setText).not.toHaveBeenCalled();
137
+ });
138
+
139
+ it("ignores IME composition, modifiers, selections, and prevented events", () => {
140
+ const { textarea } = setup("");
141
+ fireEvent.keyDown(textarea, { key: "ArrowUp", isComposing: true });
142
+ arrow(textarea, "ArrowUp", { shiftKey: true });
143
+ arrow(textarea, "ArrowUp", { metaKey: true });
144
+ textarea.value = "ab";
145
+ textarea.setSelectionRange(0, 2);
146
+ arrow(textarea, "ArrowUp");
147
+
148
+ textarea.value = "";
149
+ textarea.setSelectionRange(0, 0);
150
+ const prevented = createEvent.keyDown(textarea, { key: "ArrowUp" });
151
+ prevented.preventDefault();
152
+ fireEvent(textarea, prevented);
153
+ expect(setText).not.toHaveBeenCalled();
154
+ });
155
+
156
+ it("derives the ring from trimmed user texts, newest first, deduped", () => {
157
+ fixture.messages = [
158
+ user("first"),
159
+ user("first"),
160
+ assistant("noise"),
161
+ user(" "),
162
+ user("second"),
163
+ ];
164
+ const { textarea } = setup("");
165
+ arrow(textarea, "ArrowUp");
166
+ expect(setText).toHaveBeenLastCalledWith("second");
167
+ textarea.value = "second";
168
+ arrow(textarea, "ArrowUp");
169
+ expect(setText).toHaveBeenLastCalledWith("first");
170
+ textarea.value = "first";
171
+ arrow(textarea, "ArrowUp");
172
+ expect(setText).toHaveBeenCalledTimes(2);
173
+ });
174
+
175
+ it("starts a fresh browse after the composer was cleared externally", () => {
176
+ const { textarea } = setup("");
177
+ arrow(textarea, "ArrowUp");
178
+ fixture.messages = [...fixture.messages, user("third")];
179
+ textarea.value = "";
180
+ arrow(textarea, "ArrowUp");
181
+ expect(setText).toHaveBeenLastCalledWith("third");
182
+ });
183
+
184
+ it("is inert on edit composers", () => {
185
+ fixture.composerType = "edit";
186
+ const { textarea } = setup("");
187
+ const notPrevented = arrow(textarea, "ArrowUp");
188
+ expect(notPrevented).toBe(true);
189
+ expect(setText).not.toHaveBeenCalled();
190
+ });
191
+
192
+ it("resets browsing when the thread switches", () => {
193
+ const { textarea } = setup("");
194
+ arrow(textarea, "ArrowUp");
195
+ fixture.switchedToHandlers.forEach((cb) => cb());
196
+ textarea.value = "second";
197
+ const notPrevented = arrow(textarea, "ArrowDown");
198
+ expect(notPrevented).toBe(true);
199
+ expect(setText).toHaveBeenCalledTimes(1);
200
+ });
201
+ });
@@ -0,0 +1,160 @@
1
+ "use client";
2
+
3
+ import {
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ type KeyboardEvent,
9
+ type KeyboardEventHandler,
10
+ } from "react";
11
+ import { useAui } from "@assistant-ui/store";
12
+ import { flushTapSync } from "@assistant-ui/tap";
13
+ import type { ThreadMessage } from "@assistant-ui/core";
14
+ import { getThreadMessageText } from "@assistant-ui/core/internal";
15
+ import { useTriggerPopoverRootContextOptional } from "../primitives/composer/trigger/TriggerPopoverRootContext";
16
+
17
+ export type Unstable_ComposerInputHistory = {
18
+ /** Keydown handler to spread onto `ComposerPrimitive.Input`. */
19
+ onKeyDown: KeyboardEventHandler<HTMLTextAreaElement>;
20
+ };
21
+
22
+ type BrowseState = {
23
+ cursor: number;
24
+ draftSnapshot: string;
25
+ lastRecalledText: string;
26
+ };
27
+
28
+ const deriveHistory = (messages: readonly ThreadMessage[]): string[] => {
29
+ const entries: string[] = [];
30
+ for (let i = messages.length - 1; i >= 0; i--) {
31
+ const message = messages[i]!;
32
+ if (message.role !== "user") continue;
33
+ const text = getThreadMessageText(message).trim();
34
+ if (!text) continue;
35
+ if (entries[entries.length - 1] === text) continue;
36
+ entries.push(text);
37
+ }
38
+ return entries;
39
+ };
40
+
41
+ const isOnFirstLine = (value: string, caret: number): boolean =>
42
+ !value.slice(0, caret).includes("\n");
43
+
44
+ const isOnLastLine = (value: string, caret: number): boolean =>
45
+ !value.slice(caret).includes("\n");
46
+
47
+ /**
48
+ * @deprecated Under active development and might change without notice.
49
+ *
50
+ * Terminal-style input history for the thread composer: ArrowUp on an
51
+ * empty draft recalls previously sent user messages (newest first),
52
+ * ArrowDown steps back toward the newest and finally restores the draft
53
+ * that was being typed when browsing started.
54
+ *
55
+ * Recall only triggers when the caret is on the first/last line with no
56
+ * selection, so multi-line editing keeps native arrow behavior. The
57
+ * handler yields to an open mention/slash popover, to IME composition,
58
+ * to modifier keys, and to consumer handlers that already called
59
+ * `preventDefault`. It is inert on edit composers.
60
+ *
61
+ * @example
62
+ * ```tsx
63
+ * const history = unstable_useComposerInputHistory();
64
+ * <ComposerPrimitive.Input {...history} />
65
+ * ```
66
+ */
67
+ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistory {
68
+ const aui = useAui();
69
+ const popoverCtx = useTriggerPopoverRootContextOptional();
70
+ const browseRef = useRef<BrowseState | null>(null);
71
+
72
+ useEffect(() => {
73
+ if (aui.composer().getState().type !== "thread") return undefined;
74
+
75
+ return aui.on("threadListItem.switchedTo", () => {
76
+ browseRef.current = null;
77
+ });
78
+ }, [aui]);
79
+
80
+ const onKeyDown = useCallback(
81
+ (e: KeyboardEvent<HTMLTextAreaElement>) => {
82
+ if (e.defaultPrevented) return;
83
+ if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
84
+ if (e.nativeEvent.isComposing) return;
85
+ if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;
86
+ if (popoverCtx && popoverCtx.getActiveAria() !== null) return;
87
+ if (aui.composer().getState().type !== "thread") return;
88
+
89
+ const textarea = e.currentTarget;
90
+ const { selectionStart, selectionEnd, value } = textarea;
91
+ if (selectionStart !== selectionEnd) return;
92
+
93
+ if (browseRef.current && value !== browseRef.current.lastRecalledText) {
94
+ browseRef.current = null;
95
+ }
96
+ const browse = browseRef.current;
97
+
98
+ const commitText = (text: string): void => {
99
+ flushTapSync(() => aui.composer().setText(text));
100
+ // React's controlled-value commit restores the pre-recall caret;
101
+ // reposition after the commit, before paint.
102
+ requestAnimationFrame(() => {
103
+ textarea.setSelectionRange(text.length, text.length);
104
+ });
105
+ e.preventDefault();
106
+ };
107
+
108
+ const recall = (
109
+ history: readonly string[],
110
+ cursor: number,
111
+ draftSnapshot: string,
112
+ ): void => {
113
+ const entry = history[cursor];
114
+ if (entry === undefined) {
115
+ e.preventDefault();
116
+ return;
117
+ }
118
+ browseRef.current = { cursor, draftSnapshot, lastRecalledText: entry };
119
+ commitText(entry);
120
+ };
121
+
122
+ if (e.key === "ArrowUp") {
123
+ if (!isOnFirstLine(value, selectionStart)) return;
124
+
125
+ if (!browse) {
126
+ if (value.trim() !== "") return;
127
+ const history = deriveHistory(aui.thread().getState().messages);
128
+ if (history.length === 0) return;
129
+ recall(history, 0, value);
130
+ return;
131
+ }
132
+
133
+ const history = deriveHistory(aui.thread().getState().messages);
134
+ const next = browse.cursor + 1;
135
+ if (next >= history.length) {
136
+ e.preventDefault();
137
+ return;
138
+ }
139
+ recall(history, next, browse.draftSnapshot);
140
+ return;
141
+ }
142
+
143
+ if (!browse) return;
144
+ if (!isOnLastLine(value, selectionEnd)) return;
145
+
146
+ const next = browse.cursor - 1;
147
+ if (next < 0) {
148
+ browseRef.current = null;
149
+ commitText(browse.draftSnapshot);
150
+ return;
151
+ }
152
+
153
+ const history = deriveHistory(aui.thread().getState().messages);
154
+ recall(history, next, browse.draftSnapshot);
155
+ },
156
+ [aui, popoverCtx],
157
+ );
158
+
159
+ return useMemo(() => ({ onKeyDown }), [onKeyDown]);
160
+ }
@@ -0,0 +1,91 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { useAuiState } from "@assistant-ui/store";
5
+
6
+ export type Unstable_MessageStallDetectionOptions = {
7
+ /**
8
+ * Milliseconds of unchanged message content before the message counts as
9
+ * stalled.
10
+ * @default 2000
11
+ */
12
+ thresholdMs?: number | undefined;
13
+ };
14
+
15
+ export type Unstable_MessageStallDetection = {
16
+ /** True while the message is running and its content has not changed for at least `thresholdMs`. */
17
+ stalled: boolean;
18
+ /** Milliseconds since the last observed content change. `0` while not stalled. */
19
+ stalledForMs: number;
20
+ };
21
+
22
+ /**
23
+ * @deprecated Under active development and might change without notice.
24
+ *
25
+ * Detects mid-run output stalls on the current message: while the message is
26
+ * running, watches a fingerprint of its content (part count plus text,
27
+ * argument, and result sizes) and reports a stall once the fingerprint stops
28
+ * changing for `thresholdMs`. Useful for re-surfacing a "still working"
29
+ * indicator during tool think-time or provider stalls, after the first
30
+ * tokens have already streamed.
31
+ *
32
+ * Must be used inside a message scope.
33
+ */
34
+ export function unstable_useMessageStallDetection(
35
+ options?: Unstable_MessageStallDetectionOptions,
36
+ ): Unstable_MessageStallDetection {
37
+ const thresholdMs = options?.thresholdMs ?? 2000;
38
+
39
+ const fingerprint = useAuiState((s) => {
40
+ if (s.message.status?.type !== "running") return undefined;
41
+ let size = 0;
42
+ for (const part of s.message.content) {
43
+ if (part.type === "text" || part.type === "reasoning") {
44
+ size += part.text.length;
45
+ } else if (part.type === "tool-call") {
46
+ size += part.argsText.length + (part.result !== undefined ? 1 : 0);
47
+ }
48
+ }
49
+ return `${s.message.content.length}:${size}`;
50
+ });
51
+
52
+ const running = fingerprint !== undefined;
53
+ const lastActivityRef = useRef(Date.now());
54
+ const [stalled, setStalled] = useState(false);
55
+ const [, setTick] = useState(0);
56
+
57
+ useEffect(() => {
58
+ if (!running) return undefined;
59
+ lastActivityRef.current = Date.now();
60
+ return undefined;
61
+ }, [running, fingerprint]);
62
+
63
+ useEffect(() => {
64
+ if (!running) {
65
+ setStalled(false);
66
+ return undefined;
67
+ }
68
+
69
+ const sinceActivity = Date.now() - lastActivityRef.current;
70
+ if (sinceActivity >= thresholdMs) {
71
+ setStalled(true);
72
+ return undefined;
73
+ }
74
+
75
+ setStalled(false);
76
+ const id = setTimeout(() => setStalled(true), thresholdMs - sinceActivity);
77
+ return () => clearTimeout(id);
78
+ }, [running, fingerprint, thresholdMs]);
79
+
80
+ useEffect(() => {
81
+ if (!stalled) return undefined;
82
+ const id = setInterval(() => setTick((t) => t + 1), 1000);
83
+ return () => clearInterval(id);
84
+ }, [stalled]);
85
+
86
+ if (!stalled) return { stalled: false, stalledForMs: 0 };
87
+ return {
88
+ stalled: true,
89
+ stalledForMs: Math.max(0, Date.now() - lastActivityRef.current),
90
+ };
91
+ }