@assistant-ui/core 0.3.13 → 0.3.14

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 (396) hide show
  1. package/dist/adapters/speech.d.ts.map +1 -1
  2. package/dist/adapters/speech.js +9 -15
  3. package/dist/adapters/speech.js.map +1 -1
  4. package/dist/adapters/thread-history.d.ts +2 -0
  5. package/dist/adapters/thread-history.d.ts.map +1 -1
  6. package/dist/adapters/voice.d.ts.map +1 -1
  7. package/dist/adapters/voice.js +27 -15
  8. package/dist/adapters/voice.js.map +1 -1
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal.d.ts +1 -1
  12. package/dist/internal.js +6 -6
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +2 -1
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/registry.d.ts.map +1 -1
  17. package/dist/model-context/registry.js +16 -4
  18. package/dist/model-context/registry.js.map +1 -1
  19. package/dist/react/RuntimeAdapter.d.ts +8 -1
  20. package/dist/react/RuntimeAdapter.d.ts.map +1 -1
  21. package/dist/react/RuntimeAdapter.js +23 -18
  22. package/dist/react/RuntimeAdapter.js.map +1 -1
  23. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  24. package/dist/react/adapters/LocalStorageThreadListAdapter.js +28 -12
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  26. package/dist/react/client/AdaptedRemoteThread.d.ts +11 -0
  27. package/dist/react/client/AdaptedRemoteThread.d.ts.map +1 -0
  28. package/dist/react/client/AdaptedRemoteThread.js +27 -0
  29. package/dist/react/client/AdaptedRemoteThread.js.map +1 -0
  30. package/dist/react/client/InMemoryThreadList.d.ts +19 -0
  31. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -0
  32. package/dist/react/client/InMemoryThreadList.js +418 -0
  33. package/dist/react/client/InMemoryThreadList.js.map +1 -0
  34. package/dist/react/client/Interactables.d.ts.map +1 -1
  35. package/dist/react/client/Interactables.js +159 -102
  36. package/dist/react/client/Interactables.js.map +1 -1
  37. package/dist/react/client/RemoteThreadList.d.ts +34 -0
  38. package/dist/react/client/RemoteThreadList.d.ts.map +1 -0
  39. package/dist/react/client/RemoteThreadList.js +826 -0
  40. package/dist/react/client/RemoteThreadList.js.map +1 -0
  41. package/dist/react/client/Tools.d.ts.map +1 -1
  42. package/dist/react/client/Tools.js +38 -27
  43. package/dist/react/client/Tools.js.map +1 -1
  44. package/dist/react/index.d.ts +7 -2
  45. package/dist/react/index.js +5 -2
  46. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  47. package/dist/react/interactables-legacy/Interactables.js +145 -110
  48. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  49. package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
  50. package/dist/react/primitive-hooks/useActionBarCopy.js +13 -18
  51. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  52. package/dist/react/primitive-hooks/useActionBarEdit.d.ts.map +1 -1
  53. package/dist/react/primitive-hooks/useActionBarEdit.js +2 -4
  54. package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
  55. package/dist/react/primitive-hooks/useActionBarReload.d.ts.map +1 -1
  56. package/dist/react/primitive-hooks/useActionBarReload.js +2 -4
  57. package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
  58. package/dist/react/primitive-hooks/useBranchPickerNext.d.ts.map +1 -1
  59. package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -6
  60. package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
  61. package/dist/react/primitive-hooks/useBranchPickerPrevious.d.ts.map +1 -1
  62. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -6
  63. package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
  64. package/dist/react/primitive-hooks/useComposerCancel.d.ts.map +1 -1
  65. package/dist/react/primitive-hooks/useComposerCancel.js +2 -4
  66. package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
  67. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  68. package/dist/react/primitive-hooks/useComposerSend.js +2 -4
  69. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  70. package/dist/react/primitive-hooks/useSuggestionTrigger.d.ts.map +1 -1
  71. package/dist/react/primitive-hooks/useSuggestionTrigger.js +6 -2
  72. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  73. package/dist/react/primitives/message/MessageParts.d.ts +13 -0
  74. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  75. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  76. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  77. package/dist/react/primitives/thread/ThreadMessages.js +1 -1
  78. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  79. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +22 -10
  80. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  81. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +109 -71
  82. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  83. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +8 -2
  84. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  85. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +40 -13
  86. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  87. package/dist/react/runtimes/RemoteThreadResource.d.ts +30 -0
  88. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -0
  89. package/dist/react/runtimes/RemoteThreadResource.js +134 -0
  90. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -0
  91. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts +3 -10
  92. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
  93. package/dist/react/runtimes/RuntimeAdapterProvider.js +3 -6
  94. package/dist/react/runtimes/RuntimeAdapterProvider.js.map +1 -1
  95. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts +17 -0
  96. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts.map +1 -0
  97. package/dist/react/runtimes/assistant-transport/commandQueue.js +55 -0
  98. package/dist/react/runtimes/assistant-transport/commandQueue.js.map +1 -0
  99. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts +11 -0
  100. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
  101. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js +165 -0
  102. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js.map +1 -0
  103. package/dist/react/runtimes/assistant-transport/runManager.d.ts +15 -0
  104. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -0
  105. package/dist/react/runtimes/assistant-transport/runManager.js +86 -0
  106. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -0
  107. package/dist/react/runtimes/assistant-transport/types.d.ts +130 -0
  108. package/dist/react/runtimes/assistant-transport/types.d.ts.map +1 -0
  109. package/dist/react/runtimes/assistant-transport/types.js +0 -0
  110. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts +14 -0
  111. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -0
  112. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +296 -0
  113. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -0
  114. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts +7 -0
  115. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts.map +1 -0
  116. package/dist/react/runtimes/assistant-transport/useConvertedState.js +25 -0
  117. package/dist/react/runtimes/assistant-transport/useConvertedState.js.map +1 -0
  118. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts +7 -0
  119. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts.map +1 -0
  120. package/dist/react/runtimes/assistant-transport/useLatestRef.js +27 -0
  121. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -0
  122. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +39 -13
  124. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  125. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +2 -1
  126. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  127. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -3
  128. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  129. package/dist/react/runtimes/cloud/auiV0.d.ts +4 -0
  130. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  131. package/dist/react/runtimes/cloud/auiV0.js +7 -0
  132. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  133. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  134. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +22 -12
  135. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  136. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts +16 -0
  137. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts.map +1 -0
  138. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js +36 -0
  139. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -0
  140. package/dist/react/runtimes/external-message-converter.js +14 -14
  141. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  142. package/dist/react/runtimes/useExternalStoreRuntime.d.ts.map +1 -1
  143. package/dist/react/runtimes/useExternalStoreRuntime.js +44 -28
  144. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  145. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
  146. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  147. package/dist/react/runtimes/useRuntimeAdapters.d.ts +9 -0
  148. package/dist/react/runtimes/useRuntimeAdapters.d.ts.map +1 -0
  149. package/dist/react/runtimes/useRuntimeAdapters.js +20 -0
  150. package/dist/react/runtimes/useRuntimeAdapters.js.map +1 -0
  151. package/dist/runtime/api/composer-runtime.d.ts +5 -0
  152. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  153. package/dist/runtime/api/composer-runtime.js.map +1 -1
  154. package/dist/runtime/api/message-runtime.js +1 -1
  155. package/dist/runtime/api/thread-list-runtime.d.ts +1 -0
  156. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  157. package/dist/runtime/api/thread-list-runtime.js +4 -2
  158. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  159. package/dist/runtime/api/thread-runtime.d.ts +12 -2
  160. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  161. package/dist/runtime/api/thread-runtime.js +8 -2
  162. package/dist/runtime/api/thread-runtime.js.map +1 -1
  163. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  164. package/dist/runtime/base/base-composer-runtime-core.js +8 -7
  165. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  166. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
  167. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  168. package/dist/runtime/base/base-thread-runtime-core.js +3 -3
  169. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  170. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +0 -1
  171. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  172. package/dist/runtime/base/default-thread-composer-runtime-core.js +10 -4
  173. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  174. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  175. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  176. package/dist/runtime/internal.js +5 -5
  177. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  178. package/dist/runtime/queue/message-queue.js +2 -1
  179. package/dist/runtime/queue/message-queue.js.map +1 -1
  180. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts +8 -0
  181. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts.map +1 -0
  182. package/dist/runtime/utils/thread-runtime-lifecycle.js +11 -0
  183. package/dist/runtime/utils/thread-runtime-lifecycle.js.map +1 -0
  184. package/dist/runtimes/external-store/external-store-shared-options.js.map +1 -1
  185. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  186. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +4 -1
  187. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  188. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +8 -0
  189. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  190. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +35 -13
  191. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  192. package/dist/runtimes/internal.d.ts +1 -1
  193. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
  194. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  195. package/dist/runtimes/local/local-thread-runtime-core.js +53 -13
  196. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  197. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
  198. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  199. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +3 -0
  200. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  201. package/dist/runtimes/remote-thread-list/empty-thread-core.js +3 -0
  202. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  203. package/dist/runtimes/remote-thread-list/types.d.ts +29 -1
  204. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  205. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +1 -12
  206. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  207. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +67 -38
  208. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  209. package/dist/store/clients/suggestions.js +2 -2
  210. package/dist/store/clients/suggestions.js.map +1 -1
  211. package/dist/store/clients/thread-selection-events.d.ts +10 -0
  212. package/dist/store/clients/thread-selection-events.d.ts.map +1 -0
  213. package/dist/store/clients/thread-selection-events.js +40 -0
  214. package/dist/store/clients/thread-selection-events.js.map +1 -0
  215. package/dist/store/env.d.ts +5 -0
  216. package/dist/store/env.d.ts.map +1 -0
  217. package/dist/store/env.js +12 -0
  218. package/dist/store/env.js.map +1 -0
  219. package/dist/store/index.d.ts +5 -3
  220. package/dist/store/index.js +4 -2
  221. package/dist/store/internal.d.ts +4 -1
  222. package/dist/store/internal.js +4 -1
  223. package/dist/store/primitive-predicates.d.ts +19 -0
  224. package/dist/store/primitive-predicates.d.ts.map +1 -0
  225. package/dist/store/primitive-predicates.js +19 -0
  226. package/dist/store/primitive-predicates.js.map +1 -0
  227. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  228. package/dist/store/runtime-clients/attachment-runtime-client.js +18 -11
  229. package/dist/store/runtime-clients/attachment-runtime-client.js.map +1 -1
  230. package/dist/store/runtime-clients/handle-runtime-action.d.ts +5 -0
  231. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -0
  232. package/dist/store/runtime-clients/handle-runtime-action.js +12 -0
  233. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -0
  234. package/dist/store/runtime-clients/handle-thread-list-action.d.ts +5 -0
  235. package/dist/store/runtime-clients/handle-thread-list-action.d.ts.map +1 -0
  236. package/dist/store/runtime-clients/handle-thread-list-action.js +7 -0
  237. package/dist/store/runtime-clients/handle-thread-list-action.js.map +1 -0
  238. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  239. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +62 -29
  240. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  241. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  242. package/dist/store/runtime-clients/thread-list-runtime-client.js +5 -6
  243. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  244. package/dist/store/scopes/composer.d.ts +9 -4
  245. package/dist/store/scopes/composer.d.ts.map +1 -1
  246. package/dist/store/scopes/thread-list-item.d.ts +7 -6
  247. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  248. package/dist/store/scopes/thread.d.ts +8 -7
  249. package/dist/store/scopes/thread.d.ts.map +1 -1
  250. package/dist/store/scopes/threads.d.ts +17 -1
  251. package/dist/store/scopes/threads.d.ts.map +1 -1
  252. package/dist/subscribable/subscribable.d.ts +2 -1
  253. package/dist/subscribable/subscribable.d.ts.map +1 -1
  254. package/dist/subscribable/subscribable.js +23 -18
  255. package/dist/subscribable/subscribable.js.map +1 -1
  256. package/dist/utils/composite-context-provider.d.ts +1 -0
  257. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  258. package/dist/utils/composite-context-provider.js +25 -9
  259. package/dist/utils/composite-context-provider.js.map +1 -1
  260. package/dist/utils/notify-event-listeners.d.ts +2 -2
  261. package/dist/utils/notify-event-listeners.d.ts.map +1 -1
  262. package/dist/utils/notify-event-listeners.js +2 -2
  263. package/dist/utils/notify-event-listeners.js.map +1 -1
  264. package/package.json +7 -7
  265. package/src/adapters/speech.test.ts +118 -1
  266. package/src/adapters/speech.ts +21 -22
  267. package/src/adapters/thread-history.ts +2 -0
  268. package/src/adapters/voice.test.ts +69 -0
  269. package/src/adapters/voice.ts +27 -27
  270. package/src/index.ts +1 -0
  271. package/src/model-context/frame/host.test.ts +23 -0
  272. package/src/model-context/frame/host.ts +2 -1
  273. package/src/model-context/registry.test.ts +131 -0
  274. package/src/model-context/registry.ts +16 -6
  275. package/src/react/RuntimeAdapter.test.ts +2 -0
  276. package/src/react/RuntimeAdapter.ts +20 -8
  277. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +34 -17
  278. package/src/react/client/AdaptedRemoteThread.ts +41 -0
  279. package/src/react/client/InMemoryThreadList.test.tsx +148 -0
  280. package/src/react/client/InMemoryThreadList.ts +290 -0
  281. package/src/react/client/Interactables.test.ts +326 -2
  282. package/src/react/client/Interactables.ts +227 -110
  283. package/src/react/client/RemoteThreadList.test.ts +1026 -0
  284. package/src/react/client/RemoteThreadList.ts +1067 -0
  285. package/src/react/client/Tools.ts +36 -29
  286. package/src/react/index.ts +13 -0
  287. package/src/react/interactables-legacy/Interactables.test.ts +317 -0
  288. package/src/react/interactables-legacy/Interactables.ts +204 -128
  289. package/src/react/primitive-hooks/useActionBarCopy.ts +2 -7
  290. package/src/react/primitive-hooks/useActionBarEdit.ts +2 -1
  291. package/src/react/primitive-hooks/useActionBarReload.ts +2 -6
  292. package/src/react/primitive-hooks/useBranchPickerNext.ts +2 -7
  293. package/src/react/primitive-hooks/useBranchPickerPrevious.ts +2 -7
  294. package/src/react/primitive-hooks/useComposerCancel.ts +2 -1
  295. package/src/react/primitive-hooks/useComposerSend.ts +2 -5
  296. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +11 -0
  297. package/src/react/primitive-hooks/useSuggestionTrigger.ts +4 -5
  298. package/src/react/primitives/message/MessageParts.tsx +0 -1
  299. package/src/react/primitives/thread/ThreadMessages.tsx +2 -1
  300. package/src/react/runtimes/RemoteThreadListHookInstanceManager.retry.test.tsx +116 -0
  301. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +1 -1
  302. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +57 -6
  303. package/src/react/runtimes/RemoteThreadListHookInstanceManager.title.test.tsx +272 -0
  304. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +159 -124
  305. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +59 -20
  306. package/src/react/runtimes/RemoteThreadResource.test.ts +141 -0
  307. package/src/react/runtimes/RemoteThreadResource.ts +231 -0
  308. package/src/react/runtimes/RuntimeAdapterProvider.tsx +8 -20
  309. package/src/react/runtimes/assistant-transport/commandQueue.ts +68 -0
  310. package/src/react/runtimes/assistant-transport/replayBoundaryStream.test.ts +455 -0
  311. package/src/react/runtimes/assistant-transport/replayBoundaryStream.ts +172 -0
  312. package/src/react/runtimes/assistant-transport/runManager.ts +124 -0
  313. package/src/react/runtimes/assistant-transport/transport-delivery.test.tsx +79 -0
  314. package/src/react/runtimes/assistant-transport/transport-parentid.test.tsx +97 -0
  315. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +414 -0
  316. package/src/react/runtimes/assistant-transport/types.ts +170 -0
  317. package/src/react/runtimes/assistant-transport/useAssistantTransport.spec.md +141 -0
  318. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +625 -0
  319. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +481 -0
  320. package/src/react/runtimes/assistant-transport/useConvertedState.ts +20 -0
  321. package/src/react/runtimes/assistant-transport/useLatestRef.ts +9 -0
  322. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +131 -6
  323. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +61 -15
  324. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +6 -4
  325. package/src/react/runtimes/cloud/auiV0.ts +17 -1
  326. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +36 -21
  327. package/src/react/runtimes/cloud/useCloudThreadListRuntime.test.tsx +56 -0
  328. package/src/react/runtimes/cloud/useCloudThreadListRuntime.ts +31 -0
  329. package/src/react/runtimes/external-message-converter.test.ts +143 -0
  330. package/src/react/runtimes/external-message-converter.ts +41 -41
  331. package/src/react/runtimes/remote-thread-hook-on-tap.test.ts +58 -0
  332. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +83 -0
  333. package/src/react/runtimes/useExternalStoreRuntime.suggestions.test.tsx +13 -3
  334. package/src/react/runtimes/useExternalStoreRuntime.ts +7 -0
  335. package/src/react/runtimes/useExternalStoreSharedOptions.ts +3 -1
  336. package/src/react/runtimes/useLocalRuntime.test.tsx +130 -4
  337. package/src/react/runtimes/useRuntimeAdapters.test.tsx +62 -0
  338. package/src/react/runtimes/useRuntimeAdapters.ts +28 -0
  339. package/src/runtime/api/composer-runtime.ts +5 -0
  340. package/src/runtime/api/thread-list-runtime.ts +3 -1
  341. package/src/runtime/api/thread-runtime.ts +20 -2
  342. package/src/runtime/base/base-composer-runtime-core.ts +6 -5
  343. package/src/runtime/base/base-thread-runtime-core.test.ts +191 -0
  344. package/src/runtime/base/base-thread-runtime-core.ts +11 -2
  345. package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -4
  346. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  347. package/src/runtime/queue/message-queue.ts +2 -1
  348. package/src/runtime/utils/thread-runtime-lifecycle.ts +18 -0
  349. package/src/runtimes/external-store/external-store-shared-options.ts +3 -1
  350. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
  351. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +99 -34
  352. package/src/runtimes/local/local-thread-runtime-core.test.ts +373 -4
  353. package/src/runtimes/local/local-thread-runtime-core.ts +79 -14
  354. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +4 -0
  355. package/src/runtimes/remote-thread-list/empty-thread-core.ts +4 -0
  356. package/src/runtimes/remote-thread-list/types.ts +30 -0
  357. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -6
  358. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +243 -0
  359. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +127 -61
  360. package/src/store/clients/model-context-client.test.ts +16 -8
  361. package/src/store/clients/suggestions.ts +2 -2
  362. package/src/store/clients/thread-selection-events.ts +21 -0
  363. package/src/store/env.ts +11 -0
  364. package/src/store/index.ts +14 -1
  365. package/src/store/internal.ts +13 -0
  366. package/src/store/primitive-predicates.test.ts +184 -0
  367. package/src/store/primitive-predicates.ts +43 -0
  368. package/src/store/runtime-clients/attachment-runtime-client.test.ts +41 -0
  369. package/src/store/runtime-clients/attachment-runtime-client.ts +2 -1
  370. package/src/store/runtime-clients/handle-runtime-action.ts +11 -0
  371. package/src/store/runtime-clients/handle-thread-list-action.test.ts +69 -0
  372. package/src/store/runtime-clients/handle-thread-list-action.ts +6 -0
  373. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +15 -7
  374. package/src/store/runtime-clients/thread-list-runtime-client.ts +9 -6
  375. package/src/store/scopes/composer.ts +9 -4
  376. package/src/store/scopes/thread-list-item.ts +7 -6
  377. package/src/store/scopes/thread.ts +8 -7
  378. package/src/store/scopes/threads.ts +14 -0
  379. package/src/subscribable/subscribable.test.ts +77 -1
  380. package/src/subscribable/subscribable.ts +38 -23
  381. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-unarchive.test.ts +25 -0
  382. package/src/tests/auiV0Encode.test.ts +66 -0
  383. package/src/tests/base-composer-runtime-core-send.test.ts +30 -0
  384. package/src/tests/base-composer-runtime-core.test.ts +28 -0
  385. package/src/tests/composer-can-cancel.test.ts +101 -0
  386. package/src/tests/external-store-thread-list-runtime-core.test.ts +101 -0
  387. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +191 -1
  388. package/src/tests/external-store-thread-runtime-core.test.ts +124 -0
  389. package/src/tests/interactables-toolui-migration.test.tsx +66 -0
  390. package/src/tests/message-queue.test.ts +28 -0
  391. package/src/tests/session-reset-notification.test.ts +145 -0
  392. package/src/tests/thread-switch-events.test.tsx +99 -1
  393. package/src/tests/tools-scope-migration.test.tsx +58 -0
  394. package/src/utils/composite-context-provider.test.ts +139 -0
  395. package/src/utils/composite-context-provider.ts +26 -8
  396. package/src/utils/notify-event-listeners.ts +10 -5
@@ -10,6 +10,9 @@ observe via `setState(snapshot)` and what the current behavior is.
10
10
  > first observation — args regress, args change after first completion,
11
11
  > result is replaced, result is cleared, key order shuffles — the tracker
12
12
  > never invokes the host's tool callback a second time.
13
+ >
14
+ > `reset()` starts a new execution boundary. A reused `toolCallId` in the
15
+ > next boundary gets a new execution identity and may fire once there.
13
16
 
14
17
  This guarantees host-side side effects (the typical reason `streamCall` /
15
18
  `execute` exists at all) can't double-run. The cost: post-completion
@@ -65,7 +68,7 @@ The tracker calls `setResponse` on the active controller and closes it.
65
68
  The backend result is emitted before the args stream closes, so a stale
66
69
  args parse failure cannot replace it. `reader.response.get()` resolves.
67
70
  If the tool also had a frontend `execute`, the executor is short-circuited
68
- via `_skipExecuteStreamIds`. Single fire.
71
+ via the entry's per-execution skip marker. Single fire.
69
72
 
70
73
  ### A.6. Previously-resolved tool's `result` is replaced
71
74
  Silently ignored — `entry.hasResult` short-circuits both the
@@ -153,15 +156,16 @@ tracker's view of "what we last observed". The next snapshot retries.
153
156
 
154
157
  ### F.1. `reset()` called while `execute()` invocations are in flight
155
158
  `abort()` is invoked, in-flight executions reject with
156
- `Tool execution aborted`. Once they settle, the cleanup logic clears
157
- `_executing`. The settled-resolver promises fire so the abort promise
158
- resolves.
159
+ `Tool execution aborted`. Once they settle, the cleanup logic removes their
160
+ execution identities from `_executing`. The settled-resolver promises fire so
161
+ the abort promise resolves.
159
162
 
160
163
  ### F.2. `setState` called during `reset()`'s in-flight abort
161
164
  The new snapshot is processed against an empty `_entries`. Tool calls
162
165
  in it are seeded as restored (because `reset()` re-armed
163
- `_pendingRestore`). Eventual cancellation `result` chunks for the
164
- aborted executions are dropped via `_skipExecuteStreamIds`.
166
+ `_pendingRestore`). Eventual cancellation `result` chunks for the aborted
167
+ executions are dropped when their execution identity no longer matches the
168
+ current entry.
165
169
 
166
170
  ### F.3. `resume(toolCallId, payload)` for an unknown id
167
171
  Silently no-ops. (The pre-class hook *threw*; the tracker softens this
@@ -175,6 +179,13 @@ rebuilt pipeline does not re-fire `streamCall` for them) and the
175
179
  snapshot is processed against the fresh pipeline. Repeated failures
176
180
  keep the tracker dead with a visible error to avoid restart loops.
177
181
 
182
+ ### F.5. Reset followed by same-id reuse
183
+ The assistant-stream pipeline assigns each streamed tool part an opaque
184
+ execution identity. Human-input callbacks, lifecycle callbacks, result chunks,
185
+ and pending execution cleanup are accepted only when that identity is still
186
+ current for the `toolCallId`. A late callback from before `reset()` is therefore
187
+ dropped even when the next session reuses the id.
188
+
178
189
  ## Known limitations
179
190
 
180
191
  ### Host callback throws
@@ -1172,3 +1172,246 @@ describe("ToolInvocationTracker", () => {
1172
1172
  }
1173
1173
  });
1174
1174
  });
1175
+
1176
+ describe("ToolInvocationTracker reset", () => {
1177
+ const trackerWith = (tools: Record<string, Tool>) => {
1178
+ let statuses: Record<string, ToolExecutionStatus> = {};
1179
+ const tracker = new ToolInvocationTracker(() => tools, {
1180
+ onResult: vi.fn(),
1181
+ onStatusesChange: (s: ReadonlyMap<string, ToolExecutionStatus>) => {
1182
+ statuses = Object.fromEntries(s);
1183
+ },
1184
+ });
1185
+ return { tracker, statuses: () => statuses };
1186
+ };
1187
+
1188
+ it("clears statuses for discarded executions that never settle", async () => {
1189
+ const { tracker, statuses } = trackerWith({
1190
+ weatherSearch: {
1191
+ parameters: { type: "object", properties: {} },
1192
+ execute: vi.fn(() => new Promise(() => {})),
1193
+ } satisfies Tool,
1194
+ });
1195
+ tracker.setState(createState([]));
1196
+ tracker.setState(
1197
+ createState([
1198
+ createAssistantMessage('{"query":"London"}', { query: "London" }),
1199
+ ]),
1200
+ );
1201
+ await waitFor(() => {
1202
+ expect(statuses()["tool-1"]?.type).toBe("executing");
1203
+ });
1204
+
1205
+ tracker.reset();
1206
+
1207
+ expect(statuses()).toEqual({});
1208
+
1209
+ // A new-session snapshot restoring tool activity must not drag the
1210
+ // discarded id back in through the whole-map republication. The first
1211
+ // post-reset snapshot is the fresh session's empty state, consuming the
1212
+ // pending-restore mark.
1213
+ tracker.setState(createState([]));
1214
+ tracker.setState(
1215
+ createState([
1216
+ createAssistantMessage(
1217
+ '{"query":"Paris"}',
1218
+ { query: "Paris" },
1219
+ { toolCallId: "tool-2" },
1220
+ ),
1221
+ ]),
1222
+ );
1223
+ await waitFor(() => {
1224
+ expect(statuses()["tool-2"]?.type).toBe("executing");
1225
+ });
1226
+ expect(statuses()["tool-1"]).toBeUndefined();
1227
+ });
1228
+
1229
+ it("does not republish sibling statuses when a discarded execution settles after reset", async () => {
1230
+ const settled = Promise.withResolvers<void>();
1231
+ const { tracker, statuses } = trackerWith({
1232
+ weatherSearch: {
1233
+ parameters: { type: "object", properties: {} },
1234
+ execute: vi.fn(
1235
+ (_args, { abortSignal }: { abortSignal: AbortSignal }) =>
1236
+ new Promise((resolve) => {
1237
+ abortSignal.addEventListener("abort", () => {
1238
+ resolve({ ok: true });
1239
+ settled.resolve();
1240
+ });
1241
+ }),
1242
+ ),
1243
+ } satisfies Tool,
1244
+ pressureSearch: {
1245
+ parameters: { type: "object", properties: {} },
1246
+ execute: vi.fn(() => new Promise(() => {})),
1247
+ } satisfies Tool,
1248
+ });
1249
+ tracker.setState(createState([]));
1250
+ tracker.setState(
1251
+ createState([
1252
+ createAssistantMessage('{"query":"London"}', { query: "London" }),
1253
+ createAssistantMessage(
1254
+ '{"query":"London"}',
1255
+ { query: "London" },
1256
+ {
1257
+ toolCallId: "tool-2",
1258
+ toolName: "pressureSearch",
1259
+ },
1260
+ ),
1261
+ ]),
1262
+ );
1263
+ await waitFor(() => {
1264
+ expect(statuses()["tool-1"]?.type).toBe("executing");
1265
+ expect(statuses()["tool-2"]?.type).toBe("executing");
1266
+ });
1267
+
1268
+ tracker.reset();
1269
+ expect(statuses()).toEqual({});
1270
+
1271
+ // The settle-after-abort of tool-1 is what could republish tool-2;
1272
+ // await it deterministically instead of sleeping.
1273
+ await settled.promise;
1274
+ await new Promise((r) => setTimeout(r, 0));
1275
+ expect(statuses()).toEqual({});
1276
+ });
1277
+
1278
+ it("rejects a human-input request from a discarded execution without resurrecting its status", async () => {
1279
+ let humanFn: ((payload: unknown) => Promise<unknown>) | undefined;
1280
+ const { tracker, statuses } = trackerWith({
1281
+ weatherSearch: {
1282
+ parameters: { type: "object", properties: {} },
1283
+ execute: vi.fn(() => new Promise(() => {})),
1284
+ streamCall: vi.fn((_reader, { human }) => {
1285
+ humanFn = human;
1286
+ }),
1287
+ } satisfies Tool,
1288
+ });
1289
+ tracker.setState(createState([]));
1290
+ tracker.setState(
1291
+ createState([
1292
+ createAssistantMessage('{"query":"London"}', { query: "London" }),
1293
+ ]),
1294
+ );
1295
+ await waitFor(() => {
1296
+ expect(statuses()["tool-1"]?.type).toBe("executing");
1297
+ expect(humanFn).toBeDefined();
1298
+ });
1299
+
1300
+ tracker.reset();
1301
+
1302
+ await expect(humanFn!({ request: "approve" })).rejects.toThrow(
1303
+ "Tool execution aborted",
1304
+ );
1305
+ expect(statuses()).toEqual({});
1306
+ });
1307
+
1308
+ it("rejects a late human-input request when reset reuses the tool call id", async () => {
1309
+ const humanFns: ((payload: unknown) => Promise<unknown>)[] = [];
1310
+ const { tracker, statuses } = trackerWith({
1311
+ weatherSearch: {
1312
+ parameters: { type: "object", properties: {} },
1313
+ execute: vi.fn(() => new Promise(() => {})),
1314
+ streamCall: vi.fn((_reader, { human }) => {
1315
+ humanFns.push(human);
1316
+ }),
1317
+ } satisfies Tool,
1318
+ });
1319
+
1320
+ tracker.setState(createState([]));
1321
+ tracker.setState(
1322
+ createState([
1323
+ createAssistantMessage('{"query":"London"}', { query: "London" }),
1324
+ ]),
1325
+ );
1326
+ await waitFor(() => {
1327
+ expect(humanFns).toHaveLength(1);
1328
+ expect(statuses()["tool-1"]?.type).toBe("executing");
1329
+ });
1330
+
1331
+ tracker.reset();
1332
+ tracker.setState(createState([]));
1333
+ tracker.setState(
1334
+ createState([
1335
+ createAssistantMessage(
1336
+ '{"query":"Paris"}',
1337
+ { query: "Paris" },
1338
+ { toolCallId: "tool-1" },
1339
+ ),
1340
+ ]),
1341
+ );
1342
+ await waitFor(() => expect(humanFns).toHaveLength(2));
1343
+
1344
+ await expect(humanFns[0]!({ stale: true })).rejects.toThrow(
1345
+ "Tool execution aborted",
1346
+ );
1347
+ expect(statuses()["tool-1"]?.type).toBe("executing");
1348
+
1349
+ tracker.reset();
1350
+ });
1351
+
1352
+ it("drops a late result when reset reuses the tool call id", async () => {
1353
+ const executions: Array<{
1354
+ resolve: (value: { session: string }) => void;
1355
+ promise: Promise<{ session: string }>;
1356
+ }> = [];
1357
+ const onResult = vi.fn();
1358
+ const tracker = new ToolInvocationTracker(
1359
+ () => ({
1360
+ weatherSearch: {
1361
+ parameters: { type: "object", properties: {} },
1362
+ execute: vi.fn(
1363
+ (_args: unknown, { abortSignal }: { abortSignal: AbortSignal }) => {
1364
+ let resolveExecution!: (value: { session: string }) => void;
1365
+ const promise = new Promise<{ session: string }>((resolve) => {
1366
+ resolveExecution = resolve;
1367
+ });
1368
+ const execution = { promise, resolve: resolveExecution };
1369
+ abortSignal.addEventListener("abort", () => {
1370
+ execution.resolve({ session: "old" });
1371
+ });
1372
+ executions.push(execution);
1373
+ return promise;
1374
+ },
1375
+ ),
1376
+ } satisfies Tool,
1377
+ }),
1378
+ { onResult, onStatusesChange: vi.fn() },
1379
+ );
1380
+
1381
+ tracker.setState(createState([]));
1382
+ tracker.setState(
1383
+ createState([
1384
+ createAssistantMessage('{"query":"London"}', { query: "London" }),
1385
+ ]),
1386
+ );
1387
+ await waitFor(() => expect(executions).toHaveLength(1));
1388
+
1389
+ tracker.reset();
1390
+ tracker.setState(createState([]));
1391
+ tracker.setState(
1392
+ createState([
1393
+ createAssistantMessage(
1394
+ '{"query":"Paris"}',
1395
+ { query: "Paris" },
1396
+ { toolCallId: "tool-1" },
1397
+ ),
1398
+ ]),
1399
+ );
1400
+ await waitFor(() => expect(executions).toHaveLength(2));
1401
+ await new Promise((resolve) => setTimeout(resolve, 0));
1402
+ expect(onResult).not.toHaveBeenCalled();
1403
+
1404
+ executions[1]!.resolve({ session: "new" });
1405
+ await waitFor(() => {
1406
+ expect(onResult).toHaveBeenCalledOnce();
1407
+ expect(onResult).toHaveBeenCalledWith(
1408
+ expect.objectContaining({
1409
+ toolCallId: "tool-1",
1410
+ result: { session: "new" },
1411
+ }),
1412
+ );
1413
+ });
1414
+
1415
+ tracker.reset();
1416
+ });
1417
+ });
@@ -7,6 +7,7 @@ import {
7
7
  unstable_toolResultStream,
8
8
  type Tool,
9
9
  type ToolModelContentPart,
10
+ type ToolResultStreamOptions,
10
11
  } from "assistant-stream";
11
12
  import {
12
13
  AssistantMetaTransformStream,
@@ -15,6 +16,8 @@ import {
15
16
  import { isJSONValueEqual } from "../../utils/json/is-json-equal";
16
17
  import type { ThreadMessage } from "../../types/message";
17
18
 
19
+ const TOOL_EXECUTION_ID = Symbol.for("assistant-stream.tool-execution-id");
20
+
18
21
  /**
19
22
  * Streaming execution state for a frontend tool.
20
23
  */
@@ -39,6 +42,8 @@ type ToolCallEntry = {
39
42
  toolName: string;
40
43
  argsText: string;
41
44
  hasResult: boolean;
45
+ executionId?: symbol;
46
+ skipExecute?: boolean;
42
47
  } & (
43
48
  | {
44
49
  /** Restored phase — observed during a history-load snapshot. */
@@ -52,6 +57,11 @@ type ToolCallEntry = {
52
57
  }
53
58
  );
54
59
 
60
+ type SettledResolver = {
61
+ executionIds: ReadonlySet<symbol>;
62
+ resolve: () => void;
63
+ };
64
+
55
65
  const isArgsTextComplete = (argsText: string) => {
56
66
  try {
57
67
  JSON.parse(argsText);
@@ -76,6 +86,11 @@ const isEquivalentCompleteArgsText = (previous: string, next: string) => {
76
86
  return isJSONValueEqual(previousValue, nextValue);
77
87
  };
78
88
 
89
+ const getToolExecutionId = (value: object): symbol | undefined =>
90
+ (value as Record<PropertyKey, unknown>)[TOOL_EXECUTION_ID] as
91
+ | symbol
92
+ | undefined;
93
+
79
94
  /**
80
95
  * Plain-class port of the former `useToolInvocations` React hook. Owns the
81
96
  * assistant-stream pipeline that drives client-side `streamCall` / `execute`
@@ -103,27 +118,16 @@ export class ToolInvocationTracker {
103
118
  private readonly _callbacks: ToolInvocationTracker.Callbacks;
104
119
 
105
120
  private readonly _entries = new Map<string, ToolCallEntry>();
106
- /**
107
- * Tool call ids whose `execute` should be short-circuited in the wrapper.
108
- * Populated when an entry is created with a result already attached
109
- * (history reload, mid-run resume, etc.) — `execute` is suppressed so
110
- * client-side side effects don't double-run. Membership outlives the
111
- * entry: `reset()` deliberately does *not* clear this so post-abort
112
- * cancellation `result` chunks for pre-resolved entries can still be
113
- * recognized and dropped. Growth is bounded by the number of pre-resolved
114
- * tool calls observed in the session.
115
- */
116
- private readonly _skipExecuteStreamIds = new Set<string>();
117
121
  private readonly _humanInput = new Map<
118
122
  string,
119
123
  {
124
+ executionId: symbol;
120
125
  resolve: (payload: unknown) => void;
121
126
  reject: (reason: unknown) => void;
122
127
  }
123
128
  >();
124
- /** In-flight `execute` invocations keyed by tool call id. */
125
- private readonly _executing = new Set<string>();
126
- private readonly _settledResolvers: Array<() => void> = [];
129
+ private readonly _executing = new Set<symbol>();
130
+ private readonly _settledResolvers: SettledResolver[] = [];
127
131
 
128
132
  private _statuses = new Map<string, ToolExecutionStatus>();
129
133
 
@@ -166,14 +170,21 @@ export class ToolInvocationTracker {
166
170
  const [stream, controller] = createAssistantStreamController();
167
171
  this._controller = controller;
168
172
 
173
+ const human = (
174
+ toolCallId: string,
175
+ payload: unknown,
176
+ executionId?: symbol,
177
+ ) => this._onHumanInput(toolCallId, payload, executionId);
169
178
  const transform = unstable_toolResultStream(
170
179
  () => this._getWrappedTools(),
171
180
  () => this._ac.signal,
172
- (toolCallId, payload) => this._onHumanInput(toolCallId, payload),
181
+ human,
173
182
  {
174
- onExecutionStart: (id) => this._onExecutionStart(id),
175
- onExecutionEnd: (id) => this._onExecutionEnd(id),
176
- },
183
+ onExecutionStart: (id: string, _name: string, executionId?: symbol) =>
184
+ this._onExecutionStart(id, executionId),
185
+ onExecutionEnd: (id: string, _name: string, executionId?: symbol) =>
186
+ this._onExecutionEnd(id, executionId),
187
+ } as ToolResultStreamOptions,
177
188
  );
178
189
 
179
190
  stream
@@ -281,10 +292,16 @@ export class ToolInvocationTracker {
281
292
  this._pendingRestore = true;
282
293
  this._entries.clear();
283
294
  this._lastSnapshot = null;
284
- // `_skipExecuteStreamIds` is intentionally not cleared — see field doc.
285
- void this.abort().finally(() => {
286
- this._executing.clear();
287
- });
295
+ void this.abort();
296
+ // Statuses are cleared synchronously: discarded executions may never
297
+ // settle (aborting hands the signal to the tool, it does not force
298
+ // settlement), and a late settlement of one sibling must not republish
299
+ // the others. `_deleteStatus` no-ops for cleared ids, so post-reset
300
+ // settlements stay silent.
301
+ if (this._statuses.size > 0) {
302
+ this._statuses = new Map();
303
+ this._invokeOnStatusesChange();
304
+ }
288
305
  } catch (err) {
289
306
  console.error("[ToolInvocationTracker] reset failed", err);
290
307
  }
@@ -312,8 +329,9 @@ export class ToolInvocationTracker {
312
329
  if (this._executing.size === 0) {
313
330
  return Promise.resolve();
314
331
  }
332
+ const executionIds = new Set(this._executing);
315
333
  return new Promise<void>((resolve) => {
316
- this._settledResolvers.push(resolve);
334
+ this._settledResolvers.push({ executionIds, resolve });
317
335
  });
318
336
  } catch (err) {
319
337
  console.error("[ToolInvocationTracker] abort failed", err);
@@ -360,34 +378,70 @@ export class ToolInvocationTracker {
360
378
  return Object.fromEntries(
361
379
  Object.entries(tools).map(([name, tool]) => {
362
380
  const execute = tool.execute;
363
- if (execute === undefined) return [name, tool];
381
+ const streamCall = tool.streamCall;
382
+ if (execute === undefined && streamCall === undefined)
383
+ return [name, tool];
364
384
 
365
385
  const wrappedTool = {
366
386
  ...tool,
367
- execute: (
368
- ...[args, context]: Parameters<NonNullable<typeof execute>>
369
- ) => {
370
- if (this._skipExecuteStreamIds.has(context.toolCallId)) {
371
- // Pre-resolved tool call: never invoke the host's execute.
372
- // Returning a never-settling Promise keeps the executor's
373
- // pending entry alive but enqueues nothing.
374
- return new Promise(() => {}) as never;
375
- }
376
- return execute(args, context);
377
- },
387
+ ...(execute !== undefined && {
388
+ execute: (...[args, context]: Parameters<typeof execute>) => {
389
+ const executionId = getToolExecutionId(context);
390
+ const entry = this._captureExecution(
391
+ context.toolCallId,
392
+ executionId,
393
+ );
394
+ if (!entry || entry.skipExecute) {
395
+ return new Promise(() => {}) as never;
396
+ }
397
+ return execute(args, context);
398
+ },
399
+ }),
400
+ ...(streamCall !== undefined && {
401
+ streamCall: (
402
+ ...[reader, context]: Parameters<typeof streamCall>
403
+ ) => {
404
+ const executionId = getToolExecutionId(context);
405
+ const entry = this._captureExecution(
406
+ context.toolCallId,
407
+ executionId,
408
+ );
409
+ if (!entry) return;
410
+ return streamCall(reader, context);
411
+ },
412
+ }),
378
413
  } as Tool;
379
414
  return [name, wrappedTool];
380
415
  }),
381
416
  ) as Record<string, Tool>;
382
417
  }
383
418
 
419
+ private _captureExecution(
420
+ toolCallId: string,
421
+ executionId: symbol | undefined,
422
+ ): ToolCallEntry | undefined {
423
+ if (executionId === undefined) return undefined;
424
+ const entry = this._entries.get(toolCallId);
425
+ if (!entry?.controller) return undefined;
426
+ if (entry.executionId === undefined) entry.executionId = executionId;
427
+ return entry.executionId === executionId ? entry : undefined;
428
+ }
429
+
384
430
  // ──────────────── internal: execution lifecycle callbacks ────────────────
385
431
 
386
432
  private _onHumanInput(
387
433
  toolCallId: string,
388
434
  payload: unknown,
435
+ executionId?: symbol,
389
436
  ): Promise<unknown> {
390
437
  return new Promise<unknown>((resolve, reject) => {
438
+ // A discarded execution must not resurrect a status entry or park an
439
+ // unanswerable request after its id has been reused.
440
+ const entry = this._entries.get(toolCallId);
441
+ if (!entry?.controller || entry.executionId !== executionId) {
442
+ reject(new Error("Tool execution aborted"));
443
+ return;
444
+ }
391
445
  const previous = this._humanInput.get(toolCallId);
392
446
  if (previous) {
393
447
  try {
@@ -398,7 +452,11 @@ export class ToolInvocationTracker {
398
452
  // host rejection handler threw; ignore and proceed
399
453
  }
400
454
  }
401
- this._humanInput.set(toolCallId, { resolve, reject });
455
+ this._humanInput.set(toolCallId, {
456
+ executionId: executionId!,
457
+ resolve,
458
+ reject,
459
+ });
402
460
  this._setStatus(toolCallId, {
403
461
  type: "interrupt",
404
462
  payload: { type: "human", payload },
@@ -406,28 +464,42 @@ export class ToolInvocationTracker {
406
464
  });
407
465
  }
408
466
 
409
- private _onExecutionStart(toolCallId: string): void {
410
- if (this._skipExecuteStreamIds.has(toolCallId)) return;
467
+ private _onExecutionStart(
468
+ toolCallId: string,
469
+ executionId: symbol | undefined,
470
+ ): void {
471
+ if (!this._captureExecution(toolCallId, executionId)) return;
472
+ const entry = this._entries.get(toolCallId)!;
473
+ if (entry.skipExecute) return;
411
474
 
412
- this._executing.add(toolCallId);
475
+ this._executing.add(executionId!);
413
476
  this._setStatus(toolCallId, { type: "executing" });
414
477
  }
415
478
 
416
- private _onExecutionEnd(toolCallId: string): void {
417
- if (!this._executing.delete(toolCallId)) return;
479
+ private _onExecutionEnd(
480
+ toolCallId: string,
481
+ executionId: symbol | undefined,
482
+ ): void {
483
+ if (executionId === undefined || !this._executing.delete(executionId))
484
+ return;
418
485
 
419
- this._deleteStatus(toolCallId);
486
+ const entry = this._entries.get(toolCallId);
487
+ if (entry?.executionId === executionId) this._deleteStatus(toolCallId);
420
488
 
421
- if (this._executing.size === 0) {
422
- const resolvers = this._settledResolvers.splice(0);
423
- resolvers.forEach((resolve) => {
424
- try {
425
- resolve();
426
- } catch {
427
- // ignore — settled-resolver consumer threw
428
- }
429
- });
430
- }
489
+ const pending: SettledResolver[] = [];
490
+ this._settledResolvers.forEach(({ executionIds, resolve }) => {
491
+ if ([...executionIds].some((id) => this._executing.has(id))) {
492
+ pending.push({ executionIds, resolve });
493
+ return;
494
+ }
495
+ try {
496
+ resolve();
497
+ } catch {
498
+ // ignore — settled-resolver consumer threw
499
+ }
500
+ });
501
+ this._settledResolvers.length = 0;
502
+ this._settledResolvers.push(...pending);
431
503
  }
432
504
 
433
505
  private _handleResultChunk(chunk: {
@@ -439,14 +511,10 @@ export class ToolInvocationTracker {
439
511
  meta: { toolCallId: string; toolName: string };
440
512
  }): void {
441
513
  const toolCallId = chunk.meta.toolCallId;
514
+ const executionId = getToolExecutionId(chunk);
442
515
  const entry = this._entries.get(toolCallId);
443
516
 
444
- // Pre-resolved tool call whose entry has been cleared by `reset()`.
445
- // The post-abort cancellation chunk lands here after the entry is
446
- // gone; suppress via the long-lived skip-execute marker.
447
- if (!entry && this._skipExecuteStreamIds.has(toolCallId)) {
448
- return;
449
- }
517
+ if (!entry || entry.executionId !== executionId) return;
450
518
 
451
519
  // The host already set the result (via the live snapshot's
452
520
  // `setResponse` path). Suppress the executor's redundant emit.
@@ -538,14 +606,12 @@ export class ToolInvocationTracker {
538
606
  toolName,
539
607
  toolCallId,
540
608
  });
541
- if (skipExecute) {
542
- this._skipExecuteStreamIds.add(toolCallId);
543
- }
544
609
  const entry: ToolCallEntry = {
545
610
  toolName,
546
611
  controller: toolCallController,
547
612
  argsText: "",
548
613
  hasResult: false,
614
+ skipExecute,
549
615
  argsComplete: false,
550
616
  };
551
617
  this._entries.set(toolCallId, entry);
@@ -39,10 +39,12 @@ describe("ModelContext", () => {
39
39
  it("reflects modelName from a registered provider", async () => {
40
40
  const { sub, unmount } = render();
41
41
  try {
42
- sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
42
+ sub
43
+ .getValue()
44
+ .register(provider({ config: { modelName: "gpt-5.6-luna" } }));
43
45
  await tick();
44
46
 
45
- expect(sub.getValue().getState().modelName).toBe("gpt-4");
47
+ expect(sub.getValue().getState().modelName).toBe("gpt-5.6-luna");
46
48
  } finally {
47
49
  unmount();
48
50
  }
@@ -67,11 +69,15 @@ describe("ModelContext", () => {
67
69
  it("keeps the same state reference when an extra provider does not change the merged values", async () => {
68
70
  const { sub, unmount } = render();
69
71
  try {
70
- sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
72
+ sub
73
+ .getValue()
74
+ .register(provider({ config: { modelName: "gpt-5.6-luna" } }));
71
75
  await tick();
72
76
  const before = sub.getValue().getState();
73
77
 
74
- sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
78
+ sub
79
+ .getValue()
80
+ .register(provider({ config: { modelName: "gpt-5.6-luna" } }));
75
81
  await tick();
76
82
  const after = sub.getValue().getState();
77
83
 
@@ -86,9 +92,9 @@ describe("ModelContext", () => {
86
92
  try {
87
93
  const unsubscribe = sub
88
94
  .getValue()
89
- .register(provider({ config: { modelName: "gpt-4" } }));
95
+ .register(provider({ config: { modelName: "gpt-5.6-luna" } }));
90
96
  await tick();
91
- expect(sub.getValue().getState().modelName).toBe("gpt-4");
97
+ expect(sub.getValue().getState().modelName).toBe("gpt-5.6-luna");
92
98
 
93
99
  unsubscribe();
94
100
  await tick();
@@ -102,9 +108,11 @@ describe("ModelContext", () => {
102
108
  it("reflects modelName synchronously after register without awaiting", () => {
103
109
  const { sub, unmount } = render();
104
110
  try {
105
- sub.getValue().register(provider({ config: { modelName: "gpt-4" } }));
111
+ sub
112
+ .getValue()
113
+ .register(provider({ config: { modelName: "gpt-5.6-luna" } }));
106
114
 
107
- expect(sub.getValue().getState().modelName).toBe("gpt-4");
115
+ expect(sub.getValue().getState().modelName).toBe("gpt-5.6-luna");
108
116
  } finally {
109
117
  unmount();
110
118
  }