@assistant-ui/core 0.3.12 → 0.3.14

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 (452) hide show
  1. package/dist/adapters/speech.d.ts.map +1 -1
  2. package/dist/adapters/speech.js +9 -15
  3. package/dist/adapters/speech.js.map +1 -1
  4. package/dist/adapters/thread-history.d.ts +2 -0
  5. package/dist/adapters/thread-history.d.ts.map +1 -1
  6. package/dist/adapters/voice.d.ts.map +1 -1
  7. package/dist/adapters/voice.js +27 -15
  8. package/dist/adapters/voice.js.map +1 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +2 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/internal.d.ts +1 -1
  13. package/dist/internal.js +6 -6
  14. package/dist/model-context/frame/host.d.ts.map +1 -1
  15. package/dist/model-context/frame/host.js +2 -1
  16. package/dist/model-context/frame/host.js.map +1 -1
  17. package/dist/model-context/registry.d.ts.map +1 -1
  18. package/dist/model-context/registry.js +16 -4
  19. package/dist/model-context/registry.js.map +1 -1
  20. package/dist/react/RuntimeAdapter.d.ts +8 -1
  21. package/dist/react/RuntimeAdapter.d.ts.map +1 -1
  22. package/dist/react/RuntimeAdapter.js +23 -18
  23. package/dist/react/RuntimeAdapter.js.map +1 -1
  24. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.js +28 -12
  26. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  27. package/dist/react/client/AdaptedRemoteThread.d.ts +11 -0
  28. package/dist/react/client/AdaptedRemoteThread.d.ts.map +1 -0
  29. package/dist/react/client/AdaptedRemoteThread.js +27 -0
  30. package/dist/react/client/AdaptedRemoteThread.js.map +1 -0
  31. package/dist/react/client/InMemoryThreadList.d.ts +19 -0
  32. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -0
  33. package/dist/react/client/InMemoryThreadList.js +418 -0
  34. package/dist/react/client/InMemoryThreadList.js.map +1 -0
  35. package/dist/react/client/Interactables.d.ts.map +1 -1
  36. package/dist/react/client/Interactables.js +159 -102
  37. package/dist/react/client/Interactables.js.map +1 -1
  38. package/dist/react/client/RemoteThreadList.d.ts +34 -0
  39. package/dist/react/client/RemoteThreadList.d.ts.map +1 -0
  40. package/dist/react/client/RemoteThreadList.js +826 -0
  41. package/dist/react/client/RemoteThreadList.js.map +1 -0
  42. package/dist/react/client/Tools.d.ts.map +1 -1
  43. package/dist/react/client/Tools.js +38 -27
  44. package/dist/react/client/Tools.js.map +1 -1
  45. package/dist/react/client/interactable-model-context.d.ts.map +1 -1
  46. package/dist/react/client/interactable-model-context.js +5 -1
  47. package/dist/react/client/interactable-model-context.js.map +1 -1
  48. package/dist/react/index.d.ts +7 -2
  49. package/dist/react/index.js +5 -2
  50. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  51. package/dist/react/interactables-legacy/Interactables.js +145 -110
  52. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  53. package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
  54. package/dist/react/primitive-hooks/useActionBarCopy.js +13 -18
  55. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  56. package/dist/react/primitive-hooks/useActionBarEdit.d.ts.map +1 -1
  57. package/dist/react/primitive-hooks/useActionBarEdit.js +2 -4
  58. package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
  59. package/dist/react/primitive-hooks/useActionBarReload.d.ts.map +1 -1
  60. package/dist/react/primitive-hooks/useActionBarReload.js +2 -4
  61. package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
  62. package/dist/react/primitive-hooks/useBranchPickerNext.d.ts.map +1 -1
  63. package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -6
  64. package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
  65. package/dist/react/primitive-hooks/useBranchPickerPrevious.d.ts.map +1 -1
  66. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -6
  67. package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
  68. package/dist/react/primitive-hooks/useComposerCancel.d.ts.map +1 -1
  69. package/dist/react/primitive-hooks/useComposerCancel.js +2 -4
  70. package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
  71. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  72. package/dist/react/primitive-hooks/useComposerSend.js +2 -4
  73. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  74. package/dist/react/primitive-hooks/useSuggestionTrigger.d.ts.map +1 -1
  75. package/dist/react/primitive-hooks/useSuggestionTrigger.js +6 -2
  76. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  77. package/dist/react/primitives/message/MessageParts.d.ts +13 -0
  78. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  79. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  80. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  81. package/dist/react/primitives/thread/ThreadMessages.js +1 -1
  82. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  83. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +22 -10
  84. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  85. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +109 -71
  86. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  87. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +8 -2
  88. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  89. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +40 -13
  90. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  91. package/dist/react/runtimes/RemoteThreadResource.d.ts +30 -0
  92. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -0
  93. package/dist/react/runtimes/RemoteThreadResource.js +134 -0
  94. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -0
  95. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts +3 -10
  96. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
  97. package/dist/react/runtimes/RuntimeAdapterProvider.js +3 -6
  98. package/dist/react/runtimes/RuntimeAdapterProvider.js.map +1 -1
  99. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts +17 -0
  100. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts.map +1 -0
  101. package/dist/react/runtimes/assistant-transport/commandQueue.js +55 -0
  102. package/dist/react/runtimes/assistant-transport/commandQueue.js.map +1 -0
  103. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts +11 -0
  104. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
  105. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js +165 -0
  106. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js.map +1 -0
  107. package/dist/react/runtimes/assistant-transport/runManager.d.ts +15 -0
  108. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -0
  109. package/dist/react/runtimes/assistant-transport/runManager.js +86 -0
  110. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -0
  111. package/dist/react/runtimes/assistant-transport/types.d.ts +130 -0
  112. package/dist/react/runtimes/assistant-transport/types.d.ts.map +1 -0
  113. package/dist/react/runtimes/assistant-transport/types.js +0 -0
  114. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts +14 -0
  115. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -0
  116. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +296 -0
  117. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -0
  118. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts +7 -0
  119. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts.map +1 -0
  120. package/dist/react/runtimes/assistant-transport/useConvertedState.js +25 -0
  121. package/dist/react/runtimes/assistant-transport/useConvertedState.js.map +1 -0
  122. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts +7 -0
  123. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts.map +1 -0
  124. package/dist/react/runtimes/assistant-transport/useLatestRef.js +27 -0
  125. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -0
  126. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  127. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +39 -13
  128. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  129. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +2 -1
  130. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  131. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -3
  132. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  133. package/dist/react/runtimes/cloud/auiV0.d.ts +4 -0
  134. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  135. package/dist/react/runtimes/cloud/auiV0.js +7 -0
  136. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  137. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  138. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +22 -12
  139. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  140. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts +16 -0
  141. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts.map +1 -0
  142. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js +36 -0
  143. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -0
  144. package/dist/react/runtimes/external-message-converter.js +14 -14
  145. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  146. package/dist/react/runtimes/useExternalStoreRuntime.d.ts.map +1 -1
  147. package/dist/react/runtimes/useExternalStoreRuntime.js +44 -28
  148. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  149. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
  150. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  151. package/dist/react/runtimes/useRuntimeAdapters.d.ts +9 -0
  152. package/dist/react/runtimes/useRuntimeAdapters.d.ts.map +1 -0
  153. package/dist/react/runtimes/useRuntimeAdapters.js +20 -0
  154. package/dist/react/runtimes/useRuntimeAdapters.js.map +1 -0
  155. package/dist/runtime/api/composer-runtime.d.ts +5 -0
  156. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  157. package/dist/runtime/api/composer-runtime.js.map +1 -1
  158. package/dist/runtime/api/message-runtime.js +1 -1
  159. package/dist/runtime/api/thread-list-runtime.d.ts +1 -0
  160. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  161. package/dist/runtime/api/thread-list-runtime.js +4 -2
  162. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  163. package/dist/runtime/api/thread-runtime.d.ts +12 -2
  164. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  165. package/dist/runtime/api/thread-runtime.js +13 -3
  166. package/dist/runtime/api/thread-runtime.js.map +1 -1
  167. package/dist/runtime/base/base-composer-runtime-core.d.ts +25 -0
  168. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  169. package/dist/runtime/base/base-composer-runtime-core.js +72 -18
  170. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  171. package/dist/runtime/base/base-thread-runtime-core.d.ts +15 -0
  172. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  173. package/dist/runtime/base/base-thread-runtime-core.js +34 -3
  174. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  175. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  176. package/dist/runtime/base/default-edit-composer-runtime-core.js +1 -6
  177. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  178. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +0 -1
  179. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  180. package/dist/runtime/base/default-thread-composer-runtime-core.js +11 -8
  181. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  182. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  183. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  184. package/dist/runtime/internal.js +5 -5
  185. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +15 -0
  186. package/dist/runtime/queue/external-thread-queue-adapter.d.ts.map +1 -1
  187. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  188. package/dist/runtime/queue/message-queue.js +25 -11
  189. package/dist/runtime/queue/message-queue.js.map +1 -1
  190. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts +8 -0
  191. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts.map +1 -0
  192. package/dist/runtime/utils/thread-runtime-lifecycle.js +11 -0
  193. package/dist/runtime/utils/thread-runtime-lifecycle.js.map +1 -0
  194. package/dist/runtimes/external-store/external-store-adapter.d.ts +7 -0
  195. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  196. package/dist/runtimes/external-store/external-store-shared-options.js.map +1 -1
  197. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  198. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +4 -1
  199. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  200. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +11 -1
  201. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  202. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +78 -20
  203. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  204. package/dist/runtimes/internal.d.ts +1 -1
  205. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
  206. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  207. package/dist/runtimes/local/local-thread-runtime-core.js +55 -14
  208. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  209. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
  210. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  211. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +3 -0
  212. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  213. package/dist/runtimes/remote-thread-list/empty-thread-core.js +3 -0
  214. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  215. package/dist/runtimes/remote-thread-list/types.d.ts +29 -1
  216. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  217. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +1 -12
  218. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  219. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +67 -38
  220. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  221. package/dist/store/clients/external-thread.d.ts +7 -0
  222. package/dist/store/clients/external-thread.d.ts.map +1 -1
  223. package/dist/store/clients/external-thread.js +460 -369
  224. package/dist/store/clients/external-thread.js.map +1 -1
  225. package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
  226. package/dist/store/clients/runtime-adapter.js +5 -2
  227. package/dist/store/clients/runtime-adapter.js.map +1 -1
  228. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  229. package/dist/store/clients/single-thread-list.js +31 -27
  230. package/dist/store/clients/single-thread-list.js.map +1 -1
  231. package/dist/store/clients/suggestions.d.ts +3 -1
  232. package/dist/store/clients/suggestions.d.ts.map +1 -1
  233. package/dist/store/clients/suggestions.js +65 -37
  234. package/dist/store/clients/suggestions.js.map +1 -1
  235. package/dist/store/clients/thread-selection-events.d.ts +10 -0
  236. package/dist/store/clients/thread-selection-events.d.ts.map +1 -0
  237. package/dist/store/clients/thread-selection-events.js +40 -0
  238. package/dist/store/clients/thread-selection-events.js.map +1 -0
  239. package/dist/store/env.d.ts +5 -0
  240. package/dist/store/env.d.ts.map +1 -0
  241. package/dist/store/env.js +12 -0
  242. package/dist/store/env.js.map +1 -0
  243. package/dist/store/index.d.ts +6 -4
  244. package/dist/store/index.js +5 -3
  245. package/dist/store/internal.d.ts +4 -1
  246. package/dist/store/internal.js +4 -1
  247. package/dist/store/primitive-predicates.d.ts +19 -0
  248. package/dist/store/primitive-predicates.d.ts.map +1 -0
  249. package/dist/store/primitive-predicates.js +19 -0
  250. package/dist/store/primitive-predicates.js.map +1 -0
  251. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  252. package/dist/store/runtime-clients/attachment-runtime-client.js +18 -11
  253. package/dist/store/runtime-clients/attachment-runtime-client.js.map +1 -1
  254. package/dist/store/runtime-clients/handle-runtime-action.d.ts +5 -0
  255. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -0
  256. package/dist/store/runtime-clients/handle-runtime-action.js +12 -0
  257. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -0
  258. package/dist/store/runtime-clients/handle-thread-list-action.d.ts +5 -0
  259. package/dist/store/runtime-clients/handle-thread-list-action.d.ts.map +1 -0
  260. package/dist/store/runtime-clients/handle-thread-list-action.js +7 -0
  261. package/dist/store/runtime-clients/handle-thread-list-action.js.map +1 -0
  262. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  263. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +104 -57
  264. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  265. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  266. package/dist/store/runtime-clients/thread-list-runtime-client.js +5 -6
  267. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  268. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  269. package/dist/store/runtime-clients/thread-runtime-client.js +98 -82
  270. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  271. package/dist/store/scope-registration.d.ts +2 -2
  272. package/dist/store/scopes/composer.d.ts +9 -4
  273. package/dist/store/scopes/composer.d.ts.map +1 -1
  274. package/dist/store/scopes/suggestions.d.ts +6 -1
  275. package/dist/store/scopes/suggestions.d.ts.map +1 -1
  276. package/dist/store/scopes/thread-list-item.d.ts +7 -6
  277. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  278. package/dist/store/scopes/thread.d.ts +25 -7
  279. package/dist/store/scopes/thread.d.ts.map +1 -1
  280. package/dist/store/scopes/threads.d.ts +17 -1
  281. package/dist/store/scopes/threads.d.ts.map +1 -1
  282. package/dist/subscribable/subscribable.d.ts +2 -1
  283. package/dist/subscribable/subscribable.d.ts.map +1 -1
  284. package/dist/subscribable/subscribable.js +23 -18
  285. package/dist/subscribable/subscribable.js.map +1 -1
  286. package/dist/types/error.d.ts +15 -1
  287. package/dist/types/error.d.ts.map +1 -1
  288. package/dist/types/error.js +18 -1
  289. package/dist/types/error.js.map +1 -1
  290. package/dist/types/message.d.ts +2 -0
  291. package/dist/types/message.d.ts.map +1 -1
  292. package/dist/types/message.js.map +1 -1
  293. package/dist/utils/composite-context-provider.d.ts +1 -0
  294. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  295. package/dist/utils/composite-context-provider.js +25 -9
  296. package/dist/utils/composite-context-provider.js.map +1 -1
  297. package/dist/utils/notify-event-listeners.d.ts +2 -2
  298. package/dist/utils/notify-event-listeners.d.ts.map +1 -1
  299. package/dist/utils/notify-event-listeners.js +2 -2
  300. package/dist/utils/notify-event-listeners.js.map +1 -1
  301. package/package.json +7 -7
  302. package/src/adapters/speech.test.ts +118 -1
  303. package/src/adapters/speech.ts +21 -22
  304. package/src/adapters/thread-history.ts +2 -0
  305. package/src/adapters/voice.test.ts +69 -0
  306. package/src/adapters/voice.ts +27 -27
  307. package/src/index.ts +2 -0
  308. package/src/model-context/frame/host.test.ts +23 -0
  309. package/src/model-context/frame/host.ts +2 -1
  310. package/src/model-context/registry.test.ts +131 -0
  311. package/src/model-context/registry.ts +16 -6
  312. package/src/react/RuntimeAdapter.test.ts +2 -0
  313. package/src/react/RuntimeAdapter.ts +20 -8
  314. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +34 -17
  315. package/src/react/client/AdaptedRemoteThread.ts +41 -0
  316. package/src/react/client/InMemoryThreadList.test.tsx +148 -0
  317. package/src/react/client/InMemoryThreadList.ts +290 -0
  318. package/src/react/client/Interactables.test.ts +326 -2
  319. package/src/react/client/Interactables.ts +227 -110
  320. package/src/react/client/RemoteThreadList.test.ts +1026 -0
  321. package/src/react/client/RemoteThreadList.ts +1067 -0
  322. package/src/react/client/Tools.ts +36 -29
  323. package/src/react/client/interactable-model-context.ts +6 -1
  324. package/src/react/index.ts +13 -0
  325. package/src/react/interactables-legacy/Interactables.test.ts +317 -0
  326. package/src/react/interactables-legacy/Interactables.ts +204 -128
  327. package/src/react/primitive-hooks/useActionBarCopy.ts +2 -7
  328. package/src/react/primitive-hooks/useActionBarEdit.ts +2 -1
  329. package/src/react/primitive-hooks/useActionBarReload.ts +2 -6
  330. package/src/react/primitive-hooks/useBranchPickerNext.ts +2 -7
  331. package/src/react/primitive-hooks/useBranchPickerPrevious.ts +2 -7
  332. package/src/react/primitive-hooks/useComposerCancel.ts +2 -1
  333. package/src/react/primitive-hooks/useComposerSend.ts +2 -5
  334. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +11 -0
  335. package/src/react/primitive-hooks/useSuggestionTrigger.ts +4 -5
  336. package/src/react/primitives/message/MessageParts.tsx +0 -1
  337. package/src/react/primitives/thread/ThreadMessages.tsx +2 -1
  338. package/src/react/runtimes/RemoteThreadListHookInstanceManager.retry.test.tsx +116 -0
  339. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +1 -1
  340. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +57 -6
  341. package/src/react/runtimes/RemoteThreadListHookInstanceManager.title.test.tsx +272 -0
  342. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +159 -124
  343. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +59 -20
  344. package/src/react/runtimes/RemoteThreadResource.test.ts +141 -0
  345. package/src/react/runtimes/RemoteThreadResource.ts +231 -0
  346. package/src/react/runtimes/RuntimeAdapterProvider.tsx +8 -20
  347. package/src/react/runtimes/assistant-transport/commandQueue.ts +68 -0
  348. package/src/react/runtimes/assistant-transport/replayBoundaryStream.test.ts +455 -0
  349. package/src/react/runtimes/assistant-transport/replayBoundaryStream.ts +172 -0
  350. package/src/react/runtimes/assistant-transport/runManager.ts +124 -0
  351. package/src/react/runtimes/assistant-transport/transport-delivery.test.tsx +79 -0
  352. package/src/react/runtimes/assistant-transport/transport-parentid.test.tsx +97 -0
  353. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +414 -0
  354. package/src/react/runtimes/assistant-transport/types.ts +170 -0
  355. package/src/react/runtimes/assistant-transport/useAssistantTransport.spec.md +141 -0
  356. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +625 -0
  357. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +481 -0
  358. package/src/react/runtimes/assistant-transport/useConvertedState.ts +20 -0
  359. package/src/react/runtimes/assistant-transport/useLatestRef.ts +9 -0
  360. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +131 -6
  361. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +61 -15
  362. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +6 -4
  363. package/src/react/runtimes/cloud/auiV0.ts +17 -1
  364. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +36 -21
  365. package/src/react/runtimes/cloud/useCloudThreadListRuntime.test.tsx +56 -0
  366. package/src/react/runtimes/cloud/useCloudThreadListRuntime.ts +31 -0
  367. package/src/react/runtimes/external-message-converter.test.ts +143 -0
  368. package/src/react/runtimes/external-message-converter.ts +41 -41
  369. package/src/react/runtimes/remote-thread-hook-on-tap.test.ts +58 -0
  370. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +83 -0
  371. package/src/react/runtimes/useExternalStoreRuntime.suggestions.test.tsx +124 -0
  372. package/src/react/runtimes/useExternalStoreRuntime.ts +7 -0
  373. package/src/react/runtimes/useExternalStoreSharedOptions.ts +3 -1
  374. package/src/react/runtimes/useLocalRuntime.test.tsx +130 -4
  375. package/src/react/runtimes/useRuntimeAdapters.test.tsx +62 -0
  376. package/src/react/runtimes/useRuntimeAdapters.ts +28 -0
  377. package/src/runtime/api/composer-runtime.ts +5 -0
  378. package/src/runtime/api/thread-list-runtime.ts +3 -1
  379. package/src/runtime/api/thread-runtime.ts +28 -3
  380. package/src/runtime/base/base-composer-runtime-core.ts +106 -13
  381. package/src/runtime/base/base-thread-runtime-core.test.ts +191 -0
  382. package/src/runtime/base/base-thread-runtime-core.ts +47 -2
  383. package/src/runtime/base/default-edit-composer-runtime-core.ts +1 -15
  384. package/src/runtime/base/default-thread-composer-runtime-core.ts +13 -15
  385. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  386. package/src/runtime/queue/external-thread-queue-adapter.ts +17 -0
  387. package/src/runtime/queue/message-queue.ts +28 -10
  388. package/src/runtime/utils/thread-runtime-lifecycle.ts +18 -0
  389. package/src/runtimes/external-store/external-store-adapter.ts +7 -0
  390. package/src/runtimes/external-store/external-store-shared-options.ts +3 -1
  391. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
  392. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +187 -48
  393. package/src/runtimes/local/local-thread-runtime-core.test.ts +373 -4
  394. package/src/runtimes/local/local-thread-runtime-core.ts +83 -15
  395. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +4 -0
  396. package/src/runtimes/remote-thread-list/empty-thread-core.ts +4 -0
  397. package/src/runtimes/remote-thread-list/types.ts +30 -0
  398. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -6
  399. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +243 -0
  400. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +127 -61
  401. package/src/store/clients/external-thread.ts +119 -16
  402. package/src/store/clients/model-context-client.test.ts +16 -8
  403. package/src/store/clients/runtime-adapter.ts +5 -2
  404. package/src/store/clients/single-thread-list.ts +2 -1
  405. package/src/store/clients/suggestions.ts +39 -13
  406. package/src/store/clients/thread-selection-events.ts +21 -0
  407. package/src/store/env.ts +11 -0
  408. package/src/store/index.ts +14 -1
  409. package/src/store/internal.ts +13 -0
  410. package/src/store/primitive-predicates.test.ts +184 -0
  411. package/src/store/primitive-predicates.ts +43 -0
  412. package/src/store/runtime-clients/attachment-runtime-client.test.ts +41 -0
  413. package/src/store/runtime-clients/attachment-runtime-client.ts +2 -1
  414. package/src/store/runtime-clients/handle-runtime-action.ts +11 -0
  415. package/src/store/runtime-clients/handle-thread-list-action.test.ts +69 -0
  416. package/src/store/runtime-clients/handle-thread-list-action.ts +6 -0
  417. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +28 -35
  418. package/src/store/runtime-clients/thread-list-runtime-client.ts +9 -6
  419. package/src/store/runtime-clients/thread-runtime-client.ts +5 -0
  420. package/src/store/scopes/composer.ts +9 -4
  421. package/src/store/scopes/suggestions.ts +6 -0
  422. package/src/store/scopes/thread-list-item.ts +7 -6
  423. package/src/store/scopes/thread.ts +25 -7
  424. package/src/store/scopes/threads.ts +14 -0
  425. package/src/subscribable/subscribable.test.ts +77 -1
  426. package/src/subscribable/subscribable.ts +38 -23
  427. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-unarchive.test.ts +25 -0
  428. package/src/tests/append-interactable-snapshots.test.ts +350 -0
  429. package/src/tests/auiV0Encode.test.ts +66 -0
  430. package/src/tests/base-composer-runtime-core-send.test.ts +156 -0
  431. package/src/tests/base-composer-runtime-core.test.ts +136 -1
  432. package/src/tests/composer-can-cancel.test.ts +101 -0
  433. package/src/tests/default-edit-composer-runtime-core.test.ts +0 -68
  434. package/src/tests/external-store-thread-list-runtime-core.test.ts +101 -0
  435. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +491 -1
  436. package/src/tests/external-store-thread-runtime-core.test.ts +124 -0
  437. package/src/tests/external-thread-attachments.test.tsx +214 -0
  438. package/src/tests/external-thread-parity.test.tsx +54 -0
  439. package/src/tests/external-thread-refetch.test.tsx +117 -0
  440. package/src/tests/external-thread-suggestions.test.tsx +59 -0
  441. package/src/tests/interactables-toolui-migration.test.tsx +66 -0
  442. package/src/tests/message-queue.test.ts +54 -0
  443. package/src/tests/session-reset-notification.test.ts +145 -0
  444. package/src/tests/thread-message-like.test.ts +39 -0
  445. package/src/tests/thread-switch-events.test.tsx +234 -0
  446. package/src/tests/tools-scope-migration.test.tsx +58 -0
  447. package/src/types/error.ts +24 -0
  448. package/src/types/message.ts +2 -0
  449. package/src/utils/composite-context-provider.test.ts +139 -0
  450. package/src/utils/composite-context-provider.ts +26 -8
  451. package/src/utils/notify-event-listeners.ts +10 -5
  452. package/src/tests/default-thread-composer-runtime-core.test.ts +0 -82
@@ -35,6 +35,20 @@ export type ThreadsMethods = {
35
35
  __internal_getAssistantRuntime?(): AssistantRuntime;
36
36
  };
37
37
 
38
+ export type ThreadsEvents = {
39
+ /**
40
+ * The main thread selection changed. Fires once per switch with the newly
41
+ * selected thread and the thread that was selected before. Does not fire
42
+ * for the initially selected thread on mount. Runtimes that resolve a
43
+ * deep-linked `threadId`/`initialThreadId` after mount (such as
44
+ * `useRemoteThreadListRuntime`) start on a placeholder new thread, so the
45
+ * deep link's resolution fires this event with the placeholder as
46
+ * `previousThreadId`.
47
+ */
48
+ "threads.selectionChanged": { threadId: string; previousThreadId: string };
49
+ };
50
+
38
51
  export type ThreadsClientSchema = {
39
52
  methods: ThreadsMethods;
53
+ events: ThreadsEvents;
40
54
  };
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import type { SubscribableWithState } from "./subscribable";
3
- import { ShallowMemoizeSubject } from "./subscribable";
3
+ import { LazyMemoizeSubject, ShallowMemoizeSubject } from "./subscribable";
4
4
 
5
5
  type TestState = {
6
6
  status: string;
@@ -26,6 +26,9 @@ const createBinding = (initialState: TestState) => {
26
26
  state = nextState;
27
27
  for (const callback of subscribers) callback();
28
28
  },
29
+ replace(nextState: TestState) {
30
+ state = nextState;
31
+ },
29
32
  };
30
33
  };
31
34
 
@@ -55,4 +58,77 @@ describe("ShallowMemoizeSubject", () => {
55
58
 
56
59
  expect(subscriber).not.toHaveBeenCalled();
57
60
  });
61
+
62
+ it("notifies later subscribers when one throws", () => {
63
+ const source = createBinding({ status: "empty" });
64
+ const subject = new ShallowMemoizeSubject(source.binding);
65
+ const error = new Error("subscriber failed");
66
+ const laterSubscriber = vi.fn();
67
+ subject.subscribe(() => {
68
+ throw error;
69
+ });
70
+ subject.subscribe(laterSubscriber);
71
+
72
+ expect(() => source.update({ status: "ready" })).toThrow(error);
73
+ expect(laterSubscriber).toHaveBeenCalledOnce();
74
+ expect(subject.getState()).toEqual({ status: "ready" });
75
+ });
76
+
77
+ it("reads a value that landed while nobody was subscribed", () => {
78
+ const source = createBinding({ status: "empty" });
79
+ const subject = new ShallowMemoizeSubject(source.binding);
80
+ expect(subject.getState()).toEqual({ status: "empty" });
81
+
82
+ source.update({ status: "ready" });
83
+ const subscriber = vi.fn();
84
+ subject.subscribe(subscriber);
85
+
86
+ expect(subject.getState()).toEqual({ status: "ready" });
87
+ expect(subscriber).not.toHaveBeenCalled();
88
+ });
89
+ });
90
+
91
+ describe("LazyMemoizeSubject", () => {
92
+ it("notifies later subscribers when one throws", () => {
93
+ const source = createBinding({ status: "empty" });
94
+ const subject = new LazyMemoizeSubject(source.binding);
95
+ const error = new Error("subscriber failed");
96
+ const laterSubscriber = vi.fn();
97
+ subject.subscribe(() => {
98
+ throw error;
99
+ });
100
+ subject.subscribe(laterSubscriber);
101
+
102
+ expect(() => source.update({ status: "ready" })).toThrow(error);
103
+ expect(laterSubscriber).toHaveBeenCalledOnce();
104
+ expect(subject.getState()).toEqual({ status: "ready" });
105
+ });
106
+
107
+ it("reads a value replaced before connecting", () => {
108
+ const source = createBinding({ status: "empty" });
109
+ const subject = new LazyMemoizeSubject(source.binding);
110
+ expect(subject.getState()).toEqual({ status: "empty" });
111
+
112
+ source.replace({ status: "ready" });
113
+ const subscriber = vi.fn();
114
+ const unsubscribe = subject.subscribe(subscriber);
115
+
116
+ expect(subject.getState()).toEqual({ status: "ready" });
117
+ expect(subscriber).not.toHaveBeenCalled();
118
+ unsubscribe();
119
+ });
120
+
121
+ it("resynchronizes after reconnecting", () => {
122
+ const source = createBinding({ status: "empty" });
123
+ const subject = new LazyMemoizeSubject(source.binding);
124
+ expect(subject.getState()).toEqual({ status: "empty" });
125
+
126
+ const unsubscribe = subject.subscribe(() => {});
127
+ unsubscribe();
128
+ source.replace({ status: "ready" });
129
+ const reconnect = subject.subscribe(() => {});
130
+
131
+ expect(subject.getState()).toEqual({ status: "ready" });
132
+ reconnect();
133
+ });
58
134
  });
@@ -32,6 +32,31 @@ export type EventSubscribable<TEvent extends string> = {
32
32
  >;
33
33
  };
34
34
 
35
+ export const notifySubscribers = <TArgs extends unknown[]>(
36
+ subscribers: Iterable<(...args: TArgs) => void>,
37
+ ...args: TArgs
38
+ ): void => {
39
+ const errors: unknown[] = [];
40
+ for (const callback of subscribers) {
41
+ try {
42
+ callback(...args);
43
+ } catch (error) {
44
+ errors.push(error);
45
+ }
46
+ }
47
+
48
+ if (errors.length === 1) {
49
+ throw errors[0];
50
+ }
51
+
52
+ if (errors.length > 1) {
53
+ for (const error of errors) {
54
+ console.error(error);
55
+ }
56
+ throw new AggregateError(errors);
57
+ }
58
+ };
59
+
35
60
  function shallowEqual<T extends object>(
36
61
  objA: T | undefined,
37
62
  objB: T | undefined,
@@ -70,25 +95,7 @@ export class BaseSubscribable {
70
95
  }
71
96
 
72
97
  protected _notifySubscribers() {
73
- const errors = [];
74
- for (const callback of this._subscribers) {
75
- try {
76
- callback();
77
- } catch (error) {
78
- errors.push(error);
79
- }
80
- }
81
-
82
- if (errors.length > 0) {
83
- if (errors.length === 1) {
84
- throw errors[0];
85
- } else {
86
- for (const error of errors) {
87
- console.error(error);
88
- }
89
- throw new AggregateError(errors);
90
- }
91
- }
98
+ notifySubscribers(this._subscribers);
92
99
  }
93
100
  }
94
101
 
@@ -109,7 +116,7 @@ export abstract class BaseSubject {
109
116
  return;
110
117
  }
111
118
 
112
- for (const callback of this._subscriptions) callback(payload);
119
+ notifySubscribers(this._subscriptions, payload);
113
120
  }
114
121
 
115
122
  private _updateConnection() {
@@ -173,7 +180,9 @@ export class ShallowMemoizeSubject<TState extends object, TPath>
173
180
  }
174
181
  };
175
182
 
176
- return this.binding.subscribe(callback);
183
+ const unsubscribe = this.binding.subscribe(callback);
184
+ this._syncState();
185
+ return unsubscribe;
177
186
  }
178
187
  }
179
188
 
@@ -197,7 +206,11 @@ export class LazyMemoizeSubject<TState extends object, TPath>
197
206
  public getState = () => {
198
207
  if (!this.isConnected || this._previousStateDirty) {
199
208
  const newState = this.binding.getState();
200
- if (newState !== SKIP_UPDATE) {
209
+ if (
210
+ newState !== SKIP_UPDATE &&
211
+ (this._previousState === undefined ||
212
+ !shallowEqual(newState, this._previousState))
213
+ ) {
201
214
  this._previousState = newState;
202
215
  }
203
216
  this._previousStateDirty = false;
@@ -213,7 +226,9 @@ export class LazyMemoizeSubject<TState extends object, TPath>
213
226
  this.notifySubscribers();
214
227
  };
215
228
 
216
- return this.binding.subscribe(callback);
229
+ const unsubscribe = this.binding.subscribe(callback);
230
+ this._previousStateDirty = true;
231
+ return unsubscribe;
217
232
  }
218
233
  }
219
234
 
@@ -63,6 +63,31 @@ describe("RemoteThreadListThreadListRuntimeCore.switchToThread unarchive option"
63
63
  expect(core.mainThreadId).toBe(core.getItemById(THREAD_ID)?.id);
64
64
  });
65
65
 
66
+ it("restores an initialized draft's archived thread when unarchive fails", async () => {
67
+ const THREAD_ID = "archived-initialized-draft";
68
+ const error = new Error("unarchive failed");
69
+ const adapter = makeAdapter({
70
+ list: vi.fn(async () => ({ threads: [archivedThread(THREAD_ID)] })),
71
+ unarchive: vi.fn(async () => {
72
+ throw error;
73
+ }),
74
+ });
75
+
76
+ const core = createCore(adapter);
77
+ await core.getLoadThreadsPromise();
78
+ const draftId = core.newThreadId;
79
+ if (draftId === undefined) throw new Error("Expected seeded draft");
80
+ await core.initialize(draftId);
81
+ expect(core.newThreadId).toBeUndefined();
82
+ await core.switchToThread(THREAD_ID, { unarchive: false });
83
+
84
+ await expect(core.unarchive(THREAD_ID)).rejects.toBe(error);
85
+ expect(core.mainThreadId).not.toBe(THREAD_ID);
86
+ expect(core.getItemById(THREAD_ID)?.status).toBe("archived");
87
+ expect(core.archivedThreadIds).toContain(THREAD_ID);
88
+ expect(core.threadIds).not.toContain(THREAD_ID);
89
+ });
90
+
66
91
  it("does not call unarchive when the target is already regular, regardless of option", async () => {
67
92
  const THREAD_ID = "regular-target";
68
93
  const adapter = makeAdapter({
@@ -0,0 +1,350 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { ExternalStoreRuntimeCore } from "../runtimes/external-store/external-store-runtime-core";
3
+ import { LocalRuntimeCore } from "../runtimes/local/local-runtime-core";
4
+ import { createMessageQueue } from "../runtime/queue/message-queue";
5
+ import { getThreadMessageText } from "../utils/text";
6
+ import type { ChatModelAdapter } from "../runtime/utils/chat-model-adapter";
7
+ import type { AppendMessage, ThreadMessage } from "../types/message";
8
+
9
+ const live = (state: unknown) => ({
10
+ interactables: [{ id: "n1", name: "note", state }],
11
+ });
12
+
13
+ const snapshotMessage = (id: string, state: unknown) =>
14
+ ({
15
+ id,
16
+ role: "user",
17
+ createdAt: new Date(),
18
+ content: [{ type: "text", text: "old" }],
19
+ attachments: [],
20
+ metadata: {
21
+ custom: { interactables: [{ id: "n1", name: "note", state }] },
22
+ },
23
+ }) as ThreadMessage;
24
+
25
+ const userMessage = (text: string, parentId: string | null): AppendMessage => ({
26
+ parentId,
27
+ sourceId: null,
28
+ runConfig: {},
29
+ role: "user",
30
+ content: [{ type: "text", text }],
31
+ attachments: [],
32
+ metadata: { custom: {} },
33
+ createdAt: new Date(),
34
+ });
35
+
36
+ const externalThread = (options?: {
37
+ messages?: ThreadMessage[];
38
+ composerMetadata?: Record<string, unknown>;
39
+ }) => {
40
+ const onNew = vi.fn(async () => {});
41
+ const onEdit = vi.fn(async () => {});
42
+ const core = new ExternalStoreRuntimeCore({
43
+ messages: options?.messages ?? [],
44
+ onNew,
45
+ onEdit,
46
+ });
47
+ if (options?.composerMetadata) {
48
+ core.registerModelContextProvider({
49
+ getModelContext: () => ({
50
+ unstable_composerMetadata: options.composerMetadata,
51
+ }),
52
+ });
53
+ }
54
+ return { thread: core.threads.getMainThreadRuntimeCore(), onNew, onEdit };
55
+ };
56
+
57
+ const appendedSnapshots = (sink: ReturnType<typeof vi.fn>) =>
58
+ (sink.mock.calls[0]![0] as AppendMessage).metadata?.custom?.interactables;
59
+
60
+ const tailId = (messages: ThreadMessage[]) => messages.at(-1)?.id ?? null;
61
+
62
+ describe("interactable snapshots on the append seam", () => {
63
+ it("stamps a baseline on the first send of the thread", async () => {
64
+ const { thread, onNew } = externalThread({
65
+ composerMetadata: live({ v: 1 }),
66
+ });
67
+ await thread.append(userMessage("hello", null));
68
+ expect(appendedSnapshots(onNew)).toEqual([
69
+ { id: "n1", name: "note", state: { v: 1 } },
70
+ ]);
71
+ });
72
+
73
+ it("stamps nothing when the history already carries the live state", async () => {
74
+ const messages = [snapshotMessage("msg-1", { v: 1 })];
75
+ const { thread, onNew } = externalThread({
76
+ messages,
77
+ composerMetadata: live({ v: 1 }),
78
+ });
79
+ await thread.append(userMessage("hello", tailId(messages)));
80
+ expect(appendedSnapshots(onNew)).toBeUndefined();
81
+ });
82
+
83
+ it("stamps the new state when it diverged from the latest snapshot", async () => {
84
+ const messages = [snapshotMessage("msg-1", { v: 1 })];
85
+ const { thread, onNew } = externalThread({
86
+ messages,
87
+ composerMetadata: live({ v: 2 }),
88
+ });
89
+ await thread.append(userMessage("hello", tailId(messages)));
90
+ expect(appendedSnapshots(onNew)).toEqual([
91
+ { id: "n1", name: "note", state: { v: 2 } },
92
+ ]);
93
+ });
94
+
95
+ it("stamps a programmatic append, the path suggestions and thread.append() take", async () => {
96
+ const { thread, onNew } = externalThread({
97
+ composerMetadata: live({ tasks: [] }),
98
+ });
99
+ await thread.append(userMessage("Add 3 tasks for a grocery run", null));
100
+ expect(appendedSnapshots(onNew)).toEqual([
101
+ { id: "n1", name: "note", state: { tasks: [] } },
102
+ ]);
103
+ });
104
+
105
+ it("leaves a non-user append unstamped, since no reader folds those", async () => {
106
+ const { thread, onNew } = externalThread({
107
+ composerMetadata: live({ v: 1 }),
108
+ });
109
+ await thread.append({
110
+ ...userMessage("an assistant turn", null),
111
+ role: "assistant",
112
+ } as AppendMessage);
113
+ expect(appendedSnapshots(onNew)).toBeUndefined();
114
+ });
115
+
116
+ it("re-stamps the baseline when the edited message carried the only snapshot", async () => {
117
+ const messages = [snapshotMessage("msg-1", { v: 1 })];
118
+ const { thread, onEdit } = externalThread({
119
+ messages,
120
+ composerMetadata: live({ v: 1 }),
121
+ });
122
+ // An edit branches off the edited message's parent, so the prefix it is
123
+ // gated against excludes the edited message and its snapshot.
124
+ await thread.append({ ...userMessage("new", null), sourceId: "msg-1" });
125
+ expect(appendedSnapshots(onEdit)).toEqual([
126
+ { id: "n1", name: "note", state: { v: 1 } },
127
+ ]);
128
+ });
129
+
130
+ it("stamps nothing when the edit's branch prefix already carries the live state", async () => {
131
+ const messages = [
132
+ snapshotMessage("parent-1", { v: 1 }),
133
+ snapshotMessage("msg-1", { v: 1 }),
134
+ ];
135
+ const { thread, onEdit } = externalThread({
136
+ messages,
137
+ composerMetadata: live({ v: 1 }),
138
+ });
139
+ await thread.append({
140
+ ...userMessage("new", "parent-1"),
141
+ sourceId: "msg-1",
142
+ });
143
+ expect(appendedSnapshots(onEdit)).toBeUndefined();
144
+ });
145
+
146
+ it("stamps on the local runtime core too", async () => {
147
+ const run = vi.fn(async function* () {
148
+ yield { content: [{ type: "text" as const, text: "ok" }] };
149
+ } as unknown as ChatModelAdapter["run"]);
150
+ const core = new LocalRuntimeCore(
151
+ { adapters: { chatModel: { run } } },
152
+ undefined,
153
+ );
154
+ core.registerModelContextProvider({
155
+ getModelContext: () => ({
156
+ unstable_composerMetadata: live({ v: 1 }),
157
+ }),
158
+ });
159
+ const thread = core.threads.getMainThreadRuntimeCore();
160
+ await thread.append(userMessage("hello", null));
161
+ expect(thread.messages[0]!.metadata.custom?.interactables).toEqual([
162
+ { id: "n1", name: "note", state: { v: 1 } },
163
+ ]);
164
+ });
165
+ });
166
+
167
+ describe("queued sends gate at dispatch, not at enqueue", () => {
168
+ const queuedExternalThread = () => {
169
+ const run = vi.fn();
170
+ const onNew = vi.fn(async () => {});
171
+ const queue = createMessageQueue({ run });
172
+ let composerMetadata: Record<string, unknown> = live({ v: 1 });
173
+ const store = (messages: ThreadMessage[]) => ({
174
+ messages,
175
+ onNew,
176
+ queue: queue.adapter,
177
+ });
178
+ const core = new ExternalStoreRuntimeCore(store([]));
179
+ core.registerModelContextProvider({
180
+ getModelContext: () => ({ unstable_composerMetadata: composerMetadata }),
181
+ });
182
+ return {
183
+ thread: core.threads.getMainThreadRuntimeCore(),
184
+ queue,
185
+ run,
186
+ advanceRecord: (messages: ThreadMessage[], live: unknown) => {
187
+ composerMetadata = live as Record<string, unknown>;
188
+ core.setAdapter(store(messages));
189
+ },
190
+ };
191
+ };
192
+
193
+ const dispatched = (run: ReturnType<typeof vi.fn>) =>
194
+ (run.mock.calls[0]![0] as AppendMessage).metadata?.custom?.interactables;
195
+
196
+ it("drops the enqueue-time stamp once the run has told the model that state", async () => {
197
+ const t = queuedExternalThread();
198
+ t.queue.notifyBusy();
199
+ await t.thread.append(userMessage("queued", null));
200
+ expect(t.run).not.toHaveBeenCalled();
201
+
202
+ // the run moved the record to v:2 and the live state matches it
203
+ t.advanceRecord([snapshotMessage("msg-1", { v: 2 })], live({ v: 2 }));
204
+ t.queue.notifyIdle();
205
+
206
+ expect(t.run).toHaveBeenCalledTimes(1);
207
+ expect(dispatched(t.run)).toBeUndefined();
208
+ });
209
+
210
+ it("stamps the state as of the flush when it still diverges", async () => {
211
+ const t = queuedExternalThread();
212
+ t.queue.notifyBusy();
213
+ await t.thread.append(userMessage("queued", null));
214
+
215
+ // the run moved the record to v:2 while the user edited on to v:3
216
+ t.advanceRecord([snapshotMessage("msg-1", { v: 2 })], live({ v: 3 }));
217
+ t.queue.notifyIdle();
218
+
219
+ expect(dispatched(t.run)).toEqual([
220
+ { id: "n1", name: "note", state: { v: 3 } },
221
+ ]);
222
+ });
223
+
224
+ // interrupt dispatches synchronously inside `append`, so nothing can go
225
+ // stale in between; what matters is that this second dispatch point stamps
226
+ // at all, the way advance does.
227
+ it("stamps on the interrupt path a mid-run steer takes", async () => {
228
+ const run = vi.fn();
229
+ const cancel = vi.fn();
230
+ const onNew = vi.fn(async () => {});
231
+ const queue = createMessageQueue({ run, cancel });
232
+ let composerMetadata: Record<string, unknown> = live({ v: 1 });
233
+ const store = (messages: ThreadMessage[]) => ({
234
+ messages,
235
+ onNew,
236
+ queue: queue.adapter,
237
+ isRunning: true,
238
+ });
239
+ const core = new ExternalStoreRuntimeCore(store([]));
240
+ core.registerModelContextProvider({
241
+ getModelContext: () => ({ unstable_composerMetadata: composerMetadata }),
242
+ });
243
+ const thread = core.threads.getMainThreadRuntimeCore();
244
+
245
+ queue.notifyBusy();
246
+ composerMetadata = live({ v: 2 });
247
+ // isRunning routes this to the steer lane, and a cancel-capable driver
248
+ // makes that an interrupt rather than a buffered enqueue
249
+ await thread.append(
250
+ userMessage("steered", thread.messages.at(-1)?.id ?? null),
251
+ );
252
+
253
+ expect(cancel).toHaveBeenCalled();
254
+ expect(dispatched(run)).toEqual([
255
+ { id: "n1", name: "note", state: { v: 2 } },
256
+ ]);
257
+ });
258
+
259
+ it("re-points a flushed message at the tail it was gated against", async () => {
260
+ const t = queuedExternalThread();
261
+ t.queue.notifyBusy();
262
+ await t.thread.append(userMessage("queued", null));
263
+
264
+ t.advanceRecord([snapshotMessage("msg-1", { v: 2 })], live({ v: 3 }));
265
+ t.queue.notifyIdle();
266
+
267
+ // routing and gating have to name the same point, or the branch the
268
+ // message lands on is not the one its snapshot was computed against
269
+ expect((t.run.mock.calls[0]![0] as AppendMessage).parentId).toBe("msg-1");
270
+ });
271
+
272
+ it("leaves a hand-rolled adapter stamping at enqueue", async () => {
273
+ const enqueue = vi.fn();
274
+ const onNew = vi.fn(async () => {});
275
+ const core = new ExternalStoreRuntimeCore({
276
+ messages: [],
277
+ onNew,
278
+ // no __internal_setDispatchTransform: nothing re-gates on the way out,
279
+ // so the runtime has to stamp before handing the message over
280
+ queue: {
281
+ items: [],
282
+ steerItems: [],
283
+ enqueue,
284
+ steer: vi.fn(),
285
+ move: vi.fn(),
286
+ edit: vi.fn(),
287
+ remove: vi.fn(),
288
+ },
289
+ });
290
+ core.registerModelContextProvider({
291
+ getModelContext: () => ({ unstable_composerMetadata: live({ v: 1 }) }),
292
+ });
293
+ const thread = core.threads.getMainThreadRuntimeCore();
294
+
295
+ await thread.append(userMessage("queued", null));
296
+ expect(dispatched(enqueue)).toEqual([
297
+ { id: "n1", name: "note", state: { v: 1 } },
298
+ ]);
299
+ });
300
+
301
+ it("gates a queued local send against the tail it flushes onto", async () => {
302
+ let composerMetadata: Record<string, unknown> = live({ v: 1 });
303
+ let releaseFirstRun!: () => void;
304
+ const firstRunGate = new Promise<void>((resolve) => {
305
+ releaseFirstRun = resolve;
306
+ });
307
+ let runs = 0;
308
+ const chatModel: ChatModelAdapter = {
309
+ run: async function* () {
310
+ if (runs++ === 0) await firstRunGate;
311
+ yield { content: [{ type: "text" as const, text: "ok" }] };
312
+ },
313
+ };
314
+ const core = new LocalRuntimeCore(
315
+ { adapters: { chatModel }, unstable_enableMessageQueue: true },
316
+ undefined,
317
+ );
318
+ core.registerModelContextProvider({
319
+ getModelContext: () => ({ unstable_composerMetadata: composerMetadata }),
320
+ });
321
+ const thread = core.threads.getMainThreadRuntimeCore();
322
+ const settle = () => new Promise((resolve) => setTimeout(resolve, 20));
323
+
324
+ void thread.append(userMessage("first", null));
325
+ await settle();
326
+
327
+ // the first run is still open, so this one buffers with the live state at v:1
328
+ void thread.append(
329
+ userMessage("second", thread.messages.at(-1)?.id ?? null),
330
+ );
331
+ await settle();
332
+ expect(
333
+ thread.messages.some(
334
+ (m) => m.role === "user" && getThreadMessageText(m) === "second",
335
+ ),
336
+ ).toBe(false);
337
+
338
+ composerMetadata = live({ v: 2 });
339
+ releaseFirstRun();
340
+ await settle();
341
+ await settle();
342
+
343
+ const second = thread.messages.find(
344
+ (m) => m.role === "user" && getThreadMessageText(m) === "second",
345
+ );
346
+ expect(second!.metadata.custom?.interactables).toEqual([
347
+ { id: "n1", name: "note", state: { v: 2 } },
348
+ ]);
349
+ });
350
+ });
@@ -330,6 +330,33 @@ describe("auiV0Encode", () => {
330
330
  { type: "data", name: "telemetry", data: { runs: 3 } },
331
331
  ]);
332
332
  });
333
+
334
+ it("preserves data message parts verbatim in the core cloud encoder", () => {
335
+ const encoded = auiV0Encode({
336
+ id: "m1",
337
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
338
+ role: "assistant",
339
+ status: { type: "complete", reason: "stop" },
340
+ metadata: {
341
+ unstable_state: null,
342
+ unstable_annotations: [],
343
+ unstable_data: [],
344
+ steps: [],
345
+ custom: {},
346
+ },
347
+ content: [
348
+ { type: "text", text: "planned" },
349
+ { type: "data", name: "PredictState", data: { steps: ["a"] } },
350
+ { type: "data", name: "PredictState", data: '{"steps":["a","b"]}' },
351
+ ],
352
+ });
353
+
354
+ expect(encoded.content).toEqual([
355
+ { type: "text", text: "planned" },
356
+ { type: "data", name: "PredictState", data: { steps: ["a"] } },
357
+ { type: "data", name: "PredictState", data: '{"steps":["a","b"]}' },
358
+ ]);
359
+ });
333
360
  });
334
361
 
335
362
  describe("auiV0Decode", () => {
@@ -573,4 +600,43 @@ describe("auiV0Decode", () => {
573
600
  const toolCall = message.content.find((p) => p.type === "tool-call");
574
601
  expect(toolCall).toHaveProperty("result", false);
575
602
  });
603
+
604
+ it("round-trips data message parts, keeping repeated names in order", () => {
605
+ const content = auiV0Encode({
606
+ id: "local",
607
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
608
+ role: "assistant",
609
+ status: { type: "complete", reason: "stop" },
610
+ metadata: {
611
+ unstable_state: null,
612
+ unstable_annotations: [],
613
+ unstable_data: [],
614
+ steps: [],
615
+ custom: {},
616
+ },
617
+ content: [
618
+ { type: "text", text: "planned" },
619
+ { type: "data", name: "PredictState", data: { steps: ["a"] } },
620
+ { type: "data", name: "PredictState", data: '{"steps":["a","b"]}' },
621
+ ],
622
+ });
623
+
624
+ const decoded = auiV0Decode({
625
+ id: "cloud",
626
+ parent_id: null,
627
+ height: 0,
628
+ created_at: new Date("2026-03-15T00:00:00.000Z"),
629
+ updated_at: new Date("2026-03-15T00:00:00.000Z"),
630
+ format: "aui/v0",
631
+ content: content as never,
632
+ });
633
+
634
+ if (decoded.message.role !== "assistant")
635
+ throw new Error("expected assistant");
636
+ expect(decoded.message.content).toEqual([
637
+ { type: "text", text: "planned" },
638
+ { type: "data", name: "PredictState", data: { steps: ["a"] } },
639
+ { type: "data", name: "PredictState", data: '{"steps":["a","b"]}' },
640
+ ]);
641
+ });
576
642
  });