@assistant-ui/core 0.3.17 → 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 (284) 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 +2 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal.d.ts +3 -2
  7. package/dist/internal.js +3 -2
  8. package/dist/model-context/frame/host.d.ts.map +1 -1
  9. package/dist/model-context/frame/host.js +10 -4
  10. package/dist/model-context/frame/host.js.map +1 -1
  11. package/dist/model-context/frame/provider.d.ts +2 -0
  12. package/dist/model-context/frame/provider.d.ts.map +1 -1
  13. package/dist/model-context/frame/provider.js +30 -7
  14. package/dist/model-context/frame/provider.js.map +1 -1
  15. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  16. package/dist/model-context/interactable-composer-metadata.js +16 -9
  17. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  18. package/dist/model-context/registry.d.ts.map +1 -1
  19. package/dist/model-context/registry.js +3 -5
  20. package/dist/model-context/registry.js.map +1 -1
  21. package/dist/model-context/types.d.ts.map +1 -1
  22. package/dist/model-context/types.js +5 -4
  23. package/dist/model-context/types.js.map +1 -1
  24. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  25. package/dist/react/client/InMemoryThreadList.js +62 -56
  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 +24 -27
  29. package/dist/react/client/Interactables.js.map +1 -1
  30. package/dist/react/client/RemoteThreadList.d.ts +1 -0
  31. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  32. package/dist/react/client/RemoteThreadList.js +151 -141
  33. package/dist/react/client/RemoteThreadList.js.map +1 -1
  34. package/dist/react/client/Tools.d.ts.map +1 -1
  35. package/dist/react/client/Tools.js +18 -16
  36. package/dist/react/client/Tools.js.map +1 -1
  37. package/dist/react/interactables-legacy/Interactables.js +242 -121
  38. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  39. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  40. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  41. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  42. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  43. package/dist/react/model-context/makeAssistantTool.js +1 -1
  44. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  45. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  46. package/dist/react/model-context/useAssistantTool.js +1 -1
  47. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  48. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  49. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  50. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  51. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  52. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +16 -8
  53. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  54. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -1
  55. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  56. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +103 -103
  57. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  58. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -0
  59. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  60. package/dist/react/runtimes/RemoteThreadResource.js +13 -11
  61. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  62. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +4 -1
  63. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  64. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +401 -34
  65. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  66. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
  67. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +2 -1
  68. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -1
  69. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  70. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  71. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  72. package/dist/react/runtimes/useRemoteThreadListRuntime.js +63 -23
  73. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  74. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  75. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  76. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  77. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  78. package/dist/runtime/api/thread-list-runtime.d.ts +3 -0
  79. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  80. package/dist/runtime/api/thread-list-runtime.js +18 -14
  81. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  82. package/dist/runtime/api/thread-runtime.d.ts +4 -0
  83. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  84. package/dist/runtime/api/thread-runtime.js +1 -1
  85. package/dist/runtime/api/thread-runtime.js.map +1 -1
  86. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  87. package/dist/runtime/base/base-composer-runtime-core.js +16 -12
  88. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  89. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  90. package/dist/runtime/base/base-thread-runtime-core.js +6 -5
  91. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  92. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  93. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  94. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +4 -1
  95. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  96. package/dist/runtime/utils/external-message-conversion.js +1 -1
  97. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  98. package/dist/runtime/utils/message-repository.d.ts +1 -0
  99. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  100. package/dist/runtime/utils/message-repository.js +15 -6
  101. package/dist/runtime/utils/message-repository.js.map +1 -1
  102. package/dist/runtime/utils/thread-message-like.js +1 -1
  103. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  104. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  105. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  106. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  107. package/dist/runtimes/internal.d.ts +2 -2
  108. package/dist/runtimes/internal.js +2 -2
  109. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  110. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  111. package/dist/runtimes/remote-thread-list/remote-thread-state.js +130 -42
  112. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  113. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  114. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  115. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  116. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  117. package/dist/runtimes/remote-thread-list/title.d.ts +3 -1
  118. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -1
  119. package/dist/runtimes/remote-thread-list/title.js +9 -1
  120. package/dist/runtimes/remote-thread-list/title.js.map +1 -1
  121. package/dist/runtimes/remote-thread-list/types.d.ts +10 -0
  122. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  123. package/dist/store/clients/external-thread.d.ts.map +1 -1
  124. package/dist/store/clients/external-thread.js +19 -3
  125. package/dist/store/clients/external-thread.js.map +1 -1
  126. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  127. package/dist/store/clients/model-context-client.js +2 -1
  128. package/dist/store/clients/model-context-client.js.map +1 -1
  129. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  130. package/dist/store/clients/single-thread-list.js +1 -0
  131. package/dist/store/clients/single-thread-list.js.map +1 -1
  132. package/dist/store/clients/suggestions.d.ts.map +1 -1
  133. package/dist/store/clients/suggestions.js +5 -4
  134. package/dist/store/clients/suggestions.js.map +1 -1
  135. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  136. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  137. package/dist/store/runtime-clients/composer-runtime-client.js +97 -61
  138. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  139. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  140. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  141. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  142. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  143. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  144. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  145. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  146. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  147. package/dist/store/runtime-clients/thread-list-runtime-client.js +54 -52
  148. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  149. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  150. package/dist/store/runtime-clients/thread-runtime-client.js +193 -121
  151. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  152. package/dist/store/scopes/composer.d.ts +10 -2
  153. package/dist/store/scopes/composer.d.ts.map +1 -1
  154. package/dist/store/scopes/message.d.ts +30 -1
  155. package/dist/store/scopes/message.d.ts.map +1 -1
  156. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  157. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  158. package/dist/store/scopes/thread.d.ts +8 -0
  159. package/dist/store/scopes/thread.d.ts.map +1 -1
  160. package/dist/store/scopes/threads.d.ts +1 -0
  161. package/dist/store/scopes/threads.d.ts.map +1 -1
  162. package/dist/types/attachment.d.ts +2 -1
  163. package/dist/types/attachment.d.ts.map +1 -1
  164. package/dist/types/attachment.js +2 -1
  165. package/dist/types/attachment.js.map +1 -1
  166. package/dist/utils/abortable-iterable.d.ts +19 -0
  167. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  168. package/dist/utils/abortable-iterable.js +72 -0
  169. package/dist/utils/abortable-iterable.js.map +1 -0
  170. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  171. package/dist/utils/composite-context-provider.js +20 -2
  172. package/dist/utils/composite-context-provider.js.map +1 -1
  173. package/dist/utils/record.d.ts +11 -0
  174. package/dist/utils/record.d.ts.map +1 -0
  175. package/dist/utils/record.js +12 -0
  176. package/dist/utils/record.js.map +1 -0
  177. package/package.json +10 -10
  178. package/src/adapters/attachment.test.ts +50 -1
  179. package/src/adapters/attachment.ts +7 -4
  180. package/src/index.ts +1 -0
  181. package/src/internal.ts +4 -0
  182. package/src/model-context/frame/host.test.ts +24 -0
  183. package/src/model-context/frame/host.ts +10 -4
  184. package/src/model-context/frame/provider.test.ts +71 -0
  185. package/src/model-context/frame/provider.ts +37 -7
  186. package/src/model-context/interactable-composer-metadata.test.ts +28 -0
  187. package/src/model-context/interactable-composer-metadata.ts +9 -3
  188. package/src/model-context/registry.test.ts +12 -0
  189. package/src/model-context/registry.ts +3 -2
  190. package/src/model-context/types.test.ts +25 -0
  191. package/src/model-context/types.ts +9 -4
  192. package/src/react/RuntimeAdapter.test.ts +34 -0
  193. package/src/react/client/InMemoryThreadList.ts +1 -0
  194. package/src/react/client/Interactables.test.ts +28 -0
  195. package/src/react/client/Interactables.ts +15 -13
  196. package/src/react/client/RemoteThreadList.background.test.ts +148 -6
  197. package/src/react/client/RemoteThreadList.concurrent.test.tsx +42 -1
  198. package/src/react/client/RemoteThreadList.test.ts +345 -3
  199. package/src/react/client/RemoteThreadList.ts +166 -110
  200. package/src/react/client/Tools.ts +22 -11
  201. package/src/react/interactables-legacy/Interactables.ts +16 -16
  202. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  203. package/src/react/model-context/makeAssistantTool.ts +2 -2
  204. package/src/react/model-context/useAssistantTool.ts +2 -2
  205. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  206. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  207. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  208. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +33 -0
  209. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  210. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +28 -5
  211. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  212. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +105 -0
  213. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +136 -111
  214. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  215. package/src/react/runtimes/RemoteThreadResource.ts +19 -12
  216. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +483 -6
  217. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +504 -68
  218. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +15 -0
  219. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +1 -0
  220. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  221. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  222. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  223. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  224. package/src/react/runtimes/useRemoteThreadListRuntime.ts +33 -0
  225. package/src/runtime/api/message-runtime.test.ts +75 -0
  226. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  227. package/src/runtime/api/thread-list-runtime.ts +21 -11
  228. package/src/runtime/api/thread-runtime.test.ts +78 -0
  229. package/src/runtime/api/thread-runtime.ts +8 -1
  230. package/src/runtime/base/base-composer-runtime-core.ts +36 -8
  231. package/src/runtime/base/base-thread-runtime-core.test.ts +55 -1
  232. package/src/runtime/base/base-thread-runtime-core.ts +6 -4
  233. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  234. package/src/runtime/interfaces/thread-list-runtime-core.ts +5 -1
  235. package/src/runtime/utils/external-message-conversion.test.ts +68 -0
  236. package/src/runtime/utils/external-message-conversion.ts +1 -1
  237. package/src/runtime/utils/message-repository.ts +29 -17
  238. package/src/runtime/utils/thread-message-like.ts +1 -1
  239. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  240. package/src/runtimes/internal.ts +1 -0
  241. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +128 -2
  242. package/src/runtimes/remote-thread-list/remote-thread-state.ts +182 -42
  243. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  244. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  245. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  246. package/src/runtimes/remote-thread-list/title.ts +13 -0
  247. package/src/runtimes/remote-thread-list/types.ts +10 -0
  248. package/src/store/clients/external-thread.ts +48 -3
  249. package/src/store/clients/model-context-client.ts +5 -1
  250. package/src/store/clients/single-thread-list.ts +1 -0
  251. package/src/store/clients/suggestions.test.ts +40 -0
  252. package/src/store/clients/suggestions.ts +13 -10
  253. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  254. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  255. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  256. package/src/store/runtime-clients/thread-list-runtime-client.ts +1 -0
  257. package/src/store/runtime-clients/thread-runtime-client.ts +58 -10
  258. package/src/store/scopes/composer.ts +16 -4
  259. package/src/store/scopes/message.ts +14 -0
  260. package/src/store/scopes/thread-list-item.ts +5 -2
  261. package/src/store/scopes/thread.ts +4 -0
  262. package/src/store/scopes/threads.ts +1 -0
  263. package/src/tests/MessageRepository.test.ts +116 -0
  264. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  265. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  266. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +47 -0
  267. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  268. package/src/tests/attachment-adapters.test.ts +14 -0
  269. package/src/tests/base-composer-runtime-core.test.ts +39 -0
  270. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  271. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +27 -18
  272. package/src/tests/external-thread-attachments.test.tsx +139 -0
  273. package/src/tests/external-thread-parity.test.tsx +23 -0
  274. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  275. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  276. package/src/tests/thread-switch-events.test.tsx +78 -0
  277. package/src/tests/tools-scope-migration.test.tsx +28 -0
  278. package/src/types/attachment.ts +4 -0
  279. package/src/utils/abortable-iterable.test.ts +213 -0
  280. package/src/utils/abortable-iterable.ts +93 -0
  281. package/src/utils/composite-context-provider.test.ts +25 -0
  282. package/src/utils/composite-context-provider.ts +23 -2
  283. package/src/utils/record.test.ts +48 -0
  284. package/src/utils/record.ts +11 -0
@@ -7,6 +7,7 @@ import {
7
7
  WritableSubscribable,
8
8
  } from "../../subscribable/subscribable";
9
9
  import { useSubscribable } from "../../store/runtime-clients/useSubscribable";
10
+ import { nullProtoRecord } from "../../utils/record";
10
11
  import { OptimisticState } from "../../runtimes/remote-thread-list/optimistic-state";
11
12
  import { EMPTY_THREAD_CORE } from "../../runtimes/remote-thread-list/empty-thread-core";
12
13
  import type {
@@ -20,6 +21,8 @@ import {
20
21
  createThreadMappingId,
21
22
  getThreadData,
22
23
  normalizeCursor,
24
+ reconcileInitializedThread,
25
+ promoteNewThreadReducer,
23
26
  updateStatusReducer,
24
27
  preserveMidLoadTransitions,
25
28
  seedNewThread,
@@ -32,7 +35,17 @@ import type {
32
35
  } from "../../runtimes/remote-thread-list/types";
33
36
  import { ThreadListAdapterChangedError } from "../../runtimes/remote-thread-list/adapter-changed";
34
37
  import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
35
- import { isTitleSourceMessage } from "../../runtimes/remote-thread-list/title";
38
+ import {
39
+ applyTitleStream,
40
+ isTitleSourceMessage,
41
+ } from "../../runtimes/remote-thread-list/title";
42
+ import {
43
+ clearThreadTitleState,
44
+ finishThreadTitleRename,
45
+ runThreadTitleGeneration,
46
+ startThreadTitleRename,
47
+ type ThreadTitleState,
48
+ } from "../../runtimes/remote-thread-list/title-generation";
36
49
  import {
37
50
  type ComponentType,
38
51
  type FC,
@@ -42,7 +55,6 @@ import {
42
55
  useId,
43
56
  } from "react";
44
57
  import { useAui } from "@assistant-ui/store";
45
- import { AssistantMessageStream } from "assistant-stream";
46
58
  import type { ModelContextProvider } from "../../model-context/types";
47
59
  import { RuntimeAdapterProvider } from "./RuntimeAdapterProvider";
48
60
  import { useStableRuntimeAdapters } from "./useRuntimeAdapters";
@@ -77,6 +89,7 @@ export class RemoteThreadListThreadListRuntimeCore
77
89
  private _staleThreadIdsOnReplace: ReadonlySet<string> | undefined;
78
90
  private _switchGeneration = 0;
79
91
  private _switchTask: Promise<void> | undefined;
92
+ private readonly _titleStates = new Map<string, ThreadTitleState>();
80
93
 
81
94
  private _mainThreadId!: string;
82
95
  private readonly _state = new OptimisticState<RemoteThreadState>(
@@ -137,6 +150,7 @@ export class RemoteThreadListThreadListRuntimeCore
137
150
  return {
138
151
  ...state,
139
152
  isLoading: true,
153
+ loadError: undefined,
140
154
  };
141
155
  },
142
156
  then: (state, l) => {
@@ -146,7 +160,7 @@ export class RemoteThreadListThreadListRuntimeCore
146
160
  this._replaceListOnNextLoad = false;
147
161
  replacedList = true;
148
162
  return this._replaceWithThreads(
149
- state,
163
+ { ...state, loadError: undefined },
150
164
  l.threads,
151
165
  normalizeCursor(l.nextCursor),
152
166
  );
@@ -161,6 +175,7 @@ export class RemoteThreadListThreadListRuntimeCore
161
175
  const merged = {
162
176
  ...state,
163
177
  isLoading: false,
178
+ loadError: undefined,
164
179
  cursor: normalizeCursor(l.nextCursor),
165
180
  threadIds: fresh.threadIds,
166
181
  archivedThreadIds: fresh.archivedThreadIds,
@@ -178,13 +193,18 @@ export class RemoteThreadListThreadListRuntimeCore
178
193
  this._state.update({
179
194
  ...this._state.baseValue,
180
195
  isLoading: false,
196
+ loadError: error,
181
197
  });
182
198
  return;
183
199
  }
184
200
  this._replaceListOnNextLoad = false;
185
201
  replacedList = true;
186
202
  this._state.update(
187
- this._replaceWithThreads(this._state.baseValue, [], undefined),
203
+ this._replaceWithThreads(
204
+ { ...this._state.baseValue, loadError: error },
205
+ [],
206
+ undefined,
207
+ ),
188
208
  );
189
209
  })
190
210
  .then(() => {
@@ -320,7 +340,9 @@ export class RemoteThreadListThreadListRuntimeCore
320
340
  this._state.update({
321
341
  ...this._state.baseValue,
322
342
  cursor: undefined,
343
+ loadError: undefined,
323
344
  });
345
+ this._titleStates.clear();
324
346
  }
325
347
 
326
348
  if (controlledThreadIdChanged) {
@@ -347,8 +369,13 @@ export class RemoteThreadListThreadListRuntimeCore
347
369
  ): RemoteThreadState {
348
370
  const carried: RemoteThreadData[] = [];
349
371
  if (state.newThreadId) {
350
- const mappingId = state.threadIdMap[state.newThreadId];
351
- const draft = mappingId ? state.threadData[mappingId] : undefined;
372
+ const mappingId = Object.hasOwn(state.threadIdMap, state.newThreadId)
373
+ ? state.threadIdMap[state.newThreadId]
374
+ : undefined;
375
+ const draft =
376
+ mappingId && Object.hasOwn(state.threadData, mappingId)
377
+ ? state.threadData[mappingId]
378
+ : undefined;
352
379
  if (draft?.status === "new") carried.push(draft);
353
380
  }
354
381
 
@@ -365,12 +392,12 @@ export class RemoteThreadListThreadListRuntimeCore
365
392
  const seed: ClassifyAccumulator = {
366
393
  threadIds: [],
367
394
  archivedThreadIds: [],
368
- threadIdMap: {},
369
- threadData: {},
395
+ threadIdMap: nullProtoRecord(),
396
+ threadData: nullProtoRecord(),
370
397
  };
371
398
  for (const item of carried) {
372
399
  const mappingId = createThreadMappingId(item.id);
373
- if (seed.threadData[mappingId]) continue;
400
+ if (Object.hasOwn(seed.threadData, mappingId)) continue;
374
401
  seed.threadIdMap[item.id] = mappingId;
375
402
  if (item.remoteId !== undefined) {
376
403
  seed.threadIdMap[item.remoteId] = mappingId;
@@ -383,7 +410,10 @@ export class RemoteThreadListThreadListRuntimeCore
383
410
 
384
411
  for (const item of carried) {
385
412
  if (item.remoteId === undefined) continue;
386
- const current = threadData[createThreadMappingId(item.id)];
413
+ const currentMappingId = createThreadMappingId(item.id);
414
+ const current = Object.hasOwn(threadData, currentMappingId)
415
+ ? threadData[currentMappingId]
416
+ : undefined;
387
417
  if (current === undefined) continue;
388
418
  if (current.status === "regular" && !threadIds.includes(current.id)) {
389
419
  threadIds.push(current.id);
@@ -405,7 +435,7 @@ export class RemoteThreadListThreadListRuntimeCore
405
435
  threadData,
406
436
  newThreadId:
407
437
  state.newThreadId !== undefined &&
408
- threadIdMap[state.newThreadId] === undefined
438
+ !Object.hasOwn(threadIdMap, state.newThreadId)
409
439
  ? undefined
410
440
  : state.newThreadId,
411
441
  };
@@ -501,6 +531,10 @@ export class RemoteThreadListThreadListRuntimeCore
501
531
  return this._state.value.isLoading;
502
532
  }
503
533
 
534
+ public get loadError() {
535
+ return this._state.value.loadError;
536
+ }
537
+
504
538
  public get isLoadingMore() {
505
539
  return this._state.value.isLoadingMore;
506
540
  }
@@ -778,71 +812,40 @@ export class RemoteThreadListThreadListRuntimeCore
778
812
  return { remoteId, externalId };
779
813
  }
780
814
 
815
+ this._requireAdapterGeneration(adapterGeneration);
816
+ const initializeTask = adapter.initialize(threadId);
817
+ let removedMappingId: string | undefined;
781
818
  const { remoteId, externalId } = await this._state.optimisticUpdate({
782
- execute: () => {
783
- this._requireAdapterGeneration(adapterGeneration);
784
- return adapter.initialize(threadId);
785
- },
786
- optimistic: (state) => {
787
- return updateStatusReducer(state, threadId, "regular");
788
- },
789
- loading: (state, task) => {
790
- const mappingId = createThreadMappingId(threadId);
791
- return {
792
- ...state,
793
- threadData: {
794
- ...state.threadData,
795
- [mappingId]: {
796
- ...state.threadData[mappingId],
797
- initializeTask: task,
798
- },
799
- },
800
- };
801
- },
819
+ execute: () => initializeTask,
820
+ optimistic: (state) =>
821
+ promoteNewThreadReducer(state, threadId, initializeTask),
802
822
  then: (state, { remoteId, externalId }) => {
803
823
  if (adapterGeneration !== this._adapterGeneration) return state;
804
- const data = getThreadData(state, threadId);
805
- if (!data) return state;
806
-
807
- const mappingId = createThreadMappingId(threadId);
808
- // A list() response that landed while this initialize was in flight
809
- // could not know the remote id yet, so it may have minted its own slot
810
- // for it; that slot collapses into this one.
811
- const listedMappingId = state.threadIdMap[remoteId];
812
- const orphan =
813
- listedMappingId !== undefined && listedMappingId !== mappingId
814
- ? state.threadData[listedMappingId]
815
- : undefined;
816
-
817
- const threadData = { ...state.threadData };
818
- if (orphan !== undefined) delete threadData[listedMappingId!];
819
- threadData[mappingId] = {
820
- ...data,
821
- initializeTask: Promise.resolve({ remoteId, externalId }),
824
+ const reconciliation = reconcileInitializedThread(
825
+ state,
826
+ threadId,
822
827
  remoteId,
823
828
  externalId,
824
- } as RemoteThreadData;
825
-
826
- const rewire = (ids: readonly string[]) =>
827
- orphan === undefined ? ids : ids.filter((id) => id !== orphan.id);
828
-
829
- return {
830
- ...state,
831
- threadIds: rewire(state.threadIds),
832
- archivedThreadIds: rewire(state.archivedThreadIds),
833
- threadIdMap: {
834
- ...state.threadIdMap,
835
- [remoteId]: mappingId,
836
- },
837
- threadData,
838
- };
829
+ threadId,
830
+ );
831
+ removedMappingId = reconciliation.removedMappingId;
832
+ if (removedMappingId === this._mainThreadId) {
833
+ this._mainThreadId = reconciliation.survivorMappingId;
834
+ }
835
+ return reconciliation.state;
839
836
  },
840
837
  });
841
838
  this._requireAdapterGeneration(adapterGeneration);
839
+ if (removedMappingId !== undefined) {
840
+ this._hookManager.stopThreadRuntime(removedMappingId);
841
+ }
842
842
  return { remoteId, externalId };
843
843
  };
844
844
 
845
- public generateTitle = async (threadId: string) => {
845
+ public generateTitle = async (
846
+ threadId: string,
847
+ options?: { automatic?: boolean },
848
+ ) => {
846
849
  this._requireAdapterSettled();
847
850
  const adapter = this._options.adapter;
848
851
  const adapterGeneration = this._adapterGeneration;
@@ -861,30 +864,40 @@ export class RemoteThreadListThreadListRuntimeCore
861
864
  // would make the payload race-dependent; the title reads settled
862
865
  // messages only, matching the trigger's readiness gate.
863
866
  const messages = runtimeCore.messages.filter(isTitleSourceMessage);
864
- const stream = await adapter.generateTitle(remoteId, messages);
865
- const messageStream = AssistantMessageStream.fromAssistantStream(stream);
866
- for await (const result of messageStream) {
867
- if (adapterGeneration !== this._adapterGeneration) return;
868
- const newTitle = result.parts.filter((c) => c.type === "text")[0]?.text;
869
- await this._state.optimisticUpdate({
870
- execute: async () => {},
871
- optimistic: (state) => {
872
- if (adapterGeneration !== this._adapterGeneration) return state;
873
- const currentData = getThreadData(state, data.id);
874
- if (!currentData) return state;
875
- return {
876
- ...state,
877
- threadData: {
878
- ...state.threadData,
879
- [currentData.id]: {
880
- ...currentData,
881
- title: newTitle,
867
+ await runThreadTitleGeneration({
868
+ states: this._titleStates,
869
+ threadId: data.id,
870
+ automatic: options?.automatic === true,
871
+ generate: async (onTitle) => {
872
+ const stream = await adapter.generateTitle(remoteId, messages);
873
+ this._requireAdapterGeneration(adapterGeneration);
874
+ await applyTitleStream(stream, onTitle);
875
+ },
876
+ rename: async (title) => {
877
+ this._requireAdapterGeneration(adapterGeneration);
878
+ await adapter.rename(remoteId, title);
879
+ },
880
+ applyTitle: async (title) => {
881
+ await this._state.optimisticUpdate({
882
+ execute: async () => {},
883
+ optimistic: (state) => {
884
+ if (adapterGeneration !== this._adapterGeneration) return state;
885
+ const currentData = getThreadData(state, data.id);
886
+ if (!currentData) return state;
887
+ return {
888
+ ...state,
889
+ threadData: {
890
+ ...state.threadData,
891
+ [currentData.id]: {
892
+ ...currentData,
893
+ title,
894
+ },
882
895
  },
883
- },
884
- };
885
- },
886
- });
887
- }
896
+ };
897
+ },
898
+ });
899
+ },
900
+ });
888
901
  };
889
902
 
890
903
  public async rename(
@@ -899,28 +912,36 @@ export class RemoteThreadListThreadListRuntimeCore
899
912
  if (data.status === "new")
900
913
  throw threadStatusError(threadIdOrRemoteId, data.status, "be renamed");
901
914
 
902
- return this._state.optimisticUpdate({
903
- execute: async () => {
904
- const { remoteId } = await data.initializeTask;
905
- this._requireAdapterGeneration(adapterGeneration);
906
- return adapter.rename(remoteId, newTitle);
907
- },
908
- optimistic: (state) => {
909
- const data = getThreadData(state, threadIdOrRemoteId);
910
- if (!data) return state;
915
+ const claim = startThreadTitleRename(this._titleStates, data.id, newTitle);
916
+ try {
917
+ const result = await this._state.optimisticUpdate({
918
+ execute: async () => {
919
+ const { remoteId } = await data.initializeTask;
920
+ this._requireAdapterGeneration(adapterGeneration);
921
+ return adapter.rename(remoteId, newTitle);
922
+ },
923
+ optimistic: (state) => {
924
+ const currentData = getThreadData(state, threadIdOrRemoteId);
925
+ if (!currentData) return state;
911
926
 
912
- return {
913
- ...state,
914
- threadData: {
915
- ...state.threadData,
916
- [data.id]: {
917
- ...data,
918
- title: newTitle,
927
+ return {
928
+ ...state,
929
+ threadData: {
930
+ ...state.threadData,
931
+ [currentData.id]: {
932
+ ...currentData,
933
+ title: newTitle,
934
+ },
919
935
  },
920
- },
921
- };
922
- },
923
- });
936
+ };
937
+ },
938
+ });
939
+ finishThreadTitleRename(this._titleStates, data.id, claim, true);
940
+ return result;
941
+ } catch (error) {
942
+ finishThreadTitleRename(this._titleStates, data.id, claim, false);
943
+ throw error;
944
+ }
924
945
  }
925
946
 
926
947
  public async updateCustom(
@@ -1063,9 +1084,7 @@ export class RemoteThreadListThreadListRuntimeCore
1063
1084
 
1064
1085
  await this._ensureThreadIsNotMain(data.id);
1065
1086
  this._requireAdapterGeneration(adapterGeneration);
1066
- this._hookManager.stopThreadRuntime(data.id);
1067
-
1068
- return this._state.optimisticUpdate({
1087
+ const result = await this._state.optimisticUpdate({
1069
1088
  execute: async () => {
1070
1089
  const { remoteId } = await data.initializeTask;
1071
1090
  this._requireAdapterGeneration(adapterGeneration);
@@ -1075,6 +1094,12 @@ export class RemoteThreadListThreadListRuntimeCore
1075
1094
  return updateStatusReducer(state, data.id, "deleted");
1076
1095
  },
1077
1096
  });
1097
+ // The optimistic layer survives an adapter swap, so a resolved deletion has
1098
+ // dropped the slot from `threadData`, where `_replaceWithThreads` would
1099
+ // otherwise have found it to stop.
1100
+ this._hookManager.stopThreadRuntime(data.id);
1101
+ clearThreadTitleState(this._titleStates, data.id);
1102
+ return result;
1078
1103
  }
1079
1104
 
1080
1105
  public __internal_dispose() {
@@ -53,6 +53,9 @@ describe("subscribeToTitleGeneration", () => {
53
53
 
54
54
  await vi.waitFor(() => {
55
55
  expect(itemRuntime.generateTitle).toHaveBeenCalledOnce();
56
+ expect(itemRuntime.generateTitle).toHaveBeenCalledWith({
57
+ automatic: true,
58
+ });
56
59
  expect(consoleError).toHaveBeenCalledWith(
57
60
  "[assistant-ui] Thread title generation failed",
58
61
  error,
@@ -97,6 +100,9 @@ describe("subscribeToTitleGeneration", () => {
97
100
 
98
101
  await vi.waitFor(() => {
99
102
  expect(itemRuntime.generateTitle).toHaveBeenCalledOnce();
103
+ expect(itemRuntime.generateTitle).toHaveBeenCalledWith({
104
+ automatic: true,
105
+ });
100
106
  });
101
107
  });
102
108
 
@@ -136,6 +142,9 @@ describe("subscribeToTitleGeneration", () => {
136
142
 
137
143
  await vi.waitFor(() => {
138
144
  expect(itemRuntime.generateTitle).toHaveBeenCalledOnce();
145
+ expect(itemRuntime.generateTitle).toHaveBeenCalledWith({
146
+ automatic: true,
147
+ });
139
148
  });
140
149
  });
141
150
  });
@@ -3,6 +3,7 @@ import { resource } from "@assistant-ui/tap";
3
3
  import {
4
4
  useAssistantContextProvider,
5
5
  useConfiguredAui,
6
+ useDestroySignalProvider,
6
7
  } from "@assistant-ui/store/client";
7
8
  import { ThreadListItemClient } from "../../store/internal";
8
9
  import type { AssistantRuntime } from "../../runtime/api/assistant-runtime";
@@ -38,6 +39,7 @@ export type RemoteThreadResourceProps = {
38
39
  runtime: ThreadRuntimeCore,
39
40
  generation: number,
40
41
  ) => void;
42
+ destroySignal: AbortSignal;
41
43
  };
42
44
 
43
45
  export const subscribeToTitleGeneration = (
@@ -45,10 +47,12 @@ export const subscribeToTitleGeneration = (
45
47
  itemRuntime: ThreadListItemRuntime,
46
48
  ) => {
47
49
  const generate = () =>
48
- void itemRuntime.generateTitle().catch((error: unknown) => {
49
- if (isSilentRuntimeAction(error)) return;
50
- console.error("[assistant-ui] Thread title generation failed", error);
51
- });
50
+ void itemRuntime
51
+ .generateTitle({ automatic: true })
52
+ .catch((error: unknown) => {
53
+ if (isSilentRuntimeAction(error)) return;
54
+ console.error("[assistant-ui] Thread title generation failed", error);
55
+ });
52
56
 
53
57
  const hasTitleSource = () =>
54
58
  threadRuntime.getState().messages.some(isTitleSourceMessage);
@@ -178,6 +182,7 @@ const useRemoteThreadResource = ({
178
182
  parentClient,
179
183
  adapters,
180
184
  publish,
185
+ destroySignal,
181
186
  }: RemoteThreadResourceProps) => {
182
187
  const itemRuntime = useMemo(
183
188
  () =>
@@ -214,14 +219,16 @@ const useRemoteThreadResource = ({
214
219
  threadListItem: ThreadListItemClient({ runtime: itemRuntime }),
215
220
  });
216
221
 
217
- return useAssistantContextProvider(client, function useThreadClient() {
218
- return useRuntimeAdaptersProvider(adapters, function useBoundRuntime() {
219
- return useRemoteThreadBinder({
220
- threadId,
221
- generation,
222
- runtimeHook,
223
- publish,
224
- itemRuntime,
222
+ return useDestroySignalProvider(destroySignal, function useOwnedThread() {
223
+ return useAssistantContextProvider(client, function useThreadClient() {
224
+ return useRuntimeAdaptersProvider(adapters, function useBoundRuntime() {
225
+ return useRemoteThreadBinder({
226
+ threadId,
227
+ generation,
228
+ runtimeHook,
229
+ publish,
230
+ itemRuntime,
231
+ });
225
232
  });
226
233
  });
227
234
  });