@assistant-ui/core 0.3.13 → 0.3.14

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 (396) hide show
  1. package/dist/adapters/speech.d.ts.map +1 -1
  2. package/dist/adapters/speech.js +9 -15
  3. package/dist/adapters/speech.js.map +1 -1
  4. package/dist/adapters/thread-history.d.ts +2 -0
  5. package/dist/adapters/thread-history.d.ts.map +1 -1
  6. package/dist/adapters/voice.d.ts.map +1 -1
  7. package/dist/adapters/voice.js +27 -15
  8. package/dist/adapters/voice.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal.d.ts +1 -1
  12. package/dist/internal.js +6 -6
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +2 -1
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/registry.d.ts.map +1 -1
  17. package/dist/model-context/registry.js +16 -4
  18. package/dist/model-context/registry.js.map +1 -1
  19. package/dist/react/RuntimeAdapter.d.ts +8 -1
  20. package/dist/react/RuntimeAdapter.d.ts.map +1 -1
  21. package/dist/react/RuntimeAdapter.js +23 -18
  22. package/dist/react/RuntimeAdapter.js.map +1 -1
  23. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  24. package/dist/react/adapters/LocalStorageThreadListAdapter.js +28 -12
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  26. package/dist/react/client/AdaptedRemoteThread.d.ts +11 -0
  27. package/dist/react/client/AdaptedRemoteThread.d.ts.map +1 -0
  28. package/dist/react/client/AdaptedRemoteThread.js +27 -0
  29. package/dist/react/client/AdaptedRemoteThread.js.map +1 -0
  30. package/dist/react/client/InMemoryThreadList.d.ts +19 -0
  31. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -0
  32. package/dist/react/client/InMemoryThreadList.js +418 -0
  33. package/dist/react/client/InMemoryThreadList.js.map +1 -0
  34. package/dist/react/client/Interactables.d.ts.map +1 -1
  35. package/dist/react/client/Interactables.js +159 -102
  36. package/dist/react/client/Interactables.js.map +1 -1
  37. package/dist/react/client/RemoteThreadList.d.ts +34 -0
  38. package/dist/react/client/RemoteThreadList.d.ts.map +1 -0
  39. package/dist/react/client/RemoteThreadList.js +826 -0
  40. package/dist/react/client/RemoteThreadList.js.map +1 -0
  41. package/dist/react/client/Tools.d.ts.map +1 -1
  42. package/dist/react/client/Tools.js +38 -27
  43. package/dist/react/client/Tools.js.map +1 -1
  44. package/dist/react/index.d.ts +7 -2
  45. package/dist/react/index.js +5 -2
  46. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  47. package/dist/react/interactables-legacy/Interactables.js +145 -110
  48. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  49. package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
  50. package/dist/react/primitive-hooks/useActionBarCopy.js +13 -18
  51. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  52. package/dist/react/primitive-hooks/useActionBarEdit.d.ts.map +1 -1
  53. package/dist/react/primitive-hooks/useActionBarEdit.js +2 -4
  54. package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
  55. package/dist/react/primitive-hooks/useActionBarReload.d.ts.map +1 -1
  56. package/dist/react/primitive-hooks/useActionBarReload.js +2 -4
  57. package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
  58. package/dist/react/primitive-hooks/useBranchPickerNext.d.ts.map +1 -1
  59. package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -6
  60. package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
  61. package/dist/react/primitive-hooks/useBranchPickerPrevious.d.ts.map +1 -1
  62. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -6
  63. package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
  64. package/dist/react/primitive-hooks/useComposerCancel.d.ts.map +1 -1
  65. package/dist/react/primitive-hooks/useComposerCancel.js +2 -4
  66. package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
  67. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  68. package/dist/react/primitive-hooks/useComposerSend.js +2 -4
  69. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  70. package/dist/react/primitive-hooks/useSuggestionTrigger.d.ts.map +1 -1
  71. package/dist/react/primitive-hooks/useSuggestionTrigger.js +6 -2
  72. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  73. package/dist/react/primitives/message/MessageParts.d.ts +13 -0
  74. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  75. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  76. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  77. package/dist/react/primitives/thread/ThreadMessages.js +1 -1
  78. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  79. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +22 -10
  80. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  81. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +109 -71
  82. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  83. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +8 -2
  84. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  85. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +40 -13
  86. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  87. package/dist/react/runtimes/RemoteThreadResource.d.ts +30 -0
  88. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -0
  89. package/dist/react/runtimes/RemoteThreadResource.js +134 -0
  90. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -0
  91. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts +3 -10
  92. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
  93. package/dist/react/runtimes/RuntimeAdapterProvider.js +3 -6
  94. package/dist/react/runtimes/RuntimeAdapterProvider.js.map +1 -1
  95. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts +17 -0
  96. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts.map +1 -0
  97. package/dist/react/runtimes/assistant-transport/commandQueue.js +55 -0
  98. package/dist/react/runtimes/assistant-transport/commandQueue.js.map +1 -0
  99. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts +11 -0
  100. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
  101. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js +165 -0
  102. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js.map +1 -0
  103. package/dist/react/runtimes/assistant-transport/runManager.d.ts +15 -0
  104. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -0
  105. package/dist/react/runtimes/assistant-transport/runManager.js +86 -0
  106. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -0
  107. package/dist/react/runtimes/assistant-transport/types.d.ts +130 -0
  108. package/dist/react/runtimes/assistant-transport/types.d.ts.map +1 -0
  109. package/dist/react/runtimes/assistant-transport/types.js +0 -0
  110. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts +14 -0
  111. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -0
  112. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +296 -0
  113. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -0
  114. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts +7 -0
  115. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts.map +1 -0
  116. package/dist/react/runtimes/assistant-transport/useConvertedState.js +25 -0
  117. package/dist/react/runtimes/assistant-transport/useConvertedState.js.map +1 -0
  118. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts +7 -0
  119. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts.map +1 -0
  120. package/dist/react/runtimes/assistant-transport/useLatestRef.js +27 -0
  121. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -0
  122. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +39 -13
  124. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  125. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +2 -1
  126. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  127. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -3
  128. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  129. package/dist/react/runtimes/cloud/auiV0.d.ts +4 -0
  130. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  131. package/dist/react/runtimes/cloud/auiV0.js +7 -0
  132. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  133. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  134. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +22 -12
  135. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  136. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts +16 -0
  137. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts.map +1 -0
  138. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js +36 -0
  139. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -0
  140. package/dist/react/runtimes/external-message-converter.js +14 -14
  141. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  142. package/dist/react/runtimes/useExternalStoreRuntime.d.ts.map +1 -1
  143. package/dist/react/runtimes/useExternalStoreRuntime.js +44 -28
  144. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  145. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
  146. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  147. package/dist/react/runtimes/useRuntimeAdapters.d.ts +9 -0
  148. package/dist/react/runtimes/useRuntimeAdapters.d.ts.map +1 -0
  149. package/dist/react/runtimes/useRuntimeAdapters.js +20 -0
  150. package/dist/react/runtimes/useRuntimeAdapters.js.map +1 -0
  151. package/dist/runtime/api/composer-runtime.d.ts +5 -0
  152. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  153. package/dist/runtime/api/composer-runtime.js.map +1 -1
  154. package/dist/runtime/api/message-runtime.js +1 -1
  155. package/dist/runtime/api/thread-list-runtime.d.ts +1 -0
  156. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  157. package/dist/runtime/api/thread-list-runtime.js +4 -2
  158. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  159. package/dist/runtime/api/thread-runtime.d.ts +12 -2
  160. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  161. package/dist/runtime/api/thread-runtime.js +8 -2
  162. package/dist/runtime/api/thread-runtime.js.map +1 -1
  163. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  164. package/dist/runtime/base/base-composer-runtime-core.js +8 -7
  165. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  166. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
  167. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  168. package/dist/runtime/base/base-thread-runtime-core.js +3 -3
  169. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  170. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +0 -1
  171. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  172. package/dist/runtime/base/default-thread-composer-runtime-core.js +10 -4
  173. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  174. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  175. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  176. package/dist/runtime/internal.js +5 -5
  177. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  178. package/dist/runtime/queue/message-queue.js +2 -1
  179. package/dist/runtime/queue/message-queue.js.map +1 -1
  180. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts +8 -0
  181. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts.map +1 -0
  182. package/dist/runtime/utils/thread-runtime-lifecycle.js +11 -0
  183. package/dist/runtime/utils/thread-runtime-lifecycle.js.map +1 -0
  184. package/dist/runtimes/external-store/external-store-shared-options.js.map +1 -1
  185. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  186. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +4 -1
  187. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  188. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +8 -0
  189. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  190. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +35 -13
  191. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  192. package/dist/runtimes/internal.d.ts +1 -1
  193. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
  194. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  195. package/dist/runtimes/local/local-thread-runtime-core.js +53 -13
  196. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  197. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
  198. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  199. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +3 -0
  200. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  201. package/dist/runtimes/remote-thread-list/empty-thread-core.js +3 -0
  202. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  203. package/dist/runtimes/remote-thread-list/types.d.ts +29 -1
  204. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  205. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +1 -12
  206. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  207. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +67 -38
  208. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  209. package/dist/store/clients/suggestions.js +2 -2
  210. package/dist/store/clients/suggestions.js.map +1 -1
  211. package/dist/store/clients/thread-selection-events.d.ts +10 -0
  212. package/dist/store/clients/thread-selection-events.d.ts.map +1 -0
  213. package/dist/store/clients/thread-selection-events.js +40 -0
  214. package/dist/store/clients/thread-selection-events.js.map +1 -0
  215. package/dist/store/env.d.ts +5 -0
  216. package/dist/store/env.d.ts.map +1 -0
  217. package/dist/store/env.js +12 -0
  218. package/dist/store/env.js.map +1 -0
  219. package/dist/store/index.d.ts +5 -3
  220. package/dist/store/index.js +4 -2
  221. package/dist/store/internal.d.ts +4 -1
  222. package/dist/store/internal.js +4 -1
  223. package/dist/store/primitive-predicates.d.ts +19 -0
  224. package/dist/store/primitive-predicates.d.ts.map +1 -0
  225. package/dist/store/primitive-predicates.js +19 -0
  226. package/dist/store/primitive-predicates.js.map +1 -0
  227. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  228. package/dist/store/runtime-clients/attachment-runtime-client.js +18 -11
  229. package/dist/store/runtime-clients/attachment-runtime-client.js.map +1 -1
  230. package/dist/store/runtime-clients/handle-runtime-action.d.ts +5 -0
  231. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -0
  232. package/dist/store/runtime-clients/handle-runtime-action.js +12 -0
  233. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -0
  234. package/dist/store/runtime-clients/handle-thread-list-action.d.ts +5 -0
  235. package/dist/store/runtime-clients/handle-thread-list-action.d.ts.map +1 -0
  236. package/dist/store/runtime-clients/handle-thread-list-action.js +7 -0
  237. package/dist/store/runtime-clients/handle-thread-list-action.js.map +1 -0
  238. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  239. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +62 -29
  240. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  241. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  242. package/dist/store/runtime-clients/thread-list-runtime-client.js +5 -6
  243. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  244. package/dist/store/scopes/composer.d.ts +9 -4
  245. package/dist/store/scopes/composer.d.ts.map +1 -1
  246. package/dist/store/scopes/thread-list-item.d.ts +7 -6
  247. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  248. package/dist/store/scopes/thread.d.ts +8 -7
  249. package/dist/store/scopes/thread.d.ts.map +1 -1
  250. package/dist/store/scopes/threads.d.ts +17 -1
  251. package/dist/store/scopes/threads.d.ts.map +1 -1
  252. package/dist/subscribable/subscribable.d.ts +2 -1
  253. package/dist/subscribable/subscribable.d.ts.map +1 -1
  254. package/dist/subscribable/subscribable.js +23 -18
  255. package/dist/subscribable/subscribable.js.map +1 -1
  256. package/dist/utils/composite-context-provider.d.ts +1 -0
  257. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  258. package/dist/utils/composite-context-provider.js +25 -9
  259. package/dist/utils/composite-context-provider.js.map +1 -1
  260. package/dist/utils/notify-event-listeners.d.ts +2 -2
  261. package/dist/utils/notify-event-listeners.d.ts.map +1 -1
  262. package/dist/utils/notify-event-listeners.js +2 -2
  263. package/dist/utils/notify-event-listeners.js.map +1 -1
  264. package/package.json +7 -7
  265. package/src/adapters/speech.test.ts +118 -1
  266. package/src/adapters/speech.ts +21 -22
  267. package/src/adapters/thread-history.ts +2 -0
  268. package/src/adapters/voice.test.ts +69 -0
  269. package/src/adapters/voice.ts +27 -27
  270. package/src/index.ts +1 -0
  271. package/src/model-context/frame/host.test.ts +23 -0
  272. package/src/model-context/frame/host.ts +2 -1
  273. package/src/model-context/registry.test.ts +131 -0
  274. package/src/model-context/registry.ts +16 -6
  275. package/src/react/RuntimeAdapter.test.ts +2 -0
  276. package/src/react/RuntimeAdapter.ts +20 -8
  277. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +34 -17
  278. package/src/react/client/AdaptedRemoteThread.ts +41 -0
  279. package/src/react/client/InMemoryThreadList.test.tsx +148 -0
  280. package/src/react/client/InMemoryThreadList.ts +290 -0
  281. package/src/react/client/Interactables.test.ts +326 -2
  282. package/src/react/client/Interactables.ts +227 -110
  283. package/src/react/client/RemoteThreadList.test.ts +1026 -0
  284. package/src/react/client/RemoteThreadList.ts +1067 -0
  285. package/src/react/client/Tools.ts +36 -29
  286. package/src/react/index.ts +13 -0
  287. package/src/react/interactables-legacy/Interactables.test.ts +317 -0
  288. package/src/react/interactables-legacy/Interactables.ts +204 -128
  289. package/src/react/primitive-hooks/useActionBarCopy.ts +2 -7
  290. package/src/react/primitive-hooks/useActionBarEdit.ts +2 -1
  291. package/src/react/primitive-hooks/useActionBarReload.ts +2 -6
  292. package/src/react/primitive-hooks/useBranchPickerNext.ts +2 -7
  293. package/src/react/primitive-hooks/useBranchPickerPrevious.ts +2 -7
  294. package/src/react/primitive-hooks/useComposerCancel.ts +2 -1
  295. package/src/react/primitive-hooks/useComposerSend.ts +2 -5
  296. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +11 -0
  297. package/src/react/primitive-hooks/useSuggestionTrigger.ts +4 -5
  298. package/src/react/primitives/message/MessageParts.tsx +0 -1
  299. package/src/react/primitives/thread/ThreadMessages.tsx +2 -1
  300. package/src/react/runtimes/RemoteThreadListHookInstanceManager.retry.test.tsx +116 -0
  301. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +1 -1
  302. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +57 -6
  303. package/src/react/runtimes/RemoteThreadListHookInstanceManager.title.test.tsx +272 -0
  304. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +159 -124
  305. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +59 -20
  306. package/src/react/runtimes/RemoteThreadResource.test.ts +141 -0
  307. package/src/react/runtimes/RemoteThreadResource.ts +231 -0
  308. package/src/react/runtimes/RuntimeAdapterProvider.tsx +8 -20
  309. package/src/react/runtimes/assistant-transport/commandQueue.ts +68 -0
  310. package/src/react/runtimes/assistant-transport/replayBoundaryStream.test.ts +455 -0
  311. package/src/react/runtimes/assistant-transport/replayBoundaryStream.ts +172 -0
  312. package/src/react/runtimes/assistant-transport/runManager.ts +124 -0
  313. package/src/react/runtimes/assistant-transport/transport-delivery.test.tsx +79 -0
  314. package/src/react/runtimes/assistant-transport/transport-parentid.test.tsx +97 -0
  315. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +414 -0
  316. package/src/react/runtimes/assistant-transport/types.ts +170 -0
  317. package/src/react/runtimes/assistant-transport/useAssistantTransport.spec.md +141 -0
  318. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +625 -0
  319. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +481 -0
  320. package/src/react/runtimes/assistant-transport/useConvertedState.ts +20 -0
  321. package/src/react/runtimes/assistant-transport/useLatestRef.ts +9 -0
  322. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +131 -6
  323. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +61 -15
  324. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +6 -4
  325. package/src/react/runtimes/cloud/auiV0.ts +17 -1
  326. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +36 -21
  327. package/src/react/runtimes/cloud/useCloudThreadListRuntime.test.tsx +56 -0
  328. package/src/react/runtimes/cloud/useCloudThreadListRuntime.ts +31 -0
  329. package/src/react/runtimes/external-message-converter.test.ts +143 -0
  330. package/src/react/runtimes/external-message-converter.ts +41 -41
  331. package/src/react/runtimes/remote-thread-hook-on-tap.test.ts +58 -0
  332. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +83 -0
  333. package/src/react/runtimes/useExternalStoreRuntime.suggestions.test.tsx +13 -3
  334. package/src/react/runtimes/useExternalStoreRuntime.ts +7 -0
  335. package/src/react/runtimes/useExternalStoreSharedOptions.ts +3 -1
  336. package/src/react/runtimes/useLocalRuntime.test.tsx +130 -4
  337. package/src/react/runtimes/useRuntimeAdapters.test.tsx +62 -0
  338. package/src/react/runtimes/useRuntimeAdapters.ts +28 -0
  339. package/src/runtime/api/composer-runtime.ts +5 -0
  340. package/src/runtime/api/thread-list-runtime.ts +3 -1
  341. package/src/runtime/api/thread-runtime.ts +20 -2
  342. package/src/runtime/base/base-composer-runtime-core.ts +6 -5
  343. package/src/runtime/base/base-thread-runtime-core.test.ts +191 -0
  344. package/src/runtime/base/base-thread-runtime-core.ts +11 -2
  345. package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -4
  346. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  347. package/src/runtime/queue/message-queue.ts +2 -1
  348. package/src/runtime/utils/thread-runtime-lifecycle.ts +18 -0
  349. package/src/runtimes/external-store/external-store-shared-options.ts +3 -1
  350. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
  351. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +99 -34
  352. package/src/runtimes/local/local-thread-runtime-core.test.ts +373 -4
  353. package/src/runtimes/local/local-thread-runtime-core.ts +79 -14
  354. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +4 -0
  355. package/src/runtimes/remote-thread-list/empty-thread-core.ts +4 -0
  356. package/src/runtimes/remote-thread-list/types.ts +30 -0
  357. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -6
  358. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +243 -0
  359. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +127 -61
  360. package/src/store/clients/model-context-client.test.ts +16 -8
  361. package/src/store/clients/suggestions.ts +2 -2
  362. package/src/store/clients/thread-selection-events.ts +21 -0
  363. package/src/store/env.ts +11 -0
  364. package/src/store/index.ts +14 -1
  365. package/src/store/internal.ts +13 -0
  366. package/src/store/primitive-predicates.test.ts +184 -0
  367. package/src/store/primitive-predicates.ts +43 -0
  368. package/src/store/runtime-clients/attachment-runtime-client.test.ts +41 -0
  369. package/src/store/runtime-clients/attachment-runtime-client.ts +2 -1
  370. package/src/store/runtime-clients/handle-runtime-action.ts +11 -0
  371. package/src/store/runtime-clients/handle-thread-list-action.test.ts +69 -0
  372. package/src/store/runtime-clients/handle-thread-list-action.ts +6 -0
  373. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +15 -7
  374. package/src/store/runtime-clients/thread-list-runtime-client.ts +9 -6
  375. package/src/store/scopes/composer.ts +9 -4
  376. package/src/store/scopes/thread-list-item.ts +7 -6
  377. package/src/store/scopes/thread.ts +8 -7
  378. package/src/store/scopes/threads.ts +14 -0
  379. package/src/subscribable/subscribable.test.ts +77 -1
  380. package/src/subscribable/subscribable.ts +38 -23
  381. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-unarchive.test.ts +25 -0
  382. package/src/tests/auiV0Encode.test.ts +66 -0
  383. package/src/tests/base-composer-runtime-core-send.test.ts +30 -0
  384. package/src/tests/base-composer-runtime-core.test.ts +28 -0
  385. package/src/tests/composer-can-cancel.test.ts +101 -0
  386. package/src/tests/external-store-thread-list-runtime-core.test.ts +101 -0
  387. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +191 -1
  388. package/src/tests/external-store-thread-runtime-core.test.ts +124 -0
  389. package/src/tests/interactables-toolui-migration.test.tsx +66 -0
  390. package/src/tests/message-queue.test.ts +28 -0
  391. package/src/tests/session-reset-notification.test.ts +145 -0
  392. package/src/tests/thread-switch-events.test.tsx +99 -1
  393. package/src/tests/tools-scope-migration.test.tsx +58 -0
  394. package/src/utils/composite-context-provider.test.ts +139 -0
  395. package/src/utils/composite-context-provider.ts +26 -8
  396. package/src/utils/notify-event-listeners.ts +10 -5
@@ -1,4 +1,5 @@
1
1
  import type { Unsubscribe } from "../types/unsubscribe";
2
+ import { notifyEventListeners } from "../utils/notify-event-listeners";
2
3
 
3
4
  export namespace RealtimeVoiceAdapter {
4
5
  export type Status =
@@ -57,22 +58,6 @@ export type VoiceSessionHelpers = {
57
58
  isDisposed: () => boolean;
58
59
  };
59
60
 
60
- const notifyListeners = <T>(
61
- listeners: ReadonlySet<(value: T) => void>,
62
- value: T,
63
- ) => {
64
- for (const listener of listeners) {
65
- try {
66
- listener(value);
67
- } catch (error) {
68
- console.error(
69
- "[assistant-ui] Voice session listener threw an error",
70
- error,
71
- );
72
- }
73
- }
74
- };
75
-
76
61
  export function createVoiceSession(
77
62
  options: { abortSignal?: AbortSignal },
78
63
  setup: (helpers: VoiceSessionHelpers) => Promise<VoiceSessionControls>,
@@ -87,7 +72,17 @@ export function createVoiceSession(
87
72
  let currentStatus: RealtimeVoiceAdapter.Status = { type: "starting" };
88
73
  let isMuted = false;
89
74
  let disposed = false;
75
+ let disconnected = false;
90
76
  let controls: VoiceSessionControls | null = null;
77
+ const abortSignal = options.abortSignal;
78
+ let abortHandler: (() => void) | undefined;
79
+
80
+ const detachAbortHandler = () => {
81
+ if (abortHandler) {
82
+ abortSignal?.removeEventListener("abort", abortHandler);
83
+ abortHandler = undefined;
84
+ }
85
+ };
91
86
 
92
87
  const cleanup = () => {
93
88
  disposed = true;
@@ -101,25 +96,25 @@ export function createVoiceSession(
101
96
  setStatus: (status) => {
102
97
  if (disposed) return;
103
98
  currentStatus = status;
104
- notifyListeners(statusCbs, status);
99
+ notifyEventListeners(statusCbs, status, "Voice session");
105
100
  },
106
101
  end: (reason, error?) => {
107
102
  if (disposed) return;
108
103
  currentStatus = { type: "ended", reason, error };
109
- notifyListeners(statusCbs, currentStatus);
104
+ notifyEventListeners(statusCbs, currentStatus, "Voice session");
110
105
  cleanup();
111
106
  },
112
107
  emitTranscript: (item) => {
113
108
  if (disposed) return;
114
- notifyListeners(transcriptCbs, item);
109
+ notifyEventListeners(transcriptCbs, item, "Voice session");
115
110
  },
116
111
  emitMode: (mode) => {
117
112
  if (disposed) return;
118
- notifyListeners(modeCbs, mode);
113
+ notifyEventListeners(modeCbs, mode, "Voice session");
119
114
  },
120
115
  emitVolume: (volume) => {
121
116
  if (disposed) return;
122
- notifyListeners(volumeCbs, volume);
117
+ notifyEventListeners(volumeCbs, volume, "Voice session");
123
118
  },
124
119
  isDisposed: () => disposed,
125
120
  };
@@ -132,8 +127,14 @@ export function createVoiceSession(
132
127
  return isMuted;
133
128
  },
134
129
  disconnect: () => {
135
- controls?.disconnect();
136
- cleanup();
130
+ if (disconnected) return;
131
+ disconnected = true;
132
+ detachAbortHandler();
133
+ try {
134
+ controls?.disconnect();
135
+ } finally {
136
+ cleanup();
137
+ }
137
138
  },
138
139
  mute: () => {
139
140
  controls?.mute();
@@ -161,10 +162,9 @@ export function createVoiceSession(
161
162
  },
162
163
  };
163
164
 
164
- if (options.abortSignal) {
165
- options.abortSignal.addEventListener("abort", () => session.disconnect(), {
166
- once: true,
167
- });
165
+ if (abortSignal) {
166
+ abortHandler = () => session.disconnect();
167
+ abortSignal.addEventListener("abort", abortHandler, { once: true });
168
168
  }
169
169
 
170
170
  const doSetup = async () => {
package/src/index.ts CHANGED
@@ -347,6 +347,7 @@ export {
347
347
  // Remote Thread List (user-facing)
348
348
  export type {
349
349
  RemoteThreadListAdapter,
350
+ RuntimeAdapters,
350
351
  RemoteThreadListOptions,
351
352
  RemoteThreadListProviderComponent,
352
353
  RemoteThreadInitializeResponse,
@@ -144,4 +144,27 @@ describe("AssistantFrameHost", () => {
144
144
  );
145
145
  expect(postMessage).toHaveBeenCalledOnce();
146
146
  });
147
+
148
+ it("isolates subscriber errors while applying context updates", () => {
149
+ const { dispatchMessage, host } = createHost();
150
+ const error = new Error("subscriber failed");
151
+ const laterSubscriber = vi.fn();
152
+
153
+ host.subscribe(() => {
154
+ throw error;
155
+ });
156
+ host.subscribe(laterSubscriber);
157
+
158
+ expect(() =>
159
+ dispatchMessage({
160
+ type: "model-context-update",
161
+ context: { system: "frame instructions" },
162
+ }),
163
+ ).toThrow(error);
164
+
165
+ expect(host.getModelContext().system).toBe("frame instructions");
166
+ expect(laterSubscriber).toHaveBeenCalledOnce();
167
+
168
+ host.dispose();
169
+ });
147
170
  });
@@ -1,6 +1,7 @@
1
1
  import type { ModelContextProvider, ModelContext } from "../types";
2
2
  import type { Unsubscribe } from "../../types/unsubscribe";
3
3
  import type { Tool } from "assistant-stream";
4
+ import { notifySubscribers as notifyStateSubscribers } from "../../subscribable/subscribable";
4
5
  import {
5
6
  type FrameMessage,
6
7
  FRAME_MESSAGE_CHANNEL,
@@ -207,7 +208,7 @@ export class AssistantFrameHost implements ModelContextProvider {
207
208
  }
208
209
 
209
210
  private notifySubscribers() {
210
- this._subscribers.forEach((callback) => callback());
211
+ notifyStateSubscribers(this._subscribers);
211
212
  }
212
213
 
213
214
  getModelContext(): ModelContext {
@@ -0,0 +1,131 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { ModelContextRegistry } from "./registry";
3
+
4
+ describe("ModelContextRegistry", () => {
5
+ it("notifies every subscriber and rethrows when a registration subscriber throws", () => {
6
+ const registry = new ModelContextRegistry();
7
+ const error = new Error("subscriber failed");
8
+ const laterSubscriber = vi.fn();
9
+
10
+ registry.subscribe(() => {
11
+ throw error;
12
+ });
13
+ registry.subscribe(laterSubscriber);
14
+
15
+ expect(() => registry.addTool({ toolName: "search" })).toThrow(error);
16
+
17
+ expect(registry.getModelContext().tools).toHaveProperty("search");
18
+ expect(laterSubscriber).toHaveBeenCalledTimes(1);
19
+ });
20
+
21
+ it("keeps a provider registered when a registration subscriber throws", () => {
22
+ const registry = new ModelContextRegistry();
23
+ const error = new Error("subscriber failed");
24
+ const laterSubscriber = vi.fn();
25
+
26
+ registry.subscribe(() => {
27
+ throw error;
28
+ });
29
+ registry.subscribe(laterSubscriber);
30
+
31
+ expect(() =>
32
+ registry.addProvider({
33
+ getModelContext: () => ({ system: "provider instructions" }),
34
+ }),
35
+ ).toThrow(error);
36
+
37
+ expect(registry.getModelContext().system).toBe("provider instructions");
38
+ expect(laterSubscriber).toHaveBeenCalledTimes(1);
39
+ });
40
+
41
+ it("notifies every subscriber and rethrows when registering instructions", () => {
42
+ const registry = new ModelContextRegistry();
43
+ const error = new Error("subscriber failed");
44
+ const laterSubscriber = vi.fn();
45
+
46
+ registry.subscribe(() => {
47
+ throw error;
48
+ });
49
+ registry.subscribe(laterSubscriber);
50
+
51
+ expect(() => registry.addInstruction("be concise")).toThrow(error);
52
+
53
+ expect(registry.getModelContext().system).toContain("be concise");
54
+ expect(laterSubscriber).toHaveBeenCalledTimes(1);
55
+ });
56
+
57
+ it("rethrows the provider subscription failure when a rollback subscriber throws", () => {
58
+ const registry = new ModelContextRegistry();
59
+ const subscriptionError = new Error("subscription failed");
60
+ const subscriberError = new Error("subscriber failed");
61
+ const consoleError = vi
62
+ .spyOn(console, "error")
63
+ .mockImplementation(() => {});
64
+
65
+ registry.subscribe(() => {
66
+ throw subscriberError;
67
+ });
68
+
69
+ try {
70
+ expect(() =>
71
+ registry.addProvider({
72
+ getModelContext: () => ({ system: "provider instructions" }),
73
+ subscribe: () => {
74
+ throw subscriptionError;
75
+ },
76
+ }),
77
+ ).toThrow(subscriptionError);
78
+
79
+ expect(registry.getModelContext().system).toBeUndefined();
80
+ expect(consoleError).toHaveBeenCalledWith(subscriberError);
81
+ } finally {
82
+ consoleError.mockRestore();
83
+ }
84
+ });
85
+
86
+ it("rolls back providers that fail to subscribe", () => {
87
+ const registry = new ModelContextRegistry();
88
+ const error = new Error("subscription failed");
89
+ const observedSystems: Array<string | undefined> = [];
90
+
91
+ registry.subscribe(() => {
92
+ observedSystems.push(registry.getModelContext().system);
93
+ });
94
+
95
+ expect(() =>
96
+ registry.addProvider({
97
+ getModelContext: () => ({ system: "provider instructions" }),
98
+ subscribe: (callback) => {
99
+ callback();
100
+ throw error;
101
+ },
102
+ }),
103
+ ).toThrow(error);
104
+
105
+ expect(registry.getModelContext().system).toBeUndefined();
106
+ expect(observedSystems).toEqual(["provider instructions", undefined]);
107
+ });
108
+
109
+ it("notifies every subscriber and rethrows on provider updates", () => {
110
+ const registry = new ModelContextRegistry();
111
+ const error = new Error("subscriber failed");
112
+ const laterSubscriber = vi.fn();
113
+ let publishUpdate = () => {};
114
+
115
+ registry.addProvider({
116
+ getModelContext: () => ({ system: "provider instructions" }),
117
+ subscribe: (callback) => {
118
+ publishUpdate = callback;
119
+ return () => {};
120
+ },
121
+ });
122
+
123
+ registry.subscribe(() => {
124
+ throw error;
125
+ });
126
+ registry.subscribe(laterSubscriber);
127
+
128
+ expect(() => publishUpdate()).toThrow(error);
129
+ expect(laterSubscriber).toHaveBeenCalledTimes(1);
130
+ });
131
+ });
@@ -7,6 +7,7 @@ import {
7
7
  type AssistantInstructionsConfig,
8
8
  } from "./types";
9
9
  import type { Unsubscribe } from "../types/unsubscribe";
10
+ import { notifySubscribers as notifyStateSubscribers } from "../subscribable/subscribable";
10
11
  import type {
11
12
  ModelContextRegistryToolHandle,
12
13
  ModelContextRegistryInstructionHandle,
@@ -75,9 +76,7 @@ export class ModelContextRegistry implements ModelContextProvider {
75
76
  }
76
77
 
77
78
  private notifySubscribers(): void {
78
- for (const callback of this._subscribers) {
79
- callback();
80
- }
79
+ notifyStateSubscribers(this._subscribers);
81
80
  }
82
81
 
83
82
  addTool<TArgs extends Record<string, unknown>, TResult>(
@@ -144,9 +143,20 @@ export class ModelContextRegistry implements ModelContextProvider {
144
143
 
145
144
  this._providers.set(id, provider);
146
145
 
147
- const unsubscribe = provider.subscribe?.(() => {
148
- this.notifySubscribers();
149
- });
146
+ let unsubscribe: Unsubscribe | undefined;
147
+ try {
148
+ unsubscribe = provider.subscribe?.(() => {
149
+ this.notifySubscribers();
150
+ });
151
+ } catch (error) {
152
+ this._providers.delete(id);
153
+ try {
154
+ this.notifySubscribers();
155
+ } catch (notifyError) {
156
+ console.error(notifyError);
157
+ }
158
+ throw error;
159
+ }
150
160
  this._providerUnsubscribes.set(id, unsubscribe);
151
161
 
152
162
  this.notifySubscribers();
@@ -54,6 +54,7 @@ describe("RuntimeAdapter via the neutral store entry", () => {
54
54
  const handle = createAssistantClient(
55
55
  AuiConfig({ threads: RuntimeAdapter(runtime) }),
56
56
  );
57
+ handle.subscribe(() => {});
57
58
  const aui = handle.getClient();
58
59
 
59
60
  flushTapSync(() => aui.composer.setText("hello"));
@@ -75,6 +76,7 @@ describe("RuntimeAdapter via the neutral store entry", () => {
75
76
  const handle = createAssistantClient(
76
77
  AuiConfig({ threads: RuntimeAdapter(runtime) }),
77
78
  );
79
+ handle.subscribe(() => {});
78
80
 
79
81
  // Dev mode double-mounts the root (StrictMode emulation), so assert
80
82
  // presence rather than an exact count
@@ -1,5 +1,5 @@
1
- import { useEffect } from "react";
2
1
  import { useResource, resource } from "@assistant-ui/tap";
2
+ import type { AssistantClient, ScopesConfig } from "@assistant-ui/store";
3
3
  import type { AssistantRuntime } from "..";
4
4
  import {
5
5
  baseRuntimeAdapterTransformScopes,
@@ -8,6 +8,7 @@ import {
8
8
  import {
9
9
  attachTransformScopes,
10
10
  useAssistantClientRef,
11
+ useAssistantScopeEffect,
11
12
  } from "@assistant-ui/store/client";
12
13
  import { DataRenderers } from "./client/DataRenderers";
13
14
  import { Tools } from "./client/Tools";
@@ -15,11 +16,12 @@ import { Tools } from "./client/Tools";
15
16
  const useRuntimeAdapter = (runtime: AssistantRuntime) => {
16
17
  const clientRef = useAssistantClientRef();
17
18
 
18
- useEffect(() => {
19
- return runtime.registerModelContextProvider(
20
- clientRef.current!.modelContext(),
21
- );
22
- }, [runtime, clientRef]);
19
+ useAssistantScopeEffect(
20
+ "modelContext",
21
+ () =>
22
+ runtime.registerModelContextProvider(clientRef.current!.modelContext()),
23
+ [runtime],
24
+ );
23
25
 
24
26
  return useResource(
25
27
  ThreadListClient({
@@ -31,7 +33,15 @@ const useRuntimeAdapter = (runtime: AssistantRuntime) => {
31
33
 
32
34
  export const RuntimeAdapter = resource(useRuntimeAdapter);
33
35
 
34
- attachTransformScopes(useRuntimeAdapter, (scopes, parent) => {
36
+ /**
37
+ * The scope defaults `RuntimeAdapter` installs when it is used as the `threads`
38
+ * config entry. Adapter packages that wrap a runtime in their own config entry
39
+ * attach this to the wrapping resource for scope parity with `RuntimeAdapter`.
40
+ */
41
+ export const runtimeAdapterTransformScopes = (
42
+ scopes: ScopesConfig,
43
+ parent: AssistantClient,
44
+ ): void => {
35
45
  baseRuntimeAdapterTransformScopes(scopes, parent);
36
46
 
37
47
  if (!scopes.tools && parent.tools.source === null) {
@@ -41,4 +51,6 @@ attachTransformScopes(useRuntimeAdapter, (scopes, parent) => {
41
51
  if (!scopes.dataRenderers && parent.dataRenderers.source === null) {
42
52
  scopes.dataRenderers = DataRenderers();
43
53
  }
44
- });
54
+ };
55
+
56
+ attachTransformScopes(useRuntimeAdapter, runtimeAdapterTransformScopes);
@@ -21,7 +21,10 @@ import type {
21
21
  ExportedMessageRepository,
22
22
  ExportedMessageRepositoryItem,
23
23
  } from "../../internal";
24
- import { RuntimeAdapterProvider } from "../runtimes/RuntimeAdapterProvider";
24
+ import {
25
+ RuntimeAdapterProvider,
26
+ type RuntimeAdapters,
27
+ } from "../runtimes/RuntimeAdapterProvider";
25
28
  import type { TitleGenerationAdapter } from "./TitleGenerationAdapter";
26
29
 
27
30
  export type AsyncStorageLike = {
@@ -338,29 +341,40 @@ class AsyncStorageHistoryAdapter implements ThreadHistoryAdapter {
338
341
  }
339
342
  }
340
343
 
344
+ const useLocalStorageThreadAdapters = (
345
+ storage: AsyncStorageLike,
346
+ prefix: string,
347
+ mutationQueue: KeyedMutationQueue,
348
+ ): RuntimeAdapters => {
349
+ const aui = useAui();
350
+ // Not useEffectEvent: history adapter methods run during render (SSR load).
351
+ const auiRef = useRef(aui);
352
+ useEffect(() => {
353
+ auiRef.current = aui;
354
+ });
355
+ const [history] = useState(
356
+ () =>
357
+ new AsyncStorageHistoryAdapter(
358
+ storage,
359
+ () => auiRef.current,
360
+ prefix,
361
+ mutationQueue,
362
+ ),
363
+ );
364
+ return useMemo(() => ({ history }), [history]);
365
+ };
366
+
341
367
  const createHistoryProvider = (
342
368
  storage: AsyncStorageLike,
343
369
  prefix: string,
344
370
  mutationQueue: KeyedMutationQueue,
345
371
  ): FC<PropsWithChildren> => {
346
372
  const Provider: FC<PropsWithChildren> = ({ children }) => {
347
- const aui = useAui();
348
- // Not useEffectEvent: history adapter methods run during render (SSR load).
349
- const auiRef = useRef(aui);
350
- useEffect(() => {
351
- auiRef.current = aui;
352
- });
353
- const [history] = useState(
354
- () =>
355
- new AsyncStorageHistoryAdapter(
356
- storage,
357
- () => auiRef.current,
358
- prefix,
359
- mutationQueue,
360
- ),
373
+ const adapters = useLocalStorageThreadAdapters(
374
+ storage,
375
+ prefix,
376
+ mutationQueue,
361
377
  );
362
- const adapters = useMemo(() => ({ history }), [history]);
363
-
364
378
  return (
365
379
  <RuntimeAdapterProvider adapters={adapters}>
366
380
  {children}
@@ -406,6 +420,9 @@ export const createLocalStorageAdapter = (
406
420
 
407
421
  const adapter: RemoteThreadListAdapter = {
408
422
  unstable_Provider: createHistoryProvider(storage, prefix, mutationQueue),
423
+ unstable_useAdapters: function useLocalStorageAdapters() {
424
+ return useLocalStorageThreadAdapters(storage, prefix, mutationQueue);
425
+ },
409
426
 
410
427
  async list(): Promise<RemoteThreadListResponse> {
411
428
  const threads = await loadThreadMetadata();
@@ -0,0 +1,41 @@
1
+ import { useEffect, useMemo } from "react";
2
+ import { resource, type ResourceElement } from "@assistant-ui/tap";
3
+ import { useClientResource } from "@assistant-ui/store/client";
4
+ import type { ClientOutput } from "@assistant-ui/store";
5
+ import type { RuntimeAdapters } from "../../runtimes/remote-thread-list/types";
6
+ import { isDevelopment } from "../../store/internal";
7
+ import {
8
+ useRuntimeAdapters,
9
+ useRuntimeAdaptersProvider,
10
+ useStableRuntimeAdapters,
11
+ } from "../runtimes/useRuntimeAdapters";
12
+
13
+ const useAdaptedRemoteThread = ({
14
+ useAdapters,
15
+ thread,
16
+ }: {
17
+ useAdapters: () => RuntimeAdapters | null | undefined;
18
+ thread: ResourceElement<ClientOutput<"thread">>;
19
+ }): ClientOutput<"thread"> => {
20
+ const parent = useRuntimeAdapters();
21
+ const stableAdapters = useStableRuntimeAdapters(useAdapters());
22
+
23
+ const unkeyedHistory =
24
+ stableAdapters?.history != null && thread.key === undefined;
25
+ useEffect(() => {
26
+ if (!isDevelopment || !unkeyedHistory) return;
27
+ console.warn(
28
+ "[assistant-ui] RemoteThreadList received a history adapter, but the thread factory is unkeyed. Key the thread resource by thread id (withKey) so switching threads reloads history.",
29
+ );
30
+ }, [unkeyedHistory]);
31
+
32
+ const merged = useMemo(
33
+ () => (stableAdapters == null ? parent : { ...parent, ...stableAdapters }),
34
+ [parent, stableAdapters],
35
+ );
36
+ return useRuntimeAdaptersProvider(merged, function useBoundRemoteThread() {
37
+ return useClientResource(thread).methods;
38
+ });
39
+ };
40
+
41
+ export const AdaptedRemoteThread = resource(useAdaptedRemoteThread);
@@ -0,0 +1,148 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, render } from "@testing-library/react";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { resource } from "@assistant-ui/tap";
6
+ import { AuiProvider, useAui, useAuiEvent } from "@assistant-ui/store";
7
+ import { InMemoryThreadList } from "./InMemoryThreadList";
8
+
9
+ const stubComposer = { getState: () => ({}) };
10
+ const stubSuggestions = { getState: () => ({ suggestions: [] }) };
11
+ const useStubThread = (_props: { threadId: string }) => ({
12
+ getState: () => ({ isRunning: false }),
13
+ composer: () => stubComposer,
14
+ suggestions: () => stubSuggestions,
15
+ });
16
+ const StubThread = resource(useStubThread);
17
+
18
+ const setup = () => {
19
+ const selectionChanged = vi.fn();
20
+ let aui!: ReturnType<typeof useAui>;
21
+ const Consumer = () => {
22
+ useAuiEvent("threads.selectionChanged" as never, selectionChanged as never);
23
+ return null;
24
+ };
25
+ const Harness = () => {
26
+ aui = useAui({
27
+ threads: InMemoryThreadList({
28
+ thread: (threadId) => StubThread({ threadId }) as never,
29
+ }),
30
+ } as never);
31
+ return (
32
+ <AuiProvider value={aui}>
33
+ <Consumer />
34
+ </AuiProvider>
35
+ );
36
+ };
37
+ render(<Harness />);
38
+ return { getAui: () => aui, selectionChanged };
39
+ };
40
+
41
+ describe("InMemoryThreadList selection events", () => {
42
+ it("does not emit for the initially selected thread on mount", async () => {
43
+ const { selectionChanged } = setup();
44
+ await act(async () => {});
45
+ expect(selectionChanged).not.toHaveBeenCalled();
46
+ });
47
+
48
+ it("emits on switchToNewThread and on switching back", async () => {
49
+ const { getAui, selectionChanged } = setup();
50
+ await act(async () => {});
51
+
52
+ await act(async () => {
53
+ getAui().threads.switchToNewThread();
54
+ });
55
+ await act(async () => {});
56
+
57
+ const newThreadId = getAui().threads.getState().mainThreadId;
58
+ expect(newThreadId).not.toBe("main");
59
+ expect(selectionChanged).toHaveBeenCalledExactlyOnceWith({
60
+ threadId: newThreadId,
61
+ previousThreadId: "main",
62
+ });
63
+
64
+ await act(async () => {
65
+ getAui().threads.switchToThread("main");
66
+ });
67
+ await act(async () => {});
68
+
69
+ expect(selectionChanged).toHaveBeenCalledTimes(2);
70
+ expect(selectionChanged).toHaveBeenLastCalledWith({
71
+ threadId: "main",
72
+ previousThreadId: newThreadId,
73
+ });
74
+ });
75
+
76
+ it("emits when deleting the selected thread falls back to another", async () => {
77
+ const { getAui, selectionChanged } = setup();
78
+ await act(async () => {});
79
+
80
+ await act(async () => {
81
+ getAui().threads.switchToNewThread();
82
+ });
83
+ await act(async () => {});
84
+
85
+ const newThreadId = getAui().threads.getState().mainThreadId;
86
+ selectionChanged.mockClear();
87
+
88
+ await act(async () => {
89
+ getAui().threads.item({ id: newThreadId }).delete();
90
+ });
91
+ await act(async () => {});
92
+
93
+ expect(getAui().threads.getState().mainThreadId).toBe("main");
94
+ expect(selectionChanged).toHaveBeenCalledExactlyOnceWith({
95
+ threadId: "main",
96
+ previousThreadId: newThreadId,
97
+ });
98
+ });
99
+ });
100
+
101
+ describe("InMemoryThreadList delete", () => {
102
+ it("notifies onDelete with the removed thread id", async () => {
103
+ const onDelete = vi.fn();
104
+ let aui!: ReturnType<typeof useAui>;
105
+ const Harness = () => {
106
+ aui = useAui({
107
+ threads: InMemoryThreadList({
108
+ thread: (threadId) => StubThread({ threadId }) as never,
109
+ onDelete,
110
+ }),
111
+ } as never);
112
+ return <AuiProvider value={aui}>{null}</AuiProvider>;
113
+ };
114
+ render(<Harness />);
115
+ await act(async () => {});
116
+
117
+ await act(async () => {
118
+ aui.threads.switchToNewThread();
119
+ });
120
+ const doomed = aui.threads.getState().mainThreadId;
121
+ await act(async () => {
122
+ aui.threads.item({ id: doomed }).delete();
123
+ });
124
+ expect(onDelete).toHaveBeenCalledExactlyOnceWith(doomed);
125
+ });
126
+
127
+ it("starts a fresh thread when the last one is deleted", async () => {
128
+ let aui!: ReturnType<typeof useAui>;
129
+ const Harness = () => {
130
+ aui = useAui({
131
+ threads: InMemoryThreadList({
132
+ thread: (threadId) => StubThread({ threadId }) as never,
133
+ }),
134
+ } as never);
135
+ return <AuiProvider value={aui}>{null}</AuiProvider>;
136
+ };
137
+ render(<Harness />);
138
+ await act(async () => {});
139
+
140
+ await act(async () => {
141
+ aui.threads.item({ id: "main" }).delete();
142
+ });
143
+ const state = aui.threads.getState();
144
+ expect(state.threadIds).toHaveLength(1);
145
+ expect(state.mainThreadId).toBe(state.threadIds[0]);
146
+ expect(state.mainThreadId).not.toBe("main");
147
+ });
148
+ });