@assistant-ui/core 0.3.17 → 0.3.19

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 (470) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/attachment.js +5 -4
  3. package/dist/adapters/attachment.js.map +1 -1
  4. package/dist/adapters/speech.d.ts.map +1 -1
  5. package/dist/adapters/speech.js +13 -3
  6. package/dist/adapters/speech.js.map +1 -1
  7. package/dist/adapters/thread-history.d.ts +4 -0
  8. package/dist/adapters/thread-history.d.ts.map +1 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal.d.ts +4 -3
  12. package/dist/internal.js +3 -2
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +35 -14
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/frame/provider.d.ts +2 -0
  17. package/dist/model-context/frame/provider.d.ts.map +1 -1
  18. package/dist/model-context/frame/provider.js +30 -7
  19. package/dist/model-context/frame/provider.js.map +1 -1
  20. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  21. package/dist/model-context/interactable-composer-metadata.js +27 -18
  22. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  23. package/dist/model-context/registry.d.ts.map +1 -1
  24. package/dist/model-context/registry.js +3 -5
  25. package/dist/model-context/registry.js.map +1 -1
  26. package/dist/model-context/types.d.ts.map +1 -1
  27. package/dist/model-context/types.js +5 -4
  28. package/dist/model-context/types.js.map +1 -1
  29. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  30. package/dist/react/adapters/LocalStorageThreadListAdapter.js +45 -7
  31. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  32. package/dist/react/client/DataRenderers.d.ts.map +1 -1
  33. package/dist/react/client/DataRenderers.js +20 -15
  34. package/dist/react/client/DataRenderers.js.map +1 -1
  35. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  36. package/dist/react/client/InMemoryThreadList.js +62 -56
  37. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  38. package/dist/react/client/Interactables.d.ts.map +1 -1
  39. package/dist/react/client/Interactables.js +35 -32
  40. package/dist/react/client/Interactables.js.map +1 -1
  41. package/dist/react/client/RemoteThreadList.d.ts +1 -0
  42. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  43. package/dist/react/client/RemoteThreadList.js +154 -143
  44. package/dist/react/client/RemoteThreadList.js.map +1 -1
  45. package/dist/react/client/Tools.d.ts.map +1 -1
  46. package/dist/react/client/Tools.js +18 -16
  47. package/dist/react/client/Tools.js.map +1 -1
  48. package/dist/react/client/interactable-model-context.d.ts.map +1 -1
  49. package/dist/react/client/interactable-model-context.js +3 -2
  50. package/dist/react/client/interactable-model-context.js.map +1 -1
  51. package/dist/react/index.d.ts +2 -2
  52. package/dist/react/index.js +2 -2
  53. package/dist/react/interactables-legacy/Interactables.js +242 -121
  54. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  55. package/dist/react/interactables-legacy/useAssistantInteractable.d.ts.map +1 -1
  56. package/dist/react/interactables-legacy/useAssistantInteractable.js +3 -0
  57. package/dist/react/interactables-legacy/useAssistantInteractable.js.map +1 -1
  58. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  59. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  60. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  61. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  62. package/dist/react/model-context/makeAssistantTool.js +1 -1
  63. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  64. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  65. package/dist/react/model-context/useAssistantTool.js +1 -1
  66. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  67. package/dist/react/model-context/useInteractable.d.ts.map +1 -1
  68. package/dist/react/model-context/useInteractable.js +4 -1
  69. package/dist/react/model-context/useInteractable.js.map +1 -1
  70. package/dist/react/model-context/useInteractableVersions.js +1 -1
  71. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  72. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  73. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  74. package/dist/react/primitives/message/MessageGroupedParts.d.ts +11 -2
  75. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  76. package/dist/react/primitives/message/MessageGroupedParts.js +3 -2
  77. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  78. package/dist/react/primitives/part/PartMessages.d.ts +1 -0
  79. package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
  80. package/dist/react/primitives/part/PartMessages.js +1 -0
  81. package/dist/react/primitives/part/PartMessages.js.map +1 -1
  82. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  83. package/dist/react/primitives/thread/ThreadMessages.js +6 -5
  84. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  85. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  86. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +16 -8
  87. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  88. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -1
  89. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +105 -104
  91. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  92. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -0
  93. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  94. package/dist/react/runtimes/RemoteThreadResource.js +13 -11
  95. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  96. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +5 -24
  97. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  98. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +354 -226
  99. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  100. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -0
  101. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  102. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +22 -2
  103. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  104. package/dist/react/runtimes/cloud/auiV0.d.ts +40 -15
  105. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  106. package/dist/react/runtimes/cloud/auiV0.js +62 -15
  107. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  108. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +2 -1
  109. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
  110. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +6 -1
  111. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -1
  112. package/dist/react/runtimes/cloud/sdkIdentity.d.ts +6 -0
  113. package/dist/react/runtimes/cloud/sdkIdentity.d.ts.map +1 -0
  114. package/dist/react/runtimes/cloud/sdkIdentity.js +9 -0
  115. package/dist/react/runtimes/cloud/sdkIdentity.js.map +1 -0
  116. package/dist/react/runtimes/external-message-converter.d.ts +12 -2
  117. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  118. package/dist/react/runtimes/external-message-converter.js +26 -61
  119. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  120. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  121. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  122. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  123. package/dist/react/runtimes/useRemoteThreadListRuntime.js +64 -24
  124. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  125. package/dist/react/utils/groupParts.d.ts +6 -1
  126. package/dist/react/utils/groupParts.d.ts.map +1 -1
  127. package/dist/react/utils/groupParts.js +6 -0
  128. package/dist/react/utils/groupParts.js.map +1 -1
  129. package/dist/react/utils/useJSONEqual.d.ts +3 -1
  130. package/dist/react/utils/useJSONEqual.d.ts.map +1 -1
  131. package/dist/react/utils/useJSONEqual.js +6 -1
  132. package/dist/react/utils/useJSONEqual.js.map +1 -1
  133. package/dist/react/utils/useJSONSchemaDependency.d.ts +7 -0
  134. package/dist/react/utils/useJSONSchemaDependency.d.ts.map +1 -0
  135. package/dist/react/utils/useJSONSchemaDependency.js +20 -0
  136. package/dist/react/utils/useJSONSchemaDependency.js.map +1 -0
  137. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  138. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  139. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  140. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  141. package/dist/runtime/api/thread-list-runtime.d.ts +3 -0
  142. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  143. package/dist/runtime/api/thread-list-runtime.js +18 -14
  144. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  145. package/dist/runtime/api/thread-runtime.d.ts +4 -0
  146. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  147. package/dist/runtime/api/thread-runtime.js +3 -6
  148. package/dist/runtime/api/thread-runtime.js.map +1 -1
  149. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
  150. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  151. package/dist/runtime/base/base-composer-runtime-core.js +62 -22
  152. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  153. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -0
  154. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  155. package/dist/runtime/base/base-thread-runtime-core.js +161 -69
  156. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  157. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  158. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  159. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +4 -1
  160. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  161. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  162. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/queue/message-queue.d.ts +5 -0
  164. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  165. package/dist/runtime/queue/message-queue.js +61 -6
  166. package/dist/runtime/queue/message-queue.js.map +1 -1
  167. package/dist/runtime/utils/auto-status.d.ts +6 -2
  168. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  169. package/dist/runtime/utils/auto-status.js +12 -4
  170. package/dist/runtime/utils/auto-status.js.map +1 -1
  171. package/dist/runtime/utils/external-message-conversion.d.ts +16 -4
  172. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  173. package/dist/runtime/utils/external-message-conversion.js +49 -13
  174. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  175. package/dist/runtime/utils/message-repository.d.ts +1 -0
  176. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  177. package/dist/runtime/utils/message-repository.js +18 -9
  178. package/dist/runtime/utils/message-repository.js.map +1 -1
  179. package/dist/runtime/utils/thread-message-like.d.ts +2 -1
  180. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  181. package/dist/runtime/utils/thread-message-like.js +5 -3
  182. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  183. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  184. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  185. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  186. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  187. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +8 -1
  188. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  189. package/dist/runtimes/internal.d.ts +3 -3
  190. package/dist/runtimes/internal.js +2 -2
  191. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
  192. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  193. package/dist/runtimes/local/local-thread-runtime-core.js +4 -1
  194. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  195. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  196. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  197. package/dist/runtimes/remote-thread-list/remote-thread-state.js +130 -42
  198. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  199. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  200. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  201. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  202. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  203. package/dist/runtimes/remote-thread-list/title.d.ts +3 -1
  204. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -1
  205. package/dist/runtimes/remote-thread-list/title.js +9 -1
  206. package/dist/runtimes/remote-thread-list/title.js.map +1 -1
  207. package/dist/runtimes/remote-thread-list/types.d.ts +10 -0
  208. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  209. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  210. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -1
  211. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  212. package/dist/store/clients/external-thread.d.ts.map +1 -1
  213. package/dist/store/clients/external-thread.js +260 -203
  214. package/dist/store/clients/external-thread.js.map +1 -1
  215. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  216. package/dist/store/clients/model-context-client.js +2 -1
  217. package/dist/store/clients/model-context-client.js.map +1 -1
  218. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  219. package/dist/store/clients/single-thread-list.js +1 -0
  220. package/dist/store/clients/single-thread-list.js.map +1 -1
  221. package/dist/store/clients/suggestions.d.ts.map +1 -1
  222. package/dist/store/clients/suggestions.js +5 -4
  223. package/dist/store/clients/suggestions.js.map +1 -1
  224. package/dist/store/clients/thread-tasks.d.ts +13 -0
  225. package/dist/store/clients/thread-tasks.d.ts.map +1 -0
  226. package/dist/store/clients/thread-tasks.js +69 -0
  227. package/dist/store/clients/thread-tasks.js.map +1 -0
  228. package/dist/store/index.d.ts +3 -2
  229. package/dist/store/index.js +3 -3
  230. package/dist/store/primitive-predicates.d.ts.map +1 -1
  231. package/dist/store/primitive-predicates.js +4 -4
  232. package/dist/store/primitive-predicates.js.map +1 -1
  233. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  234. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  235. package/dist/store/runtime-clients/composer-runtime-client.js +97 -61
  236. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  237. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  238. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  239. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  240. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  241. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  242. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  243. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  244. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  245. package/dist/store/runtime-clients/thread-list-runtime-client.js +54 -52
  246. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  247. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  248. package/dist/store/runtime-clients/thread-runtime-client.js +246 -126
  249. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  250. package/dist/store/scope-registration.d.ts +2 -0
  251. package/dist/store/scope-registration.d.ts.map +1 -1
  252. package/dist/store/scopes/composer.d.ts +10 -2
  253. package/dist/store/scopes/composer.d.ts.map +1 -1
  254. package/dist/store/scopes/message.d.ts +30 -1
  255. package/dist/store/scopes/message.d.ts.map +1 -1
  256. package/dist/store/scopes/task.d.ts +36 -0
  257. package/dist/store/scopes/task.d.ts.map +1 -0
  258. package/dist/store/scopes/task.js +0 -0
  259. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  260. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  261. package/dist/store/scopes/thread.d.ts +28 -0
  262. package/dist/store/scopes/thread.d.ts.map +1 -1
  263. package/dist/store/scopes/threads.d.ts +1 -0
  264. package/dist/store/scopes/threads.d.ts.map +1 -1
  265. package/dist/subscribable/subscribable.d.ts +2 -1
  266. package/dist/subscribable/subscribable.d.ts.map +1 -1
  267. package/dist/subscribable/subscribable.js +23 -15
  268. package/dist/subscribable/subscribable.js.map +1 -1
  269. package/dist/types/attachment.d.ts +2 -1
  270. package/dist/types/attachment.d.ts.map +1 -1
  271. package/dist/types/attachment.js +2 -1
  272. package/dist/types/attachment.js.map +1 -1
  273. package/dist/types/index.d.ts +2 -2
  274. package/dist/types/message.d.ts +8 -1
  275. package/dist/types/message.d.ts.map +1 -1
  276. package/dist/types/message.js.map +1 -1
  277. package/dist/utils/abortable-iterable.d.ts +19 -0
  278. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  279. package/dist/utils/abortable-iterable.js +88 -0
  280. package/dist/utils/abortable-iterable.js.map +1 -0
  281. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  282. package/dist/utils/composite-context-provider.js +20 -2
  283. package/dist/utils/composite-context-provider.js.map +1 -1
  284. package/dist/utils/getGroupStatus.d.ts +18 -2
  285. package/dist/utils/getGroupStatus.d.ts.map +1 -1
  286. package/dist/utils/getGroupStatus.js +32 -7
  287. package/dist/utils/getGroupStatus.js.map +1 -1
  288. package/dist/utils/record.d.ts +11 -0
  289. package/dist/utils/record.d.ts.map +1 -0
  290. package/dist/utils/record.js +12 -0
  291. package/dist/utils/record.js.map +1 -0
  292. package/dist/utils/wire-media.d.ts.map +1 -1
  293. package/dist/utils/wire-media.js +2 -2
  294. package/dist/utils/wire-media.js.map +1 -1
  295. package/package.json +11 -11
  296. package/src/adapters/attachment.test.ts +50 -1
  297. package/src/adapters/attachment.ts +7 -4
  298. package/src/adapters/speech.test.ts +236 -0
  299. package/src/adapters/speech.ts +17 -10
  300. package/src/adapters/thread-history.ts +3 -0
  301. package/src/index.ts +2 -0
  302. package/src/internal.ts +4 -0
  303. package/src/model-context/frame/host.test.ts +137 -1
  304. package/src/model-context/frame/host.ts +49 -16
  305. package/src/model-context/frame/provider.test.ts +71 -0
  306. package/src/model-context/frame/provider.ts +37 -7
  307. package/src/model-context/interactable-composer-metadata.test.ts +58 -0
  308. package/src/model-context/interactable-composer-metadata.ts +19 -13
  309. package/src/model-context/registry.test.ts +12 -0
  310. package/src/model-context/registry.ts +3 -2
  311. package/src/model-context/types.test.ts +25 -0
  312. package/src/model-context/types.ts +9 -4
  313. package/src/react/RuntimeAdapter.test.ts +34 -0
  314. package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +244 -0
  315. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +104 -7
  316. package/src/react/client/DataRenderers.test.tsx +45 -0
  317. package/src/react/client/DataRenderers.ts +13 -9
  318. package/src/react/client/InMemoryThreadList.ts +1 -0
  319. package/src/react/client/Interactables.test.ts +72 -1
  320. package/src/react/client/Interactables.ts +22 -14
  321. package/src/react/client/RemoteThreadList.background.test.ts +148 -6
  322. package/src/react/client/RemoteThreadList.concurrent.test.tsx +42 -1
  323. package/src/react/client/RemoteThreadList.test.ts +386 -4
  324. package/src/react/client/RemoteThreadList.ts +181 -112
  325. package/src/react/client/Tools.ts +22 -11
  326. package/src/react/client/interactable-model-context.test.ts +44 -0
  327. package/src/react/client/interactable-model-context.ts +3 -2
  328. package/src/react/index.ts +5 -1
  329. package/src/react/interactables-legacy/Interactables.ts +16 -16
  330. package/src/react/interactables-legacy/useAssistantInteractable.test.tsx +132 -0
  331. package/src/react/interactables-legacy/useAssistantInteractable.ts +10 -1
  332. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  333. package/src/react/model-context/makeAssistantTool.ts +2 -2
  334. package/src/react/model-context/useAssistantTool.ts +2 -2
  335. package/src/react/model-context/useInteractable.test.tsx +177 -0
  336. package/src/react/model-context/useInteractable.ts +3 -0
  337. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  338. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  339. package/src/react/primitive-hooks/useActionBarCopy.test.ts +0 -1
  340. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +0 -1
  341. package/src/react/primitives/message/MessageGroupedParts.test.tsx +96 -0
  342. package/src/react/primitives/message/MessageGroupedParts.tsx +15 -4
  343. package/src/react/primitives/part/PartMessages.test.tsx +104 -1
  344. package/src/react/primitives/part/PartMessages.tsx +1 -0
  345. package/src/react/primitives/thread/ThreadMessages.test.tsx +142 -0
  346. package/src/react/primitives/thread/ThreadMessages.tsx +8 -5
  347. package/src/react/providers/ReadonlyThreadProvider.test.tsx +56 -0
  348. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  349. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +33 -0
  350. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  351. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +28 -5
  352. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  353. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +105 -0
  354. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +143 -112
  355. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  356. package/src/react/runtimes/RemoteThreadResource.ts +19 -12
  357. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +20 -14
  358. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +703 -6
  359. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +492 -402
  360. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +77 -0
  361. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +27 -1
  362. package/src/react/runtimes/cloud/auiV0.ts +160 -25
  363. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +36 -0
  364. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +8 -1
  365. package/src/react/runtimes/cloud/sdkIdentity.ts +9 -0
  366. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +18 -4
  367. package/src/react/runtimes/external-message-converter.test.ts +136 -1
  368. package/src/react/runtimes/external-message-converter.ts +39 -93
  369. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  370. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  371. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  372. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  373. package/src/react/runtimes/useRemoteThreadListRuntime.ts +34 -1
  374. package/src/react/utils/groupParts.ts +12 -1
  375. package/src/react/utils/useJSONEqual.ts +9 -0
  376. package/src/react/utils/useJSONSchemaDependency.ts +22 -0
  377. package/src/runtime/api/message-runtime.test.ts +75 -0
  378. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  379. package/src/runtime/api/thread-list-runtime.ts +21 -11
  380. package/src/runtime/api/thread-runtime.test.ts +136 -0
  381. package/src/runtime/api/thread-runtime.ts +10 -5
  382. package/src/runtime/base/base-composer-runtime-core.ts +105 -24
  383. package/src/runtime/base/base-thread-runtime-core.test.ts +1063 -2
  384. package/src/runtime/base/base-thread-runtime-core.ts +228 -87
  385. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  386. package/src/runtime/interfaces/thread-list-runtime-core.ts +5 -1
  387. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  388. package/src/runtime/queue/message-queue.ts +68 -6
  389. package/src/runtime/utils/auto-status.test.ts +112 -1
  390. package/src/runtime/utils/auto-status.ts +31 -5
  391. package/src/runtime/utils/external-message-conversion.test.ts +202 -0
  392. package/src/runtime/utils/external-message-conversion.ts +123 -32
  393. package/src/runtime/utils/message-repository.test.ts +49 -0
  394. package/src/runtime/utils/message-repository.ts +32 -20
  395. package/src/runtime/utils/thread-message-like.test.ts +52 -0
  396. package/src/runtime/utils/thread-message-like.ts +5 -1
  397. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  398. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +31 -1
  399. package/src/runtimes/internal.ts +1 -0
  400. package/src/runtimes/local/local-thread-runtime-core.test.ts +21 -0
  401. package/src/runtimes/local/local-thread-runtime-core.ts +11 -1
  402. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +128 -2
  403. package/src/runtimes/remote-thread-list/remote-thread-state.ts +182 -42
  404. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  405. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  406. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  407. package/src/runtimes/remote-thread-list/title.ts +13 -0
  408. package/src/runtimes/remote-thread-list/types.ts +10 -0
  409. package/src/runtimes/tool-invocations/EDGE_CASES.md +2 -6
  410. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +133 -0
  411. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +10 -6
  412. package/src/store/clients/external-thread.ts +67 -4
  413. package/src/store/clients/model-context-client.ts +5 -1
  414. package/src/store/clients/single-thread-list.ts +1 -0
  415. package/src/store/clients/suggestions.test.ts +40 -0
  416. package/src/store/clients/suggestions.ts +13 -10
  417. package/src/store/clients/thread-tasks.test.ts +274 -0
  418. package/src/store/clients/thread-tasks.ts +138 -0
  419. package/src/store/index.ts +8 -0
  420. package/src/store/primitive-predicates.test.ts +48 -5
  421. package/src/store/primitive-predicates.ts +7 -2
  422. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  423. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  424. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  425. package/src/store/runtime-clients/thread-list-runtime-client.ts +1 -0
  426. package/src/store/runtime-clients/thread-runtime-client.ts +89 -11
  427. package/src/store/scope-registration.ts +2 -0
  428. package/src/store/scopes/composer.ts +16 -4
  429. package/src/store/scopes/message.ts +14 -0
  430. package/src/store/scopes/task.ts +34 -0
  431. package/src/store/scopes/thread-list-item.ts +5 -2
  432. package/src/store/scopes/thread.ts +20 -0
  433. package/src/store/scopes/threads.ts +1 -0
  434. package/src/store/thread-tasks.test.tsx +209 -0
  435. package/src/subscribable/subscribable.test.ts +156 -1
  436. package/src/subscribable/subscribable.ts +25 -15
  437. package/src/tests/MessageRepository.test.ts +117 -5
  438. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  439. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  440. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +47 -0
  441. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  442. package/src/tests/attachment-adapters.test.ts +14 -0
  443. package/src/tests/auiV0Encode.test.ts +300 -0
  444. package/src/tests/base-composer-runtime-core-send.test.ts +51 -0
  445. package/src/tests/base-composer-runtime-core.test.ts +353 -2
  446. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  447. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +178 -18
  448. package/src/tests/external-thread-attachments.test.tsx +208 -0
  449. package/src/tests/external-thread-parity.test.tsx +57 -0
  450. package/src/tests/groupParts.test.ts +71 -0
  451. package/src/tests/message-queue.test.ts +253 -0
  452. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  453. package/src/tests/remote-thread-list-reactive-threadId.test.ts +51 -0
  454. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  455. package/src/tests/thread-switch-events.test.tsx +78 -0
  456. package/src/tests/tools-scope-migration.test.tsx +28 -0
  457. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +43 -0
  458. package/src/types/attachment.ts +4 -0
  459. package/src/types/index.ts +1 -0
  460. package/src/types/message.ts +8 -0
  461. package/src/utils/abortable-iterable.test.ts +328 -0
  462. package/src/utils/abortable-iterable.ts +104 -0
  463. package/src/utils/composite-context-provider.test.ts +25 -0
  464. package/src/utils/composite-context-provider.ts +23 -2
  465. package/src/utils/getGroupStatus.test.ts +70 -4
  466. package/src/utils/getGroupStatus.ts +35 -12
  467. package/src/utils/record.test.ts +48 -0
  468. package/src/utils/record.ts +11 -0
  469. package/src/utils/wire-media.test.ts +10 -0
  470. package/src/utils/wire-media.ts +6 -2
@@ -0,0 +1,96 @@
1
+ // @vitest-environment jsdom
2
+ import { cleanup, render } from "@testing-library/react";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import type { ThreadMessageLike } from "../../../runtime/utils/thread-message-like";
5
+ import { AssistantRuntimeProvider } from "../../AssistantRuntimeProvider";
6
+ import { ThreadPrimitiveMessages } from "../thread/ThreadMessages";
7
+ import { useExternalStoreRuntime } from "../../runtimes/useExternalStoreRuntime";
8
+ import { groupPartByType } from "../../utils/groupParts";
9
+ import { MessagePrimitiveGroupedParts } from "./MessageGroupedParts";
10
+
11
+ type Msg = {
12
+ id: string;
13
+ content: readonly {
14
+ type: "tool-call";
15
+ toolCallId: string;
16
+ toolName: string;
17
+ args: {};
18
+ result?: { ok: true };
19
+ }[];
20
+ };
21
+
22
+ const task = (toolCallId: string, hasResult: boolean) => ({
23
+ type: "tool-call" as const,
24
+ toolCallId,
25
+ toolName: "task",
26
+ args: {},
27
+ ...(hasResult ? { result: { ok: true } } : {}),
28
+ });
29
+
30
+ const convertMessage = (message: Msg): ThreadMessageLike => ({
31
+ id: message.id,
32
+ role: "assistant",
33
+ content: message.content,
34
+ });
35
+
36
+ afterEach(cleanup);
37
+
38
+ describe("MessagePrimitive.GroupedParts", () => {
39
+ it("passes status counts to a tool-name group", () => {
40
+ let group:
41
+ | {
42
+ counts: MessagePrimitiveGroupedParts.GroupCounts;
43
+ indices: readonly number[];
44
+ }
45
+ | undefined;
46
+
47
+ const GroupedParts = () => (
48
+ <MessagePrimitiveGroupedParts
49
+ groupBy={groupPartByType({
50
+ "tool-call:task": ["group-subagents"],
51
+ })}
52
+ >
53
+ {({ part, children }) => {
54
+ if (part.type === "group-subagents") {
55
+ group = { counts: part.counts, indices: part.indices };
56
+ return children;
57
+ }
58
+ return null;
59
+ }}
60
+ </MessagePrimitiveGroupedParts>
61
+ );
62
+
63
+ const App = () => {
64
+ const runtime = useExternalStoreRuntime<Msg>({
65
+ messages: [
66
+ {
67
+ id: "assistant-1",
68
+ content: [
69
+ task("task-1", true),
70
+ task("task-2", true),
71
+ task("task-3", false),
72
+ ],
73
+ },
74
+ ],
75
+ isRunning: true,
76
+ convertMessage,
77
+ onNew: async () => {},
78
+ });
79
+ return (
80
+ <AssistantRuntimeProvider runtime={runtime}>
81
+ <ThreadPrimitiveMessages components={{ Message: GroupedParts }} />
82
+ </AssistantRuntimeProvider>
83
+ );
84
+ };
85
+
86
+ render(<App />);
87
+
88
+ expect(group?.counts).toEqual({
89
+ running: 1,
90
+ complete: 2,
91
+ incomplete: 0,
92
+ requiresAction: 0,
93
+ });
94
+ expect(group?.indices).toHaveLength(3);
95
+ });
96
+ });
@@ -8,7 +8,7 @@ import type {
8
8
  MessagePartStatus,
9
9
  ToolCallMessagePartStatus,
10
10
  } from "../../../types/message";
11
- import { getGroupStatus } from "../../../utils/getGroupStatus";
11
+ import { getGroupSummary } from "../../../utils/getGroupStatus";
12
12
  import {
13
13
  buildGroupTree,
14
14
  GROUPBY_MEMO_KEY,
@@ -18,6 +18,14 @@ import {
18
18
  import { MessagePartChildren, type EnrichedPartState } from "./MessageParts";
19
19
 
20
20
  export namespace MessagePrimitiveGroupedParts {
21
+ /** Per status tallies over the group's `indices`; they sum to `indices.length`. */
22
+ export type GroupCounts = {
23
+ readonly running: number;
24
+ readonly complete: number;
25
+ readonly incomplete: number;
26
+ readonly requiresAction: number;
27
+ };
28
+
21
29
  /**
22
30
  * A coalesced group of adjacent parts. Surfaced through the same
23
31
  * `{ part }` channel as a leaf {@link EnrichedPartState} so consumers
@@ -28,6 +36,8 @@ export namespace MessagePrimitiveGroupedParts {
28
36
  export type GroupPart<TKey extends `group-${string}` = `group-${string}`> = {
29
37
  readonly type: TKey;
30
38
  readonly status: MessagePartStatus | ToolCallMessagePartStatus;
39
+ /** Per status tallies over `indices`. */
40
+ readonly counts: GroupCounts;
31
41
  readonly indices: readonly number[];
32
42
  };
33
43
 
@@ -85,10 +95,10 @@ export namespace MessagePrimitiveGroupedParts {
85
95
  * `switch (part.type)` can tell groups apart from real part types.
86
96
  *
87
97
  * **Prefer {@link groupPartByType}** for the common case of mapping by
88
- * `part.type` — it ships a stable memo fingerprint so the tree
98
+ * `part.type` or tool name — it ships a stable memo fingerprint so the tree
89
99
  * survives unrelated re-renders. Use an inline function only when
90
100
  * the helper isn't expressive enough (e.g. branching on
91
- * `part.toolName` or part metadata).
101
+ * `part.parentId` or part metadata).
92
102
  *
93
103
  * The second argument is a {@link GroupByContext} carrying the tool-UI
94
104
  * registry, for grouping that depends on it (e.g. standalone tool calls).
@@ -194,10 +204,11 @@ const renderNode = <TKey extends `group-${string}`>(
194
204
  );
195
205
  }
196
206
 
197
- const status = getGroupStatus(parts, node.indices);
207
+ const { status, counts } = getGroupSummary(parts, node.indices);
198
208
  const groupPart: MessagePrimitiveGroupedParts.GroupPart<TKey> = {
199
209
  type: node.key as TKey,
200
210
  status,
211
+ counts,
201
212
  indices: node.indices,
202
213
  };
203
214
 
@@ -1,4 +1,13 @@
1
- import { expectTypeOf } from "vitest";
1
+ // @vitest-environment jsdom
2
+
3
+ import { cleanup, render, screen } from "@testing-library/react";
4
+ import { useAuiState } from "@assistant-ui/store";
5
+ import { afterEach, describe, expect, expectTypeOf, it } from "vitest";
6
+ import type { ThreadMessage } from "../../../types/message";
7
+ import { AssistantRuntimeProvider } from "../../AssistantRuntimeProvider";
8
+ import { useExternalStoreRuntime } from "../../runtimes/useExternalStoreRuntime";
9
+ import { MessagePrimitiveParts } from "../message/MessageParts";
10
+ import { ThreadPrimitiveMessages } from "../thread/ThreadMessages";
2
11
  import { PartPrimitiveMessages } from "./PartMessages";
3
12
 
4
13
  const checkPartMessagesProps = () => {
@@ -15,3 +24,97 @@ const checkPartMessagesProps = () => {
15
24
  </PartPrimitiveMessages>;
16
25
  };
17
26
  expectTypeOf(checkPartMessagesProps).toEqualTypeOf<() => void>();
27
+
28
+ const nestedAssistant: ThreadMessage = {
29
+ id: "nested-assistant",
30
+ createdAt: new Date(0),
31
+ role: "assistant",
32
+ content: [],
33
+ status: { type: "running" },
34
+ metadata: {
35
+ unstable_state: {},
36
+ unstable_annotations: [],
37
+ unstable_data: [],
38
+ steps: [],
39
+ custom: {},
40
+ },
41
+ };
42
+
43
+ const NestedThreadStatus = () => {
44
+ const isRunning = useAuiState((s) => s.thread.isRunning);
45
+ return (
46
+ <output data-testid="nested-thread-running">{String(isRunning)}</output>
47
+ );
48
+ };
49
+
50
+ const OuterMessageStatus = () => {
51
+ const status = useAuiState((s) => s.message.status?.type);
52
+ return <output data-testid="outer-message-status">{status}</output>;
53
+ };
54
+
55
+ afterEach(cleanup);
56
+
57
+ describe("PartPrimitiveMessages", () => {
58
+ it("keeps the outer message running and reports the nested thread as running after the outer run ends", () => {
59
+ const App = () => {
60
+ const runtime = useExternalStoreRuntime({
61
+ messages: [
62
+ {
63
+ id: "outer-assistant",
64
+ createdAt: new Date(0),
65
+ role: "assistant" as const,
66
+ content: [
67
+ {
68
+ type: "tool-call" as const,
69
+ toolCallId: "delegate-1",
70
+ toolName: "delegate",
71
+ args: {},
72
+ argsText: "",
73
+ messages: [nestedAssistant],
74
+ },
75
+ ],
76
+ metadata: {
77
+ unstable_state: {},
78
+ unstable_annotations: [],
79
+ unstable_data: [],
80
+ steps: [],
81
+ custom: {},
82
+ },
83
+ },
84
+ ],
85
+ isRunning: false,
86
+ convertMessage: (message) => message,
87
+ onNew: async () => {},
88
+ });
89
+ return (
90
+ <AssistantRuntimeProvider runtime={runtime}>
91
+ <ThreadPrimitiveMessages>
92
+ {() => (
93
+ <>
94
+ <OuterMessageStatus />
95
+ <MessagePrimitiveParts>
96
+ {({ part }) =>
97
+ part.type === "tool-call" ? (
98
+ <PartPrimitiveMessages>
99
+ {() => <NestedThreadStatus />}
100
+ </PartPrimitiveMessages>
101
+ ) : null
102
+ }
103
+ </MessagePrimitiveParts>
104
+ </>
105
+ )}
106
+ </ThreadPrimitiveMessages>
107
+ </AssistantRuntimeProvider>
108
+ );
109
+ };
110
+
111
+ render(<App />);
112
+
113
+ expect(screen.getByTestId("outer-message-status").textContent).toBe(
114
+ "running",
115
+ );
116
+ expect(screen.getByTestId("nested-thread-running").textContent).toBe(
117
+ "true",
118
+ );
119
+ });
120
+ });
@@ -36,6 +36,7 @@ const usePartMessages = (): readonly ThreadMessage[] | undefined => {
36
36
  * This primitive reads `messages` from the current tool call part in the PartScope
37
37
  * and renders them using a readonly thread context. All existing message and part
38
38
  * primitives work inside, and parent tool UI registrations are inherited.
39
+ * The nested thread reports `isRunning` while its trailing assistant message is running.
39
40
  *
40
41
  * @example
41
42
  * ```tsx
@@ -0,0 +1,142 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { cleanup, render, screen, waitFor } from "@testing-library/react";
4
+ import { AuiProvider, useAui } from "@assistant-ui/store";
5
+ import { type FC, useState } from "react";
6
+ import { afterEach, describe, expect, it } from "vitest";
7
+ import {
8
+ ExternalThread,
9
+ type ExternalThreadProps,
10
+ } from "../../../store/clients/external-thread";
11
+ import type { MessageState } from "../../../store";
12
+ import { AssistantRuntimeProvider } from "../../AssistantRuntimeProvider";
13
+ import { useExternalStoreRuntime } from "../../runtimes/useExternalStoreRuntime";
14
+ import { ThreadPrimitiveMessages } from "./ThreadMessages";
15
+
16
+ const message = (id: string, role: "user" | "assistant") =>
17
+ ({
18
+ id,
19
+ createdAt: new Date(0),
20
+ role,
21
+ content: [{ type: "text", text: id }],
22
+ attachments: [],
23
+ metadata: {
24
+ custom: {},
25
+ },
26
+ ...(role === "assistant" ? { status: { type: "complete" } } : undefined),
27
+ }) as ExternalThreadProps["messages"][number];
28
+
29
+ const StatefulMessage = ({ message }: { message: MessageState }) => {
30
+ const [initialId] = useState(message.id);
31
+ return <span data-role={message.role}>{`${initialId}:${message.id}`}</span>;
32
+ };
33
+
34
+ const renderMessages = (messages: ExternalThreadProps["messages"]) => {
35
+ const App: FC<{ messages: ExternalThreadProps["messages"] }> = ({
36
+ messages,
37
+ }) => {
38
+ const aui = useAui({ thread: ExternalThread({ messages }) });
39
+ return (
40
+ <AuiProvider value={aui}>
41
+ <ThreadPrimitiveMessages>
42
+ {({ message }) => <StatefulMessage message={message} />}
43
+ </ThreadPrimitiveMessages>
44
+ </AuiProvider>
45
+ );
46
+ };
47
+ const result = render(<App messages={messages} />);
48
+ return {
49
+ rerender: (nextMessages: ExternalThreadProps["messages"]) =>
50
+ result.rerender(<App messages={nextMessages} />),
51
+ };
52
+ };
53
+
54
+ const renderExternalStoreMessages = (
55
+ messages: ExternalThreadProps["messages"],
56
+ isRunning: boolean,
57
+ ) => {
58
+ const App: FC<{
59
+ messages: ExternalThreadProps["messages"];
60
+ isRunning: boolean;
61
+ }> = ({ messages, isRunning }) => {
62
+ const runtime = useExternalStoreRuntime({
63
+ messages,
64
+ isRunning,
65
+ convertMessage: (message) => message,
66
+ onNew: async () => {},
67
+ });
68
+ return (
69
+ <AssistantRuntimeProvider runtime={runtime}>
70
+ <ThreadPrimitiveMessages>
71
+ {({ message }) => <StatefulMessage message={message} />}
72
+ </ThreadPrimitiveMessages>
73
+ </AssistantRuntimeProvider>
74
+ );
75
+ };
76
+ const result = render(<App messages={messages} isRunning={isRunning} />);
77
+ return {
78
+ container: result.container,
79
+ rerender: (
80
+ nextMessages: ExternalThreadProps["messages"],
81
+ nextIsRunning: boolean,
82
+ ) =>
83
+ result.rerender(
84
+ <App messages={nextMessages} isRunning={nextIsRunning} />,
85
+ ),
86
+ };
87
+ };
88
+
89
+ describe("ThreadPrimitiveMessages", () => {
90
+ afterEach(cleanup);
91
+
92
+ it("keeps component state with the surviving message after removal", () => {
93
+ const first = message("first", "user");
94
+ const second = message("second", "assistant");
95
+ const thread = renderMessages([first, second]);
96
+
97
+ thread.rerender([second]);
98
+
99
+ expect(screen.queryByText("second:second")).not.toBeNull();
100
+ expect(screen.queryByText("first:second")).toBeNull();
101
+ });
102
+
103
+ it("keeps component state with messages after prepending history", () => {
104
+ const first = message("first", "user");
105
+ const second = message("second", "assistant");
106
+ const thread = renderMessages([first, second]);
107
+
108
+ thread.rerender([message("zeroth", "assistant"), first, second]);
109
+
110
+ expect(screen.queryByText("first:first")).not.toBeNull();
111
+ expect(screen.queryByText("second:second")).not.toBeNull();
112
+ expect(screen.queryByText("first:zeroth")).toBeNull();
113
+ expect(screen.queryByText("second:first")).toBeNull();
114
+ });
115
+
116
+ it("remounts a message when its runtime ID changes", () => {
117
+ const thread = renderMessages([message("client-id", "assistant")]);
118
+
119
+ thread.rerender([message("server-id", "assistant")]);
120
+
121
+ expect(screen.queryByText("server-id:server-id")).not.toBeNull();
122
+ expect(screen.queryByText("client-id:server-id")).toBeNull();
123
+ });
124
+
125
+ it("remounts an optimistic placeholder as the real assistant message", async () => {
126
+ const userMessage = message("user-id", "user");
127
+ const thread = renderExternalStoreMessages([userMessage], true);
128
+ const optimisticRow = thread.container.querySelector(
129
+ '[data-role="assistant"]',
130
+ );
131
+
132
+ expect(optimisticRow).not.toBeNull();
133
+ expect(optimisticRow?.textContent).not.toBe("assistant-id:assistant-id");
134
+
135
+ thread.rerender([userMessage, message("assistant-id", "assistant")], false);
136
+
137
+ await waitFor(() => {
138
+ expect(screen.queryByText("assistant-id:assistant-id")).not.toBeNull();
139
+ });
140
+ expect(optimisticRow?.isConnected).toBe(false);
141
+ });
142
+ });
@@ -6,6 +6,7 @@ import {
6
6
  useMemo,
7
7
  } from "react";
8
8
  import { RenderChildrenWithAccessor, useAuiState } from "@assistant-ui/store";
9
+ import { useShallowSelector } from "@assistant-ui/store/internal";
9
10
  import { MessageByIndexProvider } from "../../providers/MessageByIndexProvider";
10
11
  import { MessageByIdProvider } from "../../providers/MessageByIdProvider";
11
12
  import type { MessageState } from "../../../store";
@@ -241,12 +242,14 @@ ThreadPrimitiveUnstable_MessageById.displayName =
241
242
  const ThreadPrimitiveMessagesInner: FC<{
242
243
  children: (value: { message: MessageState }) => ReactNode;
243
244
  }> = ({ children }) => {
244
- const messagesLength = useAuiState((s) => s.thread.messages.length);
245
+ const messageIds = useAuiState(
246
+ useShallowSelector((s) => s.thread.messages.map((message) => message.id)),
247
+ );
245
248
 
246
249
  return useMemo(() => {
247
- if (messagesLength === 0) return null;
248
- return Array.from({ length: messagesLength }, (_, index) => (
249
- <MessageByIndexProvider key={index} index={index}>
250
+ if (messageIds.length === 0) return null;
251
+ return messageIds.map((messageId, index) => (
252
+ <MessageByIndexProvider key={messageId} index={index}>
250
253
  <RenderChildrenWithAccessor
251
254
  getItemState={(aui) => aui.thread.message({ index }).getState()}
252
255
  >
@@ -260,7 +263,7 @@ const ThreadPrimitiveMessagesInner: FC<{
260
263
  </RenderChildrenWithAccessor>
261
264
  </MessageByIndexProvider>
262
265
  ));
263
- }, [messagesLength, children]);
266
+ }, [messageIds, children]);
264
267
  };
265
268
 
266
269
  /**
@@ -0,0 +1,56 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { cleanup, render, screen, waitFor } from "@testing-library/react";
4
+ import { useAuiState } from "@assistant-ui/store";
5
+ import { afterEach, describe, expect, it } from "vitest";
6
+ import type { ThreadMessage } from "../../types/message";
7
+ import { ReadonlyThreadProvider } from "./ReadonlyThreadProvider";
8
+
9
+ const messages = (status: "running" | "complete"): readonly ThreadMessage[] => [
10
+ {
11
+ id: "nested-assistant",
12
+ createdAt: new Date(0),
13
+ role: "assistant",
14
+ content: [],
15
+ status:
16
+ status === "running"
17
+ ? { type: "running" }
18
+ : { type: "complete", reason: "unknown" },
19
+ metadata: {
20
+ unstable_state: {},
21
+ unstable_annotations: [],
22
+ unstable_data: [],
23
+ steps: [],
24
+ custom: {},
25
+ },
26
+ },
27
+ ];
28
+
29
+ const ThreadStatus = () => {
30
+ const isRunning = useAuiState((s) => s.thread.isRunning);
31
+ return <output data-testid="thread-running">{String(isRunning)}</output>;
32
+ };
33
+
34
+ afterEach(cleanup);
35
+
36
+ describe("ReadonlyThreadProvider", () => {
37
+ it("reports isRunning from the trailing assistant message", async () => {
38
+ const result = render(
39
+ <ReadonlyThreadProvider messages={messages("running")}>
40
+ <ThreadStatus />
41
+ </ReadonlyThreadProvider>,
42
+ );
43
+
44
+ expect(screen.getByTestId("thread-running").textContent).toBe("true");
45
+
46
+ result.rerender(
47
+ <ReadonlyThreadProvider messages={messages("complete")}>
48
+ <ThreadStatus />
49
+ </ReadonlyThreadProvider>,
50
+ );
51
+
52
+ await waitFor(() => {
53
+ expect(screen.getByTestId("thread-running").textContent).toBe("false");
54
+ });
55
+ });
56
+ });
@@ -0,0 +1,172 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { ThreadListRuntimeCore } from "../../runtime/interfaces/thread-list-runtime-core";
3
+ import type { ThreadRuntimeCore } from "../../runtime/interfaces/thread-runtime-core";
4
+ import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
5
+
6
+ const makeManager = () =>
7
+ new RemoteThreadListHookInstanceManager(
8
+ () => ({}) as never,
9
+ {} as ThreadListRuntimeCore,
10
+ );
11
+
12
+ // No AdapterSink attaches a runtime here, so the start and restart promises
13
+ // stay pending or reject on stop; neither is what these tests are about.
14
+ const start = (manager: RemoteThreadListHookInstanceManager, id: string) => {
15
+ manager.startThreadRuntime(id).catch(() => {});
16
+ };
17
+ const restart = (manager: RemoteThreadListHookInstanceManager, id: string) => {
18
+ manager.__internal_restartThreadRuntime(id).catch(() => {});
19
+ };
20
+
21
+ const publishedSignal = (
22
+ manager: RemoteThreadListHookInstanceManager,
23
+ id: string,
24
+ ) => {
25
+ const { hostStore } = manager as unknown as {
26
+ hostStore: {
27
+ getState: () => {
28
+ threads: readonly { id: string; destroySignal: AbortSignal }[];
29
+ };
30
+ };
31
+ };
32
+ return hostStore.getState().threads.find((thread) => thread.id === id)
33
+ ?.destroySignal;
34
+ };
35
+
36
+ describe("RemoteThreadListHookInstanceManager destroy signal", () => {
37
+ it("publishes a live signal for each started thread", () => {
38
+ const manager = makeManager();
39
+ start(manager, "thread-1");
40
+
41
+ expect(publishedSignal(manager, "thread-1")?.aborted).toBe(false);
42
+ });
43
+
44
+ it("aborts the thread's signal when its runtime stops", () => {
45
+ const manager = makeManager();
46
+ start(manager, "thread-1");
47
+ const signal = publishedSignal(manager, "thread-1")!;
48
+
49
+ manager.stopThreadRuntime("thread-1");
50
+
51
+ expect(signal.aborted).toBe(true);
52
+ });
53
+
54
+ it("leaves sibling threads untouched when one stops", () => {
55
+ const manager = makeManager();
56
+ start(manager, "thread-1");
57
+ start(manager, "thread-2");
58
+ const sibling = publishedSignal(manager, "thread-2")!;
59
+
60
+ manager.stopThreadRuntime("thread-1");
61
+
62
+ expect(sibling.aborted).toBe(false);
63
+ });
64
+
65
+ it("hands the next generation a fresh signal on restart", () => {
66
+ const manager = makeManager();
67
+ start(manager, "thread-1");
68
+ const first = publishedSignal(manager, "thread-1")!;
69
+
70
+ restart(manager, "thread-1");
71
+ const second = publishedSignal(manager, "thread-1")!;
72
+
73
+ expect(first.aborted).toBe(true);
74
+ expect(second).not.toBe(first);
75
+ expect(second.aborted).toBe(false);
76
+ });
77
+
78
+ it("aborts every thread when the manager is disposed", () => {
79
+ const manager = makeManager();
80
+ start(manager, "thread-1");
81
+ start(manager, "thread-2");
82
+ const signals = [
83
+ publishedSignal(manager, "thread-1")!,
84
+ publishedSignal(manager, "thread-2")!,
85
+ ];
86
+
87
+ manager.__internal_dispose();
88
+
89
+ expect(signals.map((signal) => signal.aborted)).toEqual([true, true]);
90
+ });
91
+ });
92
+
93
+ const makeRunningRuntime = () => {
94
+ const subscribers = new Set<() => void>();
95
+ const eventListeners = new Map<string, Set<() => void>>();
96
+ const runtime = {
97
+ isRunning: true,
98
+ messages: [],
99
+ subscribe: (callback: () => void) => {
100
+ subscribers.add(callback);
101
+ return () => subscribers.delete(callback);
102
+ },
103
+ unstable_on: (event: string, callback: () => void) => {
104
+ let listeners = eventListeners.get(event);
105
+ if (!listeners) {
106
+ listeners = new Set();
107
+ eventListeners.set(event, listeners);
108
+ }
109
+ listeners.add(callback);
110
+ return () => listeners.delete(callback);
111
+ },
112
+ } as unknown as ThreadRuntimeCore & { isRunning: boolean };
113
+
114
+ return {
115
+ runtime,
116
+ // what a stopOnClientDestroy consumer does when the destroy signal fires
117
+ stop: () => {
118
+ runtime.isRunning = false;
119
+ for (const callback of eventListeners.get("runEnd") ?? []) callback();
120
+ for (const callback of subscribers) callback();
121
+ },
122
+ };
123
+ };
124
+
125
+ const publish = (
126
+ manager: RemoteThreadListHookInstanceManager,
127
+ threadId: string,
128
+ runtime: ThreadRuntimeCore,
129
+ ) => {
130
+ const internals = manager as unknown as {
131
+ instances: Map<string, { generation: number }>;
132
+ _publishThreadRuntime: (
133
+ threadId: string,
134
+ runtime: ThreadRuntimeCore,
135
+ generation: number,
136
+ ) => void;
137
+ };
138
+ internals._publishThreadRuntime(
139
+ threadId,
140
+ runtime,
141
+ internals.instances.get(threadId)!.generation,
142
+ );
143
+ };
144
+
145
+ describe("RemoteThreadListHookInstanceManager restart teardown", () => {
146
+ it("keeps the outgoing generation's terminal events off the thread's subscribers", () => {
147
+ const manager = makeManager();
148
+ start(manager, "thread-1");
149
+ const { runtime, stop } = makeRunningRuntime();
150
+ publish(manager, "thread-1", runtime);
151
+
152
+ const signal = publishedSignal(manager, "thread-1")!;
153
+ signal.addEventListener("abort", stop, { once: true });
154
+
155
+ const events: string[] = [];
156
+ manager.__internal_subscribeThreadEvents((event) =>
157
+ events.push(event.type),
158
+ );
159
+ const runningChanges: boolean[] = [];
160
+ manager.__internal_subscribeRunningChanged(() =>
161
+ runningChanges.push(manager.__internal_isThreadRunning("thread-1")),
162
+ );
163
+ expect(manager.__internal_isThreadRunning("thread-1")).toBe(true);
164
+
165
+ restart(manager, "thread-1");
166
+
167
+ expect(signal.aborted).toBe(true);
168
+ expect(events).toEqual([]);
169
+ expect(runningChanges).toEqual([]);
170
+ expect(manager.__internal_isThreadRunning("thread-1")).toBe(true);
171
+ });
172
+ });