@assistant-ui/core 0.3.16 → 0.3.18

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 (474) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/attachment.js +5 -4
  3. package/dist/adapters/attachment.js.map +1 -1
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.js +2 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/internal.d.ts +4 -2
  8. package/dist/internal.js +4 -2
  9. package/dist/model-context/frame/host.d.ts.map +1 -1
  10. package/dist/model-context/frame/host.js +10 -4
  11. package/dist/model-context/frame/host.js.map +1 -1
  12. package/dist/model-context/frame/provider.d.ts +5 -0
  13. package/dist/model-context/frame/provider.d.ts.map +1 -1
  14. package/dist/model-context/frame/provider.js +101 -28
  15. package/dist/model-context/frame/provider.js.map +1 -1
  16. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  17. package/dist/model-context/interactable-composer-metadata.js +16 -9
  18. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  19. package/dist/model-context/registry.d.ts.map +1 -1
  20. package/dist/model-context/registry.js +3 -5
  21. package/dist/model-context/registry.js.map +1 -1
  22. package/dist/model-context/tool-call-text.d.ts +26 -0
  23. package/dist/model-context/tool-call-text.d.ts.map +1 -0
  24. package/dist/model-context/tool-call-text.js +18 -0
  25. package/dist/model-context/tool-call-text.js.map +1 -0
  26. package/dist/model-context/types.d.ts.map +1 -1
  27. package/dist/model-context/types.js +5 -4
  28. package/dist/model-context/types.js.map +1 -1
  29. package/dist/react/RuntimeAdapter.js +1 -1
  30. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  31. package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -1
  32. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  33. package/dist/react/client/AdaptedRemoteThread.js +1 -1
  34. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  35. package/dist/react/client/InMemoryThreadList.js +63 -57
  36. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  37. package/dist/react/client/Interactables.d.ts.map +1 -1
  38. package/dist/react/client/Interactables.js +25 -28
  39. package/dist/react/client/Interactables.js.map +1 -1
  40. package/dist/react/client/RemoteThreadList.d.ts +16 -0
  41. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  42. package/dist/react/client/RemoteThreadList.js +354 -242
  43. package/dist/react/client/RemoteThreadList.js.map +1 -1
  44. package/dist/react/client/Tools.d.ts.map +1 -1
  45. package/dist/react/client/Tools.js +24 -18
  46. package/dist/react/client/Tools.js.map +1 -1
  47. package/dist/react/index.d.ts +2 -1
  48. package/dist/react/index.js +2 -1
  49. package/dist/react/interactables-legacy/Interactables.js +246 -125
  50. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  51. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  52. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  53. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  54. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  55. package/dist/react/model-context/makeAssistantTool.js +1 -1
  56. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  57. package/dist/react/model-context/toolbox.d.ts +2 -14
  58. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  59. package/dist/react/model-context/toolbox.js +1 -13
  60. package/dist/react/model-context/toolbox.js.map +1 -1
  61. package/dist/react/model-context/useAssistantContext.js +37 -7
  62. package/dist/react/model-context/useAssistantContext.js.map +1 -1
  63. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  64. package/dist/react/model-context/useAssistantTool.js +1 -1
  65. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  66. package/dist/react/model-context/useAuiToolOverrides.js +31 -5
  67. package/dist/react/model-context/useAuiToolOverrides.js.map +1 -1
  68. package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
  69. package/dist/react/model-context/useInlineRender.js +10 -36
  70. package/dist/react/model-context/useInlineRender.js.map +1 -1
  71. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  72. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  73. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  74. package/dist/react/primitive-hooks/useMessageError.d.ts.map +1 -1
  75. package/dist/react/primitive-hooks/useMessageError.js +2 -8
  76. package/dist/react/primitive-hooks/useMessageError.js.map +1 -1
  77. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
  78. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -4
  79. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  80. package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  81. package/dist/react/primitives/composer/ComposerAttachments.js +4 -3
  82. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  83. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  84. package/dist/react/primitives/composer/ComposerQueue.js +4 -3
  85. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  86. package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
  87. package/dist/react/primitives/message/MessageAttachments.js +7 -6
  88. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  89. package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
  90. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  91. package/dist/react/primitives/message/MessageParts.js +3 -3
  92. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  93. package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  94. package/dist/react/primitives/threadList/ThreadListItems.js +5 -4
  95. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  96. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +6 -4
  97. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  98. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +59 -29
  99. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  100. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +11 -6
  101. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  102. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +160 -143
  103. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  104. package/dist/react/runtimes/RemoteThreadResource.d.ts +2 -6
  105. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  106. package/dist/react/runtimes/RemoteThreadResource.js +15 -13
  107. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  108. package/dist/react/runtimes/assistant-transport/useLatestRef.js +2 -2
  109. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -1
  110. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +4 -1
  111. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  112. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +424 -52
  113. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  114. package/dist/react/runtimes/cloud/auiV0.d.ts +5 -1
  115. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  116. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  117. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +29 -0
  118. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -0
  119. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +160 -0
  120. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -0
  121. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +1 -9
  122. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +16 -153
  124. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  125. package/dist/react/runtimes/external-message-converter.js +1 -1
  126. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  127. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  128. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  129. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  130. package/dist/react/runtimes/useRemoteThreadListRuntime.js +77 -29
  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/message-part-runtime.d.ts +2 -2
  137. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  138. package/dist/runtime/api/message-part-runtime.js +1 -1
  139. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  140. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  141. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  142. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  143. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  144. package/dist/runtime/api/thread-list-runtime.d.ts +12 -1
  145. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  146. package/dist/runtime/api/thread-list-runtime.js +23 -14
  147. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  148. package/dist/runtime/api/thread-runtime.d.ts +5 -1
  149. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  150. package/dist/runtime/api/thread-runtime.js +1 -1
  151. package/dist/runtime/api/thread-runtime.js.map +1 -1
  152. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  153. package/dist/runtime/base/base-composer-runtime-core.js +16 -12
  154. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  155. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -2
  156. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  157. package/dist/runtime/base/base-thread-runtime-core.js +8 -7
  158. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  159. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  160. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  161. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +18 -3
  162. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/interfaces/thread-runtime-core.d.ts +15 -1
  164. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  165. package/dist/runtime/utils/auto-status.d.ts +1 -1
  166. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  167. package/dist/runtime/utils/auto-status.js +6 -2
  168. package/dist/runtime/utils/auto-status.js.map +1 -1
  169. package/dist/runtime/utils/external-message-conversion.d.ts +3 -2
  170. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  171. package/dist/runtime/utils/external-message-conversion.js +5 -4
  172. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  173. package/dist/runtime/utils/message-repository.d.ts +1 -0
  174. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  175. package/dist/runtime/utils/message-repository.js +15 -6
  176. package/dist/runtime/utils/message-repository.js.map +1 -1
  177. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +6 -3
  178. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
  179. package/dist/runtime/utils/resolveToolApprovalResponse.js +12 -3
  180. package/dist/runtime/utils/resolveToolApprovalResponse.js.map +1 -1
  181. package/dist/runtime/utils/thread-message-like.d.ts +2 -10
  182. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  183. package/dist/runtime/utils/thread-message-like.js +1 -1
  184. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  185. package/dist/runtime/utils/tool-call-tree.d.ts +39 -0
  186. package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -0
  187. package/dist/runtime/utils/tool-call-tree.js +85 -0
  188. package/dist/runtime/utils/tool-call-tree.js.map +1 -0
  189. package/dist/runtimes/external-store/external-store-adapter.d.ts +29 -2
  190. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  191. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  192. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  193. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  194. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -1
  195. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  196. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +34 -28
  197. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  198. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +1 -1
  199. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
  200. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
  201. package/dist/runtimes/internal.d.ts +2 -2
  202. package/dist/runtimes/internal.js +2 -2
  203. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
  204. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  205. package/dist/runtimes/local/local-thread-runtime-core.js +3 -1
  206. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  207. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  208. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  209. package/dist/runtimes/remote-thread-list/remote-thread-state.js +148 -43
  210. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  211. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  212. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  213. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  214. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  215. package/dist/runtimes/remote-thread-list/title.d.ts +11 -0
  216. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -0
  217. package/dist/runtimes/remote-thread-list/title.js +14 -0
  218. package/dist/runtimes/remote-thread-list/title.js.map +1 -0
  219. package/dist/runtimes/remote-thread-list/types.d.ts +16 -5
  220. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  221. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +31 -8
  222. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  223. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +97 -60
  224. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  225. package/dist/store/clients/external-thread.d.ts +1 -1
  226. package/dist/store/clients/external-thread.d.ts.map +1 -1
  227. package/dist/store/clients/external-thread.js +30 -7
  228. package/dist/store/clients/external-thread.js.map +1 -1
  229. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  230. package/dist/store/clients/model-context-client.js +2 -1
  231. package/dist/store/clients/model-context-client.js.map +1 -1
  232. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  233. package/dist/store/clients/single-thread-list.js +2 -1
  234. package/dist/store/clients/single-thread-list.js.map +1 -1
  235. package/dist/store/clients/suggestions.d.ts.map +1 -1
  236. package/dist/store/clients/suggestions.js +19 -6
  237. package/dist/store/clients/suggestions.js.map +1 -1
  238. package/dist/store/clients/thread-message-client.js +1 -1
  239. package/dist/store/clients/thread-selection-events.js +1 -1
  240. package/dist/store/index.d.ts +2 -1
  241. package/dist/store/index.js +2 -1
  242. package/dist/store/internal.d.ts +2 -2
  243. package/dist/store/internal.js +2 -2
  244. package/dist/store/primitive-predicates.d.ts +4 -1
  245. package/dist/store/primitive-predicates.d.ts.map +1 -1
  246. package/dist/store/primitive-predicates.js +9 -1
  247. package/dist/store/primitive-predicates.js.map +1 -1
  248. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  249. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  250. package/dist/store/runtime-clients/composer-runtime-client.js +98 -62
  251. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  252. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  253. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  254. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  255. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  256. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  257. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +2 -2
  258. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  259. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  260. package/dist/store/runtime-clients/thread-list-runtime-client.js +128 -107
  261. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  262. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  263. package/dist/store/runtime-clients/thread-runtime-client.js +194 -122
  264. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  265. package/dist/store/runtime-clients/useSubscribable.d.ts +3 -1
  266. package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
  267. package/dist/store/runtime-clients/useSubscribable.js +1 -1
  268. package/dist/store/runtime-clients/useSubscribable.js.map +1 -1
  269. package/dist/store/scopes/composer.d.ts +10 -2
  270. package/dist/store/scopes/composer.d.ts.map +1 -1
  271. package/dist/store/scopes/message.d.ts +30 -1
  272. package/dist/store/scopes/message.d.ts.map +1 -1
  273. package/dist/store/scopes/part.d.ts +6 -3
  274. package/dist/store/scopes/part.d.ts.map +1 -1
  275. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  276. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  277. package/dist/store/scopes/thread.d.ts +8 -0
  278. package/dist/store/scopes/thread.d.ts.map +1 -1
  279. package/dist/store/scopes/threads.d.ts +1 -0
  280. package/dist/store/scopes/threads.d.ts.map +1 -1
  281. package/dist/subscribable/subscribable.d.ts +8 -1
  282. package/dist/subscribable/subscribable.d.ts.map +1 -1
  283. package/dist/subscribable/subscribable.js +24 -16
  284. package/dist/subscribable/subscribable.js.map +1 -1
  285. package/dist/types/attachment.d.ts +2 -1
  286. package/dist/types/attachment.d.ts.map +1 -1
  287. package/dist/types/attachment.js +2 -1
  288. package/dist/types/attachment.js.map +1 -1
  289. package/dist/types/message.d.ts +40 -1
  290. package/dist/types/message.d.ts.map +1 -1
  291. package/dist/types/message.js +7 -1
  292. package/dist/types/message.js.map +1 -1
  293. package/dist/utils/abortable-iterable.d.ts +19 -0
  294. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  295. package/dist/utils/abortable-iterable.js +72 -0
  296. package/dist/utils/abortable-iterable.js.map +1 -0
  297. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  298. package/dist/utils/composite-context-provider.js +20 -2
  299. package/dist/utils/composite-context-provider.js.map +1 -1
  300. package/dist/utils/record.d.ts +11 -0
  301. package/dist/utils/record.d.ts.map +1 -0
  302. package/dist/utils/record.js +12 -0
  303. package/dist/utils/record.js.map +1 -0
  304. package/package.json +12 -17
  305. package/src/adapters/attachment.test.ts +50 -1
  306. package/src/adapters/attachment.ts +7 -4
  307. package/src/index.ts +7 -1
  308. package/src/internal.ts +12 -0
  309. package/src/model-context/frame/host.test.ts +24 -0
  310. package/src/model-context/frame/host.ts +10 -4
  311. package/src/model-context/frame/provider.test.ts +288 -1
  312. package/src/model-context/frame/provider.ts +148 -30
  313. package/src/model-context/interactable-composer-metadata.test.ts +28 -0
  314. package/src/model-context/interactable-composer-metadata.ts +9 -3
  315. package/src/model-context/registry.test.ts +12 -0
  316. package/src/model-context/registry.ts +3 -2
  317. package/src/model-context/tool-call-text.test.ts +67 -0
  318. package/src/model-context/tool-call-text.ts +67 -0
  319. package/src/model-context/types.test.ts +25 -0
  320. package/src/model-context/types.ts +9 -4
  321. package/src/react/AssistantRuntimeProvider.test.tsx +66 -2
  322. package/src/react/RuntimeAdapter.test.ts +34 -0
  323. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +1 -3
  324. package/src/react/client/InMemoryThreadList.ts +1 -0
  325. package/src/react/client/Interactables.test.ts +28 -0
  326. package/src/react/client/Interactables.ts +15 -13
  327. package/src/react/client/RemoteThreadList.background.test.ts +526 -0
  328. package/src/react/client/RemoteThreadList.concurrent.test.tsx +315 -0
  329. package/src/react/client/RemoteThreadList.test.ts +617 -3
  330. package/src/react/client/RemoteThreadList.ts +433 -167
  331. package/src/react/client/Tools.ts +29 -12
  332. package/src/react/index.ts +4 -0
  333. package/src/react/interactables-legacy/Interactables.ts +16 -16
  334. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  335. package/src/react/model-context/makeAssistantTool.ts +2 -2
  336. package/src/react/model-context/modelContextHooks.test.tsx +131 -0
  337. package/src/react/model-context/toolbox.test.ts +9 -9
  338. package/src/react/model-context/toolbox.ts +9 -36
  339. package/src/react/model-context/useAssistantContext.ts +4 -2
  340. package/src/react/model-context/useAssistantTool.ts +2 -2
  341. package/src/react/model-context/useAuiToolOverrides.ts +4 -2
  342. package/src/react/model-context/useInlineRender.test.tsx +48 -0
  343. package/src/react/model-context/useInlineRender.ts +14 -10
  344. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  345. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  346. package/src/react/primitive-hooks/useMessageError.ts +2 -19
  347. package/src/react/primitive-hooks/useThreadListLoadMore.ts +2 -3
  348. package/src/react/primitives/composer/ComposerAttachments.test.tsx +76 -0
  349. package/src/react/primitives/composer/ComposerAttachments.tsx +9 -4
  350. package/src/react/primitives/composer/ComposerQueue.test.tsx +72 -0
  351. package/src/react/primitives/composer/ComposerQueue.tsx +7 -4
  352. package/src/react/primitives/message/MessageAttachments.test.tsx +107 -37
  353. package/src/react/primitives/message/MessageAttachments.tsx +10 -7
  354. package/src/react/primitives/message/MessageGroupedParts.tsx +2 -2
  355. package/src/react/primitives/message/MessageParts.tsx +3 -3
  356. package/src/react/primitives/threadList/ThreadListItems.test.tsx +99 -0
  357. package/src/react/primitives/threadList/ThreadListItems.tsx +8 -5
  358. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  359. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +127 -1
  360. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  361. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +99 -35
  362. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  363. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +274 -0
  364. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +223 -152
  365. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  366. package/src/react/runtimes/RemoteThreadResource.ts +20 -16
  367. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +51 -2
  368. package/src/react/runtimes/assistant-transport/useLatestRef.ts +2 -2
  369. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +45 -0
  370. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +546 -5
  371. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +546 -103
  372. package/src/react/runtimes/cloud/auiV0.ts +5 -0
  373. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +103 -0
  374. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +237 -0
  375. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +90 -1
  376. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +33 -238
  377. package/src/react/runtimes/external-message-converter.ts +1 -0
  378. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  379. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  380. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  381. package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +216 -0
  382. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  383. package/src/react/runtimes/useRemoteThreadListRuntime.ts +57 -6
  384. package/src/react/types/MessagePartComponentTypes.ts +7 -3
  385. package/src/react/types/scopes/tools.ts +2 -0
  386. package/src/runtime/api/message-part-runtime.ts +3 -3
  387. package/src/runtime/api/message-runtime.test.ts +75 -0
  388. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  389. package/src/runtime/api/thread-list-runtime.ts +46 -12
  390. package/src/runtime/api/thread-runtime.test.ts +78 -0
  391. package/src/runtime/api/thread-runtime.ts +8 -1
  392. package/src/runtime/base/base-composer-runtime-core.ts +36 -8
  393. package/src/runtime/base/base-thread-runtime-core.test.ts +149 -4
  394. package/src/runtime/base/base-thread-runtime-core.ts +16 -8
  395. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  396. package/src/runtime/interfaces/thread-list-runtime-core.ts +25 -2
  397. package/src/runtime/interfaces/thread-runtime-core.ts +17 -1
  398. package/src/runtime/utils/auto-status.test.ts +57 -0
  399. package/src/runtime/utils/auto-status.ts +13 -1
  400. package/src/runtime/utils/external-message-conversion.test.ts +165 -0
  401. package/src/runtime/utils/external-message-conversion.ts +18 -2
  402. package/src/runtime/utils/message-repository.ts +29 -17
  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 +3 -11
  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 +18 -13
  410. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +56 -33
  411. package/src/runtimes/inert/InertThreadRuntimeCore.ts +1 -1
  412. package/src/runtimes/internal.ts +1 -0
  413. package/src/runtimes/local/local-thread-runtime-core.test.ts +29 -3
  414. package/src/runtimes/local/local-thread-runtime-core.ts +5 -1
  415. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +238 -0
  416. package/src/runtimes/remote-thread-list/remote-thread-state.ts +223 -44
  417. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  418. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  419. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  420. package/src/runtimes/remote-thread-list/title.ts +16 -0
  421. package/src/runtimes/remote-thread-list/types.ts +16 -5
  422. package/src/runtimes/tool-invocations/EDGE_CASES.md +110 -5
  423. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +750 -136
  424. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +144 -91
  425. package/src/store/clients/external-thread.ts +62 -11
  426. package/src/store/clients/model-context-client.ts +5 -1
  427. package/src/store/clients/single-thread-list.ts +1 -0
  428. package/src/store/clients/suggestions.test.ts +195 -1
  429. package/src/store/clients/suggestions.ts +45 -34
  430. package/src/store/index.ts +4 -0
  431. package/src/store/internal.ts +2 -0
  432. package/src/store/primitive-predicates.ts +35 -0
  433. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  434. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  435. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  436. package/src/store/runtime-clients/thread-list-runtime-client.ts +17 -2
  437. package/src/store/runtime-clients/thread-runtime-client.ts +58 -10
  438. package/src/store/runtime-clients/useSubscribable.ts +8 -2
  439. package/src/store/scopes/composer.ts +16 -4
  440. package/src/store/scopes/message.ts +14 -0
  441. package/src/store/scopes/part.ts +6 -3
  442. package/src/store/scopes/thread-list-item.ts +5 -2
  443. package/src/store/scopes/thread.ts +4 -0
  444. package/src/store/scopes/threads.ts +1 -0
  445. package/src/subscribable/subscribable.test.ts +50 -0
  446. package/src/subscribable/subscribable.ts +33 -21
  447. package/src/tests/MessageRepository.test.ts +116 -0
  448. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  449. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  450. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +158 -0
  451. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +78 -0
  452. package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +52 -0
  453. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  454. package/src/tests/attachment-adapters.test.ts +14 -0
  455. package/src/tests/base-composer-runtime-core.test.ts +39 -0
  456. package/src/tests/external-store-optimistic-id.test.ts +79 -0
  457. package/src/tests/external-store-repository-instance.test.ts +166 -0
  458. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  459. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +49 -34
  460. package/src/tests/external-thread-attachments.test.tsx +224 -1
  461. package/src/tests/external-thread-parity.test.tsx +23 -0
  462. package/src/tests/remote-thread-list-adapter-switch.test.ts +66 -1
  463. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  464. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  465. package/src/tests/thread-switch-events.test.tsx +475 -2
  466. package/src/tests/tools-scope-migration.test.tsx +28 -0
  467. package/src/types/attachment.ts +4 -0
  468. package/src/types/message.ts +50 -2
  469. package/src/utils/abortable-iterable.test.ts +213 -0
  470. package/src/utils/abortable-iterable.ts +93 -0
  471. package/src/utils/composite-context-provider.test.ts +25 -0
  472. package/src/utils/composite-context-provider.ts +23 -2
  473. package/src/utils/record.test.ts +48 -0
  474. package/src/utils/record.ts +11 -0
@@ -38,18 +38,27 @@ export class AssistantFrameProvider {
38
38
  private _providerUnsubscribes = new Map<symbol, Unsubscribe | undefined>();
39
39
  private _activeToolCalls = new Map<
40
40
  string,
41
- { abortController: AbortController; event: MessageEvent }
41
+ {
42
+ abortController: AbortController;
43
+ event: MessageEvent;
44
+ provider: ModelContextProvider | undefined;
45
+ }
42
46
  >();
43
47
  private _targetOrigin: string;
44
48
  private _strictRegistrations = 0;
45
49
  private _wildcardRegistrations = 0;
50
+ private _startupTimer: ReturnType<typeof setTimeout> | undefined;
51
+ private _disposed = false;
46
52
 
47
53
  private constructor(targetOrigin: string = getDefaultTargetOrigin()) {
48
54
  this._targetOrigin = targetOrigin;
49
55
  this.handleMessage = this.handleMessage.bind(this);
50
56
  window.addEventListener("message", this.handleMessage);
51
57
 
52
- setTimeout(() => this.broadcastUpdate(), 0);
58
+ this._startupTimer = setTimeout(() => {
59
+ this._startupTimer = undefined;
60
+ this.broadcastUpdate();
61
+ }, 0);
53
62
  }
54
63
 
55
64
  private static getInstance(targetOrigin?: string): AssistantFrameProvider {
@@ -102,7 +111,12 @@ export class AssistantFrameProvider {
102
111
  break;
103
112
 
104
113
  case "tool-call":
105
- this.handleToolCall(message, event);
114
+ void this.handleToolCall(message, event).catch((error: unknown) => {
115
+ console.error(
116
+ "[assistant-ui] AssistantFrame tool call failed.",
117
+ error,
118
+ );
119
+ });
106
120
  break;
107
121
 
108
122
  case "tool-cancel":
@@ -115,10 +129,15 @@ export class AssistantFrameProvider {
115
129
  message: Extract<FrameMessage, { type: "tool-call" }>,
116
130
  event: MessageEvent,
117
131
  ) {
118
- const tool = this.getModelContext().tools?.[message.toolName];
132
+ const resolvedTool = this.getTool(message.toolName);
133
+ const tool = resolvedTool?.tool;
119
134
  const abortController = new AbortController();
120
135
  this._activeToolCalls.get(message.id)?.abortController.abort();
121
- const activeCall = { abortController, event };
136
+ const activeCall = {
137
+ abortController,
138
+ event,
139
+ provider: resolvedTool?.provider,
140
+ };
122
141
  this._activeToolCalls.set(message.id, activeCall);
123
142
 
124
143
  let result: any;
@@ -147,11 +166,25 @@ export class AssistantFrameProvider {
147
166
  if (this._activeToolCalls.get(message.id) !== activeCall) return;
148
167
  this._activeToolCalls.delete(message.id);
149
168
 
150
- this.sendMessage(event, {
151
- type: "tool-result",
152
- id: message.id,
153
- ...(error !== undefined ? { error } : { result }),
154
- });
169
+ try {
170
+ this.sendMessage(event, {
171
+ type: "tool-result",
172
+ id: message.id,
173
+ ...(error !== undefined ? { error } : { result }),
174
+ });
175
+ } catch (sendError) {
176
+ if (error !== undefined) throw sendError;
177
+
178
+ console.error(
179
+ "[assistant-ui] AssistantFrame tool result could not be sent.",
180
+ sendError,
181
+ );
182
+ this.sendMessage(event, {
183
+ type: "tool-result",
184
+ id: message.id,
185
+ error: "Tool result could not be sent across the frame boundary",
186
+ });
187
+ }
155
188
  }
156
189
 
157
190
  private cancelToolCall(id: string) {
@@ -161,6 +194,37 @@ export class AssistantFrameProvider {
161
194
  activeCall.abortController.abort();
162
195
  }
163
196
 
197
+ private cancelToolCallsForProvider(provider: ModelContextProvider) {
198
+ const matchingCalls = Array.from(this._activeToolCalls).filter(
199
+ ([, activeCall]) => activeCall.provider === provider,
200
+ );
201
+ for (const [id, activeCall] of matchingCalls) {
202
+ this._activeToolCalls.delete(id);
203
+ activeCall.abortController.abort();
204
+ }
205
+
206
+ let sendFailed = false;
207
+ let sendError: unknown;
208
+ for (const [id, activeCall] of matchingCalls) {
209
+ try {
210
+ this.sendMessage(activeCall.event, {
211
+ type: "tool-result",
212
+ id,
213
+ error: "AssistantFrame tool provider has been removed",
214
+ });
215
+ } catch (error) {
216
+ if (sendFailed) {
217
+ console.error(error);
218
+ } else {
219
+ sendFailed = true;
220
+ sendError = error;
221
+ }
222
+ }
223
+ }
224
+
225
+ if (sendFailed) throw sendError;
226
+ }
227
+
164
228
  private sendMessage(event: MessageEvent, message: FrameMessage) {
165
229
  event.source?.postMessage(
166
230
  { channel: FRAME_MESSAGE_CHANNEL, message },
@@ -168,9 +232,26 @@ export class AssistantFrameProvider {
168
232
  );
169
233
  }
170
234
 
235
+ private getProviders() {
236
+ return Array.from(new Set(this._providers.values()));
237
+ }
238
+
239
+ private getTool(toolName: string) {
240
+ let resolved:
241
+ | { provider: ModelContextProvider; tool: Tool<any, any> }
242
+ | undefined;
243
+
244
+ for (const provider of this.getProviders()) {
245
+ const tool = provider.getModelContext().tools?.[toolName];
246
+ if (tool) resolved = { provider, tool };
247
+ }
248
+
249
+ return resolved;
250
+ }
251
+
171
252
  private getModelContext(): ModelContext {
172
- const contexts = Array.from(new Set(this._providers.values())).map((p) =>
173
- p.getModelContext(),
253
+ const contexts = this.getProviders().map((provider) =>
254
+ provider.getModelContext(),
174
255
  );
175
256
 
176
257
  return contexts.reduce(
@@ -187,6 +268,7 @@ export class AssistantFrameProvider {
187
268
  }
188
269
 
189
270
  private broadcastUpdate() {
271
+ if (this._disposed) return;
190
272
  if (window.parent && window.parent !== window) {
191
273
  const updateMessage: FrameMessage = {
192
274
  type: "model-context-update",
@@ -200,7 +282,14 @@ export class AssistantFrameProvider {
200
282
  }
201
283
  }
202
284
 
203
- private removeProvider(id: symbol, origin: string): Unsubscribe | undefined {
285
+ private removeProvider(
286
+ id: symbol,
287
+ origin: string,
288
+ ): {
289
+ unsubscribe: Unsubscribe | undefined;
290
+ removedProvider: ModelContextProvider | undefined;
291
+ } {
292
+ const provider = this._providers.get(id);
204
293
  this._providers.delete(id);
205
294
  const unsubscribe = this._providerUnsubscribes.get(id);
206
295
  this._providerUnsubscribes.delete(id);
@@ -219,7 +308,11 @@ export class AssistantFrameProvider {
219
308
  this._wildcardRegistrations > 0 ? "*" : getDefaultTargetOrigin();
220
309
  }
221
310
  }
222
- return unsubscribe;
311
+ const removedProvider =
312
+ provider && !this.getProviders().includes(provider)
313
+ ? provider
314
+ : undefined;
315
+ return { unsubscribe, removedProvider };
223
316
  }
224
317
 
225
318
  static addModelContextProvider(
@@ -246,8 +339,18 @@ export class AssistantFrameProvider {
246
339
 
247
340
  instance.broadcastUpdate();
248
341
  } catch (error) {
249
- const unsubscribe = instance.removeProvider(id, origin);
342
+ const { unsubscribe, removedProvider } = instance.removeProvider(
343
+ id,
344
+ origin,
345
+ );
250
346
  // Rollback failures must not replace the registration error.
347
+ try {
348
+ if (removedProvider) {
349
+ instance.cancelToolCallsForProvider(removedProvider);
350
+ }
351
+ } catch (cancelError) {
352
+ console.error(cancelError);
353
+ }
251
354
  try {
252
355
  unsubscribe?.();
253
356
  } catch (unsubscribeError) {
@@ -265,28 +368,43 @@ export class AssistantFrameProvider {
265
368
  return () => {
266
369
  if (released) return;
267
370
  released = true;
268
- const unsubscribe = instance.removeProvider(id, origin);
269
- let unsubscribeFailed = false;
270
- let unsubscribeError: unknown;
271
- try {
272
- unsubscribe?.();
273
- } catch (error) {
274
- unsubscribeFailed = true;
275
- unsubscribeError = error;
276
- }
277
- try {
278
- instance.broadcastUpdate();
279
- } catch (error) {
280
- if (!unsubscribeFailed) throw error;
281
- console.error(error);
371
+ const { unsubscribe, removedProvider } = instance.removeProvider(
372
+ id,
373
+ origin,
374
+ );
375
+ let cleanupFailed = false;
376
+ let cleanupError: unknown;
377
+ const runCleanup = (cleanup: () => void) => {
378
+ try {
379
+ cleanup();
380
+ } catch (error) {
381
+ if (cleanupFailed) {
382
+ console.error(error);
383
+ } else {
384
+ cleanupFailed = true;
385
+ cleanupError = error;
386
+ }
387
+ }
388
+ };
389
+
390
+ if (removedProvider) {
391
+ runCleanup(() => instance.cancelToolCallsForProvider(removedProvider));
282
392
  }
283
- if (unsubscribeFailed) throw unsubscribeError;
393
+ if (unsubscribe) runCleanup(unsubscribe);
394
+ runCleanup(() => instance.broadcastUpdate());
395
+
396
+ if (cleanupFailed) throw cleanupError;
284
397
  };
285
398
  }
286
399
 
287
400
  static dispose() {
288
401
  if (AssistantFrameProvider._instance) {
289
402
  const instance = AssistantFrameProvider._instance;
403
+ instance._disposed = true;
404
+ if (instance._startupTimer !== undefined) {
405
+ clearTimeout(instance._startupTimer);
406
+ instance._startupTimer = undefined;
407
+ }
290
408
  window.removeEventListener("message", instance.handleMessage);
291
409
 
292
410
  let cleanupFailed = false;
@@ -131,6 +131,34 @@ describe("shallowMergeInteractableState", () => {
131
131
  });
132
132
  });
133
133
 
134
+ it("uses the first valid patch for each array item id", () => {
135
+ expect(
136
+ shallowMergeInteractableState(
137
+ { tasks: [{ id: 1, title: "Original", done: false }] },
138
+ {
139
+ tasks: {
140
+ update: [
141
+ null,
142
+ { title: "Missing id" },
143
+ { id: "1", title: "String id" },
144
+ { id: 1, title: "First", done: true },
145
+ { id: 1, title: "Second" },
146
+ ],
147
+ },
148
+ },
149
+ ),
150
+ ).toEqual({ tasks: [{ id: 1, title: "First", done: true }] });
151
+ });
152
+
153
+ it("does not match NaN array item ids", () => {
154
+ expect(
155
+ shallowMergeInteractableState(
156
+ { tasks: [{ id: Number.NaN, title: "Original" }] },
157
+ { tasks: { update: [{ id: Number.NaN, title: "Patched" }] } },
158
+ ),
159
+ ).toEqual({ tasks: [{ id: Number.NaN, title: "Original" }] });
160
+ });
161
+
134
162
  it("keeps raw array replacement semantics", () => {
135
163
  expect(
136
164
  shallowMergeInteractableState(
@@ -94,12 +94,18 @@ function applyArrayUpdate(
94
94
 
95
95
  const patches = update.update;
96
96
  if (Array.isArray(patches) && patches.length > 0) {
97
+ const patchesById = new Map<string | number, Record<string, unknown>>();
98
+ for (const candidate of patches) {
99
+ const id = getArrayItemId(candidate);
100
+ if (id !== undefined && !Number.isNaN(id) && !patchesById.has(id)) {
101
+ patchesById.set(id, candidate as Record<string, unknown>);
102
+ }
103
+ }
104
+
97
105
  next = next.map((item) => {
98
106
  const id = getArrayItemId(item);
99
107
  if (id === undefined || !isRecord(item)) return item;
100
- const patch = patches.find(
101
- (candidate) => isRecord(candidate) && candidate.id === id,
102
- );
108
+ const patch = patchesById.get(id);
103
109
  return patch ? { ...item, ...patch } : item;
104
110
  });
105
111
  }
@@ -2,6 +2,18 @@ import { describe, expect, it, vi } from "vitest";
2
2
  import { ModelContextRegistry } from "./registry";
3
3
 
4
4
  describe("ModelContextRegistry", () => {
5
+ it.each(["__proto__", "constructor", "toString"])(
6
+ "retains a tool named %s",
7
+ (toolName) => {
8
+ const registry = new ModelContextRegistry();
9
+ registry.addTool({ toolName });
10
+
11
+ const tools = registry.getModelContext().tools!;
12
+ expect(Object.hasOwn(tools, toolName)).toBe(true);
13
+ expect(Object.keys(tools)).toEqual([toolName]);
14
+ },
15
+ );
16
+
5
17
  it("notifies every subscriber and rethrows when a registration subscriber throws", () => {
6
18
  const registry = new ModelContextRegistry();
7
19
  const error = new Error("subscriber failed");
@@ -13,6 +13,7 @@ import type {
13
13
  ModelContextRegistryInstructionHandle,
14
14
  ModelContextRegistryProviderHandle,
15
15
  } from "./registry-handles";
16
+ import { nullProtoRecord } from "../utils/record";
16
17
 
17
18
  export class ModelContextRegistry implements ModelContextProvider {
18
19
  private _tools = new Map<symbol, AssistantToolProps<any, any>>();
@@ -34,7 +35,7 @@ export class ModelContextRegistry implements ModelContextProvider {
34
35
  const system =
35
36
  instructions.length > 0 ? instructions.join("\n\n") : undefined;
36
37
 
37
- const tools: Record<string, Tool<any, any>> = {};
38
+ const tools = nullProtoRecord<Tool<any, any>>();
38
39
  for (const toolProps of this._tools.values()) {
39
40
  const { toolName, render, ...tool } = toolProps;
40
41
  tools[toolName] = tool;
@@ -54,7 +55,7 @@ export class ModelContextRegistry implements ModelContextProvider {
54
55
  }
55
56
 
56
57
  if (providerContexts.tools) {
57
- context.tools = { ...(context.tools || {}), ...providerContexts.tools };
58
+ context.tools = nullProtoRecord(context.tools, providerContexts.tools);
58
59
  }
59
60
 
60
61
  if (providerContexts.callSettings) {
@@ -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
+ };
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mergeModelContexts } from "./types";
3
+
4
+ const provider = (tools: Record<string, any>, priority?: number) =>
5
+ ({ getModelContext: () => ({ tools, priority }) }) as any;
6
+
7
+ describe("mergeModelContexts", () => {
8
+ it.each(["__proto__", "constructor", "toString"])(
9
+ "retains and prioritizes a tool named %s",
10
+ (name) => {
11
+ const highPriorityTool = { description: "high", parameters: {} };
12
+ const lowPriorityTool = { description: "low", parameters: {} };
13
+ const otherTool = { description: "other", parameters: {} };
14
+ const result = mergeModelContexts(
15
+ new Set([
16
+ provider({ [name]: highPriorityTool, ok: otherTool }, 1),
17
+ provider({ [name]: lowPriorityTool }, 0),
18
+ ]),
19
+ );
20
+
21
+ expect(Object.keys(result.tools ?? {})).toEqual([name, "ok"]);
22
+ expect(result.tools?.[name]?.description).toBe("high");
23
+ },
24
+ );
25
+ });
@@ -1,5 +1,6 @@
1
1
  import type { Unsubscribe } from "../types/unsubscribe";
2
2
  import type { Tool } from "assistant-stream";
3
+ import { nullProtoRecord } from "../utils/record";
3
4
 
4
5
  export type LanguageModelV1CallSettings = {
5
6
  maxTokens?: number;
@@ -68,7 +69,7 @@ export const mergeModelContexts = (
68
69
  .map((c) => c.getModelContext())
69
70
  .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
70
71
 
71
- const toolPriorities: Record<string, number> = {};
72
+ const toolPriorities = nullProtoRecord<number>();
72
73
 
73
74
  return configs.reduce((acc, config) => {
74
75
  const priority = config.priority ?? 0;
@@ -81,7 +82,9 @@ export const mergeModelContexts = (
81
82
  }
82
83
  if (config.tools) {
83
84
  for (const [name, tool] of Object.entries(config.tools)) {
84
- const existing = acc.tools?.[name];
85
+ const hasExisting =
86
+ acc.tools !== undefined && Object.hasOwn(acc.tools, name);
87
+ const existing = hasExisting ? acc.tools![name] : undefined;
85
88
  if (existing && existing !== tool) {
86
89
  const existingPriority = toolPriorities[name]!;
87
90
  if (existingPriority === priority) {
@@ -106,9 +109,11 @@ export const mergeModelContexts = (
106
109
  continue;
107
110
  }
108
111
 
109
- if (!acc.tools) acc.tools = {};
112
+ if (!acc.tools) acc.tools = nullProtoRecord();
110
113
  acc.tools[name] = stripOverwrite(tool);
111
- toolPriorities[name] ??= priority;
114
+ if (!Object.hasOwn(toolPriorities, name)) {
115
+ toolPriorities[name] = priority;
116
+ }
112
117
  }
113
118
  }
114
119
  if (config.config) {
@@ -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
  });