@assistant-ui/core 0.2.21 → 0.2.23

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 (345) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/speech.d.ts.map +1 -1
  3. package/dist/adapters/speech.js +9 -2
  4. package/dist/adapters/speech.js.map +1 -1
  5. package/dist/adapters/thread-history.d.ts +8 -0
  6. package/dist/adapters/thread-history.d.ts.map +1 -1
  7. package/dist/adapters/voice.d.ts.map +1 -1
  8. package/dist/adapters/voice.js +13 -5
  9. package/dist/adapters/voice.js.map +1 -1
  10. package/dist/internal.d.ts +5 -2
  11. package/dist/internal.js +5 -2
  12. package/dist/model-context/frame/host.d.ts +1 -0
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +41 -21
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/frame/provider.d.ts.map +1 -1
  17. package/dist/model-context/frame/provider.js +1 -0
  18. package/dist/model-context/frame/provider.js.map +1 -1
  19. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  20. package/dist/model-context/types.d.ts.map +1 -1
  21. package/dist/model-context/types.js +13 -4
  22. package/dist/model-context/types.js.map +1 -1
  23. package/dist/react/AssistantProvider.js.map +1 -1
  24. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  25. package/dist/react/adapters/LocalStorageThreadListAdapter.js +78 -50
  26. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  27. package/dist/react/client/Interactables.js.map +1 -1
  28. package/dist/react/client/Tools.d.ts.map +1 -1
  29. package/dist/react/client/interactable-model-context.d.ts.map +1 -1
  30. package/dist/react/client/interactable-model-context.js +1 -0
  31. package/dist/react/client/interactable-model-context.js.map +1 -1
  32. package/dist/react/interactables-legacy/useInteractableState.js +1 -0
  33. package/dist/react/interactables-legacy/useInteractableState.js.map +1 -1
  34. package/dist/react/model-context/useInteractable.js.map +1 -1
  35. package/dist/react/model-context/useInteractableState.js +1 -0
  36. package/dist/react/model-context/useInteractableState.js.map +1 -1
  37. package/dist/react/model-context/useInteractableVersions.js +1 -0
  38. package/dist/react/model-context/useInteractableVersions.js.map +1 -1
  39. package/dist/react/model-context/useToolArgsStatus.js +1 -0
  40. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  41. package/dist/react/primitive-hooks/useActionBarCopy.js +14 -13
  42. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  43. package/dist/react/primitive-hooks/useActionBarEdit.js +1 -0
  44. package/dist/react/primitive-hooks/useActionBarEdit.js.map +1 -1
  45. package/dist/react/primitive-hooks/useActionBarFeedback.js +1 -0
  46. package/dist/react/primitive-hooks/useActionBarFeedback.js.map +1 -1
  47. package/dist/react/primitive-hooks/useActionBarReload.js +1 -0
  48. package/dist/react/primitive-hooks/useActionBarReload.js.map +1 -1
  49. package/dist/react/primitive-hooks/useActionBarSpeak.js +1 -0
  50. package/dist/react/primitive-hooks/useActionBarSpeak.js.map +1 -1
  51. package/dist/react/primitive-hooks/useActionBarStopSpeaking.js +1 -0
  52. package/dist/react/primitive-hooks/useActionBarStopSpeaking.js.map +1 -1
  53. package/dist/react/primitive-hooks/useBranchPickerNext.js +1 -0
  54. package/dist/react/primitive-hooks/useBranchPickerNext.js.map +1 -1
  55. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +1 -0
  56. package/dist/react/primitive-hooks/useBranchPickerPrevious.js.map +1 -1
  57. package/dist/react/primitive-hooks/useComposerAddAttachment.d.ts.map +1 -1
  58. package/dist/react/primitive-hooks/useComposerAddAttachment.js +1 -0
  59. package/dist/react/primitive-hooks/useComposerAddAttachment.js.map +1 -1
  60. package/dist/react/primitive-hooks/useComposerCancel.js +1 -0
  61. package/dist/react/primitive-hooks/useComposerCancel.js.map +1 -1
  62. package/dist/react/primitive-hooks/useComposerDictate.js +1 -0
  63. package/dist/react/primitive-hooks/useComposerDictate.js.map +1 -1
  64. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  65. package/dist/react/primitive-hooks/useComposerSend.js +1 -0
  66. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  67. package/dist/react/primitive-hooks/useEditComposerCancel.js +1 -0
  68. package/dist/react/primitive-hooks/useEditComposerCancel.js.map +1 -1
  69. package/dist/react/primitive-hooks/useEditComposerSend.js +1 -0
  70. package/dist/react/primitive-hooks/useEditComposerSend.js.map +1 -1
  71. package/dist/react/primitive-hooks/useMessageBranching.js +1 -0
  72. package/dist/react/primitive-hooks/useMessageBranching.js.map +1 -1
  73. package/dist/react/primitive-hooks/useMessageReload.js +1 -0
  74. package/dist/react/primitive-hooks/useMessageReload.js.map +1 -1
  75. package/dist/react/primitive-hooks/useSuggestionTrigger.js +1 -0
  76. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  77. package/dist/react/primitive-hooks/useThreadListItemArchive.js +1 -0
  78. package/dist/react/primitive-hooks/useThreadListItemArchive.js.map +1 -1
  79. package/dist/react/primitive-hooks/useThreadListItemDelete.js +1 -0
  80. package/dist/react/primitive-hooks/useThreadListItemDelete.js.map +1 -1
  81. package/dist/react/primitive-hooks/useThreadListItemTrigger.js +1 -0
  82. package/dist/react/primitive-hooks/useThreadListItemTrigger.js.map +1 -1
  83. package/dist/react/primitive-hooks/useThreadListItemUnarchive.js +1 -0
  84. package/dist/react/primitive-hooks/useThreadListItemUnarchive.js.map +1 -1
  85. package/dist/react/primitive-hooks/useThreadListLoadMore.js +1 -0
  86. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  87. package/dist/react/primitive-hooks/useThreadListNew.js +1 -0
  88. package/dist/react/primitive-hooks/useThreadListNew.js.map +1 -1
  89. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.d.ts.map +1 -1
  90. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +1 -1
  91. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js.map +1 -1
  92. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  93. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  94. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  95. package/dist/react/primitives/generativeUI/GenerativeUI.d.ts.map +1 -1
  96. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  97. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  98. package/dist/react/primitives/message/MessageParts.d.ts +2 -1
  99. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  100. package/dist/react/primitives/message/MessageParts.js +46 -36
  101. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  102. package/dist/react/primitives/part/PartMessages.js.map +1 -1
  103. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  104. package/dist/react/primitives/thread/ThreadSuggestions.js.map +1 -1
  105. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  106. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.d.ts.map +1 -1
  107. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +18 -30
  108. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js.map +1 -1
  109. package/dist/react/providers/PartByIndexProvider.d.ts.map +1 -1
  110. package/dist/react/providers/PartByIndexProvider.js +18 -30
  111. package/dist/react/providers/PartByIndexProvider.js.map +1 -1
  112. package/dist/react/providers/TextMessagePartProvider.js +1 -0
  113. package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
  114. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  115. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +1 -0
  116. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  117. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +9 -2
  118. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  119. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +55 -12
  120. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  121. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +31 -1
  122. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +12 -2
  124. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  125. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -1
  126. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  127. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +3 -2
  128. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  129. package/dist/react/runtimes/cloud/auiV0.d.ts +12 -1
  130. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  131. package/dist/react/runtimes/cloud/auiV0.js +2 -1
  132. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  133. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +75 -73
  134. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  135. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  136. package/dist/react/runtimes/external-message-converter.js +2 -2
  137. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  138. package/dist/react/runtimes/useExternalStoreSharedOptions.js +1 -0
  139. package/dist/react/runtimes/useExternalStoreSharedOptions.js.map +1 -1
  140. package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
  141. package/dist/react/runtimes/useLocalRuntime.js +9 -1
  142. package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
  143. package/dist/runtime/api/assistant-runtime.d.ts +1 -1
  144. package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
  145. package/dist/runtime/api/assistant-runtime.js +1 -1
  146. package/dist/runtime/api/assistant-runtime.js.map +1 -1
  147. package/dist/runtime/api/attachment-runtime.d.ts +1 -1
  148. package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
  149. package/dist/runtime/api/attachment-runtime.js +1 -1
  150. package/dist/runtime/api/attachment-runtime.js.map +1 -1
  151. package/dist/runtime/api/composer-runtime.d.ts +2 -2
  152. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  153. package/dist/runtime/api/composer-runtime.js +2 -2
  154. package/dist/runtime/api/composer-runtime.js.map +1 -1
  155. package/dist/runtime/api/message-part-runtime.d.ts +4 -4
  156. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  157. package/dist/runtime/api/message-part-runtime.js +3 -3
  158. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  159. package/dist/runtime/api/message-runtime.d.ts +1 -1
  160. package/dist/runtime/api/message-runtime.d.ts.map +1 -1
  161. package/dist/runtime/api/message-runtime.js +6 -6
  162. package/dist/runtime/api/message-runtime.js.map +1 -1
  163. package/dist/runtime/api/thread-list-item-runtime.d.ts +1 -1
  164. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  165. package/dist/runtime/api/thread-list-item-runtime.js +4 -3
  166. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  167. package/dist/runtime/api/thread-list-runtime.d.ts +1 -1
  168. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  169. package/dist/runtime/api/thread-list-runtime.js +1 -1
  170. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  171. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  172. package/dist/runtime/base/base-composer-runtime-core.d.ts +4 -1
  173. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  174. package/dist/runtime/base/base-composer-runtime-core.js +41 -14
  175. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  176. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -1
  177. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  178. package/dist/runtime/base/base-thread-runtime-core.js +3 -2
  179. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  180. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +2 -2
  181. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  182. package/dist/runtime/base/default-edit-composer-runtime-core.js +6 -4
  183. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  184. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  185. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  186. package/dist/runtime/base/default-thread-composer-runtime-core.js +3 -3
  187. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  188. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  189. package/dist/runtime/utils/message-repository.js +4 -2
  190. package/dist/runtime/utils/message-repository.js.map +1 -1
  191. package/dist/runtime/utils/thread-message-like.js +4 -1
  192. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  193. package/dist/runtimes/external-store/external-store-runtime-core.d.ts.map +1 -1
  194. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -1
  195. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  196. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +1 -1
  197. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  198. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  199. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +12 -0
  200. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  201. package/dist/runtimes/internal.d.ts +2 -1
  202. package/dist/runtimes/internal.js +2 -1
  203. package/dist/runtimes/local/local-runtime-core.d.ts.map +1 -1
  204. package/dist/runtimes/local/local-thread-list-runtime-core.d.ts.map +1 -1
  205. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -0
  206. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  207. package/dist/runtimes/local/local-thread-runtime-core.js +49 -15
  208. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  209. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  210. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +4 -4
  211. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  212. package/dist/runtimes/remote-thread-list/optimistic-state.js +22 -9
  213. package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
  214. package/dist/runtimes/remote-thread-list/types.d.ts +1 -1
  215. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +32 -27
  216. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  217. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +3 -2
  218. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  219. package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
  220. package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
  221. package/dist/store/clients/no-op-composer-client.js +1 -0
  222. package/dist/store/clients/no-op-composer-client.js.map +1 -1
  223. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  224. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  225. package/dist/store/runtime-clients/message-part-runtime-client.d.ts.map +1 -1
  226. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  227. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  228. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  229. package/dist/store/runtime-clients/thread-list-runtime-client.js +1 -0
  230. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  231. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  232. package/dist/store/runtime-clients/thread-runtime-client.js +17 -15
  233. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  234. package/dist/store/scopes/thread.d.ts +1 -0
  235. package/dist/store/scopes/thread.d.ts.map +1 -1
  236. package/dist/subscribable/subscribable.d.ts +4 -4
  237. package/dist/subscribable/subscribable.d.ts.map +1 -1
  238. package/dist/subscribable/subscribable.js +14 -6
  239. package/dist/subscribable/subscribable.js.map +1 -1
  240. package/dist/tests/remote-thread-list-test-helpers.d.ts +2 -1
  241. package/dist/tests/remote-thread-list-test-helpers.d.ts.map +1 -1
  242. package/dist/tests/remote-thread-list-test-helpers.js +5 -2
  243. package/dist/tests/remote-thread-list-test-helpers.js.map +1 -1
  244. package/dist/types/attachment.d.ts +2 -1
  245. package/dist/types/attachment.d.ts.map +1 -1
  246. package/dist/types/attachment.js +6 -0
  247. package/dist/types/attachment.js.map +1 -0
  248. package/dist/types/message.d.ts +1 -0
  249. package/dist/types/message.d.ts.map +1 -1
  250. package/dist/types/message.js.map +1 -1
  251. package/dist/utils/data-url.d.ts +9 -0
  252. package/dist/utils/data-url.d.ts.map +1 -0
  253. package/dist/utils/data-url.js +14 -0
  254. package/dist/utils/data-url.js.map +1 -0
  255. package/dist/utils/notify-event-listeners.d.ts +6 -0
  256. package/dist/utils/notify-event-listeners.d.ts.map +1 -0
  257. package/dist/utils/notify-event-listeners.js +16 -0
  258. package/dist/utils/notify-event-listeners.js.map +1 -0
  259. package/package.json +10 -9
  260. package/src/adapters/speech.test.ts +88 -0
  261. package/src/adapters/speech.ts +17 -2
  262. package/src/adapters/thread-history.ts +8 -0
  263. package/src/adapters/voice.test.ts +130 -0
  264. package/src/adapters/voice.ts +26 -6
  265. package/src/internal.ts +6 -1
  266. package/src/model-context/frame/host.test.ts +147 -0
  267. package/src/model-context/frame/host.ts +56 -19
  268. package/src/model-context/frame/provider.test.ts +83 -0
  269. package/src/model-context/frame/provider.ts +1 -0
  270. package/src/model-context/types.ts +16 -6
  271. package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +82 -0
  272. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +108 -58
  273. package/src/react/primitive-hooks/useActionBarCopy.test.ts +79 -0
  274. package/src/react/primitive-hooks/useActionBarCopy.ts +4 -3
  275. package/src/react/primitives/chainOfThought/ChainOfThoughtParts.tsx +1 -3
  276. package/src/react/primitives/message/MessageParts.tsx +48 -35
  277. package/src/react/providers/ChainOfThoughtPartByIndexProvider.tsx +19 -2
  278. package/src/react/providers/PartByIndexProvider.tsx +16 -2
  279. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +18 -0
  280. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +1 -0
  281. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +68 -13
  282. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +34 -0
  283. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +69 -0
  284. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +27 -6
  285. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +16 -0
  286. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +7 -2
  287. package/src/react/runtimes/cloud/auiV0.ts +14 -0
  288. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +90 -88
  289. package/src/react/runtimes/external-message-converter.test.ts +346 -0
  290. package/src/react/runtimes/external-message-converter.test.tsx +21 -0
  291. package/src/react/runtimes/external-message-converter.ts +2 -2
  292. package/src/react/runtimes/useLocalRuntime.test.tsx +99 -0
  293. package/src/react/runtimes/useLocalRuntime.ts +13 -1
  294. package/src/runtime/api/assistant-runtime.ts +4 -1
  295. package/src/runtime/api/attachment-runtime.ts +8 -2
  296. package/src/runtime/api/composer-runtime.ts +9 -5
  297. package/src/runtime/api/message-part-runtime.ts +10 -3
  298. package/src/runtime/api/message-runtime.test.ts +81 -0
  299. package/src/runtime/api/message-runtime.ts +11 -8
  300. package/src/runtime/api/thread-list-item-runtime.ts +13 -3
  301. package/src/runtime/api/thread-list-runtime.ts +10 -2
  302. package/src/runtime/base/base-composer-runtime-core.ts +77 -31
  303. package/src/runtime/base/base-thread-runtime-core.ts +7 -2
  304. package/src/runtime/base/default-edit-composer-runtime-core.ts +18 -4
  305. package/src/runtime/base/default-thread-composer-runtime-core.ts +13 -3
  306. package/src/runtime/utils/message-repository.ts +19 -13
  307. package/src/runtime/utils/thread-message-like.ts +1 -0
  308. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +4 -1
  309. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +15 -0
  310. package/src/runtimes/internal.ts +3 -0
  311. package/src/runtimes/local/local-thread-runtime-core.test.ts +746 -1
  312. package/src/runtimes/local/local-thread-runtime-core.ts +89 -23
  313. package/src/runtimes/local/should-continue.test.ts +122 -0
  314. package/src/runtimes/remote-thread-list/optimistic-state.test.ts +63 -0
  315. package/src/runtimes/remote-thread-list/optimistic-state.ts +49 -17
  316. package/src/runtimes/remote-thread-list/types.ts +1 -1
  317. package/src/runtimes/tool-invocations/EDGE_CASES.md +13 -19
  318. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +128 -8
  319. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +42 -34
  320. package/src/store/clients/model-context-client.test.ts +21 -1
  321. package/src/store/runtime-clients/thread-runtime-client.ts +2 -1
  322. package/src/store/scopes/thread.ts +1 -0
  323. package/src/subscribable/subscribable.test.ts +58 -0
  324. package/src/subscribable/subscribable.ts +29 -11
  325. package/src/tests/MessageRepository.test.ts +12 -0
  326. package/src/tests/OptimisticState-list-race.test.ts +33 -0
  327. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-order.test.ts +223 -0
  328. package/src/tests/RemoteThreadListThreadListRuntimeCore-switchToThread-position.test.ts +121 -0
  329. package/src/tests/auiV0Encode.test.ts +74 -0
  330. package/src/tests/base-composer-runtime-core-addAttachment.test.ts +44 -0
  331. package/src/tests/base-composer-runtime-core-send.test.ts +433 -5
  332. package/src/tests/base-composer-runtime-core.test.ts +422 -0
  333. package/src/tests/default-edit-composer-runtime-core.test.ts +43 -5
  334. package/src/tests/event-subscription-listener-errors.test.ts +205 -0
  335. package/src/tests/external-store-thread-list-runtime-core.test.ts +6 -6
  336. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +575 -0
  337. package/src/tests/external-store-thread-runtime-core.test.ts +55 -1
  338. package/src/tests/remote-thread-list-test-helpers.ts +14 -7
  339. package/src/tests/thread-message-like.test.ts +57 -0
  340. package/src/types/attachment.ts +5 -0
  341. package/src/types/message.ts +1 -0
  342. package/src/utils/data-url.test.ts +50 -0
  343. package/src/utils/data-url.ts +9 -0
  344. package/src/utils/notify-event-listeners.ts +30 -0
  345. package/src/tests/OptimisticState-delete-crash.test.ts +0 -208
@@ -68,6 +68,45 @@ export class LocalThreadRuntimeCore
68
68
  private _queue: MessageQueueController | null = null;
69
69
  private _queueRunInFlight = false;
70
70
 
71
+ private _historyWrites = new Map<string, Promise<void>>();
72
+
73
+ // Writes for one message id must land in issue order; an earlier paused
74
+ // snapshot arriving after the terminal write would resurrect the pause.
75
+ private _chainHistoryWrite(
76
+ id: string,
77
+ write: () => Promise<void>,
78
+ ): Promise<void> {
79
+ const next = (this._historyWrites.get(id) ?? Promise.resolve()).then(
80
+ write,
81
+ write,
82
+ );
83
+ const stored = next.then(
84
+ () => {},
85
+ () => {},
86
+ );
87
+ this._historyWrites.set(id, stored);
88
+ void stored.then(() => {
89
+ if (this._historyWrites.get(id) === stored) {
90
+ this._historyWrites.delete(id);
91
+ }
92
+ });
93
+ return next;
94
+ }
95
+
96
+ // A decision recorded on a still-paused message must reach history before
97
+ // the run resumes, or a refresh would restore the message without it.
98
+ private _persistPausedMessage(
99
+ parentId: string | null,
100
+ message: ThreadAssistantMessage,
101
+ ) {
102
+ if (message.status?.type !== "requires-action") return;
103
+ const history = this._options.adapters.history;
104
+ if (!history?.update) return;
105
+ const update = history.update.bind(history);
106
+ const item = { parentId, message, runConfig: this._lastRunConfig };
107
+ this._chainHistoryWrite(message.id, () => update(item)).catch(() => {});
108
+ }
109
+
71
110
  public readonly isDisabled = false;
72
111
  public readonly isSendDisabled = false;
73
112
 
@@ -351,6 +390,12 @@ export class LocalThreadRuntimeCore
351
390
  ): Promise<void> {
352
391
  this._notifyEventSubscribers("runStart", {});
353
392
 
393
+ // A run entered on a requires-action message resumes a pause an
394
+ // update-capable adapter already holds (written at pause time or loaded).
395
+ const alreadyPersisted =
396
+ message.status?.type === "requires-action" &&
397
+ this._options.adapters.history?.update !== undefined;
398
+
354
399
  try {
355
400
  // mark busy for runs not started through the queue (regenerate, resume)
356
401
  this._queue?.notifyBusy();
@@ -364,6 +409,7 @@ export class LocalThreadRuntimeCore
364
409
  parentId,
365
410
  message,
366
411
  runConfig,
412
+ alreadyPersisted,
367
413
  runCallback,
368
414
  );
369
415
  runCallback = undefined;
@@ -406,6 +452,7 @@ export class LocalThreadRuntimeCore
406
452
  parentId: string | null,
407
453
  message: ThreadAssistantMessage,
408
454
  runConfig: RunConfig | undefined,
455
+ alreadyPersisted: boolean,
409
456
  runCallback?: ChatModelAdapter["run"],
410
457
  ) {
411
458
  const messages = parentId ? this.repository.getMessages(parentId) : [];
@@ -442,7 +489,7 @@ export class LocalThreadRuntimeCore
442
489
  ? {
443
490
  metadata: {
444
491
  ...message.metadata,
445
- ...(m.metadata.unstable_state
492
+ ...(m.metadata.unstable_state !== undefined
446
493
  ? { unstable_state: m.metadata.unstable_state }
447
494
  : undefined),
448
495
  ...(annotations
@@ -471,17 +518,18 @@ export class LocalThreadRuntimeCore
471
518
 
472
519
  const maxSteps = this._options.maxSteps ?? 2;
473
520
 
474
- const steps = message.metadata?.steps?.length ?? 0;
475
- if (steps >= maxSteps) {
476
- // reached max tool steps
477
- updateMessage({
478
- status: {
479
- type: "incomplete",
480
- reason: "tool-calls",
481
- },
482
- });
483
- return message;
484
- } else {
521
+ try {
522
+ const steps = message.metadata?.steps?.length ?? 0;
523
+ if (steps >= maxSteps) {
524
+ updateMessage({
525
+ status: {
526
+ type: "incomplete",
527
+ reason: "tool-calls",
528
+ },
529
+ });
530
+ return message;
531
+ }
532
+
485
533
  updateMessage({
486
534
  status: {
487
535
  type: "running",
@@ -491,9 +539,7 @@ export class LocalThreadRuntimeCore
491
539
  // Switch to the new message branch right after adding it for the first time
492
540
  this.repository.resetHead(message.id);
493
541
  this._notifySubscribers();
494
- }
495
542
 
496
- try {
497
543
  this._lastRunConfig = runConfig ?? {};
498
544
  // unstable_composerMetadata is composer-only (stamped onto the outgoing
499
545
  // message); never expose it to the chat-model adapter's run context.
@@ -537,7 +583,9 @@ export class LocalThreadRuntimeCore
537
583
 
538
584
  if (message.status.type === "running") {
539
585
  updateMessage({
540
- status: { type: "complete", reason: "unknown" },
586
+ status: abortSignal.aborted
587
+ ? { type: "incomplete", reason: "cancelled" }
588
+ : { type: "complete", reason: "unknown" },
541
589
  });
542
590
  }
543
591
  } catch (e) {
@@ -563,15 +611,29 @@ export class LocalThreadRuntimeCore
563
611
  } finally {
564
612
  this.abortController = null;
565
613
 
566
- if (
614
+ const history = this._options.adapters.history;
615
+ const item = {
616
+ parentId,
617
+ message,
618
+ runConfig: this._lastRunConfig,
619
+ };
620
+ const isTerminal =
567
621
  message.status.type === "complete" ||
568
- message.status.type === "incomplete"
569
- ) {
570
- await this._options.adapters.history?.append({
571
- parentId,
572
- message: message,
573
- runConfig: this._lastRunConfig,
574
- });
622
+ message.status.type === "incomplete";
623
+ const isPausing =
624
+ message.status.type === "requires-action" &&
625
+ !shouldContinue(message, this._options.unstable_humanToolNames);
626
+
627
+ // Pauses are written only for adapters that can rewrite the entry later;
628
+ // an append-only adapter would strand a half-finished run in history.
629
+ if (isTerminal || (isPausing && history?.update)) {
630
+ const write =
631
+ alreadyPersisted && history?.update
632
+ ? history.update.bind(history)
633
+ : history?.append.bind(history);
634
+ if (write) {
635
+ await this._chainHistoryWrite(message.id, () => write(item));
636
+ }
575
637
  }
576
638
  }
577
639
  return message;
@@ -641,6 +703,8 @@ export class LocalThreadRuntimeCore
641
703
  shouldContinue(message, this._options.unstable_humanToolNames)
642
704
  ) {
643
705
  this._runLoop(parentId, message, this._lastRunConfig).catch(() => {});
706
+ } else if (added) {
707
+ this._persistPausedMessage(parentId, message);
644
708
  }
645
709
  }
646
710
 
@@ -719,6 +783,8 @@ export class LocalThreadRuntimeCore
719
783
  shouldContinue(message, this._options.unstable_humanToolNames)
720
784
  ) {
721
785
  this._runLoop(parentId, message, this._lastRunConfig).catch(() => {});
786
+ } else {
787
+ this._persistPausedMessage(parentId, message);
722
788
  }
723
789
  }
724
790
  }
@@ -0,0 +1,122 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { ThreadAssistantMessage } from "../../types/message";
3
+ import { shouldContinue } from "./should-continue";
4
+
5
+ const makeMessage = (
6
+ overrides: Partial<ThreadAssistantMessage>,
7
+ ): ThreadAssistantMessage => ({
8
+ id: "msg-1",
9
+ role: "assistant",
10
+ createdAt: new Date(),
11
+ content: [],
12
+ status: { type: "complete", reason: "stop" },
13
+ metadata: {
14
+ unstable_state: null,
15
+ unstable_annotations: [],
16
+ unstable_data: [],
17
+ steps: [],
18
+ custom: {},
19
+ },
20
+ ...overrides,
21
+ });
22
+
23
+ const toolCall = (toolName: string, result?: unknown) => ({
24
+ type: "tool-call" as const,
25
+ toolCallId: `call-${toolName}`,
26
+ toolName,
27
+ args: {},
28
+ argsText: "{}",
29
+ ...(result !== undefined ? { result } : {}),
30
+ });
31
+
32
+ describe("shouldContinue", () => {
33
+ it("returns false when status is not requires-action", () => {
34
+ const msg = makeMessage({
35
+ status: { type: "complete", reason: "stop" },
36
+ });
37
+ expect(shouldContinue(msg, undefined)).toBe(false);
38
+ });
39
+
40
+ it("returns false when reason is not tool-calls", () => {
41
+ const msg = makeMessage({
42
+ status: { type: "requires-action", reason: "interrupt" },
43
+ });
44
+ expect(shouldContinue(msg, undefined)).toBe(false);
45
+ });
46
+
47
+ it("returns true when all tool calls have results (no humanToolNames)", () => {
48
+ const msg = makeMessage({
49
+ status: { type: "requires-action", reason: "tool-calls" },
50
+ content: [toolCall("search", "found it"), toolCall("calculate", 42)],
51
+ });
52
+ expect(shouldContinue(msg, undefined)).toBe(true);
53
+ });
54
+
55
+ it("returns false when some tool calls have no result (no humanToolNames)", () => {
56
+ const msg = makeMessage({
57
+ status: { type: "requires-action", reason: "tool-calls" },
58
+ content: [toolCall("search", "found it"), toolCall("calculate")],
59
+ });
60
+ expect(shouldContinue(msg, undefined)).toBe(false);
61
+ });
62
+
63
+ it("returns true when unresolved tool call is not a human tool", () => {
64
+ const msg = makeMessage({
65
+ status: { type: "requires-action", reason: "tool-calls" },
66
+ content: [toolCall("auto-tool")],
67
+ });
68
+ expect(shouldContinue(msg, ["human-approval"])).toBe(true);
69
+ });
70
+
71
+ it("returns false when unresolved tool call IS a human tool", () => {
72
+ const msg = makeMessage({
73
+ status: { type: "requires-action", reason: "tool-calls" },
74
+ content: [toolCall("human-approval")],
75
+ });
76
+ expect(shouldContinue(msg, ["human-approval"])).toBe(false);
77
+ });
78
+
79
+ it("returns true for non-tool-call content parts", () => {
80
+ const msg = makeMessage({
81
+ status: { type: "requires-action", reason: "tool-calls" },
82
+ content: [
83
+ { type: "text", text: "Hello" } as any,
84
+ toolCall("search", "done"),
85
+ ],
86
+ });
87
+ expect(shouldContinue(msg, undefined)).toBe(true);
88
+ });
89
+
90
+ it("returns false while a tool call has a pending approval", () => {
91
+ const msg = makeMessage({
92
+ status: { type: "requires-action", reason: "tool-calls" },
93
+ content: [{ ...toolCall("deploy"), approval: { id: "a1" } }],
94
+ });
95
+ expect(shouldContinue(msg, undefined)).toBe(false);
96
+ expect(shouldContinue(msg, ["human-approval"])).toBe(false);
97
+ });
98
+
99
+ it("returns true when a decided approval has no result", () => {
100
+ const msg = makeMessage({
101
+ status: { type: "requires-action", reason: "tool-calls" },
102
+ content: [
103
+ { ...toolCall("deploy"), approval: { id: "a1", approved: true } },
104
+ ],
105
+ });
106
+ expect(shouldContinue(msg, undefined)).toBe(true);
107
+ expect(shouldContinue(msg, ["human-approval"])).toBe(true);
108
+ });
109
+
110
+ it("exempts approval-gated tool calls from the human tool result requirement", () => {
111
+ const msg = makeMessage({
112
+ status: { type: "requires-action", reason: "tool-calls" },
113
+ content: [
114
+ {
115
+ ...toolCall("human-approval"),
116
+ approval: { id: "a1", approved: true },
117
+ },
118
+ ],
119
+ });
120
+ expect(shouldContinue(msg, ["human-approval"])).toBe(true);
121
+ });
122
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { OptimisticState } from "./optimistic-state";
3
+
4
+ const deferred = () => {
5
+ let resolve!: () => void;
6
+ const promise = new Promise<void>((resolvePromise) => {
7
+ resolve = resolvePromise;
8
+ });
9
+
10
+ return { promise, resolve };
11
+ };
12
+
13
+ describe("OptimisticState", () => {
14
+ it("preserves invocation order when optimistic updates resolve in order", async () => {
15
+ const state = new OptimisticState({ title: "Untitled" });
16
+ const firstRequest = deferred();
17
+ const secondRequest = deferred();
18
+
19
+ const firstUpdate = state.optimisticUpdate({
20
+ execute: () => firstRequest.promise,
21
+ optimistic: (value) => ({ ...value, title: "Project Alpha" }),
22
+ });
23
+ const secondUpdate = state.optimisticUpdate({
24
+ execute: () => secondRequest.promise,
25
+ optimistic: (value) => ({ ...value, title: "Project Beta" }),
26
+ });
27
+
28
+ expect(state.value.title).toBe("Project Beta");
29
+
30
+ firstRequest.resolve();
31
+ await firstUpdate;
32
+ expect(state.value.title).toBe("Project Beta");
33
+
34
+ secondRequest.resolve();
35
+ await secondUpdate;
36
+
37
+ expect(state.value.title).toBe("Project Beta");
38
+ });
39
+
40
+ it("preserves invocation order when optimistic updates resolve out of order", async () => {
41
+ const state = new OptimisticState({ title: "Untitled" });
42
+ const firstRequest = deferred();
43
+ const secondRequest = deferred();
44
+
45
+ const firstUpdate = state.optimisticUpdate({
46
+ execute: () => firstRequest.promise,
47
+ optimistic: (value) => ({ ...value, title: "Project Alpha" }),
48
+ });
49
+ const secondUpdate = state.optimisticUpdate({
50
+ execute: () => secondRequest.promise,
51
+ optimistic: (value) => ({ ...value, title: "Project Beta" }),
52
+ });
53
+
54
+ secondRequest.resolve();
55
+ await secondUpdate;
56
+ expect(state.value.title).toBe("Project Beta");
57
+
58
+ firstRequest.resolve();
59
+ await firstUpdate;
60
+
61
+ expect(state.value.title).toBe("Project Beta");
62
+ });
63
+ });
@@ -14,9 +14,15 @@ type Transform<TState, TResult> = {
14
14
  };
15
15
 
16
16
  type PendingTransform<TState, TResult> = Transform<TState, TResult> & {
17
+ order: number;
17
18
  task: Promise<TResult>;
18
19
  };
19
20
 
21
+ type CompletedOptimistic<TState> = {
22
+ order: number;
23
+ optimistic: (state: TState) => TState;
24
+ };
25
+
20
26
  const pipeTransforms = <TState, TExtra>(
21
27
  initialState: TState,
22
28
  extraParam: TExtra,
@@ -32,14 +38,16 @@ export class OptimisticState<TState> extends BaseSubscribable {
32
38
  [];
33
39
 
34
40
  /**
35
- * `optimistic` callbacks from transforms that have already resolved.
36
- * Re-applied after every `then` callback so that a wholesale state
37
- * replacement (e.g. list()) cannot erase earlier completed effects
38
- * (e.g. delete). Cleared when no pending transforms remain.
41
+ * Completed optimistic callbacks remain active while any transform is
42
+ * pending, so later state replacements cannot erase them. Invocation order
43
+ * determines which overlapping optimistic update wins.
39
44
  *
40
45
  * Correctness requirement: `optimistic` callbacks must be idempotent.
41
46
  */
42
- private readonly _completedOptimistics: Array<(state: TState) => TState> = [];
47
+ private readonly _completedOptimistics: Array<CompletedOptimistic<TState>> =
48
+ [];
49
+
50
+ private _nextTransformOrder = 0;
43
51
 
44
52
  private _baseValue: TState;
45
53
  private _cachedValue: TState;
@@ -51,12 +59,25 @@ export class OptimisticState<TState> extends BaseSubscribable {
51
59
  }
52
60
 
53
61
  private _updateState(): void {
54
- this._cachedValue = this._pendingTransforms.reduce((state, transform) => {
55
- return pipeTransforms(state, transform.task, [
56
- transform.loading,
57
- transform.optimistic,
58
- ]);
59
- }, this._baseValue);
62
+ const activeTransforms = [
63
+ ...this._pendingTransforms.map((transform) => ({
64
+ order: transform.order,
65
+ apply: (state: TState) =>
66
+ pipeTransforms(state, transform.task, [
67
+ transform.loading,
68
+ transform.optimistic,
69
+ ]),
70
+ })),
71
+ ...this._completedOptimistics.map(({ order, optimistic }) => ({
72
+ order,
73
+ apply: optimistic,
74
+ })),
75
+ ].sort((a, b) => a.order - b.order);
76
+
77
+ this._cachedValue = activeTransforms.reduce(
78
+ (state, transform) => transform.apply(state),
79
+ this._baseValue,
80
+ );
60
81
 
61
82
  this._notifySubscribers();
62
83
  }
@@ -77,8 +98,13 @@ export class OptimisticState<TState> extends BaseSubscribable {
77
98
  public async optimisticUpdate<TResult>(
78
99
  transform: Transform<TState, TResult>,
79
100
  ): Promise<TResult> {
101
+ const order = this._nextTransformOrder++;
80
102
  const task = transform.execute();
81
- const pendingTransform = { ...transform, task };
103
+ const pendingTransform = {
104
+ ...transform,
105
+ order,
106
+ task,
107
+ };
82
108
  try {
83
109
  this._pendingTransforms.push(pendingTransform);
84
110
  this._updateState();
@@ -89,14 +115,20 @@ export class OptimisticState<TState> extends BaseSubscribable {
89
115
  transform.then,
90
116
  ]);
91
117
 
92
- // Re-apply previously completed optimistic callbacks so that a
93
- // then() that does wholesale replacement cannot erase their effects.
94
- for (const fn of this._completedOptimistics) {
95
- this._baseValue = fn(this._baseValue);
118
+ // `then` can replace state with a stale snapshot, so replay every
119
+ // completed effect; otherwise replay only newer overlapping effects.
120
+ for (const completed of this._completedOptimistics) {
121
+ if (transform.then || completed.order > pendingTransform.order) {
122
+ this._baseValue = completed.optimistic(this._baseValue);
123
+ }
96
124
  }
97
125
 
98
126
  if (transform.optimistic) {
99
- this._completedOptimistics.push(transform.optimistic);
127
+ this._completedOptimistics.push({
128
+ order: pendingTransform.order,
129
+ optimistic: transform.optimistic,
130
+ });
131
+ this._completedOptimistics.sort((a, b) => a.order - b.order);
100
132
  }
101
133
 
102
134
  return result;
@@ -5,7 +5,7 @@ import type { AssistantStream } from "assistant-stream";
5
5
 
6
6
  export type RemoteThreadInitializeResponse = {
7
7
  remoteId: string;
8
- externalId: string | undefined;
8
+ externalId?: string | undefined;
9
9
  };
10
10
 
11
11
  export type RemoteThreadMetadata = {
@@ -41,6 +41,15 @@ Subsequent snapshots that *are* prefixes of the new (regressed) snapshot
41
41
  also won't be appended, because `entry.argsText` still points at the
42
42
  pre-regression value used for delta calculation.
43
43
 
44
+ The args stream closes only when the controller's *streamed* content
45
+ (`entry.argsText`) is complete, not when a later snapshot is. A divergent
46
+ snapshot can be complete while the controller still holds an incomplete
47
+ stale prefix; closing on the snapshot would parse that stale prefix and
48
+ auto-submit a bogus parse-error result (resuming the host graph and
49
+ abandoning a pending interrupt). Gating the close on the streamed content
50
+ leaves the stream open until the prefix itself completes, so no stale
51
+ parse runs and no error result is fabricated from divergent args.
52
+
44
53
  ### A.3. Args complete then equivalent-JSON key reorder
45
54
  Both old and new `argsText` parse to equivalent JSON values (e.g. keys
46
55
  reordered by the backend). The tracker updates its tracked `argsText`
@@ -53,9 +62,10 @@ existing `streamCall` keeps its original args view.
53
62
 
54
63
  ### A.5. First resolution (`result` becomes defined)
55
64
  The tracker calls `setResponse` on the active controller and closes it.
56
- `reader.response.get()` resolves. If the tool also had a frontend
57
- `execute`, the executor is short-circuited via `_skipExecuteStreamIds`.
58
- Single fire.
65
+ The backend result is emitted before the args stream closes, so a stale
66
+ args parse failure cannot replace it. `reader.response.get()` resolves.
67
+ If the tool also had a frontend `execute`, the executor is short-circuited
68
+ via `_skipExecuteStreamIds`. Single fire.
59
69
 
60
70
  ### A.6. Previously-resolved tool's `result` is replaced
61
71
  Silently ignored — `entry.hasResult` short-circuits both the
@@ -167,22 +177,6 @@ keep the tracker dead with a visible error to avoid restart loops.
167
177
 
168
178
  ## Known limitations
169
179
 
170
- ### Result delivery after args regression (A.2 + A.5 in the same snapshot)
171
- When a snapshot has both a regressed `argsText` *and* a backend result
172
- on the same tool call, `activeController.setResponse(result)` closes
173
- `argsText` before enqueueing the result chunk. The args-text-finish
174
- chunk reaches `ToolExecutionStream` first, attempts to parse the
175
- (stale) accumulated argsText, fails, and emits a parse-error result
176
- that beats the backend result to the reader's response promise.
177
-
178
- The tracker's `entry.hasResult` short-circuit *does* suppress both
179
- result chunks at the `onResult` callback level (no double-fire), but
180
- the reader's `response.get()` already resolved with the parse error.
181
-
182
- Fixable upstream in `ToolCallStreamControllerImpl.setResponse` by
183
- enqueueing the result chunk before closing argsText. Tracked separately;
184
- out of scope for the tracker layer.
185
-
186
180
  ### Host callback throws
187
181
  `onResult` and `onStatusesChange` are invoked through wrappers that
188
182
  catch and log. The tracker continues to function; the host's bad