@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
@@ -14,7 +14,8 @@ import {
14
14
  type ReadonlyJSONValue,
15
15
  } from "assistant-stream/utils";
16
16
  import { isJSONValueEqual } from "../../utils/json/is-json-equal";
17
- import type { ThreadMessage } from "../../types/message";
17
+ import type { ThreadMessage, ToolCallMessagePart } from "../../types/message";
18
+ import { walkToolCallTree } from "../../runtime/utils/tool-call-tree";
18
19
 
19
20
  const TOOL_EXECUTION_ID = Symbol.for("assistant-stream.tool-execution-id");
20
21
 
@@ -54,6 +55,7 @@ type ToolCallEntry = {
54
55
  /** Active phase — chunks are flowing through `controller`. */
55
56
  controller: ToolCallStreamController;
56
57
  argsComplete: boolean;
58
+ clientOwned: boolean;
57
59
  }
58
60
  );
59
61
 
@@ -116,6 +118,9 @@ const getToolExecutionId = (value: object): symbol | undefined =>
116
118
  export class ToolInvocationTracker {
117
119
  private readonly _getTools: () => Record<string, Tool> | undefined;
118
120
  private readonly _callbacks: ToolInvocationTracker.Callbacks;
121
+ private readonly _isClientToolCall:
122
+ | ((toolCall: ToolCallMessagePart) => boolean | undefined)
123
+ | undefined;
119
124
 
120
125
  private readonly _entries = new Map<string, ToolCallEntry>();
121
126
  private readonly _humanInput = new Map<
@@ -127,6 +132,13 @@ export class ToolInvocationTracker {
127
132
  }
128
133
  >();
129
134
  private readonly _executing = new Set<symbol>();
135
+ /**
136
+ * Tool calls whose turn ended before they reached the executor. Held here
137
+ * rather than on the entry because an entry is rebuilt whenever a snapshot
138
+ * re-creates the call, and this is the one reason to skip that no later
139
+ * snapshot carries.
140
+ */
141
+ private readonly _discardedToolCallIds = new Set<string>();
130
142
  private readonly _settledResolvers: SettledResolver[] = [];
131
143
 
132
144
  private _statuses = new Map<string, ToolExecutionStatus>();
@@ -143,10 +155,12 @@ export class ToolInvocationTracker {
143
155
  /**
144
156
  * Set when the assistant-stream pipeline has died (errored out via
145
157
  * `.pipeTo(...).catch(...)`). The next `setState` re-initializes the
146
- * pipeline and demotes all active entries to restored so they survive
147
- * across the restart without re-firing `streamCall` (preserves the
148
- * "exactly once" contract). Capped at a single auto-restart per session
149
- * — repeated failures keep the tracker dead with a more visible error.
158
+ * pipeline and demotes each active entry that reached the executor to
159
+ * restored, so it survives the restart without re-firing `streamCall`.
160
+ * A restart is an execution boundary like `reset()`: an entry that had
161
+ * not reached it starts over and fires once there (F.4). Capped at a
162
+ * single auto-restart per session — repeated failures keep the tracker
163
+ * dead with a more visible error.
150
164
  */
151
165
  private _pipelineDead = false;
152
166
  private _pipelineRestartUsed = false;
@@ -154,9 +168,11 @@ export class ToolInvocationTracker {
154
168
  constructor(
155
169
  getTools: () => Record<string, Tool> | undefined,
156
170
  callbacks: ToolInvocationTracker.Callbacks,
171
+ isClientToolCall?: (toolCall: ToolCallMessagePart) => boolean | undefined,
157
172
  ) {
158
173
  this._getTools = getTools;
159
174
  this._callbacks = callbacks;
175
+ this._isClientToolCall = isClientToolCall;
160
176
 
161
177
  this._initPipeline();
162
178
  }
@@ -222,10 +238,10 @@ export class ToolInvocationTracker {
222
238
  */
223
239
  public setState(snapshot: ToolInvocationTracker.Snapshot): void {
224
240
  try {
225
- // Recover from a dead pipeline before processing anything. We demote
226
- // all active entries to "restored" so the rebuilt pipeline does not
227
- // re-fire `streamCall` for tool calls that already fired pre-death;
228
- // preserves the "exactly once per toolCallId" contract.
241
+ // Recover from a dead pipeline before processing anything. Entries
242
+ // that reached the executor are demoted to "restored" so the rebuilt
243
+ // pipeline does not re-fire `streamCall` for them; the rest start over
244
+ // across the boundary the restart opens (F.4).
229
245
  if (this._pipelineDead) {
230
246
  if (this._pipelineRestartUsed) {
231
247
  // Already retried once and failed again. Stay dead.
@@ -291,6 +307,7 @@ export class ToolInvocationTracker {
291
307
  try {
292
308
  this._pendingRestore = true;
293
309
  this._entries.clear();
310
+ this._discardedToolCallIds.clear();
294
311
  this._lastSnapshot = null;
295
312
  void this.abort();
296
313
  // Statuses are cleared synchronously: discarded executions may never
@@ -310,8 +327,13 @@ export class ToolInvocationTracker {
310
327
  /**
311
328
  * Abort any in-flight `execute()` invocations. Resolves once all of them
312
329
  * have settled (or immediately if none are running).
330
+ *
331
+ * `discardPending` additionally kills the calls that never reached the
332
+ * executor, for a caller ending the turn rather than interrupting it. The
333
+ * signal cannot reach those: they are waiting on the run to settle (A.10),
334
+ * and the settled snapshot arrives after this installs a fresh controller.
313
335
  */
314
- public abort(): Promise<void> {
336
+ public abort(options?: { discardPending?: boolean }): Promise<void> {
315
337
  try {
316
338
  this._humanInput.forEach(({ reject }) => {
317
339
  try {
@@ -323,6 +345,15 @@ export class ToolInvocationTracker {
323
345
  });
324
346
  this._humanInput.clear();
325
347
 
348
+ if (options?.discardPending) {
349
+ for (const [toolCallId, entry] of this._entries) {
350
+ if (!entry.controller) continue;
351
+ if (entry.argsComplete || entry.hasResult) continue;
352
+ this._discardedToolCallIds.add(toolCallId);
353
+ entry.skipExecute = true;
354
+ }
355
+ }
356
+
326
357
  this._ac.abort();
327
358
  this._ac = new AbortController();
328
359
 
@@ -473,6 +504,8 @@ export class ToolInvocationTracker {
473
504
  if (entry.skipExecute) return;
474
505
 
475
506
  this._executing.add(executionId!);
507
+ // execute can park human() before onExecutionStart; preserve this execution's interrupt.
508
+ if (this._humanInput.get(toolCallId)?.executionId === executionId) return;
476
509
  this._setStatus(toolCallId, { type: "executing" });
477
510
  }
478
511
 
@@ -577,31 +610,40 @@ export class ToolInvocationTracker {
577
610
 
578
611
  // ──────────────── internal: snapshot processing ────────────────
579
612
 
580
- private _hasExecutableTool(toolName: string): boolean {
581
- const tool = this._getTools()?.[toolName];
582
- return tool?.execute !== undefined || tool?.streamCall !== undefined;
613
+ private _warnProviderOwnedSkip(toolName: string, toolCallId: string): void {
614
+ if (process.env.NODE_ENV === "production") return;
615
+ if (this._getTools()?.[toolName]?.execute === undefined) return;
616
+ console.warn(
617
+ "[ToolInvocationTracker] the runtime reports this tool call as provider-owned, so the registered execute is skipped; the provider has to hand the call to the client for it to run (see EDGE_CASES.md A.9)",
618
+ { toolCallId, toolName },
619
+ );
583
620
  }
584
621
 
622
+ /**
623
+ * Closing the args stream hands the call to the client executor, so it may
624
+ * only happen once the provider can no longer speak about that call. The run
625
+ * ending is the only universal signal for that; an adapter that reports the
626
+ * call as client-owned has said it earlier, per call.
627
+ */
585
628
  private _shouldCloseArgsStream({
586
- toolName,
587
629
  argsText,
588
630
  hasResult,
631
+ clientOwned,
589
632
  }: {
590
- toolName: string;
591
633
  argsText: string;
592
634
  hasResult: boolean;
635
+ clientOwned: boolean;
593
636
  }): boolean {
594
637
  if (hasResult) return true;
595
- if (!this._hasExecutableTool(toolName)) {
596
- return !this._isRunning && isArgsTextComplete(argsText);
597
- }
598
- return isArgsTextComplete(argsText);
638
+ if (!isArgsTextComplete(argsText)) return false;
639
+ return clientOwned || !this._isRunning;
599
640
  }
600
641
 
601
642
  private _startActiveEntry(
602
643
  toolCallId: string,
603
644
  toolName: string,
604
645
  skipExecute: boolean,
646
+ clientOwned: boolean,
605
647
  ): ToolCallEntry {
606
648
  const toolCallController = this._controller.addToolCallPart({
607
649
  toolName,
@@ -614,6 +656,7 @@ export class ToolInvocationTracker {
614
656
  hasResult: false,
615
657
  skipExecute,
616
658
  argsComplete: false,
659
+ clientOwned,
617
660
  };
618
661
  this._entries.set(toolCallId, entry);
619
662
  return entry;
@@ -629,6 +672,16 @@ export class ToolInvocationTracker {
629
672
  private _demoteEntriesToRestored(): void {
630
673
  for (const [toolCallId, entry] of this._entries) {
631
674
  if (!entry.controller) continue;
675
+ if (!entry.argsComplete && !entry.hasResult) {
676
+ // The call never reached the executor. A restored entry is promoted
677
+ // only when its signature changes, and a call waiting on the run to
678
+ // settle already holds its final args, so demoting it would strand it
679
+ // unexecuted. Dropping it lets the next snapshot start it over; a call
680
+ // whose turn was discarded is held by `_discardedToolCallIds`, not by
681
+ // the entry, so starting over does not revive it.
682
+ this._entries.delete(toolCallId);
683
+ continue;
684
+ }
632
685
  this._entries.set(toolCallId, {
633
686
  toolName: entry.toolName,
634
687
  argsText: entry.argsText,
@@ -683,9 +736,9 @@ export class ToolInvocationTracker {
683
736
  isEquivalentCompleteArgsText(entry.argsText, content.argsText)
684
737
  ) {
685
738
  const shouldClose = this._shouldCloseArgsStream({
686
- toolName: content.toolName,
687
739
  argsText: content.argsText,
688
740
  hasResult,
741
+ clientOwned: entry.clientOwned,
689
742
  });
690
743
  if (shouldClose) entry.controller.argsText.close();
691
744
  entry.argsText = content.argsText;
@@ -717,9 +770,9 @@ export class ToolInvocationTracker {
717
770
  const delta = content.argsText.slice(entry.argsText.length);
718
771
  entry.controller.argsText.append(delta);
719
772
  const shouldClose = this._shouldCloseArgsStream({
720
- toolName: content.toolName,
721
773
  argsText: content.argsText,
722
774
  hasResult,
775
+ clientOwned: entry.clientOwned,
723
776
  });
724
777
  if (shouldClose) entry.controller.argsText.close();
725
778
  entry.argsText = content.argsText;
@@ -732,9 +785,9 @@ export class ToolInvocationTracker {
732
785
  // gates on the streamed content; a divergent snapshot (A.2) can be
733
786
  // complete while the controller still holds an incomplete stale prefix.
734
787
  const shouldClose = this._shouldCloseArgsStream({
735
- toolName: content.toolName,
736
788
  argsText: entry.argsText,
737
789
  hasResult,
790
+ clientOwned: entry.clientOwned,
738
791
  });
739
792
  if (shouldClose) {
740
793
  entry.controller.argsText.close();
@@ -746,81 +799,81 @@ export class ToolInvocationTracker {
746
799
  private _processMessages(messages: readonly ThreadMessage[]): void {
747
800
  const isRestore = this._pendingRestore;
748
801
 
749
- for (const message of messages) {
750
- if (!message || !Array.isArray((message as ThreadMessage).content)) {
802
+ for (const { part: content } of walkToolCallTree(messages)) {
803
+ const existing = this._entries.get(content.toolCallId);
804
+
805
+ if (isRestore) {
806
+ // Don't overwrite an already-active entry (e.g. live tool-call
807
+ // observed before this restore snapshot landed). Restore can
808
+ // only seed entries the runtime has never seen.
809
+ if (!existing?.controller) {
810
+ this._entries.set(content.toolCallId, {
811
+ toolName: content.toolName,
812
+ argsText: content.argsText,
813
+ hasResult: content.result !== undefined,
814
+ });
815
+ }
751
816
  continue;
752
817
  }
753
- for (const content of message.content as readonly ThreadMessage["content"][number][]) {
754
- if (!content || content.type !== "tool-call") continue;
755
-
756
- const existing = this._entries.get(content.toolCallId);
757
-
758
- if (isRestore) {
759
- // Don't overwrite an already-active entry (e.g. live tool-call
760
- // observed before this restore snapshot landed). Restore can
761
- // only seed entries the runtime has never seen.
762
- if (!existing?.controller) {
763
- this._entries.set(content.toolCallId, {
764
- toolName: content.toolName,
765
- argsText: content.argsText,
766
- hasResult: content.result !== undefined,
767
- });
768
- }
769
- if (content.messages) this._processMessages(content.messages);
770
- continue;
771
- }
772
818
 
773
- // Live snapshot.
774
- let entry = existing;
775
-
776
- if (entry && !entry.controller) {
777
- // Restored entry observed in a live snapshot. Promote if its
778
- // signature has changed; otherwise treat as still-historical.
779
- const signatureChanged =
780
- content.argsText !== entry.argsText ||
781
- (content.result !== undefined) !== entry.hasResult;
782
- if (!signatureChanged) {
783
- if (content.messages) this._processMessages(content.messages);
784
- continue;
785
- }
786
- this._entries.delete(content.toolCallId);
787
- entry = undefined;
788
- }
819
+ // Live snapshot.
820
+ let entry = existing;
821
+
822
+ // A discarded id is remembered only until the call is answered, which
823
+ // bounds the set to the open calls of a discarded turn.
824
+ if (content.result !== undefined)
825
+ this._discardedToolCallIds.delete(content.toolCallId);
826
+
827
+ if (entry && !entry.controller) {
828
+ // Restored entry observed in a live snapshot. Promote if its
829
+ // signature has changed; otherwise treat as still-historical.
830
+ const signatureChanged =
831
+ content.argsText !== entry.argsText ||
832
+ (content.result !== undefined) !== entry.hasResult;
833
+ if (!signatureChanged) continue;
834
+ this._entries.delete(content.toolCallId);
835
+ entry = undefined;
836
+ }
789
837
 
790
- if (!entry) {
791
- entry = this._startActiveEntry(
792
- content.toolCallId,
793
- content.toolName,
794
- content.result !== undefined,
795
- );
796
- }
838
+ if (!entry) {
839
+ const ownership = this._isClientToolCall?.(content);
840
+ const providerOwned =
841
+ content.result === undefined && ownership === false;
842
+ if (providerOwned)
843
+ this._warnProviderOwnedSkip(content.toolName, content.toolCallId);
844
+ entry = this._startActiveEntry(
845
+ content.toolCallId,
846
+ content.toolName,
847
+ content.result !== undefined ||
848
+ providerOwned ||
849
+ this._discardedToolCallIds.has(content.toolCallId),
850
+ ownership === true,
851
+ );
852
+ }
797
853
 
798
- if (content.approval !== undefined) entry.skipExecute = true;
799
-
800
- this._processArgsText(entry, content);
801
-
802
- if (content.result !== undefined && !entry.hasResult) {
803
- // `entry` is in active phase from this point — either just
804
- // created by `_startActiveEntry`, or pre-existing with a live
805
- // controller. Narrow once instead of asserting at every use.
806
- const { controller: activeController } = entry;
807
- if (!activeController) continue;
808
- entry.hasResult = true;
809
- entry.argsComplete = true;
810
- activeController.setResponse(
811
- new ToolResponse({
812
- result: content.result as ReadonlyJSONValue,
813
- artifact: content.artifact as ReadonlyJSONValue | undefined,
814
- isError: content.isError,
815
- ...(content.modelContent !== undefined
816
- ? { modelContent: content.modelContent }
817
- : {}),
818
- }),
819
- );
820
- activeController.close();
821
- }
854
+ if (content.approval !== undefined) entry.skipExecute = true;
822
855
 
823
- if (content.messages) this._processMessages(content.messages);
856
+ this._processArgsText(entry, content);
857
+
858
+ if (content.result !== undefined && !entry.hasResult) {
859
+ // `entry` is in active phase from this point — either just
860
+ // created by `_startActiveEntry`, or pre-existing with a live
861
+ // controller. Narrow once instead of asserting at every use.
862
+ const { controller: activeController } = entry;
863
+ if (!activeController) continue;
864
+ entry.hasResult = true;
865
+ entry.argsComplete = true;
866
+ activeController.setResponse(
867
+ new ToolResponse({
868
+ result: content.result as ReadonlyJSONValue,
869
+ artifact: content.artifact as ReadonlyJSONValue | undefined,
870
+ isError: content.isError,
871
+ ...(content.modelContent !== undefined
872
+ ? { modelContent: content.modelContent }
873
+ : {}),
874
+ }),
875
+ );
876
+ activeController.close();
824
877
  }
825
878
  }
826
879
  }
@@ -21,7 +21,10 @@ import type {
21
21
  CreateAttachment,
22
22
  PendingAttachment,
23
23
  } from "../../types/attachment";
24
- import { isCreateAttachment } from "../../types/attachment";
24
+ import {
25
+ isAttachmentComplete,
26
+ isCreateAttachment,
27
+ } from "../../types/attachment";
25
28
  import type {
26
29
  AddToolResultOptions,
27
30
  RespondToToolApprovalOptions,
@@ -49,6 +52,7 @@ import {
49
52
  drainAttachmentAdd,
50
53
  } from "../../runtime/utils/attachment-add-operations";
51
54
  import { toMessagePartStatus } from "../../utils/normalizePartStatus";
55
+ import { generateId } from "../../utils/id";
52
56
  import { ModelContext } from "./model-context-client";
53
57
  import { ThreadSuggestions } from "./suggestions";
54
58
  import { Tools } from "../../react/client/Tools";
@@ -118,7 +122,9 @@ export type ExternalThreadProps = {
118
122
  /** Branch adapter for runtimes that track sibling variants of messages. */
119
123
  branches?: ExternalThreadBranchAdapter;
120
124
  /** Callback for tool approval decisions. Absent: responding to an approval throws a capability error. */
121
- onRespondToToolApproval?: (options: RespondToToolApprovalOptions) => void;
125
+ onRespondToToolApproval?: (
126
+ options: RespondToToolApprovalOptions,
127
+ ) => void | Promise<void>;
122
128
  };
123
129
 
124
130
  type MessageClientProps = {
@@ -130,7 +136,7 @@ type MessageClientProps = {
130
136
  queue?: ExternalThreadQueueAdapter | undefined;
131
137
  branches?: ExternalThreadBranchAdapter | undefined;
132
138
  onRespondToToolApproval?:
133
- | ((options: RespondToToolApprovalOptions) => void)
139
+ | ((options: RespondToToolApprovalOptions) => void | Promise<void>)
134
140
  | undefined;
135
141
  onAddToolResult?: ((options: AddToolResultOptions) => void) | undefined;
136
142
  onResumeToolCall?: ((options: ResumeToolCallOptions) => void) | undefined;
@@ -313,7 +319,7 @@ type PartResourceProps = {
313
319
  status: ToolCallMessagePartStatus;
314
320
  messageId: string;
315
321
  onRespondToToolApproval?:
316
- | ((options: RespondToToolApprovalOptions) => void)
322
+ | ((options: RespondToToolApprovalOptions) => void | Promise<void>)
317
323
  | undefined;
318
324
  onAddToolResult?: ((options: AddToolResultOptions) => void) | undefined;
319
325
  onResumeToolCall?: ((options: ResumeToolCallOptions) => void) | undefined;
@@ -382,9 +388,12 @@ const usePartResource = ({
382
388
  )
383
389
  throw new Error("Tool call has no pending approval");
384
390
 
385
- onRespondToToolApproval(
386
- resolveToolApprovalResponse(part.approval, response),
387
- );
391
+ const options = resolveToolApprovalResponse(part.approval, response);
392
+ try {
393
+ return Promise.resolve(onRespondToToolApproval(options));
394
+ } catch (error) {
395
+ return Promise.reject(error);
396
+ }
388
397
  },
389
398
  };
390
399
  };
@@ -457,6 +466,35 @@ const useLiveState = <T>(initial: T) => {
457
466
  return [state, set, ref] as const;
458
467
  };
459
468
 
469
+ // A try/catch inside the resource makes the React Compiler bail out of the
470
+ // whole hook, so the adapter call and its failure status live at module scope.
471
+ const removeAttachmentThroughAdapter = async (
472
+ attachment: Attachment,
473
+ attachmentAdapter: AttachmentAdapter | undefined,
474
+ setAttachments: (
475
+ next:
476
+ | readonly Attachment[]
477
+ | ((prev: readonly Attachment[]) => readonly Attachment[]),
478
+ ) => void,
479
+ ) => {
480
+ try {
481
+ await attachmentAdapter?.remove(attachment);
482
+ } catch (error) {
483
+ const message = error instanceof Error ? error.message : String(error);
484
+ setAttachments((prev) =>
485
+ prev.map((candidate) =>
486
+ candidate.id === attachment.id && !isAttachmentComplete(candidate)
487
+ ? {
488
+ ...candidate,
489
+ status: { type: "incomplete", reason: "error", message },
490
+ }
491
+ : candidate,
492
+ ),
493
+ );
494
+ throw error;
495
+ }
496
+ };
497
+
460
498
  // Composer Client - minimal implementation
461
499
  const useComposerClientResource = ({
462
500
  type,
@@ -510,7 +548,13 @@ const useComposerClientResource = ({
510
548
  attachment,
511
549
  onRemove: async () => {
512
550
  attachmentAddOperations.cancel(attachment.id);
513
- await attachmentAdapter?.remove(attachment);
551
+ if (!isAttachmentComplete(attachment)) {
552
+ await removeAttachmentThroughAdapter(
553
+ attachment,
554
+ attachmentAdapter,
555
+ setAttachments,
556
+ );
557
+ }
514
558
  setAttachments((prev) =>
515
559
  prev.filter((a) => a.id !== attachment.id),
516
560
  );
@@ -632,7 +676,7 @@ const useComposerClientResource = ({
632
676
  }
633
677
  } else if (!isCreateAttachment(fileOrAttachment)) {
634
678
  const newAttachment: Attachment = {
635
- id: Math.random().toString(36).substring(7),
679
+ id: generateId(),
636
680
  type: "file",
637
681
  name: fileOrAttachment.name,
638
682
  contentType: fileOrAttachment.type,
@@ -643,7 +687,7 @@ const useComposerClientResource = ({
643
687
  setAttachments((prev) => [...prev, newAttachment]);
644
688
  } else {
645
689
  const newAttachment: Attachment = {
646
- id: fileOrAttachment.id ?? Math.random().toString(36).substring(7),
690
+ id: fileOrAttachment.id ?? generateId(),
647
691
  type: fileOrAttachment.type ?? "document",
648
692
  name: fileOrAttachment.name,
649
693
  contentType: fileOrAttachment.contentType,
@@ -899,7 +943,11 @@ const useExternalThread = ({
899
943
  : undefined;
900
944
  };
901
945
 
946
+ // `messages` changes identity on every streamed token, and this body fans out
947
+ // to one client per message, so pruning during render would re-run the whole
948
+ // fan-out per token. The effect bails out on an unchanged map instead.
902
949
  useEffect(() => {
950
+ // eslint-disable-next-line react-hooks/set-state-in-effect
903
951
  setSubmittedFeedback((prev) => {
904
952
  const live = Object.entries(prev).filter(([id, entry]) => {
905
953
  const msg = messages.find((m) => m.id === id);
@@ -1107,7 +1155,10 @@ const useExternalThread = ({
1107
1155
  }
1108
1156
  : {
1109
1157
  createdAt: message.createdAt ?? new Date(),
1110
- parentId: message.parentId ?? messages.at(-1)?.id ?? null,
1158
+ parentId:
1159
+ message.parentId === undefined
1160
+ ? (messages.at(-1)?.id ?? null)
1161
+ : message.parentId,
1111
1162
  sourceId: message.sourceId ?? null,
1112
1163
  role: message.role ?? "user",
1113
1164
  content: message.content,
@@ -3,6 +3,7 @@ import { resource } from "@assistant-ui/tap";
3
3
  import type { ClientOutput } from "@assistant-ui/store";
4
4
  import { CompositeContextProvider } from "../../utils/composite-context-provider";
5
5
  import type { ModelContextState } from "../scopes/model-context";
6
+ import { shallowEqual } from "@assistant-ui/store/internal";
6
7
 
7
8
  const EMPTY_TOOL_NAMES: readonly string[] = [];
8
9
 
@@ -12,7 +13,7 @@ const INITIAL_STATE: ModelContextState = {
12
13
  };
13
14
 
14
15
  const toolNamesEqual = (a: readonly string[], b: readonly string[]): boolean =>
15
- a === b || (a.length === b.length && a.every((v, i) => v === b[i]));
16
+ a === b || shallowEqual(a, b);
16
17
 
17
18
  const deriveState = (
18
19
  composite: CompositeContextProvider,
@@ -36,6 +37,9 @@ const useModelContext = (): ClientOutput<"modelContext"> => {
36
37
  );
37
38
 
38
39
  useEffect(() => {
40
+ // The composite is an external store; this catches the snapshot up for
41
+ // providers registered between render and subscribe.
42
+ // eslint-disable-next-line react-hooks/set-state-in-effect
39
43
  setState((prev) => deriveState(composite, prev));
40
44
  return composite.subscribe(() => {
41
45
  setState((prev) => deriveState(composite, prev));
@@ -59,6 +59,7 @@ const useSingleThreadList = ({
59
59
  mainThreadId: THREAD_ID,
60
60
  newThreadId: null,
61
61
  isLoading: false,
62
+ loadError: undefined,
62
63
  isLoadingMore: false,
63
64
  hasMore: false,
64
65
  threadIds: [THREAD_ID],