@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
@@ -7,6 +7,62 @@ afterEach(() => {
7
7
  });
8
8
 
9
9
  describe("WebSpeechSynthesisAdapter", () => {
10
+ const stubSpeechSynthesis = () => {
11
+ const utterances: EventTarget[] = [];
12
+ class MockSpeechSynthesisUtterance extends EventTarget {
13
+ constructor() {
14
+ super();
15
+ utterances.push(this);
16
+ }
17
+ }
18
+ const cancel = vi.fn();
19
+ vi.stubGlobal("SpeechSynthesisUtterance", MockSpeechSynthesisUtterance);
20
+ vi.stubGlobal("window", { speechSynthesis: { speak: vi.fn(), cancel } });
21
+ return { utterances, cancel };
22
+ };
23
+
24
+ it.each(["end", "error"])(
25
+ "does not cancel newer playback through a handle that received %s",
26
+ (event) => {
27
+ const { utterances, cancel } = stubSpeechSynthesis();
28
+ const adapter = new WebSpeechSynthesisAdapter();
29
+ const old = adapter.speak("old");
30
+ utterances[0]!.dispatchEvent(new Event(event));
31
+ const endedStatus = old.status;
32
+ const current = adapter.speak("current");
33
+
34
+ old.cancel();
35
+ old.cancel();
36
+
37
+ expect(cancel).not.toHaveBeenCalled();
38
+ expect(old.status).toBe(endedStatus);
39
+ expect(current.status).toEqual({ type: "running" });
40
+ },
41
+ );
42
+
43
+ it("cancels active playback once and leaves later playback alone", () => {
44
+ const { cancel } = stubSpeechSynthesis();
45
+ const adapter = new WebSpeechSynthesisAdapter();
46
+ const old = adapter.speak("old");
47
+ const onChange = vi.fn();
48
+ old.subscribe(onChange);
49
+
50
+ old.cancel();
51
+
52
+ expect(cancel).toHaveBeenCalledOnce();
53
+ expect(old.status).toMatchObject({ type: "ended", reason: "cancelled" });
54
+ expect(onChange).toHaveBeenCalledOnce();
55
+
56
+ const current = adapter.speak("current");
57
+ old.cancel();
58
+
59
+ expect(cancel).toHaveBeenCalledOnce();
60
+ expect(onChange).toHaveBeenCalledOnce();
61
+ expect(current.status).toEqual({ type: "running" });
62
+ current.cancel();
63
+ expect(cancel).toHaveBeenCalledTimes(2);
64
+ });
65
+
10
66
  it("isolates a late subscriber that throws after the utterance ended", async () => {
11
67
  const listeners = new Map<string, EventListener>();
12
68
  class MockSpeechSynthesisUtterance {
@@ -109,6 +165,186 @@ describe("WebSpeechDictationAdapter", () => {
109
165
  return listeners;
110
166
  };
111
167
 
168
+ const emitResults = (
169
+ listeners: Map<string, EventListener>,
170
+ results: [transcript: string, isFinal: boolean][],
171
+ resultIndex = 0,
172
+ ) => {
173
+ listeners.get("result")!({
174
+ resultIndex,
175
+ results: results.map(([transcript, isFinal]) => ({
176
+ 0: { transcript },
177
+ isFinal,
178
+ })),
179
+ } as unknown as Event);
180
+ };
181
+
182
+ it("publishes the entire interim suffix when only its last result changes", () => {
183
+ const listeners = stubSpeechRecognition();
184
+ const session = new WebSpeechDictationAdapter().listen();
185
+ const onSpeech = vi.fn();
186
+ session.onSpeech(onSpeech);
187
+
188
+ emitResults(listeners, [
189
+ ["hello ", false],
190
+ ["world", false],
191
+ ]);
192
+ emitResults(
193
+ listeners,
194
+ [
195
+ ["hello ", false],
196
+ ["there", false],
197
+ ],
198
+ 1,
199
+ );
200
+
201
+ expect(onSpeech.mock.calls).toEqual([
202
+ [{ transcript: "hello world", isFinal: false }],
203
+ [{ transcript: "hello there", isFinal: false }],
204
+ ]);
205
+ });
206
+
207
+ it("publishes partial and complete retractions with no changed results", () => {
208
+ const listeners = stubSpeechRecognition();
209
+ const session = new WebSpeechDictationAdapter().listen();
210
+ const onSpeech = vi.fn();
211
+ session.onSpeech(onSpeech);
212
+
213
+ emitResults(listeners, [
214
+ ["hello ", false],
215
+ ["world", false],
216
+ ]);
217
+ emitResults(listeners, [["hello ", false]], 1);
218
+ emitResults(listeners, []);
219
+ emitResults(listeners, []);
220
+
221
+ expect(onSpeech.mock.calls).toEqual([
222
+ [{ transcript: "hello world", isFinal: false }],
223
+ [{ transcript: "hello ", isFinal: false }],
224
+ [{ transcript: "", isFinal: false }],
225
+ ]);
226
+ });
227
+
228
+ it("delivers changed final results once before the remaining interim suffix", () => {
229
+ const listeners = stubSpeechRecognition();
230
+ const session = new WebSpeechDictationAdapter().listen();
231
+ const onSpeech = vi.fn();
232
+ const onEnd = vi.fn();
233
+ session.onSpeech(onSpeech);
234
+ session.onSpeechEnd(onEnd);
235
+
236
+ emitResults(listeners, [
237
+ ["hello ", true],
238
+ ["wor", false],
239
+ ["ld", false],
240
+ ]);
241
+ emitResults(
242
+ listeners,
243
+ [
244
+ ["hello ", true],
245
+ ["world", true],
246
+ ],
247
+ 1,
248
+ );
249
+ listeners.get("end")!(new Event("end"));
250
+
251
+ expect(onSpeech.mock.calls).toEqual([
252
+ [{ transcript: "hello ", isFinal: true }],
253
+ [{ transcript: "world", isFinal: false }],
254
+ [{ transcript: "world", isFinal: true }],
255
+ ]);
256
+ expect(onEnd).toHaveBeenCalledExactlyOnceWith({
257
+ transcript: "hello world",
258
+ });
259
+ });
260
+
261
+ it("clears an interim suffix while retaining earlier final results", () => {
262
+ const listeners = stubSpeechRecognition();
263
+ const session = new WebSpeechDictationAdapter().listen();
264
+ const onSpeech = vi.fn();
265
+ session.onSpeech(onSpeech);
266
+
267
+ emitResults(listeners, [
268
+ ["hello ", true],
269
+ ["world", false],
270
+ ]);
271
+ emitResults(listeners, [["hello ", true]], 1);
272
+
273
+ expect(onSpeech.mock.calls).toEqual([
274
+ [{ transcript: "hello ", isFinal: true }],
275
+ [{ transcript: "world", isFinal: false }],
276
+ [{ transcript: "", isFinal: false }],
277
+ ]);
278
+ });
279
+
280
+ it("keeps final-only delivery free of empty interim updates", () => {
281
+ const listeners = stubSpeechRecognition();
282
+ const session = new WebSpeechDictationAdapter().listen();
283
+ const onSpeech = vi.fn();
284
+ session.onSpeech(onSpeech);
285
+
286
+ emitResults(listeners, [["hello", true]]);
287
+ emitResults(listeners, [["hello", true]], 1);
288
+
289
+ expect(onSpeech).toHaveBeenCalledExactlyOnceWith({
290
+ transcript: "hello",
291
+ isFinal: true,
292
+ });
293
+ });
294
+
295
+ it("does not drop an unconsumed final when resultIndex skips it", () => {
296
+ const listeners = stubSpeechRecognition();
297
+ const session = new WebSpeechDictationAdapter().listen();
298
+ const onSpeech = vi.fn();
299
+ const onEnd = vi.fn();
300
+ session.onSpeech(onSpeech);
301
+ session.onSpeechEnd(onEnd);
302
+
303
+ emitResults(listeners, [["hello ", false]]);
304
+ emitResults(
305
+ listeners,
306
+ [
307
+ ["hello ", true],
308
+ ["world", false],
309
+ ],
310
+ 1,
311
+ );
312
+ listeners.get("end")!(new Event("end"));
313
+
314
+ expect(onSpeech.mock.calls).toEqual([
315
+ [{ transcript: "hello ", isFinal: false }],
316
+ [{ transcript: "hello ", isFinal: true }],
317
+ [{ transcript: "world", isFinal: false }],
318
+ ]);
319
+ expect(onEnd).toHaveBeenCalledExactlyOnceWith({ transcript: "hello " });
320
+ });
321
+
322
+ it("does not reread historical finalized results during interim updates", () => {
323
+ const listeners = stubSpeechRecognition();
324
+ new WebSpeechDictationAdapter().listen();
325
+ const readFinal = vi.fn(() => ({ transcript: "word " }));
326
+ const finals = Array.from({ length: 1_000 }, () => ({
327
+ get 0() {
328
+ return readFinal();
329
+ },
330
+ isFinal: true,
331
+ }));
332
+ listeners.get("result")!({
333
+ resultIndex: 0,
334
+ results: finals,
335
+ } as unknown as Event);
336
+ expect(readFinal).toHaveBeenCalledTimes(1_000);
337
+
338
+ for (const transcript of ["hel", "hello", "hello world"]) {
339
+ listeners.get("result")!({
340
+ resultIndex: finals.length,
341
+ results: [...finals, { 0: { transcript }, isFinal: false }],
342
+ } as unknown as Event);
343
+ }
344
+
345
+ expect(readFinal).toHaveBeenCalledTimes(1_000);
346
+ });
347
+
112
348
  it("continues notifying dictation listeners when one throws", () => {
113
349
  const listeners = stubSpeechRecognition();
114
350
  const listenerError = new Error("listener failed");
@@ -76,6 +76,7 @@ export class WebSpeechSynthesisAdapter implements SpeechSynthesisAdapter {
76
76
  const res: SpeechSynthesisAdapter.Utterance = {
77
77
  status: { type: "running" },
78
78
  cancel: () => {
79
+ if (res.status.type === "ended") return;
79
80
  window.speechSynthesis.cancel();
80
81
  handleEnd("cancelled");
81
82
  },
@@ -205,6 +206,8 @@ export class WebSpeechDictationAdapter implements DictationAdapter {
205
206
  >();
206
207
 
207
208
  let finalTranscript = "";
209
+ let hasInterimTranscript = false;
210
+ let firstInterimIndex = 0;
208
211
 
209
212
  const session: DictationAdapter.Session = {
210
213
  status: { type: "starting" },
@@ -267,32 +270,36 @@ export class WebSpeechDictationAdapter implements DictationAdapter {
267
270
 
268
271
  recognition.addEventListener("result", (event) => {
269
272
  const speechEvent = event as unknown as SpeechRecognitionEvent;
273
+ let interimTranscript = "";
270
274
 
271
- for (
272
- let i = speechEvent.resultIndex;
273
- i < speechEvent.results.length;
274
- i++
275
- ) {
275
+ for (let i = firstInterimIndex; i < speechEvent.results.length; i++) {
276
276
  const result = speechEvent.results[i];
277
277
  if (!result) continue;
278
278
 
279
279
  const transcript = result[0]?.transcript ?? "";
280
280
 
281
281
  if (result.isFinal) {
282
+ firstInterimIndex = i + 1;
282
283
  finalTranscript += transcript;
284
+ hasInterimTranscript = false;
283
285
  notifyEventListeners(
284
286
  speechCallbacks,
285
287
  () => ({ transcript, isFinal: true }),
286
288
  "Dictation",
287
289
  );
288
290
  } else {
289
- notifyEventListeners(
290
- speechCallbacks,
291
- () => ({ transcript, isFinal: false }),
292
- "Dictation",
293
- );
291
+ interimTranscript += transcript;
294
292
  }
295
293
  }
294
+
295
+ if (interimTranscript || hasInterimTranscript) {
296
+ hasInterimTranscript = interimTranscript.length > 0;
297
+ notifyEventListeners(
298
+ speechCallbacks,
299
+ () => ({ transcript: interimTranscript, isFinal: false }),
300
+ "Dictation",
301
+ );
302
+ }
296
303
  });
297
304
 
298
305
  recognition.addEventListener("speechend", () => {
@@ -7,6 +7,7 @@ import type {
7
7
  ExportedMessageRepositoryItem,
8
8
  } from "../runtime/utils/message-repository";
9
9
  import type { ReadonlyJSONValue } from "assistant-stream/utils";
10
+ import type { ThreadMessage } from "../types";
10
11
 
11
12
  export interface MessageStorageEntry<TPayload> {
12
13
  id: string;
@@ -52,6 +53,8 @@ export type GenericThreadHistoryAdapter<TMessage> = {
52
53
  options?: {
53
54
  durationMs?: number;
54
55
  stepTimestamps?: { start_ms: number; end_ms: number }[];
56
+ /** The thread message the items were persisted from; its status and timing complete a report the stored format cannot carry. */
57
+ message?: ThreadMessage;
55
58
  },
56
59
  ): void;
57
60
  };
package/src/index.ts CHANGED
@@ -40,6 +40,7 @@ export type {
40
40
  MessageStatus,
41
41
  // Thread messages
42
42
  MessageTiming,
43
+ MessageModality,
43
44
  ThreadStep,
44
45
  ThreadSystemMessage,
45
46
  ThreadUserMessage,
@@ -261,6 +262,7 @@ export type {
261
262
  ThreadListItemEventCallback,
262
263
  ThreadListItemEventPayload,
263
264
  ThreadListItemEventType,
265
+ ThreadListItemGenerateTitleOptions,
264
266
  ThreadListItemRuntime,
265
267
  } from "./runtime/api/thread-list-item-runtime";
266
268
 
package/src/internal.ts CHANGED
@@ -67,6 +67,10 @@ export {
67
67
  resolveFilePartSource,
68
68
  } from "./utils/data-url";
69
69
  export { invokeUserCallback } from "./utils/invoke-user-callback";
70
+ export {
71
+ abortableIterable,
72
+ openAbortableIterable,
73
+ } from "./utils/abortable-iterable";
70
74
  export { detectImageMediaType } from "./utils/image-media-type";
71
75
  export {
72
76
  resolveFileMediaType,
@@ -74,9 +74,31 @@ beforeEach(() => {
74
74
  afterEach(() => {
75
75
  vi.useRealTimers();
76
76
  vi.unstubAllGlobals();
77
+ vi.restoreAllMocks();
77
78
  });
78
79
 
79
80
  describe("AssistantFrameHost", () => {
81
+ it("does not install its message listener when initialization fails", () => {
82
+ const addEventListener = vi.fn();
83
+ const removeEventListener = vi.fn();
84
+ vi.stubGlobal("window", {
85
+ addEventListener,
86
+ removeEventListener,
87
+ location: { origin: DEFAULT_ORIGIN },
88
+ });
89
+ const error = new Error("postMessage failed");
90
+ const iframeWindow = {
91
+ postMessage: vi.fn(() => {
92
+ throw error;
93
+ }),
94
+ } as unknown as Window;
95
+
96
+ expect(() => new AssistantFrameHost(iframeWindow)).toThrow(error);
97
+
98
+ expect(addEventListener).not.toHaveBeenCalled();
99
+ expect(removeEventListener).not.toHaveBeenCalled();
100
+ });
101
+
80
102
  it("defaults to the current origin", () => {
81
103
  const { host, postMessage } = createHost();
82
104
 
@@ -133,6 +155,30 @@ describe("AssistantFrameHost", () => {
133
155
  host.dispose();
134
156
  });
135
157
 
158
+ it("cleans up tool calls when posting the request fails", async () => {
159
+ const { execute, host, postMessage } = createHost();
160
+ const error = new Error("postMessage failed");
161
+ const abortController = new AbortController();
162
+ const removeEventListener = vi.spyOn(
163
+ abortController.signal,
164
+ "removeEventListener",
165
+ );
166
+ postMessage.mockImplementation((data) => {
167
+ if (data.message.type === "tool-call") throw error;
168
+ });
169
+
170
+ await expect(
171
+ execute({}, { ...executionContext, abortSignal: abortController.signal }),
172
+ ).rejects.toBe(error);
173
+
174
+ expect(vi.getTimerCount()).toBe(0);
175
+ expect(removeEventListener).toHaveBeenCalledWith(
176
+ "abort",
177
+ expect.any(Function),
178
+ );
179
+ host.dispose();
180
+ });
181
+
136
182
  it("rejects tool results that carry an empty error message", async () => {
137
183
  const { dispatchMessage, execute, getToolCallId, host } = createHost();
138
184
  const result = Promise.resolve(
@@ -188,8 +234,83 @@ describe("AssistantFrameHost", () => {
188
234
  expect(vi.getTimerCount()).toBe(0);
189
235
  });
190
236
 
237
+ it("settles every pending tool call when cancellation posting fails", async () => {
238
+ const { execute, host, postMessage } = createHost();
239
+ const consoleError = vi
240
+ .spyOn(console, "error")
241
+ .mockImplementation(() => undefined);
242
+ const firstAbortController = new AbortController();
243
+ const secondAbortController = new AbortController();
244
+ const firstRemoveEventListener = vi.spyOn(
245
+ firstAbortController.signal,
246
+ "removeEventListener",
247
+ );
248
+ const secondRemoveEventListener = vi.spyOn(
249
+ secondAbortController.signal,
250
+ "removeEventListener",
251
+ );
252
+ const firstRejected = vi.fn();
253
+ const secondRejected = vi.fn();
254
+
255
+ void execute(
256
+ {},
257
+ { ...executionContext, abortSignal: firstAbortController.signal },
258
+ ).catch(firstRejected);
259
+ void execute(
260
+ {},
261
+ { ...executionContext, abortSignal: secondAbortController.signal },
262
+ ).catch(secondRejected);
263
+
264
+ const firstTransportError = new Error("first tool cancellation failed");
265
+ const secondTransportError = new Error("second tool cancellation failed");
266
+ let cancellationCount = 0;
267
+ postMessage.mockImplementation((data) => {
268
+ if (data.message.type !== "tool-cancel") return;
269
+ cancellationCount += 1;
270
+ throw cancellationCount === 1
271
+ ? firstTransportError
272
+ : secondTransportError;
273
+ });
274
+
275
+ expect(() => host.dispose()).toThrow(firstTransportError);
276
+ await Promise.resolve();
277
+
278
+ expect(firstRejected).toHaveBeenCalledWith(
279
+ expect.objectContaining({
280
+ message: "AssistantFrameHost has been disposed",
281
+ }),
282
+ );
283
+ expect(secondRejected).toHaveBeenCalledWith(
284
+ expect.objectContaining({
285
+ message: "AssistantFrameHost has been disposed",
286
+ }),
287
+ );
288
+ expect(
289
+ postMessage.mock.calls.filter(
290
+ ([data]) => data.message.type === "tool-cancel",
291
+ ),
292
+ ).toHaveLength(2);
293
+ expect(firstRemoveEventListener).toHaveBeenCalledWith(
294
+ "abort",
295
+ expect.any(Function),
296
+ );
297
+ expect(secondRemoveEventListener).toHaveBeenCalledWith(
298
+ "abort",
299
+ expect.any(Function),
300
+ );
301
+ expect(vi.getTimerCount()).toBe(0);
302
+ expect(consoleError).toHaveBeenCalledWith(
303
+ "[assistant-ui] AssistantFrameHost tool cancellation could not be sent.",
304
+ secondTransportError,
305
+ );
306
+ expect(() => host.dispose()).not.toThrow();
307
+ });
308
+
191
309
  it("rejects pending tool calls when execution is aborted", async () => {
192
310
  const { execute, getToolCallId, host, postMessage } = createHost();
311
+ const consoleError = vi
312
+ .spyOn(console, "error")
313
+ .mockImplementation(() => undefined);
193
314
  const abortController = new AbortController();
194
315
  const abortError = new Error("Run cancelled");
195
316
  abortError.name = "AbortError";
@@ -205,8 +326,13 @@ describe("AssistantFrameHost", () => {
205
326
  const onRejected = vi.fn();
206
327
  void result.catch(onRejected);
207
328
  const toolCallId = getToolCallId();
329
+ postMessage.mockImplementation((data) => {
330
+ if (data.message.type === "tool-cancel") {
331
+ throw new Error("tool cancellation failed");
332
+ }
333
+ });
208
334
 
209
- abortController.abort(abortError);
335
+ expect(() => abortController.abort(abortError)).not.toThrow();
210
336
  await Promise.resolve();
211
337
 
212
338
  expect(onRejected).toHaveBeenCalledWith(abortError);
@@ -217,17 +343,26 @@ describe("AssistantFrameHost", () => {
217
343
  },
218
344
  DEFAULT_ORIGIN,
219
345
  );
346
+ expect(consoleError).toHaveBeenCalledOnce();
220
347
  expect(vi.getTimerCount()).toBe(0);
221
348
  host.dispose();
222
349
  });
223
350
 
224
351
  it("cancels tool calls when they time out", async () => {
225
352
  const { execute, getToolCallId, host, postMessage } = createHost();
353
+ const consoleError = vi
354
+ .spyOn(console, "error")
355
+ .mockImplementation(() => undefined);
226
356
  const result = Promise.resolve(execute({}, executionContext));
227
357
  const toolCallId = getToolCallId();
228
358
  const rejection = expect(result).rejects.toThrow(
229
359
  'Tool call "search" timed out',
230
360
  );
361
+ postMessage.mockImplementation((data) => {
362
+ if (data.message.type === "tool-cancel") {
363
+ throw new Error("tool cancellation failed");
364
+ }
365
+ });
231
366
 
232
367
  await vi.advanceTimersByTimeAsync(30000);
233
368
 
@@ -239,6 +374,7 @@ describe("AssistantFrameHost", () => {
239
374
  },
240
375
  DEFAULT_ORIGIN,
241
376
  );
377
+ expect(consoleError).toHaveBeenCalledOnce();
242
378
  expect(vi.getTimerCount()).toBe(0);
243
379
  host.dispose();
244
380
  });
@@ -12,6 +12,13 @@ import {
12
12
 
13
13
  const getDefaultTargetOrigin = () => window.location.origin;
14
14
 
15
+ const logCancellationFailure = (error: unknown) => {
16
+ console.error(
17
+ "[assistant-ui] AssistantFrameHost tool cancellation could not be sent.",
18
+ error,
19
+ );
20
+ };
21
+
15
22
  /**
16
23
  * Deserializes tools from JSON Schema format back to Tool objects
17
24
  */
@@ -73,9 +80,10 @@ export class AssistantFrameHost implements ModelContextProvider {
73
80
  this._targetOrigin = targetOrigin;
74
81
 
75
82
  this.handleMessage = this.handleMessage.bind(this);
76
- window.addEventListener("message", this.handleMessage);
77
-
83
+ // A posted message cannot be delivered before this constructor returns, so
84
+ // requesting first means a failed request leaves no listener to clean up.
78
85
  this.requestContext();
86
+ window.addEventListener("message", this.handleMessage);
79
87
  }
80
88
 
81
89
  private handleMessage(event: MessageEvent) {
@@ -195,21 +203,34 @@ export class AssistantFrameHost implements ModelContextProvider {
195
203
  }, timeout);
196
204
  abortSignal?.addEventListener("abort", onAbort, { once: true });
197
205
 
198
- this._iframeWindow.postMessage(
199
- { channel: FRAME_MESSAGE_CHANNEL, message },
200
- this._targetOrigin,
201
- );
206
+ try {
207
+ this._iframeWindow.postMessage(
208
+ { channel: FRAME_MESSAGE_CHANNEL, message },
209
+ this._targetOrigin,
210
+ );
211
+ } catch (error) {
212
+ const pending = this._pendingRequests.get(message.id);
213
+ this._pendingRequests.delete(message.id);
214
+ pending?.reject(error);
215
+ }
202
216
  });
203
217
  }
204
218
 
205
- private cancelToolCall(id: string) {
206
- this._iframeWindow.postMessage(
207
- {
208
- channel: FRAME_MESSAGE_CHANNEL,
209
- message: { type: "tool-cancel", id } satisfies FrameMessage,
210
- },
211
- this._targetOrigin,
212
- );
219
+ private cancelToolCall(
220
+ id: string,
221
+ onError: (error: unknown) => void = logCancellationFailure,
222
+ ) {
223
+ try {
224
+ this._iframeWindow.postMessage(
225
+ {
226
+ channel: FRAME_MESSAGE_CHANNEL,
227
+ message: { type: "tool-cancel", id } satisfies FrameMessage,
228
+ },
229
+ this._targetOrigin,
230
+ );
231
+ } catch (error) {
232
+ onError(error);
233
+ }
213
234
  }
214
235
 
215
236
  private requestContext() {
@@ -242,10 +263,22 @@ export class AssistantFrameHost implements ModelContextProvider {
242
263
  window.removeEventListener("message", this.handleMessage);
243
264
  this._subscribers.clear();
244
265
  const error = new Error("AssistantFrameHost has been disposed");
266
+ let cancellationFailed = false;
267
+ let cancellationError: unknown;
268
+
245
269
  for (const [id, pending] of this._pendingRequests) {
246
- this.cancelToolCall(id);
270
+ this._pendingRequests.delete(id);
271
+ this.cancelToolCall(id, (error) => {
272
+ if (!cancellationFailed) {
273
+ cancellationFailed = true;
274
+ cancellationError = error;
275
+ } else {
276
+ logCancellationFailure(error);
277
+ }
278
+ });
247
279
  pending.reject(error);
248
280
  }
249
- this._pendingRequests.clear();
281
+
282
+ if (cancellationFailed) throw cancellationError;
250
283
  }
251
284
  }