@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,1067 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useState,
6
+ useSyncExternalStore,
7
+ } from "react";
8
+ import { resource, withKey } from "@assistant-ui/tap";
9
+ import type { ClientOutput } from "@assistant-ui/store";
10
+ import {
11
+ attachTransformScopes,
12
+ useClientLookup,
13
+ useClientResource,
14
+ } from "@assistant-ui/store/client";
15
+ import { isDevelopment, useThreadSelectionEvents } from "../../store/internal";
16
+ import { generateId } from "../../utils/id";
17
+ import { OptimisticState } from "../../runtimes/remote-thread-list/optimistic-state";
18
+ import {
19
+ classifyThreads,
20
+ createThreadMappingId,
21
+ getThreadData,
22
+ normalizeCursor,
23
+ updateStatusReducer,
24
+ type RemoteThreadData,
25
+ type RemoteThreadState,
26
+ } from "../../runtimes/remote-thread-list/remote-thread-state";
27
+ import type {
28
+ RemoteThreadInitializeResponse,
29
+ RemoteThreadListAdapter,
30
+ } from "../../runtimes/remote-thread-list/types";
31
+ import type { ThreadMessage } from "../../types/message";
32
+ import { AssistantMessageStream } from "assistant-stream";
33
+ import { handleThreadListAction } from "../../store/runtime-clients/handle-thread-list-action";
34
+ import {
35
+ inMemoryThreadListTransformScopes,
36
+ type InMemoryThreadListProps,
37
+ } from "./InMemoryThreadList";
38
+ import { AdaptedRemoteThread } from "./AdaptedRemoteThread";
39
+
40
+ const RESOLVED_PROMISE = Promise.resolve();
41
+
42
+ const EMPTY_LIST: RemoteThreadState = {
43
+ isLoading: true,
44
+ isLoadingMore: false,
45
+ cursor: undefined,
46
+ newThreadId: undefined,
47
+ threadIds: [],
48
+ archivedThreadIds: [],
49
+ threadIdMap: {},
50
+ threadData: {},
51
+ };
52
+
53
+ export type RemoteThreadListProps = {
54
+ /**
55
+ * Swapping this to a different backing store does not reload the list. Call `reload()` after a genuine swap. A recreated object for the same store is a no-op.
56
+ */
57
+ adapter: RemoteThreadListAdapter;
58
+ /**
59
+ * Factory for the visible thread. Per-thread history reload requires a
60
+ * resource keyed by thread id (`withKey(id, thread(...))`). An unkeyed
61
+ * factory keeps one instance across switches, so a mount-once history
62
+ * loader will not fetch the next thread.
63
+ */
64
+ thread: InMemoryThreadListProps["thread"];
65
+ threadId?: string | undefined;
66
+ onThreadIdChange?: ((threadId: string | undefined) => void) | undefined;
67
+ onSwitchToThread?: ((threadId: string) => void) | undefined;
68
+ onSwitchToNewThread?: (() => void) | undefined;
69
+ onDelete?: ((threadId: string) => void) | undefined;
70
+ };
71
+
72
+ const threadNotFoundError = (threadIdOrRemoteId: string, action: string) =>
73
+ new Error(`Thread "${threadIdOrRemoteId}" was not found while ${action}.`);
74
+
75
+ const threadStatusError = (
76
+ threadIdOrRemoteId: string,
77
+ status: RemoteThreadData["status"],
78
+ action: string,
79
+ ) =>
80
+ new Error(
81
+ `Thread "${threadIdOrRemoteId}" has status "${status}", so it cannot ${action}.`,
82
+ );
83
+
84
+ const toInitializeResult = (
85
+ result: RemoteThreadInitializeResponse,
86
+ ): { remoteId: string; externalId: string | undefined } => ({
87
+ remoteId: result.remoteId,
88
+ externalId: result.externalId,
89
+ });
90
+
91
+ const applyTitleStream = async (
92
+ stream: Parameters<typeof AssistantMessageStream.fromAssistantStream>[0],
93
+ onTitle: (title: string | undefined) => void,
94
+ ) => {
95
+ const messageStream = AssistantMessageStream.fromAssistantStream(stream);
96
+ for await (const result of messageStream) {
97
+ onTitle(result.parts.filter((part) => part.type === "text")[0]?.text);
98
+ }
99
+ };
100
+
101
+ const seedNewThread = (
102
+ state: RemoteThreadState,
103
+ ): { id: string; state: RemoteThreadState } => {
104
+ let id: string;
105
+ do {
106
+ id = `__LOCALID_${generateId()}`;
107
+ } while (state.threadIdMap[id]);
108
+ const mappingId = createThreadMappingId(id);
109
+ return {
110
+ id,
111
+ state: {
112
+ ...state,
113
+ newThreadId: id,
114
+ threadIdMap: {
115
+ ...state.threadIdMap,
116
+ [id]: mappingId,
117
+ },
118
+ threadData: {
119
+ ...state.threadData,
120
+ [mappingId]: {
121
+ status: "new",
122
+ id,
123
+ remoteId: undefined,
124
+ externalId: undefined,
125
+ title: undefined,
126
+ custom: undefined,
127
+ },
128
+ },
129
+ },
130
+ };
131
+ };
132
+
133
+ const useThreadListItemClient = (props: {
134
+ data: RemoteThreadData;
135
+ isRunning: boolean;
136
+ onSwitchTo: (options?: { unarchive?: boolean }) => void;
137
+ onRename: (title: string) => void;
138
+ onUpdateCustom: (custom: Record<string, unknown> | undefined) => void;
139
+ onArchive: () => void;
140
+ onUnarchive: () => void;
141
+ onDelete: () => void;
142
+ onGenerateTitle: () => void;
143
+ onInitialize: () => Promise<{
144
+ remoteId: string;
145
+ externalId: string | undefined;
146
+ }>;
147
+ onDetach: () => void;
148
+ }): ClientOutput<"threadListItem"> => {
149
+ const {
150
+ data,
151
+ isRunning,
152
+ onSwitchTo,
153
+ onRename,
154
+ onUpdateCustom,
155
+ onArchive,
156
+ onUnarchive,
157
+ onDelete,
158
+ onGenerateTitle,
159
+ onInitialize,
160
+ onDetach,
161
+ } = props;
162
+ const state = useMemo(
163
+ () => ({
164
+ id: data.id,
165
+ remoteId: data.remoteId,
166
+ externalId: data.externalId,
167
+ title: data.title,
168
+ lastMessageAt: "lastMessageAt" in data ? data.lastMessageAt : undefined,
169
+ status: data.status,
170
+ custom: data.custom,
171
+ isRunning,
172
+ }),
173
+ [data, isRunning],
174
+ );
175
+
176
+ return {
177
+ getState: () => state,
178
+ switchTo: onSwitchTo,
179
+ rename: onRename,
180
+ updateCustom: onUpdateCustom,
181
+ archive: onArchive,
182
+ unarchive: onUnarchive,
183
+ delete: onDelete,
184
+ generateTitle: onGenerateTitle,
185
+ initialize: onInitialize,
186
+ detach: onDetach,
187
+ };
188
+ };
189
+
190
+ const ThreadListItemClient = resource(useThreadListItemClient);
191
+
192
+ const collectItemOrder = (
193
+ listState: RemoteThreadState,
194
+ mainThreadId: string,
195
+ ): RemoteThreadData[] => {
196
+ const ids = [
197
+ listState.newThreadId,
198
+ ...listState.threadIds,
199
+ ...listState.archivedThreadIds,
200
+ mainThreadId,
201
+ ].filter((id): id is string => id !== undefined);
202
+ const seenIds = new Set<string>();
203
+ const seenRemoteIds = new Set<string>();
204
+ const items: RemoteThreadData[] = [];
205
+ for (const id of ids) {
206
+ const data = getThreadData(listState, id);
207
+ if (!data || seenIds.has(data.id)) continue;
208
+ if (data.remoteId !== undefined && seenRemoteIds.has(data.remoteId)) {
209
+ continue;
210
+ }
211
+ seenIds.add(data.id);
212
+ if (data.remoteId !== undefined) seenRemoteIds.add(data.remoteId);
213
+ items.push(data);
214
+ }
215
+ return items;
216
+ };
217
+
218
+ const itemMatchesId = (
219
+ item: RemoteThreadData,
220
+ listState: RemoteThreadState,
221
+ id: string,
222
+ ) => {
223
+ const data = getThreadData(listState, id);
224
+ return (
225
+ item.id === id ||
226
+ item.id === data?.id ||
227
+ (data?.remoteId !== undefined &&
228
+ (item.id === data.remoteId || item.remoteId === data.remoteId))
229
+ );
230
+ };
231
+
232
+ const isSameThread = (
233
+ listState: RemoteThreadState,
234
+ left: string,
235
+ right: string,
236
+ ) => {
237
+ if (left === right) return true;
238
+ const data = getThreadData(listState, left);
239
+ return data !== undefined && itemMatchesId(data, listState, right);
240
+ };
241
+
242
+ const useRemoteThreadListView = ({
243
+ listState,
244
+ mainThreadId,
245
+ threadFactory,
246
+ useAdapters,
247
+ onSwitchTo,
248
+ onRename,
249
+ onUpdateCustom,
250
+ onArchive,
251
+ onUnarchive,
252
+ onDelete,
253
+ onGenerateTitle,
254
+ onInitialize,
255
+ onDetach,
256
+ }: {
257
+ listState: RemoteThreadState;
258
+ mainThreadId: string;
259
+ threadFactory: RemoteThreadListProps["thread"];
260
+ useAdapters: RemoteThreadListAdapter["unstable_useAdapters"];
261
+ onSwitchTo: (
262
+ threadId: string,
263
+ options?: { unarchive?: boolean },
264
+ ) => Promise<void>;
265
+ onRename: (threadId: string, title: string) => Promise<void>;
266
+ onUpdateCustom: (
267
+ threadId: string,
268
+ custom: Record<string, unknown> | undefined,
269
+ ) => Promise<void>;
270
+ onArchive: (threadId: string) => Promise<void>;
271
+ onUnarchive: (threadId: string) => Promise<void>;
272
+ onDelete: (threadId: string) => Promise<void>;
273
+ onGenerateTitle: (
274
+ threadId: string,
275
+ messages: readonly ThreadMessage[] | undefined,
276
+ ) => Promise<void>;
277
+ onInitialize: (threadId: string) => Promise<{
278
+ remoteId: string;
279
+ externalId: string | undefined;
280
+ }>;
281
+ onDetach: (threadId: string) => Promise<void>;
282
+ }) => {
283
+ const thread = threadFactory(mainThreadId);
284
+ const wrapped =
285
+ useAdapters === undefined
286
+ ? thread
287
+ : AdaptedRemoteThread({
288
+ useAdapters,
289
+ thread,
290
+ });
291
+ const mainThreadClient = useClientResource(
292
+ thread.key === undefined ? wrapped : withKey(thread.key, wrapped),
293
+ );
294
+ const itemOrder = useMemo(
295
+ () => collectItemOrder(listState, mainThreadId),
296
+ [listState, mainThreadId],
297
+ );
298
+ const threadListItems = useClientLookup(
299
+ itemOrder.map((data) =>
300
+ withKey(
301
+ data.id,
302
+ ThreadListItemClient({
303
+ data,
304
+ isRunning:
305
+ itemMatchesId(data, listState, mainThreadId) &&
306
+ mainThreadClient.state.isRunning,
307
+ onSwitchTo: (options) =>
308
+ handleThreadListAction("switch", () =>
309
+ onSwitchTo(data.id, options),
310
+ ),
311
+ onRename: (title) =>
312
+ handleThreadListAction("rename", () => onRename(data.id, title)),
313
+ onUpdateCustom: (custom) =>
314
+ handleThreadListAction("update custom metadata", () =>
315
+ onUpdateCustom(data.id, custom),
316
+ ),
317
+ onArchive: () =>
318
+ handleThreadListAction("archive", () => onArchive(data.id)),
319
+ onUnarchive: () =>
320
+ handleThreadListAction("unarchive", () => onUnarchive(data.id)),
321
+ onDelete: () =>
322
+ handleThreadListAction("delete", () => onDelete(data.id)),
323
+ onGenerateTitle: () =>
324
+ handleThreadListAction("generate title", () =>
325
+ onGenerateTitle(
326
+ data.id,
327
+ mainThreadClient.state.messages as
328
+ | readonly ThreadMessage[]
329
+ | undefined,
330
+ ),
331
+ ),
332
+ onInitialize: () => onInitialize(data.id),
333
+ onDetach: () =>
334
+ handleThreadListAction("detach", () => onDetach(data.id)),
335
+ }),
336
+ ),
337
+ ),
338
+ );
339
+ return { mainThreadClient, itemOrder, threadListItems };
340
+ };
341
+
342
+ const useRemoteThreadList = (
343
+ props: RemoteThreadListProps,
344
+ ): ClientOutput<"threads"> => {
345
+ const {
346
+ adapter,
347
+ thread: threadFactory,
348
+ threadId,
349
+ onThreadIdChange,
350
+ onSwitchToThread,
351
+ onSwitchToNewThread,
352
+ onDelete,
353
+ } = props;
354
+
355
+ const [{ store, initialMainId, session }] = useState(() => {
356
+ const seeded = seedNewThread(EMPTY_LIST);
357
+ return {
358
+ store: new OptimisticState(seeded.state),
359
+ initialMainId: seeded.id,
360
+ session: {
361
+ adapter,
362
+ loadGeneration: 0,
363
+ switchGeneration: 0,
364
+ loadPromise: undefined as Promise<void> | undefined,
365
+ loadMorePromise: undefined as Promise<void> | undefined,
366
+ lastNotifiedRemoteId: undefined as string | undefined,
367
+ lastControlledThreadId: undefined as string | undefined,
368
+ switchTask: undefined as Promise<void> | undefined,
369
+ mainThreadId: seeded.id,
370
+ isFirstThreadIdEffect: true,
371
+ onThreadIdChange,
372
+ onSwitchToThread,
373
+ onSwitchToNewThread,
374
+ },
375
+ };
376
+ });
377
+
378
+ const listState = useSyncExternalStore(
379
+ (onStoreChange) => store.subscribe(onStoreChange),
380
+ () => store.value,
381
+ () => store.value,
382
+ );
383
+
384
+ const [mainThreadId, setMainThreadId] = useState(initialMainId);
385
+ const assignMainThreadId = useCallback(
386
+ (id: string) => {
387
+ session.mainThreadId = id;
388
+ setMainThreadId(id);
389
+ },
390
+ [session],
391
+ );
392
+ useThreadSelectionEvents(mainThreadId);
393
+ useEffect(() => {
394
+ session.adapter = adapter;
395
+ session.mainThreadId = mainThreadId;
396
+ session.onThreadIdChange = onThreadIdChange;
397
+ session.onSwitchToThread = onSwitchToThread;
398
+ session.onSwitchToNewThread = onSwitchToNewThread;
399
+ }, [
400
+ adapter,
401
+ mainThreadId,
402
+ onSwitchToNewThread,
403
+ onSwitchToThread,
404
+ onThreadIdChange,
405
+ session,
406
+ ]);
407
+
408
+ const notifyRemoteId = useCallback(
409
+ (remoteId: string | undefined, emit: boolean) => {
410
+ if (session.lastNotifiedRemoteId === remoteId) return;
411
+ session.lastNotifiedRemoteId = remoteId;
412
+ if (emit) session.onThreadIdChange?.(remoteId);
413
+ },
414
+ [session],
415
+ );
416
+
417
+ const getLoadThreadsPromise = useCallback(() => {
418
+ if (session.loadPromise) return session.loadPromise;
419
+ const generation = session.loadGeneration;
420
+ session.loadPromise = store
421
+ .optimisticUpdate({
422
+ execute: () => session.adapter.list(),
423
+ loading: (state) => ({ ...state, isLoading: true }),
424
+ then: (state, page) => {
425
+ if (generation !== session.loadGeneration) return state;
426
+ const fresh = classifyThreads(page.threads, {
427
+ threadIds: [],
428
+ archivedThreadIds: [],
429
+ threadIdMap: {},
430
+ threadData: {},
431
+ });
432
+ return {
433
+ ...state,
434
+ isLoading: false,
435
+ cursor: normalizeCursor(page.nextCursor),
436
+ threadIds: fresh.threadIds,
437
+ archivedThreadIds: fresh.archivedThreadIds,
438
+ threadIdMap: {
439
+ ...state.threadIdMap,
440
+ ...fresh.threadIdMap,
441
+ },
442
+ threadData: {
443
+ ...state.threadData,
444
+ ...fresh.threadData,
445
+ },
446
+ };
447
+ },
448
+ })
449
+ .catch((error: unknown) => {
450
+ if (generation !== session.loadGeneration) return;
451
+ console.error("[assistant-ui] thread list load failed:", error);
452
+ session.loadPromise = undefined;
453
+ store.update({
454
+ ...store.baseValue,
455
+ isLoading: false,
456
+ });
457
+ })
458
+ .then(() => {});
459
+ return session.loadPromise;
460
+ }, [session, store]);
461
+
462
+ const reload = useCallback(() => {
463
+ session.loadGeneration++;
464
+ session.loadPromise = undefined;
465
+ session.loadMorePromise = undefined;
466
+ store.update({
467
+ ...store.baseValue,
468
+ cursor: undefined,
469
+ });
470
+ return getLoadThreadsPromise();
471
+ }, [getLoadThreadsPromise, session, store]);
472
+
473
+ useEffect(() => {
474
+ void getLoadThreadsPromise();
475
+ }, [getLoadThreadsPromise]);
476
+
477
+ useEffect(() => {
478
+ if (!isDevelopment) return;
479
+ if (adapter.unstable_useAdapters !== undefined) return;
480
+ if (adapter.unstable_Provider === undefined) return;
481
+ console.warn(
482
+ "[assistant-ui] RemoteThreadList ignores RemoteThreadListAdapter.unstable_Provider. Expose unstable_useAdapters so per-thread history loads on this entry. useRemoteThreadListRuntime still honors unstable_Provider.",
483
+ );
484
+ }, [adapter]);
485
+
486
+ const loadMore = useCallback(() => {
487
+ if (session.loadMorePromise) return session.loadMorePromise;
488
+ const snapshot = store.value;
489
+ if (snapshot.cursor === undefined || snapshot.isLoading) {
490
+ return RESOLVED_PROMISE;
491
+ }
492
+ const generation = session.loadGeneration;
493
+ const cursor = snapshot.cursor;
494
+ const currentAdapter = session.adapter;
495
+ const task = store
496
+ .optimisticUpdate({
497
+ execute: () => currentAdapter.list({ after: cursor }),
498
+ loading: (state) => ({ ...state, isLoadingMore: true }),
499
+ then: (state, page) => {
500
+ if (generation !== session.loadGeneration) return state;
501
+ const appended = classifyThreads(page.threads, {
502
+ threadIds: [...state.threadIds],
503
+ archivedThreadIds: [...state.archivedThreadIds],
504
+ threadIdMap: { ...state.threadIdMap },
505
+ threadData: { ...state.threadData },
506
+ });
507
+ return {
508
+ ...state,
509
+ isLoadingMore: false,
510
+ cursor: normalizeCursor(page.nextCursor),
511
+ threadIds: appended.threadIds,
512
+ archivedThreadIds: appended.archivedThreadIds,
513
+ threadIdMap: appended.threadIdMap,
514
+ threadData: appended.threadData,
515
+ };
516
+ },
517
+ })
518
+ .catch((error: unknown) => {
519
+ console.error("[assistant-ui] thread list loadMore failed:", error);
520
+ })
521
+ .then(() => {
522
+ if (session.loadMorePromise === task) {
523
+ session.loadMorePromise = undefined;
524
+ }
525
+ });
526
+ session.loadMorePromise = task;
527
+ return task;
528
+ }, [session, store]);
529
+
530
+ const startSwitch = useCallback(
531
+ (run: (generation: number) => Promise<void>) => {
532
+ const generation = ++session.switchGeneration;
533
+ let settle!: (error?: unknown) => void;
534
+ const task = new Promise<void>((resolve, reject) => {
535
+ settle = (error) => {
536
+ if (error === undefined) resolve();
537
+ else reject(error);
538
+ };
539
+ });
540
+ session.switchTask = task;
541
+ void run(generation).then(
542
+ () => settle(),
543
+ (error: unknown) => settle(error),
544
+ );
545
+ return task;
546
+ },
547
+ [session],
548
+ );
549
+
550
+ const switchToThread = useCallback(
551
+ (
552
+ threadIdOrRemoteId: string,
553
+ options?: { unarchive?: boolean },
554
+ emitThreadIdChange = true,
555
+ ) =>
556
+ startSwitch(async (generation) => {
557
+ let data = getThreadData(store.value, threadIdOrRemoteId);
558
+ if (!data) {
559
+ const remoteMetadata =
560
+ await session.adapter.fetch(threadIdOrRemoteId);
561
+ if (generation !== session.switchGeneration) return;
562
+ const state = store.value;
563
+ const mappingId = createThreadMappingId(remoteMetadata.remoteId);
564
+ const wasInTarget =
565
+ remoteMetadata.status === "regular"
566
+ ? state.threadIds.includes(remoteMetadata.remoteId)
567
+ : state.archivedThreadIds.includes(remoteMetadata.remoteId);
568
+ const threadIdsWithoutRemote = state.threadIds.filter(
569
+ (id) => id !== remoteMetadata.remoteId,
570
+ );
571
+ const archivedThreadIdsWithoutRemote = state.archivedThreadIds.filter(
572
+ (id) => id !== remoteMetadata.remoteId,
573
+ );
574
+ store.update({
575
+ ...state,
576
+ threadIds:
577
+ remoteMetadata.status === "regular"
578
+ ? wasInTarget
579
+ ? state.threadIds
580
+ : [...threadIdsWithoutRemote, remoteMetadata.remoteId]
581
+ : threadIdsWithoutRemote,
582
+ archivedThreadIds:
583
+ remoteMetadata.status === "archived"
584
+ ? wasInTarget
585
+ ? state.archivedThreadIds
586
+ : [...archivedThreadIdsWithoutRemote, remoteMetadata.remoteId]
587
+ : archivedThreadIdsWithoutRemote,
588
+ threadIdMap: {
589
+ ...state.threadIdMap,
590
+ [remoteMetadata.remoteId]: mappingId,
591
+ },
592
+ threadData: {
593
+ ...state.threadData,
594
+ [mappingId]: {
595
+ id: mappingId,
596
+ initializeTask: Promise.resolve({
597
+ remoteId: remoteMetadata.remoteId,
598
+ externalId: remoteMetadata.externalId,
599
+ }),
600
+ remoteId: remoteMetadata.remoteId,
601
+ externalId: remoteMetadata.externalId,
602
+ status: remoteMetadata.status,
603
+ title: remoteMetadata.title,
604
+ lastMessageAt: remoteMetadata.lastMessageAt,
605
+ custom: remoteMetadata.custom,
606
+ },
607
+ },
608
+ });
609
+ data = getThreadData(store.value, threadIdOrRemoteId);
610
+ }
611
+ if (!data) {
612
+ throw threadNotFoundError(threadIdOrRemoteId, "switching to it");
613
+ }
614
+ if (isSameThread(store.value, data.id, session.mainThreadId)) return;
615
+ if (data.status === "archived" && options?.unarchive !== false) {
616
+ const current = data;
617
+ const { remoteId } = await current.initializeTask;
618
+ if (generation !== session.switchGeneration) return;
619
+ await store.optimisticUpdate({
620
+ execute: () => session.adapter.unarchive(remoteId),
621
+ optimistic: (state) =>
622
+ updateStatusReducer(state, current.id, "regular"),
623
+ });
624
+ if (generation !== session.switchGeneration) return;
625
+ data = getThreadData(store.value, current.id) ?? current;
626
+ }
627
+ if (generation !== session.switchGeneration) return;
628
+ assignMainThreadId(data.id);
629
+ notifyRemoteId(data.remoteId, emitThreadIdChange);
630
+ session.onSwitchToThread?.(data.id);
631
+ }),
632
+ [assignMainThreadId, notifyRemoteId, session, startSwitch, store],
633
+ );
634
+
635
+ const switchToNewThread = useCallback(
636
+ (emitThreadIdChange = true) =>
637
+ startSwitch(async (generation) => {
638
+ while (
639
+ store.baseValue.newThreadId !== undefined &&
640
+ store.value.newThreadId === undefined
641
+ ) {
642
+ await store.waitForUpdate();
643
+ if (generation !== session.switchGeneration) return;
644
+ }
645
+ const existing = store.value.newThreadId;
646
+ if (existing !== undefined) {
647
+ const existingId =
648
+ getThreadData(store.value, existing)?.id ?? existing;
649
+ if (isSameThread(store.value, existingId, session.mainThreadId)) {
650
+ return;
651
+ }
652
+ assignMainThreadId(existingId);
653
+ notifyRemoteId(undefined, emitThreadIdChange);
654
+ session.onSwitchToNewThread?.();
655
+ return;
656
+ }
657
+ const seeded = seedNewThread(store.baseValue);
658
+ store.update(seeded.state);
659
+ if (generation !== session.switchGeneration) return;
660
+ assignMainThreadId(seeded.id);
661
+ notifyRemoteId(undefined, emitThreadIdChange);
662
+ session.onSwitchToNewThread?.();
663
+ }),
664
+ [assignMainThreadId, notifyRemoteId, session, startSwitch, store],
665
+ );
666
+
667
+ const ensureNotMain = useCallback(
668
+ async (threadId: string) => {
669
+ if (threadId === store.value.newThreadId) {
670
+ throw new Error("Cannot ensure new thread is not main");
671
+ }
672
+ let lastAwaitedTask: Promise<void> | undefined;
673
+ while (isSameThread(store.value, threadId, session.mainThreadId)) {
674
+ let switchTask = session.switchTask;
675
+ const startedFallback = !switchTask || switchTask === lastAwaitedTask;
676
+ if (startedFallback) {
677
+ switchTask = switchToNewThread();
678
+ }
679
+ lastAwaitedTask = switchTask;
680
+ try {
681
+ await switchTask;
682
+ } catch (error) {
683
+ if (startedFallback && session.switchTask === switchTask) {
684
+ throw error;
685
+ }
686
+ }
687
+ }
688
+ },
689
+ [session, store, switchToNewThread],
690
+ );
691
+
692
+ const initialize = useCallback(
693
+ async (threadId: string) => {
694
+ if (store.value.newThreadId !== threadId) {
695
+ const data = getThreadData(store.value, threadId);
696
+ if (!data) throw threadNotFoundError(threadId, "initializing it");
697
+ if (data.status === "new") {
698
+ throw threadStatusError(threadId, data.status, "be initialized here");
699
+ }
700
+ return toInitializeResult(await data.initializeTask);
701
+ }
702
+ const result = await store.optimisticUpdate({
703
+ execute: () => session.adapter.initialize(threadId),
704
+ optimistic: (state) => updateStatusReducer(state, threadId, "regular"),
705
+ loading: (state, task) => {
706
+ const mappingId = createThreadMappingId(threadId);
707
+ return {
708
+ ...state,
709
+ threadData: {
710
+ ...state.threadData,
711
+ [mappingId]: {
712
+ ...state.threadData[mappingId],
713
+ initializeTask: task,
714
+ },
715
+ },
716
+ };
717
+ },
718
+ then: (state, { remoteId, externalId }) => {
719
+ const data = getThreadData(state, threadId);
720
+ if (!data) return state;
721
+ const mappingId = createThreadMappingId(threadId);
722
+ return {
723
+ ...state,
724
+ threadIdMap: {
725
+ ...state.threadIdMap,
726
+ [remoteId]: mappingId,
727
+ },
728
+ threadData: {
729
+ ...state.threadData,
730
+ [mappingId]: {
731
+ ...data,
732
+ initializeTask: Promise.resolve({ remoteId, externalId }),
733
+ remoteId,
734
+ externalId,
735
+ },
736
+ },
737
+ };
738
+ },
739
+ });
740
+ if (threadId === session.mainThreadId) {
741
+ notifyRemoteId(result.remoteId, true);
742
+ }
743
+ return toInitializeResult(result);
744
+ },
745
+ [notifyRemoteId, session, store],
746
+ );
747
+
748
+ const rename = useCallback(
749
+ (threadIdOrRemoteId: string, newTitle: string) => {
750
+ const data = getThreadData(store.value, threadIdOrRemoteId);
751
+ if (!data) throw threadNotFoundError(threadIdOrRemoteId, "renaming it");
752
+ if (data.status === "new") {
753
+ throw threadStatusError(threadIdOrRemoteId, data.status, "be renamed");
754
+ }
755
+ return store.optimisticUpdate({
756
+ execute: async () => {
757
+ const { remoteId } = await data.initializeTask;
758
+ return session.adapter.rename(remoteId, newTitle);
759
+ },
760
+ optimistic: (state) => {
761
+ const current = getThreadData(state, threadIdOrRemoteId);
762
+ if (!current) return state;
763
+ return {
764
+ ...state,
765
+ threadData: {
766
+ ...state.threadData,
767
+ [current.id]: {
768
+ ...current,
769
+ title: newTitle,
770
+ },
771
+ },
772
+ };
773
+ },
774
+ });
775
+ },
776
+ [session, store],
777
+ );
778
+
779
+ const updateCustom = useCallback(
780
+ (
781
+ threadIdOrRemoteId: string,
782
+ custom: Record<string, unknown> | undefined,
783
+ ) => {
784
+ const data = getThreadData(store.value, threadIdOrRemoteId);
785
+ if (!data) {
786
+ throw threadNotFoundError(
787
+ threadIdOrRemoteId,
788
+ "updating its custom metadata",
789
+ );
790
+ }
791
+ if (data.status === "new") {
792
+ throw threadStatusError(
793
+ threadIdOrRemoteId,
794
+ data.status,
795
+ "update custom metadata",
796
+ );
797
+ }
798
+ if (!session.adapter.updateCustom) {
799
+ throw new Error(
800
+ "Remote thread list adapter does not support updating custom metadata",
801
+ );
802
+ }
803
+ return store.optimisticUpdate({
804
+ execute: async () => {
805
+ const { remoteId } = await data.initializeTask;
806
+ const currentAdapter = session.adapter;
807
+ if (!currentAdapter.updateCustom) {
808
+ throw new Error(
809
+ "Remote thread list adapter does not support updating custom metadata",
810
+ );
811
+ }
812
+ return currentAdapter.updateCustom(remoteId, custom);
813
+ },
814
+ optimistic: (state) => {
815
+ const current = getThreadData(state, threadIdOrRemoteId);
816
+ if (!current) return state;
817
+ return {
818
+ ...state,
819
+ threadData: {
820
+ ...state.threadData,
821
+ [current.id]: {
822
+ ...current,
823
+ custom,
824
+ },
825
+ },
826
+ };
827
+ },
828
+ });
829
+ },
830
+ [session, store],
831
+ );
832
+
833
+ const archive = useCallback(
834
+ async (threadIdOrRemoteId: string) => {
835
+ const data = getThreadData(store.value, threadIdOrRemoteId);
836
+ if (!data) throw threadNotFoundError(threadIdOrRemoteId, "archiving it");
837
+ if (data.status !== "regular") {
838
+ throw threadStatusError(threadIdOrRemoteId, data.status, "be archived");
839
+ }
840
+ await ensureNotMain(data.id);
841
+ return store.optimisticUpdate({
842
+ execute: async () => {
843
+ const { remoteId } = await data.initializeTask;
844
+ return session.adapter.archive(remoteId);
845
+ },
846
+ optimistic: (state) => updateStatusReducer(state, data.id, "archived"),
847
+ });
848
+ },
849
+ [ensureNotMain, session, store],
850
+ );
851
+
852
+ const unarchive = useCallback(
853
+ (threadIdOrRemoteId: string) => {
854
+ const data = getThreadData(store.value, threadIdOrRemoteId);
855
+ if (!data)
856
+ throw threadNotFoundError(threadIdOrRemoteId, "unarchiving it");
857
+ if (data.status !== "archived") {
858
+ throw threadStatusError(
859
+ threadIdOrRemoteId,
860
+ data.status,
861
+ "be unarchived",
862
+ );
863
+ }
864
+ return store.optimisticUpdate({
865
+ execute: async () => {
866
+ try {
867
+ const { remoteId } = await data.initializeTask;
868
+ return await session.adapter.unarchive(remoteId);
869
+ } catch (error) {
870
+ await ensureNotMain(data.id);
871
+ throw error;
872
+ }
873
+ },
874
+ optimistic: (state) => updateStatusReducer(state, data.id, "regular"),
875
+ });
876
+ },
877
+ [ensureNotMain, session, store],
878
+ );
879
+
880
+ const deleteThread = useCallback(
881
+ async (threadIdOrRemoteId: string) => {
882
+ const data = getThreadData(store.value, threadIdOrRemoteId);
883
+ if (!data) throw threadNotFoundError(threadIdOrRemoteId, "deleting it");
884
+ if (data.status !== "regular" && data.status !== "archived") {
885
+ throw threadStatusError(threadIdOrRemoteId, data.status, "be deleted");
886
+ }
887
+ await ensureNotMain(data.id);
888
+ onDelete?.(data.id);
889
+ return store.optimisticUpdate({
890
+ execute: async () => {
891
+ const { remoteId } = await data.initializeTask;
892
+ return session.adapter.delete(remoteId);
893
+ },
894
+ optimistic: (state) => updateStatusReducer(state, data.id, "deleted"),
895
+ });
896
+ },
897
+ [ensureNotMain, onDelete, session, store],
898
+ );
899
+
900
+ const generateTitle = useCallback(
901
+ async (
902
+ threadIdOrRemoteId: string,
903
+ messages: readonly ThreadMessage[] | undefined,
904
+ ) => {
905
+ const data = getThreadData(store.value, threadIdOrRemoteId);
906
+ if (!data) {
907
+ throw threadNotFoundError(threadIdOrRemoteId, "generating its title");
908
+ }
909
+ if (data.status === "new") {
910
+ throw threadStatusError(
911
+ threadIdOrRemoteId,
912
+ data.status,
913
+ "generate a title",
914
+ );
915
+ }
916
+ const { remoteId } = await data.initializeTask;
917
+ if (!isSameThread(store.value, data.id, session.mainThreadId)) return;
918
+ if (!messages) return;
919
+ const stream = await session.adapter.generateTitle(remoteId, messages);
920
+ await applyTitleStream(stream, (newTitle) => {
921
+ const state = store.baseValue;
922
+ const current = getThreadData(state, data.id);
923
+ if (!current) return;
924
+ store.update({
925
+ ...state,
926
+ threadData: {
927
+ ...state.threadData,
928
+ [current.id]: {
929
+ ...current,
930
+ title: newTitle,
931
+ },
932
+ },
933
+ });
934
+ });
935
+ },
936
+ [session, store],
937
+ );
938
+
939
+ const { mainThreadClient, itemOrder, threadListItems } =
940
+ useRemoteThreadListView({
941
+ listState,
942
+ mainThreadId,
943
+ threadFactory,
944
+ useAdapters: adapter.unstable_useAdapters,
945
+ onSwitchTo: (id, options) => switchToThread(id, options),
946
+ onRename: (id, title) => rename(id, title),
947
+ onUpdateCustom: (id, custom) => updateCustom(id, custom),
948
+ onArchive: (id) => archive(id),
949
+ onUnarchive: (id) => unarchive(id),
950
+ onDelete: (id) => deleteThread(id),
951
+ onGenerateTitle: (id, messages) => generateTitle(id, messages),
952
+ onInitialize: async (id) => toInitializeResult(await initialize(id)),
953
+ onDetach: (id) => ensureNotMain(id),
954
+ });
955
+
956
+ const mainRemoteId = getThreadData(listState, mainThreadId)?.remoteId;
957
+ useEffect(() => {
958
+ if (session.lastNotifiedRemoteId === mainRemoteId) return;
959
+ session.lastNotifiedRemoteId = mainRemoteId;
960
+ onThreadIdChange?.(mainRemoteId);
961
+ }, [mainRemoteId, onThreadIdChange, session]);
962
+
963
+ useEffect(() => {
964
+ if (session.isFirstThreadIdEffect) {
965
+ session.isFirstThreadIdEffect = false;
966
+ session.lastControlledThreadId = threadId;
967
+ if (threadId === undefined) return;
968
+ handleThreadListAction("switch", () =>
969
+ switchToThread(threadId, undefined, false),
970
+ );
971
+ return;
972
+ }
973
+ if (Object.is(session.lastControlledThreadId, threadId)) return;
974
+ session.lastControlledThreadId = threadId;
975
+ if (threadId === undefined) {
976
+ handleThreadListAction("create", () => switchToNewThread(false));
977
+ return;
978
+ }
979
+ handleThreadListAction("switch", () =>
980
+ switchToThread(threadId, undefined, false),
981
+ );
982
+ }, [session, switchToNewThread, switchToThread, threadId]);
983
+
984
+ const state = useMemo(
985
+ () => ({
986
+ mainThreadId,
987
+ newThreadId: listState.newThreadId ?? null,
988
+ isLoading: listState.isLoading,
989
+ isLoadingMore: listState.isLoadingMore,
990
+ hasMore: listState.cursor !== undefined,
991
+ threadIds: listState.threadIds,
992
+ archivedThreadIds: listState.archivedThreadIds,
993
+ threadItems: threadListItems.state,
994
+ main: mainThreadClient.state,
995
+ }),
996
+ [
997
+ listState.archivedThreadIds,
998
+ listState.cursor,
999
+ listState.isLoading,
1000
+ listState.isLoadingMore,
1001
+ listState.newThreadId,
1002
+ listState.threadIds,
1003
+ mainThreadClient.state,
1004
+ mainThreadId,
1005
+ threadListItems.state,
1006
+ ],
1007
+ );
1008
+
1009
+ return {
1010
+ getState: () => state,
1011
+ switchToThread: (id, options) => {
1012
+ handleThreadListAction("switch", () => switchToThread(id, options));
1013
+ },
1014
+ switchToNewThread: () => {
1015
+ handleThreadListAction("create", () => switchToNewThread());
1016
+ },
1017
+ getLoadThreadsPromise,
1018
+ reload,
1019
+ reloadMainThread: () => {
1020
+ if (getThreadData(store.value, mainThreadId)?.status === "new") {
1021
+ return RESOLVED_PROMISE;
1022
+ }
1023
+ return (
1024
+ mainThreadClient.methods.unstable_refetchThread?.() ?? RESOLVED_PROMISE
1025
+ );
1026
+ },
1027
+ loadMore,
1028
+ item: (selector) => {
1029
+ if (selector === "main") {
1030
+ const index = itemOrder.findIndex((item) =>
1031
+ itemMatchesId(item, listState, mainThreadId),
1032
+ );
1033
+ return threadListItems.get({ index });
1034
+ }
1035
+ if ("id" in selector) {
1036
+ const index = itemOrder.findIndex((item) =>
1037
+ itemMatchesId(item, listState, selector.id),
1038
+ );
1039
+ return threadListItems.get({ index });
1040
+ }
1041
+ const ids = selector.archived
1042
+ ? listState.archivedThreadIds
1043
+ : listState.threadIds;
1044
+ const id = ids[selector.index];
1045
+ if (id === undefined) {
1046
+ return threadListItems.get({ index: -1 });
1047
+ }
1048
+ const index = itemOrder.findIndex((item) =>
1049
+ itemMatchesId(item, listState, id),
1050
+ );
1051
+ return threadListItems.get({ index });
1052
+ },
1053
+ thread: () => mainThreadClient.methods,
1054
+ };
1055
+ };
1056
+
1057
+ /**
1058
+ * `AuiConfig` `threads` entry backed by a `RemoteThreadListAdapter`. Thread
1059
+ * bodies are born from the `thread` factory inside the client tree, so any
1060
+ * `AssistantClient` host can run a remote or cloud list. Per-thread history
1061
+ * and attachments come from `unstable_useAdapters`. `useRemoteThreadListRuntime`
1062
+ * uses the same hook when `unstable_Provider` is omitted. Key the factory
1063
+ * with `withKey` so history reloads when the visible thread changes.
1064
+ */
1065
+ export const RemoteThreadList = resource(useRemoteThreadList);
1066
+
1067
+ attachTransformScopes(useRemoteThreadList, inMemoryThreadListTransformScopes);