@assistant-ui/core 0.3.15 → 0.3.17

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 (455) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +2 -2
  3. package/dist/index.js.map +1 -1
  4. package/dist/internal.d.ts +8 -2
  5. package/dist/internal.js +9 -3
  6. package/dist/model-context/frame/host.d.ts.map +1 -1
  7. package/dist/model-context/frame/host.js +3 -2
  8. package/dist/model-context/frame/host.js.map +1 -1
  9. package/dist/model-context/frame/provider.d.ts +5 -0
  10. package/dist/model-context/frame/provider.d.ts.map +1 -1
  11. package/dist/model-context/frame/provider.js +141 -28
  12. package/dist/model-context/frame/provider.js.map +1 -1
  13. package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
  14. package/dist/model-context/interactable-composer-metadata.js +1 -1
  15. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  16. package/dist/model-context/registry.d.ts +2 -2
  17. package/dist/model-context/registry.d.ts.map +1 -1
  18. package/dist/model-context/registry.js +9 -28
  19. package/dist/model-context/registry.js.map +1 -1
  20. package/dist/model-context/tool-call-text.d.ts +26 -0
  21. package/dist/model-context/tool-call-text.d.ts.map +1 -0
  22. package/dist/model-context/tool-call-text.js +18 -0
  23. package/dist/model-context/tool-call-text.js.map +1 -0
  24. package/dist/react/RuntimeAdapter.js +1 -1
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  26. package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -1
  27. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  28. package/dist/react/client/AdaptedRemoteThread.js +1 -1
  29. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  30. package/dist/react/client/InMemoryThreadList.js +25 -21
  31. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  32. package/dist/react/client/Interactables.d.ts.map +1 -1
  33. package/dist/react/client/Interactables.js +60 -178
  34. package/dist/react/client/Interactables.js.map +1 -1
  35. package/dist/react/client/RemoteThreadList.d.ts +15 -0
  36. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  37. package/dist/react/client/RemoteThreadList.js +263 -194
  38. package/dist/react/client/RemoteThreadList.js.map +1 -1
  39. package/dist/react/client/Tools.d.ts.map +1 -1
  40. package/dist/react/client/Tools.js +6 -2
  41. package/dist/react/client/Tools.js.map +1 -1
  42. package/dist/react/index.d.ts +4 -2
  43. package/dist/react/index.js +2 -1
  44. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  45. package/dist/react/interactables-legacy/Interactables.js +60 -178
  46. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  47. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
  48. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
  49. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
  50. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
  51. package/dist/react/model-context/toolbox.d.ts +2 -14
  52. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  53. package/dist/react/model-context/toolbox.js +1 -13
  54. package/dist/react/model-context/toolbox.js.map +1 -1
  55. package/dist/react/model-context/useAssistantContext.js +37 -7
  56. package/dist/react/model-context/useAssistantContext.js.map +1 -1
  57. package/dist/react/model-context/useAuiToolOverrides.js +31 -5
  58. package/dist/react/model-context/useAuiToolOverrides.js.map +1 -1
  59. package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
  60. package/dist/react/model-context/useInlineRender.js +10 -36
  61. package/dist/react/model-context/useInlineRender.js.map +1 -1
  62. package/dist/react/primitive-hooks/useMessageError.d.ts.map +1 -1
  63. package/dist/react/primitive-hooks/useMessageError.js +2 -8
  64. package/dist/react/primitive-hooks/useMessageError.js.map +1 -1
  65. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
  66. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -4
  67. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  68. package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  69. package/dist/react/primitives/composer/ComposerAttachments.js +4 -3
  70. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  71. package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
  72. package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
  73. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  74. package/dist/react/primitives/composer/ComposerQueue.js +4 -3
  75. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  76. package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
  77. package/dist/react/primitives/message/MessageAttachments.js +7 -6
  78. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  79. package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
  80. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  81. package/dist/react/primitives/message/MessageParts.js +3 -3
  82. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  83. package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  84. package/dist/react/primitives/threadList/ThreadListItems.js +5 -4
  85. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  86. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +7 -5
  87. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  88. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +44 -22
  89. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +7 -6
  91. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  92. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +102 -117
  93. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  94. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -6
  95. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  96. package/dist/react/runtimes/RemoteThreadResource.js +3 -3
  97. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  98. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
  99. package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
  100. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
  101. package/dist/react/runtimes/assistant-transport/useLatestRef.js +2 -2
  102. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -1
  103. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
  104. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  105. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +43 -85
  106. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  107. package/dist/react/runtimes/cloud/auiV0.d.ts +5 -1
  108. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  109. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  110. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +29 -0
  111. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -0
  112. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +159 -0
  113. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -0
  114. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
  115. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
  116. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
  117. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
  118. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +1 -9
  119. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  120. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +16 -153
  121. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  122. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
  123. package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
  124. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  125. package/dist/react/runtimes/external-message-converter.d.ts +6 -25
  126. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  127. package/dist/react/runtimes/external-message-converter.js +33 -233
  128. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  129. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  130. package/dist/react/runtimes/useRemoteThreadListRuntime.js +14 -6
  131. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  132. package/dist/react/types/MessagePartComponentTypes.d.ts +7 -3
  133. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  134. package/dist/react/types/scopes/tools.d.ts +2 -0
  135. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  136. package/dist/runtime/api/composer-runtime.d.ts +1 -1
  137. package/dist/runtime/api/composer-runtime.js.map +1 -1
  138. package/dist/runtime/api/message-part-runtime.d.ts +2 -2
  139. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  140. package/dist/runtime/api/message-part-runtime.js +1 -1
  141. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  142. package/dist/runtime/api/thread-list-runtime.d.ts +9 -1
  143. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  144. package/dist/runtime/api/thread-list-runtime.js +5 -0
  145. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  146. package/dist/runtime/api/thread-runtime.d.ts +2 -3
  147. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  148. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  149. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  150. package/dist/runtime/base/base-composer-runtime-core.js +14 -29
  151. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  152. package/dist/runtime/base/base-thread-runtime-core.d.ts +4 -6
  153. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  154. package/dist/runtime/base/base-thread-runtime-core.js +5 -11
  155. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  156. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  157. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  158. package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
  159. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  160. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
  161. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +14 -2
  162. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/interfaces/thread-runtime-core.d.ts +16 -2
  164. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  165. package/dist/runtime/internal.d.ts +2 -1
  166. package/dist/runtime/internal.js +2 -1
  167. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
  168. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  169. package/dist/runtime/queue/message-queue.js +1 -1
  170. package/dist/runtime/queue/message-queue.js.map +1 -1
  171. package/dist/runtime/queue/queue-item.d.ts +12 -0
  172. package/dist/runtime/queue/queue-item.d.ts.map +1 -0
  173. package/dist/runtime/queue/queue-item.js +6 -0
  174. package/dist/runtime/queue/queue-item.js.map +1 -0
  175. package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
  176. package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
  177. package/dist/runtime/utils/abortable-thread-load.js +35 -0
  178. package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
  179. package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
  180. package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
  181. package/dist/runtime/utils/attachment-add-operations.js +43 -0
  182. package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
  183. package/dist/runtime/utils/auto-status.d.ts +7 -2
  184. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  185. package/dist/runtime/utils/auto-status.js +13 -3
  186. package/dist/runtime/utils/auto-status.js.map +1 -1
  187. package/dist/runtime/utils/external-message-conversion.d.ts +48 -0
  188. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
  189. package/dist/runtime/utils/external-message-conversion.js +222 -0
  190. package/dist/runtime/utils/external-message-conversion.js.map +1 -0
  191. package/dist/runtime/utils/external-store-message.d.ts +2 -1
  192. package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
  193. package/dist/runtime/utils/external-store-message.js +2 -1
  194. package/dist/runtime/utils/external-store-message.js.map +1 -1
  195. package/dist/runtime/utils/message-repository-session.d.ts +30 -0
  196. package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
  197. package/dist/runtime/utils/message-repository-session.js +130 -0
  198. package/dist/runtime/utils/message-repository-session.js.map +1 -0
  199. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  200. package/dist/runtime/utils/message-repository.js +3 -4
  201. package/dist/runtime/utils/message-repository.js.map +1 -1
  202. package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
  203. package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
  204. package/dist/runtime/utils/pending-tool-calls.js +22 -0
  205. package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
  206. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +6 -3
  207. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
  208. package/dist/runtime/utils/resolveToolApprovalResponse.js +12 -3
  209. package/dist/runtime/utils/resolveToolApprovalResponse.js.map +1 -1
  210. package/dist/runtime/utils/thread-message-like.d.ts +3 -10
  211. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  212. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  213. package/dist/runtime/utils/tool-call-tree.d.ts +39 -0
  214. package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -0
  215. package/dist/runtime/utils/tool-call-tree.js +85 -0
  216. package/dist/runtime/utils/tool-call-tree.js.map +1 -0
  217. package/dist/runtimes/external-store/external-store-adapter.d.ts +29 -2
  218. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  219. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
  220. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  221. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +3 -10
  222. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  223. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +21 -2
  224. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  225. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +154 -46
  226. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  227. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +1 -1
  228. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
  229. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
  230. package/dist/runtimes/local/local-thread-runtime-core.d.ts +4 -2
  231. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  232. package/dist/runtimes/local/local-thread-runtime-core.js +23 -3
  233. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  234. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
  235. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  236. package/dist/runtimes/remote-thread-list/remote-thread-state.js +107 -14
  237. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  238. package/dist/runtimes/remote-thread-list/title.d.ts +9 -0
  239. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -0
  240. package/dist/runtimes/remote-thread-list/title.js +6 -0
  241. package/dist/runtimes/remote-thread-list/title.js.map +1 -0
  242. package/dist/runtimes/remote-thread-list/types.d.ts +6 -5
  243. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  244. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +31 -8
  245. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  246. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +98 -59
  247. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  248. package/dist/store/clients/external-thread.d.ts +1 -1
  249. package/dist/store/clients/external-thread.d.ts.map +1 -1
  250. package/dist/store/clients/external-thread.js +85 -103
  251. package/dist/store/clients/external-thread.js.map +1 -1
  252. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  253. package/dist/store/clients/single-thread-list.js +2 -2
  254. package/dist/store/clients/single-thread-list.js.map +1 -1
  255. package/dist/store/clients/suggestions.d.ts.map +1 -1
  256. package/dist/store/clients/suggestions.js +33 -12
  257. package/dist/store/clients/suggestions.js.map +1 -1
  258. package/dist/store/clients/thread-message-client.js +1 -1
  259. package/dist/store/clients/thread-selection-events.js +1 -1
  260. package/dist/store/index.d.ts +6 -3
  261. package/dist/store/index.js +2 -1
  262. package/dist/store/internal.d.ts +2 -2
  263. package/dist/store/internal.js +2 -2
  264. package/dist/store/primitive-predicates.d.ts +4 -1
  265. package/dist/store/primitive-predicates.d.ts.map +1 -1
  266. package/dist/store/primitive-predicates.js +9 -1
  267. package/dist/store/primitive-predicates.js.map +1 -1
  268. package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
  269. package/dist/store/runtime-clients/message-runtime-client.js +1 -1
  270. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  271. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  272. package/dist/store/runtime-clients/thread-list-runtime-client.js +124 -105
  273. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  274. package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
  275. package/dist/store/runtime-clients/useSubscribable.d.ts +3 -1
  276. package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
  277. package/dist/store/runtime-clients/useSubscribable.js +1 -1
  278. package/dist/store/runtime-clients/useSubscribable.js.map +1 -1
  279. package/dist/store/scope-registration.d.ts +1 -1
  280. package/dist/store/scopes/composer.d.ts +2 -1
  281. package/dist/store/scopes/composer.d.ts.map +1 -1
  282. package/dist/store/scopes/message.d.ts +1 -1
  283. package/dist/store/scopes/part.d.ts +6 -3
  284. package/dist/store/scopes/part.d.ts.map +1 -1
  285. package/dist/store/scopes/queue-item.d.ts +2 -9
  286. package/dist/store/scopes/queue-item.d.ts.map +1 -1
  287. package/dist/store/scopes/queue-item.js +0 -6
  288. package/dist/subscribable/subscribable.d.ts +8 -1
  289. package/dist/subscribable/subscribable.d.ts.map +1 -1
  290. package/dist/subscribable/subscribable.js +24 -16
  291. package/dist/subscribable/subscribable.js.map +1 -1
  292. package/dist/types/message.d.ts +47 -4
  293. package/dist/types/message.d.ts.map +1 -1
  294. package/dist/types/message.js +7 -1
  295. package/dist/types/message.js.map +1 -1
  296. package/dist/utils/data-url.d.ts +14 -1
  297. package/dist/utils/data-url.d.ts.map +1 -1
  298. package/dist/utils/data-url.js +13 -1
  299. package/dist/utils/data-url.js.map +1 -1
  300. package/dist/utils/invoke-user-callback.d.ts +5 -0
  301. package/dist/utils/invoke-user-callback.d.ts.map +1 -0
  302. package/dist/utils/invoke-user-callback.js +19 -0
  303. package/dist/utils/invoke-user-callback.js.map +1 -0
  304. package/dist/utils/normalizePartStatus.js.map +1 -1
  305. package/package.json +12 -17
  306. package/src/index.ts +6 -1
  307. package/src/internal.ts +20 -0
  308. package/src/model-context/frame/host.test.ts +91 -7
  309. package/src/model-context/frame/host.ts +7 -2
  310. package/src/model-context/frame/provider.test.ts +484 -13
  311. package/src/model-context/frame/provider.ts +205 -35
  312. package/src/model-context/interactable-composer-metadata.ts +1 -1
  313. package/src/model-context/registry.test.ts +44 -0
  314. package/src/model-context/registry.ts +13 -35
  315. package/src/model-context/tool-call-text.test.ts +67 -0
  316. package/src/model-context/tool-call-text.ts +67 -0
  317. package/src/react/AssistantRuntimeProvider.test.tsx +66 -2
  318. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +1 -3
  319. package/src/react/client/InMemoryThreadList.test.tsx +49 -0
  320. package/src/react/client/InMemoryThreadList.ts +7 -1
  321. package/src/react/client/Interactables.ts +19 -178
  322. package/src/react/client/RemoteThreadList.background.test.ts +384 -0
  323. package/src/react/client/RemoteThreadList.concurrent.test.tsx +274 -0
  324. package/src/react/client/RemoteThreadList.test.ts +369 -1
  325. package/src/react/client/RemoteThreadList.ts +320 -142
  326. package/src/react/client/Tools.ts +7 -1
  327. package/src/react/index.ts +4 -0
  328. package/src/react/interactables-legacy/Interactables.ts +20 -177
  329. package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
  330. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
  331. package/src/react/model-context/modelContextHooks.test.tsx +131 -0
  332. package/src/react/model-context/toolbox.test.ts +9 -9
  333. package/src/react/model-context/toolbox.ts +9 -36
  334. package/src/react/model-context/useAssistantContext.ts +4 -2
  335. package/src/react/model-context/useAuiToolOverrides.ts +4 -2
  336. package/src/react/model-context/useInlineRender.test.tsx +48 -0
  337. package/src/react/model-context/useInlineRender.ts +14 -10
  338. package/src/react/primitive-hooks/useMessageError.ts +2 -19
  339. package/src/react/primitive-hooks/useThreadListLoadMore.ts +2 -3
  340. package/src/react/primitives/composer/ComposerAttachments.test.tsx +76 -0
  341. package/src/react/primitives/composer/ComposerAttachments.tsx +9 -4
  342. package/src/react/primitives/composer/ComposerQueue.test.tsx +72 -0
  343. package/src/react/primitives/composer/ComposerQueue.tsx +7 -4
  344. package/src/react/primitives/message/MessageAttachments.test.tsx +107 -37
  345. package/src/react/primitives/message/MessageAttachments.tsx +10 -7
  346. package/src/react/primitives/message/MessageGroupedParts.tsx +2 -2
  347. package/src/react/primitives/message/MessageParts.tsx +3 -3
  348. package/src/react/primitives/threadList/ThreadListItems.test.tsx +99 -0
  349. package/src/react/primitives/threadList/ThreadListItems.tsx +8 -5
  350. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +94 -1
  351. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +72 -31
  352. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +169 -0
  353. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +140 -124
  354. package/src/react/runtimes/RemoteThreadResource.ts +1 -4
  355. package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
  356. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +51 -2
  357. package/src/react/runtimes/assistant-transport/useLatestRef.ts +2 -2
  358. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +45 -0
  359. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +157 -1
  360. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +82 -176
  361. package/src/react/runtimes/cloud/auiV0.ts +5 -0
  362. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +88 -0
  363. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +236 -0
  364. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
  365. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
  366. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +90 -1
  367. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +33 -238
  368. package/src/react/runtimes/external-message-converter.test.ts +0 -14
  369. package/src/react/runtimes/external-message-converter.test.tsx +77 -0
  370. package/src/react/runtimes/external-message-converter.ts +59 -481
  371. package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +216 -0
  372. package/src/react/runtimes/useRemoteThreadListRuntime.ts +24 -6
  373. package/src/react/types/MessagePartComponentTypes.ts +7 -3
  374. package/src/react/types/scopes/tools.ts +2 -0
  375. package/src/runtime/api/composer-runtime.ts +1 -1
  376. package/src/runtime/api/message-part-runtime.ts +3 -3
  377. package/src/runtime/api/message-runtime.test.ts +28 -1
  378. package/src/runtime/api/thread-list-runtime.ts +25 -1
  379. package/src/runtime/base/base-composer-runtime-core.ts +20 -40
  380. package/src/runtime/base/base-thread-runtime-core.test.ts +110 -3
  381. package/src/runtime/base/base-thread-runtime-core.ts +16 -15
  382. package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
  383. package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
  384. package/src/runtime/interfaces/thread-list-runtime-core.ts +20 -1
  385. package/src/runtime/interfaces/thread-runtime-core.ts +18 -2
  386. package/src/runtime/internal.ts +5 -0
  387. package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
  388. package/src/runtime/queue/message-queue.ts +1 -4
  389. package/src/runtime/queue/queue-item.ts +10 -0
  390. package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
  391. package/src/runtime/utils/abortable-thread-load.ts +53 -0
  392. package/src/runtime/utils/attachment-add-operations.ts +59 -0
  393. package/src/runtime/utils/auto-status.test.ts +57 -0
  394. package/src/runtime/utils/auto-status.ts +46 -1
  395. package/src/runtime/utils/external-message-conversion.test.ts +210 -0
  396. package/src/runtime/utils/external-message-conversion.ts +510 -0
  397. package/src/runtime/utils/external-store-message.ts +2 -0
  398. package/src/runtime/utils/message-repository-session.test.ts +195 -0
  399. package/src/runtime/utils/message-repository-session.ts +176 -0
  400. package/src/runtime/utils/message-repository.ts +7 -4
  401. package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
  402. package/src/runtime/utils/pending-tool-calls.ts +35 -0
  403. package/src/runtime/utils/resolveToolApprovalResponse.test.ts +73 -0
  404. package/src/runtime/utils/resolveToolApprovalResponse.ts +27 -6
  405. package/src/runtime/utils/thread-message-like.ts +4 -10
  406. package/src/runtime/utils/tool-call-tree.test.ts +191 -0
  407. package/src/runtime/utils/tool-call-tree.ts +97 -0
  408. package/src/runtimes/external-store/external-store-adapter.ts +38 -2
  409. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
  410. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +231 -61
  411. package/src/runtimes/inert/InertThreadRuntimeCore.ts +1 -1
  412. package/src/runtimes/local/local-thread-runtime-core.test.ts +201 -4
  413. package/src/runtimes/local/local-thread-runtime-core.ts +53 -4
  414. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +149 -0
  415. package/src/runtimes/remote-thread-list/remote-thread-state.ts +178 -16
  416. package/src/runtimes/remote-thread-list/title.ts +3 -0
  417. package/src/runtimes/remote-thread-list/types.ts +6 -5
  418. package/src/runtimes/tool-invocations/EDGE_CASES.md +125 -3
  419. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +856 -102
  420. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +145 -89
  421. package/src/store/clients/external-thread.ts +30 -69
  422. package/src/store/clients/single-thread-list.ts +4 -1
  423. package/src/store/clients/suggestions.test.ts +185 -0
  424. package/src/store/clients/suggestions.ts +42 -35
  425. package/src/store/index.ts +4 -0
  426. package/src/store/internal.ts +2 -0
  427. package/src/store/primitive-predicates.ts +35 -0
  428. package/src/store/runtime-clients/thread-list-runtime-client.ts +16 -2
  429. package/src/store/runtime-clients/useSubscribable.ts +8 -2
  430. package/src/store/scopes/message.ts +1 -1
  431. package/src/store/scopes/part.ts +6 -3
  432. package/src/store/scopes/queue-item.ts +2 -9
  433. package/src/subscribable/subscribable.test.ts +50 -0
  434. package/src/subscribable/subscribable.ts +33 -21
  435. package/src/tests/MessageRepository.test.ts +77 -0
  436. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +305 -0
  437. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +78 -0
  438. package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +52 -0
  439. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
  440. package/src/tests/external-store-optimistic-id.test.ts +79 -0
  441. package/src/tests/external-store-repository-instance.test.ts +166 -0
  442. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +186 -16
  443. package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
  444. package/src/tests/external-thread-attachments.test.tsx +258 -1
  445. package/src/tests/remote-thread-list-adapter-switch.test.ts +66 -1
  446. package/src/tests/single-thread-list-item.test.tsx +47 -0
  447. package/src/tests/thread-message-like.test.ts +39 -1
  448. package/src/tests/thread-switch-events.test.tsx +397 -2
  449. package/src/types/message.ts +58 -5
  450. package/src/utils/data-url.test.ts +52 -1
  451. package/src/utils/data-url.ts +21 -0
  452. package/src/utils/invoke-user-callback.test.ts +82 -0
  453. package/src/utils/invoke-user-callback.ts +31 -0
  454. package/src/utils/normalizePartStatus.ts +1 -1
  455. package/dist/store/scopes/queue-item.js.map +0 -1
@@ -29,22 +29,26 @@ const serializeModelContext = (
29
29
  }),
30
30
  });
31
31
 
32
+ const getDefaultTargetOrigin = () => window.location.origin;
33
+
32
34
  export class AssistantFrameProvider {
33
35
  private static _instance: AssistantFrameProvider | null = null;
34
36
 
35
- private _providers = new Set<ModelContextProvider>();
36
- private _providerUnsubscribes = new Map<
37
- ModelContextProvider,
38
- Unsubscribe | undefined
39
- >();
37
+ private _providers = new Map<symbol, ModelContextProvider>();
38
+ private _providerUnsubscribes = new Map<symbol, Unsubscribe | undefined>();
40
39
  private _activeToolCalls = new Map<
41
40
  string,
42
- { abortController: AbortController; event: MessageEvent }
41
+ {
42
+ abortController: AbortController;
43
+ event: MessageEvent;
44
+ provider: ModelContextProvider | undefined;
45
+ }
43
46
  >();
44
47
  private _targetOrigin: string;
45
48
  private _strictRegistrations = 0;
49
+ private _wildcardRegistrations = 0;
46
50
 
47
- private constructor(targetOrigin: string = "*") {
51
+ private constructor(targetOrigin: string = getDefaultTargetOrigin()) {
48
52
  this._targetOrigin = targetOrigin;
49
53
  this.handleMessage = this.handleMessage.bind(this);
50
54
  window.addEventListener("message", this.handleMessage);
@@ -63,8 +67,17 @@ export class AssistantFrameProvider {
63
67
  return AssistantFrameProvider._instance;
64
68
  }
65
69
 
66
- private reconcileTargetOrigin(targetOrigin: string = "*") {
67
- if (targetOrigin === "*" || targetOrigin === this._targetOrigin) return;
70
+ private reconcileTargetOrigin(
71
+ targetOrigin: string = getDefaultTargetOrigin(),
72
+ ) {
73
+ if (targetOrigin === this._targetOrigin) return;
74
+
75
+ if (this._providers.size === 0) {
76
+ this._targetOrigin = targetOrigin;
77
+ return;
78
+ }
79
+
80
+ if (targetOrigin === "*") return;
68
81
 
69
82
  if (this._targetOrigin === "*") {
70
83
  this._targetOrigin = targetOrigin;
@@ -106,10 +119,15 @@ export class AssistantFrameProvider {
106
119
  message: Extract<FrameMessage, { type: "tool-call" }>,
107
120
  event: MessageEvent,
108
121
  ) {
109
- const tool = this.getModelContext().tools?.[message.toolName];
122
+ const resolvedTool = this.getTool(message.toolName);
123
+ const tool = resolvedTool?.tool;
110
124
  const abortController = new AbortController();
111
125
  this._activeToolCalls.get(message.id)?.abortController.abort();
112
- const activeCall = { abortController, event };
126
+ const activeCall = {
127
+ abortController,
128
+ event,
129
+ provider: resolvedTool?.provider,
130
+ };
113
131
  this._activeToolCalls.set(message.id, activeCall);
114
132
 
115
133
  let result: any;
@@ -141,7 +159,7 @@ export class AssistantFrameProvider {
141
159
  this.sendMessage(event, {
142
160
  type: "tool-result",
143
161
  id: message.id,
144
- ...(error ? { error } : { result }),
162
+ ...(error !== undefined ? { error } : { result }),
145
163
  });
146
164
  }
147
165
 
@@ -152,6 +170,37 @@ export class AssistantFrameProvider {
152
170
  activeCall.abortController.abort();
153
171
  }
154
172
 
173
+ private cancelToolCallsForProvider(provider: ModelContextProvider) {
174
+ const matchingCalls = Array.from(this._activeToolCalls).filter(
175
+ ([, activeCall]) => activeCall.provider === provider,
176
+ );
177
+ for (const [id, activeCall] of matchingCalls) {
178
+ this._activeToolCalls.delete(id);
179
+ activeCall.abortController.abort();
180
+ }
181
+
182
+ let sendFailed = false;
183
+ let sendError: unknown;
184
+ for (const [id, activeCall] of matchingCalls) {
185
+ try {
186
+ this.sendMessage(activeCall.event, {
187
+ type: "tool-result",
188
+ id,
189
+ error: "AssistantFrame tool provider has been removed",
190
+ });
191
+ } catch (error) {
192
+ if (sendFailed) {
193
+ console.error(error);
194
+ } else {
195
+ sendFailed = true;
196
+ sendError = error;
197
+ }
198
+ }
199
+ }
200
+
201
+ if (sendFailed) throw sendError;
202
+ }
203
+
155
204
  private sendMessage(event: MessageEvent, message: FrameMessage) {
156
205
  event.source?.postMessage(
157
206
  { channel: FRAME_MESSAGE_CHANNEL, message },
@@ -159,9 +208,26 @@ export class AssistantFrameProvider {
159
208
  );
160
209
  }
161
210
 
211
+ private getProviders() {
212
+ return Array.from(new Set(this._providers.values()));
213
+ }
214
+
215
+ private getTool(toolName: string) {
216
+ let resolved:
217
+ | { provider: ModelContextProvider; tool: Tool<any, any> }
218
+ | undefined;
219
+
220
+ for (const provider of this.getProviders()) {
221
+ const tool = provider.getModelContext().tools?.[toolName];
222
+ if (tool) resolved = { provider, tool };
223
+ }
224
+
225
+ return resolved;
226
+ }
227
+
162
228
  private getModelContext(): ModelContext {
163
- const contexts = Array.from(this._providers).map((p) =>
164
- p.getModelContext(),
229
+ const contexts = this.getProviders().map((provider) =>
230
+ provider.getModelContext(),
165
231
  );
166
232
 
167
233
  return contexts.reduce(
@@ -191,34 +257,118 @@ export class AssistantFrameProvider {
191
257
  }
192
258
  }
193
259
 
260
+ private removeProvider(
261
+ id: symbol,
262
+ origin: string,
263
+ ): {
264
+ unsubscribe: Unsubscribe | undefined;
265
+ removedProvider: ModelContextProvider | undefined;
266
+ } {
267
+ const provider = this._providers.get(id);
268
+ this._providers.delete(id);
269
+ const unsubscribe = this._providerUnsubscribes.get(id);
270
+ this._providerUnsubscribes.delete(id);
271
+ if (origin === "*") {
272
+ this._wildcardRegistrations -= 1;
273
+ if (
274
+ this._wildcardRegistrations === 0 &&
275
+ this._strictRegistrations === 0
276
+ ) {
277
+ this._targetOrigin = getDefaultTargetOrigin();
278
+ }
279
+ } else {
280
+ this._strictRegistrations -= 1;
281
+ if (this._strictRegistrations === 0) {
282
+ this._targetOrigin =
283
+ this._wildcardRegistrations > 0 ? "*" : getDefaultTargetOrigin();
284
+ }
285
+ }
286
+ const removedProvider =
287
+ provider && !this.getProviders().includes(provider)
288
+ ? provider
289
+ : undefined;
290
+ return { unsubscribe, removedProvider };
291
+ }
292
+
194
293
  static addModelContextProvider(
195
294
  provider: ModelContextProvider,
196
295
  targetOrigin?: string,
197
296
  ): Unsubscribe {
198
- const origin = targetOrigin ?? "*";
297
+ const origin = targetOrigin ?? getDefaultTargetOrigin();
199
298
  const instance = AssistantFrameProvider.getInstance(origin);
200
- instance._providers.add(provider);
201
- if (origin !== "*") instance._strictRegistrations += 1;
202
-
203
- const unsubscribe = provider.subscribe?.(() => instance.broadcastUpdate());
204
- if (unsubscribe) {
205
- instance._providerUnsubscribes.set(provider, unsubscribe);
299
+ const id = Symbol();
300
+ instance._providers.set(id, provider);
301
+ if (origin === "*") {
302
+ instance._wildcardRegistrations += 1;
303
+ } else {
304
+ instance._strictRegistrations += 1;
206
305
  }
207
306
 
208
- instance.broadcastUpdate();
307
+ try {
308
+ const unsubscribe = provider.subscribe?.(() =>
309
+ instance.broadcastUpdate(),
310
+ );
311
+ if (unsubscribe) {
312
+ instance._providerUnsubscribes.set(id, unsubscribe);
313
+ }
314
+
315
+ instance.broadcastUpdate();
316
+ } catch (error) {
317
+ const { unsubscribe, removedProvider } = instance.removeProvider(
318
+ id,
319
+ origin,
320
+ );
321
+ // Rollback failures must not replace the registration error.
322
+ try {
323
+ if (removedProvider) {
324
+ instance.cancelToolCallsForProvider(removedProvider);
325
+ }
326
+ } catch (cancelError) {
327
+ console.error(cancelError);
328
+ }
329
+ try {
330
+ unsubscribe?.();
331
+ } catch (unsubscribeError) {
332
+ console.error(unsubscribeError);
333
+ }
334
+ try {
335
+ instance.broadcastUpdate();
336
+ } catch (broadcastError) {
337
+ console.error(broadcastError);
338
+ }
339
+ throw error;
340
+ }
209
341
 
210
342
  let released = false;
211
343
  return () => {
212
344
  if (released) return;
213
345
  released = true;
214
- instance._providers.delete(provider);
215
- instance._providerUnsubscribes.get(provider)?.();
216
- instance._providerUnsubscribes.delete(provider);
217
- if (origin !== "*") {
218
- instance._strictRegistrations -= 1;
219
- if (instance._strictRegistrations === 0) instance._targetOrigin = "*";
346
+ const { unsubscribe, removedProvider } = instance.removeProvider(
347
+ id,
348
+ origin,
349
+ );
350
+ let cleanupFailed = false;
351
+ let cleanupError: unknown;
352
+ const runCleanup = (cleanup: () => void) => {
353
+ try {
354
+ cleanup();
355
+ } catch (error) {
356
+ if (cleanupFailed) {
357
+ console.error(error);
358
+ } else {
359
+ cleanupFailed = true;
360
+ cleanupError = error;
361
+ }
362
+ }
363
+ };
364
+
365
+ if (removedProvider) {
366
+ runCleanup(() => instance.cancelToolCallsForProvider(removedProvider));
220
367
  }
221
- instance.broadcastUpdate();
368
+ if (unsubscribe) runCleanup(unsubscribe);
369
+ runCleanup(() => instance.broadcastUpdate());
370
+
371
+ if (cleanupFailed) throw cleanupError;
222
372
  };
223
373
  }
224
374
 
@@ -227,20 +377,40 @@ export class AssistantFrameProvider {
227
377
  const instance = AssistantFrameProvider._instance;
228
378
  window.removeEventListener("message", instance.handleMessage);
229
379
 
230
- instance._providerUnsubscribes.forEach((unsubscribe) => unsubscribe?.());
380
+ let cleanupFailed = false;
381
+ let cleanupError: unknown;
382
+ const runCleanup = (cleanup: () => void) => {
383
+ try {
384
+ cleanup();
385
+ } catch (error) {
386
+ if (cleanupFailed) {
387
+ console.error(error);
388
+ } else {
389
+ cleanupFailed = true;
390
+ cleanupError = error;
391
+ }
392
+ }
393
+ };
394
+
395
+ instance._providerUnsubscribes.forEach((unsubscribe) => {
396
+ if (unsubscribe) runCleanup(unsubscribe);
397
+ });
231
398
  instance._providerUnsubscribes.clear();
232
399
  instance._providers.clear();
233
400
  instance._activeToolCalls.forEach(({ abortController, event }, id) => {
234
- abortController.abort();
235
- instance.sendMessage(event, {
236
- type: "tool-result",
237
- id,
238
- error: "AssistantFrameProvider has been disposed",
401
+ runCleanup(() => {
402
+ abortController.abort();
403
+ instance.sendMessage(event, {
404
+ type: "tool-result",
405
+ id,
406
+ error: "AssistantFrameProvider has been disposed",
407
+ });
239
408
  });
240
409
  });
241
410
  instance._activeToolCalls.clear();
242
411
 
243
412
  AssistantFrameProvider._instance = null;
413
+ if (cleanupFailed) throw cleanupError;
244
414
  }
245
415
  }
246
416
  }
@@ -33,7 +33,7 @@ type SnapshotCarrierMessage = {
33
33
  * `metadata.custom.interactables`, or `undefined` if none. This is the read
34
34
  * half of the snapshot channel — integrations use it to surface interactable
35
35
  * state to the model (see `unstable_injectInteractableContext` in
36
- * `@assistant-ui/react-ai-sdk` for the AI SDK implementation).
36
+ * `@assistant-ui/ai-sdk` for the AI SDK implementation).
37
37
  *
38
38
  * @deprecated Unstable / Experimental (not actually removed).
39
39
  */
@@ -128,4 +128,48 @@ describe("ModelContextRegistry", () => {
128
128
  expect(() => publishUpdate()).toThrow(error);
129
129
  expect(laterSubscriber).toHaveBeenCalledTimes(1);
130
130
  });
131
+
132
+ it("notifies subscribers exactly once when a provider changes", () => {
133
+ const registry = new ModelContextRegistry();
134
+ const callbacks = new Set<() => void>();
135
+ const subscriber = vi.fn();
136
+
137
+ registry.addProvider({
138
+ getModelContext: () => ({ system: "provider instructions" }),
139
+ subscribe: (callback) => {
140
+ callbacks.add(callback);
141
+ return () => callbacks.delete(callback);
142
+ },
143
+ });
144
+ registry.subscribe(subscriber);
145
+
146
+ for (const callback of callbacks) {
147
+ callback();
148
+ }
149
+
150
+ expect(subscriber).toHaveBeenCalledTimes(1);
151
+ });
152
+
153
+ it("removes a provider's context and subscription through the addProvider handle", () => {
154
+ const registry = new ModelContextRegistry();
155
+ const unsubscribe = vi.fn();
156
+
157
+ const handle = registry.addProvider({
158
+ getModelContext: () => ({ system: "provider instructions" }),
159
+ subscribe: () => unsubscribe,
160
+ });
161
+ expect(registry.getModelContext().system).toContain(
162
+ "provider instructions",
163
+ );
164
+
165
+ const subscriber = vi.fn();
166
+ registry.subscribe(subscriber);
167
+ handle.remove();
168
+
169
+ expect(unsubscribe).toHaveBeenCalledTimes(1);
170
+ expect(registry.getModelContext().system ?? "").not.toContain(
171
+ "provider instructions",
172
+ );
173
+ expect(subscriber).toHaveBeenCalledTimes(1);
174
+ });
131
175
  });
@@ -2,12 +2,12 @@ import type { Tool } from "assistant-stream";
2
2
  import {
3
3
  type ModelContext,
4
4
  type ModelContextProvider,
5
- mergeModelContexts,
6
5
  type AssistantToolProps,
7
6
  type AssistantInstructionsConfig,
8
7
  } from "./types";
9
- import type { Unsubscribe } from "../types/unsubscribe";
10
8
  import { notifySubscribers as notifyStateSubscribers } from "../subscribable/subscribable";
9
+ import { CompositeContextProvider } from "../utils/composite-context-provider";
10
+ import type { Unsubscribe } from "../types/unsubscribe";
11
11
  import type {
12
12
  ModelContextRegistryToolHandle,
13
13
  ModelContextRegistryInstructionHandle,
@@ -17,9 +17,14 @@ import type {
17
17
  export class ModelContextRegistry implements ModelContextProvider {
18
18
  private _tools = new Map<symbol, AssistantToolProps<any, any>>();
19
19
  private _instructions = new Map<symbol, string>();
20
- private _providers = new Map<symbol, ModelContextProvider>();
20
+ private _contextProviders = new CompositeContextProvider();
21
21
  private _subscribers = new Set<() => void>();
22
- private _providerUnsubscribes = new Map<symbol, Unsubscribe | undefined>();
22
+
23
+ constructor() {
24
+ this._contextProviders.subscribe(() => {
25
+ this.notifySubscribers();
26
+ });
27
+ }
23
28
 
24
29
  getModelContext(): ModelContext {
25
30
  const instructions = Array.from(this._instructions.values()).filter(
@@ -35,9 +40,7 @@ export class ModelContextRegistry implements ModelContextProvider {
35
40
  tools[toolName] = tool;
36
41
  }
37
42
 
38
- const providerContexts = mergeModelContexts(
39
- new Set(this._providers.values()),
40
- );
43
+ const providerContexts = this._contextProviders.getModelContext();
41
44
 
42
45
  const context: ModelContext = {
43
46
  system,
@@ -139,36 +142,11 @@ export class ModelContextRegistry implements ModelContextProvider {
139
142
  addProvider(
140
143
  provider: ModelContextProvider,
141
144
  ): ModelContextRegistryProviderHandle {
142
- const id = Symbol();
143
-
144
- this._providers.set(id, provider);
145
-
146
- let unsubscribe: Unsubscribe | undefined;
147
- try {
148
- unsubscribe = provider.subscribe?.(() => {
149
- this.notifySubscribers();
150
- });
151
- } catch (error) {
152
- this._providers.delete(id);
153
- try {
154
- this.notifySubscribers();
155
- } catch (notifyError) {
156
- console.error(notifyError);
157
- }
158
- throw error;
159
- }
160
- this._providerUnsubscribes.set(id, unsubscribe);
161
-
162
- this.notifySubscribers();
145
+ const unregister =
146
+ this._contextProviders.registerModelContextProvider(provider);
163
147
 
164
148
  return {
165
- remove: () => {
166
- this._providers.delete(id);
167
- const unsubscribe = this._providerUnsubscribes.get(id);
168
- unsubscribe?.();
169
- this._providerUnsubscribes.delete(id);
170
- this.notifySubscribers();
171
- },
149
+ remove: unregister,
172
150
  };
173
151
  }
174
152
  }
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveToolCallText } from "./tool-call-text";
3
+
4
+ describe("resolveToolCallText", () => {
5
+ it("resolves literal text for running and complete tool calls", () => {
6
+ const text = {
7
+ running: "Searching...",
8
+ complete: "Done searching",
9
+ };
10
+
11
+ expect(
12
+ resolveToolCallText(text, {
13
+ args: {},
14
+ status: { type: "running" },
15
+ }),
16
+ ).toBe("Searching...");
17
+ expect(
18
+ resolveToolCallText(text, {
19
+ args: {},
20
+ status: { type: "complete" },
21
+ }),
22
+ ).toBe("Done searching");
23
+ });
24
+
25
+ it("passes args and results to text functions", () => {
26
+ const text = {
27
+ running: ({ args }: { args: { query: string } }) =>
28
+ `Searching ${args.query}...`,
29
+ complete: ({
30
+ args,
31
+ result,
32
+ }: {
33
+ args: { query: string };
34
+ result: number | undefined;
35
+ }) => `Found ${result ?? 0} results for ${args.query}`,
36
+ };
37
+
38
+ expect(
39
+ resolveToolCallText(text, {
40
+ args: { query: "docs" },
41
+ status: { type: "requires-action" },
42
+ }),
43
+ ).toBe("Searching docs...");
44
+ expect(
45
+ resolveToolCallText(text, {
46
+ args: { query: "docs" },
47
+ result: 3,
48
+ status: { type: "complete" },
49
+ }),
50
+ ).toBe("Found 3 results for docs");
51
+ });
52
+
53
+ it("returns null when the active status has no text", () => {
54
+ expect(
55
+ resolveToolCallText(
56
+ { running: "Searching..." },
57
+ { args: {}, status: { type: "complete" } },
58
+ ),
59
+ ).toBeNull();
60
+ expect(
61
+ resolveToolCallText(
62
+ { complete: "Done searching" },
63
+ { args: {}, status: { type: "running" } },
64
+ ),
65
+ ).toBeNull();
66
+ });
67
+ });
@@ -0,0 +1,67 @@
1
+ type ToolCallRunningText<TArgs extends Record<string, unknown>, TValue> =
2
+ | TValue
3
+ | undefined
4
+ | null
5
+ | ((options: { args: TArgs }) => TValue | undefined | null);
6
+
7
+ type ToolCallCompleteText<
8
+ TArgs extends Record<string, unknown>,
9
+ TResult,
10
+ TValue,
11
+ > =
12
+ | TValue
13
+ | undefined
14
+ | null
15
+ | ((options: {
16
+ args: TArgs;
17
+ result: TResult | undefined;
18
+ }) => TValue | undefined | null);
19
+
20
+ export type ToolCallText<
21
+ TArgs extends Record<string, unknown>,
22
+ TResult,
23
+ TValue = string,
24
+ > =
25
+ | {
26
+ running: ToolCallRunningText<TArgs, TValue>;
27
+ complete?: ToolCallCompleteText<TArgs, TResult, TValue> | undefined;
28
+ }
29
+ | {
30
+ running?: ToolCallRunningText<TArgs, TValue> | undefined;
31
+ complete: ToolCallCompleteText<TArgs, TResult, TValue>;
32
+ };
33
+
34
+ type ToolCallTextPart<TArgs extends Record<string, unknown>, TResult> = {
35
+ readonly args: TArgs;
36
+ readonly result?: TResult | undefined;
37
+ readonly status?: { readonly type?: string | undefined } | undefined;
38
+ };
39
+
40
+ export const resolveToolCallText = <
41
+ TArgs extends Record<string, unknown>,
42
+ TResult,
43
+ TValue,
44
+ >(
45
+ text: ToolCallText<TArgs, TResult, TValue>,
46
+ part: ToolCallTextPart<TArgs, TResult>,
47
+ ): TValue | undefined | null => {
48
+ const isRunning =
49
+ part.status?.type === "running" || part.status?.type === "requires-action";
50
+
51
+ if (!isRunning) {
52
+ const value = text.complete;
53
+ if (typeof value !== "function") return value ?? null;
54
+ return (
55
+ value as (options: {
56
+ args: TArgs;
57
+ result: TResult | undefined;
58
+ }) => TValue | undefined | null
59
+ )({ args: part.args, result: part.result });
60
+ }
61
+
62
+ const value = text.running;
63
+ if (typeof value !== "function") return value ?? null;
64
+ return (value as (options: { args: TArgs }) => TValue | undefined | null)({
65
+ args: part.args,
66
+ });
67
+ };
@@ -1,8 +1,8 @@
1
1
  // @vitest-environment jsdom
2
2
 
3
3
  import type { ReactNode } from "react";
4
- import { useEffect } from "react";
5
- import { cleanup, render } from "@testing-library/react";
4
+ import { useEffect, useState } from "react";
5
+ import { act, cleanup, render } from "@testing-library/react";
6
6
  import { afterEach, describe, expect, it } from "vitest";
7
7
  import { resource } from "@assistant-ui/tap";
8
8
  import {
@@ -12,8 +12,10 @@ import {
12
12
  type AssistantClient,
13
13
  } from "@assistant-ui/store";
14
14
  import type { ChatModelAdapter } from "../runtime/utils/chat-model-adapter";
15
+ import type { ThreadMessage } from "../types/message";
15
16
  import { AssistantRuntimeProvider } from "./AssistantRuntimeProvider";
16
17
  import { useLocalRuntime } from "./runtimes/useLocalRuntime";
18
+ import { useExternalStoreRuntime } from "./runtimes/useExternalStoreRuntime";
17
19
 
18
20
  type AnyClient = Record<string, any>;
19
21
 
@@ -21,6 +23,20 @@ const chatModel: ChatModelAdapter = {
21
23
  run: async () => ({ content: [] }),
22
24
  };
23
25
 
26
+ const hoistedConfig = AuiConfig({});
27
+
28
+ const message = (id: string, role: "user" | "assistant"): ThreadMessage =>
29
+ ({
30
+ id,
31
+ role,
32
+ content: [{ type: "text", text: `text of ${id}` }],
33
+ createdAt: new Date(1718000000000),
34
+ ...(role === "assistant"
35
+ ? { status: { type: "complete", reason: "stop" } }
36
+ : { attachments: [] }),
37
+ metadata: { custom: {} },
38
+ }) as ThreadMessage;
39
+
24
40
  const makeCounterClient = (log?: string[]) => {
25
41
  const useCounterClient = () => {
26
42
  useEffect(() => {
@@ -152,4 +168,52 @@ describe("AssistantRuntimeProvider aui composition", () => {
152
168
 
153
169
  expect(aui.threads.getState().main).toBeDefined();
154
170
  });
171
+
172
+ const countPublicationsOverThreeRerenders = async (
173
+ makeMessages: () => ThreadMessage[],
174
+ ) => {
175
+ let advance!: () => void;
176
+ let notifications = 0;
177
+
178
+ const Subscriber = () => {
179
+ const aui = useAui();
180
+ useEffect(() => aui.subscribe(() => notifications++), [aui]);
181
+ return null;
182
+ };
183
+
184
+ const App = () => {
185
+ const [, setTick] = useState(0);
186
+ advance = () => setTick((tick) => tick + 1);
187
+ const runtime = useExternalStoreRuntime<ThreadMessage>({
188
+ messages: makeMessages(),
189
+ onNew: async () => {},
190
+ });
191
+
192
+ return (
193
+ <AssistantRuntimeProvider runtime={runtime} config={hoistedConfig}>
194
+ <Subscriber />
195
+ </AssistantRuntimeProvider>
196
+ );
197
+ };
198
+
199
+ render(<App />);
200
+ notifications = 0;
201
+
202
+ await act(async () => advance());
203
+ await act(async () => advance());
204
+ await act(async () => advance());
205
+
206
+ return notifications;
207
+ };
208
+
209
+ it("publishes once when the external-store runtime owner rerenders", async () => {
210
+ expect(await countPublicationsOverThreeRerenders(() => [])).toBe(3);
211
+ });
212
+
213
+ it("publishes once per rerender when the thread already has messages", async () => {
214
+ const messages = [message("m1", "user"), message("m2", "assistant")];
215
+ expect(await countPublicationsOverThreeRerenders(() => [...messages])).toBe(
216
+ 3,
217
+ );
218
+ });
155
219
  });