@assistant-ui/react 0.14.18 → 0.14.20

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 (334) hide show
  1. package/dist/client/ExternalThread.d.ts +3 -2
  2. package/dist/client/ExternalThread.d.ts.map +1 -1
  3. package/dist/client/ExternalThread.js +721 -256
  4. package/dist/client/ExternalThread.js.map +1 -1
  5. package/dist/client/InMemoryThreadList.d.ts.map +1 -1
  6. package/dist/client/InMemoryThreadList.js +292 -108
  7. package/dist/client/InMemoryThreadList.js.map +1 -1
  8. package/dist/client/SingleThreadList.js +139 -53
  9. package/dist/client/SingleThreadList.js.map +1 -1
  10. package/dist/context/ReadonlyStore.js.map +1 -1
  11. package/dist/context/providers/MessageProvider.js +38 -5
  12. package/dist/context/providers/MessageProvider.js.map +1 -1
  13. package/dist/context/providers/ThreadViewportProvider.js +76 -20
  14. package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
  15. package/dist/context/react/ThreadViewportContext.js.map +1 -1
  16. package/dist/context/react/utils/createContextHook.js.map +1 -1
  17. package/dist/context/react/utils/createContextStoreHook.js +17 -2
  18. package/dist/context/react/utils/createContextStoreHook.js.map +1 -1
  19. package/dist/context/react/utils/createStateHookForRuntime.js.map +1 -1
  20. package/dist/context/react/utils/ensureBinding.js.map +1 -1
  21. package/dist/context/react/utils/useRuntimeState.js +18 -2
  22. package/dist/context/react/utils/useRuntimeState.js.map +1 -1
  23. package/dist/context/stores/ThreadViewport.js.map +1 -1
  24. package/dist/devtools/DevToolsHooks.js.map +1 -1
  25. package/dist/hooks/useMessageQuote.js.map +1 -1
  26. package/dist/hooks/useMessageTiming.js +4 -1
  27. package/dist/hooks/useMessageTiming.js.map +1 -1
  28. package/dist/hooks/useToolCallElapsed.d.ts +23 -0
  29. package/dist/hooks/useToolCallElapsed.d.ts.map +1 -0
  30. package/dist/hooks/useToolCallElapsed.js +72 -0
  31. package/dist/hooks/useToolCallElapsed.js.map +1 -0
  32. package/dist/index.d.ts +4 -2
  33. package/dist/index.js +3 -1
  34. package/dist/internal.js.map +1 -1
  35. package/dist/legacy-runtime/AssistantRuntimeProvider.js +46 -10
  36. package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
  37. package/dist/legacy-runtime/cloud/auiV0.js.map +1 -1
  38. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js +27 -6
  39. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js.map +1 -1
  40. package/dist/legacy-runtime/hooks/AssistantContext.js +13 -2
  41. package/dist/legacy-runtime/hooks/AssistantContext.js.map +1 -1
  42. package/dist/legacy-runtime/hooks/AttachmentContext.js +9 -1
  43. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +1 -1
  44. package/dist/legacy-runtime/hooks/ComposerContext.js +9 -1
  45. package/dist/legacy-runtime/hooks/ComposerContext.js.map +1 -1
  46. package/dist/legacy-runtime/hooks/MessageContext.js +12 -2
  47. package/dist/legacy-runtime/hooks/MessageContext.js.map +1 -1
  48. package/dist/legacy-runtime/hooks/MessagePartContext.js +9 -1
  49. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +1 -1
  50. package/dist/legacy-runtime/hooks/ThreadContext.js +33 -5
  51. package/dist/legacy-runtime/hooks/ThreadContext.js.map +1 -1
  52. package/dist/legacy-runtime/hooks/ThreadListItemContext.js +9 -1
  53. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +1 -1
  54. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +3 -3
  55. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +1 -1
  56. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +71 -31
  57. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +1 -1
  58. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +1 -1
  59. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +24 -16
  60. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  61. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +17 -12
  62. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +1 -1
  63. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +17 -3
  64. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js.map +1 -1
  65. package/dist/mcp-apps/McpAppRenderer.js +6 -6
  66. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  67. package/dist/mcp-apps/McpAppsRemoteHost.js +3 -3
  68. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  69. package/dist/mcp-apps/app-frame.js +33 -14
  70. package/dist/mcp-apps/app-frame.js.map +1 -1
  71. package/dist/mcp-apps/bridge.js.map +1 -1
  72. package/dist/mcp-apps/types.js.map +1 -1
  73. package/dist/mcp-apps/utils.js.map +1 -1
  74. package/dist/model-context/frame/useAssistantFrameHost.js +32 -14
  75. package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
  76. package/dist/model-context/makeAssistantVisible.js +64 -26
  77. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  78. package/dist/primitives/actionBar/ActionBarCopy.js +94 -20
  79. package/dist/primitives/actionBar/ActionBarCopy.js.map +1 -1
  80. package/dist/primitives/actionBar/ActionBarEdit.js.map +1 -1
  81. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +105 -37
  82. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  83. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +60 -11
  84. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js.map +1 -1
  85. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +60 -11
  86. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js.map +1 -1
  87. package/dist/primitives/actionBar/ActionBarInteractionContext.js +3 -1
  88. package/dist/primitives/actionBar/ActionBarInteractionContext.js.map +1 -1
  89. package/dist/primitives/actionBar/ActionBarReload.js.map +1 -1
  90. package/dist/primitives/actionBar/ActionBarRoot.js +84 -25
  91. package/dist/primitives/actionBar/ActionBarRoot.js.map +1 -1
  92. package/dist/primitives/actionBar/ActionBarSpeak.js.map +1 -1
  93. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +45 -14
  94. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
  95. package/dist/primitives/actionBar/useActionBarFloatStatus.js +22 -10
  96. package/dist/primitives/actionBar/useActionBarFloatStatus.js.map +1 -1
  97. package/dist/primitives/actionBar.js.map +1 -1
  98. package/dist/primitives/actionBarMore/ActionBarMoreContent.js +44 -7
  99. package/dist/primitives/actionBarMore/ActionBarMoreContent.js.map +1 -1
  100. package/dist/primitives/actionBarMore/ActionBarMoreItem.js +28 -6
  101. package/dist/primitives/actionBarMore/ActionBarMoreItem.js.map +1 -1
  102. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js +103 -36
  103. package/dist/primitives/actionBarMore/ActionBarMoreRoot.js.map +1 -1
  104. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js +28 -6
  105. package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js.map +1 -1
  106. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js +28 -6
  107. package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js.map +1 -1
  108. package/dist/primitives/actionBarMore/scope.js.map +1 -1
  109. package/dist/primitives/actionBarMore.js.map +1 -1
  110. package/dist/primitives/assistantModal/AssistantModalAnchor.js +27 -6
  111. package/dist/primitives/assistantModal/AssistantModalAnchor.js.map +1 -1
  112. package/dist/primitives/assistantModal/AssistantModalContent.js +71 -10
  113. package/dist/primitives/assistantModal/AssistantModalContent.js.map +1 -1
  114. package/dist/primitives/assistantModal/AssistantModalRoot.js +93 -26
  115. package/dist/primitives/assistantModal/AssistantModalRoot.js.map +1 -1
  116. package/dist/primitives/assistantModal/AssistantModalTrigger.js +27 -6
  117. package/dist/primitives/assistantModal/AssistantModalTrigger.js.map +1 -1
  118. package/dist/primitives/assistantModal/scope.js.map +1 -1
  119. package/dist/primitives/assistantModal.js.map +1 -1
  120. package/dist/primitives/attachment/AttachmentName.js +13 -1
  121. package/dist/primitives/attachment/AttachmentName.js.map +1 -1
  122. package/dist/primitives/attachment/AttachmentRemove.js +11 -4
  123. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  124. package/dist/primitives/attachment/AttachmentRoot.js +13 -4
  125. package/dist/primitives/attachment/AttachmentRoot.js.map +1 -1
  126. package/dist/primitives/attachment/AttachmentThumb.js +20 -9
  127. package/dist/primitives/attachment/AttachmentThumb.js.map +1 -1
  128. package/dist/primitives/attachment.js.map +1 -1
  129. package/dist/primitives/branchPicker/BranchPickerCount.js +14 -2
  130. package/dist/primitives/branchPicker/BranchPickerCount.js.map +1 -1
  131. package/dist/primitives/branchPicker/BranchPickerNext.js.map +1 -1
  132. package/dist/primitives/branchPicker/BranchPickerNumber.js +14 -2
  133. package/dist/primitives/branchPicker/BranchPickerNumber.js.map +1 -1
  134. package/dist/primitives/branchPicker/BranchPickerPrevious.js.map +1 -1
  135. package/dist/primitives/branchPicker/BranchPickerRoot.js +34 -6
  136. package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
  137. package/dist/primitives/branchPicker.js.map +1 -1
  138. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +16 -5
  139. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  140. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js +13 -4
  141. package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js.map +1 -1
  142. package/dist/primitives/chainOfThought.js.map +1 -1
  143. package/dist/primitives/composer/ComposerAddAttachment.js +37 -24
  144. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  145. package/dist/primitives/composer/ComposerAttachmentDropzone.js +124 -49
  146. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  147. package/dist/primitives/composer/ComposerCancel.js.map +1 -1
  148. package/dist/primitives/composer/ComposerDictate.js.map +1 -1
  149. package/dist/primitives/composer/ComposerDictationTranscript.js +32 -7
  150. package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
  151. package/dist/primitives/composer/ComposerInput.js +26 -26
  152. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  153. package/dist/primitives/composer/ComposerInputPluginContext.js +71 -25
  154. package/dist/primitives/composer/ComposerInputPluginContext.js.map +1 -1
  155. package/dist/primitives/composer/ComposerQuote.js +92 -23
  156. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  157. package/dist/primitives/composer/ComposerRoot.js +45 -11
  158. package/dist/primitives/composer/ComposerRoot.js.map +1 -1
  159. package/dist/primitives/composer/ComposerSend.js +9 -2
  160. package/dist/primitives/composer/ComposerSend.js.map +1 -1
  161. package/dist/primitives/composer/ComposerStopDictation.js +15 -5
  162. package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
  163. package/dist/primitives/composer/trigger/TriggerPopover.d.ts.map +1 -1
  164. package/dist/primitives/composer/trigger/TriggerPopover.js +215 -75
  165. package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
  166. package/dist/primitives/composer/trigger/TriggerPopoverAction.js.map +1 -1
  167. package/dist/primitives/composer/trigger/TriggerPopoverBack.js +35 -7
  168. package/dist/primitives/composer/trigger/TriggerPopoverBack.js.map +1 -1
  169. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +134 -28
  170. package/dist/primitives/composer/trigger/TriggerPopoverCategories.js.map +1 -1
  171. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js.map +1 -1
  172. package/dist/primitives/composer/trigger/TriggerPopoverItems.js +132 -28
  173. package/dist/primitives/composer/trigger/TriggerPopoverItems.js.map +1 -1
  174. package/dist/primitives/composer/trigger/TriggerPopoverResource.js +124 -52
  175. package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
  176. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +181 -78
  177. package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js.map +1 -1
  178. package/dist/primitives/composer/trigger/detectTrigger.js.map +1 -1
  179. package/dist/primitives/composer/trigger/index.js.map +1 -1
  180. package/dist/primitives/composer/trigger/triggerDetectionResource.js +28 -14
  181. package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
  182. package/dist/primitives/composer/trigger/triggerKeyboardResource.js +115 -58
  183. package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
  184. package/dist/primitives/composer/trigger/triggerNavigationResource.js +202 -70
  185. package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
  186. package/dist/primitives/composer/trigger/triggerSelectionResource.js +49 -13
  187. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  188. package/dist/primitives/composer.js.map +1 -1
  189. package/dist/primitives/dropdownMenuRenderPrimitives.js.map +1 -1
  190. package/dist/primitives/error/ErrorMessage.js +28 -6
  191. package/dist/primitives/error/ErrorMessage.js.map +1 -1
  192. package/dist/primitives/error/ErrorRoot.js +14 -5
  193. package/dist/primitives/error/ErrorRoot.js.map +1 -1
  194. package/dist/primitives/error.js.map +1 -1
  195. package/dist/primitives/message/MessageError.js +2 -1
  196. package/dist/primitives/message/MessageError.js.map +1 -1
  197. package/dist/primitives/message/MessageIf.js +50 -20
  198. package/dist/primitives/message/MessageIf.js.map +1 -1
  199. package/dist/primitives/message/MessageParts.js +41 -7
  200. package/dist/primitives/message/MessageParts.js.map +1 -1
  201. package/dist/primitives/message/MessagePartsGrouped.js +399 -94
  202. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  203. package/dist/primitives/message/MessageRoot.js +197 -65
  204. package/dist/primitives/message/MessageRoot.js.map +1 -1
  205. package/dist/primitives/message.js.map +1 -1
  206. package/dist/primitives/messagePart/MessagePartImage.js +15 -5
  207. package/dist/primitives/messagePart/MessagePartImage.js.map +1 -1
  208. package/dist/primitives/messagePart/MessagePartText.js +35 -7
  209. package/dist/primitives/messagePart/MessagePartText.js.map +1 -1
  210. package/dist/primitives/messagePart/useMessagePartData.js +5 -4
  211. package/dist/primitives/messagePart/useMessagePartData.js.map +1 -1
  212. package/dist/primitives/messagePart/useMessagePartFile.js +5 -4
  213. package/dist/primitives/messagePart/useMessagePartFile.js.map +1 -1
  214. package/dist/primitives/messagePart/useMessagePartImage.js +5 -4
  215. package/dist/primitives/messagePart/useMessagePartImage.js.map +1 -1
  216. package/dist/primitives/messagePart/useMessagePartReasoning.js +5 -4
  217. package/dist/primitives/messagePart/useMessagePartReasoning.js.map +1 -1
  218. package/dist/primitives/messagePart/useMessagePartSource.js +5 -4
  219. package/dist/primitives/messagePart/useMessagePartSource.js.map +1 -1
  220. package/dist/primitives/messagePart/useMessagePartText.js +5 -4
  221. package/dist/primitives/messagePart/useMessagePartText.js.map +1 -1
  222. package/dist/primitives/messagePart.js.map +1 -1
  223. package/dist/primitives/queueItem/QueueItemRemove.js +11 -4
  224. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  225. package/dist/primitives/queueItem/QueueItemSteer.js +11 -4
  226. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  227. package/dist/primitives/queueItem/QueueItemText.js +20 -6
  228. package/dist/primitives/queueItem/QueueItemText.js.map +1 -1
  229. package/dist/primitives/queueItem.js.map +1 -1
  230. package/dist/primitives/reasoning/useScrollLock.js +61 -43
  231. package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
  232. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +56 -16
  233. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  234. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +120 -59
  235. package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
  236. package/dist/primitives/selectionToolbar.js.map +1 -1
  237. package/dist/primitives/suggestion/SuggestionDescription.js +20 -6
  238. package/dist/primitives/suggestion/SuggestionDescription.js.map +1 -1
  239. package/dist/primitives/suggestion/SuggestionTitle.js +20 -6
  240. package/dist/primitives/suggestion/SuggestionTitle.js.map +1 -1
  241. package/dist/primitives/suggestion/SuggestionTrigger.js +39 -26
  242. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  243. package/dist/primitives/suggestion.js.map +1 -1
  244. package/dist/primitives/thread/ThreadEmpty.js +6 -2
  245. package/dist/primitives/thread/ThreadEmpty.js.map +1 -1
  246. package/dist/primitives/thread/ThreadIf.js +32 -10
  247. package/dist/primitives/thread/ThreadIf.js.map +1 -1
  248. package/dist/primitives/thread/ThreadRoot.js +13 -4
  249. package/dist/primitives/thread/ThreadRoot.js.map +1 -1
  250. package/dist/primitives/thread/ThreadScrollToBottom.js +24 -6
  251. package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
  252. package/dist/primitives/thread/ThreadSuggestion.js +18 -6
  253. package/dist/primitives/thread/ThreadSuggestion.js.map +1 -1
  254. package/dist/primitives/thread/ThreadViewport.js +185 -47
  255. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  256. package/dist/primitives/thread/ThreadViewportFooter.js +22 -9
  257. package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
  258. package/dist/primitives/thread/topAnchor/computeTopAnchorSlack.js.map +1 -1
  259. package/dist/primitives/thread/topAnchor/createReserveObservers.js.map +1 -1
  260. package/dist/primitives/thread/topAnchor/mountTopAnchorReserve.js.map +1 -1
  261. package/dist/primitives/thread/topAnchor/topAnchorTurn.js.map +1 -1
  262. package/dist/primitives/thread/topAnchor/topAnchorUtils.js.map +1 -1
  263. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +19 -4
  264. package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js.map +1 -1
  265. package/dist/primitives/thread/useThreadViewportAutoScroll.js +16 -16
  266. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  267. package/dist/primitives/thread.js.map +1 -1
  268. package/dist/primitives/threadList/ThreadListLoadMore.js.map +1 -1
  269. package/dist/primitives/threadList/ThreadListNew.js +53 -11
  270. package/dist/primitives/threadList/ThreadListNew.js.map +1 -1
  271. package/dist/primitives/threadList/ThreadListRoot.js +13 -4
  272. package/dist/primitives/threadList/ThreadListRoot.js.map +1 -1
  273. package/dist/primitives/threadList.js.map +1 -1
  274. package/dist/primitives/threadListItem/ThreadListItemArchive.js.map +1 -1
  275. package/dist/primitives/threadListItem/ThreadListItemDelete.js.map +1 -1
  276. package/dist/primitives/threadListItem/ThreadListItemRoot.js +26 -7
  277. package/dist/primitives/threadListItem/ThreadListItemRoot.js.map +1 -1
  278. package/dist/primitives/threadListItem/ThreadListItemTrigger.js.map +1 -1
  279. package/dist/primitives/threadListItem/ThreadListItemUnarchive.js.map +1 -1
  280. package/dist/primitives/threadListItem.js.map +1 -1
  281. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +44 -7
  282. package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js.map +1 -1
  283. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js +28 -6
  284. package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js.map +1 -1
  285. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js +25 -5
  286. package/dist/primitives/threadListItemMore/ThreadListItemMoreRoot.js.map +1 -1
  287. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js +28 -6
  288. package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js.map +1 -1
  289. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +28 -6
  290. package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js.map +1 -1
  291. package/dist/primitives/threadListItemMore/scope.js.map +1 -1
  292. package/dist/primitives/threadListItemMore.js.map +1 -1
  293. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  294. package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
  295. package/dist/tests/setup.js.map +1 -1
  296. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  297. package/dist/unstable/useMentionAdapter.js.map +1 -1
  298. package/dist/unstable/useMessageStallDetection.d.ts +29 -0
  299. package/dist/unstable/useMessageStallDetection.d.ts.map +1 -0
  300. package/dist/unstable/useMessageStallDetection.js +69 -0
  301. package/dist/unstable/useMessageStallDetection.js.map +1 -0
  302. package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
  303. package/dist/utils/Primitive.js +57 -12
  304. package/dist/utils/Primitive.js.map +1 -1
  305. package/dist/utils/createActionButton.js +23 -7
  306. package/dist/utils/createActionButton.js.map +1 -1
  307. package/dist/utils/getSelectionMessageId.js.map +1 -1
  308. package/dist/utils/hooks/useManagedRef.js +16 -8
  309. package/dist/utils/hooks/useManagedRef.js.map +1 -1
  310. package/dist/utils/hooks/useMediaQuery.js +25 -10
  311. package/dist/utils/hooks/useMediaQuery.js.map +1 -1
  312. package/dist/utils/hooks/useOnResizeContent.js +29 -19
  313. package/dist/utils/hooks/useOnResizeContent.js.map +1 -1
  314. package/dist/utils/hooks/useOnScrollToBottom.js +20 -4
  315. package/dist/utils/hooks/useOnScrollToBottom.js.map +1 -1
  316. package/dist/utils/hooks/useSizeHandle.js +23 -15
  317. package/dist/utils/hooks/useSizeHandle.js.map +1 -1
  318. package/dist/utils/json/is-json-equal.js.map +1 -1
  319. package/dist/utils/json/is-json.js.map +1 -1
  320. package/dist/utils/smooth/SmoothContext.js +41 -11
  321. package/dist/utils/smooth/SmoothContext.js.map +1 -1
  322. package/dist/utils/smooth/useSmooth.js +5 -5
  323. package/dist/utils/smooth/useSmooth.js.map +1 -1
  324. package/dist/utils/useToolArgsFieldStatus.js +13 -5
  325. package/dist/utils/useToolArgsFieldStatus.js.map +1 -1
  326. package/package.json +9 -9
  327. package/src/client/ExternalThread.ts +81 -52
  328. package/src/client/InMemoryThreadList.ts +12 -14
  329. package/src/hooks/useToolCallElapsed.ts +52 -0
  330. package/src/index.ts +11 -0
  331. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +10 -6
  332. package/src/primitives/composer/trigger/TriggerPopover.tsx +4 -5
  333. package/src/tests/toolCallTiming.test.tsx +221 -0
  334. package/src/unstable/useMessageStallDetection.ts +91 -0
@@ -1 +1 @@
1
- {"version":3,"file":"useSmooth.js","names":[],"sources":["../../../src/utils/smooth/useSmooth.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport type {\n MessagePartStatus,\n ReasoningMessagePart,\n TextMessagePart,\n MessagePartState,\n} from \"@assistant-ui/core\";\nimport { useCallbackRef } from \"@radix-ui/react-use-callback-ref\";\nimport { useSmoothStatusStore } from \"./SmoothContext\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\n\n/**\n * Tuning options for the smooth text streaming animation.\n */\nexport type SmoothOptions = {\n /**\n * Target time in milliseconds to drain the backlog of unrevealed\n * characters. Larger values reveal long backlogs more gradually.\n * @default 250\n */\n drainMs?: number | undefined;\n /**\n * Maximum time in milliseconds between revealed characters, i.e. the\n * slowest reveal rate when the backlog is short.\n * @default 5\n */\n maxCharIntervalMs?: number | undefined;\n /**\n * Maximum number of characters revealed per animation frame.\n * @default Infinity\n */\n maxCharsPerFrame?: number | undefined;\n};\n\nconst DEFAULT_DRAIN_MS = 250;\nconst DEFAULT_MAX_CHAR_INTERVAL_MS = 5;\n\nclass TextStreamAnimator {\n private animationFrameId: number | null = null;\n private lastUpdateTime: number = Date.now();\n\n public targetText: string = \"\";\n public drainMs: number = DEFAULT_DRAIN_MS;\n public maxCharIntervalMs: number = DEFAULT_MAX_CHAR_INTERVAL_MS;\n public maxCharsPerFrame: number = Infinity;\n\n constructor(\n public currentText: string,\n private setText: (newText: string) => void,\n ) {}\n\n start() {\n if (this.animationFrameId !== null) return;\n this.lastUpdateTime = Date.now();\n this.animate();\n }\n\n stop() {\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n }\n\n private animate = () => {\n const currentTime = Date.now();\n const deltaTime = currentTime - this.lastUpdateTime;\n let timeToConsume = deltaTime;\n\n const remainingChars = this.targetText.length - this.currentText.length;\n const baseTimePerChar = Math.min(\n this.maxCharIntervalMs,\n this.drainMs / remainingChars,\n );\n\n const frameLimit = Math.min(remainingChars, this.maxCharsPerFrame);\n let charsToAdd = 0;\n while (timeToConsume >= baseTimePerChar && charsToAdd < frameLimit) {\n charsToAdd++;\n timeToConsume -= baseTimePerChar;\n }\n // A cap-limited frame must not bank its surplus time, or the next\n // frame would burst past the cap.\n if (charsToAdd === frameLimit && frameLimit === this.maxCharsPerFrame) {\n timeToConsume = 0;\n }\n\n if (charsToAdd !== remainingChars) {\n this.animationFrameId = requestAnimationFrame(this.animate);\n } else {\n this.animationFrameId = null;\n }\n if (charsToAdd === 0) return;\n\n this.currentText = this.targetText.slice(\n 0,\n this.currentText.length + charsToAdd,\n );\n this.lastUpdateTime = currentTime - timeToConsume;\n this.setText(this.currentText);\n };\n}\n\nconst SMOOTH_STATUS: MessagePartStatus = Object.freeze({\n type: \"running\",\n});\n\nconst positiveOr = (value: number | undefined, fallback: number): number =>\n value !== undefined && value > 0 ? value : fallback;\n\n/**\n * Animates streamed message part text with a typewriter-style reveal.\n *\n * Takes the current part state and a `smooth` argument: `false` disables,\n * `true` uses the default rate, and a {@link SmoothOptions} object tunes\n * the reveal. Returns the part state with `text` replaced by the revealed\n * prefix and `status` reporting `running` until the reveal catches up.\n *\n * @example\n * ```tsx\n * const { text, status } = useSmooth(useMessagePartText(), {\n * drainMs: 500,\n * maxCharsPerFrame: 30,\n * });\n * ```\n */\nexport const useSmooth = (\n state: MessagePartState & (TextMessagePart | ReasoningMessagePart),\n smooth: boolean | SmoothOptions = false,\n): MessagePartState & (TextMessagePart | ReasoningMessagePart) => {\n const { text } = state;\n const options =\n typeof smooth === \"object\" && smooth !== null ? smooth : undefined;\n const enabled = smooth !== false && smooth !== null;\n const drainMs = positiveOr(options?.drainMs, DEFAULT_DRAIN_MS);\n const maxCharIntervalMs = positiveOr(\n options?.maxCharIntervalMs,\n DEFAULT_MAX_CHAR_INTERVAL_MS,\n );\n const maxCharsPerFrame = positiveOr(options?.maxCharsPerFrame, Infinity);\n\n const [displayedText, setDisplayedText] = useState(\n state.status.type === \"running\" ? \"\" : text,\n );\n\n // Render-phase resync on part flip or text discontinuity, so the\n // first paint after a thread switch never shows the previous\n // part's text (#4051). `displayedText` is already a prefix of\n // `text` during normal streaming, so use it as the previous-text\n // reference instead of carrying separate state — avoids the\n // double render per streaming token. Read part identity through\n // `useAuiState` so we actually subscribe to its changes instead\n // of relying on a render-time proxy reference that may be stable\n // across thread swaps.\n const aui = useAui();\n const part = useAuiState(() => aui.part());\n const [prevPart, setPrevPart] = useState(part);\n if (part !== prevPart || !text.startsWith(displayedText)) {\n setPrevPart(part);\n setDisplayedText(state.status.type === \"running\" ? \"\" : text);\n }\n\n const smoothStatusStore = useSmoothStatusStore({ optional: true });\n const setText = useCallbackRef((text: string) => {\n setDisplayedText(text);\n if (smoothStatusStore) {\n const target =\n displayedText !== text || state.status.type === \"running\"\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n });\n\n // TODO this is hacky\n useEffect(() => {\n if (smoothStatusStore) {\n const target =\n enabled && (displayedText !== text || state.status.type === \"running\")\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n }, [smoothStatusStore, enabled, text, displayedText, state.status]);\n\n const [animatorRef] = useState<TextStreamAnimator>(\n new TextStreamAnimator(displayedText, setText),\n );\n\n useEffect(() => {\n animatorRef.drainMs = drainMs;\n animatorRef.maxCharIntervalMs = maxCharIntervalMs;\n animatorRef.maxCharsPerFrame = maxCharsPerFrame;\n }, [animatorRef, drainMs, maxCharIntervalMs, maxCharsPerFrame]);\n\n const animatorPartRef = useRef(part);\n useEffect(() => {\n if (!enabled) {\n animatorRef.stop();\n return;\n }\n\n // Discontinuity: part flipped, or new text breaks continuation\n // of the animator's current target. Either case requires\n // resetting the cursor — without the part check, a new part\n // whose text happens to share a prefix with the previous target\n // would keep the stale cursor and flicker.\n const partChanged = animatorPartRef.current !== part;\n animatorPartRef.current = part;\n if (partChanged || !text.startsWith(animatorRef.targetText)) {\n if (state.status.type === \"running\") {\n animatorRef.currentText = \"\";\n animatorRef.targetText = text;\n animatorRef.start();\n } else {\n animatorRef.currentText = text;\n animatorRef.targetText = text;\n animatorRef.stop();\n }\n return;\n }\n\n animatorRef.targetText = text;\n animatorRef.start();\n }, [animatorRef, enabled, text, state.status.type, part]);\n\n useEffect(() => {\n return () => {\n animatorRef.stop();\n };\n }, [animatorRef]);\n\n return useMemo(\n () =>\n enabled\n ? {\n ...state,\n text: displayedText,\n status: text === displayedText ? state.status : SMOOTH_STATUS,\n }\n : state,\n [enabled, displayedText, state, text],\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useSmooth.js","names":["useEffect","useMemo","useRef","useState","useAui","useAuiState","MessagePartStatus","ReasoningMessagePart","TextMessagePart","MessagePartState","useCallbackRef","useSmoothStatusStore","writableStore","SmoothOptions","drainMs","maxCharIntervalMs","maxCharsPerFrame","DEFAULT_DRAIN_MS","DEFAULT_MAX_CHAR_INTERVAL_MS","TextStreamAnimator","animationFrameId","lastUpdateTime","Date","now","targetText","Infinity","constructor","currentText","setText","newText","start","animate","stop","cancelAnimationFrame","currentTime","deltaTime","timeToConsume","remainingChars","length","baseTimePerChar","Math","min","frameLimit","charsToAdd","requestAnimationFrame","slice","SMOOTH_STATUS","Object","freeze","type","positiveOr","value","fallback","undefined","useSmooth","state","smooth","text","options","enabled","displayedText","setDisplayedText","status","aui","part","prevPart","setPrevPart","startsWith","smoothStatusStore","optional","target","setState","animatorRef","animatorPartRef","partChanged","current"],"sources":["../../../src/utils/smooth/useSmooth.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useAui, useAuiState } from \"@assistant-ui/store\";\nimport type {\n MessagePartStatus,\n ReasoningMessagePart,\n TextMessagePart,\n MessagePartState,\n} from \"@assistant-ui/core\";\nimport { useCallbackRef } from \"@radix-ui/react-use-callback-ref\";\nimport { useSmoothStatusStore } from \"./SmoothContext\";\nimport { writableStore } from \"../../context/ReadonlyStore\";\n\n/**\n * Tuning options for the smooth text streaming animation.\n */\nexport type SmoothOptions = {\n /**\n * Target time in milliseconds to drain the backlog of unrevealed\n * characters. Larger values reveal long backlogs more gradually.\n * @default 250\n */\n drainMs?: number | undefined;\n /**\n * Maximum time in milliseconds between revealed characters, i.e. the\n * slowest reveal rate when the backlog is short.\n * @default 5\n */\n maxCharIntervalMs?: number | undefined;\n /**\n * Maximum number of characters revealed per animation frame.\n * @default Infinity\n */\n maxCharsPerFrame?: number | undefined;\n};\n\nconst DEFAULT_DRAIN_MS = 250;\nconst DEFAULT_MAX_CHAR_INTERVAL_MS = 5;\n\nclass TextStreamAnimator {\n private animationFrameId: number | null = null;\n private lastUpdateTime: number = Date.now();\n\n public targetText: string = \"\";\n public drainMs: number = DEFAULT_DRAIN_MS;\n public maxCharIntervalMs: number = DEFAULT_MAX_CHAR_INTERVAL_MS;\n public maxCharsPerFrame: number = Infinity;\n\n constructor(\n public currentText: string,\n private setText: (newText: string) => void,\n ) {}\n\n start() {\n if (this.animationFrameId !== null) return;\n this.lastUpdateTime = Date.now();\n this.animate();\n }\n\n stop() {\n if (this.animationFrameId !== null) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = null;\n }\n }\n\n private animate = () => {\n const currentTime = Date.now();\n const deltaTime = currentTime - this.lastUpdateTime;\n let timeToConsume = deltaTime;\n\n const remainingChars = this.targetText.length - this.currentText.length;\n const baseTimePerChar = Math.min(\n this.maxCharIntervalMs,\n this.drainMs / remainingChars,\n );\n\n const frameLimit = Math.min(remainingChars, this.maxCharsPerFrame);\n let charsToAdd = 0;\n while (timeToConsume >= baseTimePerChar && charsToAdd < frameLimit) {\n charsToAdd++;\n timeToConsume -= baseTimePerChar;\n }\n // A cap-limited frame must not bank its surplus time, or the next\n // frame would burst past the cap.\n if (charsToAdd === frameLimit && frameLimit === this.maxCharsPerFrame) {\n timeToConsume = 0;\n }\n\n if (charsToAdd !== remainingChars) {\n this.animationFrameId = requestAnimationFrame(this.animate);\n } else {\n this.animationFrameId = null;\n }\n if (charsToAdd === 0) return;\n\n this.currentText = this.targetText.slice(\n 0,\n this.currentText.length + charsToAdd,\n );\n this.lastUpdateTime = currentTime - timeToConsume;\n this.setText(this.currentText);\n };\n}\n\nconst SMOOTH_STATUS: MessagePartStatus = Object.freeze({\n type: \"running\",\n});\n\nconst positiveOr = (value: number | undefined, fallback: number): number =>\n value !== undefined && value > 0 ? value : fallback;\n\n/**\n * Animates streamed message part text with a typewriter-style reveal.\n *\n * Takes the current part state and a `smooth` argument: `false` disables,\n * `true` uses the default rate, and a {@link SmoothOptions} object tunes\n * the reveal. Returns the part state with `text` replaced by the revealed\n * prefix and `status` reporting `running` until the reveal catches up.\n *\n * @example\n * ```tsx\n * const { text, status } = useSmooth(useMessagePartText(), {\n * drainMs: 500,\n * maxCharsPerFrame: 30,\n * });\n * ```\n */\nexport const useSmooth = (\n state: MessagePartState & (TextMessagePart | ReasoningMessagePart),\n smooth: boolean | SmoothOptions = false,\n): MessagePartState & (TextMessagePart | ReasoningMessagePart) => {\n const { text } = state;\n const options =\n typeof smooth === \"object\" && smooth !== null ? smooth : undefined;\n const enabled = smooth !== false && smooth !== null;\n const drainMs = positiveOr(options?.drainMs, DEFAULT_DRAIN_MS);\n const maxCharIntervalMs = positiveOr(\n options?.maxCharIntervalMs,\n DEFAULT_MAX_CHAR_INTERVAL_MS,\n );\n const maxCharsPerFrame = positiveOr(options?.maxCharsPerFrame, Infinity);\n\n const [displayedText, setDisplayedText] = useState(\n state.status.type === \"running\" ? \"\" : text,\n );\n\n // Render-phase resync on part flip or text discontinuity, so the\n // first paint after a thread switch never shows the previous\n // part's text (#4051). `displayedText` is already a prefix of\n // `text` during normal streaming, so use it as the previous-text\n // reference instead of carrying separate state — avoids the\n // double render per streaming token. Read part identity through\n // `useAuiState` so we actually subscribe to its changes instead\n // of relying on a render-time proxy reference that may be stable\n // across thread swaps.\n const aui = useAui();\n const part = useAuiState(() => aui.part());\n const [prevPart, setPrevPart] = useState(part);\n if (part !== prevPart || !text.startsWith(displayedText)) {\n setPrevPart(part);\n setDisplayedText(state.status.type === \"running\" ? \"\" : text);\n }\n\n const smoothStatusStore = useSmoothStatusStore({ optional: true });\n const setText = useCallbackRef((text: string) => {\n setDisplayedText(text);\n if (smoothStatusStore) {\n const target =\n displayedText !== text || state.status.type === \"running\"\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n });\n\n // TODO this is hacky\n useEffect(() => {\n if (smoothStatusStore) {\n const target =\n enabled && (displayedText !== text || state.status.type === \"running\")\n ? SMOOTH_STATUS\n : state.status;\n writableStore(smoothStatusStore).setState(target, true);\n }\n }, [smoothStatusStore, enabled, text, displayedText, state.status]);\n\n const [animatorRef] = useState<TextStreamAnimator>(\n new TextStreamAnimator(displayedText, setText),\n );\n\n useEffect(() => {\n animatorRef.drainMs = drainMs;\n animatorRef.maxCharIntervalMs = maxCharIntervalMs;\n animatorRef.maxCharsPerFrame = maxCharsPerFrame;\n }, [animatorRef, drainMs, maxCharIntervalMs, maxCharsPerFrame]);\n\n const animatorPartRef = useRef(part);\n useEffect(() => {\n if (!enabled) {\n animatorRef.stop();\n return;\n }\n\n // Discontinuity: part flipped, or new text breaks continuation\n // of the animator's current target. Either case requires\n // resetting the cursor — without the part check, a new part\n // whose text happens to share a prefix with the previous target\n // would keep the stale cursor and flicker.\n const partChanged = animatorPartRef.current !== part;\n animatorPartRef.current = part;\n if (partChanged || !text.startsWith(animatorRef.targetText)) {\n if (state.status.type === \"running\") {\n animatorRef.currentText = \"\";\n animatorRef.targetText = text;\n animatorRef.start();\n } else {\n animatorRef.currentText = text;\n animatorRef.targetText = text;\n animatorRef.stop();\n }\n return;\n }\n\n animatorRef.targetText = text;\n animatorRef.start();\n }, [animatorRef, enabled, text, state.status.type, part]);\n\n useEffect(() => {\n return () => {\n animatorRef.stop();\n };\n }, [animatorRef]);\n\n return useMemo(\n () =>\n enabled\n ? {\n ...state,\n text: displayedText,\n status: text === displayedText ? state.status : SMOOTH_STATUS,\n }\n : state,\n [enabled, displayedText, state, text],\n );\n};\n"],"mappings":"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"}
@@ -1,13 +1,21 @@
1
1
  import { useAuiState } from "@assistant-ui/store";
2
+ import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
2
3
  import { getPartialJsonObjectFieldState } from "assistant-stream/utils";
3
4
  //#region src/utils/useToolArgsFieldStatus.ts
4
5
  const COMPLETE_STATUS = { type: "complete" };
5
6
  const useToolArgsFieldStatus = (fieldPath) => {
6
- return useAuiState((s) => {
7
- if (s.part.type !== "tool-call") throw new Error("useToolArgsFieldStatus can only be used inside tool-call message parts");
8
- if (getPartialJsonObjectFieldState(s.part.args, fieldPath) === "complete" || s.part.status?.type === "requires-action") return COMPLETE_STATUS;
9
- return s.part.status;
10
- });
7
+ const $ = c(2);
8
+ let t0;
9
+ if ($[0] !== fieldPath) {
10
+ t0 = (s) => {
11
+ if (s.part.type !== "tool-call") throw new Error("useToolArgsFieldStatus can only be used inside tool-call message parts");
12
+ if (getPartialJsonObjectFieldState(s.part.args, fieldPath) === "complete" || s.part.status?.type === "requires-action") return COMPLETE_STATUS;
13
+ return s.part.status;
14
+ };
15
+ $[0] = fieldPath;
16
+ $[1] = t0;
17
+ } else t0 = $[1];
18
+ return useAuiState(t0);
11
19
  };
12
20
  //#endregion
13
21
  export { useToolArgsFieldStatus };
@@ -1 +1 @@
1
- {"version":3,"file":"useToolArgsFieldStatus.js","names":[],"sources":["../../src/utils/useToolArgsFieldStatus.ts"],"sourcesContent":["import { getPartialJsonObjectFieldState } from \"assistant-stream/utils\";\nimport { useAuiState } from \"@assistant-ui/store\";\n\nconst COMPLETE_STATUS = { type: \"complete\" };\n\nexport const useToolArgsFieldStatus = (fieldPath: (string | number)[]) => {\n return useAuiState((s) => {\n if (s.part.type !== \"tool-call\")\n throw new Error(\n \"useToolArgsFieldStatus can only be used inside tool-call message parts\",\n );\n\n const state = getPartialJsonObjectFieldState(s.part.args, fieldPath);\n if (state === \"complete\" || s.part.status?.type === \"requires-action\")\n return COMPLETE_STATUS;\n return s.part.status;\n });\n};\n"],"mappings":";;;AAGA,MAAM,kBAAkB,EAAE,MAAM,WAAW;AAE3C,MAAa,0BAA0B,cAAmC;CACxE,OAAO,aAAa,MAAM;EACxB,IAAI,EAAE,KAAK,SAAS,aAClB,MAAM,IAAI,MACR,wEACF;EAGF,IADc,+BAA+B,EAAE,KAAK,MAAM,SAClD,MAAM,cAAc,EAAE,KAAK,QAAQ,SAAS,mBAClD,OAAO;EACT,OAAO,EAAE,KAAK;CAChB,CAAC;AACH"}
1
+ {"version":3,"file":"useToolArgsFieldStatus.js","names":["getPartialJsonObjectFieldState","useAuiState","COMPLETE_STATUS","type","useToolArgsFieldStatus","fieldPath","$","_c","t0","s","part","Error","state","args","status"],"sources":["../../src/utils/useToolArgsFieldStatus.ts"],"sourcesContent":["import { getPartialJsonObjectFieldState } from \"assistant-stream/utils\";\nimport { useAuiState } from \"@assistant-ui/store\";\n\nconst COMPLETE_STATUS = { type: \"complete\" };\n\nexport const useToolArgsFieldStatus = (fieldPath: (string | number)[]) => {\n return useAuiState((s) => {\n if (s.part.type !== \"tool-call\")\n throw new Error(\n \"useToolArgsFieldStatus can only be used inside tool-call message parts\",\n );\n\n const state = getPartialJsonObjectFieldState(s.part.args, fieldPath);\n if (state === \"complete\" || s.part.status?.type === \"requires-action\")\n return COMPLETE_STATUS;\n return s.part.status;\n });\n};\n"],"mappings":";;;;AAGA,MAAME,kBAAkB,EAAEC,MAAM,WAAW;AAE3C,MAAaC,0BAAyBC,cAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAA,IAAAC;CAAA,IAAAF,EAAA,OAAAD,WAAA;EACjBG,MAAAC,MAAA;GACjB,IAAIA,EAACC,KAAKP,SAAU,aAClB,MAAM,IAAIQ,MACR,wEACF;GAGF,IADcX,+BAA+BS,EAACC,KAAKG,MAAOR,SACtDO,MAAU,cAAcH,EAACC,KAAKI,QAAaX,SAAK,mBAAiB,OAC5DD;GAAgB,OAClBO,EAACC,KAAKI;EAAO;EACrBR,EAAA,KAAAD;EAAAC,EAAA,KAAAE;CAAA,OAAAA,KAAAF,EAAA;CAAA,OAVML,YAAYO,EAUlB;AAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/react",
3
- "version": "0.14.18",
3
+ "version": "0.14.20",
4
4
  "description": "Open-source TypeScript/React library for building production-grade AI chat experiences",
5
5
  "keywords": [
6
6
  "ai",
@@ -55,17 +55,17 @@
55
55
  ],
56
56
  "sideEffects": false,
57
57
  "dependencies": {
58
- "@assistant-ui/core": "^0.2.14",
59
- "@assistant-ui/store": "^0.2.16",
60
- "@assistant-ui/tap": "^0.7.1",
58
+ "@assistant-ui/core": "^0.2.16",
59
+ "@assistant-ui/store": "^0.2.18",
60
+ "@assistant-ui/tap": "^0.9.1",
61
61
  "@radix-ui/primitive": "^1.1.4",
62
62
  "@radix-ui/react-compose-refs": "^1.1.3",
63
63
  "@radix-ui/react-context": "^1.1.4",
64
64
  "@radix-ui/react-primitive": "^2.1.5",
65
65
  "@radix-ui/react-use-callback-ref": "^1.1.2",
66
66
  "@radix-ui/react-use-escape-keydown": "^1.1.2",
67
- "assistant-cloud": "^0.1.32",
68
- "assistant-stream": "^0.3.21",
67
+ "assistant-cloud": "^0.1.33",
68
+ "assistant-stream": "^0.3.23",
69
69
  "nanoid": "^5.1.11",
70
70
  "radix-ui": "^1.5.0",
71
71
  "react-textarea-autosize": "^8.5.9",
@@ -90,15 +90,15 @@
90
90
  "devDependencies": {
91
91
  "@testing-library/react": "^16.3.2",
92
92
  "@types/json-schema": "^7.0.15",
93
- "@types/node": "^25.9.2",
93
+ "@types/node": "^25.9.3",
94
94
  "@types/react": "^19.2.17",
95
95
  "@types/react-dom": "^19.2.3",
96
96
  "jsdom": "^29.1.1",
97
97
  "react": "^19.2.7",
98
98
  "react-dom": "^19.2.7",
99
99
  "vitest": "^4.1.8",
100
- "@assistant-ui/vite": "0.0.4",
101
- "@assistant-ui/x-buildutils": "0.0.12"
100
+ "@assistant-ui/vite": "0.0.5",
101
+ "@assistant-ui/x-buildutils": "0.0.14"
102
102
  },
103
103
  "publishConfig": {
104
104
  "access": "public",
@@ -13,6 +13,7 @@ import type {
13
13
  AppendMessage,
14
14
  Attachment,
15
15
  CreateAttachment,
16
+ RespondToToolApprovalOptions,
16
17
  ThreadAssistantMessagePart,
17
18
  ThreadUserMessagePart,
18
19
  ThreadMessage,
@@ -21,7 +22,10 @@ import type {
21
22
  } from "@assistant-ui/core";
22
23
  import type { QueueItemState } from "@assistant-ui/core/store";
23
24
  import type { ComposerSendOptions } from "@assistant-ui/core/store";
24
- import { getThreadMessageText } from "@assistant-ui/core/internal";
25
+ import {
26
+ getThreadMessageText,
27
+ resolveToolApprovalResponse,
28
+ } from "@assistant-ui/core/internal";
25
29
  import { ModelContext, Suggestions } from "@assistant-ui/core/store";
26
30
  import { Tools, DataRenderers } from "@assistant-ui/core/react";
27
31
  import { SingleThreadList } from "./SingleThreadList";
@@ -56,6 +60,8 @@ export type ExternalThreadProps = {
56
60
  queue?: ExternalThreadQueueAdapter;
57
61
  /** Branch adapter for runtimes that track sibling variants of messages. */
58
62
  branches?: ExternalThreadBranchAdapter;
63
+ /** Callback for tool approval decisions. Absent: responding to an approval throws a capability error. */
64
+ onRespondToToolApproval?: (options: RespondToToolApprovalOptions) => void;
59
65
  };
60
66
 
61
67
  type MessageClientProps = {
@@ -65,6 +71,9 @@ type MessageClientProps = {
65
71
  onReload?: () => void;
66
72
  queue?: ExternalThreadQueueAdapter | undefined;
67
73
  branches?: ExternalThreadBranchAdapter | undefined;
74
+ onRespondToToolApproval?:
75
+ | ((options: RespondToToolApprovalOptions) => void)
76
+ | undefined;
68
77
  };
69
78
 
70
79
  // Message Client - minimal implementation
@@ -75,29 +84,28 @@ const useMessageClient = ({
75
84
  onReload,
76
85
  queue,
77
86
  branches,
87
+ onRespondToToolApproval,
78
88
  }: MessageClientProps): ClientOutput<"message"> => {
79
89
  const [isCopied, setIsCopied] = useState(false);
80
90
  const [isHovering, setIsHovering] = useState(false);
81
91
  const [isEditing, setIsEditing] = useState(false);
82
92
 
83
93
  const partClients = useClientLookup(
84
- () =>
85
- message.content.map((part, idx) => withKey(idx, PartResource({ part }))),
86
- [message.content],
94
+ message.content.map((part, idx) =>
95
+ withKey(idx, PartResource({ part, onRespondToToolApproval })),
96
+ ),
87
97
  );
88
98
 
89
99
  const attachmentClients = useClientLookup(
90
- () =>
91
- (message.attachments ?? []).map((attachment) =>
92
- withKey(
93
- attachment.id,
94
- AttachmentResource({
95
- attachment,
96
- onRemove: () => {},
97
- }),
98
- ),
100
+ (message.attachments ?? []).map((attachment) =>
101
+ withKey(
102
+ attachment.id,
103
+ AttachmentResource({
104
+ attachment,
105
+ onRemove: () => {},
106
+ }),
99
107
  ),
100
- [message.attachments],
108
+ ),
101
109
  );
102
110
 
103
111
  const handleBeginEdit = () => {
@@ -211,10 +219,16 @@ const MessageClient = resource(useMessageClient);
211
219
 
212
220
  type PartResourceProps = {
213
221
  part: ThreadAssistantMessagePart | ThreadUserMessagePart;
222
+ onRespondToToolApproval?:
223
+ | ((options: RespondToToolApprovalOptions) => void)
224
+ | undefined;
214
225
  };
215
226
 
216
227
  // Part Client - minimal implementation
217
- const usePartResource = ({ part }: PartResourceProps): ClientOutput<"part"> => {
228
+ const usePartResource = ({
229
+ part,
230
+ onRespondToToolApproval,
231
+ }: PartResourceProps): ClientOutput<"part"> => {
218
232
  const state = useMemo(
219
233
  () => ({
220
234
  ...part,
@@ -227,7 +241,26 @@ const usePartResource = ({ part }: PartResourceProps): ClientOutput<"part"> => {
227
241
  getState: () => state,
228
242
  addToolResult: () => {},
229
243
  resumeToolCall: () => {},
230
- respondToToolApproval: () => {},
244
+ respondToToolApproval: (response) => {
245
+ if (!onRespondToToolApproval)
246
+ throw new Error("Runtime does not support tool approvals.");
247
+
248
+ if (part.type !== "tool-call")
249
+ throw new Error(
250
+ "Tried to respond to tool approval on non-tool message part",
251
+ );
252
+
253
+ if (
254
+ !part.approval ||
255
+ part.approval.approved !== undefined ||
256
+ part.approval.resolution !== undefined
257
+ )
258
+ throw new Error("Tool call has no pending approval");
259
+
260
+ onRespondToToolApproval(
261
+ resolveToolApprovalResponse(part.approval, response),
262
+ );
263
+ },
231
264
  };
232
265
  };
233
266
 
@@ -325,35 +358,31 @@ const useComposerClientResource = ({
325
358
  }, [isEditing]);
326
359
 
327
360
  const attachmentClients = useClientLookup(
328
- () =>
329
- attachments.map((attachment, idx) =>
330
- withKey(
331
- attachment.id,
332
- AttachmentResource({
333
- attachment,
334
- onRemove: () => {
335
- setAttachments(attachments.filter((_, i) => i !== idx));
336
- },
337
- }),
338
- ),
361
+ attachments.map((attachment, idx) =>
362
+ withKey(
363
+ attachment.id,
364
+ AttachmentResource({
365
+ attachment,
366
+ onRemove: () => {
367
+ setAttachments(attachments.filter((_, i) => i !== idx));
368
+ },
369
+ }),
339
370
  ),
340
- [attachments],
371
+ ),
341
372
  );
342
373
 
343
374
  const queueItems = queue?.items ?? EMPTY_QUEUE_ITEMS;
344
375
  const queueItemClients = useClientLookup(
345
- () =>
346
- queueItems.map((item) =>
347
- withKey(
348
- item.id,
349
- QueueItemClient({
350
- item,
351
- onSteer: () => queue?.steer(item.id),
352
- onRemove: () => queue?.remove(item.id),
353
- }),
354
- ),
376
+ queueItems.map((item) =>
377
+ withKey(
378
+ item.id,
379
+ QueueItemClient({
380
+ item,
381
+ onSteer: () => queue?.steer(item.id),
382
+ onRemove: () => queue?.remove(item.id),
383
+ }),
355
384
  ),
356
- [queueItems],
385
+ ),
357
386
  );
358
387
 
359
388
  const state = useMemo(() => {
@@ -485,6 +514,7 @@ const useExternalThread = ({
485
514
  onCancel,
486
515
  queue,
487
516
  branches,
517
+ onRespondToToolApproval,
488
518
  }: ExternalThreadProps): ClientOutput<"thread"> => {
489
519
  const handleReload = (messageId: string) => {
490
520
  const messageIndex = messages.findIndex((m) => m.id === messageId);
@@ -496,19 +526,18 @@ const useExternalThread = ({
496
526
  };
497
527
 
498
528
  const messageClients = useClientLookup(
499
- () =>
500
- messages.map((msg, index) => {
501
- const props: MessageClientProps = {
502
- message: msg,
503
- index,
504
- onReload: () => handleReload(msg.id),
505
- queue,
506
- branches,
507
- };
508
- if (onEdit) props.onEdit = onEdit;
509
- return withKey(msg.id, MessageClient(props));
510
- }),
511
- [messages, onEdit, queue, branches],
529
+ messages.map((msg, index) => {
530
+ const props: MessageClientProps = {
531
+ message: msg,
532
+ index,
533
+ onReload: () => handleReload(msg.id),
534
+ queue,
535
+ branches,
536
+ onRespondToToolApproval,
537
+ };
538
+ if (onEdit) props.onEdit = onEdit;
539
+ return withKey(msg.id, MessageClient(props));
540
+ }),
512
541
  );
513
542
 
514
543
  const handleCancelRun = () => {
@@ -129,21 +129,19 @@ const useInMemoryThreadList = (
129
129
  };
130
130
 
131
131
  const threadListItems = useClientLookup(
132
- () =>
133
- threads.map((t) =>
134
- withKey(
135
- t.id,
136
- ThreadListItemClient({
137
- data: t,
138
- onSwitchTo: () => handleSwitchToThread(t.id),
139
- onUpdateCustom: (custom) => handleUpdateCustom(t.id, custom),
140
- onArchive: () => handleArchive(t.id),
141
- onUnarchive: () => handleUnarchive(t.id),
142
- onDelete: () => handleDelete(t.id),
143
- }),
144
- ),
132
+ threads.map((t) =>
133
+ withKey(
134
+ t.id,
135
+ ThreadListItemClient({
136
+ data: t,
137
+ onSwitchTo: () => handleSwitchToThread(t.id),
138
+ onUpdateCustom: (custom) => handleUpdateCustom(t.id, custom),
139
+ onArchive: () => handleArchive(t.id),
140
+ onUnarchive: () => handleUnarchive(t.id),
141
+ onDelete: () => handleDelete(t.id),
142
+ }),
145
143
  ),
146
- [threads],
144
+ ),
147
145
  );
148
146
 
149
147
  // Create the main thread
@@ -0,0 +1,52 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { useAui, useAuiState } from "@assistant-ui/store";
5
+
6
+ /**
7
+ * Hook that returns the elapsed wall-clock time of the current tool call in
8
+ * milliseconds, ticking once per second while the call runs.
9
+ *
10
+ * Reads `part.timing`. Returns `undefined` when the part is not a tool call,
11
+ * carries no timing, ended without a recorded completion (the duration is
12
+ * unknown), or when no message part scope is available (so kit components
13
+ * stay renderable standalone, e.g. in docs previews).
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * function ToolDuration() {
18
+ * const elapsedMs = useToolCallElapsed();
19
+ * if (elapsedMs === undefined) return null;
20
+ * return <span>{(elapsedMs / 1000).toFixed(1)}s</span>;
21
+ * }
22
+ * ```
23
+ */
24
+ export const useToolCallElapsed = (): number | undefined => {
25
+ const aui = useAui();
26
+ const hasPart = aui.part.source !== null;
27
+ const timing = useAuiState((s) =>
28
+ hasPart && s.part.type === "tool-call" ? s.part.timing : undefined,
29
+ );
30
+ const partRunning = useAuiState(
31
+ (s) =>
32
+ hasPart &&
33
+ s.part.type === "tool-call" &&
34
+ s.part.status.type === "running",
35
+ );
36
+ const running =
37
+ timing !== undefined && timing.completedAt === undefined && partRunning;
38
+ const [now, setNow] = useState(() => Date.now());
39
+
40
+ useEffect(() => {
41
+ if (!running) return undefined;
42
+ setNow(Date.now());
43
+ const id = setInterval(() => setNow(Date.now()), 1000);
44
+ return () => clearInterval(id);
45
+ }, [running]);
46
+
47
+ if (timing === undefined) return undefined;
48
+ if (timing.completedAt !== undefined)
49
+ return Math.max(0, timing.completedAt - timing.startedAt);
50
+ if (!running) return undefined;
51
+ return Math.max(0, now - timing.startedAt);
52
+ };
package/src/index.ts CHANGED
@@ -289,6 +289,12 @@ export { useThreadViewportAutoScroll } from "./primitives/thread/useThreadViewpo
289
289
  export { useScrollLock } from "./primitives/reasoning/useScrollLock";
290
290
  export { useMessageQuote } from "./hooks/useMessageQuote";
291
291
  export { useMessageTiming } from "./hooks/useMessageTiming";
292
+ export { useToolCallElapsed } from "./hooks/useToolCallElapsed";
293
+ export {
294
+ unstable_useMessageStallDetection,
295
+ type Unstable_MessageStallDetection,
296
+ type Unstable_MessageStallDetectionOptions,
297
+ } from "./unstable/useMessageStallDetection";
292
298
  export { useSmooth, type SmoothOptions } from "./utils/smooth/useSmooth";
293
299
 
294
300
  // Re-export core types from @assistant-ui/core
@@ -309,7 +315,12 @@ export type {
309
315
  GenerativeUINode,
310
316
  GenerativeUISpec,
311
317
  Unstable_AudioMessagePart,
318
+ RespondToToolApprovalOptions,
319
+ ToolApprovalOption,
320
+ ToolApprovalOptionKind,
321
+ ToolApprovalResponse,
312
322
  ToolCallMessagePart,
323
+ ToolCallTiming,
313
324
  ToolModelContentPart,
314
325
  MessageStatus,
315
326
  MessagePartStatus,
@@ -29,12 +29,16 @@ const createResponse = (
29
29
  chunks: readonly string[],
30
30
  replayContentLength?: number | string,
31
31
  ) =>
32
- new Response(createBody(chunks), {
33
- headers:
34
- replayContentLength === undefined
35
- ? undefined
36
- : { [REPLAY_CONTENT_LENGTH_HEADER]: String(replayContentLength) },
37
- });
32
+ new Response(
33
+ createBody(chunks),
34
+ replayContentLength === undefined
35
+ ? undefined
36
+ : {
37
+ headers: {
38
+ [REPLAY_CONTENT_LENGTH_HEADER]: String(replayContentLength),
39
+ },
40
+ },
41
+ );
38
42
 
39
43
  const createRenderWait = () => {
40
44
  const pending: Array<() => void> = [];
@@ -9,6 +9,7 @@ import {
9
9
  useCallback,
10
10
  useContext,
11
11
  useEffect,
12
+ useEffectEvent,
12
13
  useId,
13
14
  useMemo,
14
15
  useRef,
@@ -161,23 +162,21 @@ export const ComposerPrimitiveTriggerPopover = forwardRef<
161
162
  }),
162
163
  );
163
164
 
164
- // Wrapper changes per render, but tap-stable methods inside don't.
165
- const resourceRef = useRef(resource);
166
- resourceRef.current = resource;
165
+ const getResource = useEffectEvent(() => resource);
167
166
 
168
167
  const root = useTriggerPopoverRootContext();
169
168
  useEffect(() => {
170
169
  return root.register({
171
170
  char,
172
171
  ...(behavior ? { behavior } : {}),
173
- resource: resourceRef.current,
172
+ resource: getResource(),
174
173
  });
175
174
  }, [root, char, behavior]);
176
175
 
177
176
  const pluginRegistry = useComposerInputPluginRegistryOptional();
178
177
  useEffect(() => {
179
178
  if (!pluginRegistry) return undefined;
180
- return pluginRegistry.register(resourceRef.current);
179
+ return pluginRegistry.register(getResource());
181
180
  }, [pluginRegistry]);
182
181
 
183
182
  const open = behavior !== null && resource.open;