@assistant-ui/core 0.3.17 → 0.3.19

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 (470) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/attachment.js +5 -4
  3. package/dist/adapters/attachment.js.map +1 -1
  4. package/dist/adapters/speech.d.ts.map +1 -1
  5. package/dist/adapters/speech.js +13 -3
  6. package/dist/adapters/speech.js.map +1 -1
  7. package/dist/adapters/thread-history.d.ts +4 -0
  8. package/dist/adapters/thread-history.d.ts.map +1 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal.d.ts +4 -3
  12. package/dist/internal.js +3 -2
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +35 -14
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/frame/provider.d.ts +2 -0
  17. package/dist/model-context/frame/provider.d.ts.map +1 -1
  18. package/dist/model-context/frame/provider.js +30 -7
  19. package/dist/model-context/frame/provider.js.map +1 -1
  20. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  21. package/dist/model-context/interactable-composer-metadata.js +27 -18
  22. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  23. package/dist/model-context/registry.d.ts.map +1 -1
  24. package/dist/model-context/registry.js +3 -5
  25. package/dist/model-context/registry.js.map +1 -1
  26. package/dist/model-context/types.d.ts.map +1 -1
  27. package/dist/model-context/types.js +5 -4
  28. package/dist/model-context/types.js.map +1 -1
  29. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  30. package/dist/react/adapters/LocalStorageThreadListAdapter.js +45 -7
  31. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  32. package/dist/react/client/DataRenderers.d.ts.map +1 -1
  33. package/dist/react/client/DataRenderers.js +20 -15
  34. package/dist/react/client/DataRenderers.js.map +1 -1
  35. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  36. package/dist/react/client/InMemoryThreadList.js +62 -56
  37. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  38. package/dist/react/client/Interactables.d.ts.map +1 -1
  39. package/dist/react/client/Interactables.js +35 -32
  40. package/dist/react/client/Interactables.js.map +1 -1
  41. package/dist/react/client/RemoteThreadList.d.ts +1 -0
  42. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  43. package/dist/react/client/RemoteThreadList.js +154 -143
  44. package/dist/react/client/RemoteThreadList.js.map +1 -1
  45. package/dist/react/client/Tools.d.ts.map +1 -1
  46. package/dist/react/client/Tools.js +18 -16
  47. package/dist/react/client/Tools.js.map +1 -1
  48. package/dist/react/client/interactable-model-context.d.ts.map +1 -1
  49. package/dist/react/client/interactable-model-context.js +3 -2
  50. package/dist/react/client/interactable-model-context.js.map +1 -1
  51. package/dist/react/index.d.ts +2 -2
  52. package/dist/react/index.js +2 -2
  53. package/dist/react/interactables-legacy/Interactables.js +242 -121
  54. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  55. package/dist/react/interactables-legacy/useAssistantInteractable.d.ts.map +1 -1
  56. package/dist/react/interactables-legacy/useAssistantInteractable.js +3 -0
  57. package/dist/react/interactables-legacy/useAssistantInteractable.js.map +1 -1
  58. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  59. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  60. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  61. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  62. package/dist/react/model-context/makeAssistantTool.js +1 -1
  63. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  64. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  65. package/dist/react/model-context/useAssistantTool.js +1 -1
  66. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  67. package/dist/react/model-context/useInteractable.d.ts.map +1 -1
  68. package/dist/react/model-context/useInteractable.js +4 -1
  69. package/dist/react/model-context/useInteractable.js.map +1 -1
  70. package/dist/react/model-context/useInteractableVersions.js +1 -1
  71. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  72. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  73. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  74. package/dist/react/primitives/message/MessageGroupedParts.d.ts +11 -2
  75. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  76. package/dist/react/primitives/message/MessageGroupedParts.js +3 -2
  77. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  78. package/dist/react/primitives/part/PartMessages.d.ts +1 -0
  79. package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
  80. package/dist/react/primitives/part/PartMessages.js +1 -0
  81. package/dist/react/primitives/part/PartMessages.js.map +1 -1
  82. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  83. package/dist/react/primitives/thread/ThreadMessages.js +6 -5
  84. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  85. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  86. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +16 -8
  87. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  88. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -1
  89. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +105 -104
  91. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  92. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -0
  93. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  94. package/dist/react/runtimes/RemoteThreadResource.js +13 -11
  95. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  96. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +5 -24
  97. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  98. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +354 -226
  99. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  100. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -0
  101. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  102. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +22 -2
  103. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  104. package/dist/react/runtimes/cloud/auiV0.d.ts +40 -15
  105. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  106. package/dist/react/runtimes/cloud/auiV0.js +62 -15
  107. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  108. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +2 -1
  109. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
  110. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +6 -1
  111. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -1
  112. package/dist/react/runtimes/cloud/sdkIdentity.d.ts +6 -0
  113. package/dist/react/runtimes/cloud/sdkIdentity.d.ts.map +1 -0
  114. package/dist/react/runtimes/cloud/sdkIdentity.js +9 -0
  115. package/dist/react/runtimes/cloud/sdkIdentity.js.map +1 -0
  116. package/dist/react/runtimes/external-message-converter.d.ts +12 -2
  117. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  118. package/dist/react/runtimes/external-message-converter.js +26 -61
  119. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  120. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  121. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  122. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  123. package/dist/react/runtimes/useRemoteThreadListRuntime.js +64 -24
  124. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  125. package/dist/react/utils/groupParts.d.ts +6 -1
  126. package/dist/react/utils/groupParts.d.ts.map +1 -1
  127. package/dist/react/utils/groupParts.js +6 -0
  128. package/dist/react/utils/groupParts.js.map +1 -1
  129. package/dist/react/utils/useJSONEqual.d.ts +3 -1
  130. package/dist/react/utils/useJSONEqual.d.ts.map +1 -1
  131. package/dist/react/utils/useJSONEqual.js +6 -1
  132. package/dist/react/utils/useJSONEqual.js.map +1 -1
  133. package/dist/react/utils/useJSONSchemaDependency.d.ts +7 -0
  134. package/dist/react/utils/useJSONSchemaDependency.d.ts.map +1 -0
  135. package/dist/react/utils/useJSONSchemaDependency.js +20 -0
  136. package/dist/react/utils/useJSONSchemaDependency.js.map +1 -0
  137. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  138. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  139. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  140. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  141. package/dist/runtime/api/thread-list-runtime.d.ts +3 -0
  142. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  143. package/dist/runtime/api/thread-list-runtime.js +18 -14
  144. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  145. package/dist/runtime/api/thread-runtime.d.ts +4 -0
  146. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  147. package/dist/runtime/api/thread-runtime.js +3 -6
  148. package/dist/runtime/api/thread-runtime.js.map +1 -1
  149. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
  150. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  151. package/dist/runtime/base/base-composer-runtime-core.js +62 -22
  152. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  153. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -0
  154. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  155. package/dist/runtime/base/base-thread-runtime-core.js +161 -69
  156. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  157. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  158. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  159. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +4 -1
  160. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  161. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  162. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/queue/message-queue.d.ts +5 -0
  164. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  165. package/dist/runtime/queue/message-queue.js +61 -6
  166. package/dist/runtime/queue/message-queue.js.map +1 -1
  167. package/dist/runtime/utils/auto-status.d.ts +6 -2
  168. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  169. package/dist/runtime/utils/auto-status.js +12 -4
  170. package/dist/runtime/utils/auto-status.js.map +1 -1
  171. package/dist/runtime/utils/external-message-conversion.d.ts +16 -4
  172. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  173. package/dist/runtime/utils/external-message-conversion.js +49 -13
  174. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  175. package/dist/runtime/utils/message-repository.d.ts +1 -0
  176. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  177. package/dist/runtime/utils/message-repository.js +18 -9
  178. package/dist/runtime/utils/message-repository.js.map +1 -1
  179. package/dist/runtime/utils/thread-message-like.d.ts +2 -1
  180. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  181. package/dist/runtime/utils/thread-message-like.js +5 -3
  182. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  183. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  184. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  185. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  186. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  187. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +8 -1
  188. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  189. package/dist/runtimes/internal.d.ts +3 -3
  190. package/dist/runtimes/internal.js +2 -2
  191. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
  192. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  193. package/dist/runtimes/local/local-thread-runtime-core.js +4 -1
  194. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  195. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  196. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  197. package/dist/runtimes/remote-thread-list/remote-thread-state.js +130 -42
  198. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  199. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  200. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  201. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  202. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  203. package/dist/runtimes/remote-thread-list/title.d.ts +3 -1
  204. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -1
  205. package/dist/runtimes/remote-thread-list/title.js +9 -1
  206. package/dist/runtimes/remote-thread-list/title.js.map +1 -1
  207. package/dist/runtimes/remote-thread-list/types.d.ts +10 -0
  208. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  209. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  210. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -1
  211. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  212. package/dist/store/clients/external-thread.d.ts.map +1 -1
  213. package/dist/store/clients/external-thread.js +260 -203
  214. package/dist/store/clients/external-thread.js.map +1 -1
  215. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  216. package/dist/store/clients/model-context-client.js +2 -1
  217. package/dist/store/clients/model-context-client.js.map +1 -1
  218. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  219. package/dist/store/clients/single-thread-list.js +1 -0
  220. package/dist/store/clients/single-thread-list.js.map +1 -1
  221. package/dist/store/clients/suggestions.d.ts.map +1 -1
  222. package/dist/store/clients/suggestions.js +5 -4
  223. package/dist/store/clients/suggestions.js.map +1 -1
  224. package/dist/store/clients/thread-tasks.d.ts +13 -0
  225. package/dist/store/clients/thread-tasks.d.ts.map +1 -0
  226. package/dist/store/clients/thread-tasks.js +69 -0
  227. package/dist/store/clients/thread-tasks.js.map +1 -0
  228. package/dist/store/index.d.ts +3 -2
  229. package/dist/store/index.js +3 -3
  230. package/dist/store/primitive-predicates.d.ts.map +1 -1
  231. package/dist/store/primitive-predicates.js +4 -4
  232. package/dist/store/primitive-predicates.js.map +1 -1
  233. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  234. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  235. package/dist/store/runtime-clients/composer-runtime-client.js +97 -61
  236. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  237. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  238. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  239. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  240. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  241. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  242. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  243. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  244. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  245. package/dist/store/runtime-clients/thread-list-runtime-client.js +54 -52
  246. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  247. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  248. package/dist/store/runtime-clients/thread-runtime-client.js +246 -126
  249. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  250. package/dist/store/scope-registration.d.ts +2 -0
  251. package/dist/store/scope-registration.d.ts.map +1 -1
  252. package/dist/store/scopes/composer.d.ts +10 -2
  253. package/dist/store/scopes/composer.d.ts.map +1 -1
  254. package/dist/store/scopes/message.d.ts +30 -1
  255. package/dist/store/scopes/message.d.ts.map +1 -1
  256. package/dist/store/scopes/task.d.ts +36 -0
  257. package/dist/store/scopes/task.d.ts.map +1 -0
  258. package/dist/store/scopes/task.js +0 -0
  259. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  260. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  261. package/dist/store/scopes/thread.d.ts +28 -0
  262. package/dist/store/scopes/thread.d.ts.map +1 -1
  263. package/dist/store/scopes/threads.d.ts +1 -0
  264. package/dist/store/scopes/threads.d.ts.map +1 -1
  265. package/dist/subscribable/subscribable.d.ts +2 -1
  266. package/dist/subscribable/subscribable.d.ts.map +1 -1
  267. package/dist/subscribable/subscribable.js +23 -15
  268. package/dist/subscribable/subscribable.js.map +1 -1
  269. package/dist/types/attachment.d.ts +2 -1
  270. package/dist/types/attachment.d.ts.map +1 -1
  271. package/dist/types/attachment.js +2 -1
  272. package/dist/types/attachment.js.map +1 -1
  273. package/dist/types/index.d.ts +2 -2
  274. package/dist/types/message.d.ts +8 -1
  275. package/dist/types/message.d.ts.map +1 -1
  276. package/dist/types/message.js.map +1 -1
  277. package/dist/utils/abortable-iterable.d.ts +19 -0
  278. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  279. package/dist/utils/abortable-iterable.js +88 -0
  280. package/dist/utils/abortable-iterable.js.map +1 -0
  281. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  282. package/dist/utils/composite-context-provider.js +20 -2
  283. package/dist/utils/composite-context-provider.js.map +1 -1
  284. package/dist/utils/getGroupStatus.d.ts +18 -2
  285. package/dist/utils/getGroupStatus.d.ts.map +1 -1
  286. package/dist/utils/getGroupStatus.js +32 -7
  287. package/dist/utils/getGroupStatus.js.map +1 -1
  288. package/dist/utils/record.d.ts +11 -0
  289. package/dist/utils/record.d.ts.map +1 -0
  290. package/dist/utils/record.js +12 -0
  291. package/dist/utils/record.js.map +1 -0
  292. package/dist/utils/wire-media.d.ts.map +1 -1
  293. package/dist/utils/wire-media.js +2 -2
  294. package/dist/utils/wire-media.js.map +1 -1
  295. package/package.json +11 -11
  296. package/src/adapters/attachment.test.ts +50 -1
  297. package/src/adapters/attachment.ts +7 -4
  298. package/src/adapters/speech.test.ts +236 -0
  299. package/src/adapters/speech.ts +17 -10
  300. package/src/adapters/thread-history.ts +3 -0
  301. package/src/index.ts +2 -0
  302. package/src/internal.ts +4 -0
  303. package/src/model-context/frame/host.test.ts +137 -1
  304. package/src/model-context/frame/host.ts +49 -16
  305. package/src/model-context/frame/provider.test.ts +71 -0
  306. package/src/model-context/frame/provider.ts +37 -7
  307. package/src/model-context/interactable-composer-metadata.test.ts +58 -0
  308. package/src/model-context/interactable-composer-metadata.ts +19 -13
  309. package/src/model-context/registry.test.ts +12 -0
  310. package/src/model-context/registry.ts +3 -2
  311. package/src/model-context/types.test.ts +25 -0
  312. package/src/model-context/types.ts +9 -4
  313. package/src/react/RuntimeAdapter.test.ts +34 -0
  314. package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +244 -0
  315. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +104 -7
  316. package/src/react/client/DataRenderers.test.tsx +45 -0
  317. package/src/react/client/DataRenderers.ts +13 -9
  318. package/src/react/client/InMemoryThreadList.ts +1 -0
  319. package/src/react/client/Interactables.test.ts +72 -1
  320. package/src/react/client/Interactables.ts +22 -14
  321. package/src/react/client/RemoteThreadList.background.test.ts +148 -6
  322. package/src/react/client/RemoteThreadList.concurrent.test.tsx +42 -1
  323. package/src/react/client/RemoteThreadList.test.ts +386 -4
  324. package/src/react/client/RemoteThreadList.ts +181 -112
  325. package/src/react/client/Tools.ts +22 -11
  326. package/src/react/client/interactable-model-context.test.ts +44 -0
  327. package/src/react/client/interactable-model-context.ts +3 -2
  328. package/src/react/index.ts +5 -1
  329. package/src/react/interactables-legacy/Interactables.ts +16 -16
  330. package/src/react/interactables-legacy/useAssistantInteractable.test.tsx +132 -0
  331. package/src/react/interactables-legacy/useAssistantInteractable.ts +10 -1
  332. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  333. package/src/react/model-context/makeAssistantTool.ts +2 -2
  334. package/src/react/model-context/useAssistantTool.ts +2 -2
  335. package/src/react/model-context/useInteractable.test.tsx +177 -0
  336. package/src/react/model-context/useInteractable.ts +3 -0
  337. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  338. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  339. package/src/react/primitive-hooks/useActionBarCopy.test.ts +0 -1
  340. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +0 -1
  341. package/src/react/primitives/message/MessageGroupedParts.test.tsx +96 -0
  342. package/src/react/primitives/message/MessageGroupedParts.tsx +15 -4
  343. package/src/react/primitives/part/PartMessages.test.tsx +104 -1
  344. package/src/react/primitives/part/PartMessages.tsx +1 -0
  345. package/src/react/primitives/thread/ThreadMessages.test.tsx +142 -0
  346. package/src/react/primitives/thread/ThreadMessages.tsx +8 -5
  347. package/src/react/providers/ReadonlyThreadProvider.test.tsx +56 -0
  348. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  349. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +33 -0
  350. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  351. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +28 -5
  352. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  353. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +105 -0
  354. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +143 -112
  355. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  356. package/src/react/runtimes/RemoteThreadResource.ts +19 -12
  357. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +20 -14
  358. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +703 -6
  359. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +492 -402
  360. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +77 -0
  361. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +27 -1
  362. package/src/react/runtimes/cloud/auiV0.ts +160 -25
  363. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +36 -0
  364. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +8 -1
  365. package/src/react/runtimes/cloud/sdkIdentity.ts +9 -0
  366. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +18 -4
  367. package/src/react/runtimes/external-message-converter.test.ts +136 -1
  368. package/src/react/runtimes/external-message-converter.ts +39 -93
  369. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  370. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  371. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  372. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  373. package/src/react/runtimes/useRemoteThreadListRuntime.ts +34 -1
  374. package/src/react/utils/groupParts.ts +12 -1
  375. package/src/react/utils/useJSONEqual.ts +9 -0
  376. package/src/react/utils/useJSONSchemaDependency.ts +22 -0
  377. package/src/runtime/api/message-runtime.test.ts +75 -0
  378. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  379. package/src/runtime/api/thread-list-runtime.ts +21 -11
  380. package/src/runtime/api/thread-runtime.test.ts +136 -0
  381. package/src/runtime/api/thread-runtime.ts +10 -5
  382. package/src/runtime/base/base-composer-runtime-core.ts +105 -24
  383. package/src/runtime/base/base-thread-runtime-core.test.ts +1063 -2
  384. package/src/runtime/base/base-thread-runtime-core.ts +228 -87
  385. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  386. package/src/runtime/interfaces/thread-list-runtime-core.ts +5 -1
  387. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  388. package/src/runtime/queue/message-queue.ts +68 -6
  389. package/src/runtime/utils/auto-status.test.ts +112 -1
  390. package/src/runtime/utils/auto-status.ts +31 -5
  391. package/src/runtime/utils/external-message-conversion.test.ts +202 -0
  392. package/src/runtime/utils/external-message-conversion.ts +123 -32
  393. package/src/runtime/utils/message-repository.test.ts +49 -0
  394. package/src/runtime/utils/message-repository.ts +32 -20
  395. package/src/runtime/utils/thread-message-like.test.ts +52 -0
  396. package/src/runtime/utils/thread-message-like.ts +5 -1
  397. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  398. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +31 -1
  399. package/src/runtimes/internal.ts +1 -0
  400. package/src/runtimes/local/local-thread-runtime-core.test.ts +21 -0
  401. package/src/runtimes/local/local-thread-runtime-core.ts +11 -1
  402. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +128 -2
  403. package/src/runtimes/remote-thread-list/remote-thread-state.ts +182 -42
  404. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  405. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  406. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  407. package/src/runtimes/remote-thread-list/title.ts +13 -0
  408. package/src/runtimes/remote-thread-list/types.ts +10 -0
  409. package/src/runtimes/tool-invocations/EDGE_CASES.md +2 -6
  410. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +133 -0
  411. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +10 -6
  412. package/src/store/clients/external-thread.ts +67 -4
  413. package/src/store/clients/model-context-client.ts +5 -1
  414. package/src/store/clients/single-thread-list.ts +1 -0
  415. package/src/store/clients/suggestions.test.ts +40 -0
  416. package/src/store/clients/suggestions.ts +13 -10
  417. package/src/store/clients/thread-tasks.test.ts +274 -0
  418. package/src/store/clients/thread-tasks.ts +138 -0
  419. package/src/store/index.ts +8 -0
  420. package/src/store/primitive-predicates.test.ts +48 -5
  421. package/src/store/primitive-predicates.ts +7 -2
  422. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  423. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  424. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  425. package/src/store/runtime-clients/thread-list-runtime-client.ts +1 -0
  426. package/src/store/runtime-clients/thread-runtime-client.ts +89 -11
  427. package/src/store/scope-registration.ts +2 -0
  428. package/src/store/scopes/composer.ts +16 -4
  429. package/src/store/scopes/message.ts +14 -0
  430. package/src/store/scopes/task.ts +34 -0
  431. package/src/store/scopes/thread-list-item.ts +5 -2
  432. package/src/store/scopes/thread.ts +20 -0
  433. package/src/store/scopes/threads.ts +1 -0
  434. package/src/store/thread-tasks.test.tsx +209 -0
  435. package/src/subscribable/subscribable.test.ts +156 -1
  436. package/src/subscribable/subscribable.ts +25 -15
  437. package/src/tests/MessageRepository.test.ts +117 -5
  438. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  439. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  440. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +47 -0
  441. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  442. package/src/tests/attachment-adapters.test.ts +14 -0
  443. package/src/tests/auiV0Encode.test.ts +300 -0
  444. package/src/tests/base-composer-runtime-core-send.test.ts +51 -0
  445. package/src/tests/base-composer-runtime-core.test.ts +353 -2
  446. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  447. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +178 -18
  448. package/src/tests/external-thread-attachments.test.tsx +208 -0
  449. package/src/tests/external-thread-parity.test.tsx +57 -0
  450. package/src/tests/groupParts.test.ts +71 -0
  451. package/src/tests/message-queue.test.ts +253 -0
  452. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  453. package/src/tests/remote-thread-list-reactive-threadId.test.ts +51 -0
  454. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  455. package/src/tests/thread-switch-events.test.tsx +78 -0
  456. package/src/tests/tools-scope-migration.test.tsx +28 -0
  457. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +43 -0
  458. package/src/types/attachment.ts +4 -0
  459. package/src/types/index.ts +1 -0
  460. package/src/types/message.ts +8 -0
  461. package/src/utils/abortable-iterable.test.ts +328 -0
  462. package/src/utils/abortable-iterable.ts +104 -0
  463. package/src/utils/composite-context-provider.test.ts +25 -0
  464. package/src/utils/composite-context-provider.ts +23 -2
  465. package/src/utils/getGroupStatus.test.ts +70 -4
  466. package/src/utils/getGroupStatus.ts +35 -12
  467. package/src/utils/record.test.ts +48 -0
  468. package/src/utils/record.ts +11 -0
  469. package/src/utils/wire-media.test.ts +10 -0
  470. package/src/utils/wire-media.ts +6 -2
@@ -1,9 +1,94 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { convertExternalMessages } from "./external-message-converter";
2
+ import {
3
+ convertExternalMessages,
4
+ createExternalMessageConversionCache,
5
+ } from "./external-message-converter";
3
6
  import type { useExternalMessageConverter } from "./external-message-converter";
4
7
  import { isErrorMessageId } from "../../utils/id";
5
8
 
6
9
  describe("convertExternalMessages", () => {
10
+ it("keeps unchanged messages and rebuilds a replaced one with an opt-in cache", () => {
11
+ type Input = { id: string; role: "user" | "assistant"; text: string };
12
+ const metadata = {};
13
+ const cache = createExternalMessageConversionCache();
14
+ const callback: useExternalMessageConverter.Callback<Input> = (
15
+ message,
16
+ ) => ({
17
+ id: message.id,
18
+ role: message.role,
19
+ content: message.text,
20
+ });
21
+ const question: Input = { id: "question", role: "user", text: "hi" };
22
+ const answer: Input = { id: "answer", role: "assistant", text: "hel" };
23
+
24
+ const first = convertExternalMessages(
25
+ [question, answer],
26
+ callback,
27
+ true,
28
+ metadata,
29
+ cache,
30
+ );
31
+ const second = convertExternalMessages(
32
+ [question, { ...answer, text: "hello" }],
33
+ callback,
34
+ true,
35
+ metadata,
36
+ cache,
37
+ );
38
+
39
+ expect(second[0]).toBe(first[0]);
40
+ expect(second[1]).not.toBe(first[1]);
41
+ expect(second[1]?.content).toMatchObject([{ type: "text", text: "hello" }]);
42
+ });
43
+
44
+ it("invalidates cached messages when the callback or metadata changes", () => {
45
+ const input = { text: "nested" };
46
+ const cache = createExternalMessageConversionCache();
47
+ const withFormat =
48
+ (
49
+ format: (text: string) => string,
50
+ ): useExternalMessageConverter.Callback<typeof input> =>
51
+ (message, metadata) => ({
52
+ id: "nested",
53
+ role: "assistant",
54
+ content: format(message.text),
55
+ metadata: { timing: metadata.messageTiming?.["nested"] },
56
+ });
57
+ const callback = withFormat((text) => text);
58
+ const uppercaseCallback = withFormat((text) => text.toUpperCase());
59
+ const metadata = {};
60
+ const timing = { streamStartTime: 1, totalChunks: 1, toolCallCount: 0 };
61
+
62
+ const first = convertExternalMessages(
63
+ [input],
64
+ callback,
65
+ false,
66
+ metadata,
67
+ cache,
68
+ );
69
+ const second = convertExternalMessages(
70
+ [input],
71
+ uppercaseCallback,
72
+ false,
73
+ metadata,
74
+ cache,
75
+ );
76
+ const third = convertExternalMessages(
77
+ [input],
78
+ uppercaseCallback,
79
+ false,
80
+ { messageTiming: { nested: timing } },
81
+ cache,
82
+ );
83
+
84
+ expect(second[0]).not.toBe(first[0]);
85
+ expect(second[0]?.content).toMatchObject([
86
+ { type: "text", text: "NESTED" },
87
+ ]);
88
+ expect(third[0]).not.toBe(second[0]);
89
+ expect(third[0]?.metadata).toMatchObject({ timing });
90
+ });
91
+
7
92
  describe("reasoning part merging", () => {
8
93
  it("should merge reasoning parts with the same parentId", () => {
9
94
  const messages = [
@@ -205,6 +290,56 @@ describe("convertExternalMessages", () => {
205
290
  expect((toolCallParts[0] as any).argsText).toBe('{"query":"new"}');
206
291
  });
207
292
 
293
+ it("keeps a merged tool call in its original content position", () => {
294
+ const messages = [
295
+ {
296
+ role: "assistant" as const,
297
+ content: [
298
+ {
299
+ type: "tool-call" as const,
300
+ toolCallId: "tc1",
301
+ toolName: "search",
302
+ args: { query: "old" },
303
+ },
304
+ { type: "text" as const, text: "after tool" },
305
+ ],
306
+ },
307
+ {
308
+ role: "assistant" as const,
309
+ content: [
310
+ {
311
+ type: "tool-call" as const,
312
+ toolCallId: "tc1",
313
+ toolName: "search",
314
+ args: { query: "new" },
315
+ },
316
+ ],
317
+ },
318
+ {
319
+ role: "tool" as const,
320
+ toolCallId: "tc1",
321
+ result: "found",
322
+ },
323
+ ];
324
+
325
+ const result = convertExternalMessages(
326
+ messages,
327
+ (message) => message,
328
+ false,
329
+ {},
330
+ );
331
+
332
+ expect(result[0]!.content).toMatchObject([
333
+ {
334
+ type: "tool-call",
335
+ toolCallId: "tc1",
336
+ args: { query: "new" },
337
+ result: "found",
338
+ },
339
+ { type: "text", text: "after tool" },
340
+ ]);
341
+ });
342
+
208
343
  it("should ignore orphaned tool results without throwing", () => {
209
344
  const messages = [
210
345
  {
@@ -2,28 +2,29 @@
2
2
 
3
3
  import { useMemo, useState } from "react";
4
4
  import {
5
- chunkExternalMessages,
6
- completeExternalMessageConversion,
7
- convertExternalMessageCallback,
8
- convertExternalMessageChunk,
9
5
  convertExternalMessages as convertExternalMessagesInternal,
10
- shallowArrayEqual,
6
+ createExternalMessageConversionCache as createExternalMessageConversionCacheInternal,
11
7
  type ExternalMessageConverterCallback,
12
- type ExternalMessageConverterCallbackResult,
13
- type ExternalMessageConverterChunk,
14
8
  type ExternalMessageConverterMessage,
15
9
  type ExternalMessageConverterMetadata,
10
+ type InternalExternalMessageConversionCache,
16
11
  type JoinStrategy,
17
12
  } from "../../runtime/utils/external-message-conversion";
18
13
  import { bindExternalStoreMessage } from "../../runtime/utils/external-store-message";
19
- import { ThreadMessageConverter } from "../../runtimes/external-store/thread-message-converter";
20
- import type { ThreadMessage } from "../../types/message";
21
-
22
- // Generatedness is tracked by identity, not by id shape: a caller-supplied id
23
- // that happens to match the generated pattern must never be rewritten.
24
- const generatedFallbackMessages = new WeakSet<object>();
25
14
 
26
15
  export type { JoinStrategy };
16
+ export type ExternalMessageConversionCache = {
17
+ readonly __brand: unique symbol;
18
+ };
19
+
20
+ /**
21
+ * Creates a cache for plain external message conversion. Pass the same cache on every call for one message list, and a source message that has not changed since the previous call converts to the same `ThreadMessage` object.
22
+ *
23
+ * Entries are keyed by source message identity and rebuilt whenever `callback` or `metadata` is a different object than on the previous call, so keep both referentially stable.
24
+ */
25
+ export const createExternalMessageConversionCache =
26
+ (): ExternalMessageConversionCache =>
27
+ createExternalMessageConversionCacheInternal() as unknown as ExternalMessageConversionCache;
27
28
 
28
29
  export namespace useExternalMessageConverter {
29
30
  export type Message = ExternalMessageConverterMessage;
@@ -31,17 +32,21 @@ export namespace useExternalMessageConverter {
31
32
  export type Callback<T> = ExternalMessageConverterCallback<T>;
32
33
  }
33
34
 
34
- export const convertExternalMessages: <T extends WeakKey>(
35
+ export const convertExternalMessages = <T extends WeakKey>(
35
36
  messages: T[],
36
37
  callback: useExternalMessageConverter.Callback<T>,
37
38
  isRunning: boolean,
38
39
  metadata: useExternalMessageConverter.Metadata,
39
- ) => ThreadMessage[] = convertExternalMessagesInternal;
40
-
41
- type CallbackCacheEntry<T> = ExternalMessageConverterCallbackResult<T> & {
42
- metadata: useExternalMessageConverter.Metadata;
43
- callback: useExternalMessageConverter.Callback<T>;
44
- };
40
+ cache?: ExternalMessageConversionCache,
41
+ ) =>
42
+ convertExternalMessagesInternal(
43
+ messages,
44
+ callback,
45
+ isRunning,
46
+ metadata,
47
+ undefined,
48
+ cache as unknown as InternalExternalMessageConversionCache<T> | undefined,
49
+ );
45
50
 
46
51
  export const useExternalMessageConverter = <T extends WeakKey>({
47
52
  callback,
@@ -56,89 +61,30 @@ export const useExternalMessageConverter = <T extends WeakKey>({
56
61
  joinStrategy?: JoinStrategy | undefined;
57
62
  metadata?: useExternalMessageConverter.Metadata | undefined;
58
63
  }) => {
59
- // The caches live for the component lifetime; React Compiler hoists
60
- // allocations without reactive dependencies out of useMemo, so re-creating
61
- // them on dependency change would not survive compilation. Staleness is
62
- // instead tracked per entry via the metadata/callback that produced it,
63
- // keeping correctness independent of memoization (which React treats as
64
- // droppable). A "use no memo" opt-out would restore the useMemo semantics
65
- // but leave cache flushing coupled to memo firing.
66
- const [caches] = useState(() => ({
67
- callbackCache: new WeakMap<T, CallbackCacheEntry<T>>(),
68
- chunkCache: new WeakMap<
69
- ExternalMessageConverterMessage,
70
- ExternalMessageConverterChunk<T>
71
- >(),
72
- converterCache: new ThreadMessageConverter(),
73
- }));
64
+ // The cache lives for the component lifetime: React Compiler hoists allocations without reactive dependencies out of useMemo, so entries carry the callback and metadata that produced them instead of being flushed when those change.
65
+ const [cache] = useState(() =>
66
+ createExternalMessageConversionCacheInternal<T>(),
67
+ );
74
68
 
75
69
  const state = useMemo(
76
70
  () => ({
77
71
  metadata: metadata ?? {},
78
72
  callback,
79
- ...caches,
80
73
  }),
81
- [callback, metadata, caches],
74
+ [callback, metadata],
82
75
  );
83
76
 
84
77
  return useMemo(() => {
85
- const callbackResults: ExternalMessageConverterCallbackResult<T>[] = [];
86
- for (const message of messages) {
87
- let result = state.callbackCache.get(message);
88
- if (
89
- !result ||
90
- result.metadata !== state.metadata ||
91
- result.callback !== state.callback
92
- ) {
93
- result = {
94
- ...convertExternalMessageCallback(
95
- message,
96
- state.callback,
97
- state.metadata,
98
- ),
99
- metadata: state.metadata,
100
- callback: state.callback,
101
- };
102
- state.callbackCache.set(message, result);
103
- }
104
- callbackResults.push(result);
105
- }
106
-
107
- const chunks = chunkExternalMessages(callbackResults, joinStrategy).map(
108
- (message) => {
109
- const key = message.outputs[0];
110
- if (!key) return message;
111
-
112
- const cached = state.chunkCache.get(key);
113
- if (cached && shallowArrayEqual(cached.outputs, message.outputs)) {
114
- return cached;
115
- }
116
- state.chunkCache.set(key, message);
117
- return message;
118
- },
119
- );
120
-
121
- const threadMessages = state.converterCache.convertMessages(
122
- chunks,
123
- (cache, message, idx) =>
124
- convertExternalMessageChunk(
125
- message,
126
- idx,
127
- chunks.length,
128
- isRunning,
129
- state.metadata.error,
130
- {
131
- message: cache,
132
- generatedFallbackMessages,
133
- },
134
- state.metadata.cancelledMessageIds,
135
- ),
78
+ const threadMessages = convertExternalMessagesInternal(
79
+ messages,
80
+ state.callback,
81
+ isRunning,
82
+ state.metadata,
83
+ joinStrategy,
84
+ cache,
136
85
  );
137
86
 
138
87
  bindExternalStoreMessage(threadMessages, messages);
139
- return completeExternalMessageConversion(
140
- threadMessages,
141
- state.metadata.error,
142
- );
143
- }, [state, messages, isRunning, joinStrategy]);
88
+ return threadMessages;
89
+ }, [state, messages, isRunning, joinStrategy, cache]);
144
90
  };
@@ -5,8 +5,49 @@ import { act, render } from "@testing-library/react";
5
5
  import { describe, expect, it, vi } from "vitest";
6
6
  import { useExternalStoreRuntime } from "./useExternalStoreRuntime";
7
7
  import type { AssistantRuntime } from "../../runtime/api/assistant-runtime";
8
+ import type { ThreadMessage } from "../../types/message";
9
+ import { RuntimeAdapterProvider } from "./RuntimeAdapterProvider";
10
+
11
+ const userMessage: ThreadMessage = {
12
+ id: "user-1",
13
+ role: "user",
14
+ content: [{ type: "text", text: "hello" }],
15
+ attachments: [],
16
+ createdAt: new Date(0),
17
+ metadata: { custom: {} },
18
+ };
8
19
 
9
20
  describe("useExternalStoreRuntime lifecycle", () => {
21
+ it("uses feedback supplied by the per-thread adapter context", () => {
22
+ const submit = vi.fn();
23
+ const capture: { runtime: AssistantRuntime | null } = { runtime: null };
24
+ const App = () => {
25
+ const runtime = useExternalStoreRuntime({
26
+ messages: [userMessage],
27
+ onNew: async () => {},
28
+ });
29
+ capture.runtime = runtime;
30
+ return null;
31
+ };
32
+
33
+ render(
34
+ <RuntimeAdapterProvider adapters={{ feedback: { submit } }}>
35
+ <App />
36
+ </RuntimeAdapterProvider>,
37
+ );
38
+
39
+ expect(capture.runtime!.thread.getState().capabilities.feedback).toBe(true);
40
+ act(() => {
41
+ capture
42
+ .runtime!.thread.getMessageById("user-1")
43
+ .submitFeedback({ type: "positive" });
44
+ });
45
+ expect(submit).toHaveBeenCalledWith({
46
+ message: expect.objectContaining({ id: "user-1" }),
47
+ type: "positive",
48
+ });
49
+ });
50
+
10
51
  it("keeps dispatching appends after StrictMode's simulated remount", async () => {
11
52
  const onNew = vi.fn(async () => {});
12
53
  const capture: { runtime: AssistantRuntime | null } = { runtime: null };
@@ -11,7 +11,15 @@ import { useRuntimeAdapters } from "./RuntimeAdapterProvider";
11
11
  export const useExternalStoreRuntime = <T>(
12
12
  store: ExternalStoreAdapter<T>,
13
13
  ): AssistantRuntime => {
14
- const [runtime] = useState(() => new ExternalStoreRuntimeCore(store));
14
+ const { modelContext, feedback } = useRuntimeAdapters() ?? {};
15
+ const adaptedStore = useMemo(() => {
16
+ if (!feedback || store.adapters?.feedback) return store;
17
+ return {
18
+ ...store,
19
+ adapters: { ...store.adapters, feedback },
20
+ };
21
+ }, [feedback, store]);
22
+ const [runtime] = useState(() => new ExternalStoreRuntimeCore(adaptedStore));
15
23
 
16
24
  useEffect(() => {
17
25
  return () => {
@@ -20,11 +28,9 @@ export const useExternalStoreRuntime = <T>(
20
28
  }, [runtime]);
21
29
 
22
30
  useEffect(() => {
23
- runtime.setAdapter(store);
31
+ runtime.setAdapter(adaptedStore);
24
32
  });
25
33
 
26
- const { modelContext } = useRuntimeAdapters() ?? {};
27
-
28
34
  useEffect(() => {
29
35
  if (!modelContext) return undefined;
30
36
  return runtime.registerModelContextProvider(modelContext);
@@ -32,6 +32,26 @@ afterEach(() => {
32
32
  });
33
33
 
34
34
  describe("useLocalRuntime", () => {
35
+ it("enables feedback for Cloud threads", async () => {
36
+ const cloud = makeCloud();
37
+ let runtime: ReturnType<typeof useLocalRuntime> | null = null;
38
+ const App = () => {
39
+ runtime = useLocalRuntime(chatModel, { cloud });
40
+ return (
41
+ <AssistantRuntimeProvider runtime={runtime}>
42
+ <div />
43
+ </AssistantRuntimeProvider>
44
+ );
45
+ };
46
+
47
+ render(<App />);
48
+
49
+ await waitFor(() => {
50
+ expect(cloud.threads.list).toHaveBeenCalledTimes(2);
51
+ expect(runtime!.thread.getState().capabilities.feedback).toBe(true);
52
+ });
53
+ });
54
+
35
55
  it("surfaces the live thread after mount without user input", async () => {
36
56
  const auiRef: { current: ReturnType<typeof useAui> | null } = {
37
57
  current: null,
@@ -0,0 +1,108 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, cleanup, render, waitFor } from "@testing-library/react";
4
+ import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import type { AssistantRuntime } from "../../runtime/api/assistant-runtime";
6
+ import { makeAdapter } from "../../tests/remote-thread-list-test-helpers";
7
+ import { AssistantRuntimeProvider } from "../AssistantRuntimeProvider";
8
+ import { useExternalStoreRuntime } from "./useExternalStoreRuntime";
9
+ import { useRemoteThreadListRuntime } from "./useRemoteThreadListRuntime";
10
+
11
+ const EMPTY_MESSAGES: readonly never[] = [];
12
+
13
+ afterEach(() => {
14
+ cleanup();
15
+ vi.restoreAllMocks();
16
+ });
17
+
18
+ describe("useRemoteThreadListRuntime load recovery", () => {
19
+ it("reloads once when the browser comes online after a failed load", async () => {
20
+ const error = new Error("offline");
21
+ vi.spyOn(console, "error").mockImplementation(() => {});
22
+ const list = vi
23
+ .fn()
24
+ .mockRejectedValueOnce(error)
25
+ .mockResolvedValueOnce({ threads: [] });
26
+ const adapter = makeAdapter({ list });
27
+ const runtimeRef: { current: AssistantRuntime | null } = { current: null };
28
+ const useThreadRuntime = () =>
29
+ useExternalStoreRuntime({
30
+ messages: EMPTY_MESSAGES,
31
+ onNew: async () => {},
32
+ } as never);
33
+
34
+ const App = () => {
35
+ const runtime = useRemoteThreadListRuntime({
36
+ adapter,
37
+ runtimeHook: useThreadRuntime,
38
+ });
39
+ runtimeRef.current = runtime;
40
+ return (
41
+ <AssistantRuntimeProvider runtime={runtime}>
42
+ {null}
43
+ </AssistantRuntimeProvider>
44
+ );
45
+ };
46
+
47
+ render(<App />);
48
+ await waitFor(() => expect(list).toHaveBeenCalledTimes(1));
49
+ await waitFor(() =>
50
+ expect(runtimeRef.current!.threads.getState().loadError).toBe(error),
51
+ );
52
+
53
+ act(() => window.dispatchEvent(new Event("online")));
54
+
55
+ await waitFor(() => expect(list).toHaveBeenCalledTimes(2));
56
+ await act(async () => {});
57
+ expect(list).toHaveBeenCalledTimes(2);
58
+ expect(runtimeRef.current!.threads.getState().loadError).toBeUndefined();
59
+ });
60
+
61
+ it("reloads once when the page becomes visible after a failed load", async () => {
62
+ const error = new Error("offline");
63
+ vi.spyOn(console, "error").mockImplementation(() => {});
64
+ vi.spyOn(document, "visibilityState", "get").mockReturnValue("visible");
65
+ const list = vi
66
+ .fn()
67
+ .mockRejectedValueOnce(error)
68
+ .mockResolvedValueOnce({ threads: [] });
69
+ const adapter = makeAdapter({ list });
70
+ const runtimeRef: { current: AssistantRuntime | null } = { current: null };
71
+ const useThreadRuntime = () =>
72
+ useExternalStoreRuntime({
73
+ messages: EMPTY_MESSAGES,
74
+ onNew: async () => {},
75
+ } as never);
76
+
77
+ const App = () => {
78
+ const runtime = useRemoteThreadListRuntime({
79
+ adapter,
80
+ runtimeHook: useThreadRuntime,
81
+ });
82
+ runtimeRef.current = runtime;
83
+ return (
84
+ <AssistantRuntimeProvider runtime={runtime}>
85
+ {null}
86
+ </AssistantRuntimeProvider>
87
+ );
88
+ };
89
+
90
+ render(<App />);
91
+ await waitFor(() => expect(list).toHaveBeenCalledTimes(1));
92
+ await waitFor(() =>
93
+ expect(runtimeRef.current!.threads.getState().loadError).toBe(error),
94
+ );
95
+
96
+ act(() => document.dispatchEvent(new Event("visibilitychange")));
97
+
98
+ await waitFor(() => expect(list).toHaveBeenCalledTimes(2));
99
+ await waitFor(() =>
100
+ expect(runtimeRef.current!.threads.getState().loadError).toBeUndefined(),
101
+ );
102
+
103
+ act(() => document.dispatchEvent(new Event("visibilitychange")));
104
+ await act(async () => {});
105
+
106
+ expect(list).toHaveBeenCalledTimes(2);
107
+ });
108
+ });
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  useState,
3
3
  useEffect,
4
+ useInsertionEffect,
4
5
  useMemo,
5
6
  useRef,
6
7
  useCallback,
@@ -39,11 +40,43 @@ const useRemoteThreadListRuntimeImpl = (
39
40
  options: RemoteThreadListOptions,
40
41
  ): AssistantRuntime => {
41
42
  const [runtime] = useState(() => new RemoteThreadListRuntimeCore(options));
43
+
44
+ // Insertion-effect cleanup runs only when React deletes the fiber, so a
45
+ // hidden <Activity> or a re-suspended boundary keeps the threads alive; the
46
+ // disposal is deferred to a microtask because it notifies subscribers and
47
+ // React forbids scheduling updates from an insertion effect.
48
+ useInsertionEffect(
49
+ () => () => queueMicrotask(() => runtime.threads.__internal_dispose()),
50
+ [runtime],
51
+ );
52
+
42
53
  useEffect(() => {
43
54
  runtime.threads.__internal_setOptions(options);
44
55
  runtime.threads.__internal_load();
45
56
  }, [runtime, options]);
46
57
 
58
+ useEffect(() => {
59
+ if (typeof window === "undefined" || typeof document === "undefined") {
60
+ return;
61
+ }
62
+
63
+ const reloadAfterError = () => {
64
+ if (runtime.threads.loadError !== undefined) {
65
+ void runtime.threads.reload();
66
+ }
67
+ };
68
+ const reloadAfterVisible = () => {
69
+ if (document.visibilityState === "visible") reloadAfterError();
70
+ };
71
+
72
+ window.addEventListener("online", reloadAfterError);
73
+ document.addEventListener("visibilitychange", reloadAfterVisible);
74
+ return () => {
75
+ window.removeEventListener("online", reloadAfterError);
76
+ document.removeEventListener("visibilitychange", reloadAfterVisible);
77
+ };
78
+ }, [runtime]);
79
+
47
80
  return useMemo(() => new AssistantRuntimeImpl(runtime), [runtime]);
48
81
  };
49
82
 
@@ -76,7 +109,7 @@ export const useRemoteThreadListRuntime = (
76
109
  );
77
110
 
78
111
  const onThreadIdChange = useEffectEvent((threadId: string | undefined) => {
79
- options.onThreadIdChange?.(threadId);
112
+ return options.onThreadIdChange?.(threadId);
80
113
  });
81
114
 
82
115
  const stableOptions = useMemo<RemoteThreadListOptions>(
@@ -47,8 +47,13 @@ export const GROUPBY_MEMO_KEY: unique symbol = Symbol.for(
47
47
  * `display: "standalone"`). Resolving the registry-driven cases reads the
48
48
  * {@link GroupByContext} passed to the `groupBy` function. Takes precedence
49
49
  * over the `"tool-call"` entry.
50
+ *
51
+ * A `"tool-call:<name>"` entry matches tool calls by tool name and takes precedence over the plain `"tool-call"` entry, while `"standalone-tool-call"` still wins first.
50
52
  */
51
- type GroupPartType = PartState["type"] | "standalone-tool-call";
53
+ type GroupPartType =
54
+ | PartState["type"]
55
+ | "standalone-tool-call"
56
+ | `tool-call:${string}`;
52
57
 
53
58
  /**
54
59
  * Build a `groupBy` from a `part.type → group-key path` lookup.
@@ -63,11 +68,14 @@ type GroupPartType = PartState["type"] | "standalone-tool-call";
63
68
  * {@link GroupByContext} that `<MessagePrimitive.GroupedParts>` passes to the
64
69
  * `groupBy` function — the helper needs nothing threaded into it.
65
70
  *
71
+ * A `"tool-call:<name>"` entry matches tool calls by tool name and takes precedence over the plain `"tool-call"` entry, while `"standalone-tool-call"` still wins first.
72
+ *
66
73
  * @example
67
74
  * ```tsx
68
75
  * <MessagePrimitive.GroupedParts
69
76
  * groupBy={groupPartByType({
70
77
  * reasoning: ["group-thought", "group-reasoning"],
78
+ * "tool-call:task": ["group-subagents"],
71
79
  * "tool-call": ["group-thought", "group-tool"],
72
80
  * "standalone-tool-call": [],
73
81
  * })}
@@ -91,6 +99,9 @@ export const groupPartByType = <TKey extends `group-${string}`>(
91
99
  if (isStandalone && lookup["standalone-tool-call"] !== undefined) {
92
100
  return lookup["standalone-tool-call"]!;
93
101
  }
102
+ const toolCallPath = lookup[`tool-call:${part.toolName}`];
103
+ if (toolCallPath !== undefined) return toolCallPath;
104
+ return lookup["tool-call"] ?? [];
94
105
  }
95
106
  return lookup[part.type] ?? [];
96
107
  }) as ((part: PartState, context?: GroupByContext) => readonly TKey[]) & {
@@ -1,6 +1,15 @@
1
1
  import { useRef } from "react";
2
+ import type { ReadonlyJSONValue } from "assistant-stream/utils";
2
3
  import { isJSONValueEqual } from "../../utils/json/is-json-equal";
3
4
 
5
+ export function useJSONEqualValue<T extends ReadonlyJSONValue>(value: T): T {
6
+ const prev = useRef(value);
7
+ if (prev.current !== value && !isJSONValueEqual(prev.current, value)) {
8
+ prev.current = value;
9
+ }
10
+ return prev.current;
11
+ }
12
+
4
13
  /**
5
14
  * Like `useShallow`, but with JSON deep-equality. Use when a selector derives an
6
15
  * equal-but-fresh value on every store update — e.g. folding over
@@ -0,0 +1,22 @@
1
+ import { useMemo } from "react";
2
+ import { toJSONSchema } from "assistant-stream";
3
+ import type { ReadonlyJSONValue } from "assistant-stream/utils";
4
+ import { isJSONValue } from "../../utils/json/is-json";
5
+ import type { Unstable_InteractableStateSchema } from "../types/scopes/interactables";
6
+ import { useJSONEqualValue } from "./useJSONEqual";
7
+
8
+ export function useJSONSchemaDependency(
9
+ schema: Unstable_InteractableStateSchema,
10
+ ): ReadonlyJSONValue {
11
+ const normalizedSchema = useMemo(() => {
12
+ try {
13
+ const jsonSchema = toJSONSchema(schema);
14
+ return isJSONValue(jsonSchema) ? jsonSchema : null;
15
+ } catch {
16
+ // Opaque schemas share one fallback so rebuilt instances do not churn registration.
17
+ return null;
18
+ }
19
+ }, [schema]);
20
+
21
+ return useJSONEqualValue(normalizedSchema);
22
+ }