@assistant-ui/core 0.3.14 → 0.3.16

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 (341) hide show
  1. package/dist/adapters/voice.js +11 -0
  2. package/dist/adapters/voice.js.map +1 -1
  3. package/dist/index.js +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 +1 -1
  7. package/dist/model-context/frame/host.d.ts.map +1 -1
  8. package/dist/model-context/frame/host.js +20 -5
  9. package/dist/model-context/frame/host.js.map +1 -1
  10. package/dist/model-context/frame/provider.d.ts +6 -0
  11. package/dist/model-context/frame/provider.d.ts.map +1 -1
  12. package/dist/model-context/frame/provider.js +126 -16
  13. package/dist/model-context/frame/provider.js.map +1 -1
  14. package/dist/model-context/frame/types.d.ts +4 -1
  15. package/dist/model-context/frame/types.d.ts.map +1 -1
  16. package/dist/model-context/frame/types.js.map +1 -1
  17. package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
  18. package/dist/model-context/interactable-composer-metadata.js +1 -1
  19. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  20. package/dist/model-context/registry.d.ts +2 -2
  21. package/dist/model-context/registry.d.ts.map +1 -1
  22. package/dist/model-context/registry.js +9 -28
  23. package/dist/model-context/registry.js.map +1 -1
  24. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  25. package/dist/react/client/InMemoryThreadList.js +24 -20
  26. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  27. package/dist/react/client/Interactables.d.ts.map +1 -1
  28. package/dist/react/client/Interactables.js +59 -177
  29. package/dist/react/client/Interactables.js.map +1 -1
  30. package/dist/react/client/RemoteThreadList.d.ts +1 -1
  31. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  32. package/dist/react/client/RemoteThreadList.js +117 -78
  33. package/dist/react/client/RemoteThreadList.js.map +1 -1
  34. package/dist/react/index.d.ts +2 -1
  35. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  36. package/dist/react/interactables-legacy/Interactables.js +56 -174
  37. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  38. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
  39. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
  40. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
  41. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
  42. package/dist/react/primitive-hooks/useActionBarCopy.js +7 -1
  43. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  44. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts +3 -0
  45. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts.map +1 -1
  46. package/dist/react/primitive-hooks/useThreadIsEmpty.js +3 -0
  47. package/dist/react/primitive-hooks/useThreadIsEmpty.js.map +1 -1
  48. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts +3 -0
  49. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts.map +1 -1
  50. package/dist/react/primitive-hooks/useThreadIsRunning.js +3 -0
  51. package/dist/react/primitive-hooks/useThreadIsRunning.js.map +1 -1
  52. package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
  53. package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
  54. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  55. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  56. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +3 -1
  57. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  58. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +6 -0
  59. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  60. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +7 -1
  61. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  62. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +174 -64
  63. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  64. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  65. package/dist/react/runtimes/RemoteThreadResource.js +2 -0
  66. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  67. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
  68. package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
  69. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
  70. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  71. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +1 -1
  72. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  73. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
  74. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  75. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +20 -67
  76. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  77. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
  78. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
  79. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
  80. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
  81. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  82. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +38 -5
  83. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  84. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
  85. package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
  86. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  87. package/dist/react/runtimes/external-message-converter.d.ts +6 -25
  88. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  89. package/dist/react/runtimes/external-message-converter.js +33 -233
  90. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  91. package/dist/runtime/api/composer-runtime.d.ts +1 -1
  92. package/dist/runtime/api/composer-runtime.js.map +1 -1
  93. package/dist/runtime/api/thread-runtime.d.ts +1 -2
  94. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  95. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  96. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  97. package/dist/runtime/base/base-composer-runtime-core.js +14 -29
  98. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  99. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -4
  100. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  101. package/dist/runtime/base/base-thread-runtime-core.js +3 -9
  102. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  103. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  104. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  105. package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
  106. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  107. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
  108. package/dist/runtime/interfaces/thread-runtime-core.d.ts +1 -1
  109. package/dist/runtime/internal.d.ts +2 -1
  110. package/dist/runtime/internal.js +2 -1
  111. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
  112. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  113. package/dist/runtime/queue/message-queue.js +1 -1
  114. package/dist/runtime/queue/message-queue.js.map +1 -1
  115. package/dist/runtime/queue/queue-item.d.ts +12 -0
  116. package/dist/runtime/queue/queue-item.d.ts.map +1 -0
  117. package/dist/runtime/queue/queue-item.js +6 -0
  118. package/dist/runtime/queue/queue-item.js.map +1 -0
  119. package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
  120. package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
  121. package/dist/runtime/utils/abortable-thread-load.js +35 -0
  122. package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
  123. package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
  124. package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
  125. package/dist/runtime/utils/attachment-add-operations.js +43 -0
  126. package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
  127. package/dist/runtime/utils/auto-status.d.ts +6 -1
  128. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  129. package/dist/runtime/utils/auto-status.js +7 -1
  130. package/dist/runtime/utils/auto-status.js.map +1 -1
  131. package/dist/runtime/utils/external-message-conversion.d.ts +47 -0
  132. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
  133. package/dist/runtime/utils/external-message-conversion.js +221 -0
  134. package/dist/runtime/utils/external-message-conversion.js.map +1 -0
  135. package/dist/runtime/utils/external-store-message.d.ts +2 -1
  136. package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
  137. package/dist/runtime/utils/external-store-message.js +2 -1
  138. package/dist/runtime/utils/external-store-message.js.map +1 -1
  139. package/dist/runtime/utils/message-repository-session.d.ts +30 -0
  140. package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
  141. package/dist/runtime/utils/message-repository-session.js +130 -0
  142. package/dist/runtime/utils/message-repository-session.js.map +1 -0
  143. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  144. package/dist/runtime/utils/message-repository.js +30 -22
  145. package/dist/runtime/utils/message-repository.js.map +1 -1
  146. package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
  147. package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
  148. package/dist/runtime/utils/pending-tool-calls.js +22 -0
  149. package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
  150. package/dist/runtime/utils/thread-message-like.d.ts +2 -1
  151. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  152. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  153. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
  154. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  155. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +3 -10
  156. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  157. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +19 -1
  158. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  159. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +124 -20
  160. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  161. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +102 -0
  162. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -0
  163. package/dist/runtimes/inert/InertThreadRuntimeCore.js +156 -0
  164. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -0
  165. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -1
  166. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  167. package/dist/runtimes/local/local-thread-runtime-core.js +45 -8
  168. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  169. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +12 -60
  170. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  171. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +10 -145
  172. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  173. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts +9 -0
  174. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts.map +1 -0
  175. package/dist/runtimes/remote-thread-list/adapter-changed.js +13 -0
  176. package/dist/runtimes/remote-thread-list/adapter-changed.js.map +1 -0
  177. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  178. package/dist/runtimes/remote-thread-list/empty-thread-core.js +16 -155
  179. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  180. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +2 -0
  181. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  182. package/dist/runtimes/remote-thread-list/optimistic-state.js +11 -0
  183. package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
  184. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
  185. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  186. package/dist/runtimes/remote-thread-list/remote-thread-state.js +77 -1
  187. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  188. package/dist/runtimes/remote-thread-list/types.d.ts +3 -0
  189. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  190. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  191. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -0
  192. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  193. package/dist/store/clients/external-thread.d.ts.map +1 -1
  194. package/dist/store/clients/external-thread.js +75 -100
  195. package/dist/store/clients/external-thread.js.map +1 -1
  196. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  197. package/dist/store/clients/single-thread-list.js +1 -1
  198. package/dist/store/clients/single-thread-list.js.map +1 -1
  199. package/dist/store/clients/suggestions.d.ts.map +1 -1
  200. package/dist/store/clients/suggestions.js +16 -7
  201. package/dist/store/clients/suggestions.js.map +1 -1
  202. package/dist/store/index.d.ts +4 -2
  203. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -1
  204. package/dist/store/runtime-clients/handle-runtime-action.js +2 -0
  205. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -1
  206. package/dist/store/scope-registration.d.ts +1 -1
  207. package/dist/store/scopes/composer.d.ts +2 -1
  208. package/dist/store/scopes/composer.d.ts.map +1 -1
  209. package/dist/store/scopes/message.d.ts +1 -1
  210. package/dist/store/scopes/queue-item.d.ts +2 -9
  211. package/dist/store/scopes/queue-item.d.ts.map +1 -1
  212. package/dist/store/scopes/queue-item.js +0 -6
  213. package/dist/types/message.d.ts +8 -4
  214. package/dist/types/message.d.ts.map +1 -1
  215. package/dist/types/message.js.map +1 -1
  216. package/dist/utils/data-url.d.ts +14 -1
  217. package/dist/utils/data-url.d.ts.map +1 -1
  218. package/dist/utils/data-url.js +13 -1
  219. package/dist/utils/data-url.js.map +1 -1
  220. package/dist/utils/invoke-user-callback.d.ts +5 -0
  221. package/dist/utils/invoke-user-callback.d.ts.map +1 -0
  222. package/dist/utils/invoke-user-callback.js +19 -0
  223. package/dist/utils/invoke-user-callback.js.map +1 -0
  224. package/dist/utils/normalizePartStatus.js.map +1 -1
  225. package/dist/utils/silent-runtime-action.d.ts +6 -0
  226. package/dist/utils/silent-runtime-action.d.ts.map +1 -0
  227. package/dist/utils/silent-runtime-action.js +7 -0
  228. package/dist/utils/silent-runtime-action.js.map +1 -0
  229. package/package.json +10 -10
  230. package/src/adapters/voice.test.ts +66 -1
  231. package/src/adapters/voice.ts +8 -0
  232. package/src/internal.ts +13 -0
  233. package/src/model-context/frame/host.test.ts +152 -9
  234. package/src/model-context/frame/host.ts +23 -5
  235. package/src/model-context/frame/provider.test.ts +601 -19
  236. package/src/model-context/frame/provider.ts +158 -20
  237. package/src/model-context/frame/types.ts +5 -0
  238. package/src/model-context/interactable-composer-metadata.ts +1 -1
  239. package/src/model-context/registry.test.ts +44 -0
  240. package/src/model-context/registry.ts +13 -35
  241. package/src/react/client/InMemoryThreadList.test.tsx +49 -0
  242. package/src/react/client/InMemoryThreadList.ts +7 -1
  243. package/src/react/client/Interactables.ts +19 -178
  244. package/src/react/client/RemoteThreadList.test.ts +292 -0
  245. package/src/react/client/RemoteThreadList.ts +119 -82
  246. package/src/react/interactables-legacy/Interactables.ts +20 -177
  247. package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
  248. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
  249. package/src/react/primitive-hooks/useActionBarCopy.test.ts +15 -0
  250. package/src/react/primitive-hooks/useActionBarCopy.ts +13 -3
  251. package/src/react/primitive-hooks/useThreadIsEmpty.ts +3 -0
  252. package/src/react/primitive-hooks/useThreadIsRunning.ts +3 -0
  253. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +12 -0
  254. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.republish.test.ts +87 -0
  255. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +251 -76
  256. package/src/react/runtimes/RemoteThreadResource.ts +2 -0
  257. package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
  258. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +58 -0
  259. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +3 -1
  260. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +92 -0
  261. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +40 -141
  262. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
  263. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
  264. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +98 -3
  265. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +68 -6
  266. package/src/react/runtimes/external-message-converter.test.ts +0 -14
  267. package/src/react/runtimes/external-message-converter.test.tsx +77 -0
  268. package/src/react/runtimes/external-message-converter.ts +58 -481
  269. package/src/react/runtimes/useLocalRuntime.test.tsx +5 -5
  270. package/src/runtime/api/composer-runtime.ts +1 -1
  271. package/src/runtime/api/message-runtime.test.ts +28 -1
  272. package/src/runtime/base/base-composer-runtime-core.ts +20 -40
  273. package/src/runtime/base/base-thread-runtime-core.test.ts +16 -0
  274. package/src/runtime/base/base-thread-runtime-core.ts +6 -11
  275. package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
  276. package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
  277. package/src/runtime/interfaces/thread-runtime-core.ts +1 -1
  278. package/src/runtime/internal.ts +5 -0
  279. package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
  280. package/src/runtime/queue/message-queue.ts +1 -4
  281. package/src/runtime/queue/queue-item.ts +10 -0
  282. package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
  283. package/src/runtime/utils/abortable-thread-load.ts +53 -0
  284. package/src/runtime/utils/attachment-add-operations.ts +59 -0
  285. package/src/runtime/utils/auto-status.ts +33 -0
  286. package/src/runtime/utils/external-message-conversion.test.ts +113 -0
  287. package/src/runtime/utils/external-message-conversion.ts +494 -0
  288. package/src/runtime/utils/external-store-message.ts +2 -0
  289. package/src/runtime/utils/message-repository-session.test.ts +195 -0
  290. package/src/runtime/utils/message-repository-session.ts +176 -0
  291. package/src/runtime/utils/message-repository.ts +33 -22
  292. package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
  293. package/src/runtime/utils/pending-tool-calls.ts +35 -0
  294. package/src/runtime/utils/thread-message-like.ts +2 -0
  295. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
  296. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +179 -28
  297. package/src/runtimes/inert/InertThreadRuntimeCore.test.ts +253 -0
  298. package/src/runtimes/inert/InertThreadRuntimeCore.ts +232 -0
  299. package/src/runtimes/local/local-thread-runtime-core.test.ts +491 -1
  300. package/src/runtimes/local/local-thread-runtime-core.ts +86 -11
  301. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +17 -209
  302. package/src/runtimes/remote-thread-list/adapter-changed.ts +10 -0
  303. package/src/runtimes/remote-thread-list/empty-thread-core.ts +28 -221
  304. package/src/runtimes/remote-thread-list/optimistic-state.test.ts +18 -0
  305. package/src/runtimes/remote-thread-list/optimistic-state.ts +13 -0
  306. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +37 -0
  307. package/src/runtimes/remote-thread-list/remote-thread-state.ts +123 -0
  308. package/src/runtimes/remote-thread-list/types.ts +4 -0
  309. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -0
  310. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +140 -0
  311. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +3 -0
  312. package/src/store/clients/external-thread.ts +17 -62
  313. package/src/store/clients/single-thread-list.ts +4 -1
  314. package/src/store/clients/suggestions.test.ts +31 -0
  315. package/src/store/clients/suggestions.ts +18 -19
  316. package/src/store/runtime-clients/handle-runtime-action.test.ts +40 -0
  317. package/src/store/runtime-clients/handle-runtime-action.ts +3 -0
  318. package/src/store/scopes/message.ts +1 -1
  319. package/src/store/scopes/queue-item.ts +2 -9
  320. package/src/tests/MessageRepository.test.ts +125 -0
  321. package/src/tests/RemoteThreadListThreadListRuntimeCore-errors.test.ts +2 -2
  322. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +194 -0
  323. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +8 -6
  324. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
  325. package/src/tests/empty-thread-core.test.ts +6 -0
  326. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +184 -0
  327. package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
  328. package/src/tests/external-thread-attachments.test.tsx +173 -0
  329. package/src/tests/external-thread-isEmpty.test.tsx +100 -0
  330. package/src/tests/remote-thread-list-adapter-switch.test.ts +383 -0
  331. package/src/tests/single-thread-list-item.test.tsx +47 -0
  332. package/src/tests/thread-message-like.test.ts +39 -1
  333. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +67 -0
  334. package/src/types/message.ts +9 -4
  335. package/src/utils/data-url.test.ts +52 -1
  336. package/src/utils/data-url.ts +21 -0
  337. package/src/utils/invoke-user-callback.test.ts +82 -0
  338. package/src/utils/invoke-user-callback.ts +31 -0
  339. package/src/utils/normalizePartStatus.ts +1 -1
  340. package/src/utils/silent-runtime-action.ts +6 -0
  341. package/dist/store/scopes/queue-item.js.map +0 -1
@@ -1,5 +1,4 @@
1
1
  import type { ThreadListRuntimeCore } from "../../runtime/interfaces/thread-list-runtime-core";
2
- import { generateId } from "../../utils/id";
3
2
  import { BaseSubscribable } from "../../subscribable/subscribable";
4
3
  import { OptimisticState } from "../../runtimes/remote-thread-list/optimistic-state";
5
4
  import { EMPTY_THREAD_CORE } from "../../runtimes/remote-thread-list/empty-thread-core";
@@ -9,15 +8,21 @@ import type {
9
8
  } from "../../runtimes/remote-thread-list/remote-thread-state";
10
9
  import {
11
10
  classifyThreads,
11
+ createEmptyRemoteThreadState,
12
12
  createThreadMappingId,
13
13
  getThreadData,
14
14
  normalizeCursor,
15
15
  updateStatusReducer,
16
+ preserveMidLoadTransitions,
17
+ seedNewThread,
18
+ statusSnapshot,
16
19
  } from "../../runtimes/remote-thread-list/remote-thread-state";
17
20
  import type {
18
21
  RemoteThreadListAdapter,
19
22
  RemoteThreadListOptions,
23
+ RemoteThreadMetadata,
20
24
  } from "../../runtimes/remote-thread-list/types";
25
+ import { ThreadListAdapterChangedError } from "../../runtimes/remote-thread-list/adapter-changed";
21
26
  import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
22
27
  import { isTitleSourceMessage } from "./RemoteThreadResource";
23
28
  import {
@@ -47,6 +52,8 @@ const threadStatusError = (
47
52
  `Thread "${threadIdOrRemoteId}" has status "${status}", so it cannot ${action}.`,
48
53
  );
49
54
 
55
+ const EMPTY_REMOTE_STATE = createEmptyRemoteThreadState();
56
+
50
57
  export class RemoteThreadListThreadListRuntimeCore
51
58
  extends BaseSubscribable
52
59
  implements ThreadListRuntimeCore
@@ -58,20 +65,16 @@ export class RemoteThreadListThreadListRuntimeCore
58
65
  private _loadThreadsPromise: Promise<void> | undefined;
59
66
  private _loadMorePromise: Promise<void> | undefined;
60
67
  private _loadGeneration = 0;
68
+ private _adapterGeneration = 0;
69
+ private _replaceListOnNextLoad = false;
70
+ private _staleThreadIdsOnReplace: ReadonlySet<string> | undefined;
61
71
  private _switchGeneration = 0;
62
72
  private _switchTask: Promise<void> | undefined;
63
73
 
64
74
  private _mainThreadId!: string;
65
- private readonly _state = new OptimisticState<RemoteThreadState>({
66
- isLoading: true,
67
- isLoadingMore: false,
68
- cursor: undefined,
69
- newThreadId: undefined,
70
- threadIds: [],
71
- archivedThreadIds: [],
72
- threadIdMap: {},
73
- threadData: {},
74
- });
75
+ private readonly _state = new OptimisticState<RemoteThreadState>(
76
+ EMPTY_REMOTE_STATE,
77
+ );
75
78
 
76
79
  private readonly _useAdaptersProvider: FC<PropsWithChildren> = ({
77
80
  children,
@@ -117,6 +120,8 @@ export class RemoteThreadListThreadListRuntimeCore
117
120
  // TODO this needs to be cached in case this promise is loaded during suspense
118
121
  if (!this._loadThreadsPromise) {
119
122
  const generation = this._loadGeneration;
123
+ let replacedList = false;
124
+ const statusAtRequest = statusSnapshot(this._state.baseValue);
120
125
  this._loadThreadsPromise = this._state
121
126
  .optimisticUpdate({
122
127
  execute: () => this._options.adapter.list(),
@@ -128,40 +133,59 @@ export class RemoteThreadListThreadListRuntimeCore
128
133
  },
129
134
  then: (state, l) => {
130
135
  if (generation !== this._loadGeneration) return state;
136
+ const replaceList = this._replaceListOnNextLoad;
137
+ if (replaceList) {
138
+ this._replaceListOnNextLoad = false;
139
+ replacedList = true;
140
+ return this._replaceWithThreads(
141
+ state,
142
+ l.threads,
143
+ normalizeCursor(l.nextCursor),
144
+ );
145
+ }
146
+
131
147
  const fresh = classifyThreads(l.threads, {
132
148
  threadIds: [],
133
149
  archivedThreadIds: [],
134
150
  threadIdMap: {},
135
151
  threadData: {},
136
152
  });
137
-
138
- return {
153
+ const merged = {
139
154
  ...state,
140
155
  isLoading: false,
141
156
  cursor: normalizeCursor(l.nextCursor),
142
157
  threadIds: fresh.threadIds,
143
158
  archivedThreadIds: fresh.archivedThreadIds,
144
- threadIdMap: {
145
- ...state.threadIdMap,
146
- ...fresh.threadIdMap,
147
- },
148
- threadData: {
149
- ...state.threadData,
150
- ...fresh.threadData,
151
- },
159
+ threadIdMap: { ...state.threadIdMap, ...fresh.threadIdMap },
160
+ threadData: { ...state.threadData, ...fresh.threadData },
152
161
  };
162
+ return preserveMidLoadTransitions(merged, state, statusAtRequest);
153
163
  },
154
164
  })
155
165
  .catch((error: unknown) => {
156
166
  if (generation !== this._loadGeneration) return;
157
167
  console.error("[assistant-ui] thread list load failed:", error);
158
168
  this._loadThreadsPromise = undefined;
159
- this._state.update({
160
- ...this._state.baseValue,
161
- isLoading: false,
162
- });
169
+ if (!this._replaceListOnNextLoad) {
170
+ this._state.update({
171
+ ...this._state.baseValue,
172
+ isLoading: false,
173
+ });
174
+ return;
175
+ }
176
+ this._replaceListOnNextLoad = false;
177
+ replacedList = true;
178
+ this._state.update(
179
+ this._replaceWithThreads(this._state.baseValue, [], undefined),
180
+ );
163
181
  })
164
- .then(() => {});
182
+ .then(() => {
183
+ if (!replacedList) return;
184
+ const threadId = this._options.threadId;
185
+ if (threadId === undefined) return;
186
+ if (this.getItemById(threadId)?.id === this._mainThreadId) return;
187
+ this._switchToThreadFromProp(threadId).catch(() => {});
188
+ });
165
189
  }
166
190
 
167
191
  return this._loadThreadsPromise;
@@ -206,6 +230,7 @@ export class RemoteThreadListThreadListRuntimeCore
206
230
  },
207
231
  })
208
232
  .catch((error: unknown) => {
233
+ if (generation !== this._loadGeneration) return;
209
234
  console.error("[assistant-ui] thread list loadMore failed:", error);
210
235
  })
211
236
  .then(() => {
@@ -237,6 +262,11 @@ export class RemoteThreadListThreadListRuntimeCore
237
262
  this._hookManager.__internal_subscribeRunningChanged(() =>
238
263
  this._notifySubscribers(),
239
264
  );
265
+ this._hookManager.__internal_subscribeRuntimeReplaced(() => {
266
+ // A republish can land during the thread resource's render, where a
267
+ // synchronous notify would re-enter store consumers mid-render.
268
+ queueMicrotask(() => this._notifySubscribers());
269
+ });
240
270
  this.useProvider = create(() => ({
241
271
  Provider: this.resolveProvider(options.adapter),
242
272
  }));
@@ -268,8 +298,17 @@ export class RemoteThreadListThreadListRuntimeCore
268
298
 
269
299
  if (adapterChanged) {
270
300
  this._loadGeneration++;
301
+ this._adapterGeneration++;
302
+ this._switchGeneration++;
303
+ this._switchTask = undefined;
271
304
  this._loadThreadsPromise = undefined;
272
305
  this._loadMorePromise = undefined;
306
+ this._replaceListOnNextLoad = true;
307
+ this._staleThreadIdsOnReplace = new Set(
308
+ Object.values(this._state.baseValue.threadData)
309
+ .filter((item) => item.status !== "new")
310
+ .map((item) => item.id),
311
+ );
273
312
  this._state.update({
274
313
  ...this._state.baseValue,
275
314
  cursor: undefined,
@@ -281,6 +320,115 @@ export class RemoteThreadListThreadListRuntimeCore
281
320
  }
282
321
  }
283
322
 
323
+ private _requireAdapterGeneration(generation: number) {
324
+ if (generation !== this._adapterGeneration) {
325
+ throw new ThreadListAdapterChangedError();
326
+ }
327
+ }
328
+
329
+ private _requireAdapterSettled() {
330
+ if (this._replaceListOnNextLoad) {
331
+ throw new ThreadListAdapterChangedError();
332
+ }
333
+ }
334
+
335
+ private _replaceWithThreads(
336
+ state: RemoteThreadState,
337
+ threads: readonly RemoteThreadMetadata[],
338
+ cursor: string | undefined,
339
+ ): RemoteThreadState {
340
+ const fresh = classifyThreads(threads, {
341
+ threadIds: [],
342
+ archivedThreadIds: [],
343
+ threadIdMap: {},
344
+ threadData: {},
345
+ });
346
+ const threadIdMap = { ...fresh.threadIdMap };
347
+ const threadData = { ...fresh.threadData };
348
+ const threadIds = [...fresh.threadIds];
349
+ const archivedThreadIds = [...fresh.archivedThreadIds];
350
+
351
+ if (state.newThreadId) {
352
+ const mappingId = state.threadIdMap[state.newThreadId];
353
+ const draft = mappingId ? state.threadData[mappingId] : undefined;
354
+ if (draft?.status === "new") {
355
+ threadIdMap[state.newThreadId] = mappingId!;
356
+ threadData[mappingId!] = draft;
357
+ }
358
+ }
359
+
360
+ const stale = this._staleThreadIdsOnReplace;
361
+ if (stale) {
362
+ for (const item of Object.values(state.threadData)) {
363
+ if (stale.has(item.id)) continue;
364
+ if (item.status !== "new" && item.remoteId === undefined) continue;
365
+ const mappingId = createThreadMappingId(item.id);
366
+ if (threadData[mappingId]) continue;
367
+ threadIdMap[item.id] = mappingId;
368
+ if (item.remoteId !== undefined) {
369
+ threadIdMap[item.remoteId] = mappingId;
370
+ }
371
+ threadData[mappingId] = item;
372
+ if (item.remoteId === undefined) continue;
373
+ if (item.status === "regular" && !threadIds.includes(item.remoteId)) {
374
+ threadIds.push(item.remoteId);
375
+ } else if (
376
+ item.status === "archived" &&
377
+ !archivedThreadIds.includes(item.remoteId)
378
+ ) {
379
+ archivedThreadIds.push(item.remoteId);
380
+ }
381
+ }
382
+ }
383
+ this._staleThreadIdsOnReplace = undefined;
384
+
385
+ let nextState: RemoteThreadState = {
386
+ ...state,
387
+ isLoading: false,
388
+ cursor,
389
+ threadIds,
390
+ archivedThreadIds,
391
+ threadIdMap,
392
+ threadData,
393
+ newThreadId:
394
+ state.newThreadId !== undefined &&
395
+ threadIdMap[state.newThreadId] === undefined
396
+ ? undefined
397
+ : state.newThreadId,
398
+ };
399
+
400
+ if (getThreadData(nextState, this._mainThreadId) === undefined) {
401
+ const preservedDraft = nextState.newThreadId;
402
+ if (preservedDraft !== undefined) {
403
+ this._mainThreadId = preservedDraft;
404
+ } else {
405
+ const seeded = seedNewThread(nextState);
406
+ this._mainThreadId = seeded.id;
407
+ nextState = seeded.state;
408
+ }
409
+ if (this._options.threadId === undefined) {
410
+ this._notifyThreadIdChange();
411
+ } else {
412
+ this._lastNotifiedThreadId = undefined;
413
+ }
414
+ }
415
+
416
+ const nextIds = new Set(
417
+ Object.values(nextState.threadData).map((item) => item.id),
418
+ );
419
+ for (const item of Object.values(state.threadData)) {
420
+ if (!nextIds.has(item.id)) {
421
+ this._hookManager.stopThreadRuntime(item.id);
422
+ }
423
+ }
424
+ void this._hookManager.startThreadRuntime(this._mainThreadId).then(
425
+ () => this._notifySubscribers(),
426
+ () => undefined,
427
+ );
428
+
429
+ return nextState;
430
+ }
431
+
284
432
  public __internal_load() {
285
433
  this.getLoadThreadsPromise(); // begin loading on initial bind
286
434
  if (this._initialThreadLoaded) return;
@@ -440,6 +588,12 @@ export class RemoteThreadListThreadListRuntimeCore
440
588
  generation: number,
441
589
  emitThreadIdChange: boolean,
442
590
  ): Promise<void> {
591
+ if (
592
+ this._replaceListOnNextLoad &&
593
+ threadIdOrRemoteId !== this._state.value.newThreadId
594
+ ) {
595
+ throw new ThreadListAdapterChangedError();
596
+ }
443
597
  let data = this.getItemById(threadIdOrRemoteId);
444
598
 
445
599
  if (!data) {
@@ -573,48 +727,32 @@ export class RemoteThreadListThreadListRuntimeCore
573
727
  const state = this._state.baseValue;
574
728
  let id: string | undefined = this._state.value.newThreadId;
575
729
  if (id === undefined) {
576
- do {
577
- id = `__LOCALID_${generateId()}`;
578
- } while (state.threadIdMap[id]);
579
-
580
- const mappingId = createThreadMappingId(id);
581
- this._state.update({
582
- ...state,
583
- newThreadId: id,
584
- threadIdMap: {
585
- ...state.threadIdMap,
586
- [id]: mappingId,
587
- },
588
- threadData: {
589
- ...state.threadData,
590
- [mappingId]: {
591
- status: "new",
592
- id,
593
- remoteId: undefined,
594
- externalId: undefined,
595
- title: undefined,
596
- custom: undefined,
597
- } satisfies RemoteThreadData,
598
- },
599
- });
730
+ const next = seedNewThread(state);
731
+ id = next.id;
732
+ this._state.update(next.state);
600
733
  }
601
734
 
602
735
  return this._switchToThread(id, undefined, generation, emitThreadIdChange);
603
736
  }
604
737
 
605
738
  public initialize = async (threadId: string) => {
739
+ const adapter = this._options.adapter;
740
+ const adapterGeneration = this._adapterGeneration;
606
741
  if (this._state.value.newThreadId !== threadId) {
742
+ this._requireAdapterSettled();
607
743
  const data = this.getItemById(threadId);
608
744
  if (!data) throw threadNotFoundError(threadId, "initializing it");
609
745
  if (data.status === "new")
610
746
  throw threadStatusError(threadId, data.status, "be initialized here");
611
747
  const { remoteId, externalId } = await data.initializeTask;
748
+ this._requireAdapterGeneration(adapterGeneration);
612
749
  return { remoteId, externalId };
613
750
  }
614
751
 
615
752
  const { remoteId, externalId } = await this._state.optimisticUpdate({
616
753
  execute: () => {
617
- return this._options.adapter.initialize(threadId);
754
+ this._requireAdapterGeneration(adapterGeneration);
755
+ return adapter.initialize(threadId);
618
756
  },
619
757
  optimistic: (state) => {
620
758
  return updateStatusReducer(state, threadId, "regular");
@@ -633,6 +771,7 @@ export class RemoteThreadListThreadListRuntimeCore
633
771
  };
634
772
  },
635
773
  then: (state, { remoteId, externalId }) => {
774
+ if (adapterGeneration !== this._adapterGeneration) return state;
636
775
  const data = getThreadData(state, threadId);
637
776
  if (!data) return state;
638
777
 
@@ -655,16 +794,21 @@ export class RemoteThreadListThreadListRuntimeCore
655
794
  };
656
795
  },
657
796
  });
797
+ this._requireAdapterGeneration(adapterGeneration);
658
798
  return { remoteId, externalId };
659
799
  };
660
800
 
661
801
  public generateTitle = async (threadId: string) => {
802
+ this._requireAdapterSettled();
803
+ const adapter = this._options.adapter;
804
+ const adapterGeneration = this._adapterGeneration;
662
805
  const data = this.getItemById(threadId);
663
806
  if (!data) throw threadNotFoundError(threadId, "generating its title");
664
807
  if (data.status === "new")
665
808
  throw threadStatusError(threadId, data.status, "generate a title");
666
809
 
667
810
  const { remoteId } = await data.initializeTask;
811
+ this._requireAdapterGeneration(adapterGeneration);
668
812
 
669
813
  const runtimeCore = this._hookManager.getThreadRuntimeCore(data.id);
670
814
  if (!runtimeCore) return; // thread is no longer running
@@ -673,30 +817,39 @@ export class RemoteThreadListThreadListRuntimeCore
673
817
  // would make the payload race-dependent; the title reads settled
674
818
  // messages only, matching the trigger's readiness gate.
675
819
  const messages = runtimeCore.messages.filter(isTitleSourceMessage);
676
- const stream = await this._options.adapter.generateTitle(
677
- remoteId,
678
- messages,
679
- );
820
+ const stream = await adapter.generateTitle(remoteId, messages);
680
821
  const messageStream = AssistantMessageStream.fromAssistantStream(stream);
681
822
  for await (const result of messageStream) {
823
+ if (adapterGeneration !== this._adapterGeneration) return;
682
824
  const newTitle = result.parts.filter((c) => c.type === "text")[0]?.text;
683
- const state = this._state.baseValue;
684
- const currentData = getThreadData(state, data.id);
685
- if (!currentData) continue;
686
- this._state.update({
687
- ...state,
688
- threadData: {
689
- ...state.threadData,
690
- [currentData.id]: {
691
- ...currentData,
692
- title: newTitle,
693
- },
825
+ await this._state.optimisticUpdate({
826
+ execute: async () => {},
827
+ optimistic: (state) => {
828
+ if (adapterGeneration !== this._adapterGeneration) return state;
829
+ const currentData = getThreadData(state, data.id);
830
+ if (!currentData) return state;
831
+ return {
832
+ ...state,
833
+ threadData: {
834
+ ...state.threadData,
835
+ [currentData.id]: {
836
+ ...currentData,
837
+ title: newTitle,
838
+ },
839
+ },
840
+ };
694
841
  },
695
842
  });
696
843
  }
697
844
  };
698
845
 
699
- public rename(threadIdOrRemoteId: string, newTitle: string): Promise<void> {
846
+ public async rename(
847
+ threadIdOrRemoteId: string,
848
+ newTitle: string,
849
+ ): Promise<void> {
850
+ this._requireAdapterSettled();
851
+ const adapter = this._options.adapter;
852
+ const adapterGeneration = this._adapterGeneration;
700
853
  const data = this.getItemById(threadIdOrRemoteId);
701
854
  if (!data) throw threadNotFoundError(threadIdOrRemoteId, "renaming it");
702
855
  if (data.status === "new")
@@ -705,7 +858,8 @@ export class RemoteThreadListThreadListRuntimeCore
705
858
  return this._state.optimisticUpdate({
706
859
  execute: async () => {
707
860
  const { remoteId } = await data.initializeTask;
708
- return this._options.adapter.rename(remoteId, newTitle);
861
+ this._requireAdapterGeneration(adapterGeneration);
862
+ return adapter.rename(remoteId, newTitle);
709
863
  },
710
864
  optimistic: (state) => {
711
865
  const data = getThreadData(state, threadIdOrRemoteId);
@@ -725,10 +879,13 @@ export class RemoteThreadListThreadListRuntimeCore
725
879
  });
726
880
  }
727
881
 
728
- public updateCustom(
882
+ public async updateCustom(
729
883
  threadIdOrRemoteId: string,
730
884
  custom: Record<string, unknown> | undefined,
731
885
  ): Promise<void> {
886
+ this._requireAdapterSettled();
887
+ const adapter = this._options.adapter;
888
+ const adapterGeneration = this._adapterGeneration;
732
889
  const data = this.getItemById(threadIdOrRemoteId);
733
890
  if (!data)
734
891
  throw threadNotFoundError(
@@ -742,7 +899,7 @@ export class RemoteThreadListThreadListRuntimeCore
742
899
  "update custom metadata",
743
900
  );
744
901
 
745
- if (!this._options.adapter.updateCustom) {
902
+ if (!adapter.updateCustom) {
746
903
  throw new Error(
747
904
  "Remote thread list adapter does not support updating custom metadata",
748
905
  );
@@ -751,7 +908,7 @@ export class RemoteThreadListThreadListRuntimeCore
751
908
  return this._state.optimisticUpdate({
752
909
  execute: async () => {
753
910
  const { remoteId } = await data.initializeTask;
754
- const adapter = this._options.adapter;
911
+ this._requireAdapterGeneration(adapterGeneration);
755
912
  if (!adapter.updateCustom) {
756
913
  throw new Error(
757
914
  "Remote thread list adapter does not support updating custom metadata",
@@ -800,17 +957,22 @@ export class RemoteThreadListThreadListRuntimeCore
800
957
  }
801
958
 
802
959
  public async archive(threadIdOrRemoteId: string) {
960
+ this._requireAdapterSettled();
961
+ const adapter = this._options.adapter;
962
+ const adapterGeneration = this._adapterGeneration;
803
963
  const data = this.getItemById(threadIdOrRemoteId);
804
964
  if (!data) throw threadNotFoundError(threadIdOrRemoteId, "archiving it");
805
965
  if (data.status !== "regular")
806
966
  throw threadStatusError(threadIdOrRemoteId, data.status, "be archived");
807
967
 
808
968
  await this._ensureThreadIsNotMain(data.id);
969
+ this._requireAdapterGeneration(adapterGeneration);
809
970
 
810
971
  return this._state.optimisticUpdate({
811
972
  execute: async () => {
812
973
  const { remoteId } = await data.initializeTask;
813
- return this._options.adapter.archive(remoteId);
974
+ this._requireAdapterGeneration(adapterGeneration);
975
+ return adapter.archive(remoteId);
814
976
  },
815
977
  optimistic: (state) => {
816
978
  return updateStatusReducer(state, data.id, "archived");
@@ -818,7 +980,10 @@ export class RemoteThreadListThreadListRuntimeCore
818
980
  });
819
981
  }
820
982
 
821
- public unarchive(threadIdOrRemoteId: string): Promise<void> {
983
+ public async unarchive(threadIdOrRemoteId: string): Promise<void> {
984
+ this._requireAdapterSettled();
985
+ const adapter = this._options.adapter;
986
+ const adapterGeneration = this._adapterGeneration;
822
987
  const data = this.getItemById(threadIdOrRemoteId);
823
988
  if (!data) throw threadNotFoundError(threadIdOrRemoteId, "unarchiving it");
824
989
  if (data.status !== "archived")
@@ -828,9 +993,12 @@ export class RemoteThreadListThreadListRuntimeCore
828
993
  execute: async () => {
829
994
  try {
830
995
  const { remoteId } = await data.initializeTask;
831
- return await this._options.adapter.unarchive(remoteId);
996
+ this._requireAdapterGeneration(adapterGeneration);
997
+ return await adapter.unarchive(remoteId);
832
998
  } catch (error) {
833
- await this._ensureThreadIsNotMain(data.id);
999
+ if (adapterGeneration === this._adapterGeneration) {
1000
+ await this._ensureThreadIsNotMain(data.id);
1001
+ }
834
1002
  throw error;
835
1003
  }
836
1004
  },
@@ -841,18 +1009,23 @@ export class RemoteThreadListThreadListRuntimeCore
841
1009
  }
842
1010
 
843
1011
  public async delete(threadIdOrRemoteId: string) {
1012
+ this._requireAdapterSettled();
1013
+ const adapter = this._options.adapter;
1014
+ const adapterGeneration = this._adapterGeneration;
844
1015
  const data = this.getItemById(threadIdOrRemoteId);
845
1016
  if (!data) throw threadNotFoundError(threadIdOrRemoteId, "deleting it");
846
1017
  if (data.status !== "regular" && data.status !== "archived")
847
1018
  throw threadStatusError(threadIdOrRemoteId, data.status, "be deleted");
848
1019
 
849
1020
  await this._ensureThreadIsNotMain(data.id);
1021
+ this._requireAdapterGeneration(adapterGeneration);
850
1022
  this._hookManager.stopThreadRuntime(data.id);
851
1023
 
852
1024
  return this._state.optimisticUpdate({
853
1025
  execute: async () => {
854
1026
  const { remoteId } = await data.initializeTask;
855
- return await this._options.adapter.delete(remoteId);
1027
+ this._requireAdapterGeneration(adapterGeneration);
1028
+ return await adapter.delete(remoteId);
856
1029
  },
857
1030
  optimistic: (state) => {
858
1031
  return updateStatusReducer(state, data.id, "deleted");
@@ -865,12 +1038,14 @@ export class RemoteThreadListThreadListRuntimeCore
865
1038
  }
866
1039
 
867
1040
  public async detach(threadIdOrRemoteId: string): Promise<void> {
1041
+ const adapterGeneration = this._adapterGeneration;
868
1042
  const data = this.getItemById(threadIdOrRemoteId);
869
1043
  if (!data) throw threadNotFoundError(threadIdOrRemoteId, "detaching it");
870
1044
  if (data.status !== "regular" && data.status !== "archived")
871
1045
  throw threadStatusError(threadIdOrRemoteId, data.status, "be detached");
872
1046
 
873
1047
  await this._ensureThreadIsNotMain(data.id);
1048
+ this._requireAdapterGeneration(adapterGeneration);
874
1049
  this._hookManager.stopThreadRuntime(data.id);
875
1050
  }
876
1051
 
@@ -21,6 +21,7 @@ import {
21
21
  useRuntimeAdaptersProvider,
22
22
  type RuntimeAdapters,
23
23
  } from "./RuntimeAdapterProvider";
24
+ import { isSilentRuntimeAction } from "../../utils/silent-runtime-action";
24
25
 
25
26
  export type RemoteThreadListHook = () => AssistantRuntime;
26
27
 
@@ -48,6 +49,7 @@ export const subscribeToTitleGeneration = (
48
49
  ) => {
49
50
  const generate = () =>
50
51
  void itemRuntime.generateTitle().catch((error: unknown) => {
52
+ if (isSilentRuntimeAction(error)) return;
51
53
  console.error("[assistant-ui] Thread title generation failed", error);
52
54
  });
53
55
 
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { invokeUserCallback } from "../../../utils/invoke-user-callback";
2
3
  import { useLatestRef } from "./useLatestRef";
3
4
 
4
5
  export type RunManager = Readonly<{
@@ -11,25 +12,11 @@ const disposeReason = Symbol("assistant-transport-dispose");
11
12
 
12
13
  type LifecycleCallbackName = "onCancel" | "onError" | "onFinish";
13
14
 
14
- const reportCallbackError = (name: LifecycleCallbackName, error: unknown) => {
15
- console.error(
16
- `[assistant-ui] Assistant transport ${name} callback threw an error`,
17
- error,
18
- );
19
- };
20
-
21
- const invokeCallback = async (
15
+ const invokeCallback = (
22
16
  name: LifecycleCallbackName,
23
17
  callback: (() => unknown) | undefined,
24
- ): Promise<void> => {
25
- try {
26
- await callback?.();
27
- } catch (error) {
28
- try {
29
- reportCallbackError(name, error);
30
- } catch {}
31
- }
32
- };
18
+ ) =>
19
+ invokeUserCallback("assistant-ui", `Assistant transport ${name}`, callback);
33
20
 
34
21
  export function useRunManager(config: {
35
22
  onRun: (signal: AbortSignal) => Promise<void>;