@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
@@ -0,0 +1,1026 @@
1
+ import { useState } from "react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { flushTapSync, resource, withKey } from "@assistant-ui/tap";
4
+ import { useAui } from "@assistant-ui/store";
5
+ import {
6
+ AuiConfig,
7
+ createAssistantClient,
8
+ type AssistantConfigSource,
9
+ } from "@assistant-ui/store/client";
10
+ import type { ThreadHistoryAdapter } from "../../adapters/thread-history";
11
+ import type { RemoteThreadListAdapter } from "../../runtimes/remote-thread-list/types";
12
+ import {
13
+ useRuntimeAdapters,
14
+ type RuntimeAdapters,
15
+ } from "../runtimes/RuntimeAdapterProvider";
16
+ import { RemoteThreadList } from "./RemoteThreadList";
17
+
18
+ const stubComposer = { getState: () => ({}) };
19
+ const stubSuggestions = { getState: () => ({ suggestions: [] }) };
20
+
21
+ const deferred = <T>() => {
22
+ let resolve!: (value: T) => void;
23
+ let reject!: (reason?: unknown) => void;
24
+ const promise = new Promise<T>((res, rej) => {
25
+ resolve = res;
26
+ reject = rej;
27
+ });
28
+ return { promise, resolve, reject };
29
+ };
30
+
31
+ const useStubThread = (props: {
32
+ threadId: string;
33
+ refetch?: (() => Promise<void>) | undefined;
34
+ isRunning?: boolean | undefined;
35
+ }) => ({
36
+ getState: () => ({
37
+ isRunning: props.isRunning === true,
38
+ messages: [],
39
+ }),
40
+ composer: () => stubComposer,
41
+ suggestions: () => stubSuggestions,
42
+ unstable_refetchThread: props.refetch,
43
+ });
44
+ const StubThread = resource(useStubThread);
45
+
46
+ const dummyHistory = (): ThreadHistoryAdapter => ({
47
+ load: async () => ({ messages: [] }),
48
+ append: async () => {},
49
+ });
50
+
51
+ const useHistoryAdapters = (history: ThreadHistoryAdapter) => () => ({
52
+ history,
53
+ });
54
+
55
+ const useCapturingThread = (props: {
56
+ threadId: string;
57
+ capture: { adapters: RuntimeAdapters | null };
58
+ }) => {
59
+ props.capture.adapters = useRuntimeAdapters();
60
+ return useStubThread(props);
61
+ };
62
+ const CapturingThread = resource(useCapturingThread);
63
+
64
+ const useCountingThread = (props: {
65
+ threadId: string;
66
+ mounts: { n: number };
67
+ }) => {
68
+ useState(() => {
69
+ props.mounts.n += 1;
70
+ return props.mounts.n;
71
+ });
72
+ return useStubThread(props);
73
+ };
74
+ const CountingThread = resource(useCountingThread);
75
+
76
+ const useHistoryLoadingThread = (props: { threadId: string }) => {
77
+ const adapters = useRuntimeAdapters();
78
+ useState(() => {
79
+ void adapters?.history?.load();
80
+ return true;
81
+ });
82
+ return useStubThread(props);
83
+ };
84
+ const HistoryLoadingThread = resource(useHistoryLoadingThread);
85
+
86
+ const makeAdapter = (
87
+ overrides: Partial<RemoteThreadListAdapter> = {},
88
+ ): RemoteThreadListAdapter => ({
89
+ list: vi.fn(async () => ({ threads: [] })),
90
+ initialize: vi.fn(async (threadId: string) => ({
91
+ remoteId: `remote-${threadId}`,
92
+ externalId: undefined,
93
+ })),
94
+ rename: vi.fn(async () => {}),
95
+ archive: vi.fn(async () => {}),
96
+ unarchive: vi.fn(async () => {}),
97
+ delete: vi.fn(async () => {}),
98
+ generateTitle: vi.fn(
99
+ async () =>
100
+ new ReadableStream({
101
+ start(controller) {
102
+ controller.close();
103
+ },
104
+ }) as never,
105
+ ),
106
+ fetch: vi.fn(async (id: string) => ({
107
+ status: "regular" as const,
108
+ remoteId: id,
109
+ externalId: undefined,
110
+ title: id,
111
+ })),
112
+ ...overrides,
113
+ });
114
+
115
+ const mountList = (
116
+ adapter: RemoteThreadListAdapter,
117
+ threadId?: string,
118
+ refetch?: () => Promise<void>,
119
+ onSwitchToThread?: (id: string) => void,
120
+ ) => {
121
+ const onThreadIdChange = vi.fn();
122
+ const handle = createAssistantClient(
123
+ AuiConfig({
124
+ threads: RemoteThreadList({
125
+ adapter,
126
+ thread: (id) => StubThread({ threadId: id, refetch }) as never,
127
+ threadId,
128
+ onThreadIdChange,
129
+ onSwitchToThread,
130
+ }),
131
+ }),
132
+ );
133
+ handle.subscribe(() => {});
134
+ return { handle, onThreadIdChange };
135
+ };
136
+
137
+ describe("RemoteThreadList", () => {
138
+ it("loads adapter threads on a standalone client", async () => {
139
+ const adapter = makeAdapter({
140
+ list: vi.fn(async () => ({
141
+ threads: [
142
+ { status: "regular" as const, remoteId: "t1", title: "One" },
143
+ { status: "archived" as const, remoteId: "t2", title: "Two" },
144
+ ],
145
+ })),
146
+ });
147
+ const { handle } = mountList(adapter);
148
+ await handle.getClient().threads.getLoadThreadsPromise();
149
+ await vi.waitFor(() => {
150
+ const state = handle.getClient().threads.getState();
151
+ expect(state.threadIds).toEqual(["t1"]);
152
+ expect(state.archivedThreadIds).toEqual(["t2"]);
153
+ expect(state.isLoading).toBe(false);
154
+ });
155
+ const state = handle.getClient().threads.getState();
156
+ expect(adapter.list).toHaveBeenCalledOnce();
157
+ expect(state.newThreadId).toMatch(/^__LOCALID_/);
158
+ expect(state.mainThreadId).toBe(state.newThreadId);
159
+ handle.destroy();
160
+ });
161
+
162
+ it("switches away when unarchiving the main thread fails", async () => {
163
+ const error = new Error("unarchive failed");
164
+ const adapter = makeAdapter({
165
+ list: vi.fn(async () => ({
166
+ threads: [
167
+ { status: "archived" as const, remoteId: "t1", title: "One" },
168
+ ],
169
+ })),
170
+ unarchive: vi.fn(async () => {
171
+ throw error;
172
+ }),
173
+ });
174
+ const { handle } = mountList(adapter);
175
+ const aui = handle.getClient();
176
+ await aui.threads.getLoadThreadsPromise();
177
+ await vi.waitFor(() => {
178
+ expect(aui.threads.getState().archivedThreadIds).toEqual(["t1"]);
179
+ });
180
+
181
+ flushTapSync(() =>
182
+ aui.threads.item({ id: "t1" }).switchTo({ unarchive: false }),
183
+ );
184
+ await vi.waitFor(() => {
185
+ expect(aui.threads.getState().mainThreadId).toBe("t1");
186
+ });
187
+
188
+ await expect(aui.threads.item({ id: "t1" }).unarchive()).rejects.toBe(
189
+ error,
190
+ );
191
+ expect(aui.threads.item({ id: "t1" }).getState().status).toBe("archived");
192
+ await vi.waitFor(() => {
193
+ expect(aui.threads.getState().mainThreadId).not.toBe("t1");
194
+ });
195
+ handle.destroy();
196
+ });
197
+
198
+ it("restores an initialized draft's archived thread when unarchive fails", async () => {
199
+ const error = new Error("unarchive failed");
200
+ const adapter = makeAdapter({
201
+ list: vi.fn(async () => ({
202
+ threads: [
203
+ { status: "archived" as const, remoteId: "t1", title: "One" },
204
+ ],
205
+ })),
206
+ unarchive: vi.fn(async () => {
207
+ throw error;
208
+ }),
209
+ });
210
+ const { handle } = mountList(adapter);
211
+ const aui = handle.getClient();
212
+ await aui.threads.getLoadThreadsPromise();
213
+ await vi.waitFor(() => {
214
+ expect(aui.threads.getState().archivedThreadIds).toContain("t1");
215
+ });
216
+ await aui.threads.item("main").initialize();
217
+
218
+ flushTapSync(() =>
219
+ aui.threads.item({ id: "t1" }).switchTo({ unarchive: false }),
220
+ );
221
+ await vi.waitFor(() => {
222
+ expect(aui.threads.getState().mainThreadId).toBe("t1");
223
+ });
224
+
225
+ await expect(aui.threads.item({ id: "t1" }).unarchive()).rejects.toBe(
226
+ error,
227
+ );
228
+ await vi.waitFor(() => {
229
+ expect(aui.threads.getState().mainThreadId).not.toBe("t1");
230
+ });
231
+ expect(aui.threads.item({ id: "t1" }).getState().status).toBe("archived");
232
+ expect(aui.threads.getState().archivedThreadIds).toContain("t1");
233
+ expect(aui.threads.getState().threadIds).not.toContain("t1");
234
+ handle.destroy();
235
+ });
236
+
237
+ it("switches to a listed thread and back to a new thread", async () => {
238
+ const adapter = makeAdapter({
239
+ list: vi.fn(async () => ({
240
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
241
+ })),
242
+ });
243
+ const { handle } = mountList(adapter);
244
+ const aui = handle.getClient();
245
+ await aui.threads.getLoadThreadsPromise();
246
+
247
+ flushTapSync(() => aui.threads.switchToThread("t1"));
248
+ await vi.waitFor(() => {
249
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
250
+ });
251
+
252
+ flushTapSync(() => handle.getClient().threads.switchToNewThread());
253
+ const afterNew = handle.getClient().threads.getState();
254
+ expect(afterNew.mainThreadId).toBe(afterNew.newThreadId);
255
+ expect(afterNew.threadIds).toContain("t1");
256
+ handle.destroy();
257
+ });
258
+
259
+ it("initializes a new thread through the adapter", async () => {
260
+ const adapter = makeAdapter();
261
+ const { handle } = mountList(adapter);
262
+ const aui = handle.getClient();
263
+ await aui.threads.getLoadThreadsPromise();
264
+ const localId = aui.threads.getState().mainThreadId;
265
+
266
+ const result = await aui.threads.item("main").initialize();
267
+ expect(adapter.initialize).toHaveBeenCalledWith(localId);
268
+ expect(result.remoteId).toBe(`remote-${localId}`);
269
+ await vi.waitFor(() => {
270
+ const state = handle.getClient().threads.getState();
271
+ expect(state.threadIds).toContain(localId);
272
+ expect(state.newThreadId).toBeNull();
273
+ expect(handle.getClient().threads.item("main").getState().remoteId).toBe(
274
+ `remote-${localId}`,
275
+ );
276
+ });
277
+ handle.destroy();
278
+ });
279
+
280
+ it("fetches an unknown thread id on switch", async () => {
281
+ const adapter = makeAdapter();
282
+ const { handle } = mountList(adapter);
283
+ const aui = handle.getClient();
284
+ await aui.threads.getLoadThreadsPromise();
285
+
286
+ flushTapSync(() => aui.threads.switchToThread("missing"));
287
+ await vi.waitFor(() => {
288
+ expect(adapter.fetch).toHaveBeenCalledWith("missing");
289
+ expect(handle.getClient().threads.getState().mainThreadId).toBe(
290
+ "missing",
291
+ );
292
+ expect(handle.getClient().threads.getState().threadIds).toContain(
293
+ "missing",
294
+ );
295
+ });
296
+ handle.destroy();
297
+ });
298
+
299
+ it("renames and deletes through the adapter", async () => {
300
+ const adapter = makeAdapter({
301
+ list: vi.fn(async () => ({
302
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
303
+ })),
304
+ });
305
+ const { handle } = mountList(adapter);
306
+ const aui = handle.getClient();
307
+ await aui.threads.getLoadThreadsPromise();
308
+ await vi.waitFor(() => {
309
+ expect(handle.getClient().threads.getState().threadIds).toEqual(["t1"]);
310
+ });
311
+
312
+ flushTapSync(() =>
313
+ handle.getClient().threads.item({ id: "t1" }).rename("Renamed"),
314
+ );
315
+ await vi.waitFor(() => {
316
+ expect(adapter.rename).toHaveBeenCalledWith("t1", "Renamed");
317
+ expect(
318
+ handle.getClient().threads.item({ id: "t1" }).getState().title,
319
+ ).toBe("Renamed");
320
+ });
321
+
322
+ const draftId = handle.getClient().threads.getState().mainThreadId;
323
+ flushTapSync(() => handle.getClient().threads.item({ id: "t1" }).delete());
324
+ await vi.waitFor(() => {
325
+ expect(adapter.delete).toHaveBeenCalledWith("t1");
326
+ expect(handle.getClient().threads.getState().threadIds).not.toContain(
327
+ "t1",
328
+ );
329
+ expect(handle.getClient().threads.getState().mainThreadId).toBe(draftId);
330
+ });
331
+ handle.destroy();
332
+ });
333
+
334
+ it("opens a controlled threadId without echoing it back", async () => {
335
+ const adapter = makeAdapter({
336
+ list: vi.fn(async () => ({
337
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
338
+ })),
339
+ });
340
+ const { handle, onThreadIdChange } = mountList(adapter, "t1");
341
+ await handle.getClient().threads.getLoadThreadsPromise();
342
+ await vi.waitFor(() => {
343
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
344
+ });
345
+ expect(onThreadIdChange).not.toHaveBeenCalled();
346
+ handle.destroy();
347
+ });
348
+
349
+ it("keeps the latest switch when an earlier fetch resolves last", async () => {
350
+ const fetchB = deferred<{
351
+ status: "regular";
352
+ remoteId: string;
353
+ externalId: undefined;
354
+ title: string;
355
+ }>();
356
+ const fetchC = deferred<{
357
+ status: "regular";
358
+ remoteId: string;
359
+ externalId: undefined;
360
+ title: string;
361
+ }>();
362
+ const adapter = makeAdapter({
363
+ fetch: vi.fn((id: string) =>
364
+ id === "thread-b" ? fetchB.promise : fetchC.promise,
365
+ ),
366
+ });
367
+ const { handle } = mountList(adapter);
368
+ const aui = handle.getClient();
369
+ await aui.threads.getLoadThreadsPromise();
370
+
371
+ flushTapSync(() => aui.threads.switchToThread("thread-b"));
372
+ flushTapSync(() => aui.threads.switchToThread("thread-c"));
373
+ fetchC.resolve({
374
+ status: "regular",
375
+ remoteId: "thread-c",
376
+ externalId: undefined,
377
+ title: "thread-c",
378
+ });
379
+ await vi.waitFor(() => {
380
+ expect(handle.getClient().threads.getState().mainThreadId).toBe(
381
+ "thread-c",
382
+ );
383
+ });
384
+ fetchB.resolve({
385
+ status: "regular",
386
+ remoteId: "thread-b",
387
+ externalId: undefined,
388
+ title: "thread-b",
389
+ });
390
+ await vi.waitFor(() => {
391
+ expect(adapter.fetch).toHaveBeenCalledWith("thread-b");
392
+ });
393
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("thread-c");
394
+ handle.destroy();
395
+ });
396
+
397
+ it("resolves item(main) after reload without aliasing another thread", async () => {
398
+ const adapter = makeAdapter({
399
+ list: vi.fn(async () => ({ threads: [] })),
400
+ });
401
+ const { handle } = mountList(adapter);
402
+ const aui = handle.getClient();
403
+ await aui.threads.getLoadThreadsPromise();
404
+ const localId = aui.threads.getState().mainThreadId;
405
+ await aui.threads.item("main").initialize();
406
+ adapter.list = vi.fn(async () => ({
407
+ threads: [
408
+ { status: "regular" as const, remoteId: "other", title: "Other" },
409
+ {
410
+ status: "regular" as const,
411
+ remoteId: `remote-${localId}`,
412
+ title: "Mine",
413
+ },
414
+ ],
415
+ }));
416
+ await handle.getClient().threads.reload();
417
+ await vi.waitFor(() => {
418
+ expect(handle.getClient().threads.getState().threadIds).toEqual([
419
+ "other",
420
+ `remote-${localId}`,
421
+ ]);
422
+ });
423
+ expect(handle.getClient().threads.item("main").getState().remoteId).toBe(
424
+ `remote-${localId}`,
425
+ );
426
+ expect(handle.getClient().threads.item("main").getState().title).toBe(
427
+ "Mine",
428
+ );
429
+ handle.destroy();
430
+ });
431
+
432
+ it("keeps item(main) isRunning after reload remaps the local id", async () => {
433
+ const adapter = makeAdapter({
434
+ list: vi.fn(async () => ({ threads: [] })),
435
+ });
436
+ const handle = createAssistantClient(
437
+ AuiConfig({
438
+ threads: RemoteThreadList({
439
+ adapter,
440
+ thread: (id) =>
441
+ StubThread({ threadId: id, isRunning: true }) as never,
442
+ }),
443
+ }),
444
+ );
445
+ handle.subscribe(() => {});
446
+ await handle.getClient().threads.getLoadThreadsPromise();
447
+ const localId = handle.getClient().threads.getState().mainThreadId;
448
+ await handle.getClient().threads.item("main").initialize();
449
+ adapter.list = vi.fn(async () => ({
450
+ threads: [
451
+ {
452
+ status: "regular" as const,
453
+ remoteId: `remote-${localId}`,
454
+ title: "Mine",
455
+ },
456
+ ],
457
+ }));
458
+ await handle.getClient().threads.reload();
459
+ await vi.waitFor(() => {
460
+ expect(handle.getClient().threads.item("main").getState().remoteId).toBe(
461
+ `remote-${localId}`,
462
+ );
463
+ });
464
+ expect(handle.getClient().threads.item("main").getState().isRunning).toBe(
465
+ true,
466
+ );
467
+ handle.destroy();
468
+ });
469
+
470
+ it("switches away when deleting the remapped main thread by remote id", async () => {
471
+ const adapter = makeAdapter({
472
+ list: vi.fn(async () => ({ threads: [] })),
473
+ });
474
+ const { handle } = mountList(adapter);
475
+ const aui = handle.getClient();
476
+ await aui.threads.getLoadThreadsPromise();
477
+ const localId = aui.threads.getState().mainThreadId;
478
+ await aui.threads.item("main").initialize();
479
+ const remoteId = `remote-${localId}`;
480
+ adapter.list = vi.fn(async () => ({
481
+ threads: [{ status: "regular" as const, remoteId, title: "Mine" }],
482
+ }));
483
+ await handle.getClient().threads.reload();
484
+ await vi.waitFor(() => {
485
+ expect(
486
+ handle.getClient().threads.item({ id: remoteId }).getState().remoteId,
487
+ ).toBe(remoteId);
488
+ });
489
+ flushTapSync(() =>
490
+ handle.getClient().threads.item({ id: remoteId }).delete(),
491
+ );
492
+ await vi.waitFor(() => {
493
+ expect(handle.getClient().threads.getState().mainThreadId).not.toBe(
494
+ localId,
495
+ );
496
+ expect(handle.getClient().threads.getState().threadIds).not.toContain(
497
+ remoteId,
498
+ );
499
+ });
500
+ expect(() =>
501
+ handle.getClient().threads.item("main").getState(),
502
+ ).not.toThrow();
503
+ handle.destroy();
504
+ });
505
+
506
+ it("does not reload when the adapter object is recreated", async () => {
507
+ const methods = makeAdapter({
508
+ list: vi.fn(async () => ({
509
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
510
+ })),
511
+ });
512
+ let adapter: RemoteThreadListAdapter = { ...methods };
513
+ const listeners = new Set<() => void>();
514
+ const source: AssistantConfigSource = {
515
+ getConfig: () =>
516
+ AuiConfig({
517
+ threads: RemoteThreadList({
518
+ adapter,
519
+ thread: (id) => StubThread({ threadId: id }) as never,
520
+ }),
521
+ }),
522
+ subscribe: (listener) => {
523
+ listeners.add(listener);
524
+ return () => {
525
+ listeners.delete(listener);
526
+ };
527
+ },
528
+ };
529
+ const handle = createAssistantClient(source);
530
+ handle.subscribe(() => {});
531
+ await handle.getClient().threads.getLoadThreadsPromise();
532
+ await vi.waitFor(() => {
533
+ expect(handle.getClient().threads.getState().threadIds).toEqual(["t1"]);
534
+ });
535
+ expect(methods.list).toHaveBeenCalledTimes(1);
536
+ adapter = { ...methods };
537
+ for (const listener of listeners) listener();
538
+ await vi.waitFor(() => {
539
+ expect(handle.getClient().threads.getState().threadIds).toEqual(["t1"]);
540
+ });
541
+ expect(methods.list).toHaveBeenCalledTimes(1);
542
+ handle.destroy();
543
+ });
544
+
545
+ it("exposes useAdapters adapters to the thread factory", async () => {
546
+ const history = dummyHistory();
547
+ const capture: { adapters: RuntimeAdapters | null } = { adapters: null };
548
+ const adapter = makeAdapter({
549
+ unstable_useAdapters: useHistoryAdapters(history),
550
+ });
551
+ const handle = createAssistantClient(
552
+ AuiConfig({
553
+ threads: RemoteThreadList({
554
+ adapter,
555
+ thread: (id) => CapturingThread({ threadId: id, capture }) as never,
556
+ }),
557
+ }),
558
+ );
559
+ handle.subscribe(() => {});
560
+ await handle.getClient().threads.getLoadThreadsPromise();
561
+ expect(capture.adapters?.history).toBe(history);
562
+ handle.destroy();
563
+ });
564
+
565
+ it("does not warn when useAdapters is supplied and the factory is keyed", async () => {
566
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
567
+ try {
568
+ const adapter = makeAdapter({
569
+ unstable_Provider: () => null,
570
+ unstable_useAdapters: useHistoryAdapters(dummyHistory()),
571
+ });
572
+ const handle = createAssistantClient(
573
+ AuiConfig({
574
+ threads: RemoteThreadList({
575
+ adapter,
576
+ thread: (id) => withKey(id, StubThread({ threadId: id }) as never),
577
+ }),
578
+ }),
579
+ );
580
+ handle.subscribe(() => {});
581
+ await handle.getClient().threads.getLoadThreadsPromise();
582
+ flushTapSync(() => {});
583
+ expect(warn).not.toHaveBeenCalled();
584
+ handle.destroy();
585
+ } finally {
586
+ warn.mockRestore();
587
+ }
588
+ });
589
+
590
+ it("warns when Provider is supplied without useAdapters", async () => {
591
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
592
+ try {
593
+ const adapter = makeAdapter({
594
+ unstable_Provider: () => null,
595
+ });
596
+ const { handle } = mountList(adapter);
597
+ await handle.getClient().threads.getLoadThreadsPromise();
598
+ expect(warn).toHaveBeenCalledWith(
599
+ expect.stringContaining("unstable_useAdapters"),
600
+ );
601
+ handle.destroy();
602
+ } finally {
603
+ warn.mockRestore();
604
+ }
605
+ });
606
+
607
+ it("warns when a history adapter arrives with an unkeyed thread factory", async () => {
608
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
609
+ try {
610
+ const adapter = makeAdapter({
611
+ unstable_useAdapters: useHistoryAdapters(dummyHistory()),
612
+ });
613
+ const handle = createAssistantClient(
614
+ AuiConfig({
615
+ threads: RemoteThreadList({
616
+ adapter,
617
+ thread: (id) => StubThread({ threadId: id }) as never,
618
+ }),
619
+ }),
620
+ );
621
+ handle.subscribe(() => {});
622
+ await handle.getClient().threads.getLoadThreadsPromise();
623
+ flushTapSync(() => {});
624
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining("unkeyed"));
625
+ handle.destroy();
626
+ } finally {
627
+ warn.mockRestore();
628
+ }
629
+ });
630
+
631
+ it("picks up a swapped useAdapters hook on a config update", async () => {
632
+ const firstHistory = dummyHistory();
633
+ const secondHistory = dummyHistory();
634
+ const capture: { adapters: RuntimeAdapters | null } = { adapters: null };
635
+ let adapter = makeAdapter({
636
+ unstable_useAdapters: useHistoryAdapters(firstHistory),
637
+ });
638
+ const listeners = new Set<() => void>();
639
+ const source: AssistantConfigSource = {
640
+ getConfig: () =>
641
+ AuiConfig({
642
+ threads: RemoteThreadList({
643
+ adapter,
644
+ thread: (id) => CapturingThread({ threadId: id, capture }) as never,
645
+ }),
646
+ }),
647
+ subscribe: (listener) => {
648
+ listeners.add(listener);
649
+ return () => {
650
+ listeners.delete(listener);
651
+ };
652
+ },
653
+ };
654
+ const handle = createAssistantClient(source);
655
+ handle.subscribe(() => {});
656
+ await handle.getClient().threads.getLoadThreadsPromise();
657
+ expect(capture.adapters?.history).toBe(firstHistory);
658
+ adapter = makeAdapter({
659
+ unstable_useAdapters: useHistoryAdapters(secondHistory),
660
+ });
661
+ for (const listener of listeners) listener();
662
+ await vi.waitFor(() => {
663
+ expect(capture.adapters?.history).toBe(secondHistory);
664
+ });
665
+ handle.destroy();
666
+ });
667
+
668
+ it("lets useAdapters read the assistant client via useAui after mount", async () => {
669
+ const seen: { aui: ReturnType<typeof useAui> | null } = { aui: null };
670
+ const adapter = makeAdapter({
671
+ unstable_useAdapters: function useAdapters() {
672
+ seen.aui = useAui();
673
+ return { history: dummyHistory() };
674
+ },
675
+ });
676
+ const { handle } = mountList(adapter);
677
+ await handle.getClient().threads.getLoadThreadsPromise();
678
+ expect(seen.aui?.threadListItem.getState().id).toBe(
679
+ handle.getClient().threads.getState().mainThreadId,
680
+ );
681
+ handle.destroy();
682
+ });
683
+
684
+ it("does not remount useAdapters when switching an unkeyed thread", async () => {
685
+ let mounts = 0;
686
+ const adapter = makeAdapter({
687
+ list: vi.fn(async () => ({
688
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
689
+ })),
690
+ unstable_useAdapters: function useAdapters() {
691
+ useState(() => {
692
+ mounts += 1;
693
+ return mounts;
694
+ });
695
+ return { history: dummyHistory() };
696
+ },
697
+ });
698
+ const { handle } = mountList(adapter);
699
+ const aui = handle.getClient();
700
+ await aui.threads.getLoadThreadsPromise();
701
+ const afterLoad = mounts;
702
+ expect(afterLoad).toBeGreaterThan(0);
703
+ flushTapSync(() => aui.threads.switchToThread("t1"));
704
+ await vi.waitFor(() => {
705
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
706
+ });
707
+ expect(mounts).toBe(afterLoad);
708
+ handle.destroy();
709
+ });
710
+
711
+ it("keeps unkeyed thread mount count independent of useAdapters", async () => {
712
+ const run = async (
713
+ mounts: { n: number },
714
+ useAdapters?: RemoteThreadListAdapter["unstable_useAdapters"],
715
+ ) => {
716
+ const adapter = makeAdapter({
717
+ list: vi.fn(async () => ({
718
+ threads: [
719
+ { status: "regular" as const, remoteId: "t1", title: "One" },
720
+ ],
721
+ })),
722
+ unstable_useAdapters: useAdapters,
723
+ });
724
+ const handle = createAssistantClient(
725
+ AuiConfig({
726
+ threads: RemoteThreadList({
727
+ adapter,
728
+ thread: (id) => CountingThread({ threadId: id, mounts }) as never,
729
+ }),
730
+ }),
731
+ );
732
+ handle.subscribe(() => {});
733
+ const aui = handle.getClient();
734
+ await aui.threads.getLoadThreadsPromise();
735
+ flushTapSync(() => aui.threads.switchToThread("t1"));
736
+ await vi.waitFor(() => {
737
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
738
+ });
739
+ handle.destroy();
740
+ };
741
+ const withoutHook = { n: 0 };
742
+ const withHook = { n: 0 };
743
+ await run(withoutHook);
744
+ await run(withHook, useHistoryAdapters(dummyHistory()));
745
+ expect(withHook.n).toBe(withoutHook.n);
746
+ });
747
+
748
+ it("forwards a caller key onto the useAdapters wrapper", async () => {
749
+ const run = async (
750
+ mounts: { n: number },
751
+ useAdapters?: RemoteThreadListAdapter["unstable_useAdapters"],
752
+ ) => {
753
+ const adapter = makeAdapter({
754
+ list: vi.fn(async () => ({
755
+ threads: [
756
+ { status: "regular" as const, remoteId: "t1", title: "One" },
757
+ ],
758
+ })),
759
+ unstable_useAdapters: useAdapters,
760
+ });
761
+ const handle = createAssistantClient(
762
+ AuiConfig({
763
+ threads: RemoteThreadList({
764
+ adapter,
765
+ thread: (id) =>
766
+ withKey(id, CountingThread({ threadId: id, mounts }) as never),
767
+ }),
768
+ }),
769
+ );
770
+ handle.subscribe(() => {});
771
+ const aui = handle.getClient();
772
+ await aui.threads.getLoadThreadsPromise();
773
+ flushTapSync(() => aui.threads.switchToThread("t1"));
774
+ await vi.waitFor(() => {
775
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
776
+ });
777
+ handle.destroy();
778
+ };
779
+ const withoutHook = { n: 0 };
780
+ const withHook = { n: 0 };
781
+ await run(withoutHook);
782
+ await run(withHook, useHistoryAdapters(dummyHistory()));
783
+ expect(withHook.n).toBe(withoutHook.n);
784
+ expect(withHook.n).toBeGreaterThan(1);
785
+ });
786
+
787
+ it("loads history for the second thread when the factory is keyed", async () => {
788
+ const load = vi.fn(async () => ({ messages: [] }));
789
+ const history: ThreadHistoryAdapter = {
790
+ load,
791
+ append: async () => {},
792
+ };
793
+ const adapter = makeAdapter({
794
+ list: vi.fn(async () => ({
795
+ threads: [
796
+ { status: "regular" as const, remoteId: "t1", title: "One" },
797
+ { status: "regular" as const, remoteId: "t2", title: "Two" },
798
+ ],
799
+ })),
800
+ unstable_useAdapters: useHistoryAdapters(history),
801
+ });
802
+ const handle = createAssistantClient(
803
+ AuiConfig({
804
+ threads: RemoteThreadList({
805
+ adapter,
806
+ thread: (id) =>
807
+ withKey(id, HistoryLoadingThread({ threadId: id }) as never),
808
+ threadId: "t1",
809
+ }),
810
+ }),
811
+ );
812
+ handle.subscribe(() => {});
813
+ const aui = handle.getClient();
814
+ await aui.threads.getLoadThreadsPromise();
815
+ await vi.waitFor(() => {
816
+ expect(load).toHaveBeenCalled();
817
+ });
818
+ const afterFirst = load.mock.calls.length;
819
+ flushTapSync(() => aui.threads.switchToThread("t2"));
820
+ await vi.waitFor(() => {
821
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t2");
822
+ });
823
+ await vi.waitFor(() => {
824
+ expect(load.mock.calls.length).toBeGreaterThan(afterFirst);
825
+ });
826
+ handle.destroy();
827
+ });
828
+
829
+ it("does not re-emit onSwitchToNewThread when the draft is already main", async () => {
830
+ const adapter = makeAdapter({
831
+ list: vi.fn(async () => ({
832
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
833
+ })),
834
+ });
835
+ const listeners = new Set<() => void>();
836
+ let threadId: string | undefined = "t1";
837
+ const onSwitchToNewThread = vi.fn();
838
+ const source: AssistantConfigSource = {
839
+ getConfig: () =>
840
+ AuiConfig({
841
+ threads: RemoteThreadList({
842
+ adapter,
843
+ thread: (id) => StubThread({ threadId: id }) as never,
844
+ threadId,
845
+ onThreadIdChange: (id) => {
846
+ threadId = id;
847
+ for (const listener of listeners) listener();
848
+ },
849
+ onSwitchToNewThread,
850
+ }),
851
+ }),
852
+ subscribe: (listener) => {
853
+ listeners.add(listener);
854
+ return () => {
855
+ listeners.delete(listener);
856
+ };
857
+ },
858
+ };
859
+ const handle = createAssistantClient(source);
860
+ handle.subscribe(() => {});
861
+ await handle.getClient().threads.getLoadThreadsPromise();
862
+ await vi.waitFor(() => {
863
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
864
+ });
865
+ flushTapSync(() => handle.getClient().threads.switchToNewThread());
866
+ await vi.waitFor(() => {
867
+ expect(handle.getClient().threads.getState().mainThreadId).not.toBe("t1");
868
+ });
869
+ expect(onSwitchToNewThread).toHaveBeenCalledTimes(1);
870
+ handle.destroy();
871
+ });
872
+
873
+ it("does not refetch a draft from reloadMainThread", async () => {
874
+ const refetch = vi.fn(async () => {});
875
+ const { handle } = mountList(makeAdapter(), undefined, refetch);
876
+ const aui = handle.getClient();
877
+ await aui.threads.getLoadThreadsPromise();
878
+ expect(aui.threads.item("main").getState().status).toBe("new");
879
+ await aui.threads.reloadMainThread();
880
+ expect(refetch).not.toHaveBeenCalled();
881
+ handle.destroy();
882
+ });
883
+
884
+ it("keeps the selected thread when config callbacks are recreated", async () => {
885
+ const adapter = makeAdapter({
886
+ list: vi.fn(async () => ({
887
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
888
+ })),
889
+ });
890
+ const listeners = new Set<() => void>();
891
+ let onThreadIdChange = vi.fn();
892
+ const source: AssistantConfigSource = {
893
+ getConfig: () =>
894
+ AuiConfig({
895
+ threads: RemoteThreadList({
896
+ adapter,
897
+ thread: (id) => StubThread({ threadId: id }) as never,
898
+ onThreadIdChange,
899
+ }),
900
+ }),
901
+ subscribe: (listener) => {
902
+ listeners.add(listener);
903
+ return () => {
904
+ listeners.delete(listener);
905
+ };
906
+ },
907
+ };
908
+ const handle = createAssistantClient(source);
909
+ handle.subscribe(() => {});
910
+ await handle.getClient().threads.getLoadThreadsPromise();
911
+ flushTapSync(() => handle.getClient().threads.switchToThread("t1"));
912
+ await vi.waitFor(() => {
913
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
914
+ });
915
+ onThreadIdChange = vi.fn();
916
+ for (const listener of listeners) listener();
917
+ await vi.waitFor(() => {
918
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
919
+ });
920
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
921
+ handle.destroy();
922
+ });
923
+
924
+ it("does not re-emit onSwitchToThread for the already selected thread", async () => {
925
+ const onSwitchToThread = vi.fn();
926
+ const adapter = makeAdapter({
927
+ list: vi.fn(async () => ({
928
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
929
+ })),
930
+ });
931
+ const { handle } = mountList(
932
+ adapter,
933
+ undefined,
934
+ undefined,
935
+ onSwitchToThread,
936
+ );
937
+ const aui = handle.getClient();
938
+ await aui.threads.getLoadThreadsPromise();
939
+ flushTapSync(() => aui.threads.switchToThread("t1"));
940
+ await vi.waitFor(() => {
941
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
942
+ });
943
+ onSwitchToThread.mockClear();
944
+ flushTapSync(() => handle.getClient().threads.switchToThread("t1"));
945
+ expect(onSwitchToThread).not.toHaveBeenCalled();
946
+ handle.destroy();
947
+ });
948
+
949
+ it("keeps item(main) resolvable after a same-tick switch then delete", async () => {
950
+ const adapter = makeAdapter({
951
+ list: vi.fn(async () => ({
952
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
953
+ })),
954
+ });
955
+ const { handle } = mountList(adapter);
956
+ const aui = handle.getClient();
957
+ await aui.threads.getLoadThreadsPromise();
958
+ await vi.waitFor(() => {
959
+ expect(handle.getClient().threads.getState().threadIds).toEqual(["t1"]);
960
+ });
961
+ flushTapSync(() => {
962
+ handle.getClient().threads.switchToThread("t1");
963
+ handle.getClient().threads.item({ id: "t1" }).delete();
964
+ });
965
+ await vi.waitFor(() => {
966
+ expect(handle.getClient().threads.getState().threadIds).not.toContain(
967
+ "t1",
968
+ );
969
+ });
970
+ expect(handle.getClient().threads.getState().mainThreadId).not.toBe("t1");
971
+ expect(() =>
972
+ handle.getClient().threads.item("main").getState(),
973
+ ).not.toThrow();
974
+ handle.destroy();
975
+ });
976
+
977
+ it("keeps item(main) resolvable when deleting a thread mid-initialize", async () => {
978
+ const initialize = deferred<{
979
+ remoteId: string;
980
+ externalId: undefined;
981
+ }>();
982
+ const adapter = makeAdapter({
983
+ initialize: vi.fn(() => initialize.promise),
984
+ });
985
+ const { handle } = mountList(adapter);
986
+ const aui = handle.getClient();
987
+ await aui.threads.getLoadThreadsPromise();
988
+ const localId = aui.threads.getState().mainThreadId;
989
+ const pending = aui.threads.item("main").initialize();
990
+ await vi.waitFor(() => {
991
+ expect(handle.getClient().threads.item("main").getState().status).toBe(
992
+ "regular",
993
+ );
994
+ });
995
+ flushTapSync(() =>
996
+ handle.getClient().threads.item({ id: localId }).delete(),
997
+ );
998
+ initialize.resolve({
999
+ remoteId: `remote-${localId}`,
1000
+ externalId: undefined,
1001
+ });
1002
+ await vi.waitFor(() => {
1003
+ expect(handle.getClient().threads.getState().mainThreadId).not.toBe(
1004
+ localId,
1005
+ );
1006
+ });
1007
+ expect(() =>
1008
+ handle.getClient().threads.item("main").getState(),
1009
+ ).not.toThrow();
1010
+ await pending;
1011
+ handle.destroy();
1012
+ });
1013
+
1014
+ it("emits onThreadIdChange after initialize settles", async () => {
1015
+ const adapter = makeAdapter();
1016
+ const { handle, onThreadIdChange } = mountList(adapter);
1017
+ const aui = handle.getClient();
1018
+ await aui.threads.getLoadThreadsPromise();
1019
+ const localId = aui.threads.getState().mainThreadId;
1020
+ await aui.threads.item("main").initialize();
1021
+ await vi.waitFor(() => {
1022
+ expect(onThreadIdChange).toHaveBeenCalledWith(`remote-${localId}`);
1023
+ });
1024
+ handle.destroy();
1025
+ });
1026
+ });