@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
@@ -1,14 +1,67 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
+ classifyThreads,
3
4
  createEmptyRemoteThreadState,
4
5
  createThreadMappingId,
6
+ getThreadData,
7
+ promoteNewThreadReducer,
8
+ reconcileInitializedThread,
5
9
  seedNewThread,
10
+ updateStatusReducer,
6
11
  } from "./remote-thread-state";
12
+ import type {
13
+ RemoteThreadData,
14
+ RemoteThreadState,
15
+ } from "./remote-thread-state";
16
+
17
+ const initializedDraft = () => {
18
+ const seeded = seedNewThread(createEmptyRemoteThreadState());
19
+ const initializeTask = Promise.resolve({
20
+ remoteId: "remote-1",
21
+ externalId: "remote-1",
22
+ });
23
+ const regular = promoteNewThreadReducer(
24
+ seeded.state,
25
+ seeded.id,
26
+ initializeTask,
27
+ );
28
+ const mappingId = regular.threadIdMap[seeded.id]!;
29
+ return {
30
+ id: seeded.id,
31
+ mappingId,
32
+ initializeTask,
33
+ state: {
34
+ ...regular,
35
+ threadIdMap: { ...regular.threadIdMap, "remote-1": mappingId },
36
+ threadData: {
37
+ ...regular.threadData,
38
+ [mappingId]: {
39
+ ...regular.threadData[mappingId]!,
40
+ remoteId: "remote-1",
41
+ externalId: "remote-1",
42
+ initializeTask,
43
+ } as RemoteThreadData,
44
+ },
45
+ } as RemoteThreadState,
46
+ };
47
+ };
48
+
49
+ const expectOneSlotPerIdentity = (state: RemoteThreadState) => {
50
+ const remoteIds = Object.values(state.threadData)
51
+ .map((data) => data.remoteId)
52
+ .filter((remoteId) => remoteId !== undefined);
53
+ expect(new Set(remoteIds).size).toBe(remoteIds.length);
54
+
55
+ for (const id of [...state.threadIds, ...state.archivedThreadIds]) {
56
+ expect(state.threadData[state.threadIdMap[id]!]?.id).toBe(id);
57
+ }
58
+ };
7
59
 
8
60
  describe("remote thread state", () => {
9
61
  it("creates an empty state", () => {
10
62
  expect(createEmptyRemoteThreadState()).toEqual({
11
63
  isLoading: true,
64
+ loadError: undefined,
12
65
  isLoadingMore: false,
13
66
  cursor: undefined,
14
67
  newThreadId: undefined,
@@ -34,4 +87,189 @@ describe("remote thread state", () => {
34
87
  );
35
88
  expect(Object.keys(second.state.threadData)).toEqual([first.id, second.id]);
36
89
  });
90
+
91
+ it("carries the initialization task onto the promoted slot", () => {
92
+ const seeded = seedNewThread(createEmptyRemoteThreadState());
93
+ const initializeTask = Promise.resolve({
94
+ remoteId: "remote-1",
95
+ externalId: "external-1",
96
+ });
97
+
98
+ const promoted = promoteNewThreadReducer(
99
+ seeded.state,
100
+ seeded.id,
101
+ initializeTask,
102
+ );
103
+ const data = getThreadData(promoted, seeded.id);
104
+
105
+ expect(data?.status).toBe("regular");
106
+ expect(data?.status === "new" ? undefined : data?.initializeTask).toBe(
107
+ initializeTask,
108
+ );
109
+ expect(promoted.newThreadId).toBeUndefined();
110
+ expect(promoted.threadIds).toEqual([seeded.id]);
111
+ });
112
+
113
+ it.each(["regular", "archived"] as const)(
114
+ "leaves a new thread untouched when moved to %s without an initialization task",
115
+ (newStatus) => {
116
+ const seeded = seedNewThread(createEmptyRemoteThreadState());
117
+
118
+ expect(updateStatusReducer(seeded.state, seeded.id, newStatus)).toBe(
119
+ seeded.state,
120
+ );
121
+ },
122
+ );
123
+
124
+ it("refreshes the local slot when a listed thread already has a mapping", () => {
125
+ const draft = initializedDraft();
126
+
127
+ const merged = classifyThreads(
128
+ [
129
+ {
130
+ status: "regular",
131
+ remoteId: "remote-1",
132
+ externalId: "remote-1",
133
+ title: "from the server",
134
+ },
135
+ ],
136
+ {
137
+ threadIds: [],
138
+ archivedThreadIds: [],
139
+ threadIdMap: { ...draft.state.threadIdMap },
140
+ threadData: { ...draft.state.threadData },
141
+ },
142
+ );
143
+
144
+ expect(Object.keys(merged.threadData)).toEqual([draft.mappingId]);
145
+ expect(merged.threadIds).toEqual([draft.id]);
146
+ expect(merged.threadIdMap["remote-1"]).toBe(draft.mappingId);
147
+ expect(merged.threadData[draft.mappingId]?.id).toBe(draft.id);
148
+ expect(merged.threadData[draft.mappingId]?.title).toBe("from the server");
149
+ expect(merged.threadData[draft.mappingId]?.localOrigin).toBe(true);
150
+ const refreshed = merged.threadData[draft.mappingId]!;
151
+ expect(
152
+ refreshed.status === "new" ? undefined : refreshed.initializeTask,
153
+ ).toBe(draft.initializeTask);
154
+ expectOneSlotPerIdentity({ ...draft.state, ...merged });
155
+ });
156
+
157
+ it("lists a repeated remote id once", () => {
158
+ const listed = classifyThreads(
159
+ [
160
+ { status: "regular", remoteId: "a", externalId: "a", title: "first" },
161
+ { status: "regular", remoteId: "a", externalId: "a", title: "second" },
162
+ ],
163
+ {
164
+ threadIds: [],
165
+ archivedThreadIds: [],
166
+ threadIdMap: {},
167
+ threadData: {},
168
+ },
169
+ );
170
+
171
+ expect(listed.threadIds).toEqual(["a"]);
172
+ expect(listed.threadData[createThreadMappingId("a")]?.title).toBe("second");
173
+ });
174
+
175
+ it("retains a listed external id when that slot survives reconciliation", async () => {
176
+ const seeded = seedNewThread(createEmptyRemoteThreadState());
177
+ const regular = promoteNewThreadReducer(
178
+ seeded.state,
179
+ seeded.id,
180
+ Promise.resolve({ remoteId: "remote-1", externalId: "external-1" }),
181
+ );
182
+ const classified = classifyThreads(
183
+ [
184
+ {
185
+ status: "regular",
186
+ remoteId: "remote-1",
187
+ externalId: "external-1",
188
+ },
189
+ ],
190
+ {
191
+ threadIds: [...regular.threadIds],
192
+ archivedThreadIds: [...regular.archivedThreadIds],
193
+ threadIdMap: { ...regular.threadIdMap },
194
+ threadData: { ...regular.threadData },
195
+ },
196
+ );
197
+ const state = { ...regular, ...classified };
198
+
199
+ const reconciled = reconcileInitializedThread(
200
+ state,
201
+ seeded.id,
202
+ "remote-1",
203
+ undefined,
204
+ "remote-1",
205
+ );
206
+ const survivor = reconciled.state.threadData[reconciled.survivorMappingId]!;
207
+
208
+ expect(survivor.externalId).toBe("external-1");
209
+ if (survivor.status === "new") throw new Error("Expected initialized slot");
210
+ await expect(survivor.initializeTask).resolves.toEqual({
211
+ remoteId: "remote-1",
212
+ externalId: "external-1",
213
+ });
214
+ });
215
+
216
+ it.each(["__proto__", "constructor", "toString"])(
217
+ "handles a prototype-named remote id %s",
218
+ (remoteId) => {
219
+ const classified = classifyThreads(
220
+ [
221
+ {
222
+ status: "regular",
223
+ remoteId,
224
+ externalId: undefined,
225
+ title: `title-${remoteId}`,
226
+ },
227
+ {
228
+ status: "regular",
229
+ remoteId: "ok",
230
+ externalId: undefined,
231
+ title: "title-ok",
232
+ },
233
+ ],
234
+ {
235
+ threadIds: [],
236
+ archivedThreadIds: [],
237
+ threadIdMap: {},
238
+ threadData: {},
239
+ },
240
+ );
241
+ const listed: RemoteThreadState = {
242
+ ...createEmptyRemoteThreadState(),
243
+ threadIds: classified.threadIds,
244
+ archivedThreadIds: classified.archivedThreadIds,
245
+ threadIdMap: classified.threadIdMap,
246
+ threadData: classified.threadData,
247
+ };
248
+
249
+ expect(Object.keys(listed.threadIdMap)).toEqual([remoteId, "ok"]);
250
+ expect(typeof listed.threadIdMap[remoteId]).toBe("string");
251
+ expect(Object.keys(listed.threadData)).toEqual([remoteId, "ok"]);
252
+
253
+ const deleted = updateStatusReducer(listed, remoteId, "deleted");
254
+ expect(Object.keys(deleted.threadIdMap)).toEqual(["ok"]);
255
+ expect(Object.keys(deleted.threadData)).toEqual(["ok"]);
256
+ expect(deleted.threadIds).toEqual(["ok"]);
257
+ },
258
+ );
259
+
260
+ it("deletes every alias of an identity, by either id", () => {
261
+ for (const target of ["remote-1", "local"] as const) {
262
+ const draft = initializedDraft();
263
+ const deleted = updateStatusReducer(
264
+ draft.state,
265
+ target === "local" ? draft.id : target,
266
+ "deleted",
267
+ );
268
+
269
+ expect(deleted.threadData).toEqual({});
270
+ expect(deleted.threadIdMap).toEqual({});
271
+ expect(deleted.threadIds).toEqual([]);
272
+ expect(deleted.archivedThreadIds).toEqual([]);
273
+ }
274
+ });
37
275
  });
@@ -3,6 +3,7 @@ import type {
3
3
  RemoteThreadMetadata,
4
4
  } from "./types";
5
5
  import { generateId } from "../../utils/id";
6
+ import { nullProtoRecord } from "../../utils/record";
6
7
 
7
8
  export type RemoteThreadData =
8
9
  | {
@@ -47,26 +48,33 @@ export const LOCAL_THREAD_ID_PREFIX = "__LOCALID_";
47
48
  export const normalizeCursor = (c: string | undefined): string | undefined =>
48
49
  c || undefined;
49
50
 
50
- type ClassifyAccumulator = {
51
+ export type ClassifyAccumulator = {
51
52
  threadIds: string[];
52
53
  archivedThreadIds: string[];
53
54
  threadIdMap: Record<string, THREAD_MAPPING_ID>;
54
55
  threadData: Record<THREAD_MAPPING_ID, RemoteThreadData>;
55
56
  };
56
57
 
58
+ // A slot's `id` is fixed when it is minted: the hook instance manager keys live
59
+ // thread runtimes by it, so renaming a slot would detach the runtime the user is
60
+ // chatting in. A listed thread whose `remoteId` already maps to a slot therefore
61
+ // refreshes that slot instead of minting a second one, and a slot minted by a
62
+ // list() that raced ahead of the remote id collapses when `initialize()` settles.
63
+ // `threadIds`/`archivedThreadIds` carry slot ids, never remote ids.
57
64
  export const classifyThreads = (
58
65
  threads: readonly RemoteThreadMetadata[],
59
66
  acc: ClassifyAccumulator,
60
67
  ): ClassifyAccumulator => {
61
- for (const thread of threads) {
62
- if (acc.threadIdMap[thread.remoteId] !== undefined) continue;
68
+ let threadIds = [...acc.threadIds];
69
+ let archivedThreadIds = [...acc.archivedThreadIds];
70
+ const threadIdMap = nullProtoRecord(acc.threadIdMap);
71
+ const threadData = nullProtoRecord(acc.threadData);
72
+ const listed = new Set([...threadIds, ...archivedThreadIds]);
63
73
 
74
+ for (const thread of threads) {
64
75
  switch (thread.status) {
65
76
  case "regular":
66
- acc.threadIds.push(thread.remoteId);
67
- break;
68
77
  case "archived":
69
- acc.archivedThreadIds.push(thread.remoteId);
70
78
  break;
71
79
  default: {
72
80
  const _exhaustiveCheck: never = thread.status;
@@ -74,27 +82,60 @@ export const classifyThreads = (
74
82
  }
75
83
  }
76
84
 
77
- const mappingId = createThreadMappingId(thread.remoteId);
78
- acc.threadIdMap[thread.remoteId] = mappingId;
79
- acc.threadData[mappingId] = {
80
- id: thread.remoteId,
85
+ const existingMappingId = threadIdMap[thread.remoteId];
86
+ const existing =
87
+ existingMappingId !== undefined
88
+ ? threadData[existingMappingId]
89
+ : undefined;
90
+ const id = existing?.id ?? thread.remoteId;
91
+ const mappingId = existingMappingId ?? createThreadMappingId(id);
92
+ const existingTask =
93
+ existing !== undefined && existing.status !== "new"
94
+ ? existing.initializeTask
95
+ : undefined;
96
+
97
+ if (!listed.has(id)) {
98
+ listed.add(id);
99
+ if (thread.status === "regular") {
100
+ threadIds.push(id);
101
+ } else {
102
+ archivedThreadIds.push(id);
103
+ }
104
+ } else if (existing !== undefined && existing.status !== thread.status) {
105
+ if (thread.status === "regular") {
106
+ archivedThreadIds = archivedThreadIds.filter((t) => t !== id);
107
+ threadIds.push(id);
108
+ } else {
109
+ threadIds = threadIds.filter((t) => t !== id);
110
+ archivedThreadIds.push(id);
111
+ }
112
+ }
113
+
114
+ threadIdMap[id] = mappingId;
115
+ threadIdMap[thread.remoteId] = mappingId;
116
+ threadData[mappingId] = {
117
+ ...(existing?.localOrigin === true ? { localOrigin: true as const } : {}),
118
+ id,
81
119
  remoteId: thread.remoteId,
82
120
  externalId: thread.externalId,
83
121
  status: thread.status,
84
122
  title: thread.title,
85
123
  lastMessageAt: thread.lastMessageAt,
86
124
  custom: thread.custom,
87
- initializeTask: Promise.resolve({
88
- remoteId: thread.remoteId,
89
- externalId: thread.externalId,
90
- }),
125
+ initializeTask:
126
+ existingTask ??
127
+ Promise.resolve({
128
+ remoteId: thread.remoteId,
129
+ externalId: thread.externalId,
130
+ }),
91
131
  };
92
132
  }
93
- return acc;
133
+ return { threadIds, archivedThreadIds, threadIdMap, threadData };
94
134
  };
95
135
 
96
136
  export type RemoteThreadState = {
97
137
  readonly isLoading: boolean;
138
+ readonly loadError: unknown;
98
139
  readonly isLoadingMore: boolean;
99
140
  readonly cursor: string | undefined;
100
141
  readonly newThreadId: string | undefined;
@@ -106,13 +147,14 @@ export type RemoteThreadState = {
106
147
 
107
148
  export const createEmptyRemoteThreadState = (): RemoteThreadState => ({
108
149
  isLoading: true,
150
+ loadError: undefined,
109
151
  isLoadingMore: false,
110
152
  cursor: undefined,
111
153
  newThreadId: undefined,
112
154
  threadIds: [],
113
155
  archivedThreadIds: [],
114
- threadIdMap: {},
115
- threadData: {},
156
+ threadIdMap: nullProtoRecord(),
157
+ threadData: nullProtoRecord(),
116
158
  });
117
159
 
118
160
  export const seedNewThread = (
@@ -128,12 +170,10 @@ export const seedNewThread = (
128
170
  state: {
129
171
  ...state,
130
172
  newThreadId: id,
131
- threadIdMap: {
132
- ...state.threadIdMap,
173
+ threadIdMap: nullProtoRecord(state.threadIdMap, {
133
174
  [id]: mappingId,
134
- },
135
- threadData: {
136
- ...state.threadData,
175
+ }),
176
+ threadData: nullProtoRecord(state.threadData, {
137
177
  [mappingId]: {
138
178
  status: "new",
139
179
  id,
@@ -143,7 +183,7 @@ export const seedNewThread = (
143
183
  custom: undefined,
144
184
  localOrigin: true,
145
185
  } satisfies RemoteThreadData,
146
- },
186
+ }),
147
187
  },
148
188
  };
149
189
  };
@@ -225,22 +265,143 @@ export const getThreadData = (
225
265
  state: RemoteThreadState,
226
266
  threadIdOrRemoteId: string,
227
267
  ) => {
228
- const idx = state.threadIdMap[threadIdOrRemoteId];
268
+ const idx = Object.hasOwn(state.threadIdMap, threadIdOrRemoteId)
269
+ ? state.threadIdMap[threadIdOrRemoteId]
270
+ : undefined;
229
271
  if (idx === undefined) return undefined;
230
- return state.threadData[idx];
272
+ return Object.hasOwn(state.threadData, idx)
273
+ ? state.threadData[idx]
274
+ : undefined;
231
275
  };
232
276
 
233
- export const updateStatusReducer = (
277
+ export const reconcileInitializedThread = (
278
+ state: RemoteThreadState,
279
+ threadId: string,
280
+ remoteId: string,
281
+ externalId: string | undefined,
282
+ retainedThreadId?: string,
283
+ ): {
284
+ state: RemoteThreadState;
285
+ removedMappingId: THREAD_MAPPING_ID | undefined;
286
+ survivorMappingId: THREAD_MAPPING_ID;
287
+ } => {
288
+ const mappingId = createThreadMappingId(threadId);
289
+ const data = Object.hasOwn(state.threadData, mappingId)
290
+ ? state.threadData[mappingId]
291
+ : undefined;
292
+ if (!data) {
293
+ return { state, removedMappingId: undefined, survivorMappingId: mappingId };
294
+ }
295
+
296
+ // A concurrent list cannot associate the remote ID with this initializing
297
+ // slot and may mint a duplicate. Retain the slot whose mounted runtime owns
298
+ // local state; initialized metadata remains authoritative over list metadata.
299
+ const listedMappingId = Object.hasOwn(state.threadIdMap, remoteId)
300
+ ? state.threadIdMap[remoteId]
301
+ : undefined;
302
+ const listedData =
303
+ listedMappingId !== undefined &&
304
+ listedMappingId !== mappingId &&
305
+ Object.hasOwn(state.threadData, listedMappingId)
306
+ ? state.threadData[listedMappingId]
307
+ : undefined;
308
+ const listedSlot =
309
+ listedMappingId !== undefined && listedData !== undefined
310
+ ? { mappingId: listedMappingId, data: listedData }
311
+ : undefined;
312
+ const retainedMappingId =
313
+ retainedThreadId !== undefined &&
314
+ Object.hasOwn(state.threadIdMap, retainedThreadId)
315
+ ? state.threadIdMap[retainedThreadId]
316
+ : undefined;
317
+ const survivorMappingId =
318
+ listedSlot !== undefined && retainedMappingId === listedSlot.mappingId
319
+ ? listedSlot.mappingId
320
+ : mappingId;
321
+ const removedMappingId =
322
+ listedSlot === undefined
323
+ ? undefined
324
+ : survivorMappingId === mappingId
325
+ ? listedSlot.mappingId
326
+ : mappingId;
327
+ const resolvedExternalId = externalId ?? listedSlot?.data.externalId;
328
+ const threadData = nullProtoRecord(state.threadData);
329
+ if (removedMappingId !== undefined) delete threadData[removedMappingId];
330
+ const initializedData = {
331
+ ...data,
332
+ id: survivorMappingId,
333
+ initializeTask: Promise.resolve({
334
+ remoteId,
335
+ externalId: resolvedExternalId,
336
+ }),
337
+ remoteId,
338
+ externalId: resolvedExternalId,
339
+ } as RemoteThreadData;
340
+ threadData[survivorMappingId] = {
341
+ ...listedSlot?.data,
342
+ ...initializedData,
343
+ title: data.title ?? listedSlot?.data.title,
344
+ lastMessageAt:
345
+ ("lastMessageAt" in data ? data.lastMessageAt : undefined) ??
346
+ (listedSlot && "lastMessageAt" in listedSlot.data
347
+ ? listedSlot.data.lastMessageAt
348
+ : undefined),
349
+ custom: data.custom ?? listedSlot?.data.custom,
350
+ } as RemoteThreadData;
351
+
352
+ const threadIdMap = nullProtoRecord(state.threadIdMap);
353
+ if (removedMappingId !== undefined) {
354
+ for (const [id, target] of Object.entries(threadIdMap)) {
355
+ if (target === removedMappingId) threadIdMap[id] = survivorMappingId;
356
+ }
357
+ }
358
+ threadIdMap[threadId] = survivorMappingId;
359
+ threadIdMap[remoteId] = survivorMappingId;
360
+
361
+ const rewire = (ids: readonly string[]) =>
362
+ listedSlot === undefined
363
+ ? ids
364
+ : ids
365
+ .filter((id) => id !== listedSlot.data.id)
366
+ .map((id) => (id === data.id ? survivorMappingId : id));
367
+
368
+ return {
369
+ state: {
370
+ ...state,
371
+ threadIds: rewire(state.threadIds),
372
+ archivedThreadIds: rewire(state.archivedThreadIds),
373
+ threadIdMap,
374
+ threadData,
375
+ },
376
+ removedMappingId,
377
+ survivorMappingId,
378
+ };
379
+ };
380
+
381
+ const transitionReducer = (
234
382
  state: RemoteThreadState,
235
383
  threadIdOrRemoteId: string,
236
384
  newStatus: "regular" | "archived" | "deleted",
385
+ initializeTask: Promise<RemoteThreadInitializeResponse> | undefined,
237
386
  ) => {
238
387
  const data = getThreadData(state, threadIdOrRemoteId);
239
388
  if (!data) return state;
240
389
 
241
- const { id, remoteId, status: lastStatus } = data;
390
+ const { id, status: lastStatus } = data;
242
391
  if (lastStatus === newStatus) return state;
243
392
 
393
+ let nextData: RemoteThreadData | undefined;
394
+ if (newStatus !== "deleted") {
395
+ if (data.status === "new") {
396
+ // The new variant holds no initialization promise, so the destination
397
+ // union member cannot be built unless the caller supplies one.
398
+ if (initializeTask === undefined) return state;
399
+ nextData = { ...data, initializeTask, status: newStatus };
400
+ } else {
401
+ nextData = { ...data, status: newStatus };
402
+ }
403
+ }
404
+
244
405
  const newState = { ...state };
245
406
 
246
407
  // lastStatus
@@ -273,16 +434,18 @@ export const updateStatusReducer = (
273
434
  newState.archivedThreadIds = [id, ...newState.archivedThreadIds];
274
435
  break;
275
436
 
276
- case "deleted":
277
- newState.threadData = Object.fromEntries(
278
- Object.entries(newState.threadData).filter(([key]) => key !== id),
279
- );
280
- newState.threadIdMap = Object.fromEntries(
281
- Object.entries(newState.threadIdMap).filter(
282
- ([key]) => key !== id && key !== remoteId,
283
- ),
284
- );
437
+ case "deleted": {
438
+ const mappingId = state.threadIdMap[threadIdOrRemoteId]!;
439
+ const threadData = nullProtoRecord(newState.threadData);
440
+ delete threadData[mappingId];
441
+ newState.threadData = threadData;
442
+ const threadIdMap = nullProtoRecord(newState.threadIdMap);
443
+ for (const [key, value] of Object.entries(threadIdMap)) {
444
+ if (value === mappingId) delete threadIdMap[key];
445
+ }
446
+ newState.threadIdMap = threadIdMap;
285
447
  break;
448
+ }
286
449
 
287
450
  default: {
288
451
  const _exhaustiveCheck: never = newStatus;
@@ -290,15 +453,31 @@ export const updateStatusReducer = (
290
453
  }
291
454
  }
292
455
 
293
- if (newStatus !== "deleted") {
294
- newState.threadData = {
295
- ...newState.threadData,
296
- [id]: {
297
- ...data,
298
- status: newStatus,
299
- },
300
- };
456
+ if (nextData !== undefined) {
457
+ newState.threadData = { ...newState.threadData, [id]: nextData };
301
458
  }
302
459
 
303
460
  return newState;
304
461
  };
462
+
463
+ /**
464
+ * Transitions a thread that already exists remotely. Promoting the local draft
465
+ * off `new` needs the initialization promise its destination variant requires,
466
+ * so that goes through `promoteNewThreadReducer`; asking for it here leaves the
467
+ * state unchanged.
468
+ */
469
+ export const updateStatusReducer = (
470
+ state: RemoteThreadState,
471
+ threadIdOrRemoteId: string,
472
+ newStatus: "regular" | "archived" | "deleted",
473
+ ) => transitionReducer(state, threadIdOrRemoteId, newStatus, undefined);
474
+
475
+ /**
476
+ * Promotes the local draft to `regular`, carrying the initialization promise
477
+ * onto the slot.
478
+ */
479
+ export const promoteNewThreadReducer = (
480
+ state: RemoteThreadState,
481
+ threadIdOrRemoteId: string,
482
+ initializeTask: Promise<RemoteThreadInitializeResponse>,
483
+ ) => transitionReducer(state, threadIdOrRemoteId, "regular", initializeTask);