@assistant-ui/core 0.3.13 → 0.3.15

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 (463) 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 +38 -15
  8. package/dist/adapters/voice.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/internal.d.ts +3 -2
  13. package/dist/internal.js +8 -7
  14. package/dist/model-context/frame/host.d.ts +1 -1
  15. package/dist/model-context/frame/host.d.ts.map +1 -1
  16. package/dist/model-context/frame/host.js +19 -4
  17. package/dist/model-context/frame/host.js.map +1 -1
  18. package/dist/model-context/frame/provider.d.ts +4 -0
  19. package/dist/model-context/frame/provider.d.ts.map +1 -1
  20. package/dist/model-context/frame/provider.js +50 -3
  21. package/dist/model-context/frame/provider.js.map +1 -1
  22. package/dist/model-context/frame/types.d.ts +4 -1
  23. package/dist/model-context/frame/types.d.ts.map +1 -1
  24. package/dist/model-context/frame/types.js.map +1 -1
  25. package/dist/model-context/registry.d.ts.map +1 -1
  26. package/dist/model-context/registry.js +16 -4
  27. package/dist/model-context/registry.js.map +1 -1
  28. package/dist/react/RuntimeAdapter.d.ts +8 -1
  29. package/dist/react/RuntimeAdapter.d.ts.map +1 -1
  30. package/dist/react/RuntimeAdapter.js +23 -18
  31. package/dist/react/RuntimeAdapter.js.map +1 -1
  32. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  33. package/dist/react/adapters/LocalStorageThreadListAdapter.js +28 -12
  34. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  35. package/dist/react/client/AdaptedRemoteThread.d.ts +11 -0
  36. package/dist/react/client/AdaptedRemoteThread.d.ts.map +1 -0
  37. package/dist/react/client/AdaptedRemoteThread.js +27 -0
  38. package/dist/react/client/AdaptedRemoteThread.js.map +1 -0
  39. package/dist/react/client/InMemoryThreadList.d.ts +19 -0
  40. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -0
  41. package/dist/react/client/InMemoryThreadList.js +418 -0
  42. package/dist/react/client/InMemoryThreadList.js.map +1 -0
  43. package/dist/react/client/Interactables.d.ts.map +1 -1
  44. package/dist/react/client/Interactables.js +159 -102
  45. package/dist/react/client/Interactables.js.map +1 -1
  46. package/dist/react/client/RemoteThreadList.d.ts +34 -0
  47. package/dist/react/client/RemoteThreadList.d.ts.map +1 -0
  48. package/dist/react/client/RemoteThreadList.js +898 -0
  49. package/dist/react/client/RemoteThreadList.js.map +1 -0
  50. package/dist/react/client/Tools.d.ts.map +1 -1
  51. package/dist/react/client/Tools.js +38 -27
  52. package/dist/react/client/Tools.js.map +1 -1
  53. package/dist/react/index.d.ts +7 -2
  54. package/dist/react/index.js +5 -2
  55. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  56. package/dist/react/interactables-legacy/Interactables.js +145 -110
  57. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  58. package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
  59. package/dist/react/primitive-hooks/useActionBarCopy.js +20 -19
  60. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  61. package/dist/react/primitive-hooks/useActionBarEdit.d.ts.map +1 -1
  62. package/dist/react/primitive-hooks/useActionBarEdit.js +2 -4
  63. package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
  64. package/dist/react/primitive-hooks/useActionBarReload.d.ts.map +1 -1
  65. package/dist/react/primitive-hooks/useActionBarReload.js +2 -4
  66. package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
  67. package/dist/react/primitive-hooks/useBranchPickerNext.d.ts.map +1 -1
  68. package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -6
  69. package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
  70. package/dist/react/primitive-hooks/useBranchPickerPrevious.d.ts.map +1 -1
  71. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -6
  72. package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
  73. package/dist/react/primitive-hooks/useComposerCancel.d.ts.map +1 -1
  74. package/dist/react/primitive-hooks/useComposerCancel.js +2 -4
  75. package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
  76. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  77. package/dist/react/primitive-hooks/useComposerSend.js +2 -4
  78. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  79. package/dist/react/primitive-hooks/useSuggestionTrigger.d.ts.map +1 -1
  80. package/dist/react/primitive-hooks/useSuggestionTrigger.js +6 -2
  81. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  82. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts +3 -0
  83. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts.map +1 -1
  84. package/dist/react/primitive-hooks/useThreadIsEmpty.js +3 -0
  85. package/dist/react/primitive-hooks/useThreadIsEmpty.js.map +1 -1
  86. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts +3 -0
  87. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts.map +1 -1
  88. package/dist/react/primitive-hooks/useThreadIsRunning.js +3 -0
  89. package/dist/react/primitive-hooks/useThreadIsRunning.js.map +1 -1
  90. package/dist/react/primitives/message/MessageParts.d.ts +13 -0
  91. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  92. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  93. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  94. package/dist/react/primitives/thread/ThreadMessages.js +1 -1
  95. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  96. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +24 -10
  97. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  98. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +115 -71
  99. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  100. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +14 -2
  101. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  102. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +232 -63
  103. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  104. package/dist/react/runtimes/RemoteThreadResource.d.ts +30 -0
  105. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -0
  106. package/dist/react/runtimes/RemoteThreadResource.js +136 -0
  107. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -0
  108. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts +3 -10
  109. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
  110. package/dist/react/runtimes/RuntimeAdapterProvider.js +3 -6
  111. package/dist/react/runtimes/RuntimeAdapterProvider.js.map +1 -1
  112. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts +17 -0
  113. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts.map +1 -0
  114. package/dist/react/runtimes/assistant-transport/commandQueue.js +55 -0
  115. package/dist/react/runtimes/assistant-transport/commandQueue.js.map +1 -0
  116. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts +11 -0
  117. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
  118. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js +165 -0
  119. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js.map +1 -0
  120. package/dist/react/runtimes/assistant-transport/runManager.d.ts +15 -0
  121. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -0
  122. package/dist/react/runtimes/assistant-transport/runManager.js +86 -0
  123. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -0
  124. package/dist/react/runtimes/assistant-transport/types.d.ts +130 -0
  125. package/dist/react/runtimes/assistant-transport/types.d.ts.map +1 -0
  126. package/dist/react/runtimes/assistant-transport/types.js +0 -0
  127. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts +14 -0
  128. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -0
  129. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +296 -0
  130. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -0
  131. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts +7 -0
  132. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts.map +1 -0
  133. package/dist/react/runtimes/assistant-transport/useConvertedState.js +25 -0
  134. package/dist/react/runtimes/assistant-transport/useConvertedState.js.map +1 -0
  135. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts +7 -0
  136. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts.map +1 -0
  137. package/dist/react/runtimes/assistant-transport/useLatestRef.js +27 -0
  138. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -0
  139. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  140. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +39 -13
  141. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  142. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +2 -1
  143. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  144. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -3
  145. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  146. package/dist/react/runtimes/cloud/auiV0.d.ts +4 -0
  147. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  148. package/dist/react/runtimes/cloud/auiV0.js +7 -0
  149. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  150. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  151. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +60 -17
  152. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  153. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts +16 -0
  154. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts.map +1 -0
  155. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js +36 -0
  156. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -0
  157. package/dist/react/runtimes/external-message-converter.js +14 -14
  158. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  159. package/dist/react/runtimes/useExternalStoreRuntime.d.ts.map +1 -1
  160. package/dist/react/runtimes/useExternalStoreRuntime.js +44 -28
  161. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  162. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
  163. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  164. package/dist/react/runtimes/useRuntimeAdapters.d.ts +9 -0
  165. package/dist/react/runtimes/useRuntimeAdapters.d.ts.map +1 -0
  166. package/dist/react/runtimes/useRuntimeAdapters.js +20 -0
  167. package/dist/react/runtimes/useRuntimeAdapters.js.map +1 -0
  168. package/dist/runtime/api/composer-runtime.d.ts +5 -0
  169. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  170. package/dist/runtime/api/composer-runtime.js.map +1 -1
  171. package/dist/runtime/api/message-runtime.js +1 -1
  172. package/dist/runtime/api/thread-list-runtime.d.ts +1 -0
  173. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  174. package/dist/runtime/api/thread-list-runtime.js +4 -2
  175. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  176. package/dist/runtime/api/thread-runtime.d.ts +12 -2
  177. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  178. package/dist/runtime/api/thread-runtime.js +8 -2
  179. package/dist/runtime/api/thread-runtime.js.map +1 -1
  180. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  181. package/dist/runtime/base/base-composer-runtime-core.js +8 -7
  182. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  183. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
  184. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  185. package/dist/runtime/base/base-thread-runtime-core.js +3 -3
  186. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  187. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +0 -1
  188. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  189. package/dist/runtime/base/default-thread-composer-runtime-core.js +10 -4
  190. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  191. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  192. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  193. package/dist/runtime/internal.js +5 -5
  194. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  195. package/dist/runtime/queue/message-queue.js +2 -1
  196. package/dist/runtime/queue/message-queue.js.map +1 -1
  197. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  198. package/dist/runtime/utils/message-repository.js +27 -18
  199. package/dist/runtime/utils/message-repository.js.map +1 -1
  200. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts +8 -0
  201. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts.map +1 -0
  202. package/dist/runtime/utils/thread-runtime-lifecycle.js +11 -0
  203. package/dist/runtime/utils/thread-runtime-lifecycle.js.map +1 -0
  204. package/dist/runtimes/external-store/external-store-shared-options.js.map +1 -1
  205. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  206. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +4 -1
  207. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  208. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +8 -0
  209. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  210. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +37 -13
  211. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  212. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +102 -0
  213. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -0
  214. package/dist/runtimes/inert/InertThreadRuntimeCore.js +156 -0
  215. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -0
  216. package/dist/runtimes/internal.d.ts +1 -1
  217. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
  218. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  219. package/dist/runtimes/local/local-thread-runtime-core.js +78 -19
  220. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  221. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +12 -59
  222. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  223. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +10 -142
  224. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  225. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts +9 -0
  226. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts.map +1 -0
  227. package/dist/runtimes/remote-thread-list/adapter-changed.js +13 -0
  228. package/dist/runtimes/remote-thread-list/adapter-changed.js.map +1 -0
  229. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  230. package/dist/runtimes/remote-thread-list/empty-thread-core.js +16 -152
  231. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  232. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +2 -0
  233. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  234. package/dist/runtimes/remote-thread-list/optimistic-state.js +11 -0
  235. package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
  236. package/dist/runtimes/remote-thread-list/types.d.ts +32 -1
  237. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  238. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +1 -12
  239. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  240. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +67 -38
  241. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  242. package/dist/store/clients/external-thread.js +1 -1
  243. package/dist/store/clients/external-thread.js.map +1 -1
  244. package/dist/store/clients/suggestions.js +2 -2
  245. package/dist/store/clients/suggestions.js.map +1 -1
  246. package/dist/store/clients/thread-selection-events.d.ts +10 -0
  247. package/dist/store/clients/thread-selection-events.d.ts.map +1 -0
  248. package/dist/store/clients/thread-selection-events.js +40 -0
  249. package/dist/store/clients/thread-selection-events.js.map +1 -0
  250. package/dist/store/env.d.ts +5 -0
  251. package/dist/store/env.d.ts.map +1 -0
  252. package/dist/store/env.js +12 -0
  253. package/dist/store/env.js.map +1 -0
  254. package/dist/store/index.d.ts +5 -3
  255. package/dist/store/index.js +4 -2
  256. package/dist/store/internal.d.ts +4 -1
  257. package/dist/store/internal.js +4 -1
  258. package/dist/store/primitive-predicates.d.ts +19 -0
  259. package/dist/store/primitive-predicates.d.ts.map +1 -0
  260. package/dist/store/primitive-predicates.js +19 -0
  261. package/dist/store/primitive-predicates.js.map +1 -0
  262. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  263. package/dist/store/runtime-clients/attachment-runtime-client.js +18 -11
  264. package/dist/store/runtime-clients/attachment-runtime-client.js.map +1 -1
  265. package/dist/store/runtime-clients/handle-runtime-action.d.ts +5 -0
  266. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -0
  267. package/dist/store/runtime-clients/handle-runtime-action.js +14 -0
  268. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -0
  269. package/dist/store/runtime-clients/handle-thread-list-action.d.ts +5 -0
  270. package/dist/store/runtime-clients/handle-thread-list-action.d.ts.map +1 -0
  271. package/dist/store/runtime-clients/handle-thread-list-action.js +7 -0
  272. package/dist/store/runtime-clients/handle-thread-list-action.js.map +1 -0
  273. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  274. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +62 -29
  275. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  276. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  277. package/dist/store/runtime-clients/thread-list-runtime-client.js +5 -6
  278. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  279. package/dist/store/scopes/composer.d.ts +9 -4
  280. package/dist/store/scopes/composer.d.ts.map +1 -1
  281. package/dist/store/scopes/thread-list-item.d.ts +7 -6
  282. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  283. package/dist/store/scopes/thread.d.ts +8 -7
  284. package/dist/store/scopes/thread.d.ts.map +1 -1
  285. package/dist/store/scopes/threads.d.ts +17 -1
  286. package/dist/store/scopes/threads.d.ts.map +1 -1
  287. package/dist/subscribable/subscribable.d.ts +2 -1
  288. package/dist/subscribable/subscribable.d.ts.map +1 -1
  289. package/dist/subscribable/subscribable.js +23 -18
  290. package/dist/subscribable/subscribable.js.map +1 -1
  291. package/dist/types/message.d.ts +1 -1
  292. package/dist/types/message.js.map +1 -1
  293. package/dist/utils/composite-context-provider.d.ts +1 -0
  294. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  295. package/dist/utils/composite-context-provider.js +25 -9
  296. package/dist/utils/composite-context-provider.js.map +1 -1
  297. package/dist/utils/notify-event-listeners.d.ts +2 -2
  298. package/dist/utils/notify-event-listeners.d.ts.map +1 -1
  299. package/dist/utils/notify-event-listeners.js +2 -2
  300. package/dist/utils/notify-event-listeners.js.map +1 -1
  301. package/dist/utils/silent-runtime-action.d.ts +6 -0
  302. package/dist/utils/silent-runtime-action.d.ts.map +1 -0
  303. package/dist/utils/silent-runtime-action.js +7 -0
  304. package/dist/utils/silent-runtime-action.js.map +1 -0
  305. package/package.json +8 -8
  306. package/src/adapters/speech.test.ts +118 -1
  307. package/src/adapters/speech.ts +21 -22
  308. package/src/adapters/thread-history.ts +2 -0
  309. package/src/adapters/voice.test.ts +134 -0
  310. package/src/adapters/voice.ts +35 -27
  311. package/src/index.ts +1 -0
  312. package/src/internal.ts +1 -0
  313. package/src/model-context/frame/host.test.ts +87 -5
  314. package/src/model-context/frame/host.ts +18 -4
  315. package/src/model-context/frame/provider.test.ts +347 -20
  316. package/src/model-context/frame/provider.ts +58 -2
  317. package/src/model-context/frame/types.ts +5 -0
  318. package/src/model-context/registry.test.ts +131 -0
  319. package/src/model-context/registry.ts +16 -6
  320. package/src/react/RuntimeAdapter.test.ts +2 -0
  321. package/src/react/RuntimeAdapter.ts +20 -8
  322. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +34 -17
  323. package/src/react/client/AdaptedRemoteThread.ts +41 -0
  324. package/src/react/client/InMemoryThreadList.test.tsx +148 -0
  325. package/src/react/client/InMemoryThreadList.ts +290 -0
  326. package/src/react/client/Interactables.test.ts +326 -2
  327. package/src/react/client/Interactables.ts +227 -110
  328. package/src/react/client/RemoteThreadList.test.ts +1222 -0
  329. package/src/react/client/RemoteThreadList.ts +1136 -0
  330. package/src/react/client/Tools.ts +36 -29
  331. package/src/react/index.ts +13 -0
  332. package/src/react/interactables-legacy/Interactables.test.ts +317 -0
  333. package/src/react/interactables-legacy/Interactables.ts +204 -128
  334. package/src/react/primitive-hooks/useActionBarCopy.test.ts +15 -0
  335. package/src/react/primitive-hooks/useActionBarCopy.ts +15 -10
  336. package/src/react/primitive-hooks/useActionBarEdit.ts +2 -1
  337. package/src/react/primitive-hooks/useActionBarReload.ts +2 -6
  338. package/src/react/primitive-hooks/useBranchPickerNext.ts +2 -7
  339. package/src/react/primitive-hooks/useBranchPickerPrevious.ts +2 -7
  340. package/src/react/primitive-hooks/useComposerCancel.ts +2 -1
  341. package/src/react/primitive-hooks/useComposerSend.ts +2 -5
  342. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +11 -0
  343. package/src/react/primitive-hooks/useSuggestionTrigger.ts +4 -5
  344. package/src/react/primitive-hooks/useThreadIsEmpty.ts +3 -0
  345. package/src/react/primitive-hooks/useThreadIsRunning.ts +3 -0
  346. package/src/react/primitives/message/MessageParts.tsx +0 -1
  347. package/src/react/primitives/thread/ThreadMessages.tsx +2 -1
  348. package/src/react/runtimes/RemoteThreadListHookInstanceManager.retry.test.tsx +116 -0
  349. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +1 -1
  350. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +57 -6
  351. package/src/react/runtimes/RemoteThreadListHookInstanceManager.title.test.tsx +272 -0
  352. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +171 -124
  353. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.republish.test.ts +87 -0
  354. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +327 -83
  355. package/src/react/runtimes/RemoteThreadResource.test.ts +141 -0
  356. package/src/react/runtimes/RemoteThreadResource.ts +233 -0
  357. package/src/react/runtimes/RuntimeAdapterProvider.tsx +8 -20
  358. package/src/react/runtimes/assistant-transport/commandQueue.ts +68 -0
  359. package/src/react/runtimes/assistant-transport/replayBoundaryStream.test.ts +455 -0
  360. package/src/react/runtimes/assistant-transport/replayBoundaryStream.ts +172 -0
  361. package/src/react/runtimes/assistant-transport/runManager.ts +124 -0
  362. package/src/react/runtimes/assistant-transport/transport-delivery.test.tsx +79 -0
  363. package/src/react/runtimes/assistant-transport/transport-parentid.test.tsx +97 -0
  364. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +414 -0
  365. package/src/react/runtimes/assistant-transport/types.ts +170 -0
  366. package/src/react/runtimes/assistant-transport/useAssistantTransport.spec.md +141 -0
  367. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +683 -0
  368. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +483 -0
  369. package/src/react/runtimes/assistant-transport/useConvertedState.ts +20 -0
  370. package/src/react/runtimes/assistant-transport/useLatestRef.ts +9 -0
  371. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +131 -6
  372. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +61 -15
  373. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +6 -4
  374. package/src/react/runtimes/cloud/auiV0.ts +17 -1
  375. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +98 -3
  376. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +104 -27
  377. package/src/react/runtimes/cloud/useCloudThreadListRuntime.test.tsx +56 -0
  378. package/src/react/runtimes/cloud/useCloudThreadListRuntime.ts +31 -0
  379. package/src/react/runtimes/external-message-converter.test.ts +143 -0
  380. package/src/react/runtimes/external-message-converter.ts +41 -41
  381. package/src/react/runtimes/remote-thread-hook-on-tap.test.ts +58 -0
  382. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +83 -0
  383. package/src/react/runtimes/useExternalStoreRuntime.suggestions.test.tsx +13 -3
  384. package/src/react/runtimes/useExternalStoreRuntime.ts +7 -0
  385. package/src/react/runtimes/useExternalStoreSharedOptions.ts +3 -1
  386. package/src/react/runtimes/useLocalRuntime.test.tsx +134 -8
  387. package/src/react/runtimes/useRuntimeAdapters.test.tsx +62 -0
  388. package/src/react/runtimes/useRuntimeAdapters.ts +28 -0
  389. package/src/runtime/api/composer-runtime.ts +5 -0
  390. package/src/runtime/api/thread-list-runtime.ts +3 -1
  391. package/src/runtime/api/thread-runtime.ts +20 -2
  392. package/src/runtime/base/base-composer-runtime-core.ts +6 -5
  393. package/src/runtime/base/base-thread-runtime-core.test.ts +191 -0
  394. package/src/runtime/base/base-thread-runtime-core.ts +11 -2
  395. package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -4
  396. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  397. package/src/runtime/queue/message-queue.ts +2 -1
  398. package/src/runtime/utils/message-repository.ts +26 -18
  399. package/src/runtime/utils/thread-runtime-lifecycle.ts +18 -0
  400. package/src/runtimes/external-store/external-store-shared-options.ts +3 -1
  401. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
  402. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +103 -34
  403. package/src/runtimes/inert/InertThreadRuntimeCore.test.ts +253 -0
  404. package/src/runtimes/inert/InertThreadRuntimeCore.ts +232 -0
  405. package/src/runtimes/local/local-thread-runtime-core.test.ts +692 -4
  406. package/src/runtimes/local/local-thread-runtime-core.ts +117 -22
  407. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +17 -205
  408. package/src/runtimes/remote-thread-list/adapter-changed.ts +10 -0
  409. package/src/runtimes/remote-thread-list/empty-thread-core.ts +28 -217
  410. package/src/runtimes/remote-thread-list/optimistic-state.test.ts +18 -0
  411. package/src/runtimes/remote-thread-list/optimistic-state.ts +13 -0
  412. package/src/runtimes/remote-thread-list/types.ts +34 -0
  413. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -6
  414. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +243 -0
  415. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +127 -61
  416. package/src/store/clients/external-thread.ts +1 -1
  417. package/src/store/clients/model-context-client.test.ts +16 -8
  418. package/src/store/clients/suggestions.ts +2 -2
  419. package/src/store/clients/thread-selection-events.ts +21 -0
  420. package/src/store/env.ts +11 -0
  421. package/src/store/index.ts +14 -1
  422. package/src/store/internal.ts +13 -0
  423. package/src/store/primitive-predicates.test.ts +184 -0
  424. package/src/store/primitive-predicates.ts +43 -0
  425. package/src/store/runtime-clients/attachment-runtime-client.test.ts +41 -0
  426. package/src/store/runtime-clients/attachment-runtime-client.ts +2 -1
  427. package/src/store/runtime-clients/handle-runtime-action.test.ts +40 -0
  428. package/src/store/runtime-clients/handle-runtime-action.ts +14 -0
  429. package/src/store/runtime-clients/handle-thread-list-action.test.ts +69 -0
  430. package/src/store/runtime-clients/handle-thread-list-action.ts +6 -0
  431. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +15 -7
  432. package/src/store/runtime-clients/thread-list-runtime-client.ts +9 -6
  433. package/src/store/scopes/composer.ts +9 -4
  434. package/src/store/scopes/thread-list-item.ts +7 -6
  435. package/src/store/scopes/thread.ts +8 -7
  436. package/src/store/scopes/threads.ts +14 -0
  437. package/src/subscribable/subscribable.test.ts +77 -1
  438. package/src/subscribable/subscribable.ts +38 -23
  439. package/src/tests/MessageRepository.test.ts +48 -0
  440. package/src/tests/RemoteThreadListThreadListRuntimeCore-errors.test.ts +2 -2
  441. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +8 -6
  442. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-unarchive.test.ts +25 -0
  443. package/src/tests/auiV0Encode.test.ts +66 -0
  444. package/src/tests/base-composer-runtime-core-send.test.ts +30 -0
  445. package/src/tests/base-composer-runtime-core.test.ts +28 -0
  446. package/src/tests/composer-can-cancel.test.ts +101 -0
  447. package/src/tests/empty-thread-core.test.ts +6 -0
  448. package/src/tests/external-store-thread-list-runtime-core.test.ts +101 -0
  449. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +211 -1
  450. package/src/tests/external-store-thread-runtime-core.test.ts +124 -0
  451. package/src/tests/external-thread-isEmpty.test.tsx +100 -0
  452. package/src/tests/interactables-toolui-migration.test.tsx +66 -0
  453. package/src/tests/message-queue.test.ts +28 -0
  454. package/src/tests/remote-thread-list-adapter-switch.test.ts +383 -0
  455. package/src/tests/session-reset-notification.test.ts +145 -0
  456. package/src/tests/thread-switch-events.test.tsx +99 -1
  457. package/src/tests/tools-scope-migration.test.tsx +58 -0
  458. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +67 -0
  459. package/src/types/message.ts +1 -1
  460. package/src/utils/composite-context-provider.test.ts +139 -0
  461. package/src/utils/composite-context-provider.ts +26 -8
  462. package/src/utils/notify-event-listeners.ts +10 -5
  463. package/src/utils/silent-runtime-action.ts +6 -0
@@ -0,0 +1,1222 @@
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("resets selection when reload follows a different adapter instance", async () => {
546
+ const methodsA = makeAdapter({
547
+ list: vi.fn(async () => ({
548
+ threads: [
549
+ { status: "regular" as const, remoteId: "thread-a", title: "A" },
550
+ ],
551
+ })),
552
+ });
553
+ const methodsB = makeAdapter({
554
+ list: vi.fn(async () => ({
555
+ threads: [
556
+ { status: "regular" as const, remoteId: "thread-b", title: "B" },
557
+ ],
558
+ })),
559
+ });
560
+ let adapter: RemoteThreadListAdapter = { ...methodsA };
561
+ const listeners = new Set<() => void>();
562
+ const source: AssistantConfigSource = {
563
+ getConfig: () =>
564
+ AuiConfig({
565
+ threads: RemoteThreadList({
566
+ adapter,
567
+ thread: (id) => StubThread({ threadId: id }) as never,
568
+ }),
569
+ }),
570
+ subscribe: (listener) => {
571
+ listeners.add(listener);
572
+ return () => {
573
+ listeners.delete(listener);
574
+ };
575
+ },
576
+ };
577
+ const handle = createAssistantClient(source);
578
+ handle.subscribe(() => {});
579
+ await handle.getClient().threads.getLoadThreadsPromise();
580
+ flushTapSync(() => handle.getClient().threads.switchToThread("thread-a"));
581
+ await vi.waitFor(() => {
582
+ expect(handle.getClient().threads.item("main").getState().remoteId).toBe(
583
+ "thread-a",
584
+ );
585
+ });
586
+
587
+ adapter = { ...methodsB };
588
+ for (const listener of listeners) listener();
589
+ await vi.waitFor(async () => {
590
+ flushTapSync(() => {});
591
+ await handle.getClient().threads.reload();
592
+ expect(handle.getClient().threads.getState().threadIds).toEqual([
593
+ "thread-b",
594
+ ]);
595
+ });
596
+ expect(
597
+ handle.getClient().threads.item("main").getState().remoteId,
598
+ ).not.toBe("thread-a");
599
+ expect(handle.getClient().threads.item("main").getState().status).toBe(
600
+ "new",
601
+ );
602
+ expect(methodsB.list).toHaveBeenCalled();
603
+ handle.destroy();
604
+ });
605
+
606
+ it("does not reset again when retrying a failed replacement load", async () => {
607
+ const methodsA = makeAdapter({
608
+ list: vi.fn(async () => ({
609
+ threads: [
610
+ { status: "regular" as const, remoteId: "thread-a", title: "A" },
611
+ ],
612
+ })),
613
+ });
614
+ const methodsB = makeAdapter({
615
+ list: vi
616
+ .fn()
617
+ .mockRejectedValueOnce(new Error("network"))
618
+ .mockResolvedValueOnce({
619
+ threads: [
620
+ { status: "regular" as const, remoteId: "thread-b", title: "B" },
621
+ ],
622
+ }),
623
+ });
624
+ let adapter: RemoteThreadListAdapter = { ...methodsA };
625
+ const listeners = new Set<() => void>();
626
+ const source: AssistantConfigSource = {
627
+ getConfig: () =>
628
+ AuiConfig({
629
+ threads: RemoteThreadList({
630
+ adapter,
631
+ thread: (id) => StubThread({ threadId: id }) as never,
632
+ }),
633
+ }),
634
+ subscribe: (listener) => {
635
+ listeners.add(listener);
636
+ return () => {
637
+ listeners.delete(listener);
638
+ };
639
+ },
640
+ };
641
+ const handle = createAssistantClient(source);
642
+ handle.subscribe(() => {});
643
+ await handle.getClient().threads.getLoadThreadsPromise();
644
+ flushTapSync(() => handle.getClient().threads.switchToThread("thread-a"));
645
+ await vi.waitFor(() => {
646
+ expect(handle.getClient().threads.item("main").getState().remoteId).toBe(
647
+ "thread-a",
648
+ );
649
+ });
650
+
651
+ adapter = { ...methodsB };
652
+ for (const listener of listeners) listener();
653
+ const consoleError = vi
654
+ .spyOn(console, "error")
655
+ .mockImplementation(() => {});
656
+ try {
657
+ await vi.waitFor(async () => {
658
+ flushTapSync(() => {});
659
+ await handle.getClient().threads.reload();
660
+ expect(handle.getClient().threads.item("main").getState().status).toBe(
661
+ "new",
662
+ );
663
+ });
664
+ const mainAfterFailure = handle
665
+ .getClient()
666
+ .threads.getState().mainThreadId;
667
+ await handle.getClient().threads.reload();
668
+ await vi.waitFor(() => {
669
+ expect(handle.getClient().threads.getState().threadIds).toEqual([
670
+ "thread-b",
671
+ ]);
672
+ });
673
+ expect(handle.getClient().threads.getState().mainThreadId).toBe(
674
+ mainAfterFailure,
675
+ );
676
+ } finally {
677
+ consoleError.mockRestore();
678
+ }
679
+ handle.destroy();
680
+ });
681
+
682
+ it("does not send a superseded rename through the replacement adapter", async () => {
683
+ const initializeRequest = deferred<{
684
+ remoteId: string;
685
+ externalId: string | undefined;
686
+ }>();
687
+ const methodsA = makeAdapter({
688
+ list: vi.fn(async () => ({ threads: [] })),
689
+ initialize: vi.fn(() => initializeRequest.promise),
690
+ });
691
+ const methodsB = makeAdapter({
692
+ list: vi.fn(async () => ({
693
+ threads: [
694
+ { status: "regular" as const, remoteId: "thread-b", title: "B" },
695
+ ],
696
+ })),
697
+ });
698
+ let adapter: RemoteThreadListAdapter = { ...methodsA };
699
+ const listeners = new Set<() => void>();
700
+ const source: AssistantConfigSource = {
701
+ getConfig: () =>
702
+ AuiConfig({
703
+ threads: RemoteThreadList({
704
+ adapter,
705
+ thread: (id) => StubThread({ threadId: id }) as never,
706
+ }),
707
+ }),
708
+ subscribe: (listener) => {
709
+ listeners.add(listener);
710
+ return () => {
711
+ listeners.delete(listener);
712
+ };
713
+ },
714
+ };
715
+ const handle = createAssistantClient(source);
716
+ handle.subscribe(() => {});
717
+ await handle.getClient().threads.getLoadThreadsPromise();
718
+ const initializeTask = handle.getClient().threads.item("main").initialize();
719
+ const renameTask = handle.getClient().threads.item("main").rename("leaked");
720
+
721
+ adapter = { ...methodsB };
722
+ for (const listener of listeners) listener();
723
+ await vi.waitFor(async () => {
724
+ flushTapSync(() => {});
725
+ await handle.getClient().threads.reload();
726
+ expect(handle.getClient().threads.getState().threadIds).toEqual([
727
+ "thread-b",
728
+ ]);
729
+ });
730
+ initializeRequest.resolve({
731
+ remoteId: "leaked",
732
+ externalId: undefined,
733
+ });
734
+ await expect(initializeTask).rejects.toThrow("adapter changed");
735
+ await expect(renameTask).rejects.toThrow("adapter changed");
736
+ expect(methodsA.rename).not.toHaveBeenCalled();
737
+ expect(methodsB.rename).not.toHaveBeenCalled();
738
+ handle.destroy();
739
+ });
740
+
741
+ it("exposes useAdapters adapters to the thread factory", async () => {
742
+ const history = dummyHistory();
743
+ const capture: { adapters: RuntimeAdapters | null } = { adapters: null };
744
+ const adapter = makeAdapter({
745
+ unstable_useAdapters: useHistoryAdapters(history),
746
+ });
747
+ const handle = createAssistantClient(
748
+ AuiConfig({
749
+ threads: RemoteThreadList({
750
+ adapter,
751
+ thread: (id) => CapturingThread({ threadId: id, capture }) as never,
752
+ }),
753
+ }),
754
+ );
755
+ handle.subscribe(() => {});
756
+ await handle.getClient().threads.getLoadThreadsPromise();
757
+ expect(capture.adapters?.history).toBe(history);
758
+ handle.destroy();
759
+ });
760
+
761
+ it("does not warn when useAdapters is supplied and the factory is keyed", async () => {
762
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
763
+ try {
764
+ const adapter = makeAdapter({
765
+ unstable_Provider: () => null,
766
+ unstable_useAdapters: useHistoryAdapters(dummyHistory()),
767
+ });
768
+ const handle = createAssistantClient(
769
+ AuiConfig({
770
+ threads: RemoteThreadList({
771
+ adapter,
772
+ thread: (id) => withKey(id, StubThread({ threadId: id }) as never),
773
+ }),
774
+ }),
775
+ );
776
+ handle.subscribe(() => {});
777
+ await handle.getClient().threads.getLoadThreadsPromise();
778
+ flushTapSync(() => {});
779
+ expect(warn).not.toHaveBeenCalled();
780
+ handle.destroy();
781
+ } finally {
782
+ warn.mockRestore();
783
+ }
784
+ });
785
+
786
+ it("warns when Provider is supplied without useAdapters", async () => {
787
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
788
+ try {
789
+ const adapter = makeAdapter({
790
+ unstable_Provider: () => null,
791
+ });
792
+ const { handle } = mountList(adapter);
793
+ await handle.getClient().threads.getLoadThreadsPromise();
794
+ expect(warn).toHaveBeenCalledWith(
795
+ expect.stringContaining("unstable_useAdapters"),
796
+ );
797
+ handle.destroy();
798
+ } finally {
799
+ warn.mockRestore();
800
+ }
801
+ });
802
+
803
+ it("warns when a history adapter arrives with an unkeyed thread factory", async () => {
804
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
805
+ try {
806
+ const adapter = makeAdapter({
807
+ unstable_useAdapters: useHistoryAdapters(dummyHistory()),
808
+ });
809
+ const handle = createAssistantClient(
810
+ AuiConfig({
811
+ threads: RemoteThreadList({
812
+ adapter,
813
+ thread: (id) => StubThread({ threadId: id }) as never,
814
+ }),
815
+ }),
816
+ );
817
+ handle.subscribe(() => {});
818
+ await handle.getClient().threads.getLoadThreadsPromise();
819
+ flushTapSync(() => {});
820
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining("unkeyed"));
821
+ handle.destroy();
822
+ } finally {
823
+ warn.mockRestore();
824
+ }
825
+ });
826
+
827
+ it("picks up a swapped useAdapters hook on a config update", async () => {
828
+ const firstHistory = dummyHistory();
829
+ const secondHistory = dummyHistory();
830
+ const capture: { adapters: RuntimeAdapters | null } = { adapters: null };
831
+ let adapter = makeAdapter({
832
+ unstable_useAdapters: useHistoryAdapters(firstHistory),
833
+ });
834
+ const listeners = new Set<() => void>();
835
+ const source: AssistantConfigSource = {
836
+ getConfig: () =>
837
+ AuiConfig({
838
+ threads: RemoteThreadList({
839
+ adapter,
840
+ thread: (id) => CapturingThread({ threadId: id, capture }) as never,
841
+ }),
842
+ }),
843
+ subscribe: (listener) => {
844
+ listeners.add(listener);
845
+ return () => {
846
+ listeners.delete(listener);
847
+ };
848
+ },
849
+ };
850
+ const handle = createAssistantClient(source);
851
+ handle.subscribe(() => {});
852
+ await handle.getClient().threads.getLoadThreadsPromise();
853
+ expect(capture.adapters?.history).toBe(firstHistory);
854
+ adapter = makeAdapter({
855
+ unstable_useAdapters: useHistoryAdapters(secondHistory),
856
+ });
857
+ for (const listener of listeners) listener();
858
+ await vi.waitFor(() => {
859
+ expect(capture.adapters?.history).toBe(secondHistory);
860
+ });
861
+ handle.destroy();
862
+ });
863
+
864
+ it("lets useAdapters read the assistant client via useAui after mount", async () => {
865
+ const seen: { aui: ReturnType<typeof useAui> | null } = { aui: null };
866
+ const adapter = makeAdapter({
867
+ unstable_useAdapters: function useAdapters() {
868
+ seen.aui = useAui();
869
+ return { history: dummyHistory() };
870
+ },
871
+ });
872
+ const { handle } = mountList(adapter);
873
+ await handle.getClient().threads.getLoadThreadsPromise();
874
+ expect(seen.aui?.threadListItem.getState().id).toBe(
875
+ handle.getClient().threads.getState().mainThreadId,
876
+ );
877
+ handle.destroy();
878
+ });
879
+
880
+ it("does not remount useAdapters when switching an unkeyed thread", async () => {
881
+ let mounts = 0;
882
+ const adapter = makeAdapter({
883
+ list: vi.fn(async () => ({
884
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
885
+ })),
886
+ unstable_useAdapters: function useAdapters() {
887
+ useState(() => {
888
+ mounts += 1;
889
+ return mounts;
890
+ });
891
+ return { history: dummyHistory() };
892
+ },
893
+ });
894
+ const { handle } = mountList(adapter);
895
+ const aui = handle.getClient();
896
+ await aui.threads.getLoadThreadsPromise();
897
+ const afterLoad = mounts;
898
+ expect(afterLoad).toBeGreaterThan(0);
899
+ flushTapSync(() => aui.threads.switchToThread("t1"));
900
+ await vi.waitFor(() => {
901
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
902
+ });
903
+ expect(mounts).toBe(afterLoad);
904
+ handle.destroy();
905
+ });
906
+
907
+ it("keeps unkeyed thread mount count independent of useAdapters", async () => {
908
+ const run = async (
909
+ mounts: { n: number },
910
+ useAdapters?: RemoteThreadListAdapter["unstable_useAdapters"],
911
+ ) => {
912
+ const adapter = makeAdapter({
913
+ list: vi.fn(async () => ({
914
+ threads: [
915
+ { status: "regular" as const, remoteId: "t1", title: "One" },
916
+ ],
917
+ })),
918
+ unstable_useAdapters: useAdapters,
919
+ });
920
+ const handle = createAssistantClient(
921
+ AuiConfig({
922
+ threads: RemoteThreadList({
923
+ adapter,
924
+ thread: (id) => CountingThread({ threadId: id, mounts }) as never,
925
+ }),
926
+ }),
927
+ );
928
+ handle.subscribe(() => {});
929
+ const aui = handle.getClient();
930
+ await aui.threads.getLoadThreadsPromise();
931
+ flushTapSync(() => aui.threads.switchToThread("t1"));
932
+ await vi.waitFor(() => {
933
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
934
+ });
935
+ handle.destroy();
936
+ };
937
+ const withoutHook = { n: 0 };
938
+ const withHook = { n: 0 };
939
+ await run(withoutHook);
940
+ await run(withHook, useHistoryAdapters(dummyHistory()));
941
+ expect(withHook.n).toBe(withoutHook.n);
942
+ });
943
+
944
+ it("forwards a caller key onto the useAdapters wrapper", async () => {
945
+ const run = async (
946
+ mounts: { n: number },
947
+ useAdapters?: RemoteThreadListAdapter["unstable_useAdapters"],
948
+ ) => {
949
+ const adapter = makeAdapter({
950
+ list: vi.fn(async () => ({
951
+ threads: [
952
+ { status: "regular" as const, remoteId: "t1", title: "One" },
953
+ ],
954
+ })),
955
+ unstable_useAdapters: useAdapters,
956
+ });
957
+ const handle = createAssistantClient(
958
+ AuiConfig({
959
+ threads: RemoteThreadList({
960
+ adapter,
961
+ thread: (id) =>
962
+ withKey(id, CountingThread({ threadId: id, mounts }) as never),
963
+ }),
964
+ }),
965
+ );
966
+ handle.subscribe(() => {});
967
+ const aui = handle.getClient();
968
+ await aui.threads.getLoadThreadsPromise();
969
+ flushTapSync(() => aui.threads.switchToThread("t1"));
970
+ await vi.waitFor(() => {
971
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
972
+ });
973
+ handle.destroy();
974
+ };
975
+ const withoutHook = { n: 0 };
976
+ const withHook = { n: 0 };
977
+ await run(withoutHook);
978
+ await run(withHook, useHistoryAdapters(dummyHistory()));
979
+ expect(withHook.n).toBe(withoutHook.n);
980
+ expect(withHook.n).toBeGreaterThan(1);
981
+ });
982
+
983
+ it("loads history for the second thread when the factory is keyed", async () => {
984
+ const load = vi.fn(async () => ({ messages: [] }));
985
+ const history: ThreadHistoryAdapter = {
986
+ load,
987
+ append: async () => {},
988
+ };
989
+ const adapter = makeAdapter({
990
+ list: vi.fn(async () => ({
991
+ threads: [
992
+ { status: "regular" as const, remoteId: "t1", title: "One" },
993
+ { status: "regular" as const, remoteId: "t2", title: "Two" },
994
+ ],
995
+ })),
996
+ unstable_useAdapters: useHistoryAdapters(history),
997
+ });
998
+ const handle = createAssistantClient(
999
+ AuiConfig({
1000
+ threads: RemoteThreadList({
1001
+ adapter,
1002
+ thread: (id) =>
1003
+ withKey(id, HistoryLoadingThread({ threadId: id }) as never),
1004
+ threadId: "t1",
1005
+ }),
1006
+ }),
1007
+ );
1008
+ handle.subscribe(() => {});
1009
+ const aui = handle.getClient();
1010
+ await aui.threads.getLoadThreadsPromise();
1011
+ await vi.waitFor(() => {
1012
+ expect(load).toHaveBeenCalled();
1013
+ });
1014
+ const afterFirst = load.mock.calls.length;
1015
+ flushTapSync(() => aui.threads.switchToThread("t2"));
1016
+ await vi.waitFor(() => {
1017
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t2");
1018
+ });
1019
+ await vi.waitFor(() => {
1020
+ expect(load.mock.calls.length).toBeGreaterThan(afterFirst);
1021
+ });
1022
+ handle.destroy();
1023
+ });
1024
+
1025
+ it("does not re-emit onSwitchToNewThread when the draft is already main", async () => {
1026
+ const adapter = makeAdapter({
1027
+ list: vi.fn(async () => ({
1028
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
1029
+ })),
1030
+ });
1031
+ const listeners = new Set<() => void>();
1032
+ let threadId: string | undefined = "t1";
1033
+ const onSwitchToNewThread = vi.fn();
1034
+ const source: AssistantConfigSource = {
1035
+ getConfig: () =>
1036
+ AuiConfig({
1037
+ threads: RemoteThreadList({
1038
+ adapter,
1039
+ thread: (id) => StubThread({ threadId: id }) as never,
1040
+ threadId,
1041
+ onThreadIdChange: (id) => {
1042
+ threadId = id;
1043
+ for (const listener of listeners) listener();
1044
+ },
1045
+ onSwitchToNewThread,
1046
+ }),
1047
+ }),
1048
+ subscribe: (listener) => {
1049
+ listeners.add(listener);
1050
+ return () => {
1051
+ listeners.delete(listener);
1052
+ };
1053
+ },
1054
+ };
1055
+ const handle = createAssistantClient(source);
1056
+ handle.subscribe(() => {});
1057
+ await handle.getClient().threads.getLoadThreadsPromise();
1058
+ await vi.waitFor(() => {
1059
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
1060
+ });
1061
+ flushTapSync(() => handle.getClient().threads.switchToNewThread());
1062
+ await vi.waitFor(() => {
1063
+ expect(handle.getClient().threads.getState().mainThreadId).not.toBe("t1");
1064
+ });
1065
+ expect(onSwitchToNewThread).toHaveBeenCalledTimes(1);
1066
+ handle.destroy();
1067
+ });
1068
+
1069
+ it("does not refetch a draft from reloadMainThread", async () => {
1070
+ const refetch = vi.fn(async () => {});
1071
+ const { handle } = mountList(makeAdapter(), undefined, refetch);
1072
+ const aui = handle.getClient();
1073
+ await aui.threads.getLoadThreadsPromise();
1074
+ expect(aui.threads.item("main").getState().status).toBe("new");
1075
+ await aui.threads.reloadMainThread();
1076
+ expect(refetch).not.toHaveBeenCalled();
1077
+ handle.destroy();
1078
+ });
1079
+
1080
+ it("keeps the selected thread when config callbacks are recreated", async () => {
1081
+ const adapter = makeAdapter({
1082
+ list: vi.fn(async () => ({
1083
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
1084
+ })),
1085
+ });
1086
+ const listeners = new Set<() => void>();
1087
+ let onThreadIdChange = vi.fn();
1088
+ const source: AssistantConfigSource = {
1089
+ getConfig: () =>
1090
+ AuiConfig({
1091
+ threads: RemoteThreadList({
1092
+ adapter,
1093
+ thread: (id) => StubThread({ threadId: id }) as never,
1094
+ onThreadIdChange,
1095
+ }),
1096
+ }),
1097
+ subscribe: (listener) => {
1098
+ listeners.add(listener);
1099
+ return () => {
1100
+ listeners.delete(listener);
1101
+ };
1102
+ },
1103
+ };
1104
+ const handle = createAssistantClient(source);
1105
+ handle.subscribe(() => {});
1106
+ await handle.getClient().threads.getLoadThreadsPromise();
1107
+ flushTapSync(() => handle.getClient().threads.switchToThread("t1"));
1108
+ await vi.waitFor(() => {
1109
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
1110
+ });
1111
+ onThreadIdChange = vi.fn();
1112
+ for (const listener of listeners) listener();
1113
+ await vi.waitFor(() => {
1114
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
1115
+ });
1116
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
1117
+ handle.destroy();
1118
+ });
1119
+
1120
+ it("does not re-emit onSwitchToThread for the already selected thread", async () => {
1121
+ const onSwitchToThread = vi.fn();
1122
+ const adapter = makeAdapter({
1123
+ list: vi.fn(async () => ({
1124
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
1125
+ })),
1126
+ });
1127
+ const { handle } = mountList(
1128
+ adapter,
1129
+ undefined,
1130
+ undefined,
1131
+ onSwitchToThread,
1132
+ );
1133
+ const aui = handle.getClient();
1134
+ await aui.threads.getLoadThreadsPromise();
1135
+ flushTapSync(() => aui.threads.switchToThread("t1"));
1136
+ await vi.waitFor(() => {
1137
+ expect(handle.getClient().threads.getState().mainThreadId).toBe("t1");
1138
+ });
1139
+ onSwitchToThread.mockClear();
1140
+ flushTapSync(() => handle.getClient().threads.switchToThread("t1"));
1141
+ expect(onSwitchToThread).not.toHaveBeenCalled();
1142
+ handle.destroy();
1143
+ });
1144
+
1145
+ it("keeps item(main) resolvable after a same-tick switch then delete", async () => {
1146
+ const adapter = makeAdapter({
1147
+ list: vi.fn(async () => ({
1148
+ threads: [{ status: "regular" as const, remoteId: "t1", title: "One" }],
1149
+ })),
1150
+ });
1151
+ const { handle } = mountList(adapter);
1152
+ const aui = handle.getClient();
1153
+ await aui.threads.getLoadThreadsPromise();
1154
+ await vi.waitFor(() => {
1155
+ expect(handle.getClient().threads.getState().threadIds).toEqual(["t1"]);
1156
+ });
1157
+ flushTapSync(() => {
1158
+ handle.getClient().threads.switchToThread("t1");
1159
+ handle.getClient().threads.item({ id: "t1" }).delete();
1160
+ });
1161
+ await vi.waitFor(() => {
1162
+ expect(handle.getClient().threads.getState().threadIds).not.toContain(
1163
+ "t1",
1164
+ );
1165
+ });
1166
+ expect(handle.getClient().threads.getState().mainThreadId).not.toBe("t1");
1167
+ expect(() =>
1168
+ handle.getClient().threads.item("main").getState(),
1169
+ ).not.toThrow();
1170
+ handle.destroy();
1171
+ });
1172
+
1173
+ it("keeps item(main) resolvable when deleting a thread mid-initialize", async () => {
1174
+ const initialize = deferred<{
1175
+ remoteId: string;
1176
+ externalId: undefined;
1177
+ }>();
1178
+ const adapter = makeAdapter({
1179
+ initialize: vi.fn(() => initialize.promise),
1180
+ });
1181
+ const { handle } = mountList(adapter);
1182
+ const aui = handle.getClient();
1183
+ await aui.threads.getLoadThreadsPromise();
1184
+ const localId = aui.threads.getState().mainThreadId;
1185
+ const pending = aui.threads.item("main").initialize();
1186
+ await vi.waitFor(() => {
1187
+ expect(handle.getClient().threads.item("main").getState().status).toBe(
1188
+ "regular",
1189
+ );
1190
+ });
1191
+ flushTapSync(() =>
1192
+ handle.getClient().threads.item({ id: localId }).delete(),
1193
+ );
1194
+ initialize.resolve({
1195
+ remoteId: `remote-${localId}`,
1196
+ externalId: undefined,
1197
+ });
1198
+ await vi.waitFor(() => {
1199
+ expect(handle.getClient().threads.getState().mainThreadId).not.toBe(
1200
+ localId,
1201
+ );
1202
+ });
1203
+ expect(() =>
1204
+ handle.getClient().threads.item("main").getState(),
1205
+ ).not.toThrow();
1206
+ await pending;
1207
+ handle.destroy();
1208
+ });
1209
+
1210
+ it("emits onThreadIdChange after initialize settles", async () => {
1211
+ const adapter = makeAdapter();
1212
+ const { handle, onThreadIdChange } = mountList(adapter);
1213
+ const aui = handle.getClient();
1214
+ await aui.threads.getLoadThreadsPromise();
1215
+ const localId = aui.threads.getState().mainThreadId;
1216
+ await aui.threads.item("main").initialize();
1217
+ await vi.waitFor(() => {
1218
+ expect(onThreadIdChange).toHaveBeenCalledWith(`remote-${localId}`);
1219
+ });
1220
+ handle.destroy();
1221
+ });
1222
+ });