@assistant-ui/core 0.3.15 → 0.3.17

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 (455) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +2 -2
  3. package/dist/index.js.map +1 -1
  4. package/dist/internal.d.ts +8 -2
  5. package/dist/internal.js +9 -3
  6. package/dist/model-context/frame/host.d.ts.map +1 -1
  7. package/dist/model-context/frame/host.js +3 -2
  8. package/dist/model-context/frame/host.js.map +1 -1
  9. package/dist/model-context/frame/provider.d.ts +5 -0
  10. package/dist/model-context/frame/provider.d.ts.map +1 -1
  11. package/dist/model-context/frame/provider.js +141 -28
  12. package/dist/model-context/frame/provider.js.map +1 -1
  13. package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
  14. package/dist/model-context/interactable-composer-metadata.js +1 -1
  15. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  16. package/dist/model-context/registry.d.ts +2 -2
  17. package/dist/model-context/registry.d.ts.map +1 -1
  18. package/dist/model-context/registry.js +9 -28
  19. package/dist/model-context/registry.js.map +1 -1
  20. package/dist/model-context/tool-call-text.d.ts +26 -0
  21. package/dist/model-context/tool-call-text.d.ts.map +1 -0
  22. package/dist/model-context/tool-call-text.js +18 -0
  23. package/dist/model-context/tool-call-text.js.map +1 -0
  24. package/dist/react/RuntimeAdapter.js +1 -1
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  26. package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -1
  27. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  28. package/dist/react/client/AdaptedRemoteThread.js +1 -1
  29. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  30. package/dist/react/client/InMemoryThreadList.js +25 -21
  31. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  32. package/dist/react/client/Interactables.d.ts.map +1 -1
  33. package/dist/react/client/Interactables.js +60 -178
  34. package/dist/react/client/Interactables.js.map +1 -1
  35. package/dist/react/client/RemoteThreadList.d.ts +15 -0
  36. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  37. package/dist/react/client/RemoteThreadList.js +263 -194
  38. package/dist/react/client/RemoteThreadList.js.map +1 -1
  39. package/dist/react/client/Tools.d.ts.map +1 -1
  40. package/dist/react/client/Tools.js +6 -2
  41. package/dist/react/client/Tools.js.map +1 -1
  42. package/dist/react/index.d.ts +4 -2
  43. package/dist/react/index.js +2 -1
  44. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  45. package/dist/react/interactables-legacy/Interactables.js +60 -178
  46. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  47. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
  48. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
  49. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
  50. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
  51. package/dist/react/model-context/toolbox.d.ts +2 -14
  52. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  53. package/dist/react/model-context/toolbox.js +1 -13
  54. package/dist/react/model-context/toolbox.js.map +1 -1
  55. package/dist/react/model-context/useAssistantContext.js +37 -7
  56. package/dist/react/model-context/useAssistantContext.js.map +1 -1
  57. package/dist/react/model-context/useAuiToolOverrides.js +31 -5
  58. package/dist/react/model-context/useAuiToolOverrides.js.map +1 -1
  59. package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
  60. package/dist/react/model-context/useInlineRender.js +10 -36
  61. package/dist/react/model-context/useInlineRender.js.map +1 -1
  62. package/dist/react/primitive-hooks/useMessageError.d.ts.map +1 -1
  63. package/dist/react/primitive-hooks/useMessageError.js +2 -8
  64. package/dist/react/primitive-hooks/useMessageError.js.map +1 -1
  65. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
  66. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -4
  67. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  68. package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  69. package/dist/react/primitives/composer/ComposerAttachments.js +4 -3
  70. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  71. package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
  72. package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
  73. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  74. package/dist/react/primitives/composer/ComposerQueue.js +4 -3
  75. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  76. package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
  77. package/dist/react/primitives/message/MessageAttachments.js +7 -6
  78. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  79. package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
  80. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  81. package/dist/react/primitives/message/MessageParts.js +3 -3
  82. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  83. package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  84. package/dist/react/primitives/threadList/ThreadListItems.js +5 -4
  85. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  86. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +7 -5
  87. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  88. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +44 -22
  89. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +7 -6
  91. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  92. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +102 -117
  93. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  94. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -6
  95. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  96. package/dist/react/runtimes/RemoteThreadResource.js +3 -3
  97. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  98. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
  99. package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
  100. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
  101. package/dist/react/runtimes/assistant-transport/useLatestRef.js +2 -2
  102. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -1
  103. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
  104. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  105. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +43 -85
  106. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  107. package/dist/react/runtimes/cloud/auiV0.d.ts +5 -1
  108. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  109. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  110. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +29 -0
  111. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -0
  112. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +159 -0
  113. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -0
  114. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
  115. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
  116. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
  117. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
  118. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +1 -9
  119. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  120. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +16 -153
  121. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  122. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
  123. package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
  124. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  125. package/dist/react/runtimes/external-message-converter.d.ts +6 -25
  126. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  127. package/dist/react/runtimes/external-message-converter.js +33 -233
  128. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  129. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  130. package/dist/react/runtimes/useRemoteThreadListRuntime.js +14 -6
  131. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  132. package/dist/react/types/MessagePartComponentTypes.d.ts +7 -3
  133. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  134. package/dist/react/types/scopes/tools.d.ts +2 -0
  135. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  136. package/dist/runtime/api/composer-runtime.d.ts +1 -1
  137. package/dist/runtime/api/composer-runtime.js.map +1 -1
  138. package/dist/runtime/api/message-part-runtime.d.ts +2 -2
  139. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  140. package/dist/runtime/api/message-part-runtime.js +1 -1
  141. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  142. package/dist/runtime/api/thread-list-runtime.d.ts +9 -1
  143. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  144. package/dist/runtime/api/thread-list-runtime.js +5 -0
  145. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  146. package/dist/runtime/api/thread-runtime.d.ts +2 -3
  147. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  148. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  149. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  150. package/dist/runtime/base/base-composer-runtime-core.js +14 -29
  151. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  152. package/dist/runtime/base/base-thread-runtime-core.d.ts +4 -6
  153. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  154. package/dist/runtime/base/base-thread-runtime-core.js +5 -11
  155. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  156. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  157. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  158. package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
  159. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  160. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
  161. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +14 -2
  162. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/interfaces/thread-runtime-core.d.ts +16 -2
  164. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  165. package/dist/runtime/internal.d.ts +2 -1
  166. package/dist/runtime/internal.js +2 -1
  167. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
  168. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  169. package/dist/runtime/queue/message-queue.js +1 -1
  170. package/dist/runtime/queue/message-queue.js.map +1 -1
  171. package/dist/runtime/queue/queue-item.d.ts +12 -0
  172. package/dist/runtime/queue/queue-item.d.ts.map +1 -0
  173. package/dist/runtime/queue/queue-item.js +6 -0
  174. package/dist/runtime/queue/queue-item.js.map +1 -0
  175. package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
  176. package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
  177. package/dist/runtime/utils/abortable-thread-load.js +35 -0
  178. package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
  179. package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
  180. package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
  181. package/dist/runtime/utils/attachment-add-operations.js +43 -0
  182. package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
  183. package/dist/runtime/utils/auto-status.d.ts +7 -2
  184. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  185. package/dist/runtime/utils/auto-status.js +13 -3
  186. package/dist/runtime/utils/auto-status.js.map +1 -1
  187. package/dist/runtime/utils/external-message-conversion.d.ts +48 -0
  188. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
  189. package/dist/runtime/utils/external-message-conversion.js +222 -0
  190. package/dist/runtime/utils/external-message-conversion.js.map +1 -0
  191. package/dist/runtime/utils/external-store-message.d.ts +2 -1
  192. package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
  193. package/dist/runtime/utils/external-store-message.js +2 -1
  194. package/dist/runtime/utils/external-store-message.js.map +1 -1
  195. package/dist/runtime/utils/message-repository-session.d.ts +30 -0
  196. package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
  197. package/dist/runtime/utils/message-repository-session.js +130 -0
  198. package/dist/runtime/utils/message-repository-session.js.map +1 -0
  199. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  200. package/dist/runtime/utils/message-repository.js +3 -4
  201. package/dist/runtime/utils/message-repository.js.map +1 -1
  202. package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
  203. package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
  204. package/dist/runtime/utils/pending-tool-calls.js +22 -0
  205. package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
  206. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +6 -3
  207. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
  208. package/dist/runtime/utils/resolveToolApprovalResponse.js +12 -3
  209. package/dist/runtime/utils/resolveToolApprovalResponse.js.map +1 -1
  210. package/dist/runtime/utils/thread-message-like.d.ts +3 -10
  211. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  212. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  213. package/dist/runtime/utils/tool-call-tree.d.ts +39 -0
  214. package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -0
  215. package/dist/runtime/utils/tool-call-tree.js +85 -0
  216. package/dist/runtime/utils/tool-call-tree.js.map +1 -0
  217. package/dist/runtimes/external-store/external-store-adapter.d.ts +29 -2
  218. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  219. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
  220. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  221. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +3 -10
  222. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  223. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +21 -2
  224. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  225. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +154 -46
  226. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  227. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +1 -1
  228. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
  229. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
  230. package/dist/runtimes/local/local-thread-runtime-core.d.ts +4 -2
  231. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  232. package/dist/runtimes/local/local-thread-runtime-core.js +23 -3
  233. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  234. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
  235. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  236. package/dist/runtimes/remote-thread-list/remote-thread-state.js +107 -14
  237. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  238. package/dist/runtimes/remote-thread-list/title.d.ts +9 -0
  239. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -0
  240. package/dist/runtimes/remote-thread-list/title.js +6 -0
  241. package/dist/runtimes/remote-thread-list/title.js.map +1 -0
  242. package/dist/runtimes/remote-thread-list/types.d.ts +6 -5
  243. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  244. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +31 -8
  245. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  246. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +98 -59
  247. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  248. package/dist/store/clients/external-thread.d.ts +1 -1
  249. package/dist/store/clients/external-thread.d.ts.map +1 -1
  250. package/dist/store/clients/external-thread.js +85 -103
  251. package/dist/store/clients/external-thread.js.map +1 -1
  252. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  253. package/dist/store/clients/single-thread-list.js +2 -2
  254. package/dist/store/clients/single-thread-list.js.map +1 -1
  255. package/dist/store/clients/suggestions.d.ts.map +1 -1
  256. package/dist/store/clients/suggestions.js +33 -12
  257. package/dist/store/clients/suggestions.js.map +1 -1
  258. package/dist/store/clients/thread-message-client.js +1 -1
  259. package/dist/store/clients/thread-selection-events.js +1 -1
  260. package/dist/store/index.d.ts +6 -3
  261. package/dist/store/index.js +2 -1
  262. package/dist/store/internal.d.ts +2 -2
  263. package/dist/store/internal.js +2 -2
  264. package/dist/store/primitive-predicates.d.ts +4 -1
  265. package/dist/store/primitive-predicates.d.ts.map +1 -1
  266. package/dist/store/primitive-predicates.js +9 -1
  267. package/dist/store/primitive-predicates.js.map +1 -1
  268. package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
  269. package/dist/store/runtime-clients/message-runtime-client.js +1 -1
  270. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  271. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  272. package/dist/store/runtime-clients/thread-list-runtime-client.js +124 -105
  273. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  274. package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
  275. package/dist/store/runtime-clients/useSubscribable.d.ts +3 -1
  276. package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
  277. package/dist/store/runtime-clients/useSubscribable.js +1 -1
  278. package/dist/store/runtime-clients/useSubscribable.js.map +1 -1
  279. package/dist/store/scope-registration.d.ts +1 -1
  280. package/dist/store/scopes/composer.d.ts +2 -1
  281. package/dist/store/scopes/composer.d.ts.map +1 -1
  282. package/dist/store/scopes/message.d.ts +1 -1
  283. package/dist/store/scopes/part.d.ts +6 -3
  284. package/dist/store/scopes/part.d.ts.map +1 -1
  285. package/dist/store/scopes/queue-item.d.ts +2 -9
  286. package/dist/store/scopes/queue-item.d.ts.map +1 -1
  287. package/dist/store/scopes/queue-item.js +0 -6
  288. package/dist/subscribable/subscribable.d.ts +8 -1
  289. package/dist/subscribable/subscribable.d.ts.map +1 -1
  290. package/dist/subscribable/subscribable.js +24 -16
  291. package/dist/subscribable/subscribable.js.map +1 -1
  292. package/dist/types/message.d.ts +47 -4
  293. package/dist/types/message.d.ts.map +1 -1
  294. package/dist/types/message.js +7 -1
  295. package/dist/types/message.js.map +1 -1
  296. package/dist/utils/data-url.d.ts +14 -1
  297. package/dist/utils/data-url.d.ts.map +1 -1
  298. package/dist/utils/data-url.js +13 -1
  299. package/dist/utils/data-url.js.map +1 -1
  300. package/dist/utils/invoke-user-callback.d.ts +5 -0
  301. package/dist/utils/invoke-user-callback.d.ts.map +1 -0
  302. package/dist/utils/invoke-user-callback.js +19 -0
  303. package/dist/utils/invoke-user-callback.js.map +1 -0
  304. package/dist/utils/normalizePartStatus.js.map +1 -1
  305. package/package.json +12 -17
  306. package/src/index.ts +6 -1
  307. package/src/internal.ts +20 -0
  308. package/src/model-context/frame/host.test.ts +91 -7
  309. package/src/model-context/frame/host.ts +7 -2
  310. package/src/model-context/frame/provider.test.ts +484 -13
  311. package/src/model-context/frame/provider.ts +205 -35
  312. package/src/model-context/interactable-composer-metadata.ts +1 -1
  313. package/src/model-context/registry.test.ts +44 -0
  314. package/src/model-context/registry.ts +13 -35
  315. package/src/model-context/tool-call-text.test.ts +67 -0
  316. package/src/model-context/tool-call-text.ts +67 -0
  317. package/src/react/AssistantRuntimeProvider.test.tsx +66 -2
  318. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +1 -3
  319. package/src/react/client/InMemoryThreadList.test.tsx +49 -0
  320. package/src/react/client/InMemoryThreadList.ts +7 -1
  321. package/src/react/client/Interactables.ts +19 -178
  322. package/src/react/client/RemoteThreadList.background.test.ts +384 -0
  323. package/src/react/client/RemoteThreadList.concurrent.test.tsx +274 -0
  324. package/src/react/client/RemoteThreadList.test.ts +369 -1
  325. package/src/react/client/RemoteThreadList.ts +320 -142
  326. package/src/react/client/Tools.ts +7 -1
  327. package/src/react/index.ts +4 -0
  328. package/src/react/interactables-legacy/Interactables.ts +20 -177
  329. package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
  330. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
  331. package/src/react/model-context/modelContextHooks.test.tsx +131 -0
  332. package/src/react/model-context/toolbox.test.ts +9 -9
  333. package/src/react/model-context/toolbox.ts +9 -36
  334. package/src/react/model-context/useAssistantContext.ts +4 -2
  335. package/src/react/model-context/useAuiToolOverrides.ts +4 -2
  336. package/src/react/model-context/useInlineRender.test.tsx +48 -0
  337. package/src/react/model-context/useInlineRender.ts +14 -10
  338. package/src/react/primitive-hooks/useMessageError.ts +2 -19
  339. package/src/react/primitive-hooks/useThreadListLoadMore.ts +2 -3
  340. package/src/react/primitives/composer/ComposerAttachments.test.tsx +76 -0
  341. package/src/react/primitives/composer/ComposerAttachments.tsx +9 -4
  342. package/src/react/primitives/composer/ComposerQueue.test.tsx +72 -0
  343. package/src/react/primitives/composer/ComposerQueue.tsx +7 -4
  344. package/src/react/primitives/message/MessageAttachments.test.tsx +107 -37
  345. package/src/react/primitives/message/MessageAttachments.tsx +10 -7
  346. package/src/react/primitives/message/MessageGroupedParts.tsx +2 -2
  347. package/src/react/primitives/message/MessageParts.tsx +3 -3
  348. package/src/react/primitives/threadList/ThreadListItems.test.tsx +99 -0
  349. package/src/react/primitives/threadList/ThreadListItems.tsx +8 -5
  350. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +94 -1
  351. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +72 -31
  352. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +169 -0
  353. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +140 -124
  354. package/src/react/runtimes/RemoteThreadResource.ts +1 -4
  355. package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
  356. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +51 -2
  357. package/src/react/runtimes/assistant-transport/useLatestRef.ts +2 -2
  358. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +45 -0
  359. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +157 -1
  360. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +82 -176
  361. package/src/react/runtimes/cloud/auiV0.ts +5 -0
  362. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +88 -0
  363. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +236 -0
  364. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
  365. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
  366. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +90 -1
  367. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +33 -238
  368. package/src/react/runtimes/external-message-converter.test.ts +0 -14
  369. package/src/react/runtimes/external-message-converter.test.tsx +77 -0
  370. package/src/react/runtimes/external-message-converter.ts +59 -481
  371. package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +216 -0
  372. package/src/react/runtimes/useRemoteThreadListRuntime.ts +24 -6
  373. package/src/react/types/MessagePartComponentTypes.ts +7 -3
  374. package/src/react/types/scopes/tools.ts +2 -0
  375. package/src/runtime/api/composer-runtime.ts +1 -1
  376. package/src/runtime/api/message-part-runtime.ts +3 -3
  377. package/src/runtime/api/message-runtime.test.ts +28 -1
  378. package/src/runtime/api/thread-list-runtime.ts +25 -1
  379. package/src/runtime/base/base-composer-runtime-core.ts +20 -40
  380. package/src/runtime/base/base-thread-runtime-core.test.ts +110 -3
  381. package/src/runtime/base/base-thread-runtime-core.ts +16 -15
  382. package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
  383. package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
  384. package/src/runtime/interfaces/thread-list-runtime-core.ts +20 -1
  385. package/src/runtime/interfaces/thread-runtime-core.ts +18 -2
  386. package/src/runtime/internal.ts +5 -0
  387. package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
  388. package/src/runtime/queue/message-queue.ts +1 -4
  389. package/src/runtime/queue/queue-item.ts +10 -0
  390. package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
  391. package/src/runtime/utils/abortable-thread-load.ts +53 -0
  392. package/src/runtime/utils/attachment-add-operations.ts +59 -0
  393. package/src/runtime/utils/auto-status.test.ts +57 -0
  394. package/src/runtime/utils/auto-status.ts +46 -1
  395. package/src/runtime/utils/external-message-conversion.test.ts +210 -0
  396. package/src/runtime/utils/external-message-conversion.ts +510 -0
  397. package/src/runtime/utils/external-store-message.ts +2 -0
  398. package/src/runtime/utils/message-repository-session.test.ts +195 -0
  399. package/src/runtime/utils/message-repository-session.ts +176 -0
  400. package/src/runtime/utils/message-repository.ts +7 -4
  401. package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
  402. package/src/runtime/utils/pending-tool-calls.ts +35 -0
  403. package/src/runtime/utils/resolveToolApprovalResponse.test.ts +73 -0
  404. package/src/runtime/utils/resolveToolApprovalResponse.ts +27 -6
  405. package/src/runtime/utils/thread-message-like.ts +4 -10
  406. package/src/runtime/utils/tool-call-tree.test.ts +191 -0
  407. package/src/runtime/utils/tool-call-tree.ts +97 -0
  408. package/src/runtimes/external-store/external-store-adapter.ts +38 -2
  409. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
  410. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +231 -61
  411. package/src/runtimes/inert/InertThreadRuntimeCore.ts +1 -1
  412. package/src/runtimes/local/local-thread-runtime-core.test.ts +201 -4
  413. package/src/runtimes/local/local-thread-runtime-core.ts +53 -4
  414. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +149 -0
  415. package/src/runtimes/remote-thread-list/remote-thread-state.ts +178 -16
  416. package/src/runtimes/remote-thread-list/title.ts +3 -0
  417. package/src/runtimes/remote-thread-list/types.ts +6 -5
  418. package/src/runtimes/tool-invocations/EDGE_CASES.md +125 -3
  419. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +856 -102
  420. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +145 -89
  421. package/src/store/clients/external-thread.ts +30 -69
  422. package/src/store/clients/single-thread-list.ts +4 -1
  423. package/src/store/clients/suggestions.test.ts +185 -0
  424. package/src/store/clients/suggestions.ts +42 -35
  425. package/src/store/index.ts +4 -0
  426. package/src/store/internal.ts +2 -0
  427. package/src/store/primitive-predicates.ts +35 -0
  428. package/src/store/runtime-clients/thread-list-runtime-client.ts +16 -2
  429. package/src/store/runtime-clients/useSubscribable.ts +8 -2
  430. package/src/store/scopes/message.ts +1 -1
  431. package/src/store/scopes/part.ts +6 -3
  432. package/src/store/scopes/queue-item.ts +2 -9
  433. package/src/subscribable/subscribable.test.ts +50 -0
  434. package/src/subscribable/subscribable.ts +33 -21
  435. package/src/tests/MessageRepository.test.ts +77 -0
  436. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +305 -0
  437. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +78 -0
  438. package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +52 -0
  439. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
  440. package/src/tests/external-store-optimistic-id.test.ts +79 -0
  441. package/src/tests/external-store-repository-instance.test.ts +166 -0
  442. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +186 -16
  443. package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
  444. package/src/tests/external-thread-attachments.test.tsx +258 -1
  445. package/src/tests/remote-thread-list-adapter-switch.test.ts +66 -1
  446. package/src/tests/single-thread-list-item.test.tsx +47 -0
  447. package/src/tests/thread-message-like.test.ts +39 -1
  448. package/src/tests/thread-switch-events.test.tsx +397 -2
  449. package/src/types/message.ts +58 -5
  450. package/src/utils/data-url.test.ts +52 -1
  451. package/src/utils/data-url.ts +21 -0
  452. package/src/utils/invoke-user-callback.test.ts +82 -0
  453. package/src/utils/invoke-user-callback.ts +31 -0
  454. package/src/utils/normalizePartStatus.ts +1 -1
  455. package/dist/store/scopes/queue-item.js.map +0 -1
@@ -2,27 +2,35 @@ import {
2
2
  useCallback,
3
3
  useEffect,
4
4
  useMemo,
5
+ useRef,
5
6
  useState,
6
7
  useSyncExternalStore,
7
8
  } from "react";
8
- import { resource, withKey } from "@assistant-ui/tap";
9
+ import { resource, withKey, type ResourceElement } from "@assistant-ui/tap";
9
10
  import type { ClientOutput } from "@assistant-ui/store";
10
11
  import {
11
12
  attachTransformScopes,
13
+ Derived,
14
+ useAssistantContextProvider,
15
+ useAssistantContextValue,
12
16
  useClientLookup,
13
17
  useClientResource,
18
+ useConfiguredAui,
14
19
  } from "@assistant-ui/store/client";
15
20
  import { isDevelopment, useThreadSelectionEvents } from "../../store/internal";
16
- import { generateId } from "../../utils/id";
17
21
  import { OptimisticState } from "../../runtimes/remote-thread-list/optimistic-state";
18
22
  import {
19
23
  classifyThreads,
24
+ createEmptyRemoteThreadState,
20
25
  createThreadMappingId,
21
26
  getThreadData,
22
27
  normalizeCursor,
23
28
  updateStatusReducer,
24
29
  type RemoteThreadData,
25
30
  type RemoteThreadState,
31
+ preserveMidLoadTransitions,
32
+ seedNewThread,
33
+ statusSnapshot,
26
34
  } from "../../runtimes/remote-thread-list/remote-thread-state";
27
35
  import type {
28
36
  RemoteThreadInitializeResponse,
@@ -37,19 +45,11 @@ import {
37
45
  type InMemoryThreadListProps,
38
46
  } from "./InMemoryThreadList";
39
47
  import { AdaptedRemoteThread } from "./AdaptedRemoteThread";
48
+ import { isTitleSourceMessage } from "../../runtimes/remote-thread-list/title";
40
49
 
41
50
  const RESOLVED_PROMISE = Promise.resolve();
42
51
 
43
- const EMPTY_LIST: RemoteThreadState = {
44
- isLoading: true,
45
- isLoadingMore: false,
46
- cursor: undefined,
47
- newThreadId: undefined,
48
- threadIds: [],
49
- archivedThreadIds: [],
50
- threadIdMap: {},
51
- threadData: {},
52
- };
52
+ const EMPTY_LIST = createEmptyRemoteThreadState();
53
53
 
54
54
  export type RemoteThreadListProps = {
55
55
  /**
@@ -68,6 +68,18 @@ export type RemoteThreadListProps = {
68
68
  onSwitchToThread?: ((threadId: string) => void) | undefined;
69
69
  onSwitchToNewThread?: (() => void) | undefined;
70
70
  onDelete?: ((threadId: string) => void) | undefined;
71
+ /**
72
+ * Keeps every thread the session switched to mounted, so a run continues
73
+ * after the user switches away, per-item `isRunning` reflects background
74
+ * runs, and a freshly initialized thread generates its title automatically.
75
+ * A body unmounts on delete, detach, or an adapter replacement (`reload()`
76
+ * after swapping the adapter); visited threads otherwise stay mounted for
77
+ * the client's lifetime. Each body reads its own thread through the ambient
78
+ * `threadListItem` scope, and `unstable_useAdapters` runs once per mounted
79
+ * body. Off (the default), only the visible thread is mounted and a switch
80
+ * unmounts it. The mode is fixed at mount.
81
+ */
82
+ backgroundThreads?: boolean | undefined;
71
83
  };
72
84
 
73
85
  const threadNotFoundError = (threadIdOrRemoteId: string, action: string) =>
@@ -91,46 +103,14 @@ const toInitializeResult = (
91
103
 
92
104
  const applyTitleStream = async (
93
105
  stream: Parameters<typeof AssistantMessageStream.fromAssistantStream>[0],
94
- onTitle: (title: string | undefined) => void,
106
+ onTitle: (title: string | undefined) => Promise<void>,
95
107
  ) => {
96
108
  const messageStream = AssistantMessageStream.fromAssistantStream(stream);
97
109
  for await (const result of messageStream) {
98
- onTitle(result.parts.filter((part) => part.type === "text")[0]?.text);
110
+ await onTitle(result.parts.filter((part) => part.type === "text")[0]?.text);
99
111
  }
100
112
  };
101
113
 
102
- const seedNewThread = (
103
- state: RemoteThreadState,
104
- ): { id: string; state: RemoteThreadState } => {
105
- let id: string;
106
- do {
107
- id = `__LOCALID_${generateId()}`;
108
- } while (state.threadIdMap[id]);
109
- const mappingId = createThreadMappingId(id);
110
- return {
111
- id,
112
- state: {
113
- ...state,
114
- newThreadId: id,
115
- threadIdMap: {
116
- ...state.threadIdMap,
117
- [id]: mappingId,
118
- },
119
- threadData: {
120
- ...state.threadData,
121
- [mappingId]: {
122
- status: "new",
123
- id,
124
- remoteId: undefined,
125
- externalId: undefined,
126
- title: undefined,
127
- custom: undefined,
128
- },
129
- },
130
- },
131
- };
132
- };
133
-
134
114
  const useThreadListItemClient = (props: {
135
115
  data: RemoteThreadData;
136
116
  isRunning: boolean;
@@ -240,9 +220,96 @@ const isSameThread = (
240
220
  return data !== undefined && itemMatchesId(data, listState, right);
241
221
  };
242
222
 
223
+ const useRemoteThreadBody = ({
224
+ id,
225
+ status,
226
+ remoteId,
227
+ item,
228
+ thread,
229
+ }: {
230
+ id: string;
231
+ status: RemoteThreadData["status"];
232
+ remoteId: string | undefined;
233
+ item: (isRunning: boolean) => ResourceElement<ClientOutput<"threadListItem">>;
234
+ thread: ResourceElement<ClientOutput<"thread">>;
235
+ }): ClientOutput<"thread"> => {
236
+ const parent = useAssistantContextValue();
237
+ const [isRunning, setIsRunning] = useState(false);
238
+ // The item client is mounted inside the body and served to the subtree
239
+ // through a Derived scope, which keeps the graft on the derived-only (no
240
+ // nested client host) path; resolving `parent.threads` here instead would
241
+ // read the threads scope during its own construction.
242
+ const itemHandle = useClientResource(item(isRunning));
243
+ const { client } = useConfiguredAui(parent, {
244
+ threadListItem: Derived({
245
+ source: "threads",
246
+ query: { type: "id", id },
247
+ get: () => itemHandle.methods,
248
+ }),
249
+ });
250
+ // Auto-title arms only for a thread born "new" in this body's lifetime;
251
+ // threads loaded or fetched from the adapter already carry their title. The
252
+ // settled (non-optimistic) initialize is what writes remoteId, so its
253
+ // arrival is the initialization signal.
254
+ const bornNewRef = useRef(status === "new");
255
+ const titleFiredRef = useRef(false);
256
+ return useAssistantContextProvider(client, function useBoundRemoteBody() {
257
+ const body = useClientResource(thread);
258
+ const bodyRunning = body.state.isRunning === true;
259
+ useEffect(() => {
260
+ setIsRunning(bodyRunning);
261
+ }, [bodyRunning]);
262
+ const armed =
263
+ bornNewRef.current && !titleFiredRef.current && remoteId !== undefined;
264
+ const hasTitleSource =
265
+ armed &&
266
+ (
267
+ body.state.messages as readonly {
268
+ status?: { type: string } | undefined;
269
+ }[]
270
+ ).some(isTitleSourceMessage);
271
+ useEffect(() => {
272
+ if (!hasTitleSource || titleFiredRef.current) return;
273
+ titleFiredRef.current = true;
274
+ client.threadListItem.generateTitle();
275
+ }, [hasTitleSource]);
276
+ return body.methods;
277
+ });
278
+ };
279
+
280
+ const RemoteThreadBody = resource(useRemoteThreadBody);
281
+
282
+ // `thread("main")` keeps one identity across switches, like the single
283
+ // useClientResource slot it replaced: consumers (the ambient `thread` scope,
284
+ // captured clients) hold the facade while it delegates to whichever body is
285
+ // currently main. Publishing the target during commit keeps abandoned
286
+ // concurrent renders from changing the facade observed by committed consumers.
287
+ const useMainThreadFacade = (
288
+ current: ClientOutput<"thread">,
289
+ ): ClientOutput<"thread"> => {
290
+ const currentRef = useRef(current);
291
+ useEffect(() => {
292
+ currentRef.current = current;
293
+ }, [current]);
294
+ const [facade] = useState(
295
+ () =>
296
+ new Proxy({} as ClientOutput<"thread">, {
297
+ get: (_, prop) =>
298
+ (currentRef.current as unknown as Record<PropertyKey, unknown>)[prop],
299
+ has: (_, prop) => prop in (currentRef.current as object),
300
+ ownKeys: () => Reflect.ownKeys(currentRef.current as object),
301
+ getOwnPropertyDescriptor: (_, prop) =>
302
+ Reflect.getOwnPropertyDescriptor(currentRef.current as object, prop),
303
+ }),
304
+ );
305
+ return facade;
306
+ };
307
+
243
308
  const useRemoteThreadListView = ({
244
309
  listState,
245
310
  mainThreadId,
311
+ startedIds,
312
+ backgroundThreads,
246
313
  threadFactory,
247
314
  useAdapters,
248
315
  onSwitchTo,
@@ -257,6 +324,8 @@ const useRemoteThreadListView = ({
257
324
  }: {
258
325
  listState: RemoteThreadState;
259
326
  mainThreadId: string;
327
+ startedIds: readonly string[];
328
+ backgroundThreads: boolean;
260
329
  threadFactory: RemoteThreadListProps["thread"];
261
330
  useAdapters: RemoteThreadListAdapter["unstable_useAdapters"];
262
331
  onSwitchTo: (
@@ -281,17 +350,102 @@ const useRemoteThreadListView = ({
281
350
  }>;
282
351
  onDetach: (threadId: string) => Promise<void>;
283
352
  }) => {
284
- const thread = threadFactory(mainThreadId);
285
- const wrapped =
286
- useAdapters === undefined
287
- ? thread
288
- : AdaptedRemoteThread({
289
- useAdapters,
290
- thread,
291
- });
292
- const mainThreadClient = useClientResource(
293
- thread.key === undefined ? wrapped : withKey(thread.key, wrapped),
353
+ const bodyIds = useMemo(() => {
354
+ if (!backgroundThreads) return [mainThreadId];
355
+ const seen = new Set<string>();
356
+ const seenRemoteIds = new Set<string>();
357
+ const ids: string[] = [];
358
+ for (const id of startedIds) {
359
+ const data = getThreadData(listState, id);
360
+ if (!data || seen.has(data.id)) continue;
361
+ if (data.remoteId !== undefined && seenRemoteIds.has(data.remoteId)) {
362
+ continue;
363
+ }
364
+ seen.add(data.id);
365
+ if (data.remoteId !== undefined) seenRemoteIds.add(data.remoteId);
366
+ ids.push(data.id);
367
+ }
368
+ return ids;
369
+ }, [backgroundThreads, listState, mainThreadId, startedIds]);
370
+
371
+ const itemElementFor = (data: RemoteThreadData, isRunning: boolean) =>
372
+ ThreadListItemClient({
373
+ data,
374
+ isRunning,
375
+ onSwitchTo: (options) =>
376
+ handleThreadListAction("switch", () => onSwitchTo(data.id, options)),
377
+ onRename: (title) =>
378
+ handleThreadListAction("rename", () => onRename(data.id, title)),
379
+ onUpdateCustom: (custom) =>
380
+ handleThreadListAction("update custom metadata", () =>
381
+ onUpdateCustom(data.id, custom),
382
+ ),
383
+ onArchive: () =>
384
+ handleThreadListAction("archive", () => onArchive(data.id)),
385
+ onUnarchive: () =>
386
+ handleThreadListAction("unarchive", () => onUnarchive(data.id)),
387
+ onDelete: () => handleThreadListAction("delete", () => onDelete(data.id)),
388
+ onGenerateTitle: () =>
389
+ handleThreadListAction("generate title", () =>
390
+ onGenerateTitle(
391
+ data.id,
392
+ (backgroundThreads
393
+ ? bodyStateOf(data.id)?.messages
394
+ : mainThreadClient.state.messages) as
395
+ | readonly ThreadMessage[]
396
+ | undefined,
397
+ ),
398
+ ),
399
+ onInitialize: () => onInitialize(data.id),
400
+ onDetach: () => handleThreadListAction("detach", () => onDetach(data.id)),
401
+ });
402
+
403
+ const bodies = useClientLookup(
404
+ bodyIds.map((id) => {
405
+ const made = threadFactory(id);
406
+ // Background bodies never change occupant, so an unkeyed factory
407
+ // element is keyed by the mapping id here; per-body history loads
408
+ // without requiring the factory to key itself.
409
+ const thread =
410
+ backgroundThreads && made.key === undefined ? withKey(id, made) : made;
411
+ const wrapped =
412
+ useAdapters === undefined
413
+ ? thread
414
+ : AdaptedRemoteThread({
415
+ useAdapters,
416
+ thread,
417
+ });
418
+ const data = getThreadData(listState, id);
419
+ const element =
420
+ backgroundThreads && data !== undefined
421
+ ? RemoteThreadBody({
422
+ id,
423
+ status: data.status,
424
+ remoteId: data.remoteId,
425
+ item: (isRunning) => itemElementFor(data, isRunning),
426
+ thread: wrapped,
427
+ })
428
+ : wrapped;
429
+ return withKey(backgroundThreads ? id : (made.key ?? "main"), element);
430
+ }),
294
431
  );
432
+ // A reload can remap a local thread id to its remote identity while the
433
+ // body stays keyed by the original mapping id, so lookups fall back to
434
+ // remote-identity matching.
435
+ const bodyIndexOf = (id: string) => {
436
+ const direct = bodyIds.indexOf(id);
437
+ if (direct !== -1) return direct;
438
+ return bodyIds.findIndex((bodyId) => isSameThread(listState, bodyId, id));
439
+ };
440
+ const bodyStateOf = (id: string) => {
441
+ const index = bodyIndexOf(id);
442
+ return index === -1 ? undefined : bodies.state[index];
443
+ };
444
+ const mainIndex = bodyIndexOf(mainThreadId);
445
+ const mainThreadClient = {
446
+ state: bodies.state[mainIndex]!,
447
+ methods: useMainThreadFacade(bodies.get({ index: mainIndex })),
448
+ };
295
449
  const itemOrder = useMemo(
296
450
  () => collectItemOrder(listState, mainThreadId),
297
451
  [listState, mainThreadId],
@@ -300,40 +454,13 @@ const useRemoteThreadListView = ({
300
454
  itemOrder.map((data) =>
301
455
  withKey(
302
456
  data.id,
303
- ThreadListItemClient({
457
+ itemElementFor(
304
458
  data,
305
- isRunning:
306
- itemMatchesId(data, listState, mainThreadId) &&
307
- mainThreadClient.state.isRunning,
308
- onSwitchTo: (options) =>
309
- handleThreadListAction("switch", () =>
310
- onSwitchTo(data.id, options),
311
- ),
312
- onRename: (title) =>
313
- handleThreadListAction("rename", () => onRename(data.id, title)),
314
- onUpdateCustom: (custom) =>
315
- handleThreadListAction("update custom metadata", () =>
316
- onUpdateCustom(data.id, custom),
317
- ),
318
- onArchive: () =>
319
- handleThreadListAction("archive", () => onArchive(data.id)),
320
- onUnarchive: () =>
321
- handleThreadListAction("unarchive", () => onUnarchive(data.id)),
322
- onDelete: () =>
323
- handleThreadListAction("delete", () => onDelete(data.id)),
324
- onGenerateTitle: () =>
325
- handleThreadListAction("generate title", () =>
326
- onGenerateTitle(
327
- data.id,
328
- mainThreadClient.state.messages as
329
- | readonly ThreadMessage[]
330
- | undefined,
331
- ),
332
- ),
333
- onInitialize: () => onInitialize(data.id),
334
- onDetach: () =>
335
- handleThreadListAction("detach", () => onDetach(data.id)),
336
- }),
459
+ backgroundThreads
460
+ ? (bodyStateOf(data.id)?.isRunning ?? false)
461
+ : itemMatchesId(data, listState, mainThreadId) &&
462
+ mainThreadClient.state.isRunning,
463
+ ),
337
464
  ),
338
465
  ),
339
466
  );
@@ -378,9 +505,6 @@ const useRemoteThreadList = (
378
505
  };
379
506
  });
380
507
 
381
- // Config updates can invoke `reload()` in the same turn as the re-render, before the effect below runs.
382
- session.adapter = adapter;
383
-
384
508
  const listState = useSyncExternalStore(
385
509
  (onStoreChange) => store.subscribe(onStoreChange),
386
510
  () => store.value,
@@ -388,15 +512,21 @@ const useRemoteThreadList = (
388
512
  );
389
513
 
390
514
  const [mainThreadId, setMainThreadId] = useState(initialMainId);
515
+ const [startedIds, setStartedIds] = useState<readonly string[]>([
516
+ initialMainId,
517
+ ]);
518
+ const [backgroundThreads] = useState(props.backgroundThreads === true);
391
519
  const assignMainThreadId = useCallback(
392
520
  (id: string) => {
393
521
  session.mainThreadId = id;
394
522
  setMainThreadId(id);
523
+ setStartedIds((prev) => (prev.includes(id) ? prev : [...prev, id]));
395
524
  },
396
525
  [session],
397
526
  );
398
527
  useThreadSelectionEvents(mainThreadId);
399
528
  useEffect(() => {
529
+ // Publishing after commit keeps imperative actions on the adapter the committed tree renders with; an abandoned render must not reach them.
400
530
  session.adapter = adapter;
401
531
  session.mainThreadId = mainThreadId;
402
532
  session.onThreadIdChange = onThreadIdChange;
@@ -424,34 +554,33 @@ const useRemoteThreadList = (
424
554
  if (session.loadPromise) return session.loadPromise;
425
555
  const generation = session.loadGeneration;
426
556
  const adapter = session.adapter;
557
+ const statusAtRequest = statusSnapshot(store.baseValue);
427
558
  session.loadPromise = store
428
559
  .optimisticUpdate({
429
560
  execute: () => adapter.list(),
430
- loading: (state) => ({ ...state, isLoading: true }),
561
+ loading: (state) => {
562
+ if (generation !== session.loadGeneration) return state;
563
+ return { ...state, isLoading: true };
564
+ },
431
565
  then: (state, page) => {
432
566
  if (generation !== session.loadGeneration) return state;
433
567
  session.adapterAtLoad = adapter;
434
568
  const fresh = classifyThreads(page.threads, {
435
569
  threadIds: [],
436
570
  archivedThreadIds: [],
437
- threadIdMap: {},
438
- threadData: {},
571
+ threadIdMap: { ...state.threadIdMap },
572
+ threadData: { ...state.threadData },
439
573
  });
440
- return {
574
+ const merged = {
441
575
  ...state,
442
576
  isLoading: false,
443
577
  cursor: normalizeCursor(page.nextCursor),
444
578
  threadIds: fresh.threadIds,
445
579
  archivedThreadIds: fresh.archivedThreadIds,
446
- threadIdMap: {
447
- ...state.threadIdMap,
448
- ...fresh.threadIdMap,
449
- },
450
- threadData: {
451
- ...state.threadData,
452
- ...fresh.threadData,
453
- },
580
+ threadIdMap: fresh.threadIdMap,
581
+ threadData: fresh.threadData,
454
582
  };
583
+ return preserveMidLoadTransitions(merged, state, statusAtRequest);
455
584
  },
456
585
  })
457
586
  .catch((error: unknown) => {
@@ -480,6 +609,7 @@ const useRemoteThreadList = (
480
609
  const seeded = seedNewThread(EMPTY_LIST);
481
610
  store.reset({ ...seeded.state, isLoading: true });
482
611
  assignMainThreadId(seeded.id);
612
+ setStartedIds([seeded.id]);
483
613
  notifyRemoteId(undefined, true);
484
614
  } else {
485
615
  store.update({
@@ -522,7 +652,10 @@ const useRemoteThreadList = (
522
652
  const task = store
523
653
  .optimisticUpdate({
524
654
  execute: () => currentAdapter.list({ after: cursor }),
525
- loading: (state) => ({ ...state, isLoadingMore: true }),
655
+ loading: (state) => {
656
+ if (generation !== session.loadGeneration) return state;
657
+ return { ...state, isLoadingMore: true };
658
+ },
526
659
  then: (state, page) => {
527
660
  if (generation !== session.loadGeneration) return state;
528
661
  const appended = classifyThreads(page.threads, {
@@ -640,22 +773,30 @@ const useRemoteThreadList = (
640
773
  throw threadNotFoundError(threadIdOrRemoteId, "switching to it");
641
774
  }
642
775
  if (isSameThread(store.value, data.id, session.mainThreadId)) return;
643
- if (data.status === "archived" && options?.unarchive !== false) {
644
- const current = data;
776
+
777
+ const targetId = data.id;
778
+ let current: RemoteThreadData | undefined = data;
779
+
780
+ if (current.status === "archived" && options?.unarchive !== false) {
645
781
  const { remoteId } = await current.initializeTask;
646
782
  if (generation !== session.switchGeneration) return;
647
- await store.optimisticUpdate({
648
- execute: () => session.adapter.unarchive(remoteId),
649
- optimistic: (state) =>
650
- updateStatusReducer(state, current.id, "regular"),
651
- });
652
- if (generation !== session.switchGeneration) return;
653
- data = getThreadData(store.value, current.id) ?? current;
783
+ current = getThreadData(store.value, targetId);
784
+ if (current?.id !== targetId) return;
785
+ if (current.status === "archived") {
786
+ await store.optimisticUpdate({
787
+ execute: () => session.adapter.unarchive(remoteId),
788
+ optimistic: (state) =>
789
+ updateStatusReducer(state, targetId, "regular"),
790
+ });
791
+ if (generation !== session.switchGeneration) return;
792
+ current = getThreadData(store.value, targetId);
793
+ if (current?.id !== targetId) return;
794
+ }
654
795
  }
655
796
  if (generation !== session.switchGeneration) return;
656
- assignMainThreadId(data.id);
657
- notifyRemoteId(data.remoteId, emitThreadIdChange);
658
- session.onSwitchToThread?.(data.id);
797
+ assignMainThreadId(current.id);
798
+ notifyRemoteId(current.remoteId, emitThreadIdChange);
799
+ session.onSwitchToThread?.(current.id);
659
800
  }),
660
801
  [assignMainThreadId, notifyRemoteId, session, startSwitch, store],
661
802
  );
@@ -764,21 +905,36 @@ const useRemoteThreadList = (
764
905
  const data = getThreadData(state, threadId);
765
906
  if (!data) return state;
766
907
  const mappingId = createThreadMappingId(threadId);
908
+ // A list() response that landed while this initialize was in flight
909
+ // could not know the remote id yet, so it may have minted its own
910
+ // slot for it; that slot collapses into this one.
911
+ const listedMappingId = state.threadIdMap[remoteId];
912
+ const orphan =
913
+ listedMappingId !== undefined && listedMappingId !== mappingId
914
+ ? state.threadData[listedMappingId]
915
+ : undefined;
916
+
917
+ const threadData = { ...state.threadData };
918
+ if (orphan !== undefined) delete threadData[listedMappingId!];
919
+ threadData[mappingId] = {
920
+ ...data,
921
+ initializeTask: Promise.resolve({ remoteId, externalId }),
922
+ remoteId,
923
+ externalId,
924
+ } as RemoteThreadData;
925
+
926
+ const rewire = (ids: readonly string[]) =>
927
+ orphan === undefined ? ids : ids.filter((id) => id !== orphan.id);
928
+
767
929
  return {
768
930
  ...state,
931
+ threadIds: rewire(state.threadIds),
932
+ archivedThreadIds: rewire(state.archivedThreadIds),
769
933
  threadIdMap: {
770
934
  ...state.threadIdMap,
771
935
  [remoteId]: mappingId,
772
936
  },
773
- threadData: {
774
- ...state.threadData,
775
- [mappingId]: {
776
- ...data,
777
- initializeTask: Promise.resolve({ remoteId, externalId }),
778
- remoteId,
779
- externalId,
780
- },
781
- },
937
+ threadData,
782
938
  };
783
939
  },
784
940
  });
@@ -981,34 +1137,53 @@ const useRemoteThreadList = (
981
1137
  }
982
1138
  const { remoteId } = await data.initializeTask;
983
1139
  requireAdapterGeneration(adapterGeneration);
984
- if (!isSameThread(store.value, data.id, session.mainThreadId)) return;
1140
+ if (
1141
+ !backgroundThreads &&
1142
+ !isSameThread(store.value, data.id, session.mainThreadId)
1143
+ ) {
1144
+ return;
1145
+ }
985
1146
  if (!messages) return;
986
1147
  const stream = await currentAdapter.generateTitle(remoteId, messages);
987
1148
  requireAdapterGeneration(adapterGeneration);
988
- await applyTitleStream(stream, (newTitle) => {
989
- if (adapterGeneration !== session.adapterGeneration) return;
990
- const state = store.baseValue;
991
- const current = getThreadData(state, data.id);
992
- if (!current) return;
993
- store.update({
994
- ...state,
995
- threadData: {
996
- ...state.threadData,
997
- [current.id]: {
998
- ...current,
999
- title: newTitle,
1000
- },
1149
+ await applyTitleStream(stream, async (newTitle) => {
1150
+ await store.optimisticUpdate({
1151
+ execute: async () => {},
1152
+ optimistic: (state) => {
1153
+ if (adapterGeneration !== session.adapterGeneration) return state;
1154
+ const current = getThreadData(state, data.id);
1155
+ if (!current) return state;
1156
+ return {
1157
+ ...state,
1158
+ threadData: {
1159
+ ...state.threadData,
1160
+ [current.id]: {
1161
+ ...current,
1162
+ title: newTitle,
1163
+ },
1164
+ },
1165
+ };
1001
1166
  },
1002
1167
  });
1003
1168
  });
1004
1169
  },
1005
- [requireAdapterGeneration, session, store],
1170
+ [backgroundThreads, requireAdapterGeneration, session, store],
1171
+ );
1172
+
1173
+ const detach = useCallback(
1174
+ async (threadId: string) => {
1175
+ await ensureNotMain(threadId);
1176
+ setStartedIds((prev) => prev.filter((id) => id !== threadId));
1177
+ },
1178
+ [ensureNotMain],
1006
1179
  );
1007
1180
 
1008
1181
  const { mainThreadClient, itemOrder, threadListItems } =
1009
1182
  useRemoteThreadListView({
1010
1183
  listState,
1011
1184
  mainThreadId,
1185
+ startedIds,
1186
+ backgroundThreads,
1012
1187
  threadFactory,
1013
1188
  useAdapters: adapter.unstable_useAdapters,
1014
1189
  onSwitchTo: (id, options) => switchToThread(id, options),
@@ -1019,7 +1194,7 @@ const useRemoteThreadList = (
1019
1194
  onDelete: (id) => deleteThread(id),
1020
1195
  onGenerateTitle: (id, messages) => generateTitle(id, messages),
1021
1196
  onInitialize: async (id) => toInitializeResult(await initialize(id)),
1022
- onDetach: (id) => ensureNotMain(id),
1197
+ onDetach: (id) => detach(id),
1023
1198
  });
1024
1199
 
1025
1200
  const mainRemoteId = getThreadData(listState, mainThreadId)?.remoteId;
@@ -1130,6 +1305,9 @@ const useRemoteThreadList = (
1130
1305
  * and attachments come from `unstable_useAdapters`. `useRemoteThreadListRuntime`
1131
1306
  * uses the same hook when `unstable_Provider` is omitted. Key the factory
1132
1307
  * with `withKey` so history reloads when the visible thread changes.
1308
+ * With `backgroundThreads`, every visited thread stays mounted: runs continue
1309
+ * across switches, per-item `isRunning` is live, and freshly initialized
1310
+ * threads title themselves.
1133
1311
  */
1134
1312
  export const RemoteThreadList = resource(useRemoteThreadList);
1135
1313
 
@@ -16,6 +16,7 @@ import type { Tool } from "assistant-stream";
16
16
  import {
17
17
  isStandaloneToolDisplay,
18
18
  makeToolCallTextComponent,
19
+ type ToolCallText,
19
20
  type Toolkit,
20
21
  } from "../model-context/toolbox";
21
22
  import type { ToolCallMessagePartComponent } from "../types/MessagePartComponentTypes";
@@ -59,12 +60,16 @@ const useTools = ({
59
60
  (
60
61
  toolName: string,
61
62
  render: ToolCallMessagePartComponent,
62
- options?: { standalone?: boolean },
63
+ options?: {
64
+ standalone?: boolean;
65
+ renderText?: ToolCallText<any, any> | undefined;
66
+ },
63
67
  ) => {
64
68
  // One registration object per call; identity is the removal key, so
65
69
  // the per-name list stays correctly ref-counted across re-registers.
66
70
  const registration = {
67
71
  render,
72
+ renderText: options?.renderText,
68
73
  standalone: options?.standalone ?? false,
69
74
  };
70
75
 
@@ -105,6 +110,7 @@ const useTools = ({
105
110
  unsubscribes.push(
106
111
  setToolUI(toolName, render, {
107
112
  standalone: isStandaloneToolDisplay(tool),
113
+ renderText: toolRenderText,
108
114
  }),
109
115
  );
110
116
  }