@assistant-ui/core 0.2.21 → 0.2.23

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 (345) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/speech.d.ts.map +1 -1
  3. package/dist/adapters/speech.js +9 -2
  4. package/dist/adapters/speech.js.map +1 -1
  5. package/dist/adapters/thread-history.d.ts +8 -0
  6. package/dist/adapters/thread-history.d.ts.map +1 -1
  7. package/dist/adapters/voice.d.ts.map +1 -1
  8. package/dist/adapters/voice.js +13 -5
  9. package/dist/adapters/voice.js.map +1 -1
  10. package/dist/internal.d.ts +5 -2
  11. package/dist/internal.js +5 -2
  12. package/dist/model-context/frame/host.d.ts +1 -0
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +41 -21
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/frame/provider.d.ts.map +1 -1
  17. package/dist/model-context/frame/provider.js +1 -0
  18. package/dist/model-context/frame/provider.js.map +1 -1
  19. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  20. package/dist/model-context/types.d.ts.map +1 -1
  21. package/dist/model-context/types.js +13 -4
  22. package/dist/model-context/types.js.map +1 -1
  23. package/dist/react/AssistantProvider.js.map +1 -1
  24. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.js +78 -50
  26. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  27. package/dist/react/client/Interactables.js.map +1 -1
  28. package/dist/react/client/Tools.d.ts.map +1 -1
  29. package/dist/react/client/interactable-model-context.d.ts.map +1 -1
  30. package/dist/react/client/interactable-model-context.js +1 -0
  31. package/dist/react/client/interactable-model-context.js.map +1 -1
  32. package/dist/react/interactables-legacy/useInteractableState.js +1 -0
  33. package/dist/react/interactables-legacy/useInteractableState.js.map +1 -1
  34. package/dist/react/model-context/useInteractable.js.map +1 -1
  35. package/dist/react/model-context/useInteractableState.js +1 -0
  36. package/dist/react/model-context/useInteractableState.js.map +1 -1
  37. package/dist/react/model-context/useInteractableVersions.js +1 -0
  38. package/dist/react/model-context/useInteractableVersions.js.map +1 -1
  39. package/dist/react/model-context/useToolArgsStatus.js +1 -0
  40. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  41. package/dist/react/primitive-hooks/useActionBarCopy.js +14 -13
  42. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  43. package/dist/react/primitive-hooks/useActionBarEdit.js +1 -0
  44. package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
  45. package/dist/react/primitive-hooks/useActionBarFeedback.js +1 -0
  46. package/dist/react/primitive-hooks/useActionBarFeedback.js.map +1 -1
  47. package/dist/react/primitive-hooks/useActionBarReload.js +1 -0
  48. package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
  49. package/dist/react/primitive-hooks/useActionBarSpeak.js +1 -0
  50. package/dist/react/primitive-hooks/useActionBarSpeak.js.map +1 -1
  51. package/dist/react/primitive-hooks/useActionBarStopSpeaking.js +1 -0
  52. package/dist/react/primitive-hooks/useActionBarStopSpeaking.js.map +1 -1
  53. package/dist/react/primitive-hooks/useBranchPickerNext.js +1 -0
  54. package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
  55. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +1 -0
  56. package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
  57. package/dist/react/primitive-hooks/useComposerAddAttachment.d.ts.map +1 -1
  58. package/dist/react/primitive-hooks/useComposerAddAttachment.js +1 -0
  59. package/dist/react/primitive-hooks/useComposerAddAttachment.js.map +1 -1
  60. package/dist/react/primitive-hooks/useComposerCancel.js +1 -0
  61. package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
  62. package/dist/react/primitive-hooks/useComposerDictate.js +1 -0
  63. package/dist/react/primitive-hooks/useComposerDictate.js.map +1 -1
  64. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  65. package/dist/react/primitive-hooks/useComposerSend.js +1 -0
  66. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  67. package/dist/react/primitive-hooks/useEditComposerCancel.js +1 -0
  68. package/dist/react/primitive-hooks/useEditComposerCancel.js.map +1 -1
  69. package/dist/react/primitive-hooks/useEditComposerSend.js +1 -0
  70. package/dist/react/primitive-hooks/useEditComposerSend.js.map +1 -1
  71. package/dist/react/primitive-hooks/useMessageBranching.js +1 -0
  72. package/dist/react/primitive-hooks/useMessageBranching.js.map +1 -1
  73. package/dist/react/primitive-hooks/useMessageReload.js +1 -0
  74. package/dist/react/primitive-hooks/useMessageReload.js.map +1 -1
  75. package/dist/react/primitive-hooks/useSuggestionTrigger.js +1 -0
  76. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  77. package/dist/react/primitive-hooks/useThreadListItemArchive.js +1 -0
  78. package/dist/react/primitive-hooks/useThreadListItemArchive.js.map +1 -1
  79. package/dist/react/primitive-hooks/useThreadListItemDelete.js +1 -0
  80. package/dist/react/primitive-hooks/useThreadListItemDelete.js.map +1 -1
  81. package/dist/react/primitive-hooks/useThreadListItemTrigger.js +1 -0
  82. package/dist/react/primitive-hooks/useThreadListItemTrigger.js.map +1 -1
  83. package/dist/react/primitive-hooks/useThreadListItemUnarchive.js +1 -0
  84. package/dist/react/primitive-hooks/useThreadListItemUnarchive.js.map +1 -1
  85. package/dist/react/primitive-hooks/useThreadListLoadMore.js +1 -0
  86. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  87. package/dist/react/primitive-hooks/useThreadListNew.js +1 -0
  88. package/dist/react/primitive-hooks/useThreadListNew.js.map +1 -1
  89. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.d.ts.map +1 -1
  90. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +1 -1
  91. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js.map +1 -1
  92. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  93. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  94. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  95. package/dist/react/primitives/generativeUI/GenerativeUI.d.ts.map +1 -1
  96. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  97. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  98. package/dist/react/primitives/message/MessageParts.d.ts +2 -1
  99. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  100. package/dist/react/primitives/message/MessageParts.js +46 -36
  101. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  102. package/dist/react/primitives/part/PartMessages.js.map +1 -1
  103. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  104. package/dist/react/primitives/thread/ThreadSuggestions.js.map +1 -1
  105. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  106. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.d.ts.map +1 -1
  107. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +18 -30
  108. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js.map +1 -1
  109. package/dist/react/providers/PartByIndexProvider.d.ts.map +1 -1
  110. package/dist/react/providers/PartByIndexProvider.js +18 -30
  111. package/dist/react/providers/PartByIndexProvider.js.map +1 -1
  112. package/dist/react/providers/TextMessagePartProvider.js +1 -0
  113. package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
  114. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  115. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +1 -0
  116. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  117. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +9 -2
  118. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  119. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +55 -12
  120. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  121. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +31 -1
  122. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +12 -2
  124. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  125. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -1
  126. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  127. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -2
  128. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  129. package/dist/react/runtimes/cloud/auiV0.d.ts +12 -1
  130. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  131. package/dist/react/runtimes/cloud/auiV0.js +2 -1
  132. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  133. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +75 -73
  134. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  135. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  136. package/dist/react/runtimes/external-message-converter.js +2 -2
  137. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  138. package/dist/react/runtimes/useExternalStoreSharedOptions.js +1 -0
  139. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
  140. package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
  141. package/dist/react/runtimes/useLocalRuntime.js +9 -1
  142. package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
  143. package/dist/runtime/api/assistant-runtime.d.ts +1 -1
  144. package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
  145. package/dist/runtime/api/assistant-runtime.js +1 -1
  146. package/dist/runtime/api/assistant-runtime.js.map +1 -1
  147. package/dist/runtime/api/attachment-runtime.d.ts +1 -1
  148. package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
  149. package/dist/runtime/api/attachment-runtime.js +1 -1
  150. package/dist/runtime/api/attachment-runtime.js.map +1 -1
  151. package/dist/runtime/api/composer-runtime.d.ts +2 -2
  152. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  153. package/dist/runtime/api/composer-runtime.js +2 -2
  154. package/dist/runtime/api/composer-runtime.js.map +1 -1
  155. package/dist/runtime/api/message-part-runtime.d.ts +4 -4
  156. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  157. package/dist/runtime/api/message-part-runtime.js +3 -3
  158. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  159. package/dist/runtime/api/message-runtime.d.ts +1 -1
  160. package/dist/runtime/api/message-runtime.d.ts.map +1 -1
  161. package/dist/runtime/api/message-runtime.js +6 -6
  162. package/dist/runtime/api/message-runtime.js.map +1 -1
  163. package/dist/runtime/api/thread-list-item-runtime.d.ts +1 -1
  164. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  165. package/dist/runtime/api/thread-list-item-runtime.js +4 -3
  166. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  167. package/dist/runtime/api/thread-list-runtime.d.ts +1 -1
  168. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  169. package/dist/runtime/api/thread-list-runtime.js +1 -1
  170. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  171. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  172. package/dist/runtime/base/base-composer-runtime-core.d.ts +4 -1
  173. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  174. package/dist/runtime/base/base-composer-runtime-core.js +41 -14
  175. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  176. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -1
  177. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  178. package/dist/runtime/base/base-thread-runtime-core.js +3 -2
  179. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  180. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +2 -2
  181. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  182. package/dist/runtime/base/default-edit-composer-runtime-core.js +6 -4
  183. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  184. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  185. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  186. package/dist/runtime/base/default-thread-composer-runtime-core.js +3 -3
  187. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  188. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  189. package/dist/runtime/utils/message-repository.js +4 -2
  190. package/dist/runtime/utils/message-repository.js.map +1 -1
  191. package/dist/runtime/utils/thread-message-like.js +4 -1
  192. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  193. package/dist/runtimes/external-store/external-store-runtime-core.d.ts.map +1 -1
  194. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -1
  195. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  196. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +1 -1
  197. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  198. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  199. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +12 -0
  200. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  201. package/dist/runtimes/internal.d.ts +2 -1
  202. package/dist/runtimes/internal.js +2 -1
  203. package/dist/runtimes/local/local-runtime-core.d.ts.map +1 -1
  204. package/dist/runtimes/local/local-thread-list-runtime-core.d.ts.map +1 -1
  205. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
  206. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  207. package/dist/runtimes/local/local-thread-runtime-core.js +49 -15
  208. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  209. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  210. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +4 -4
  211. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  212. package/dist/runtimes/remote-thread-list/optimistic-state.js +22 -9
  213. package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
  214. package/dist/runtimes/remote-thread-list/types.d.ts +1 -1
  215. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +32 -27
  216. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  217. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +3 -2
  218. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  219. package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
  220. package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
  221. package/dist/store/clients/no-op-composer-client.js +1 -0
  222. package/dist/store/clients/no-op-composer-client.js.map +1 -1
  223. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  224. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  225. package/dist/store/runtime-clients/message-part-runtime-client.d.ts.map +1 -1
  226. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  227. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  228. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  229. package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -0
  230. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  231. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  232. package/dist/store/runtime-clients/thread-runtime-client.js +17 -15
  233. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  234. package/dist/store/scopes/thread.d.ts +1 -0
  235. package/dist/store/scopes/thread.d.ts.map +1 -1
  236. package/dist/subscribable/subscribable.d.ts +4 -4
  237. package/dist/subscribable/subscribable.d.ts.map +1 -1
  238. package/dist/subscribable/subscribable.js +14 -6
  239. package/dist/subscribable/subscribable.js.map +1 -1
  240. package/dist/tests/remote-thread-list-test-helpers.d.ts +2 -1
  241. package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
  242. package/dist/tests/remote-thread-list-test-helpers.js +5 -2
  243. package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
  244. package/dist/types/attachment.d.ts +2 -1
  245. package/dist/types/attachment.d.ts.map +1 -1
  246. package/dist/types/attachment.js +6 -0
  247. package/dist/types/attachment.js.map +1 -0
  248. package/dist/types/message.d.ts +1 -0
  249. package/dist/types/message.d.ts.map +1 -1
  250. package/dist/types/message.js.map +1 -1
  251. package/dist/utils/data-url.d.ts +9 -0
  252. package/dist/utils/data-url.d.ts.map +1 -0
  253. package/dist/utils/data-url.js +14 -0
  254. package/dist/utils/data-url.js.map +1 -0
  255. package/dist/utils/notify-event-listeners.d.ts +6 -0
  256. package/dist/utils/notify-event-listeners.d.ts.map +1 -0
  257. package/dist/utils/notify-event-listeners.js +16 -0
  258. package/dist/utils/notify-event-listeners.js.map +1 -0
  259. package/package.json +10 -9
  260. package/src/adapters/speech.test.ts +88 -0
  261. package/src/adapters/speech.ts +17 -2
  262. package/src/adapters/thread-history.ts +8 -0
  263. package/src/adapters/voice.test.ts +130 -0
  264. package/src/adapters/voice.ts +26 -6
  265. package/src/internal.ts +6 -1
  266. package/src/model-context/frame/host.test.ts +147 -0
  267. package/src/model-context/frame/host.ts +56 -19
  268. package/src/model-context/frame/provider.test.ts +83 -0
  269. package/src/model-context/frame/provider.ts +1 -0
  270. package/src/model-context/types.ts +16 -6
  271. package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +82 -0
  272. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +108 -58
  273. package/src/react/primitive-hooks/useActionBarCopy.test.ts +79 -0
  274. package/src/react/primitive-hooks/useActionBarCopy.ts +4 -3
  275. package/src/react/primitives/chainOfThought/ChainOfThoughtParts.tsx +1 -3
  276. package/src/react/primitives/message/MessageParts.tsx +48 -35
  277. package/src/react/providers/ChainOfThoughtPartByIndexProvider.tsx +19 -2
  278. package/src/react/providers/PartByIndexProvider.tsx +16 -2
  279. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +18 -0
  280. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +1 -0
  281. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +68 -13
  282. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +34 -0
  283. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +69 -0
  284. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +27 -6
  285. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +16 -0
  286. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +7 -2
  287. package/src/react/runtimes/cloud/auiV0.ts +14 -0
  288. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +90 -88
  289. package/src/react/runtimes/external-message-converter.test.ts +346 -0
  290. package/src/react/runtimes/external-message-converter.test.tsx +21 -0
  291. package/src/react/runtimes/external-message-converter.ts +2 -2
  292. package/src/react/runtimes/useLocalRuntime.test.tsx +99 -0
  293. package/src/react/runtimes/useLocalRuntime.ts +13 -1
  294. package/src/runtime/api/assistant-runtime.ts +4 -1
  295. package/src/runtime/api/attachment-runtime.ts +8 -2
  296. package/src/runtime/api/composer-runtime.ts +9 -5
  297. package/src/runtime/api/message-part-runtime.ts +10 -3
  298. package/src/runtime/api/message-runtime.test.ts +81 -0
  299. package/src/runtime/api/message-runtime.ts +11 -8
  300. package/src/runtime/api/thread-list-item-runtime.ts +13 -3
  301. package/src/runtime/api/thread-list-runtime.ts +10 -2
  302. package/src/runtime/base/base-composer-runtime-core.ts +77 -31
  303. package/src/runtime/base/base-thread-runtime-core.ts +7 -2
  304. package/src/runtime/base/default-edit-composer-runtime-core.ts +18 -4
  305. package/src/runtime/base/default-thread-composer-runtime-core.ts +13 -3
  306. package/src/runtime/utils/message-repository.ts +19 -13
  307. package/src/runtime/utils/thread-message-like.ts +1 -0
  308. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
  309. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +15 -0
  310. package/src/runtimes/internal.ts +3 -0
  311. package/src/runtimes/local/local-thread-runtime-core.test.ts +746 -1
  312. package/src/runtimes/local/local-thread-runtime-core.ts +89 -23
  313. package/src/runtimes/local/should-continue.test.ts +122 -0
  314. package/src/runtimes/remote-thread-list/optimistic-state.test.ts +63 -0
  315. package/src/runtimes/remote-thread-list/optimistic-state.ts +49 -17
  316. package/src/runtimes/remote-thread-list/types.ts +1 -1
  317. package/src/runtimes/tool-invocations/EDGE_CASES.md +13 -19
  318. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +128 -8
  319. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +42 -34
  320. package/src/store/clients/model-context-client.test.ts +21 -1
  321. package/src/store/runtime-clients/thread-runtime-client.ts +2 -1
  322. package/src/store/scopes/thread.ts +1 -0
  323. package/src/subscribable/subscribable.test.ts +58 -0
  324. package/src/subscribable/subscribable.ts +29 -11
  325. package/src/tests/MessageRepository.test.ts +12 -0
  326. package/src/tests/OptimisticState-list-race.test.ts +33 -0
  327. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-order.test.ts +223 -0
  328. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-position.test.ts +121 -0
  329. package/src/tests/auiV0Encode.test.ts +74 -0
  330. package/src/tests/base-composer-runtime-core-addAttachment.test.ts +44 -0
  331. package/src/tests/base-composer-runtime-core-send.test.ts +433 -5
  332. package/src/tests/base-composer-runtime-core.test.ts +422 -0
  333. package/src/tests/default-edit-composer-runtime-core.test.ts +43 -5
  334. package/src/tests/event-subscription-listener-errors.test.ts +205 -0
  335. package/src/tests/external-store-thread-list-runtime-core.test.ts +6 -6
  336. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +575 -0
  337. package/src/tests/external-store-thread-runtime-core.test.ts +55 -1
  338. package/src/tests/remote-thread-list-test-helpers.ts +14 -7
  339. package/src/tests/thread-message-like.test.ts +57 -0
  340. package/src/types/attachment.ts +5 -0
  341. package/src/types/message.ts +1 -0
  342. package/src/utils/data-url.test.ts +50 -0
  343. package/src/utils/data-url.ts +9 -0
  344. package/src/utils/notify-event-listeners.ts +30 -0
  345. package/src/tests/OptimisticState-delete-crash.test.ts +0 -208
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assistant-ui/core",
3
- "version": "0.2.21",
3
+ "version": "0.2.23",
4
4
  "description": "Framework-agnostic core runtime for assistant-ui",
5
5
  "keywords": [
6
6
  "assistant",
@@ -59,7 +59,7 @@
59
59
  ],
60
60
  "sideEffects": false,
61
61
  "dependencies": {
62
- "assistant-stream": "^0.3.26",
62
+ "assistant-stream": "^0.3.28",
63
63
  "nanoid": "^6.0.0"
64
64
  },
65
65
  "optionalDevDependencies": {
@@ -91,15 +91,15 @@
91
91
  "@testing-library/react": "^16.3.2",
92
92
  "@types/react": "^19.2.17",
93
93
  "jsdom": "^29.1.1",
94
- "react": "^19.2.7",
95
- "react-dom": "^19.2.7",
94
+ "react": "^19.2.8",
95
+ "react-dom": "^19.2.8",
96
96
  "vitest": "^4.1.10",
97
97
  "zustand": "^5.0.14",
98
- "@assistant-ui/store": "0.2.20",
99
- "@assistant-ui/tap": "0.9.4",
100
- "@assistant-ui/vite": "0.0.9",
101
- "@assistant-ui/x-buildutils": "0.0.18",
102
- "assistant-cloud": "0.1.35"
98
+ "@assistant-ui/store": "0.2.22",
99
+ "@assistant-ui/tap": "0.9.6",
100
+ "@assistant-ui/vite": "0.0.11",
101
+ "@assistant-ui/x-buildutils": "0.0.20",
102
+ "assistant-cloud": "0.1.37"
103
103
  },
104
104
  "publishConfig": {
105
105
  "access": "public",
@@ -117,6 +117,7 @@
117
117
  "scripts": {
118
118
  "build": "aui-build",
119
119
  "test": "vitest run",
120
+ "test:react-compiler": "vitest run --config vitest.react-compiler.config.ts",
120
121
  "test:watch": "vitest"
121
122
  }
122
123
  }
@@ -0,0 +1,88 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { WebSpeechSynthesisAdapter } from "./speech";
3
+
4
+ afterEach(() => {
5
+ vi.restoreAllMocks();
6
+ vi.unstubAllGlobals();
7
+ });
8
+
9
+ describe("WebSpeechSynthesisAdapter", () => {
10
+ it("isolates a late subscriber that throws after the utterance ended", async () => {
11
+ const listeners = new Map<string, EventListener>();
12
+ class MockSpeechSynthesisUtterance {
13
+ addEventListener(type: string, listener: EventListener) {
14
+ listeners.set(type, listener);
15
+ }
16
+ }
17
+ vi.stubGlobal("SpeechSynthesisUtterance", MockSpeechSynthesisUtterance);
18
+ vi.stubGlobal("window", {
19
+ speechSynthesis: {
20
+ speak: vi.fn(),
21
+ cancel: vi.fn(),
22
+ },
23
+ });
24
+ const listenerError = new Error("late listener failed");
25
+ const consoleError = vi
26
+ .spyOn(console, "error")
27
+ .mockImplementation(() => {});
28
+
29
+ const result = new WebSpeechSynthesisAdapter().speak("Hello");
30
+ listeners.get("end")!(new Event("end"));
31
+
32
+ const lateListener = vi.fn();
33
+ result.subscribe(() => {
34
+ throw listenerError;
35
+ });
36
+ result.subscribe(lateListener);
37
+ await Promise.resolve();
38
+
39
+ expect(consoleError).toHaveBeenCalledWith(
40
+ "[assistant-ui] Speech synthesis listener threw an error",
41
+ listenerError,
42
+ );
43
+ expect(lateListener).toHaveBeenCalledOnce();
44
+ });
45
+
46
+ it("continues notifying listeners when one throws", () => {
47
+ const listeners = new Map<string, EventListener>();
48
+ class MockSpeechSynthesisUtterance {
49
+ addEventListener(type: string, listener: EventListener) {
50
+ listeners.set(type, listener);
51
+ }
52
+ }
53
+ const speak = vi.fn();
54
+ vi.stubGlobal("SpeechSynthesisUtterance", MockSpeechSynthesisUtterance);
55
+ vi.stubGlobal("window", {
56
+ speechSynthesis: {
57
+ speak,
58
+ cancel: vi.fn(),
59
+ },
60
+ });
61
+ const listenerError = new Error("listener failed");
62
+ const consoleError = vi
63
+ .spyOn(console, "error")
64
+ .mockImplementation(() => {});
65
+ const result = new WebSpeechSynthesisAdapter().speak("Hello");
66
+ const laterListener = vi.fn();
67
+
68
+ result.subscribe(() => {
69
+ throw listenerError;
70
+ });
71
+ result.subscribe(laterListener);
72
+ listeners.get("end")?.({} as Event);
73
+
74
+ expect(speak).toHaveBeenCalledWith(
75
+ expect.any(MockSpeechSynthesisUtterance),
76
+ );
77
+ expect(laterListener).toHaveBeenCalledOnce();
78
+ expect(result.status).toEqual({
79
+ type: "ended",
80
+ reason: "finished",
81
+ error: undefined,
82
+ });
83
+ expect(consoleError).toHaveBeenCalledWith(
84
+ "[assistant-ui] Speech synthesis listener threw an error",
85
+ listenerError,
86
+ );
87
+ });
88
+ });
@@ -22,6 +22,21 @@ export type SpeechSynthesisAdapter = {
22
22
  speak: (text: string) => SpeechSynthesisAdapter.Utterance;
23
23
  };
24
24
 
25
+ const notifySpeechSynthesisListeners = (
26
+ listeners: Iterable<() => void>,
27
+ ): void => {
28
+ for (const listener of listeners) {
29
+ try {
30
+ listener();
31
+ } catch (error) {
32
+ console.error(
33
+ "[assistant-ui] Speech synthesis listener threw an error",
34
+ error,
35
+ );
36
+ }
37
+ }
38
+ };
39
+
25
40
  export namespace DictationAdapter {
26
41
  export type Status =
27
42
  | {
@@ -64,7 +79,7 @@ export class WebSpeechSynthesisAdapter implements SpeechSynthesisAdapter {
64
79
  if (res.status.type === "ended") return;
65
80
 
66
81
  res.status = { type: "ended", reason, error };
67
- subscribers.forEach((handler) => handler());
82
+ notifySpeechSynthesisListeners(subscribers);
68
83
  };
69
84
 
70
85
  utterance.addEventListener("end", () => handleEnd("finished"));
@@ -82,7 +97,7 @@ export class WebSpeechSynthesisAdapter implements SpeechSynthesisAdapter {
82
97
  if (res.status.type === "ended") {
83
98
  let cancelled = false;
84
99
  queueMicrotask(() => {
85
- if (!cancelled) callback();
100
+ if (!cancelled) notifySpeechSynthesisListeners([callback]);
86
101
  });
87
102
  return () => {
88
103
  cancelled = true;
@@ -65,6 +65,14 @@ export type ThreadHistoryAdapter = {
65
65
  options: ChatModelRunOptions,
66
66
  ): AsyncGenerator<ChatModelRunResult, void, unknown>;
67
67
  append(item: ExportedMessageRepositoryItem): Promise<void>;
68
+ /**
69
+ * Rewrites a previously appended message in place, keyed by its message id.
70
+ * Adapters that implement this let a runtime persist a run paused for tool
71
+ * approval and finalize the same message once the run resumes. An update may
72
+ * arrive for an id whose earlier write failed; treat it as an upsert keyed
73
+ * on the message id rather than assuming the entry exists.
74
+ */
75
+ update?(item: ExportedMessageRepositoryItem): Promise<void>;
68
76
  delete?(items: ExportedMessageRepositoryItem[]): Promise<void>;
69
77
  /** Required when used with `useAISDKRuntime` / `useChatRuntime`. */
70
78
  withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(
@@ -0,0 +1,130 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import {
3
+ createVoiceSession,
4
+ type VoiceSessionControls,
5
+ type VoiceSessionHelpers,
6
+ } from "./voice";
7
+
8
+ const createTestSession = () => {
9
+ let helpers: VoiceSessionHelpers | undefined;
10
+ const session = createVoiceSession({}, async (sessionHelpers) => {
11
+ helpers = sessionHelpers;
12
+ return {
13
+ disconnect: vi.fn(),
14
+ mute: vi.fn(),
15
+ unmute: vi.fn(),
16
+ };
17
+ });
18
+
19
+ if (!helpers) throw new Error("Voice session setup did not start");
20
+ return { helpers, session };
21
+ };
22
+
23
+ const createPendingTestSession = () => {
24
+ let resolveControls!: (controls: VoiceSessionControls) => void;
25
+ const controlsPromise = new Promise<VoiceSessionControls>((resolve) => {
26
+ resolveControls = resolve;
27
+ });
28
+ const controls = {
29
+ disconnect: vi.fn(),
30
+ mute: vi.fn(),
31
+ unmute: vi.fn(),
32
+ };
33
+ const session = createVoiceSession({}, () => controlsPromise);
34
+
35
+ return {
36
+ controls,
37
+ controlsPromise,
38
+ resolveControls: () => resolveControls(controls),
39
+ session,
40
+ };
41
+ };
42
+
43
+ afterEach(() => {
44
+ vi.restoreAllMocks();
45
+ });
46
+
47
+ describe("createVoiceSession", () => {
48
+ it("applies mute requested while setup is pending", async () => {
49
+ const { controls, controlsPromise, resolveControls, session } =
50
+ createPendingTestSession();
51
+
52
+ session.mute();
53
+
54
+ expect(session.isMuted).toBe(true);
55
+ expect(controls.mute).not.toHaveBeenCalled();
56
+
57
+ resolveControls();
58
+ await controlsPromise;
59
+
60
+ expect(controls.mute).toHaveBeenCalledOnce();
61
+ });
62
+
63
+ it("does not apply stale mute after unmuting during setup", async () => {
64
+ const { controls, controlsPromise, resolveControls, session } =
65
+ createPendingTestSession();
66
+
67
+ session.mute();
68
+ session.unmute();
69
+ resolveControls();
70
+ await controlsPromise;
71
+
72
+ expect(session.isMuted).toBe(false);
73
+ expect(controls.mute).not.toHaveBeenCalled();
74
+ });
75
+
76
+ it("continues notifying listeners when one throws", () => {
77
+ const listenerError = new Error("listener failed");
78
+ const consoleError = vi
79
+ .spyOn(console, "error")
80
+ .mockImplementation(() => {});
81
+ const { helpers, session } = createTestSession();
82
+
83
+ const transcriptListener = vi.fn();
84
+ session.onTranscript(() => {
85
+ throw listenerError;
86
+ });
87
+ session.onTranscript(transcriptListener);
88
+
89
+ const modeListener = vi.fn();
90
+ session.onModeChange(() => {
91
+ throw listenerError;
92
+ });
93
+ session.onModeChange(modeListener);
94
+
95
+ const volumeListener = vi.fn();
96
+ session.onVolumeChange(() => {
97
+ throw listenerError;
98
+ });
99
+ session.onVolumeChange(volumeListener);
100
+
101
+ const statusListener = vi.fn();
102
+ session.onStatusChange(() => {
103
+ throw listenerError;
104
+ });
105
+ session.onStatusChange(statusListener);
106
+
107
+ helpers.emitTranscript({ role: "assistant", text: "hello" });
108
+ helpers.emitMode("speaking");
109
+ helpers.emitVolume(0.5);
110
+ helpers.end("finished");
111
+
112
+ expect(transcriptListener).toHaveBeenCalledWith({
113
+ role: "assistant",
114
+ text: "hello",
115
+ });
116
+ expect(modeListener).toHaveBeenCalledWith("speaking");
117
+ expect(volumeListener).toHaveBeenCalledWith(0.5);
118
+ expect(statusListener).toHaveBeenCalledWith({
119
+ type: "ended",
120
+ reason: "finished",
121
+ error: undefined,
122
+ });
123
+ expect(helpers.isDisposed()).toBe(true);
124
+ expect(consoleError).toHaveBeenCalledTimes(4);
125
+ expect(consoleError).toHaveBeenCalledWith(
126
+ "[assistant-ui] Voice session listener threw an error",
127
+ listenerError,
128
+ );
129
+ });
130
+ });
@@ -57,6 +57,22 @@ export type VoiceSessionHelpers = {
57
57
  isDisposed: () => boolean;
58
58
  };
59
59
 
60
+ const notifyListeners = <T>(
61
+ listeners: ReadonlySet<(value: T) => void>,
62
+ value: T,
63
+ ) => {
64
+ for (const listener of listeners) {
65
+ try {
66
+ listener(value);
67
+ } catch (error) {
68
+ console.error(
69
+ "[assistant-ui] Voice session listener threw an error",
70
+ error,
71
+ );
72
+ }
73
+ }
74
+ };
75
+
60
76
  export function createVoiceSession(
61
77
  options: { abortSignal?: AbortSignal },
62
78
  setup: (helpers: VoiceSessionHelpers) => Promise<VoiceSessionControls>,
@@ -85,25 +101,25 @@ export function createVoiceSession(
85
101
  setStatus: (status) => {
86
102
  if (disposed) return;
87
103
  currentStatus = status;
88
- for (const cb of statusCbs) cb(status);
104
+ notifyListeners(statusCbs, status);
89
105
  },
90
106
  end: (reason, error?) => {
91
107
  if (disposed) return;
92
108
  currentStatus = { type: "ended", reason, error };
93
- for (const cb of statusCbs) cb(currentStatus);
109
+ notifyListeners(statusCbs, currentStatus);
94
110
  cleanup();
95
111
  },
96
112
  emitTranscript: (item) => {
97
113
  if (disposed) return;
98
- for (const cb of transcriptCbs) cb(item);
114
+ notifyListeners(transcriptCbs, item);
99
115
  },
100
116
  emitMode: (mode) => {
101
117
  if (disposed) return;
102
- for (const cb of modeCbs) cb(mode);
118
+ notifyListeners(modeCbs, mode);
103
119
  },
104
120
  emitVolume: (volume) => {
105
121
  if (disposed) return;
106
- for (const cb of volumeCbs) cb(volume);
122
+ notifyListeners(volumeCbs, volume);
107
123
  },
108
124
  isDisposed: () => disposed,
109
125
  };
@@ -155,7 +171,11 @@ export function createVoiceSession(
155
171
  try {
156
172
  if (disposed) return;
157
173
  controls = await setup(helpers);
158
- if (disposed) controls.disconnect();
174
+ if (disposed) {
175
+ controls.disconnect();
176
+ } else if (isMuted) {
177
+ controls.mute();
178
+ }
159
179
  } catch (error) {
160
180
  helpers.end("error", error);
161
181
  }
package/src/internal.ts CHANGED
@@ -40,7 +40,8 @@ export { CompositeContextProvider } from "./utils/composite-context-provider";
40
40
 
41
41
  // Shared attachment data-URL encoder, reused by framework adapters so the
42
42
  // FileReader fallback lives in one place.
43
- export { getFileDataURL } from "./adapters/attachment";
43
+ export { getFileDataURL, fileMatchesAccept } from "./adapters/attachment";
44
+ export { isCreateAttachment } from "./types/attachment";
44
45
 
45
46
  // Streaming-stable tool-args stringifier, reused by framework adapters so the
46
47
  // key-order stabilization lives in one place.
@@ -53,6 +54,10 @@ export {
53
54
  // validation lives in one place.
54
55
  export { isJSONValue, isRecord } from "./utils/json/is-json";
55
56
 
57
+ // Data-URL decoder and http(s) matcher, reused by framework adapters so the
58
+ // outbound part conversion lives in one place.
59
+ export { httpUrlPattern, parseDataUrl } from "./utils/data-url";
60
+
56
61
  // Runtime extras helper for external-store adapters. Internal because the
57
62
  // tap-native runtime path replaces the `thread.extras` side-channel it wraps.
58
63
  export {
@@ -0,0 +1,147 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { AssistantFrameHost } from "./host";
3
+ import { type FrameMessage, FRAME_MESSAGE_CHANNEL } from "./types";
4
+
5
+ const executionContext = {
6
+ toolCallId: "tool-call",
7
+ abortSignal: new AbortController().signal,
8
+ human: async () => undefined,
9
+ };
10
+
11
+ const createHost = () => {
12
+ let handleMessage: ((event: MessageEvent) => void) | undefined;
13
+ const addEventListener = vi.fn(
14
+ (_type: string, listener: EventListenerOrEventListenerObject) => {
15
+ handleMessage = listener as (event: MessageEvent) => void;
16
+ },
17
+ );
18
+ const removeEventListener = vi.fn();
19
+ vi.stubGlobal("window", { addEventListener, removeEventListener });
20
+
21
+ const postMessage = vi.fn();
22
+ const iframeWindow = { postMessage } as unknown as Window;
23
+ const host = new AssistantFrameHost(iframeWindow);
24
+
25
+ const dispatchMessage = (message: FrameMessage) =>
26
+ handleMessage?.({
27
+ source: iframeWindow,
28
+ data: {
29
+ channel: FRAME_MESSAGE_CHANNEL,
30
+ message,
31
+ },
32
+ } as unknown as MessageEvent);
33
+
34
+ dispatchMessage({
35
+ type: "model-context-update",
36
+ context: {
37
+ tools: {
38
+ search: {
39
+ type: "frontend",
40
+ parameters: { type: "object", properties: {} },
41
+ },
42
+ },
43
+ },
44
+ });
45
+
46
+ const execute = host.getModelContext().tools?.search?.execute;
47
+ if (!execute) throw new Error("Expected the search tool to be available");
48
+
49
+ return { dispatchMessage, execute, host, postMessage };
50
+ };
51
+
52
+ beforeEach(() => {
53
+ vi.useFakeTimers();
54
+ });
55
+
56
+ afterEach(() => {
57
+ vi.useRealTimers();
58
+ vi.unstubAllGlobals();
59
+ });
60
+
61
+ describe("AssistantFrameHost", () => {
62
+ it("resolves tool calls from frame results", async () => {
63
+ const { dispatchMessage, execute, host } = createHost();
64
+ const result = Promise.resolve(
65
+ execute({ query: "weather" }, executionContext),
66
+ );
67
+
68
+ dispatchMessage({
69
+ type: "tool-result",
70
+ id: "tool-0",
71
+ result: "sunny",
72
+ });
73
+
74
+ await expect(result).resolves.toBe("sunny");
75
+ expect(vi.getTimerCount()).toBe(0);
76
+ host.dispose();
77
+ });
78
+
79
+ it("rejects pending tool calls when disposed", async () => {
80
+ const { execute, host } = createHost();
81
+ const result = Promise.resolve(execute({}, executionContext));
82
+
83
+ host.dispose();
84
+
85
+ await expect(result).rejects.toThrow(
86
+ "AssistantFrameHost has been disposed",
87
+ );
88
+ expect(vi.getTimerCount()).toBe(0);
89
+ });
90
+
91
+ it("rejects pending tool calls when execution is aborted", async () => {
92
+ const { execute, host } = createHost();
93
+ const abortController = new AbortController();
94
+ const abortError = new Error("Run cancelled");
95
+ abortError.name = "AbortError";
96
+ const result = Promise.resolve(
97
+ execute(
98
+ {},
99
+ {
100
+ ...executionContext,
101
+ abortSignal: abortController.signal,
102
+ },
103
+ ),
104
+ );
105
+ const onRejected = vi.fn();
106
+ void result.catch(onRejected);
107
+
108
+ abortController.abort(abortError);
109
+ await Promise.resolve();
110
+
111
+ expect(onRejected).toHaveBeenCalledWith(abortError);
112
+ expect(vi.getTimerCount()).toBe(0);
113
+ host.dispose();
114
+ });
115
+
116
+ it("does not post tool calls when execution is already aborted", async () => {
117
+ const { execute, host, postMessage } = createHost();
118
+ const abortController = new AbortController();
119
+ const abortError = new Error("Run cancelled");
120
+ abortError.name = "AbortError";
121
+ abortController.abort(abortError);
122
+
123
+ await expect(
124
+ execute(
125
+ {},
126
+ {
127
+ ...executionContext,
128
+ abortSignal: abortController.signal,
129
+ },
130
+ ),
131
+ ).rejects.toBe(abortError);
132
+
133
+ expect(postMessage).toHaveBeenCalledOnce();
134
+ expect(vi.getTimerCount()).toBe(0);
135
+ host.dispose();
136
+ });
137
+
138
+ it("rejects tool calls made after disposal without posting a request", async () => {
139
+ const { execute, host, postMessage } = createHost();
140
+ host.dispose();
141
+
142
+ await expect(execute({}, executionContext)).rejects.toThrow(
143
+ "AssistantFrameHost has been disposed",
144
+ );
145
+ expect(postMessage).toHaveBeenCalledOnce();
146
+ });
147
+ });
@@ -40,6 +40,13 @@ const deserializeModelContext = (
40
40
  }),
41
41
  });
42
42
 
43
+ const getAbortReason = (signal: AbortSignal): unknown => {
44
+ if (signal.reason !== undefined) return signal.reason;
45
+ const error = new Error("Tool call was aborted");
46
+ error.name = "AbortError";
47
+ return error;
48
+ };
49
+
43
50
  export class AssistantFrameHost implements ModelContextProvider {
44
51
  private _context: ModelContext = {};
45
52
  private _subscribers = new Set<() => void>();
@@ -53,6 +60,7 @@ export class AssistantFrameHost implements ModelContextProvider {
53
60
  private _requestCounter = 0;
54
61
  private _iframeWindow: Window;
55
62
  private _targetOrigin: string;
63
+ private _disposed = false;
56
64
 
57
65
  constructor(iframeWindow: Window, targetOrigin: string = "*") {
58
66
  this._iframeWindow = iframeWindow;
@@ -104,7 +112,8 @@ export class AssistantFrameHost implements ModelContextProvider {
104
112
  name,
105
113
  {
106
114
  ...tool,
107
- execute: (args: any) => this.callTool(name, args),
115
+ execute: (args: any, context: { abortSignal: AbortSignal }) =>
116
+ this.callTool(name, args, context.abortSignal),
108
117
  } as Tool<any, any>,
109
118
  ]),
110
119
  ),
@@ -112,7 +121,11 @@ export class AssistantFrameHost implements ModelContextProvider {
112
121
  this.notifySubscribers();
113
122
  }
114
123
 
115
- private callTool(toolName: string, args: any): Promise<any> {
124
+ private callTool(
125
+ toolName: string,
126
+ args: any,
127
+ abortSignal: AbortSignal,
128
+ ): Promise<any> {
116
129
  return this.sendRequest(
117
130
  {
118
131
  type: "tool-call",
@@ -122,6 +135,7 @@ export class AssistantFrameHost implements ModelContextProvider {
122
135
  },
123
136
  30000,
124
137
  `Tool call "${toolName}" timed out`,
138
+ abortSignal,
125
139
  );
126
140
  }
127
141
 
@@ -129,36 +143,54 @@ export class AssistantFrameHost implements ModelContextProvider {
129
143
  message: T,
130
144
  timeout = 30000,
131
145
  timeoutMessage = "Request timed out",
146
+ abortSignal?: AbortSignal,
132
147
  ): Promise<any> {
133
- return new Promise((resolve, reject) => {
134
- this._pendingRequests.set(message.id, { resolve, reject });
135
-
136
- this._iframeWindow.postMessage(
137
- { channel: FRAME_MESSAGE_CHANNEL, message },
138
- this._targetOrigin,
139
- );
148
+ if (this._disposed) {
149
+ return Promise.reject(new Error("AssistantFrameHost has been disposed"));
150
+ }
151
+ if (abortSignal?.aborted) {
152
+ return Promise.reject(getAbortReason(abortSignal));
153
+ }
140
154
 
141
- const timeoutId = setTimeout(() => {
155
+ return new Promise((resolve, reject) => {
156
+ let timeoutId: ReturnType<typeof setTimeout> | undefined;
157
+ const onAbort = () => {
158
+ if (!abortSignal) return;
142
159
  const pending = this._pendingRequests.get(message.id);
143
160
  if (pending) {
144
- pending.reject(new Error(timeoutMessage));
161
+ pending.reject(getAbortReason(abortSignal));
145
162
  this._pendingRequests.delete(message.id);
146
163
  }
147
- }, timeout);
148
-
149
- const originalResolve = this._pendingRequests.get(message.id)!.resolve;
150
- const originalReject = this._pendingRequests.get(message.id)!.reject;
164
+ };
165
+ const cleanup = () => {
166
+ if (timeoutId !== undefined) clearTimeout(timeoutId);
167
+ abortSignal?.removeEventListener("abort", onAbort);
168
+ };
151
169
 
152
170
  this._pendingRequests.set(message.id, {
153
171
  resolve: (value: any) => {
154
- clearTimeout(timeoutId);
155
- originalResolve(value);
172
+ cleanup();
173
+ resolve(value);
156
174
  },
157
175
  reject: (error: any) => {
158
- clearTimeout(timeoutId);
159
- originalReject(error);
176
+ cleanup();
177
+ reject(error);
160
178
  },
161
179
  });
180
+
181
+ timeoutId = setTimeout(() => {
182
+ const pending = this._pendingRequests.get(message.id);
183
+ if (pending) {
184
+ pending.reject(new Error(timeoutMessage));
185
+ this._pendingRequests.delete(message.id);
186
+ }
187
+ }, timeout);
188
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
189
+
190
+ this._iframeWindow.postMessage(
191
+ { channel: FRAME_MESSAGE_CHANNEL, message },
192
+ this._targetOrigin,
193
+ );
162
194
  });
163
195
  }
164
196
 
@@ -188,8 +220,13 @@ export class AssistantFrameHost implements ModelContextProvider {
188
220
  }
189
221
 
190
222
  dispose() {
223
+ this._disposed = true;
191
224
  window.removeEventListener("message", this.handleMessage);
192
225
  this._subscribers.clear();
226
+ const error = new Error("AssistantFrameHost has been disposed");
227
+ for (const pending of this._pendingRequests.values()) {
228
+ pending.reject(error);
229
+ }
193
230
  this._pendingRequests.clear();
194
231
  }
195
232
  }