@assistant-ui/core 0.3.13 → 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 (396) 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 +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal.d.ts +1 -1
  12. package/dist/internal.js +6 -6
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +2 -1
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/registry.d.ts.map +1 -1
  17. package/dist/model-context/registry.js +16 -4
  18. package/dist/model-context/registry.js.map +1 -1
  19. package/dist/react/RuntimeAdapter.d.ts +8 -1
  20. package/dist/react/RuntimeAdapter.d.ts.map +1 -1
  21. package/dist/react/RuntimeAdapter.js +23 -18
  22. package/dist/react/RuntimeAdapter.js.map +1 -1
  23. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  24. package/dist/react/adapters/LocalStorageThreadListAdapter.js +28 -12
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  26. package/dist/react/client/AdaptedRemoteThread.d.ts +11 -0
  27. package/dist/react/client/AdaptedRemoteThread.d.ts.map +1 -0
  28. package/dist/react/client/AdaptedRemoteThread.js +27 -0
  29. package/dist/react/client/AdaptedRemoteThread.js.map +1 -0
  30. package/dist/react/client/InMemoryThreadList.d.ts +19 -0
  31. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -0
  32. package/dist/react/client/InMemoryThreadList.js +418 -0
  33. package/dist/react/client/InMemoryThreadList.js.map +1 -0
  34. package/dist/react/client/Interactables.d.ts.map +1 -1
  35. package/dist/react/client/Interactables.js +159 -102
  36. package/dist/react/client/Interactables.js.map +1 -1
  37. package/dist/react/client/RemoteThreadList.d.ts +34 -0
  38. package/dist/react/client/RemoteThreadList.d.ts.map +1 -0
  39. package/dist/react/client/RemoteThreadList.js +826 -0
  40. package/dist/react/client/RemoteThreadList.js.map +1 -0
  41. package/dist/react/client/Tools.d.ts.map +1 -1
  42. package/dist/react/client/Tools.js +38 -27
  43. package/dist/react/client/Tools.js.map +1 -1
  44. package/dist/react/index.d.ts +7 -2
  45. package/dist/react/index.js +5 -2
  46. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  47. package/dist/react/interactables-legacy/Interactables.js +145 -110
  48. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  49. package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
  50. package/dist/react/primitive-hooks/useActionBarCopy.js +13 -18
  51. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  52. package/dist/react/primitive-hooks/useActionBarEdit.d.ts.map +1 -1
  53. package/dist/react/primitive-hooks/useActionBarEdit.js +2 -4
  54. package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
  55. package/dist/react/primitive-hooks/useActionBarReload.d.ts.map +1 -1
  56. package/dist/react/primitive-hooks/useActionBarReload.js +2 -4
  57. package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
  58. package/dist/react/primitive-hooks/useBranchPickerNext.d.ts.map +1 -1
  59. package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -6
  60. package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
  61. package/dist/react/primitive-hooks/useBranchPickerPrevious.d.ts.map +1 -1
  62. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -6
  63. package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
  64. package/dist/react/primitive-hooks/useComposerCancel.d.ts.map +1 -1
  65. package/dist/react/primitive-hooks/useComposerCancel.js +2 -4
  66. package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
  67. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  68. package/dist/react/primitive-hooks/useComposerSend.js +2 -4
  69. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  70. package/dist/react/primitive-hooks/useSuggestionTrigger.d.ts.map +1 -1
  71. package/dist/react/primitive-hooks/useSuggestionTrigger.js +6 -2
  72. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  73. package/dist/react/primitives/message/MessageParts.d.ts +13 -0
  74. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  75. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  76. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  77. package/dist/react/primitives/thread/ThreadMessages.js +1 -1
  78. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  79. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +22 -10
  80. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  81. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +109 -71
  82. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  83. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +8 -2
  84. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  85. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +40 -13
  86. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  87. package/dist/react/runtimes/RemoteThreadResource.d.ts +30 -0
  88. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -0
  89. package/dist/react/runtimes/RemoteThreadResource.js +134 -0
  90. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -0
  91. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts +3 -10
  92. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
  93. package/dist/react/runtimes/RuntimeAdapterProvider.js +3 -6
  94. package/dist/react/runtimes/RuntimeAdapterProvider.js.map +1 -1
  95. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts +17 -0
  96. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts.map +1 -0
  97. package/dist/react/runtimes/assistant-transport/commandQueue.js +55 -0
  98. package/dist/react/runtimes/assistant-transport/commandQueue.js.map +1 -0
  99. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts +11 -0
  100. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
  101. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js +165 -0
  102. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js.map +1 -0
  103. package/dist/react/runtimes/assistant-transport/runManager.d.ts +15 -0
  104. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -0
  105. package/dist/react/runtimes/assistant-transport/runManager.js +86 -0
  106. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -0
  107. package/dist/react/runtimes/assistant-transport/types.d.ts +130 -0
  108. package/dist/react/runtimes/assistant-transport/types.d.ts.map +1 -0
  109. package/dist/react/runtimes/assistant-transport/types.js +0 -0
  110. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts +14 -0
  111. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -0
  112. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +296 -0
  113. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -0
  114. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts +7 -0
  115. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts.map +1 -0
  116. package/dist/react/runtimes/assistant-transport/useConvertedState.js +25 -0
  117. package/dist/react/runtimes/assistant-transport/useConvertedState.js.map +1 -0
  118. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts +7 -0
  119. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts.map +1 -0
  120. package/dist/react/runtimes/assistant-transport/useLatestRef.js +27 -0
  121. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -0
  122. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +39 -13
  124. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  125. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +2 -1
  126. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  127. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -3
  128. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  129. package/dist/react/runtimes/cloud/auiV0.d.ts +4 -0
  130. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  131. package/dist/react/runtimes/cloud/auiV0.js +7 -0
  132. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  133. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  134. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +22 -12
  135. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  136. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts +16 -0
  137. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts.map +1 -0
  138. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js +36 -0
  139. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -0
  140. package/dist/react/runtimes/external-message-converter.js +14 -14
  141. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  142. package/dist/react/runtimes/useExternalStoreRuntime.d.ts.map +1 -1
  143. package/dist/react/runtimes/useExternalStoreRuntime.js +44 -28
  144. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  145. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
  146. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  147. package/dist/react/runtimes/useRuntimeAdapters.d.ts +9 -0
  148. package/dist/react/runtimes/useRuntimeAdapters.d.ts.map +1 -0
  149. package/dist/react/runtimes/useRuntimeAdapters.js +20 -0
  150. package/dist/react/runtimes/useRuntimeAdapters.js.map +1 -0
  151. package/dist/runtime/api/composer-runtime.d.ts +5 -0
  152. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  153. package/dist/runtime/api/composer-runtime.js.map +1 -1
  154. package/dist/runtime/api/message-runtime.js +1 -1
  155. package/dist/runtime/api/thread-list-runtime.d.ts +1 -0
  156. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  157. package/dist/runtime/api/thread-list-runtime.js +4 -2
  158. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  159. package/dist/runtime/api/thread-runtime.d.ts +12 -2
  160. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  161. package/dist/runtime/api/thread-runtime.js +8 -2
  162. package/dist/runtime/api/thread-runtime.js.map +1 -1
  163. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  164. package/dist/runtime/base/base-composer-runtime-core.js +8 -7
  165. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  166. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
  167. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  168. package/dist/runtime/base/base-thread-runtime-core.js +3 -3
  169. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  170. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +0 -1
  171. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  172. package/dist/runtime/base/default-thread-composer-runtime-core.js +10 -4
  173. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  174. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  175. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  176. package/dist/runtime/internal.js +5 -5
  177. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  178. package/dist/runtime/queue/message-queue.js +2 -1
  179. package/dist/runtime/queue/message-queue.js.map +1 -1
  180. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts +8 -0
  181. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts.map +1 -0
  182. package/dist/runtime/utils/thread-runtime-lifecycle.js +11 -0
  183. package/dist/runtime/utils/thread-runtime-lifecycle.js.map +1 -0
  184. package/dist/runtimes/external-store/external-store-shared-options.js.map +1 -1
  185. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  186. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +4 -1
  187. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  188. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +8 -0
  189. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  190. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +35 -13
  191. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  192. package/dist/runtimes/internal.d.ts +1 -1
  193. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
  194. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  195. package/dist/runtimes/local/local-thread-runtime-core.js +53 -13
  196. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  197. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
  198. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  199. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +3 -0
  200. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  201. package/dist/runtimes/remote-thread-list/empty-thread-core.js +3 -0
  202. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  203. package/dist/runtimes/remote-thread-list/types.d.ts +29 -1
  204. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  205. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +1 -12
  206. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  207. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +67 -38
  208. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  209. package/dist/store/clients/suggestions.js +2 -2
  210. package/dist/store/clients/suggestions.js.map +1 -1
  211. package/dist/store/clients/thread-selection-events.d.ts +10 -0
  212. package/dist/store/clients/thread-selection-events.d.ts.map +1 -0
  213. package/dist/store/clients/thread-selection-events.js +40 -0
  214. package/dist/store/clients/thread-selection-events.js.map +1 -0
  215. package/dist/store/env.d.ts +5 -0
  216. package/dist/store/env.d.ts.map +1 -0
  217. package/dist/store/env.js +12 -0
  218. package/dist/store/env.js.map +1 -0
  219. package/dist/store/index.d.ts +5 -3
  220. package/dist/store/index.js +4 -2
  221. package/dist/store/internal.d.ts +4 -1
  222. package/dist/store/internal.js +4 -1
  223. package/dist/store/primitive-predicates.d.ts +19 -0
  224. package/dist/store/primitive-predicates.d.ts.map +1 -0
  225. package/dist/store/primitive-predicates.js +19 -0
  226. package/dist/store/primitive-predicates.js.map +1 -0
  227. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  228. package/dist/store/runtime-clients/attachment-runtime-client.js +18 -11
  229. package/dist/store/runtime-clients/attachment-runtime-client.js.map +1 -1
  230. package/dist/store/runtime-clients/handle-runtime-action.d.ts +5 -0
  231. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -0
  232. package/dist/store/runtime-clients/handle-runtime-action.js +12 -0
  233. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -0
  234. package/dist/store/runtime-clients/handle-thread-list-action.d.ts +5 -0
  235. package/dist/store/runtime-clients/handle-thread-list-action.d.ts.map +1 -0
  236. package/dist/store/runtime-clients/handle-thread-list-action.js +7 -0
  237. package/dist/store/runtime-clients/handle-thread-list-action.js.map +1 -0
  238. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  239. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +62 -29
  240. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  241. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  242. package/dist/store/runtime-clients/thread-list-runtime-client.js +5 -6
  243. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  244. package/dist/store/scopes/composer.d.ts +9 -4
  245. package/dist/store/scopes/composer.d.ts.map +1 -1
  246. package/dist/store/scopes/thread-list-item.d.ts +7 -6
  247. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  248. package/dist/store/scopes/thread.d.ts +8 -7
  249. package/dist/store/scopes/thread.d.ts.map +1 -1
  250. package/dist/store/scopes/threads.d.ts +17 -1
  251. package/dist/store/scopes/threads.d.ts.map +1 -1
  252. package/dist/subscribable/subscribable.d.ts +2 -1
  253. package/dist/subscribable/subscribable.d.ts.map +1 -1
  254. package/dist/subscribable/subscribable.js +23 -18
  255. package/dist/subscribable/subscribable.js.map +1 -1
  256. package/dist/utils/composite-context-provider.d.ts +1 -0
  257. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  258. package/dist/utils/composite-context-provider.js +25 -9
  259. package/dist/utils/composite-context-provider.js.map +1 -1
  260. package/dist/utils/notify-event-listeners.d.ts +2 -2
  261. package/dist/utils/notify-event-listeners.d.ts.map +1 -1
  262. package/dist/utils/notify-event-listeners.js +2 -2
  263. package/dist/utils/notify-event-listeners.js.map +1 -1
  264. package/package.json +7 -7
  265. package/src/adapters/speech.test.ts +118 -1
  266. package/src/adapters/speech.ts +21 -22
  267. package/src/adapters/thread-history.ts +2 -0
  268. package/src/adapters/voice.test.ts +69 -0
  269. package/src/adapters/voice.ts +27 -27
  270. package/src/index.ts +1 -0
  271. package/src/model-context/frame/host.test.ts +23 -0
  272. package/src/model-context/frame/host.ts +2 -1
  273. package/src/model-context/registry.test.ts +131 -0
  274. package/src/model-context/registry.ts +16 -6
  275. package/src/react/RuntimeAdapter.test.ts +2 -0
  276. package/src/react/RuntimeAdapter.ts +20 -8
  277. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +34 -17
  278. package/src/react/client/AdaptedRemoteThread.ts +41 -0
  279. package/src/react/client/InMemoryThreadList.test.tsx +148 -0
  280. package/src/react/client/InMemoryThreadList.ts +290 -0
  281. package/src/react/client/Interactables.test.ts +326 -2
  282. package/src/react/client/Interactables.ts +227 -110
  283. package/src/react/client/RemoteThreadList.test.ts +1026 -0
  284. package/src/react/client/RemoteThreadList.ts +1067 -0
  285. package/src/react/client/Tools.ts +36 -29
  286. package/src/react/index.ts +13 -0
  287. package/src/react/interactables-legacy/Interactables.test.ts +317 -0
  288. package/src/react/interactables-legacy/Interactables.ts +204 -128
  289. package/src/react/primitive-hooks/useActionBarCopy.ts +2 -7
  290. package/src/react/primitive-hooks/useActionBarEdit.ts +2 -1
  291. package/src/react/primitive-hooks/useActionBarReload.ts +2 -6
  292. package/src/react/primitive-hooks/useBranchPickerNext.ts +2 -7
  293. package/src/react/primitive-hooks/useBranchPickerPrevious.ts +2 -7
  294. package/src/react/primitive-hooks/useComposerCancel.ts +2 -1
  295. package/src/react/primitive-hooks/useComposerSend.ts +2 -5
  296. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +11 -0
  297. package/src/react/primitive-hooks/useSuggestionTrigger.ts +4 -5
  298. package/src/react/primitives/message/MessageParts.tsx +0 -1
  299. package/src/react/primitives/thread/ThreadMessages.tsx +2 -1
  300. package/src/react/runtimes/RemoteThreadListHookInstanceManager.retry.test.tsx +116 -0
  301. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +1 -1
  302. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +57 -6
  303. package/src/react/runtimes/RemoteThreadListHookInstanceManager.title.test.tsx +272 -0
  304. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +159 -124
  305. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +59 -20
  306. package/src/react/runtimes/RemoteThreadResource.test.ts +141 -0
  307. package/src/react/runtimes/RemoteThreadResource.ts +231 -0
  308. package/src/react/runtimes/RuntimeAdapterProvider.tsx +8 -20
  309. package/src/react/runtimes/assistant-transport/commandQueue.ts +68 -0
  310. package/src/react/runtimes/assistant-transport/replayBoundaryStream.test.ts +455 -0
  311. package/src/react/runtimes/assistant-transport/replayBoundaryStream.ts +172 -0
  312. package/src/react/runtimes/assistant-transport/runManager.ts +124 -0
  313. package/src/react/runtimes/assistant-transport/transport-delivery.test.tsx +79 -0
  314. package/src/react/runtimes/assistant-transport/transport-parentid.test.tsx +97 -0
  315. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +414 -0
  316. package/src/react/runtimes/assistant-transport/types.ts +170 -0
  317. package/src/react/runtimes/assistant-transport/useAssistantTransport.spec.md +141 -0
  318. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +625 -0
  319. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +481 -0
  320. package/src/react/runtimes/assistant-transport/useConvertedState.ts +20 -0
  321. package/src/react/runtimes/assistant-transport/useLatestRef.ts +9 -0
  322. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +131 -6
  323. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +61 -15
  324. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +6 -4
  325. package/src/react/runtimes/cloud/auiV0.ts +17 -1
  326. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +36 -21
  327. package/src/react/runtimes/cloud/useCloudThreadListRuntime.test.tsx +56 -0
  328. package/src/react/runtimes/cloud/useCloudThreadListRuntime.ts +31 -0
  329. package/src/react/runtimes/external-message-converter.test.ts +143 -0
  330. package/src/react/runtimes/external-message-converter.ts +41 -41
  331. package/src/react/runtimes/remote-thread-hook-on-tap.test.ts +58 -0
  332. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +83 -0
  333. package/src/react/runtimes/useExternalStoreRuntime.suggestions.test.tsx +13 -3
  334. package/src/react/runtimes/useExternalStoreRuntime.ts +7 -0
  335. package/src/react/runtimes/useExternalStoreSharedOptions.ts +3 -1
  336. package/src/react/runtimes/useLocalRuntime.test.tsx +130 -4
  337. package/src/react/runtimes/useRuntimeAdapters.test.tsx +62 -0
  338. package/src/react/runtimes/useRuntimeAdapters.ts +28 -0
  339. package/src/runtime/api/composer-runtime.ts +5 -0
  340. package/src/runtime/api/thread-list-runtime.ts +3 -1
  341. package/src/runtime/api/thread-runtime.ts +20 -2
  342. package/src/runtime/base/base-composer-runtime-core.ts +6 -5
  343. package/src/runtime/base/base-thread-runtime-core.test.ts +191 -0
  344. package/src/runtime/base/base-thread-runtime-core.ts +11 -2
  345. package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -4
  346. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  347. package/src/runtime/queue/message-queue.ts +2 -1
  348. package/src/runtime/utils/thread-runtime-lifecycle.ts +18 -0
  349. package/src/runtimes/external-store/external-store-shared-options.ts +3 -1
  350. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
  351. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +99 -34
  352. package/src/runtimes/local/local-thread-runtime-core.test.ts +373 -4
  353. package/src/runtimes/local/local-thread-runtime-core.ts +79 -14
  354. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +4 -0
  355. package/src/runtimes/remote-thread-list/empty-thread-core.ts +4 -0
  356. package/src/runtimes/remote-thread-list/types.ts +30 -0
  357. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -6
  358. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +243 -0
  359. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +127 -61
  360. package/src/store/clients/model-context-client.test.ts +16 -8
  361. package/src/store/clients/suggestions.ts +2 -2
  362. package/src/store/clients/thread-selection-events.ts +21 -0
  363. package/src/store/env.ts +11 -0
  364. package/src/store/index.ts +14 -1
  365. package/src/store/internal.ts +13 -0
  366. package/src/store/primitive-predicates.test.ts +184 -0
  367. package/src/store/primitive-predicates.ts +43 -0
  368. package/src/store/runtime-clients/attachment-runtime-client.test.ts +41 -0
  369. package/src/store/runtime-clients/attachment-runtime-client.ts +2 -1
  370. package/src/store/runtime-clients/handle-runtime-action.ts +11 -0
  371. package/src/store/runtime-clients/handle-thread-list-action.test.ts +69 -0
  372. package/src/store/runtime-clients/handle-thread-list-action.ts +6 -0
  373. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +15 -7
  374. package/src/store/runtime-clients/thread-list-runtime-client.ts +9 -6
  375. package/src/store/scopes/composer.ts +9 -4
  376. package/src/store/scopes/thread-list-item.ts +7 -6
  377. package/src/store/scopes/thread.ts +8 -7
  378. package/src/store/scopes/threads.ts +14 -0
  379. package/src/subscribable/subscribable.test.ts +77 -1
  380. package/src/subscribable/subscribable.ts +38 -23
  381. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-unarchive.test.ts +25 -0
  382. package/src/tests/auiV0Encode.test.ts +66 -0
  383. package/src/tests/base-composer-runtime-core-send.test.ts +30 -0
  384. package/src/tests/base-composer-runtime-core.test.ts +28 -0
  385. package/src/tests/composer-can-cancel.test.ts +101 -0
  386. package/src/tests/external-store-thread-list-runtime-core.test.ts +101 -0
  387. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +191 -1
  388. package/src/tests/external-store-thread-runtime-core.test.ts +124 -0
  389. package/src/tests/interactables-toolui-migration.test.tsx +66 -0
  390. package/src/tests/message-queue.test.ts +28 -0
  391. package/src/tests/session-reset-notification.test.ts +145 -0
  392. package/src/tests/thread-switch-events.test.tsx +99 -1
  393. package/src/tests/tools-scope-migration.test.tsx +58 -0
  394. package/src/utils/composite-context-provider.test.ts +139 -0
  395. package/src/utils/composite-context-provider.ts +26 -8
  396. package/src/utils/notify-event-listeners.ts +10 -5
@@ -0,0 +1,625 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, render, waitFor } from "@testing-library/react";
4
+ import type { FC } from "react";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+ import { useAui } from "@assistant-ui/store";
7
+ import { AssistantRuntimeProvider } from "../../AssistantRuntimeProvider";
8
+ import {
9
+ useAssistantTransportRuntime,
10
+ useAssistantTransportSendCommand,
11
+ } from "./useAssistantTransportRuntime";
12
+ import type {
13
+ AssistantTransportCommand,
14
+ AssistantTransportOptions,
15
+ AssistantTransportStateConverter,
16
+ } from "./types";
17
+
18
+ const converter: AssistantTransportStateConverter<unknown> = (
19
+ _state,
20
+ meta,
21
+ ) => ({
22
+ messages: [],
23
+ isRunning: meta.isSending,
24
+ });
25
+
26
+ const createMessageCommand = (text: string): AssistantTransportCommand => ({
27
+ type: "add-message",
28
+ message: {
29
+ role: "user",
30
+ parts: [{ type: "text", text }],
31
+ },
32
+ parentId: null,
33
+ sourceId: null,
34
+ });
35
+
36
+ const createStreamResponse = () => {
37
+ let controller!: ReadableStreamDefaultController<Uint8Array>;
38
+ const stream = new ReadableStream<Uint8Array>({
39
+ start(c) {
40
+ controller = c;
41
+ },
42
+ });
43
+ return {
44
+ response: new Response(stream, { status: 200 }),
45
+ close: () => controller.close(),
46
+ };
47
+ };
48
+
49
+ type StreamResponse = ReturnType<typeof createStreamResponse>;
50
+
51
+ type RecordedRequest = {
52
+ url: string;
53
+ init: RequestInit;
54
+ body: Record<string, any>;
55
+ };
56
+
57
+ const installFetch = () => {
58
+ const requests: RecordedRequest[] = [];
59
+ const servers: StreamResponse[] = [];
60
+
61
+ vi.stubGlobal(
62
+ "fetch",
63
+ async (url: RequestInfo | URL, init: RequestInit = {}) => {
64
+ requests.push({
65
+ url: String(url),
66
+ init,
67
+ body: JSON.parse(init.body as string),
68
+ });
69
+ const server = createStreamResponse();
70
+ servers.push(server);
71
+ return server.response;
72
+ },
73
+ );
74
+
75
+ return { requests, servers };
76
+ };
77
+
78
+ const installPendingFetch = () => {
79
+ const requests: RecordedRequest[] = [];
80
+ const pending: {
81
+ resolve: (response: Response) => void;
82
+ reject: (reason: unknown) => void;
83
+ }[] = [];
84
+
85
+ vi.stubGlobal("fetch", (url: RequestInfo | URL, init: RequestInit = {}) => {
86
+ requests.push({
87
+ url: String(url),
88
+ init,
89
+ body: JSON.parse(init.body as string),
90
+ });
91
+
92
+ return new Promise<Response>((resolve, reject) => {
93
+ pending.push({ resolve, reject });
94
+ init.signal?.addEventListener(
95
+ "abort",
96
+ () => reject(init.signal?.reason),
97
+ {
98
+ once: true,
99
+ },
100
+ );
101
+ });
102
+ });
103
+
104
+ return { requests, pending };
105
+ };
106
+
107
+ const mountRuntime = (
108
+ options?: Partial<AssistantTransportOptions<unknown>>,
109
+ ) => {
110
+ const captured: {
111
+ aui?: ReturnType<typeof useAui>;
112
+ sendCommand?: (command: AssistantTransportCommand) => void;
113
+ } = {};
114
+ const Capture: FC = () => {
115
+ captured.aui = useAui();
116
+ captured.sendCommand = useAssistantTransportSendCommand();
117
+ return null;
118
+ };
119
+ const App: FC = () => {
120
+ const runtime = useAssistantTransportRuntime({
121
+ initialState: {},
122
+ api: "https://example.com/api",
123
+ headers: {},
124
+ converter,
125
+ ...options,
126
+ });
127
+ return (
128
+ <AssistantRuntimeProvider runtime={runtime}>
129
+ <Capture />
130
+ </AssistantRuntimeProvider>
131
+ );
132
+ };
133
+ const utils = render(<App />);
134
+ return {
135
+ aui: () => captured.aui!,
136
+ sendCommand: (command: AssistantTransportCommand) =>
137
+ captured.sendCommand!(command),
138
+ ...utils,
139
+ };
140
+ };
141
+
142
+ afterEach(() => {
143
+ vi.unstubAllGlobals();
144
+ vi.restoreAllMocks();
145
+ });
146
+
147
+ describe("useAssistantTransportRuntime", () => {
148
+ it.each(["throws", "rejects"] as const)(
149
+ "cancels the response body when onResponse %s",
150
+ async (failureMode) => {
151
+ const callbackError = new Error("response callback failed");
152
+ const cancel = vi.fn().mockRejectedValue(new Error("cancel failed"));
153
+ const onError = vi.fn();
154
+ vi.stubGlobal(
155
+ "fetch",
156
+ vi.fn().mockResolvedValue(
157
+ new Response(
158
+ new ReadableStream({
159
+ cancel,
160
+ }),
161
+ ),
162
+ ),
163
+ );
164
+
165
+ const { aui, sendCommand } = mountRuntime({
166
+ onResponse: () => {
167
+ if (failureMode === "throws") throw callbackError;
168
+ return Promise.reject(callbackError);
169
+ },
170
+ onError,
171
+ });
172
+ await waitFor(() =>
173
+ expect(
174
+ (aui().thread.getState().extras as { sendCommand?: unknown })
175
+ ?.sendCommand,
176
+ ).toBeTypeOf("function"),
177
+ );
178
+
179
+ act(() => sendCommand(createMessageCommand("hello")));
180
+
181
+ await waitFor(() =>
182
+ expect(onError).toHaveBeenCalledWith(callbackError, expect.anything()),
183
+ );
184
+ expect(cancel).toHaveBeenCalledOnce();
185
+ },
186
+ );
187
+
188
+ it("no-ops a follow-up run that finds an empty queue", async () => {
189
+ const fetchMock = installFetch();
190
+ const onError = vi.fn();
191
+ const { aui, sendCommand } = mountRuntime({ onError });
192
+ await waitFor(() =>
193
+ expect(
194
+ (aui().thread.getState().extras as { sendCommand?: unknown })
195
+ ?.sendCommand,
196
+ ).toBeTypeOf("function"),
197
+ );
198
+
199
+ // Two synchronous sends coalesce into one request plus one follow-up run.
200
+ act(() => {
201
+ sendCommand(createMessageCommand("a"));
202
+ sendCommand(createMessageCommand("b"));
203
+ });
204
+
205
+ await waitFor(() => expect(fetchMock.requests).toHaveLength(1));
206
+ expect(
207
+ fetchMock.requests[0]!.body["commands"].map((c: any) => c.type),
208
+ ).toEqual(["add-message", "add-message"]);
209
+ expect(fetchMock.requests[0]!.body["state"]).toEqual({});
210
+
211
+ act(() => fetchMock.servers[0]!.close());
212
+
213
+ await waitFor(() => expect(aui().thread.getState().isRunning).toBe(false));
214
+ await act(async () => {});
215
+ expect(onError).not.toHaveBeenCalled();
216
+ expect(fetchMock.requests).toHaveLength(1);
217
+ });
218
+
219
+ it("skips add-message commands with no supported parts", async () => {
220
+ const fetchMock = installFetch();
221
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
222
+ const { aui, sendCommand } = mountRuntime();
223
+ await waitFor(() =>
224
+ expect(
225
+ (aui().thread.getState().extras as { sendCommand?: unknown })
226
+ ?.sendCommand,
227
+ ).toBeTypeOf("function"),
228
+ );
229
+
230
+ act(() =>
231
+ aui().thread.append({
232
+ role: "user",
233
+ content: [{ type: "audio", audio: { data: "", format: "mp3" } }],
234
+ }),
235
+ );
236
+
237
+ await act(async () => {});
238
+ expect(warn).toHaveBeenCalledWith(
239
+ "[assistant-ui] Skipped add-message command with no supported parts",
240
+ );
241
+ expect(fetchMock.requests).toHaveLength(0);
242
+
243
+ // The skipped message must not leak its parentId into later batches.
244
+ act(() => sendCommand(createMessageCommand("follow-up")));
245
+ await waitFor(() => expect(fetchMock.requests).toHaveLength(1));
246
+ expect(fetchMock.requests[0]!.body).not.toHaveProperty("parentId");
247
+ });
248
+
249
+ it("flushes commands enqueued during a resume run in a follow-up run", async () => {
250
+ const fetchMock = installFetch();
251
+ const { aui, sendCommand } = mountRuntime({
252
+ resumeApi: "https://example.com/resume",
253
+ });
254
+ await waitFor(() =>
255
+ expect(
256
+ (aui().thread.getState().extras as { sendCommand?: unknown })
257
+ ?.sendCommand,
258
+ ).toBeTypeOf("function"),
259
+ );
260
+
261
+ act(() => sendCommand(createMessageCommand("a")));
262
+ await waitFor(() => expect(fetchMock.requests).toHaveLength(1));
263
+
264
+ // Both land while the first run is active and coalesce into one follow-up.
265
+ act(() => {
266
+ sendCommand(createMessageCommand("b"));
267
+ aui().thread.resumeRun({ parentId: null });
268
+ });
269
+
270
+ act(() => fetchMock.servers[0]!.close());
271
+ await waitFor(() => expect(fetchMock.requests).toHaveLength(2));
272
+ expect(fetchMock.requests[1]!.url).toBe("https://example.com/resume");
273
+ expect(fetchMock.requests[1]!.body["commands"]).toEqual([]);
274
+ expect(fetchMock.requests[1]!.body).toHaveProperty("state");
275
+
276
+ // "b" coalesced into the resume run and must not starve in the queue.
277
+ act(() => fetchMock.servers[1]!.close());
278
+ await waitFor(() => expect(fetchMock.requests).toHaveLength(3));
279
+ expect(fetchMock.requests[2]!.url).toBe("https://example.com/api");
280
+ expect(fetchMock.requests[2]!.body["commands"]).toEqual([
281
+ createMessageCommand("b"),
282
+ ]);
283
+
284
+ act(() => fetchMock.servers[2]!.close());
285
+ await waitFor(() => expect(aui().thread.getState().isRunning).toBe(false));
286
+ });
287
+
288
+ it.each(["error", "cancellation"] as const)(
289
+ "does not apply a dropped resume after run %s",
290
+ async (settlement) => {
291
+ const fetchMock = installPendingFetch();
292
+ const onError = vi.fn();
293
+ const { aui, sendCommand } = mountRuntime({
294
+ resumeApi: "https://example.com/resume",
295
+ onError,
296
+ });
297
+ await waitFor(() =>
298
+ expect(
299
+ (aui().thread.getState().extras as { sendCommand?: unknown })
300
+ ?.sendCommand,
301
+ ).toBeTypeOf("function"),
302
+ );
303
+
304
+ act(() => sendCommand(createMessageCommand("a")));
305
+ await waitFor(() => expect(fetchMock.requests).toHaveLength(1));
306
+
307
+ await act(async () => {
308
+ await aui().thread.resumeRun({ parentId: null });
309
+ });
310
+ if (settlement === "cancellation") {
311
+ act(() => aui().thread.cancelRun());
312
+ } else {
313
+ await act(async () => {
314
+ fetchMock.pending[0]!.reject(new Error("request failed"));
315
+ });
316
+ }
317
+ await waitFor(() =>
318
+ expect(aui().thread.getState().isRunning).toBe(false),
319
+ );
320
+
321
+ act(() => sendCommand(createMessageCommand("b")));
322
+ await waitFor(() => expect(fetchMock.requests).toHaveLength(2));
323
+ expect(fetchMock.requests[1]!.url).toBe("https://example.com/api");
324
+ expect(fetchMock.requests[1]!.body["commands"]).toEqual([
325
+ createMessageCommand("b"),
326
+ ]);
327
+
328
+ await act(async () => {
329
+ fetchMock.pending[1]!.resolve(new Response("", { status: 200 }));
330
+ });
331
+ await waitFor(() =>
332
+ expect(aui().thread.getState().isRunning).toBe(false),
333
+ );
334
+ expect(onError).toHaveBeenCalledTimes(settlement === "error" ? 1 : 0);
335
+ },
336
+ );
337
+
338
+ it("applies resumed operations to the retained initial state", async () => {
339
+ const requests: RecordedRequest[] = [];
340
+ vi.stubGlobal(
341
+ "fetch",
342
+ async (url: RequestInfo | URL, init: RequestInit = {}) => {
343
+ requests.push({
344
+ url: String(url),
345
+ init,
346
+ body: JSON.parse(init.body as string),
347
+ });
348
+
349
+ if (String(url) === "https://example.com/resume-state") {
350
+ return Response.json({
351
+ runId: "run-1",
352
+ state: { message: "Hello" },
353
+ });
354
+ }
355
+
356
+ return new Response(
357
+ 'aui-state:[{"type":"append-text","path":["message"],"value":" world"}]\n',
358
+ { status: 200 },
359
+ );
360
+ },
361
+ );
362
+ const { aui } = mountRuntime({
363
+ resumeApi: "https://example.com/resume",
364
+ resumeStateApi: "https://example.com/resume-state",
365
+ });
366
+ await waitFor(() =>
367
+ expect(
368
+ (aui().thread.getState().extras as { sendCommand?: unknown })
369
+ ?.sendCommand,
370
+ ).toBeTypeOf("function"),
371
+ );
372
+
373
+ act(() => {
374
+ aui().thread.importExternalState({ message: "Wrong" });
375
+ });
376
+ await act(async () => {
377
+ await aui().thread.resumeRun({ parentId: null });
378
+ });
379
+
380
+ await waitFor(() =>
381
+ expect(
382
+ (aui().thread.getState().extras as { state: unknown }).state,
383
+ ).toEqual({ message: "Hello world" }),
384
+ );
385
+ expect(requests.map((request) => request.url)).toEqual([
386
+ "https://example.com/resume-state",
387
+ "https://example.com/resume",
388
+ ]);
389
+ expect(requests[1]!.body).toMatchObject({ runId: "run-1" });
390
+ expect(requests[1]!.body).not.toHaveProperty("state");
391
+ });
392
+
393
+ it("rejects malformed resume state responses before replay", async () => {
394
+ const fetchMock = vi.fn(async () => Response.json({ state: {} }));
395
+ vi.stubGlobal("fetch", fetchMock);
396
+ const onError = vi.fn();
397
+ const { aui } = mountRuntime({
398
+ resumeApi: "https://example.com/resume",
399
+ resumeStateApi: "https://example.com/resume-state",
400
+ onError,
401
+ });
402
+ await waitFor(() =>
403
+ expect(
404
+ (aui().thread.getState().extras as { sendCommand?: unknown })
405
+ ?.sendCommand,
406
+ ).toBeTypeOf("function"),
407
+ );
408
+
409
+ await act(async () => {
410
+ await aui().thread.resumeRun({ parentId: null });
411
+ });
412
+
413
+ await waitFor(() =>
414
+ expect(onError).toHaveBeenCalledWith(
415
+ expect.objectContaining({
416
+ message: "Resume state response must contain state and runId",
417
+ }),
418
+ expect.anything(),
419
+ ),
420
+ );
421
+ expect(fetchMock).toHaveBeenCalledTimes(1);
422
+ });
423
+
424
+ it("commits a retained null state locally and omits state from the resume request", async () => {
425
+ const requests: RecordedRequest[] = [];
426
+ vi.stubGlobal(
427
+ "fetch",
428
+ async (url: RequestInfo | URL, init: RequestInit = {}) => {
429
+ requests.push({
430
+ url: String(url),
431
+ init,
432
+ body: JSON.parse(init.body as string),
433
+ });
434
+
435
+ if (String(url) === "https://example.com/resume-state") {
436
+ return Response.json({ runId: "run-1", state: null });
437
+ }
438
+
439
+ return new Response("", { status: 200 });
440
+ },
441
+ );
442
+ const { aui } = mountRuntime({
443
+ resumeApi: "https://example.com/resume",
444
+ resumeStateApi: "https://example.com/resume-state",
445
+ });
446
+ await waitFor(() =>
447
+ expect(
448
+ (aui().thread.getState().extras as { sendCommand?: unknown })
449
+ ?.sendCommand,
450
+ ).toBeTypeOf("function"),
451
+ );
452
+
453
+ act(() => {
454
+ aui().thread.importExternalState({ message: "Wrong" });
455
+ });
456
+ await act(async () => {
457
+ await aui().thread.resumeRun({ parentId: null });
458
+ });
459
+
460
+ expect(requests[1]!.body).toMatchObject({ runId: "run-1" });
461
+ expect(requests[1]!.body).not.toHaveProperty("state");
462
+ await waitFor(() =>
463
+ expect(
464
+ (aui().thread.getState().extras as { state: unknown }).state,
465
+ ).toBeNull(),
466
+ );
467
+ });
468
+
469
+ it("skips the resume without error when the state endpoint reports no active run", async () => {
470
+ const fetchMock = vi.fn(async () => new Response(null, { status: 204 }));
471
+ vi.stubGlobal("fetch", fetchMock);
472
+ const onError = vi.fn();
473
+ const { aui } = mountRuntime({
474
+ resumeApi: "https://example.com/resume",
475
+ resumeStateApi: "https://example.com/resume-state",
476
+ onError,
477
+ });
478
+ await waitFor(() =>
479
+ expect(
480
+ (aui().thread.getState().extras as { sendCommand?: unknown })
481
+ ?.sendCommand,
482
+ ).toBeTypeOf("function"),
483
+ );
484
+
485
+ act(() => {
486
+ aui().thread.importExternalState({ message: "Kept" });
487
+ });
488
+ await act(async () => {
489
+ await aui().thread.resumeRun({ parentId: null });
490
+ });
491
+
492
+ await waitFor(() => expect(aui().thread.getState().isRunning).toBe(false));
493
+ expect(fetchMock).toHaveBeenCalledTimes(1);
494
+ expect(onError).not.toHaveBeenCalled();
495
+ expect(
496
+ (aui().thread.getState().extras as { state: unknown }).state,
497
+ ).toEqual({ message: "Kept" });
498
+ });
499
+
500
+ it("keeps the retained runId over body overrides in the resume request", async () => {
501
+ const requests: RecordedRequest[] = [];
502
+ vi.stubGlobal(
503
+ "fetch",
504
+ async (url: RequestInfo | URL, init: RequestInit = {}) => {
505
+ requests.push({
506
+ url: String(url),
507
+ init,
508
+ body: JSON.parse(init.body as string),
509
+ });
510
+
511
+ if (String(url) === "https://example.com/resume-state") {
512
+ return Response.json({
513
+ runId: "run-1",
514
+ state: { message: "Hello" },
515
+ });
516
+ }
517
+
518
+ return new Response("", { status: 200 });
519
+ },
520
+ );
521
+ const { aui } = mountRuntime({
522
+ resumeApi: "https://example.com/resume",
523
+ resumeStateApi: "https://example.com/resume-state",
524
+ body: { state: { message: "Injected" }, runId: "bogus" },
525
+ });
526
+ await waitFor(() =>
527
+ expect(
528
+ (aui().thread.getState().extras as { sendCommand?: unknown })
529
+ ?.sendCommand,
530
+ ).toBeTypeOf("function"),
531
+ );
532
+
533
+ await act(async () => {
534
+ await aui().thread.resumeRun({ parentId: null });
535
+ });
536
+
537
+ expect(requests[1]!.body["runId"]).toBe("run-1");
538
+ expect(requests[1]!.body).not.toHaveProperty("state");
539
+ });
540
+
541
+ it("re-attaches runId and strips substituted state when prepareSendCommandsRequest rebuilds the body", async () => {
542
+ const requests: RecordedRequest[] = [];
543
+ vi.stubGlobal(
544
+ "fetch",
545
+ async (url: RequestInfo | URL, init: RequestInit = {}) => {
546
+ requests.push({
547
+ url: String(url),
548
+ init,
549
+ body: JSON.parse(init.body as string),
550
+ });
551
+
552
+ if (String(url) === "https://example.com/resume-state") {
553
+ return Response.json({
554
+ runId: "run-1",
555
+ state: { message: "Hello" },
556
+ });
557
+ }
558
+
559
+ return new Response("", { status: 200 });
560
+ },
561
+ );
562
+ const { aui } = mountRuntime({
563
+ resumeApi: "https://example.com/resume",
564
+ resumeStateApi: "https://example.com/resume-state",
565
+ prepareSendCommandsRequest: (body) => ({
566
+ commands: body.commands,
567
+ state: { message: "Substituted" },
568
+ rebuilt: true,
569
+ }),
570
+ });
571
+ await waitFor(() =>
572
+ expect(
573
+ (aui().thread.getState().extras as { sendCommand?: unknown })
574
+ ?.sendCommand,
575
+ ).toBeTypeOf("function"),
576
+ );
577
+
578
+ await act(async () => {
579
+ await aui().thread.resumeRun({ parentId: null });
580
+ });
581
+
582
+ expect(requests[1]!.body).toMatchObject({ runId: "run-1", rebuilt: true });
583
+ expect(requests[1]!.body).not.toHaveProperty("state");
584
+ });
585
+
586
+ it("keeps local state when the matching resume stream is rejected", async () => {
587
+ const fetchMock = vi
588
+ .fn()
589
+ .mockResolvedValueOnce(
590
+ Response.json({ runId: "run-1", state: { message: "Hello" } }),
591
+ )
592
+ .mockResolvedValueOnce(new Response("run mismatch", { status: 409 }));
593
+ vi.stubGlobal("fetch", fetchMock);
594
+ const onError = vi.fn();
595
+ const { aui } = mountRuntime({
596
+ resumeApi: "https://example.com/resume",
597
+ resumeStateApi: "https://example.com/resume-state",
598
+ onError,
599
+ });
600
+ await waitFor(() =>
601
+ expect(
602
+ (aui().thread.getState().extras as { sendCommand?: unknown })
603
+ ?.sendCommand,
604
+ ).toBeTypeOf("function"),
605
+ );
606
+
607
+ act(() => {
608
+ aui().thread.importExternalState({ message: "Wrong" });
609
+ });
610
+ await act(async () => {
611
+ await aui().thread.resumeRun({ parentId: null });
612
+ });
613
+
614
+ await waitFor(() =>
615
+ expect(onError).toHaveBeenCalledWith(
616
+ expect.objectContaining({ message: "Status 409: run mismatch" }),
617
+ expect.anything(),
618
+ ),
619
+ );
620
+ expect(
621
+ (aui().thread.getState().extras as { state: unknown }).state,
622
+ ).toEqual({ message: "Wrong" });
623
+ expect(fetchMock).toHaveBeenCalledTimes(2);
624
+ });
625
+ });