@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,7 +1,10 @@
1
1
  import type { Unsubscribe } from "../../types/unsubscribe";
2
2
  import type { ThreadRuntimeEventType } from "../../runtime/interfaces/thread-runtime-core";
3
- import type { ThreadRuntime } from "../../runtime/api/thread-runtime";
4
- import { useMemo, useEffect, type RefObject } from "react";
3
+ import type {
4
+ CreateAppendMessage,
5
+ ThreadRuntime,
6
+ } from "../../runtime/api/thread-runtime";
7
+ import { useMemo, useEffect, useCallback, type RefObject } from "react";
5
8
  import { useResource, resource, withKey } from "@assistant-ui/tap";
6
9
  import { liveRef } from "./liveRef";
7
10
  import type { ClientOutput } from "@assistant-ui/store";
@@ -20,17 +23,21 @@ const useMessageClientById = ({
20
23
  runtime,
21
24
  id,
22
25
  threadIdRef,
26
+ threadId,
23
27
  }: {
24
28
  runtime: ThreadRuntime;
25
29
  id: string;
26
30
  threadIdRef: RefObject<string>;
31
+ threadId: string;
27
32
  }) => {
28
33
  const messageRuntime = useMemo(
29
34
  () => runtime.getMessageById(id),
30
35
  [runtime, id],
31
36
  );
32
37
 
33
- return useResource(MessageClient({ runtime: messageRuntime, threadIdRef }));
38
+ return useResource(
39
+ MessageClient({ runtime: messageRuntime, threadIdRef, threadId }),
40
+ );
34
41
  };
35
42
 
36
43
  const MessageClientById = resource(useMessageClientById);
@@ -72,11 +79,24 @@ const useThreadClient = ({
72
79
  () => liveRef(() => runtime.getState()!.threadId),
73
80
  [runtime],
74
81
  );
82
+ const emitThreadEvent = (
83
+ event: "thread.cancelRun" | "thread.voiceStarted",
84
+ ) => {
85
+ emit(event, { threadId: runtime.getState()!.threadId });
86
+ };
87
+ const isSuggestion = useCallback(
88
+ (text: string) =>
89
+ runtime
90
+ .getState()!
91
+ .suggestions.some((suggestion) => suggestion.prompt === text),
92
+ [runtime],
93
+ );
75
94
 
76
95
  const composer = useClientResource(
77
96
  ComposerClient({
78
97
  runtime: runtime.composer,
79
98
  threadIdRef,
99
+ isSuggestion,
80
100
  }),
81
101
  );
82
102
  const suggestions = useClientResource(
@@ -84,11 +104,16 @@ const useThreadClient = ({
84
104
  );
85
105
  const messages = useClientLookup(
86
106
  runtimeState.messages.map((m) =>
87
- withKey(m.id, MessageClientById({ runtime, id: m.id, threadIdRef }), [
88
- runtime,
107
+ withKey(
89
108
  m.id,
90
- threadIdRef,
91
- ]),
109
+ MessageClientById({
110
+ runtime,
111
+ id: m.id,
112
+ threadIdRef,
113
+ threadId: runtimeState.threadId,
114
+ }),
115
+ [runtime, m.id, threadIdRef, runtimeState.threadId],
116
+ ),
92
117
  ),
93
118
  );
94
119
 
@@ -114,18 +139,41 @@ const useThreadClient = ({
114
139
  getState: () => state,
115
140
  composer: () => composer.methods,
116
141
  suggestions: () => suggestions.methods,
117
- append: runtime.append,
142
+ append: (message) => {
143
+ const appended: Exclude<CreateAppendMessage, string> =
144
+ typeof message === "string"
145
+ ? { content: [{ type: "text", text: message }] }
146
+ : message;
147
+ if ((appended.role ?? "user") === "user") {
148
+ const text = appended.content
149
+ .map((part) => (part.type === "text" ? part.text : ""))
150
+ .join("");
151
+ emit("composer.send", {
152
+ threadId: runtime.getState()!.threadId,
153
+ chars: text.length,
154
+ attachments: appended.attachments?.length ?? 0,
155
+ ...(isSuggestion(text) ? { suggestion: true } : undefined),
156
+ });
157
+ }
158
+ runtime.append(message);
159
+ },
118
160
  deleteMessage: runtime.deleteMessage,
119
161
  startRun: runtime.startRun,
120
162
  resumeRun: runtime.resumeRun,
121
163
  importExternalState: runtime.importExternalState,
122
- cancelRun: runtime.cancelRun,
164
+ cancelRun: () => {
165
+ if (runtimeState.isRunning) emitThreadEvent("thread.cancelRun");
166
+ runtime.cancelRun();
167
+ },
123
168
  getModelContext: runtime.getModelContext,
124
169
  export: runtime.export,
125
170
  import: runtime.import,
126
171
  reset: runtime.reset,
127
172
  stopSpeaking: runtime.stopSpeaking,
128
- connectVoice: runtime.connectVoice,
173
+ connectVoice: () => {
174
+ runtime.connectVoice();
175
+ emitThreadEvent("thread.voiceStarted");
176
+ },
129
177
  disconnectVoice: runtime.disconnectVoice,
130
178
  getVoiceVolume: runtime.getVoiceVolume,
131
179
  subscribeVoiceVolume: runtime.subscribeVoiceVolume,
@@ -2,7 +2,13 @@ import { useSyncExternalStore } from "react";
2
2
  import type { SubscribableWithState } from "../../subscribable/subscribable";
3
3
 
4
4
  export const useSubscribable = <T>(
5
- subscribable: Omit<SubscribableWithState<T, any>, "path">,
5
+ subscribable: Omit<SubscribableWithState<T, any>, "path"> & {
6
+ getServerSnapshot?: () => T;
7
+ },
6
8
  ) => {
7
- return useSyncExternalStore(subscribable.subscribe, subscribable.getState);
9
+ return useSyncExternalStore(
10
+ subscribable.subscribe,
11
+ subscribable.getState,
12
+ subscribable.getServerSnapshot,
13
+ );
8
14
  };
@@ -109,22 +109,34 @@ export type ComposerMeta = {
109
109
 
110
110
  export type ComposerEvents = {
111
111
  /**
112
- * The user sent the composer contents. `messageId` is set when the send
113
- * came from an edit composer.
112
+ * The user sent a message, from the composer or through `thread.append`.
113
+ * `messageId` is set when the send came from an edit composer.
114
114
  */
115
- "composer.send": { threadId: string; messageId?: string };
115
+ "composer.send": {
116
+ threadId: string;
117
+ messageId?: string;
118
+ chars: number;
119
+ attachments: number;
120
+ suggestion?: boolean;
121
+ };
116
122
  /**
117
123
  * An attachment was added to the composer. `messageId` is set when the
118
124
  * attachment was added to an edit composer.
119
125
  */
120
- "composer.attachmentAdd": { threadId: string; messageId?: string };
126
+ "composer.attachmentAdd": {
127
+ threadId: string;
128
+ messageId?: string;
129
+ contentType?: string;
130
+ };
121
131
  "composer.attachmentAddError": {
122
132
  threadId: string;
123
133
  messageId?: string;
124
134
  attachmentId?: string;
125
135
  reason: AttachmentAddErrorReason;
126
136
  message: string;
137
+ contentType?: string;
127
138
  };
139
+ "composer.cancel": { threadId: string };
128
140
  };
129
141
 
130
142
  export type ComposerClientSchema = {
@@ -66,7 +66,21 @@ export type MessageMeta = {
66
66
  query: { type: "id"; id: string } | { type: "index"; index: number };
67
67
  };
68
68
 
69
+ export type MessageEvents = {
70
+ /** The user asked for this assistant message to be generated again. */
71
+ "message.reload": { threadId: string; messageId: string };
72
+ /** The user moved to another branch at this message. */
73
+ "message.branchSwitched": { threadId: string; messageId: string };
74
+ /** The user copied this message. */
75
+ "message.copied": { threadId: string; messageId: string };
76
+ /** Speech synthesis started for this message. */
77
+ "message.speak": { threadId: string; messageId: string };
78
+ /** This message entered an error status. */
79
+ "message.error": { threadId: string; messageId: string; reason: "error" };
80
+ };
81
+
69
82
  export type MessageClientSchema = {
70
83
  methods: MessageMethods;
71
84
  meta: MessageMeta;
85
+ events: MessageEvents;
72
86
  };
@@ -29,10 +29,13 @@ export type PartMethods = {
29
29
  resumeToolCall(payload: unknown): void;
30
30
  /**
31
31
  * Respond to a server-side tool approval gate. The approval id is read from
32
- * the part. Accepts a boolean decision or the id of one of the approval's
33
- * options.
32
+ * the part. Accepts a boolean decision, the id of one of the approval's
33
+ * options, or a free-form answer when the request asks for one.
34
+ *
35
+ * Resolves once the runtime has accepted the response, and rejects when it
36
+ * could not be recorded, so a renderer can leave its controls retryable.
34
37
  */
35
- respondToToolApproval(response: ToolApprovalResponse): void;
38
+ respondToToolApproval(response: ToolApprovalResponse): Promise<void>;
36
39
  __internal_getRuntime?(): MessagePartRuntime;
37
40
  };
38
41
 
@@ -1,4 +1,7 @@
1
- import type { ThreadListItemRuntime } from "../../runtime/api/thread-list-item-runtime";
1
+ import type {
2
+ ThreadListItemGenerateTitleOptions,
3
+ ThreadListItemRuntime,
4
+ } from "../../runtime/api/thread-list-item-runtime";
2
5
  import type { ThreadListItemStatus } from "../../runtime/interfaces/thread-list-runtime-core";
3
6
 
4
7
  export type ThreadListItemState = {
@@ -26,7 +29,7 @@ export type ThreadListItemMethods = {
26
29
  archive(): void;
27
30
  unarchive(): void;
28
31
  delete(): void;
29
- generateTitle(): void;
32
+ generateTitle(options?: ThreadListItemGenerateTitleOptions): void;
30
33
  initialize(): Promise<{ remoteId: string; externalId: string | undefined }>;
31
34
  detach(): void;
32
35
  __internal_getRuntime?(): ThreadListItemRuntime;
@@ -155,6 +155,10 @@ export type ThreadEvents = {
155
155
  * state.
156
156
  */
157
157
  "thread.runEnd": { threadId: string };
158
+ /** The user stopped the run in progress on this thread. */
159
+ "thread.cancelRun": { threadId: string };
160
+ /** The user started a voice session on this thread. */
161
+ "thread.voiceStarted": { threadId: string };
158
162
  /**
159
163
  * The thread transitioned from new to initialized. Fires before the first
160
164
  * message is added, so read thread state via `useAuiState` rather than
@@ -9,6 +9,7 @@ export type ThreadsState = {
9
9
  readonly mainThreadId: string;
10
10
  readonly newThreadId: string | null;
11
11
  readonly isLoading: boolean;
12
+ readonly loadError: unknown;
12
13
  readonly isLoadingMore: boolean;
13
14
  readonly hasMore: boolean;
14
15
  readonly threadIds: readonly string[];
@@ -32,6 +32,30 @@ const createBinding = (initialState: TestState) => {
32
32
  };
33
33
  };
34
34
 
35
+ // Mirrors runtime bindings such as `getThreadListState`, which assemble a new
36
+ // object on every read while the underlying values keep their identity.
37
+ const createRebuildingBinding = (initialState: TestState) => {
38
+ let state = initialState;
39
+ const subscribers = new Set<() => void>();
40
+
41
+ const binding: SubscribableWithState<TestState, null> = {
42
+ path: null,
43
+ getState: () => ({ ...state }),
44
+ subscribe: (callback) => {
45
+ subscribers.add(callback);
46
+ return () => subscribers.delete(callback);
47
+ },
48
+ };
49
+
50
+ return {
51
+ binding,
52
+ update(nextState: TestState) {
53
+ state = nextState;
54
+ for (const callback of subscribers) callback();
55
+ },
56
+ };
57
+ };
58
+
35
59
  describe("ShallowMemoizeSubject", () => {
36
60
  it("notifies subscribers when a state key is removed", () => {
37
61
  const source = createBinding({
@@ -131,4 +155,30 @@ describe("LazyMemoizeSubject", () => {
131
155
  expect(subject.getState()).toEqual({ status: "ready" });
132
156
  reconnect();
133
157
  });
158
+
159
+ it("keeps one reference while unconnected and the state is unchanged", () => {
160
+ // A consumer that reads without subscribing (React's useSyncExternalStore
161
+ // calls getSnapshot outside the subscription) must not see a new object on
162
+ // every call: React compares snapshots with Object.is, so a fresh reference
163
+ // reads as a change and re-renders forever.
164
+ const source = createRebuildingBinding({ status: "ready" });
165
+ const subject = new LazyMemoizeSubject(source.binding);
166
+
167
+ const first = subject.getState();
168
+
169
+ expect(subject.getState()).toBe(first);
170
+ expect(subject.getState()).toBe(first);
171
+ });
172
+
173
+ it("returns a new reference once the unchanged state actually changes", () => {
174
+ const source = createRebuildingBinding({ status: "ready" });
175
+ const subject = new LazyMemoizeSubject(source.binding);
176
+
177
+ const first = subject.getState();
178
+ source.update({ status: "done" });
179
+
180
+ const second = subject.getState();
181
+ expect(second).not.toBe(first);
182
+ expect(second).toEqual({ status: "done" });
183
+ });
134
184
  });
@@ -1,3 +1,4 @@
1
+ import { shallowEqual } from "@assistant-ui/store/client";
1
2
  import type { Unsubscribe } from "../types/unsubscribe";
2
3
  import { notifyEventListeners } from "../utils/notify-event-listeners";
3
4
 
@@ -57,25 +58,10 @@ export const notifySubscribers = <TArgs extends unknown[]>(
57
58
  }
58
59
  };
59
60
 
60
- function shallowEqual<T extends object>(
61
- objA: T | undefined,
62
- objB: T | undefined,
63
- ) {
64
- if (objA === undefined && objB === undefined) return true;
65
- if (objA === undefined) return false;
66
- if (objB === undefined) return false;
67
-
68
- const keysA = Object.keys(objA);
69
- if (keysA.length !== Object.keys(objB).length) return false;
70
-
71
- for (const key of keysA) {
72
- const valueA = objA[key as keyof T];
73
- const valueB = objB[key as keyof T];
74
- if (!Object.is(valueA, valueB)) return false;
75
- }
76
-
77
- return true;
78
- }
61
+ const shallowEqualOrUndefined = <T extends object>(
62
+ a: T | undefined,
63
+ b: T | undefined,
64
+ ) => (a === undefined || b === undefined ? a === b : shallowEqual(a, b));
79
65
 
80
66
  export class BaseSubscribable {
81
67
  private _subscribers = new Set<() => void>();
@@ -99,6 +85,32 @@ export class BaseSubscribable {
99
85
  }
100
86
  }
101
87
 
88
+ export class WritableSubscribable<TState> extends BaseSubscribable {
89
+ private _state: TState;
90
+
91
+ constructor(state: TState) {
92
+ super();
93
+ this._state = state;
94
+ this.subscribe = this.subscribe.bind(this);
95
+ this.getState = this.getState.bind(this);
96
+ // Hydration has to agree with what the server rendered, so the server
97
+ // snapshot stays at the creation-time state rather than following writes.
98
+ this.getServerSnapshot = () => state;
99
+ }
100
+
101
+ public getState(): TState {
102
+ return this._state;
103
+ }
104
+
105
+ public getServerSnapshot: () => TState;
106
+
107
+ public setState(state: TState): void {
108
+ if (Object.is(state, this._state)) return;
109
+ this._state = state;
110
+ this._notifySubscribers();
111
+ }
112
+ }
113
+
102
114
  // lazy connect/disconnect: only opens upstream subscription while it has subscribers
103
115
  export abstract class BaseSubject {
104
116
  private _subscriptions = new Set<(payload?: unknown) => void>();
@@ -168,7 +180,7 @@ export class ShallowMemoizeSubject<TState extends object, TPath>
168
180
  private _syncState() {
169
181
  const state = this.binding.getState();
170
182
  if (state === SKIP_UPDATE) return false;
171
- if (shallowEqual(state, this._previousState)) return false;
183
+ if (shallowEqualOrUndefined(state, this._previousState)) return false;
172
184
  this._previousState = state;
173
185
  return true;
174
186
  }
@@ -209,7 +221,7 @@ export class LazyMemoizeSubject<TState extends object, TPath>
209
221
  if (
210
222
  newState !== SKIP_UPDATE &&
211
223
  (this._previousState === undefined ||
212
- !shallowEqual(newState, this._previousState))
224
+ !shallowEqualOrUndefined(newState, this._previousState))
213
225
  ) {
214
226
  this._previousState = newState;
215
227
  }
@@ -240,6 +240,80 @@ describe("MessageRepository", () => {
240
240
  expect(messages2.map((m) => m.id)).toEqual(["parent-id", "branch2-id"]);
241
241
  });
242
242
 
243
+ it("keeps a nested branch selected when revisiting its ancestor", () => {
244
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "r" }));
245
+ repository.addOrUpdateMessage("r", createTestMessage({ id: "a" }));
246
+ repository.addOrUpdateMessage("a", createTestMessage({ id: "x" }));
247
+ repository.addOrUpdateMessage("r", createTestMessage({ id: "b" }));
248
+ repository.addOrUpdateMessage("b", createTestMessage({ id: "y" }));
249
+
250
+ repository.switchToBranch("y");
251
+ expect(repository.getMessages().map((m) => m.id)).toEqual([
252
+ "r",
253
+ "b",
254
+ "y",
255
+ ]);
256
+
257
+ repository.switchToBranch("r");
258
+ expect(repository.getMessages().map((m) => m.id)).toEqual([
259
+ "r",
260
+ "b",
261
+ "y",
262
+ ]);
263
+
264
+ repository.switchToBranch("x");
265
+ repository.switchToBranch("r");
266
+ expect(repository.getMessages().map((m) => m.id)).toEqual([
267
+ "r",
268
+ "a",
269
+ "x",
270
+ ]);
271
+ });
272
+
273
+ it("keeps the selected root branch when deletion selects from the root", () => {
274
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "a" }));
275
+ repository.addOrUpdateMessage("a", createTestMessage({ id: "x" }));
276
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "b" }));
277
+ repository.addOrUpdateMessage("b", createTestMessage({ id: "y" }));
278
+
279
+ repository.switchToBranch("y");
280
+ repository.deleteMessage("y", null);
281
+
282
+ expect(repository.getMessages().map((m) => m.id)).toEqual(["b"]);
283
+ });
284
+
285
+ it("keeps the selected root branch after relinking a message", () => {
286
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "a" }));
287
+ repository.addOrUpdateMessage("a", createTestMessage({ id: "m" }));
288
+ repository.addOrUpdateMessage("m", createTestMessage({ id: "x" }));
289
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "b" }));
290
+ repository.addOrUpdateMessage("b", createTestMessage({ id: "m" }));
291
+
292
+ expect(repository.getMessages().map((m) => m.id)).toEqual([
293
+ "b",
294
+ "m",
295
+ "x",
296
+ ]);
297
+
298
+ repository.deleteMessage("x", null);
299
+
300
+ expect(repository.getMessages().map((m) => m.id)).toEqual(["b", "m"]);
301
+ });
302
+
303
+ it("keeps the selected branch when relinking an off-branch message", () => {
304
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "a" }));
305
+ repository.addOrUpdateMessage("a", createTestMessage({ id: "m" }));
306
+ repository.addOrUpdateMessage("a", createTestMessage({ id: "n" }));
307
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "b" }));
308
+ repository.addOrUpdateMessage("b", createTestMessage({ id: "n" }));
309
+
310
+ expect(repository.getMessages().map((m) => m.id)).toEqual(["a", "m"]);
311
+
312
+ repository.deleteMessage("m", null);
313
+
314
+ expect(repository.getMessages().map((m) => m.id)).toEqual(["a"]);
315
+ });
316
+
243
317
  it("should operate on a long message history without overflowing the stack", () => {
244
318
  const messageCount = 30_000;
245
319
  const messages = createLongBranchMessages(messageCount);
@@ -486,6 +560,31 @@ describe("MessageRepository", () => {
486
560
  ).toBe("parent-id");
487
561
  });
488
562
 
563
+ it("round-trips after reparenting under a later-added ancestor", () => {
564
+ const messageA = createTestMessage({ id: "A" });
565
+ repository.addOrUpdateMessage(null, messageA);
566
+ repository.addOrUpdateMessage("A", createTestMessage({ id: "B" }));
567
+ repository.addOrUpdateMessage("A", createTestMessage({ id: "C" }));
568
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "X" }));
569
+ repository.addOrUpdateMessage("X", messageA);
570
+
571
+ const exported = repository.export();
572
+ expect(exported.messages.map((m) => m.message.id)).toEqual([
573
+ "X",
574
+ "A",
575
+ "B",
576
+ "C",
577
+ ]);
578
+
579
+ const restored = new MessageRepository();
580
+ restored.import(exported);
581
+
582
+ expect(restored.headId).toBe("B");
583
+ expect(restored.getMessages().map((m) => m.id)).toEqual(["X", "A", "B"]);
584
+ restored.switchToBranch("C");
585
+ expect(restored.getMessages().map((m) => m.id)).toEqual(["X", "A", "C"]);
586
+ });
587
+
489
588
  it("should import repository state", () => {
490
589
  const parent = createTestMessage({ id: "parent-id" });
491
590
  const child = createTestMessage({ id: "child-id" });
@@ -941,5 +1040,22 @@ describe("MessageRepository", () => {
941
1040
 
942
1041
  expect(repository.headId).toBe("C");
943
1042
  });
1043
+
1044
+ it("should advance the head when re-parenting onto the current head", () => {
1045
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "a" }));
1046
+ repository.addOrUpdateMessage("a", createTestMessage({ id: "h" }));
1047
+ repository.addOrUpdateMessage(null, createTestMessage({ id: "b" }));
1048
+ repository.addOrUpdateMessage("b", createTestMessage({ id: "c" }));
1049
+
1050
+ repository.addOrUpdateMessage("h", createTestMessage({ id: "c" }));
1051
+
1052
+ expect(repository.getMessages().map((m) => m.id)).toEqual([
1053
+ "a",
1054
+ "h",
1055
+ "c",
1056
+ ]);
1057
+ expect(repository.headId).toBe("c");
1058
+ expect(repository.getMessage("c").parentId).toBe("h");
1059
+ });
944
1060
  });
945
1061
  });
@@ -22,6 +22,7 @@ import { deferred } from "./remote-thread-list-test-helpers";
22
22
 
23
23
  const EMPTY_STATE: RemoteThreadState = {
24
24
  isLoading: false,
25
+ loadError: undefined,
25
26
  isLoadingMore: false,
26
27
  cursor: undefined,
27
28
  newThreadId: undefined,
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import {
3
+ createCore,
4
+ deferred,
5
+ makeAdapter,
6
+ } from "./remote-thread-list-test-helpers";
7
+
8
+ type InitializeResult = { remoteId: string; externalId: string };
9
+
10
+ describe("RemoteThreadListThreadListRuntimeCore initialize", () => {
11
+ it("keeps the initialization task on the promoted slot when the adapter changes mid-flight", async () => {
12
+ const initializing = deferred<InitializeResult>();
13
+ const core = createCore(
14
+ makeAdapter({ initialize: vi.fn(() => initializing.promise) }),
15
+ );
16
+
17
+ await core.switchToNewThread();
18
+ const localId = core.newThreadId!;
19
+ const pending = core.initialize(localId);
20
+
21
+ // An adapter swap advances the generation without resetting the store, so
22
+ // the completion still applies its optimistic transform while `then`
23
+ // declines to reconcile against the retired adapter.
24
+ core.__internal_setOptions({
25
+ adapter: makeAdapter(),
26
+ runtimeHook: () => ({}) as never,
27
+ });
28
+
29
+ initializing.resolve({ remoteId: "remote-1", externalId: "external-1" });
30
+ await expect(pending).rejects.toThrow();
31
+
32
+ const item = core.getItemById(localId);
33
+ expect(item?.status).toBe("regular");
34
+ await expect(
35
+ item?.status === "new" ? undefined : item?.initializeTask,
36
+ ).resolves.toEqual({ remoteId: "remote-1", externalId: "external-1" });
37
+ });
38
+ });