@assistant-ui/core 0.3.16 → 0.3.18

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 (474) 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/index.d.ts +3 -3
  5. package/dist/index.js +2 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/internal.d.ts +4 -2
  8. package/dist/internal.js +4 -2
  9. package/dist/model-context/frame/host.d.ts.map +1 -1
  10. package/dist/model-context/frame/host.js +10 -4
  11. package/dist/model-context/frame/host.js.map +1 -1
  12. package/dist/model-context/frame/provider.d.ts +5 -0
  13. package/dist/model-context/frame/provider.d.ts.map +1 -1
  14. package/dist/model-context/frame/provider.js +101 -28
  15. package/dist/model-context/frame/provider.js.map +1 -1
  16. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  17. package/dist/model-context/interactable-composer-metadata.js +16 -9
  18. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  19. package/dist/model-context/registry.d.ts.map +1 -1
  20. package/dist/model-context/registry.js +3 -5
  21. package/dist/model-context/registry.js.map +1 -1
  22. package/dist/model-context/tool-call-text.d.ts +26 -0
  23. package/dist/model-context/tool-call-text.d.ts.map +1 -0
  24. package/dist/model-context/tool-call-text.js +18 -0
  25. package/dist/model-context/tool-call-text.js.map +1 -0
  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/RuntimeAdapter.js +1 -1
  30. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  31. package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -1
  32. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  33. package/dist/react/client/AdaptedRemoteThread.js +1 -1
  34. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  35. package/dist/react/client/InMemoryThreadList.js +63 -57
  36. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  37. package/dist/react/client/Interactables.d.ts.map +1 -1
  38. package/dist/react/client/Interactables.js +25 -28
  39. package/dist/react/client/Interactables.js.map +1 -1
  40. package/dist/react/client/RemoteThreadList.d.ts +16 -0
  41. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  42. package/dist/react/client/RemoteThreadList.js +354 -242
  43. package/dist/react/client/RemoteThreadList.js.map +1 -1
  44. package/dist/react/client/Tools.d.ts.map +1 -1
  45. package/dist/react/client/Tools.js +24 -18
  46. package/dist/react/client/Tools.js.map +1 -1
  47. package/dist/react/index.d.ts +2 -1
  48. package/dist/react/index.js +2 -1
  49. package/dist/react/interactables-legacy/Interactables.js +246 -125
  50. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  51. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  52. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  53. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  54. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  55. package/dist/react/model-context/makeAssistantTool.js +1 -1
  56. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  57. package/dist/react/model-context/toolbox.d.ts +2 -14
  58. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  59. package/dist/react/model-context/toolbox.js +1 -13
  60. package/dist/react/model-context/toolbox.js.map +1 -1
  61. package/dist/react/model-context/useAssistantContext.js +37 -7
  62. package/dist/react/model-context/useAssistantContext.js.map +1 -1
  63. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  64. package/dist/react/model-context/useAssistantTool.js +1 -1
  65. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  66. package/dist/react/model-context/useAuiToolOverrides.js +31 -5
  67. package/dist/react/model-context/useAuiToolOverrides.js.map +1 -1
  68. package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
  69. package/dist/react/model-context/useInlineRender.js +10 -36
  70. package/dist/react/model-context/useInlineRender.js.map +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/primitive-hooks/useMessageError.d.ts.map +1 -1
  75. package/dist/react/primitive-hooks/useMessageError.js +2 -8
  76. package/dist/react/primitive-hooks/useMessageError.js.map +1 -1
  77. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
  78. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -4
  79. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  80. package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  81. package/dist/react/primitives/composer/ComposerAttachments.js +4 -3
  82. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  83. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  84. package/dist/react/primitives/composer/ComposerQueue.js +4 -3
  85. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  86. package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
  87. package/dist/react/primitives/message/MessageAttachments.js +7 -6
  88. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  89. package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
  90. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  91. package/dist/react/primitives/message/MessageParts.js +3 -3
  92. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  93. package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  94. package/dist/react/primitives/threadList/ThreadListItems.js +5 -4
  95. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  96. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +6 -4
  97. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  98. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +59 -29
  99. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  100. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +11 -6
  101. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  102. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +160 -143
  103. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  104. package/dist/react/runtimes/RemoteThreadResource.d.ts +2 -6
  105. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  106. package/dist/react/runtimes/RemoteThreadResource.js +15 -13
  107. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  108. package/dist/react/runtimes/assistant-transport/useLatestRef.js +2 -2
  109. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -1
  110. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +4 -1
  111. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  112. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +424 -52
  113. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  114. package/dist/react/runtimes/cloud/auiV0.d.ts +5 -1
  115. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  116. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  117. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +29 -0
  118. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -0
  119. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +160 -0
  120. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -0
  121. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +1 -9
  122. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +16 -153
  124. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  125. package/dist/react/runtimes/external-message-converter.js +1 -1
  126. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  127. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  128. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  129. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  130. package/dist/react/runtimes/useRemoteThreadListRuntime.js +77 -29
  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/message-part-runtime.d.ts +2 -2
  137. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  138. package/dist/runtime/api/message-part-runtime.js +1 -1
  139. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  140. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  141. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  142. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  143. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  144. package/dist/runtime/api/thread-list-runtime.d.ts +12 -1
  145. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  146. package/dist/runtime/api/thread-list-runtime.js +23 -14
  147. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  148. package/dist/runtime/api/thread-runtime.d.ts +5 -1
  149. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  150. package/dist/runtime/api/thread-runtime.js +1 -1
  151. package/dist/runtime/api/thread-runtime.js.map +1 -1
  152. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  153. package/dist/runtime/base/base-composer-runtime-core.js +16 -12
  154. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  155. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -2
  156. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  157. package/dist/runtime/base/base-thread-runtime-core.js +8 -7
  158. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  159. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  160. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  161. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +18 -3
  162. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/interfaces/thread-runtime-core.d.ts +15 -1
  164. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  165. package/dist/runtime/utils/auto-status.d.ts +1 -1
  166. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  167. package/dist/runtime/utils/auto-status.js +6 -2
  168. package/dist/runtime/utils/auto-status.js.map +1 -1
  169. package/dist/runtime/utils/external-message-conversion.d.ts +3 -2
  170. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  171. package/dist/runtime/utils/external-message-conversion.js +5 -4
  172. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  173. package/dist/runtime/utils/message-repository.d.ts +1 -0
  174. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  175. package/dist/runtime/utils/message-repository.js +15 -6
  176. package/dist/runtime/utils/message-repository.js.map +1 -1
  177. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +6 -3
  178. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
  179. package/dist/runtime/utils/resolveToolApprovalResponse.js +12 -3
  180. package/dist/runtime/utils/resolveToolApprovalResponse.js.map +1 -1
  181. package/dist/runtime/utils/thread-message-like.d.ts +2 -10
  182. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  183. package/dist/runtime/utils/thread-message-like.js +1 -1
  184. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  185. package/dist/runtime/utils/tool-call-tree.d.ts +39 -0
  186. package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -0
  187. package/dist/runtime/utils/tool-call-tree.js +85 -0
  188. package/dist/runtime/utils/tool-call-tree.js.map +1 -0
  189. package/dist/runtimes/external-store/external-store-adapter.d.ts +29 -2
  190. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  191. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  192. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  193. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  194. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -1
  195. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  196. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +34 -28
  197. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  198. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +1 -1
  199. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
  200. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
  201. package/dist/runtimes/internal.d.ts +2 -2
  202. package/dist/runtimes/internal.js +2 -2
  203. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
  204. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  205. package/dist/runtimes/local/local-thread-runtime-core.js +3 -1
  206. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  207. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  208. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  209. package/dist/runtimes/remote-thread-list/remote-thread-state.js +148 -43
  210. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  211. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  212. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  213. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  214. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  215. package/dist/runtimes/remote-thread-list/title.d.ts +11 -0
  216. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -0
  217. package/dist/runtimes/remote-thread-list/title.js +14 -0
  218. package/dist/runtimes/remote-thread-list/title.js.map +1 -0
  219. package/dist/runtimes/remote-thread-list/types.d.ts +16 -5
  220. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  221. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +31 -8
  222. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  223. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +97 -60
  224. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  225. package/dist/store/clients/external-thread.d.ts +1 -1
  226. package/dist/store/clients/external-thread.d.ts.map +1 -1
  227. package/dist/store/clients/external-thread.js +30 -7
  228. package/dist/store/clients/external-thread.js.map +1 -1
  229. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  230. package/dist/store/clients/model-context-client.js +2 -1
  231. package/dist/store/clients/model-context-client.js.map +1 -1
  232. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  233. package/dist/store/clients/single-thread-list.js +2 -1
  234. package/dist/store/clients/single-thread-list.js.map +1 -1
  235. package/dist/store/clients/suggestions.d.ts.map +1 -1
  236. package/dist/store/clients/suggestions.js +19 -6
  237. package/dist/store/clients/suggestions.js.map +1 -1
  238. package/dist/store/clients/thread-message-client.js +1 -1
  239. package/dist/store/clients/thread-selection-events.js +1 -1
  240. package/dist/store/index.d.ts +2 -1
  241. package/dist/store/index.js +2 -1
  242. package/dist/store/internal.d.ts +2 -2
  243. package/dist/store/internal.js +2 -2
  244. package/dist/store/primitive-predicates.d.ts +4 -1
  245. package/dist/store/primitive-predicates.d.ts.map +1 -1
  246. package/dist/store/primitive-predicates.js +9 -1
  247. package/dist/store/primitive-predicates.js.map +1 -1
  248. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  249. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  250. package/dist/store/runtime-clients/composer-runtime-client.js +98 -62
  251. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  252. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  253. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  254. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  255. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  256. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  257. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +2 -2
  258. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  259. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  260. package/dist/store/runtime-clients/thread-list-runtime-client.js +128 -107
  261. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  262. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  263. package/dist/store/runtime-clients/thread-runtime-client.js +194 -122
  264. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  265. package/dist/store/runtime-clients/useSubscribable.d.ts +3 -1
  266. package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
  267. package/dist/store/runtime-clients/useSubscribable.js +1 -1
  268. package/dist/store/runtime-clients/useSubscribable.js.map +1 -1
  269. package/dist/store/scopes/composer.d.ts +10 -2
  270. package/dist/store/scopes/composer.d.ts.map +1 -1
  271. package/dist/store/scopes/message.d.ts +30 -1
  272. package/dist/store/scopes/message.d.ts.map +1 -1
  273. package/dist/store/scopes/part.d.ts +6 -3
  274. package/dist/store/scopes/part.d.ts.map +1 -1
  275. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  276. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  277. package/dist/store/scopes/thread.d.ts +8 -0
  278. package/dist/store/scopes/thread.d.ts.map +1 -1
  279. package/dist/store/scopes/threads.d.ts +1 -0
  280. package/dist/store/scopes/threads.d.ts.map +1 -1
  281. package/dist/subscribable/subscribable.d.ts +8 -1
  282. package/dist/subscribable/subscribable.d.ts.map +1 -1
  283. package/dist/subscribable/subscribable.js +24 -16
  284. package/dist/subscribable/subscribable.js.map +1 -1
  285. package/dist/types/attachment.d.ts +2 -1
  286. package/dist/types/attachment.d.ts.map +1 -1
  287. package/dist/types/attachment.js +2 -1
  288. package/dist/types/attachment.js.map +1 -1
  289. package/dist/types/message.d.ts +40 -1
  290. package/dist/types/message.d.ts.map +1 -1
  291. package/dist/types/message.js +7 -1
  292. package/dist/types/message.js.map +1 -1
  293. package/dist/utils/abortable-iterable.d.ts +19 -0
  294. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  295. package/dist/utils/abortable-iterable.js +72 -0
  296. package/dist/utils/abortable-iterable.js.map +1 -0
  297. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  298. package/dist/utils/composite-context-provider.js +20 -2
  299. package/dist/utils/composite-context-provider.js.map +1 -1
  300. package/dist/utils/record.d.ts +11 -0
  301. package/dist/utils/record.d.ts.map +1 -0
  302. package/dist/utils/record.js +12 -0
  303. package/dist/utils/record.js.map +1 -0
  304. package/package.json +12 -17
  305. package/src/adapters/attachment.test.ts +50 -1
  306. package/src/adapters/attachment.ts +7 -4
  307. package/src/index.ts +7 -1
  308. package/src/internal.ts +12 -0
  309. package/src/model-context/frame/host.test.ts +24 -0
  310. package/src/model-context/frame/host.ts +10 -4
  311. package/src/model-context/frame/provider.test.ts +288 -1
  312. package/src/model-context/frame/provider.ts +148 -30
  313. package/src/model-context/interactable-composer-metadata.test.ts +28 -0
  314. package/src/model-context/interactable-composer-metadata.ts +9 -3
  315. package/src/model-context/registry.test.ts +12 -0
  316. package/src/model-context/registry.ts +3 -2
  317. package/src/model-context/tool-call-text.test.ts +67 -0
  318. package/src/model-context/tool-call-text.ts +67 -0
  319. package/src/model-context/types.test.ts +25 -0
  320. package/src/model-context/types.ts +9 -4
  321. package/src/react/AssistantRuntimeProvider.test.tsx +66 -2
  322. package/src/react/RuntimeAdapter.test.ts +34 -0
  323. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +1 -3
  324. package/src/react/client/InMemoryThreadList.ts +1 -0
  325. package/src/react/client/Interactables.test.ts +28 -0
  326. package/src/react/client/Interactables.ts +15 -13
  327. package/src/react/client/RemoteThreadList.background.test.ts +526 -0
  328. package/src/react/client/RemoteThreadList.concurrent.test.tsx +315 -0
  329. package/src/react/client/RemoteThreadList.test.ts +617 -3
  330. package/src/react/client/RemoteThreadList.ts +433 -167
  331. package/src/react/client/Tools.ts +29 -12
  332. package/src/react/index.ts +4 -0
  333. package/src/react/interactables-legacy/Interactables.ts +16 -16
  334. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  335. package/src/react/model-context/makeAssistantTool.ts +2 -2
  336. package/src/react/model-context/modelContextHooks.test.tsx +131 -0
  337. package/src/react/model-context/toolbox.test.ts +9 -9
  338. package/src/react/model-context/toolbox.ts +9 -36
  339. package/src/react/model-context/useAssistantContext.ts +4 -2
  340. package/src/react/model-context/useAssistantTool.ts +2 -2
  341. package/src/react/model-context/useAuiToolOverrides.ts +4 -2
  342. package/src/react/model-context/useInlineRender.test.tsx +48 -0
  343. package/src/react/model-context/useInlineRender.ts +14 -10
  344. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  345. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  346. package/src/react/primitive-hooks/useMessageError.ts +2 -19
  347. package/src/react/primitive-hooks/useThreadListLoadMore.ts +2 -3
  348. package/src/react/primitives/composer/ComposerAttachments.test.tsx +76 -0
  349. package/src/react/primitives/composer/ComposerAttachments.tsx +9 -4
  350. package/src/react/primitives/composer/ComposerQueue.test.tsx +72 -0
  351. package/src/react/primitives/composer/ComposerQueue.tsx +7 -4
  352. package/src/react/primitives/message/MessageAttachments.test.tsx +107 -37
  353. package/src/react/primitives/message/MessageAttachments.tsx +10 -7
  354. package/src/react/primitives/message/MessageGroupedParts.tsx +2 -2
  355. package/src/react/primitives/message/MessageParts.tsx +3 -3
  356. package/src/react/primitives/threadList/ThreadListItems.test.tsx +99 -0
  357. package/src/react/primitives/threadList/ThreadListItems.tsx +8 -5
  358. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  359. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +127 -1
  360. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  361. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +99 -35
  362. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  363. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +274 -0
  364. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +223 -152
  365. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  366. package/src/react/runtimes/RemoteThreadResource.ts +20 -16
  367. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +51 -2
  368. package/src/react/runtimes/assistant-transport/useLatestRef.ts +2 -2
  369. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +45 -0
  370. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +546 -5
  371. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +546 -103
  372. package/src/react/runtimes/cloud/auiV0.ts +5 -0
  373. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +103 -0
  374. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +237 -0
  375. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +90 -1
  376. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +33 -238
  377. package/src/react/runtimes/external-message-converter.ts +1 -0
  378. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  379. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  380. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  381. package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +216 -0
  382. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  383. package/src/react/runtimes/useRemoteThreadListRuntime.ts +57 -6
  384. package/src/react/types/MessagePartComponentTypes.ts +7 -3
  385. package/src/react/types/scopes/tools.ts +2 -0
  386. package/src/runtime/api/message-part-runtime.ts +3 -3
  387. package/src/runtime/api/message-runtime.test.ts +75 -0
  388. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  389. package/src/runtime/api/thread-list-runtime.ts +46 -12
  390. package/src/runtime/api/thread-runtime.test.ts +78 -0
  391. package/src/runtime/api/thread-runtime.ts +8 -1
  392. package/src/runtime/base/base-composer-runtime-core.ts +36 -8
  393. package/src/runtime/base/base-thread-runtime-core.test.ts +149 -4
  394. package/src/runtime/base/base-thread-runtime-core.ts +16 -8
  395. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  396. package/src/runtime/interfaces/thread-list-runtime-core.ts +25 -2
  397. package/src/runtime/interfaces/thread-runtime-core.ts +17 -1
  398. package/src/runtime/utils/auto-status.test.ts +57 -0
  399. package/src/runtime/utils/auto-status.ts +13 -1
  400. package/src/runtime/utils/external-message-conversion.test.ts +165 -0
  401. package/src/runtime/utils/external-message-conversion.ts +18 -2
  402. package/src/runtime/utils/message-repository.ts +29 -17
  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 +3 -11
  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 +18 -13
  410. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +56 -33
  411. package/src/runtimes/inert/InertThreadRuntimeCore.ts +1 -1
  412. package/src/runtimes/internal.ts +1 -0
  413. package/src/runtimes/local/local-thread-runtime-core.test.ts +29 -3
  414. package/src/runtimes/local/local-thread-runtime-core.ts +5 -1
  415. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +238 -0
  416. package/src/runtimes/remote-thread-list/remote-thread-state.ts +223 -44
  417. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  418. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  419. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  420. package/src/runtimes/remote-thread-list/title.ts +16 -0
  421. package/src/runtimes/remote-thread-list/types.ts +16 -5
  422. package/src/runtimes/tool-invocations/EDGE_CASES.md +110 -5
  423. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +750 -136
  424. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +144 -91
  425. package/src/store/clients/external-thread.ts +62 -11
  426. package/src/store/clients/model-context-client.ts +5 -1
  427. package/src/store/clients/single-thread-list.ts +1 -0
  428. package/src/store/clients/suggestions.test.ts +195 -1
  429. package/src/store/clients/suggestions.ts +45 -34
  430. package/src/store/index.ts +4 -0
  431. package/src/store/internal.ts +2 -0
  432. package/src/store/primitive-predicates.ts +35 -0
  433. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  434. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  435. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  436. package/src/store/runtime-clients/thread-list-runtime-client.ts +17 -2
  437. package/src/store/runtime-clients/thread-runtime-client.ts +58 -10
  438. package/src/store/runtime-clients/useSubscribable.ts +8 -2
  439. package/src/store/scopes/composer.ts +16 -4
  440. package/src/store/scopes/message.ts +14 -0
  441. package/src/store/scopes/part.ts +6 -3
  442. package/src/store/scopes/thread-list-item.ts +5 -2
  443. package/src/store/scopes/thread.ts +4 -0
  444. package/src/store/scopes/threads.ts +1 -0
  445. package/src/subscribable/subscribable.test.ts +50 -0
  446. package/src/subscribable/subscribable.ts +33 -21
  447. package/src/tests/MessageRepository.test.ts +116 -0
  448. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  449. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  450. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +158 -0
  451. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +78 -0
  452. package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +52 -0
  453. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  454. package/src/tests/attachment-adapters.test.ts +14 -0
  455. package/src/tests/base-composer-runtime-core.test.ts +39 -0
  456. package/src/tests/external-store-optimistic-id.test.ts +79 -0
  457. package/src/tests/external-store-repository-instance.test.ts +166 -0
  458. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  459. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +49 -34
  460. package/src/tests/external-thread-attachments.test.tsx +224 -1
  461. package/src/tests/external-thread-parity.test.tsx +23 -0
  462. package/src/tests/remote-thread-list-adapter-switch.test.ts +66 -1
  463. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  464. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  465. package/src/tests/thread-switch-events.test.tsx +475 -2
  466. package/src/tests/tools-scope-migration.test.tsx +28 -0
  467. package/src/types/attachment.ts +4 -0
  468. package/src/types/message.ts +50 -2
  469. package/src/utils/abortable-iterable.test.ts +213 -0
  470. package/src/utils/abortable-iterable.ts +93 -0
  471. package/src/utils/composite-context-provider.test.ts +25 -0
  472. package/src/utils/composite-context-provider.ts +23 -2
  473. package/src/utils/record.test.ts +48 -0
  474. package/src/utils/record.ts +11 -0
@@ -2,15 +2,20 @@ 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
21
  import { OptimisticState } from "../../runtimes/remote-thread-list/optimistic-state";
@@ -20,6 +25,8 @@ import {
20
25
  createThreadMappingId,
21
26
  getThreadData,
22
27
  normalizeCursor,
28
+ reconcileInitializedThread,
29
+ promoteNewThreadReducer,
23
30
  updateStatusReducer,
24
31
  type RemoteThreadData,
25
32
  type RemoteThreadState,
@@ -33,13 +40,23 @@ import type {
33
40
  } from "../../runtimes/remote-thread-list/types";
34
41
  import { ThreadListAdapterChangedError } from "../../runtimes/remote-thread-list/adapter-changed";
35
42
  import type { ThreadMessage } from "../../types/message";
36
- import { AssistantMessageStream } from "assistant-stream";
37
43
  import { handleThreadListAction } from "../../store/runtime-clients/handle-thread-list-action";
38
44
  import {
39
45
  inMemoryThreadListTransformScopes,
40
46
  type InMemoryThreadListProps,
41
47
  } from "./InMemoryThreadList";
42
48
  import { AdaptedRemoteThread } from "./AdaptedRemoteThread";
49
+ import {
50
+ applyTitleStream,
51
+ isTitleSourceMessage,
52
+ } from "../../runtimes/remote-thread-list/title";
53
+ import {
54
+ clearThreadTitleState,
55
+ finishThreadTitleRename,
56
+ runThreadTitleGeneration,
57
+ startThreadTitleRename,
58
+ type ThreadTitleState,
59
+ } from "../../runtimes/remote-thread-list/title-generation";
43
60
 
44
61
  const RESOLVED_PROMISE = Promise.resolve();
45
62
 
@@ -61,7 +78,20 @@ export type RemoteThreadListProps = {
61
78
  onThreadIdChange?: ((threadId: string | undefined) => void) | undefined;
62
79
  onSwitchToThread?: ((threadId: string) => void) | undefined;
63
80
  onSwitchToNewThread?: (() => void) | undefined;
81
+ /** Called after the backing adapter successfully deletes the thread. */
64
82
  onDelete?: ((threadId: string) => void) | undefined;
83
+ /**
84
+ * Keeps every thread the session switched to mounted, so a run continues
85
+ * after the user switches away, per-item `isRunning` reflects background
86
+ * runs, and a freshly initialized thread generates its title automatically.
87
+ * A body unmounts on delete, detach, or an adapter replacement (`reload()`
88
+ * after swapping the adapter); visited threads otherwise stay mounted for
89
+ * the client's lifetime. Each body reads its own thread through the ambient
90
+ * `threadListItem` scope, and `unstable_useAdapters` runs once per mounted
91
+ * body. Off (the default), only the visible thread is mounted and a switch
92
+ * unmounts it. The mode is fixed at mount.
93
+ */
94
+ backgroundThreads?: boolean | undefined;
65
95
  };
66
96
 
67
97
  const threadNotFoundError = (threadIdOrRemoteId: string, action: string) =>
@@ -83,16 +113,6 @@ const toInitializeResult = (
83
113
  externalId: result.externalId,
84
114
  });
85
115
 
86
- const applyTitleStream = async (
87
- stream: Parameters<typeof AssistantMessageStream.fromAssistantStream>[0],
88
- onTitle: (title: string | undefined) => Promise<void>,
89
- ) => {
90
- const messageStream = AssistantMessageStream.fromAssistantStream(stream);
91
- for await (const result of messageStream) {
92
- await onTitle(result.parts.filter((part) => part.type === "text")[0]?.text);
93
- }
94
- };
95
-
96
116
  const useThreadListItemClient = (props: {
97
117
  data: RemoteThreadData;
98
118
  isRunning: boolean;
@@ -102,7 +122,7 @@ const useThreadListItemClient = (props: {
102
122
  onArchive: () => void;
103
123
  onUnarchive: () => void;
104
124
  onDelete: () => void;
105
- onGenerateTitle: () => void;
125
+ onGenerateTitle: (options?: { automatic?: boolean }) => void;
106
126
  onInitialize: () => Promise<{
107
127
  remoteId: string;
108
128
  externalId: string | undefined;
@@ -202,9 +222,96 @@ const isSameThread = (
202
222
  return data !== undefined && itemMatchesId(data, listState, right);
203
223
  };
204
224
 
225
+ const useRemoteThreadBody = ({
226
+ id,
227
+ status,
228
+ remoteId,
229
+ item,
230
+ thread,
231
+ }: {
232
+ id: string;
233
+ status: RemoteThreadData["status"];
234
+ remoteId: string | undefined;
235
+ item: (isRunning: boolean) => ResourceElement<ClientOutput<"threadListItem">>;
236
+ thread: ResourceElement<ClientOutput<"thread">>;
237
+ }): ClientOutput<"thread"> => {
238
+ const parent = useAssistantContextValue();
239
+ const [isRunning, setIsRunning] = useState(false);
240
+ // The item client is mounted inside the body and served to the subtree
241
+ // through a Derived scope, which keeps the graft on the derived-only (no
242
+ // nested client host) path; resolving `parent.threads` here instead would
243
+ // read the threads scope during its own construction.
244
+ const itemHandle = useClientResource(item(isRunning));
245
+ const { client } = useConfiguredAui(parent, {
246
+ threadListItem: Derived({
247
+ source: "threads",
248
+ query: { type: "id", id },
249
+ get: () => itemHandle.methods,
250
+ }),
251
+ });
252
+ // Auto-title arms only for a thread born "new" in this body's lifetime;
253
+ // threads loaded or fetched from the adapter already carry their title. The
254
+ // settled (non-optimistic) initialize is what writes remoteId, so its
255
+ // arrival is the initialization signal.
256
+ const bornNewRef = useRef(status === "new");
257
+ const titleFiredRef = useRef(false);
258
+ return useAssistantContextProvider(client, function useBoundRemoteBody() {
259
+ const body = useClientResource(thread);
260
+ const bodyRunning = body.state.isRunning === true;
261
+ useEffect(() => {
262
+ setIsRunning(bodyRunning);
263
+ }, [bodyRunning]);
264
+ const armed =
265
+ bornNewRef.current && !titleFiredRef.current && remoteId !== undefined;
266
+ const hasTitleSource =
267
+ armed &&
268
+ (
269
+ body.state.messages as readonly {
270
+ status?: { type: string } | undefined;
271
+ }[]
272
+ ).some(isTitleSourceMessage);
273
+ useEffect(() => {
274
+ if (!hasTitleSource || titleFiredRef.current) return;
275
+ titleFiredRef.current = true;
276
+ client.threadListItem.generateTitle({ automatic: true });
277
+ }, [hasTitleSource]);
278
+ return body.methods;
279
+ });
280
+ };
281
+
282
+ const RemoteThreadBody = resource(useRemoteThreadBody);
283
+
284
+ // `thread("main")` keeps one identity across switches, like the single
285
+ // useClientResource slot it replaced: consumers (the ambient `thread` scope,
286
+ // captured clients) hold the facade while it delegates to whichever body is
287
+ // currently main. Publishing the target during commit keeps abandoned
288
+ // concurrent renders from changing the facade observed by committed consumers.
289
+ const useMainThreadFacade = (
290
+ current: ClientOutput<"thread">,
291
+ ): ClientOutput<"thread"> => {
292
+ const currentRef = useRef(current);
293
+ useEffect(() => {
294
+ currentRef.current = current;
295
+ }, [current]);
296
+ const [facade] = useState(
297
+ () =>
298
+ new Proxy({} as ClientOutput<"thread">, {
299
+ get: (_, prop) =>
300
+ (currentRef.current as unknown as Record<PropertyKey, unknown>)[prop],
301
+ has: (_, prop) => prop in (currentRef.current as object),
302
+ ownKeys: () => Reflect.ownKeys(currentRef.current as object),
303
+ getOwnPropertyDescriptor: (_, prop) =>
304
+ Reflect.getOwnPropertyDescriptor(currentRef.current as object, prop),
305
+ }),
306
+ );
307
+ return facade;
308
+ };
309
+
205
310
  const useRemoteThreadListView = ({
206
311
  listState,
207
312
  mainThreadId,
313
+ startedIds,
314
+ backgroundThreads,
208
315
  threadFactory,
209
316
  useAdapters,
210
317
  onSwitchTo,
@@ -219,6 +326,8 @@ const useRemoteThreadListView = ({
219
326
  }: {
220
327
  listState: RemoteThreadState;
221
328
  mainThreadId: string;
329
+ startedIds: readonly string[];
330
+ backgroundThreads: boolean;
222
331
  threadFactory: RemoteThreadListProps["thread"];
223
332
  useAdapters: RemoteThreadListAdapter["unstable_useAdapters"];
224
333
  onSwitchTo: (
@@ -236,6 +345,7 @@ const useRemoteThreadListView = ({
236
345
  onGenerateTitle: (
237
346
  threadId: string,
238
347
  messages: readonly ThreadMessage[] | undefined,
348
+ options?: { automatic?: boolean },
239
349
  ) => Promise<void>;
240
350
  onInitialize: (threadId: string) => Promise<{
241
351
  remoteId: string;
@@ -243,17 +353,103 @@ const useRemoteThreadListView = ({
243
353
  }>;
244
354
  onDetach: (threadId: string) => Promise<void>;
245
355
  }) => {
246
- const thread = threadFactory(mainThreadId);
247
- const wrapped =
248
- useAdapters === undefined
249
- ? thread
250
- : AdaptedRemoteThread({
251
- useAdapters,
252
- thread,
253
- });
254
- const mainThreadClient = useClientResource(
255
- thread.key === undefined ? wrapped : withKey(thread.key, wrapped),
356
+ const bodyIds = useMemo(() => {
357
+ if (!backgroundThreads) return [mainThreadId];
358
+ const seen = new Set<string>();
359
+ const seenRemoteIds = new Set<string>();
360
+ const ids: string[] = [];
361
+ for (const id of startedIds) {
362
+ const data = getThreadData(listState, id);
363
+ if (!data || seen.has(data.id)) continue;
364
+ if (data.remoteId !== undefined && seenRemoteIds.has(data.remoteId)) {
365
+ continue;
366
+ }
367
+ seen.add(data.id);
368
+ if (data.remoteId !== undefined) seenRemoteIds.add(data.remoteId);
369
+ ids.push(data.id);
370
+ }
371
+ return ids;
372
+ }, [backgroundThreads, listState, mainThreadId, startedIds]);
373
+
374
+ const itemElementFor = (data: RemoteThreadData, isRunning: boolean) =>
375
+ ThreadListItemClient({
376
+ data,
377
+ isRunning,
378
+ onSwitchTo: (options) =>
379
+ handleThreadListAction("switch", () => onSwitchTo(data.id, options)),
380
+ onRename: (title) =>
381
+ handleThreadListAction("rename", () => onRename(data.id, title)),
382
+ onUpdateCustom: (custom) =>
383
+ handleThreadListAction("update custom metadata", () =>
384
+ onUpdateCustom(data.id, custom),
385
+ ),
386
+ onArchive: () =>
387
+ handleThreadListAction("archive", () => onArchive(data.id)),
388
+ onUnarchive: () =>
389
+ handleThreadListAction("unarchive", () => onUnarchive(data.id)),
390
+ onDelete: () => handleThreadListAction("delete", () => onDelete(data.id)),
391
+ onGenerateTitle: (options) =>
392
+ handleThreadListAction("generate title", () =>
393
+ onGenerateTitle(
394
+ data.id,
395
+ (backgroundThreads
396
+ ? bodyStateOf(data.id)?.messages
397
+ : mainThreadClient.state.messages) as
398
+ | readonly ThreadMessage[]
399
+ | undefined,
400
+ options,
401
+ ),
402
+ ),
403
+ onInitialize: () => onInitialize(data.id),
404
+ onDetach: () => handleThreadListAction("detach", () => onDetach(data.id)),
405
+ });
406
+
407
+ const bodies = useClientLookup(
408
+ bodyIds.map((id) => {
409
+ const made = threadFactory(id);
410
+ // Background bodies never change occupant, so an unkeyed factory
411
+ // element is keyed by the mapping id here; per-body history loads
412
+ // without requiring the factory to key itself.
413
+ const thread =
414
+ backgroundThreads && made.key === undefined ? withKey(id, made) : made;
415
+ const wrapped =
416
+ useAdapters === undefined
417
+ ? thread
418
+ : AdaptedRemoteThread({
419
+ useAdapters,
420
+ thread,
421
+ });
422
+ const data = getThreadData(listState, id);
423
+ const element =
424
+ backgroundThreads && data !== undefined
425
+ ? RemoteThreadBody({
426
+ id,
427
+ status: data.status,
428
+ remoteId: data.remoteId,
429
+ item: (isRunning) => itemElementFor(data, isRunning),
430
+ thread: wrapped,
431
+ })
432
+ : wrapped;
433
+ return withKey(backgroundThreads ? id : (made.key ?? "main"), element);
434
+ }),
256
435
  );
436
+ // A reload can remap a local thread id to its remote identity while the
437
+ // body stays keyed by the original mapping id, so lookups fall back to
438
+ // remote-identity matching.
439
+ const bodyIndexOf = (id: string) => {
440
+ const direct = bodyIds.indexOf(id);
441
+ if (direct !== -1) return direct;
442
+ return bodyIds.findIndex((bodyId) => isSameThread(listState, bodyId, id));
443
+ };
444
+ const bodyStateOf = (id: string) => {
445
+ const index = bodyIndexOf(id);
446
+ return index === -1 ? undefined : bodies.state[index];
447
+ };
448
+ const mainIndex = bodyIndexOf(mainThreadId);
449
+ const mainThreadClient = {
450
+ state: bodies.state[mainIndex]!,
451
+ methods: useMainThreadFacade(bodies.get({ index: mainIndex })),
452
+ };
257
453
  const itemOrder = useMemo(
258
454
  () => collectItemOrder(listState, mainThreadId),
259
455
  [listState, mainThreadId],
@@ -262,40 +458,13 @@ const useRemoteThreadListView = ({
262
458
  itemOrder.map((data) =>
263
459
  withKey(
264
460
  data.id,
265
- ThreadListItemClient({
461
+ itemElementFor(
266
462
  data,
267
- isRunning:
268
- itemMatchesId(data, listState, mainThreadId) &&
269
- mainThreadClient.state.isRunning,
270
- onSwitchTo: (options) =>
271
- handleThreadListAction("switch", () =>
272
- onSwitchTo(data.id, options),
273
- ),
274
- onRename: (title) =>
275
- handleThreadListAction("rename", () => onRename(data.id, title)),
276
- onUpdateCustom: (custom) =>
277
- handleThreadListAction("update custom metadata", () =>
278
- onUpdateCustom(data.id, custom),
279
- ),
280
- onArchive: () =>
281
- handleThreadListAction("archive", () => onArchive(data.id)),
282
- onUnarchive: () =>
283
- handleThreadListAction("unarchive", () => onUnarchive(data.id)),
284
- onDelete: () =>
285
- handleThreadListAction("delete", () => onDelete(data.id)),
286
- onGenerateTitle: () =>
287
- handleThreadListAction("generate title", () =>
288
- onGenerateTitle(
289
- data.id,
290
- mainThreadClient.state.messages as
291
- | readonly ThreadMessage[]
292
- | undefined,
293
- ),
294
- ),
295
- onInitialize: () => onInitialize(data.id),
296
- onDetach: () =>
297
- handleThreadListAction("detach", () => onDetach(data.id)),
298
- }),
463
+ backgroundThreads
464
+ ? (bodyStateOf(data.id)?.isRunning ?? false)
465
+ : itemMatchesId(data, listState, mainThreadId) &&
466
+ mainThreadClient.state.isRunning,
467
+ ),
299
468
  ),
300
469
  ),
301
470
  );
@@ -324,6 +493,7 @@ const useRemoteThreadList = (
324
493
  adapter,
325
494
  adapterAtLoad: adapter,
326
495
  adapterGeneration: 0,
496
+ titleStates: new Map<string, ThreadTitleState>(),
327
497
  loadGeneration: 0,
328
498
  switchGeneration: 0,
329
499
  loadPromise: undefined as Promise<void> | undefined,
@@ -340,9 +510,6 @@ const useRemoteThreadList = (
340
510
  };
341
511
  });
342
512
 
343
- // Config updates can invoke `reload()` in the same turn as the re-render, before the effect below runs.
344
- session.adapter = adapter;
345
-
346
513
  const listState = useSyncExternalStore(
347
514
  (onStoreChange) => store.subscribe(onStoreChange),
348
515
  () => store.value,
@@ -350,15 +517,21 @@ const useRemoteThreadList = (
350
517
  );
351
518
 
352
519
  const [mainThreadId, setMainThreadId] = useState(initialMainId);
520
+ const [startedIds, setStartedIds] = useState<readonly string[]>([
521
+ initialMainId,
522
+ ]);
523
+ const [backgroundThreads] = useState(props.backgroundThreads === true);
353
524
  const assignMainThreadId = useCallback(
354
525
  (id: string) => {
355
526
  session.mainThreadId = id;
356
527
  setMainThreadId(id);
528
+ setStartedIds((prev) => (prev.includes(id) ? prev : [...prev, id]));
357
529
  },
358
530
  [session],
359
531
  );
360
532
  useThreadSelectionEvents(mainThreadId);
361
533
  useEffect(() => {
534
+ // Publishing after commit keeps imperative actions on the adapter the committed tree renders with; an abandoned render must not reach them.
362
535
  session.adapter = adapter;
363
536
  session.mainThreadId = mainThreadId;
364
537
  session.onThreadIdChange = onThreadIdChange;
@@ -390,30 +563,28 @@ const useRemoteThreadList = (
390
563
  session.loadPromise = store
391
564
  .optimisticUpdate({
392
565
  execute: () => adapter.list(),
393
- loading: (state) => ({ ...state, isLoading: true }),
566
+ loading: (state) => {
567
+ if (generation !== session.loadGeneration) return state;
568
+ return { ...state, isLoading: true, loadError: undefined };
569
+ },
394
570
  then: (state, page) => {
395
571
  if (generation !== session.loadGeneration) return state;
396
572
  session.adapterAtLoad = adapter;
397
573
  const fresh = classifyThreads(page.threads, {
398
574
  threadIds: [],
399
575
  archivedThreadIds: [],
400
- threadIdMap: {},
401
- threadData: {},
576
+ threadIdMap: { ...state.threadIdMap },
577
+ threadData: { ...state.threadData },
402
578
  });
403
579
  const merged = {
404
580
  ...state,
405
581
  isLoading: false,
582
+ loadError: undefined,
406
583
  cursor: normalizeCursor(page.nextCursor),
407
584
  threadIds: fresh.threadIds,
408
585
  archivedThreadIds: fresh.archivedThreadIds,
409
- threadIdMap: {
410
- ...state.threadIdMap,
411
- ...fresh.threadIdMap,
412
- },
413
- threadData: {
414
- ...state.threadData,
415
- ...fresh.threadData,
416
- },
586
+ threadIdMap: fresh.threadIdMap,
587
+ threadData: fresh.threadData,
417
588
  };
418
589
  return preserveMidLoadTransitions(merged, state, statusAtRequest);
419
590
  },
@@ -425,6 +596,7 @@ const useRemoteThreadList = (
425
596
  store.update({
426
597
  ...store.baseValue,
427
598
  isLoading: false,
599
+ loadError: error,
428
600
  });
429
601
  })
430
602
  .then(() => {});
@@ -438,12 +610,14 @@ const useRemoteThreadList = (
438
610
  session.loadMorePromise = undefined;
439
611
  if (adapterChanged) {
440
612
  session.adapterGeneration++;
613
+ session.titleStates.clear();
441
614
  session.switchGeneration++;
442
615
  session.switchTask = undefined;
443
616
  session.adapterAtLoad = adapter;
444
617
  const seeded = seedNewThread(EMPTY_LIST);
445
618
  store.reset({ ...seeded.state, isLoading: true });
446
619
  assignMainThreadId(seeded.id);
620
+ setStartedIds([seeded.id]);
447
621
  notifyRemoteId(undefined, true);
448
622
  } else {
449
623
  store.update({
@@ -461,6 +635,26 @@ const useRemoteThreadList = (
461
635
  store,
462
636
  ]);
463
637
 
638
+ useEffect(() => {
639
+ if (typeof window === "undefined" || typeof document === "undefined") {
640
+ return;
641
+ }
642
+
643
+ const retryAfterError = () => {
644
+ if (store.value.loadError !== undefined) void getLoadThreadsPromise();
645
+ };
646
+ const retryAfterVisible = () => {
647
+ if (document.visibilityState === "visible") retryAfterError();
648
+ };
649
+
650
+ window.addEventListener("online", retryAfterError);
651
+ document.addEventListener("visibilitychange", retryAfterVisible);
652
+ return () => {
653
+ window.removeEventListener("online", retryAfterError);
654
+ document.removeEventListener("visibilitychange", retryAfterVisible);
655
+ };
656
+ }, [getLoadThreadsPromise, store]);
657
+
464
658
  useEffect(() => {
465
659
  void getLoadThreadsPromise();
466
660
  }, [getLoadThreadsPromise]);
@@ -486,7 +680,10 @@ const useRemoteThreadList = (
486
680
  const task = store
487
681
  .optimisticUpdate({
488
682
  execute: () => currentAdapter.list({ after: cursor }),
489
- loading: (state) => ({ ...state, isLoadingMore: true }),
683
+ loading: (state) => {
684
+ if (generation !== session.loadGeneration) return state;
685
+ return { ...state, isLoadingMore: true };
686
+ },
490
687
  then: (state, page) => {
491
688
  if (generation !== session.loadGeneration) return state;
492
689
  const appended = classifyThreads(page.threads, {
@@ -604,22 +801,30 @@ const useRemoteThreadList = (
604
801
  throw threadNotFoundError(threadIdOrRemoteId, "switching to it");
605
802
  }
606
803
  if (isSameThread(store.value, data.id, session.mainThreadId)) return;
607
- if (data.status === "archived" && options?.unarchive !== false) {
608
- const current = data;
804
+
805
+ const targetId = data.id;
806
+ let current: RemoteThreadData | undefined = data;
807
+
808
+ if (current.status === "archived" && options?.unarchive !== false) {
609
809
  const { remoteId } = await current.initializeTask;
610
810
  if (generation !== session.switchGeneration) return;
611
- await store.optimisticUpdate({
612
- execute: () => session.adapter.unarchive(remoteId),
613
- optimistic: (state) =>
614
- updateStatusReducer(state, current.id, "regular"),
615
- });
616
- if (generation !== session.switchGeneration) return;
617
- data = getThreadData(store.value, current.id) ?? current;
811
+ current = getThreadData(store.value, targetId);
812
+ if (current?.id !== targetId) return;
813
+ if (current.status === "archived") {
814
+ await store.optimisticUpdate({
815
+ execute: () => session.adapter.unarchive(remoteId),
816
+ optimistic: (state) =>
817
+ updateStatusReducer(state, targetId, "regular"),
818
+ });
819
+ if (generation !== session.switchGeneration) return;
820
+ current = getThreadData(store.value, targetId);
821
+ if (current?.id !== targetId) return;
822
+ }
618
823
  }
619
824
  if (generation !== session.switchGeneration) return;
620
- assignMainThreadId(data.id);
621
- notifyRemoteId(data.remoteId, emitThreadIdChange);
622
- session.onSwitchToThread?.(data.id);
825
+ assignMainThreadId(current.id);
826
+ notifyRemoteId(current.remoteId, emitThreadIdChange);
827
+ session.onSwitchToThread?.(current.id);
623
828
  }),
624
829
  [assignMainThreadId, notifyRemoteId, session, startSwitch, store],
625
830
  );
@@ -704,55 +909,60 @@ const useRemoteThreadList = (
704
909
  requireAdapterGeneration(adapterGeneration);
705
910
  return result;
706
911
  }
912
+ requireAdapterGeneration(adapterGeneration);
913
+ const initializeTask = currentAdapter.initialize(threadId);
914
+ let removedMappingId: string | undefined;
915
+ let replacementMainThreadId: string | undefined;
707
916
  const result = await store.optimisticUpdate({
708
- execute: () => {
709
- requireAdapterGeneration(adapterGeneration);
710
- return currentAdapter.initialize(threadId);
711
- },
712
- optimistic: (state) => updateStatusReducer(state, threadId, "regular"),
713
- loading: (state, task) => {
714
- const mappingId = createThreadMappingId(threadId);
715
- return {
716
- ...state,
717
- threadData: {
718
- ...state.threadData,
719
- [mappingId]: {
720
- ...state.threadData[mappingId],
721
- initializeTask: task,
722
- },
723
- },
724
- };
725
- },
917
+ execute: () => initializeTask,
918
+ optimistic: (state) =>
919
+ promoteNewThreadReducer(state, threadId, initializeTask),
726
920
  then: (state, { remoteId, externalId }) => {
727
921
  if (adapterGeneration !== session.adapterGeneration) return state;
728
- const data = getThreadData(state, threadId);
729
- if (!data) return state;
730
- const mappingId = createThreadMappingId(threadId);
731
- return {
732
- ...state,
733
- threadIdMap: {
734
- ...state.threadIdMap,
735
- [remoteId]: mappingId,
736
- },
737
- threadData: {
738
- ...state.threadData,
739
- [mappingId]: {
740
- ...data,
741
- initializeTask: Promise.resolve({ remoteId, externalId }),
742
- remoteId,
743
- externalId,
744
- },
745
- },
746
- };
922
+ // Background mode still owns the initializing body. Single-body mode
923
+ // has already replaced it, so retain the currently mounted body.
924
+ const retainedThreadId = backgroundThreads
925
+ ? threadId
926
+ : session.mainThreadId;
927
+ const reconciliation = reconcileInitializedThread(
928
+ state,
929
+ threadId,
930
+ remoteId,
931
+ externalId,
932
+ retainedThreadId,
933
+ );
934
+ removedMappingId = reconciliation.removedMappingId;
935
+ if (removedMappingId === session.mainThreadId) {
936
+ replacementMainThreadId = reconciliation.survivorMappingId;
937
+ }
938
+ return reconciliation.state;
747
939
  },
748
940
  });
749
941
  requireAdapterGeneration(adapterGeneration);
942
+ if (removedMappingId !== undefined) {
943
+ setStartedIds((prev) =>
944
+ prev.filter((startedId) => startedId !== removedMappingId),
945
+ );
946
+ }
947
+ if (
948
+ replacementMainThreadId !== undefined &&
949
+ session.mainThreadId === removedMappingId
950
+ ) {
951
+ assignMainThreadId(replacementMainThreadId);
952
+ }
750
953
  if (threadId === session.mainThreadId) {
751
954
  notifyRemoteId(result.remoteId, true);
752
955
  }
753
956
  return toInitializeResult(result);
754
957
  },
755
- [notifyRemoteId, requireAdapterGeneration, session, store],
958
+ [
959
+ assignMainThreadId,
960
+ backgroundThreads,
961
+ notifyRemoteId,
962
+ requireAdapterGeneration,
963
+ session,
964
+ store,
965
+ ],
756
966
  );
757
967
 
758
968
  const rename = useCallback(
@@ -764,27 +974,43 @@ const useRemoteThreadList = (
764
974
  if (data.status === "new") {
765
975
  throw threadStatusError(threadIdOrRemoteId, data.status, "be renamed");
766
976
  }
767
- return store.optimisticUpdate({
768
- execute: async () => {
769
- const { remoteId } = await data.initializeTask;
770
- requireAdapterGeneration(adapterGeneration);
771
- return currentAdapter.rename(remoteId, newTitle);
772
- },
773
- optimistic: (state) => {
774
- const current = getThreadData(state, threadIdOrRemoteId);
775
- if (!current) return state;
776
- return {
777
- ...state,
778
- threadData: {
779
- ...state.threadData,
780
- [current.id]: {
781
- ...current,
782
- title: newTitle,
977
+ const claim = startThreadTitleRename(
978
+ session.titleStates,
979
+ data.id,
980
+ newTitle,
981
+ );
982
+ return store
983
+ .optimisticUpdate({
984
+ execute: async () => {
985
+ const { remoteId } = await data.initializeTask;
986
+ requireAdapterGeneration(adapterGeneration);
987
+ return currentAdapter.rename(remoteId, newTitle);
988
+ },
989
+ optimistic: (state) => {
990
+ const current = getThreadData(state, threadIdOrRemoteId);
991
+ if (!current) return state;
992
+ return {
993
+ ...state,
994
+ threadData: {
995
+ ...state.threadData,
996
+ [current.id]: {
997
+ ...current,
998
+ title: newTitle,
999
+ },
783
1000
  },
784
- },
785
- };
786
- },
787
- });
1001
+ };
1002
+ },
1003
+ })
1004
+ .then(
1005
+ (result) => {
1006
+ finishThreadTitleRename(session.titleStates, data.id, claim, true);
1007
+ return result;
1008
+ },
1009
+ (error: unknown) => {
1010
+ finishThreadTitleRename(session.titleStates, data.id, claim, false);
1011
+ throw error;
1012
+ },
1013
+ );
788
1014
  },
789
1015
  [requireAdapterGeneration, session, store],
790
1016
  );
@@ -912,8 +1138,7 @@ const useRemoteThreadList = (
912
1138
  }
913
1139
  await ensureNotMain(data.id);
914
1140
  requireAdapterGeneration(adapterGeneration);
915
- onDelete?.(data.id);
916
- return store.optimisticUpdate({
1141
+ const result = await store.optimisticUpdate({
917
1142
  execute: async () => {
918
1143
  const { remoteId } = await data.initializeTask;
919
1144
  requireAdapterGeneration(adapterGeneration);
@@ -921,6 +1146,13 @@ const useRemoteThreadList = (
921
1146
  },
922
1147
  optimistic: (state) => updateStatusReducer(state, data.id, "deleted"),
923
1148
  });
1149
+ // An adapter swap resets the optimistic layer, and a listed thread's slot
1150
+ // id is its remote id, so a replacement adapter can re-list this slot
1151
+ // while the deletion is in flight.
1152
+ if (getThreadData(store.value, data.id) !== undefined) return result;
1153
+ clearThreadTitleState(session.titleStates, data.id);
1154
+ onDelete?.(data.id);
1155
+ return result;
924
1156
  },
925
1157
  [ensureNotMain, onDelete, requireAdapterGeneration, session, store],
926
1158
  );
@@ -929,6 +1161,7 @@ const useRemoteThreadList = (
929
1161
  async (
930
1162
  threadIdOrRemoteId: string,
931
1163
  messages: readonly ThreadMessage[] | undefined,
1164
+ options?: { automatic?: boolean },
932
1165
  ) => {
933
1166
  const currentAdapter = session.adapter;
934
1167
  const adapterGeneration = session.adapterGeneration;
@@ -945,38 +1178,65 @@ const useRemoteThreadList = (
945
1178
  }
946
1179
  const { remoteId } = await data.initializeTask;
947
1180
  requireAdapterGeneration(adapterGeneration);
948
- if (!isSameThread(store.value, data.id, session.mainThreadId)) return;
1181
+ if (
1182
+ !backgroundThreads &&
1183
+ !isSameThread(store.value, data.id, session.mainThreadId)
1184
+ ) {
1185
+ return;
1186
+ }
949
1187
  if (!messages) return;
950
- const stream = await currentAdapter.generateTitle(remoteId, messages);
951
- requireAdapterGeneration(adapterGeneration);
952
- await applyTitleStream(stream, async (newTitle) => {
953
- await store.optimisticUpdate({
954
- execute: async () => {},
955
- optimistic: (state) => {
956
- if (adapterGeneration !== session.adapterGeneration) return state;
957
- const current = getThreadData(state, data.id);
958
- if (!current) return state;
959
- return {
960
- ...state,
961
- threadData: {
962
- ...state.threadData,
963
- [current.id]: {
964
- ...current,
965
- title: newTitle,
1188
+ await runThreadTitleGeneration({
1189
+ states: session.titleStates,
1190
+ threadId: data.id,
1191
+ automatic: options?.automatic === true,
1192
+ generate: async (onTitle) => {
1193
+ const stream = await currentAdapter.generateTitle(remoteId, messages);
1194
+ requireAdapterGeneration(adapterGeneration);
1195
+ await applyTitleStream(stream, onTitle);
1196
+ },
1197
+ rename: async (title) => {
1198
+ requireAdapterGeneration(adapterGeneration);
1199
+ await currentAdapter.rename(remoteId, title);
1200
+ },
1201
+ applyTitle: async (title) => {
1202
+ await store.optimisticUpdate({
1203
+ execute: async () => {},
1204
+ optimistic: (state) => {
1205
+ if (adapterGeneration !== session.adapterGeneration) return state;
1206
+ const current = getThreadData(state, data.id);
1207
+ if (!current) return state;
1208
+ return {
1209
+ ...state,
1210
+ threadData: {
1211
+ ...state.threadData,
1212
+ [current.id]: {
1213
+ ...current,
1214
+ title,
1215
+ },
966
1216
  },
967
- },
968
- };
969
- },
970
- });
1217
+ };
1218
+ },
1219
+ });
1220
+ },
971
1221
  });
972
1222
  },
973
- [requireAdapterGeneration, session, store],
1223
+ [backgroundThreads, requireAdapterGeneration, session, store],
1224
+ );
1225
+
1226
+ const detach = useCallback(
1227
+ async (threadId: string) => {
1228
+ await ensureNotMain(threadId);
1229
+ setStartedIds((prev) => prev.filter((id) => id !== threadId));
1230
+ },
1231
+ [ensureNotMain],
974
1232
  );
975
1233
 
976
1234
  const { mainThreadClient, itemOrder, threadListItems } =
977
1235
  useRemoteThreadListView({
978
1236
  listState,
979
1237
  mainThreadId,
1238
+ startedIds,
1239
+ backgroundThreads,
980
1240
  threadFactory,
981
1241
  useAdapters: adapter.unstable_useAdapters,
982
1242
  onSwitchTo: (id, options) => switchToThread(id, options),
@@ -985,9 +1245,10 @@ const useRemoteThreadList = (
985
1245
  onArchive: (id) => archive(id),
986
1246
  onUnarchive: (id) => unarchive(id),
987
1247
  onDelete: (id) => deleteThread(id),
988
- onGenerateTitle: (id, messages) => generateTitle(id, messages),
1248
+ onGenerateTitle: (id, messages, options) =>
1249
+ generateTitle(id, messages, options),
989
1250
  onInitialize: async (id) => toInitializeResult(await initialize(id)),
990
- onDetach: (id) => ensureNotMain(id),
1251
+ onDetach: (id) => detach(id),
991
1252
  });
992
1253
 
993
1254
  const mainRemoteId = getThreadData(listState, mainThreadId)?.remoteId;
@@ -1023,6 +1284,7 @@ const useRemoteThreadList = (
1023
1284
  mainThreadId,
1024
1285
  newThreadId: listState.newThreadId ?? null,
1025
1286
  isLoading: listState.isLoading,
1287
+ loadError: listState.loadError,
1026
1288
  isLoadingMore: listState.isLoadingMore,
1027
1289
  hasMore: listState.cursor !== undefined,
1028
1290
  threadIds: listState.threadIds,
@@ -1034,6 +1296,7 @@ const useRemoteThreadList = (
1034
1296
  listState.archivedThreadIds,
1035
1297
  listState.cursor,
1036
1298
  listState.isLoading,
1299
+ listState.loadError,
1037
1300
  listState.isLoadingMore,
1038
1301
  listState.newThreadId,
1039
1302
  listState.threadIds,
@@ -1098,6 +1361,9 @@ const useRemoteThreadList = (
1098
1361
  * and attachments come from `unstable_useAdapters`. `useRemoteThreadListRuntime`
1099
1362
  * uses the same hook when `unstable_Provider` is omitted. Key the factory
1100
1363
  * with `withKey` so history reloads when the visible thread changes.
1364
+ * With `backgroundThreads`, every visited thread stays mounted: runs continue
1365
+ * across switches, per-item `isRunning` is live, and freshly initialized
1366
+ * threads title themselves.
1101
1367
  */
1102
1368
  export const RemoteThreadList = resource(useRemoteThreadList);
1103
1369