@assistant-ui/core 0.3.15 → 0.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +2 -2
  3. package/dist/index.js.map +1 -1
  4. package/dist/internal.d.ts +8 -2
  5. package/dist/internal.js +9 -3
  6. package/dist/model-context/frame/host.d.ts.map +1 -1
  7. package/dist/model-context/frame/host.js +3 -2
  8. package/dist/model-context/frame/host.js.map +1 -1
  9. package/dist/model-context/frame/provider.d.ts +5 -0
  10. package/dist/model-context/frame/provider.d.ts.map +1 -1
  11. package/dist/model-context/frame/provider.js +141 -28
  12. package/dist/model-context/frame/provider.js.map +1 -1
  13. package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
  14. package/dist/model-context/interactable-composer-metadata.js +1 -1
  15. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  16. package/dist/model-context/registry.d.ts +2 -2
  17. package/dist/model-context/registry.d.ts.map +1 -1
  18. package/dist/model-context/registry.js +9 -28
  19. package/dist/model-context/registry.js.map +1 -1
  20. package/dist/model-context/tool-call-text.d.ts +26 -0
  21. package/dist/model-context/tool-call-text.d.ts.map +1 -0
  22. package/dist/model-context/tool-call-text.js +18 -0
  23. package/dist/model-context/tool-call-text.js.map +1 -0
  24. package/dist/react/RuntimeAdapter.js +1 -1
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  26. package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -1
  27. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  28. package/dist/react/client/AdaptedRemoteThread.js +1 -1
  29. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  30. package/dist/react/client/InMemoryThreadList.js +25 -21
  31. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  32. package/dist/react/client/Interactables.d.ts.map +1 -1
  33. package/dist/react/client/Interactables.js +60 -178
  34. package/dist/react/client/Interactables.js.map +1 -1
  35. package/dist/react/client/RemoteThreadList.d.ts +15 -0
  36. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  37. package/dist/react/client/RemoteThreadList.js +263 -194
  38. package/dist/react/client/RemoteThreadList.js.map +1 -1
  39. package/dist/react/client/Tools.d.ts.map +1 -1
  40. package/dist/react/client/Tools.js +6 -2
  41. package/dist/react/client/Tools.js.map +1 -1
  42. package/dist/react/index.d.ts +4 -2
  43. package/dist/react/index.js +2 -1
  44. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  45. package/dist/react/interactables-legacy/Interactables.js +60 -178
  46. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  47. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
  48. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
  49. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
  50. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
  51. package/dist/react/model-context/toolbox.d.ts +2 -14
  52. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  53. package/dist/react/model-context/toolbox.js +1 -13
  54. package/dist/react/model-context/toolbox.js.map +1 -1
  55. package/dist/react/model-context/useAssistantContext.js +37 -7
  56. package/dist/react/model-context/useAssistantContext.js.map +1 -1
  57. package/dist/react/model-context/useAuiToolOverrides.js +31 -5
  58. package/dist/react/model-context/useAuiToolOverrides.js.map +1 -1
  59. package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
  60. package/dist/react/model-context/useInlineRender.js +10 -36
  61. package/dist/react/model-context/useInlineRender.js.map +1 -1
  62. package/dist/react/primitive-hooks/useMessageError.d.ts.map +1 -1
  63. package/dist/react/primitive-hooks/useMessageError.js +2 -8
  64. package/dist/react/primitive-hooks/useMessageError.js.map +1 -1
  65. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
  66. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -4
  67. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  68. package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  69. package/dist/react/primitives/composer/ComposerAttachments.js +4 -3
  70. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  71. package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
  72. package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
  73. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  74. package/dist/react/primitives/composer/ComposerQueue.js +4 -3
  75. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  76. package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
  77. package/dist/react/primitives/message/MessageAttachments.js +7 -6
  78. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  79. package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
  80. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  81. package/dist/react/primitives/message/MessageParts.js +3 -3
  82. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  83. package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  84. package/dist/react/primitives/threadList/ThreadListItems.js +5 -4
  85. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  86. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +7 -5
  87. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  88. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +44 -22
  89. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +7 -6
  91. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  92. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +102 -117
  93. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  94. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -6
  95. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  96. package/dist/react/runtimes/RemoteThreadResource.js +3 -3
  97. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  98. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
  99. package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
  100. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
  101. package/dist/react/runtimes/assistant-transport/useLatestRef.js +2 -2
  102. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -1
  103. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
  104. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  105. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +43 -85
  106. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  107. package/dist/react/runtimes/cloud/auiV0.d.ts +5 -1
  108. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  109. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  110. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +29 -0
  111. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -0
  112. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +159 -0
  113. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -0
  114. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
  115. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
  116. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
  117. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
  118. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +1 -9
  119. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  120. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +16 -153
  121. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  122. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
  123. package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
  124. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  125. package/dist/react/runtimes/external-message-converter.d.ts +6 -25
  126. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  127. package/dist/react/runtimes/external-message-converter.js +33 -233
  128. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  129. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  130. package/dist/react/runtimes/useRemoteThreadListRuntime.js +14 -6
  131. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  132. package/dist/react/types/MessagePartComponentTypes.d.ts +7 -3
  133. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  134. package/dist/react/types/scopes/tools.d.ts +2 -0
  135. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  136. package/dist/runtime/api/composer-runtime.d.ts +1 -1
  137. package/dist/runtime/api/composer-runtime.js.map +1 -1
  138. package/dist/runtime/api/message-part-runtime.d.ts +2 -2
  139. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  140. package/dist/runtime/api/message-part-runtime.js +1 -1
  141. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  142. package/dist/runtime/api/thread-list-runtime.d.ts +9 -1
  143. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  144. package/dist/runtime/api/thread-list-runtime.js +5 -0
  145. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  146. package/dist/runtime/api/thread-runtime.d.ts +2 -3
  147. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  148. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  149. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  150. package/dist/runtime/base/base-composer-runtime-core.js +14 -29
  151. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  152. package/dist/runtime/base/base-thread-runtime-core.d.ts +4 -6
  153. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  154. package/dist/runtime/base/base-thread-runtime-core.js +5 -11
  155. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  156. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  157. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  158. package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
  159. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  160. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
  161. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +14 -2
  162. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/interfaces/thread-runtime-core.d.ts +16 -2
  164. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  165. package/dist/runtime/internal.d.ts +2 -1
  166. package/dist/runtime/internal.js +2 -1
  167. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
  168. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  169. package/dist/runtime/queue/message-queue.js +1 -1
  170. package/dist/runtime/queue/message-queue.js.map +1 -1
  171. package/dist/runtime/queue/queue-item.d.ts +12 -0
  172. package/dist/runtime/queue/queue-item.d.ts.map +1 -0
  173. package/dist/runtime/queue/queue-item.js +6 -0
  174. package/dist/runtime/queue/queue-item.js.map +1 -0
  175. package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
  176. package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
  177. package/dist/runtime/utils/abortable-thread-load.js +35 -0
  178. package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
  179. package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
  180. package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
  181. package/dist/runtime/utils/attachment-add-operations.js +43 -0
  182. package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
  183. package/dist/runtime/utils/auto-status.d.ts +7 -2
  184. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  185. package/dist/runtime/utils/auto-status.js +13 -3
  186. package/dist/runtime/utils/auto-status.js.map +1 -1
  187. package/dist/runtime/utils/external-message-conversion.d.ts +48 -0
  188. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
  189. package/dist/runtime/utils/external-message-conversion.js +222 -0
  190. package/dist/runtime/utils/external-message-conversion.js.map +1 -0
  191. package/dist/runtime/utils/external-store-message.d.ts +2 -1
  192. package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
  193. package/dist/runtime/utils/external-store-message.js +2 -1
  194. package/dist/runtime/utils/external-store-message.js.map +1 -1
  195. package/dist/runtime/utils/message-repository-session.d.ts +30 -0
  196. package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
  197. package/dist/runtime/utils/message-repository-session.js +130 -0
  198. package/dist/runtime/utils/message-repository-session.js.map +1 -0
  199. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  200. package/dist/runtime/utils/message-repository.js +3 -4
  201. package/dist/runtime/utils/message-repository.js.map +1 -1
  202. package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
  203. package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
  204. package/dist/runtime/utils/pending-tool-calls.js +22 -0
  205. package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
  206. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +6 -3
  207. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
  208. package/dist/runtime/utils/resolveToolApprovalResponse.js +12 -3
  209. package/dist/runtime/utils/resolveToolApprovalResponse.js.map +1 -1
  210. package/dist/runtime/utils/thread-message-like.d.ts +3 -10
  211. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  212. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  213. package/dist/runtime/utils/tool-call-tree.d.ts +39 -0
  214. package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -0
  215. package/dist/runtime/utils/tool-call-tree.js +85 -0
  216. package/dist/runtime/utils/tool-call-tree.js.map +1 -0
  217. package/dist/runtimes/external-store/external-store-adapter.d.ts +29 -2
  218. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  219. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
  220. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  221. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +3 -10
  222. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  223. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +21 -2
  224. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  225. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +154 -46
  226. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  227. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +1 -1
  228. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
  229. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
  230. package/dist/runtimes/local/local-thread-runtime-core.d.ts +4 -2
  231. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  232. package/dist/runtimes/local/local-thread-runtime-core.js +23 -3
  233. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  234. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
  235. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  236. package/dist/runtimes/remote-thread-list/remote-thread-state.js +107 -14
  237. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  238. package/dist/runtimes/remote-thread-list/title.d.ts +9 -0
  239. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -0
  240. package/dist/runtimes/remote-thread-list/title.js +6 -0
  241. package/dist/runtimes/remote-thread-list/title.js.map +1 -0
  242. package/dist/runtimes/remote-thread-list/types.d.ts +6 -5
  243. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  244. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +31 -8
  245. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  246. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +98 -59
  247. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  248. package/dist/store/clients/external-thread.d.ts +1 -1
  249. package/dist/store/clients/external-thread.d.ts.map +1 -1
  250. package/dist/store/clients/external-thread.js +85 -103
  251. package/dist/store/clients/external-thread.js.map +1 -1
  252. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  253. package/dist/store/clients/single-thread-list.js +2 -2
  254. package/dist/store/clients/single-thread-list.js.map +1 -1
  255. package/dist/store/clients/suggestions.d.ts.map +1 -1
  256. package/dist/store/clients/suggestions.js +33 -12
  257. package/dist/store/clients/suggestions.js.map +1 -1
  258. package/dist/store/clients/thread-message-client.js +1 -1
  259. package/dist/store/clients/thread-selection-events.js +1 -1
  260. package/dist/store/index.d.ts +6 -3
  261. package/dist/store/index.js +2 -1
  262. package/dist/store/internal.d.ts +2 -2
  263. package/dist/store/internal.js +2 -2
  264. package/dist/store/primitive-predicates.d.ts +4 -1
  265. package/dist/store/primitive-predicates.d.ts.map +1 -1
  266. package/dist/store/primitive-predicates.js +9 -1
  267. package/dist/store/primitive-predicates.js.map +1 -1
  268. package/dist/store/runtime-clients/composer-runtime-client.js +1 -1
  269. package/dist/store/runtime-clients/message-runtime-client.js +1 -1
  270. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  271. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  272. package/dist/store/runtime-clients/thread-list-runtime-client.js +124 -105
  273. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  274. package/dist/store/runtime-clients/thread-runtime-client.js +1 -1
  275. package/dist/store/runtime-clients/useSubscribable.d.ts +3 -1
  276. package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
  277. package/dist/store/runtime-clients/useSubscribable.js +1 -1
  278. package/dist/store/runtime-clients/useSubscribable.js.map +1 -1
  279. package/dist/store/scope-registration.d.ts +1 -1
  280. package/dist/store/scopes/composer.d.ts +2 -1
  281. package/dist/store/scopes/composer.d.ts.map +1 -1
  282. package/dist/store/scopes/message.d.ts +1 -1
  283. package/dist/store/scopes/part.d.ts +6 -3
  284. package/dist/store/scopes/part.d.ts.map +1 -1
  285. package/dist/store/scopes/queue-item.d.ts +2 -9
  286. package/dist/store/scopes/queue-item.d.ts.map +1 -1
  287. package/dist/store/scopes/queue-item.js +0 -6
  288. package/dist/subscribable/subscribable.d.ts +8 -1
  289. package/dist/subscribable/subscribable.d.ts.map +1 -1
  290. package/dist/subscribable/subscribable.js +24 -16
  291. package/dist/subscribable/subscribable.js.map +1 -1
  292. package/dist/types/message.d.ts +47 -4
  293. package/dist/types/message.d.ts.map +1 -1
  294. package/dist/types/message.js +7 -1
  295. package/dist/types/message.js.map +1 -1
  296. package/dist/utils/data-url.d.ts +14 -1
  297. package/dist/utils/data-url.d.ts.map +1 -1
  298. package/dist/utils/data-url.js +13 -1
  299. package/dist/utils/data-url.js.map +1 -1
  300. package/dist/utils/invoke-user-callback.d.ts +5 -0
  301. package/dist/utils/invoke-user-callback.d.ts.map +1 -0
  302. package/dist/utils/invoke-user-callback.js +19 -0
  303. package/dist/utils/invoke-user-callback.js.map +1 -0
  304. package/dist/utils/normalizePartStatus.js.map +1 -1
  305. package/package.json +12 -17
  306. package/src/index.ts +6 -1
  307. package/src/internal.ts +20 -0
  308. package/src/model-context/frame/host.test.ts +91 -7
  309. package/src/model-context/frame/host.ts +7 -2
  310. package/src/model-context/frame/provider.test.ts +484 -13
  311. package/src/model-context/frame/provider.ts +205 -35
  312. package/src/model-context/interactable-composer-metadata.ts +1 -1
  313. package/src/model-context/registry.test.ts +44 -0
  314. package/src/model-context/registry.ts +13 -35
  315. package/src/model-context/tool-call-text.test.ts +67 -0
  316. package/src/model-context/tool-call-text.ts +67 -0
  317. package/src/react/AssistantRuntimeProvider.test.tsx +66 -2
  318. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +1 -3
  319. package/src/react/client/InMemoryThreadList.test.tsx +49 -0
  320. package/src/react/client/InMemoryThreadList.ts +7 -1
  321. package/src/react/client/Interactables.ts +19 -178
  322. package/src/react/client/RemoteThreadList.background.test.ts +384 -0
  323. package/src/react/client/RemoteThreadList.concurrent.test.tsx +274 -0
  324. package/src/react/client/RemoteThreadList.test.ts +369 -1
  325. package/src/react/client/RemoteThreadList.ts +320 -142
  326. package/src/react/client/Tools.ts +7 -1
  327. package/src/react/index.ts +4 -0
  328. package/src/react/interactables-legacy/Interactables.ts +20 -177
  329. package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
  330. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
  331. package/src/react/model-context/modelContextHooks.test.tsx +131 -0
  332. package/src/react/model-context/toolbox.test.ts +9 -9
  333. package/src/react/model-context/toolbox.ts +9 -36
  334. package/src/react/model-context/useAssistantContext.ts +4 -2
  335. package/src/react/model-context/useAuiToolOverrides.ts +4 -2
  336. package/src/react/model-context/useInlineRender.test.tsx +48 -0
  337. package/src/react/model-context/useInlineRender.ts +14 -10
  338. package/src/react/primitive-hooks/useMessageError.ts +2 -19
  339. package/src/react/primitive-hooks/useThreadListLoadMore.ts +2 -3
  340. package/src/react/primitives/composer/ComposerAttachments.test.tsx +76 -0
  341. package/src/react/primitives/composer/ComposerAttachments.tsx +9 -4
  342. package/src/react/primitives/composer/ComposerQueue.test.tsx +72 -0
  343. package/src/react/primitives/composer/ComposerQueue.tsx +7 -4
  344. package/src/react/primitives/message/MessageAttachments.test.tsx +107 -37
  345. package/src/react/primitives/message/MessageAttachments.tsx +10 -7
  346. package/src/react/primitives/message/MessageGroupedParts.tsx +2 -2
  347. package/src/react/primitives/message/MessageParts.tsx +3 -3
  348. package/src/react/primitives/threadList/ThreadListItems.test.tsx +99 -0
  349. package/src/react/primitives/threadList/ThreadListItems.tsx +8 -5
  350. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +94 -1
  351. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +72 -31
  352. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +169 -0
  353. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +140 -124
  354. package/src/react/runtimes/RemoteThreadResource.ts +1 -4
  355. package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
  356. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +51 -2
  357. package/src/react/runtimes/assistant-transport/useLatestRef.ts +2 -2
  358. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +45 -0
  359. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +157 -1
  360. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +82 -176
  361. package/src/react/runtimes/cloud/auiV0.ts +5 -0
  362. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +88 -0
  363. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +236 -0
  364. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
  365. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
  366. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +90 -1
  367. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +33 -238
  368. package/src/react/runtimes/external-message-converter.test.ts +0 -14
  369. package/src/react/runtimes/external-message-converter.test.tsx +77 -0
  370. package/src/react/runtimes/external-message-converter.ts +59 -481
  371. package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +216 -0
  372. package/src/react/runtimes/useRemoteThreadListRuntime.ts +24 -6
  373. package/src/react/types/MessagePartComponentTypes.ts +7 -3
  374. package/src/react/types/scopes/tools.ts +2 -0
  375. package/src/runtime/api/composer-runtime.ts +1 -1
  376. package/src/runtime/api/message-part-runtime.ts +3 -3
  377. package/src/runtime/api/message-runtime.test.ts +28 -1
  378. package/src/runtime/api/thread-list-runtime.ts +25 -1
  379. package/src/runtime/base/base-composer-runtime-core.ts +20 -40
  380. package/src/runtime/base/base-thread-runtime-core.test.ts +110 -3
  381. package/src/runtime/base/base-thread-runtime-core.ts +16 -15
  382. package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
  383. package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
  384. package/src/runtime/interfaces/thread-list-runtime-core.ts +20 -1
  385. package/src/runtime/interfaces/thread-runtime-core.ts +18 -2
  386. package/src/runtime/internal.ts +5 -0
  387. package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
  388. package/src/runtime/queue/message-queue.ts +1 -4
  389. package/src/runtime/queue/queue-item.ts +10 -0
  390. package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
  391. package/src/runtime/utils/abortable-thread-load.ts +53 -0
  392. package/src/runtime/utils/attachment-add-operations.ts +59 -0
  393. package/src/runtime/utils/auto-status.test.ts +57 -0
  394. package/src/runtime/utils/auto-status.ts +46 -1
  395. package/src/runtime/utils/external-message-conversion.test.ts +210 -0
  396. package/src/runtime/utils/external-message-conversion.ts +510 -0
  397. package/src/runtime/utils/external-store-message.ts +2 -0
  398. package/src/runtime/utils/message-repository-session.test.ts +195 -0
  399. package/src/runtime/utils/message-repository-session.ts +176 -0
  400. package/src/runtime/utils/message-repository.ts +7 -4
  401. package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
  402. package/src/runtime/utils/pending-tool-calls.ts +35 -0
  403. package/src/runtime/utils/resolveToolApprovalResponse.test.ts +73 -0
  404. package/src/runtime/utils/resolveToolApprovalResponse.ts +27 -6
  405. package/src/runtime/utils/thread-message-like.ts +4 -10
  406. package/src/runtime/utils/tool-call-tree.test.ts +191 -0
  407. package/src/runtime/utils/tool-call-tree.ts +97 -0
  408. package/src/runtimes/external-store/external-store-adapter.ts +38 -2
  409. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
  410. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +231 -61
  411. package/src/runtimes/inert/InertThreadRuntimeCore.ts +1 -1
  412. package/src/runtimes/local/local-thread-runtime-core.test.ts +201 -4
  413. package/src/runtimes/local/local-thread-runtime-core.ts +53 -4
  414. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +149 -0
  415. package/src/runtimes/remote-thread-list/remote-thread-state.ts +178 -16
  416. package/src/runtimes/remote-thread-list/title.ts +3 -0
  417. package/src/runtimes/remote-thread-list/types.ts +6 -5
  418. package/src/runtimes/tool-invocations/EDGE_CASES.md +125 -3
  419. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +856 -102
  420. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +145 -89
  421. package/src/store/clients/external-thread.ts +30 -69
  422. package/src/store/clients/single-thread-list.ts +4 -1
  423. package/src/store/clients/suggestions.test.ts +185 -0
  424. package/src/store/clients/suggestions.ts +42 -35
  425. package/src/store/index.ts +4 -0
  426. package/src/store/internal.ts +2 -0
  427. package/src/store/primitive-predicates.ts +35 -0
  428. package/src/store/runtime-clients/thread-list-runtime-client.ts +16 -2
  429. package/src/store/runtime-clients/useSubscribable.ts +8 -2
  430. package/src/store/scopes/message.ts +1 -1
  431. package/src/store/scopes/part.ts +6 -3
  432. package/src/store/scopes/queue-item.ts +2 -9
  433. package/src/subscribable/subscribable.test.ts +50 -0
  434. package/src/subscribable/subscribable.ts +33 -21
  435. package/src/tests/MessageRepository.test.ts +77 -0
  436. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +305 -0
  437. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +78 -0
  438. package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +52 -0
  439. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
  440. package/src/tests/external-store-optimistic-id.test.ts +79 -0
  441. package/src/tests/external-store-repository-instance.test.ts +166 -0
  442. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +186 -16
  443. package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
  444. package/src/tests/external-thread-attachments.test.tsx +258 -1
  445. package/src/tests/remote-thread-list-adapter-switch.test.ts +66 -1
  446. package/src/tests/single-thread-list-item.test.tsx +47 -0
  447. package/src/tests/thread-message-like.test.ts +39 -1
  448. package/src/tests/thread-switch-events.test.tsx +397 -2
  449. package/src/types/message.ts +58 -5
  450. package/src/utils/data-url.test.ts +52 -1
  451. package/src/utils/data-url.ts +21 -0
  452. package/src/utils/invoke-user-callback.test.ts +82 -0
  453. package/src/utils/invoke-user-callback.ts +31 -0
  454. package/src/utils/normalizePartStatus.ts +1 -1
  455. package/dist/store/scopes/queue-item.js.map +0 -1
@@ -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
 
@@ -519,6 +552,7 @@ export class ToolInvocationTracker {
519
552
  // The host already set the result (via the live snapshot's
520
553
  // `setResponse` path). Suppress the executor's redundant emit.
521
554
  if (entry?.hasResult) return;
555
+ if (entry.skipExecute) return;
522
556
 
523
557
  this._invokeOnResult({
524
558
  type: "add-tool-result",
@@ -576,31 +610,40 @@ export class ToolInvocationTracker {
576
610
 
577
611
  // ──────────────── internal: snapshot processing ────────────────
578
612
 
579
- private _hasExecutableTool(toolName: string): boolean {
580
- const tool = this._getTools()?.[toolName];
581
- 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
+ );
582
620
  }
583
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
+ */
584
628
  private _shouldCloseArgsStream({
585
- toolName,
586
629
  argsText,
587
630
  hasResult,
631
+ clientOwned,
588
632
  }: {
589
- toolName: string;
590
633
  argsText: string;
591
634
  hasResult: boolean;
635
+ clientOwned: boolean;
592
636
  }): boolean {
593
637
  if (hasResult) return true;
594
- if (!this._hasExecutableTool(toolName)) {
595
- return !this._isRunning && isArgsTextComplete(argsText);
596
- }
597
- return isArgsTextComplete(argsText);
638
+ if (!isArgsTextComplete(argsText)) return false;
639
+ return clientOwned || !this._isRunning;
598
640
  }
599
641
 
600
642
  private _startActiveEntry(
601
643
  toolCallId: string,
602
644
  toolName: string,
603
645
  skipExecute: boolean,
646
+ clientOwned: boolean,
604
647
  ): ToolCallEntry {
605
648
  const toolCallController = this._controller.addToolCallPart({
606
649
  toolName,
@@ -613,6 +656,7 @@ export class ToolInvocationTracker {
613
656
  hasResult: false,
614
657
  skipExecute,
615
658
  argsComplete: false,
659
+ clientOwned,
616
660
  };
617
661
  this._entries.set(toolCallId, entry);
618
662
  return entry;
@@ -628,6 +672,16 @@ export class ToolInvocationTracker {
628
672
  private _demoteEntriesToRestored(): void {
629
673
  for (const [toolCallId, entry] of this._entries) {
630
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
+ }
631
685
  this._entries.set(toolCallId, {
632
686
  toolName: entry.toolName,
633
687
  argsText: entry.argsText,
@@ -682,9 +736,9 @@ export class ToolInvocationTracker {
682
736
  isEquivalentCompleteArgsText(entry.argsText, content.argsText)
683
737
  ) {
684
738
  const shouldClose = this._shouldCloseArgsStream({
685
- toolName: content.toolName,
686
739
  argsText: content.argsText,
687
740
  hasResult,
741
+ clientOwned: entry.clientOwned,
688
742
  });
689
743
  if (shouldClose) entry.controller.argsText.close();
690
744
  entry.argsText = content.argsText;
@@ -716,9 +770,9 @@ export class ToolInvocationTracker {
716
770
  const delta = content.argsText.slice(entry.argsText.length);
717
771
  entry.controller.argsText.append(delta);
718
772
  const shouldClose = this._shouldCloseArgsStream({
719
- toolName: content.toolName,
720
773
  argsText: content.argsText,
721
774
  hasResult,
775
+ clientOwned: entry.clientOwned,
722
776
  });
723
777
  if (shouldClose) entry.controller.argsText.close();
724
778
  entry.argsText = content.argsText;
@@ -731,9 +785,9 @@ export class ToolInvocationTracker {
731
785
  // gates on the streamed content; a divergent snapshot (A.2) can be
732
786
  // complete while the controller still holds an incomplete stale prefix.
733
787
  const shouldClose = this._shouldCloseArgsStream({
734
- toolName: content.toolName,
735
788
  argsText: entry.argsText,
736
789
  hasResult,
790
+ clientOwned: entry.clientOwned,
737
791
  });
738
792
  if (shouldClose) {
739
793
  entry.controller.argsText.close();
@@ -745,79 +799,81 @@ export class ToolInvocationTracker {
745
799
  private _processMessages(messages: readonly ThreadMessage[]): void {
746
800
  const isRestore = this._pendingRestore;
747
801
 
748
- for (const message of messages) {
749
- 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
+ }
750
816
  continue;
751
817
  }
752
- for (const content of message.content as readonly ThreadMessage["content"][number][]) {
753
- if (!content || content.type !== "tool-call") continue;
754
-
755
- const existing = this._entries.get(content.toolCallId);
756
-
757
- if (isRestore) {
758
- // Don't overwrite an already-active entry (e.g. live tool-call
759
- // observed before this restore snapshot landed). Restore can
760
- // only seed entries the runtime has never seen.
761
- if (!existing?.controller) {
762
- this._entries.set(content.toolCallId, {
763
- toolName: content.toolName,
764
- argsText: content.argsText,
765
- hasResult: content.result !== undefined,
766
- });
767
- }
768
- if (content.messages) this._processMessages(content.messages);
769
- continue;
770
- }
771
818
 
772
- // Live snapshot.
773
- let entry = existing;
774
-
775
- if (entry && !entry.controller) {
776
- // Restored entry observed in a live snapshot. Promote if its
777
- // signature has changed; otherwise treat as still-historical.
778
- const signatureChanged =
779
- content.argsText !== entry.argsText ||
780
- (content.result !== undefined) !== entry.hasResult;
781
- if (!signatureChanged) {
782
- if (content.messages) this._processMessages(content.messages);
783
- continue;
784
- }
785
- this._entries.delete(content.toolCallId);
786
- entry = undefined;
787
- }
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
+ }
788
837
 
789
- if (!entry) {
790
- entry = this._startActiveEntry(
791
- content.toolCallId,
792
- content.toolName,
793
- content.result !== undefined,
794
- );
795
- }
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
+ }
796
853
 
797
- this._processArgsText(entry, content);
798
-
799
- if (content.result !== undefined && !entry.hasResult) {
800
- // `entry` is in active phase from this point — either just
801
- // created by `_startActiveEntry`, or pre-existing with a live
802
- // controller. Narrow once instead of asserting at every use.
803
- const { controller: activeController } = entry;
804
- if (!activeController) continue;
805
- entry.hasResult = true;
806
- entry.argsComplete = true;
807
- activeController.setResponse(
808
- new ToolResponse({
809
- result: content.result as ReadonlyJSONValue,
810
- artifact: content.artifact as ReadonlyJSONValue | undefined,
811
- isError: content.isError,
812
- ...(content.modelContent !== undefined
813
- ? { modelContent: content.modelContent }
814
- : {}),
815
- }),
816
- );
817
- activeController.close();
818
- }
854
+ if (content.approval !== undefined) entry.skipExecute = true;
819
855
 
820
- 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();
821
877
  }
822
878
  }
823
879
  }
@@ -44,7 +44,12 @@ import type { ComposerSendOptions } from "../scopes/composer";
44
44
  import { fileMatchesAccept } from "../../adapters/attachment";
45
45
  import { getThreadMessageText } from "../../utils/text";
46
46
  import { resolveToolApprovalResponse } from "../../runtime/utils/resolveToolApprovalResponse";
47
+ import {
48
+ AttachmentAddOperations,
49
+ drainAttachmentAdd,
50
+ } from "../../runtime/utils/attachment-add-operations";
47
51
  import { toMessagePartStatus } from "../../utils/normalizePartStatus";
52
+ import { generateId } from "../../utils/id";
48
53
  import { ModelContext } from "./model-context-client";
49
54
  import { ThreadSuggestions } from "./suggestions";
50
55
  import { Tools } from "../../react/client/Tools";
@@ -114,7 +119,9 @@ export type ExternalThreadProps = {
114
119
  /** Branch adapter for runtimes that track sibling variants of messages. */
115
120
  branches?: ExternalThreadBranchAdapter;
116
121
  /** Callback for tool approval decisions. Absent: responding to an approval throws a capability error. */
117
- onRespondToToolApproval?: (options: RespondToToolApprovalOptions) => void;
122
+ onRespondToToolApproval?: (
123
+ options: RespondToToolApprovalOptions,
124
+ ) => void | Promise<void>;
118
125
  };
119
126
 
120
127
  type MessageClientProps = {
@@ -126,7 +133,7 @@ type MessageClientProps = {
126
133
  queue?: ExternalThreadQueueAdapter | undefined;
127
134
  branches?: ExternalThreadBranchAdapter | undefined;
128
135
  onRespondToToolApproval?:
129
- | ((options: RespondToToolApprovalOptions) => void)
136
+ | ((options: RespondToToolApprovalOptions) => void | Promise<void>)
130
137
  | undefined;
131
138
  onAddToolResult?: ((options: AddToolResultOptions) => void) | undefined;
132
139
  onResumeToolCall?: ((options: ResumeToolCallOptions) => void) | undefined;
@@ -309,7 +316,7 @@ type PartResourceProps = {
309
316
  status: ToolCallMessagePartStatus;
310
317
  messageId: string;
311
318
  onRespondToToolApproval?:
312
- | ((options: RespondToToolApprovalOptions) => void)
319
+ | ((options: RespondToToolApprovalOptions) => void | Promise<void>)
313
320
  | undefined;
314
321
  onAddToolResult?: ((options: AddToolResultOptions) => void) | undefined;
315
322
  onResumeToolCall?: ((options: ResumeToolCallOptions) => void) | undefined;
@@ -378,9 +385,12 @@ const usePartResource = ({
378
385
  )
379
386
  throw new Error("Tool call has no pending approval");
380
387
 
381
- onRespondToToolApproval(
382
- resolveToolApprovalResponse(part.approval, response),
383
- );
388
+ const options = resolveToolApprovalResponse(part.approval, response);
389
+ try {
390
+ return Promise.resolve(onRespondToToolApproval(options));
391
+ } catch (error) {
392
+ return Promise.reject(error);
393
+ }
384
394
  },
385
395
  };
386
396
  };
@@ -420,53 +430,6 @@ type ComposerClientResourceProps = {
420
430
  attachmentAdapter?: AttachmentAdapter | undefined;
421
431
  };
422
432
 
423
- type AttachmentAddOperation = {
424
- cancelled: boolean;
425
- attachmentIds: Set<string>;
426
- };
427
-
428
- class AttachmentAddOperations {
429
- private readonly operations = new Set<AttachmentAddOperation>();
430
-
431
- start() {
432
- const operation: AttachmentAddOperation = {
433
- cancelled: false,
434
- attachmentIds: new Set(),
435
- };
436
- this.operations.add(operation);
437
- return operation;
438
- }
439
-
440
- accept(operation: AttachmentAddOperation, attachmentId: string) {
441
- if (operation.cancelled) return false;
442
- operation.attachmentIds.add(attachmentId);
443
- return true;
444
- }
445
-
446
- finish(operation: AttachmentAddOperation) {
447
- this.operations.delete(operation);
448
- }
449
-
450
- isCancelled(operation: AttachmentAddOperation) {
451
- return operation.cancelled;
452
- }
453
-
454
- cancel(attachmentId: string) {
455
- for (const operation of [...this.operations]) {
456
- if (!operation.attachmentIds.has(attachmentId)) continue;
457
- operation.cancelled = true;
458
- this.operations.delete(operation);
459
- }
460
- }
461
-
462
- cancelAll() {
463
- for (const operation of this.operations) {
464
- operation.cancelled = true;
465
- }
466
- this.operations.clear();
467
- }
468
- }
469
-
470
433
  const useQueueItemClient = ({
471
434
  item,
472
435
  onMove,
@@ -486,19 +449,6 @@ const useQueueItemClient = ({
486
449
 
487
450
  const QueueItemClient = resource(useQueueItemClient);
488
451
 
489
- const drainAdapterAdd = async (
490
- result: ReturnType<AttachmentAdapter["add"]>,
491
- upsert: (attachment: Attachment) => boolean,
492
- ) => {
493
- if (Symbol.asyncIterator in result) {
494
- for await (const attachment of result) {
495
- if (!upsert(attachment)) break;
496
- }
497
- } else {
498
- upsert(await result);
499
- }
500
- };
501
-
502
452
  // State whose setter tracks the latest value in a ref, so imperative
503
453
  // call sequences (setText immediately followed by send) observe the write
504
454
  // before React re-renders — legacy composer parity.
@@ -672,7 +622,7 @@ const useComposerClientResource = ({
672
622
  if (!isCreateAttachment(fileOrAttachment) && attachmentAdapter) {
673
623
  const operation = attachmentAddOperations.start();
674
624
  try {
675
- await drainAdapterAdd(
625
+ await drainAttachmentAdd(
676
626
  attachmentAdapter.add({ file: fileOrAttachment }),
677
627
  (attachment) => {
678
628
  if (!attachmentAddOperations.accept(operation, attachment.id))
@@ -688,7 +638,7 @@ const useComposerClientResource = ({
688
638
  }
689
639
  } else if (!isCreateAttachment(fileOrAttachment)) {
690
640
  const newAttachment: Attachment = {
691
- id: Math.random().toString(36).substring(7),
641
+ id: generateId(),
692
642
  type: "file",
693
643
  name: fileOrAttachment.name,
694
644
  contentType: fileOrAttachment.type,
@@ -699,7 +649,7 @@ const useComposerClientResource = ({
699
649
  setAttachments((prev) => [...prev, newAttachment]);
700
650
  } else {
701
651
  const newAttachment: Attachment = {
702
- id: fileOrAttachment.id ?? Math.random().toString(36).substring(7),
652
+ id: fileOrAttachment.id ?? generateId(),
703
653
  type: fileOrAttachment.type ?? "document",
704
654
  name: fileOrAttachment.name,
705
655
  contentType: fileOrAttachment.contentType,
@@ -795,6 +745,17 @@ const useComposerClientResource = ({
795
745
  }
796
746
  },
797
747
  cancel: () => {
748
+ // An edit session ends here, so its in-flight adapter adds must not
749
+ // land in a later session; the thread composer's cancel stops the run
750
+ // and leaves the draft (and its pending adds) alone.
751
+ if (type === "edit") {
752
+ attachmentAddOperations.cancelAll();
753
+ const removed = attachmentsRef.current;
754
+ setAttachments([]);
755
+ removePendingAttachments(removed).catch((error) => {
756
+ console.error("Failed to remove cancelled edit attachments", error);
757
+ });
758
+ }
798
759
  onCancel?.();
799
760
  if (type === "edit") setIsEditing(false);
800
761
  },
@@ -93,7 +93,10 @@ const useSingleThreadList = ({
93
93
  !(
94
94
  typeof selector === "object" &&
95
95
  "index" in selector &&
96
- selector.index === 0
96
+ selector.index === 0 &&
97
+ // Index selectors address their archived/regular subset, and the
98
+ // archived subset here is always empty.
99
+ !selector.archived
97
100
  )
98
101
  ) {
99
102
  throw new Error(