@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
@@ -728,50 +728,63 @@ const EMPTY_RUNNING_TEXT_PART: Extract<EnrichedPartState, { type: "text" }> =
728
728
  * `<MessagePrimitive.GroupedParts>`. Returns whatever `children`
729
729
  * returns — callers decide how to handle a `null` return.
730
730
  */
731
- export const MessagePartChildren: FC<{
731
+ type MessagePartChildrenProps = {
732
732
  index: number;
733
733
  children: (value: { part: EnrichedPartState }) => ReactNode;
734
- }> = ({ index, children }) => {
734
+ };
735
+
736
+ const MessagePartChildrenInner: FC<
737
+ Pick<MessagePartChildrenProps, "children">
738
+ > = ({ children }) => {
735
739
  const aui = useAui();
736
740
  // Subscribed (not snapshotted like `tools`) so fallbacks registered
737
741
  // after the first render trigger a re-render and `hasUI` re-evaluates.
738
742
  const dataRenderers = useAuiState((s) => s.dataRenderers);
739
743
 
744
+ return (
745
+ <RenderChildrenWithAccessor
746
+ getItemState={(client) => client.part().getState()}
747
+ >
748
+ {(getItem) =>
749
+ children({
750
+ get part() {
751
+ const state = getItem();
752
+ if (state.type === "tool-call") {
753
+ const toolsState = aui.tools().getState();
754
+ const hasUI = resolveToolRender(toolsState, state) !== null;
755
+ const partMethods = aui.part();
756
+ return {
757
+ ...state,
758
+ toolUI: hasUI ? <RegisteredToolUI /> : null,
759
+ addResult: partMethods.addToolResult,
760
+ resume: partMethods.resumeToolCall,
761
+ respondToApproval: partMethods.respondToToolApproval,
762
+ };
763
+ }
764
+ if (state.type === "data") {
765
+ const hasUI =
766
+ getDataRenderer(dataRenderers, state.name, undefined) !==
767
+ undefined;
768
+ return {
769
+ ...state,
770
+ dataRendererUI: hasUI ? <RegisteredDataRendererUI /> : null,
771
+ };
772
+ }
773
+ return state;
774
+ },
775
+ })
776
+ }
777
+ </RenderChildrenWithAccessor>
778
+ );
779
+ };
780
+
781
+ export const MessagePartChildren: FC<MessagePartChildrenProps> = ({
782
+ index,
783
+ children,
784
+ }) => {
740
785
  return (
741
786
  <PartByIndexProvider index={index}>
742
- <RenderChildrenWithAccessor
743
- getItemState={(aui) => aui.message().part({ index }).getState()}
744
- >
745
- {(getItem) =>
746
- children({
747
- get part() {
748
- const state = getItem();
749
- if (state.type === "tool-call") {
750
- const toolsState = aui.tools().getState();
751
- const hasUI = resolveToolRender(toolsState, state) !== null;
752
- const partMethods = aui.message().part({ index });
753
- return {
754
- ...state,
755
- toolUI: hasUI ? <RegisteredToolUI /> : null,
756
- addResult: partMethods.addToolResult,
757
- resume: partMethods.resumeToolCall,
758
- respondToApproval: partMethods.respondToToolApproval,
759
- };
760
- }
761
- if (state.type === "data") {
762
- const hasUI =
763
- getDataRenderer(dataRenderers, state.name, undefined) !==
764
- undefined;
765
- return {
766
- ...state,
767
- dataRendererUI: hasUI ? <RegisteredDataRendererUI /> : null,
768
- };
769
- }
770
- return state;
771
- },
772
- })
773
- }
774
- </RenderChildrenWithAccessor>
787
+ <MessagePartChildrenInner>{children}</MessagePartChildrenInner>
775
788
  </PartByIndexProvider>
776
789
  );
777
790
  };
@@ -1,16 +1,33 @@
1
- import type { FC, PropsWithChildren } from "react";
1
+ import { useMemo, type FC, type PropsWithChildren } from "react";
2
2
  import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
3
+ import type { PartMethods } from "../../store/scopes/part";
3
4
 
4
5
  export const ChainOfThoughtPartByIndexProvider: FC<
5
6
  PropsWithChildren<{
6
7
  index: number;
7
8
  }>
8
9
  > = ({ index, children }) => {
10
+ const lastPartRef = useMemo(
11
+ () => ({ index, current: null as PartMethods | null }),
12
+ [index],
13
+ );
9
14
  const aui = useAui({
10
15
  part: Derived({
11
16
  source: "chainOfThought",
12
17
  query: { type: "index", index },
13
- get: (aui) => aui.chainOfThought().part({ index }),
18
+ get: (aui) => {
19
+ const chainOfThought = aui.chainOfThought();
20
+ if (
21
+ index >= chainOfThought.getState().parts.length &&
22
+ lastPartRef.current
23
+ ) {
24
+ return lastPartRef.current;
25
+ }
26
+
27
+ const part = chainOfThought.part({ index });
28
+ lastPartRef.current = part;
29
+ return part;
30
+ },
14
31
  }),
15
32
  });
16
33
 
@@ -1,16 +1,30 @@
1
- import type { FC, PropsWithChildren } from "react";
1
+ import { useMemo, type FC, type PropsWithChildren } from "react";
2
2
  import { useAui, AuiProvider, Derived } from "@assistant-ui/store";
3
+ import type { PartMethods } from "../../store/scopes/part";
3
4
 
4
5
  export const PartByIndexProvider: FC<
5
6
  PropsWithChildren<{
6
7
  index: number;
7
8
  }>
8
9
  > = ({ index, children }) => {
10
+ const lastPartRef = useMemo(
11
+ () => ({ index, current: null as PartMethods | null }),
12
+ [index],
13
+ );
9
14
  const aui = useAui({
10
15
  part: Derived({
11
16
  source: "message",
12
17
  query: { type: "index", index },
13
- get: (aui) => aui.message().part({ index }),
18
+ get: (aui) => {
19
+ const message = aui.message();
20
+ if (index >= message.getState().parts.length && lastPartRef.current) {
21
+ return lastPartRef.current;
22
+ }
23
+
24
+ const part = message.part({ index });
25
+ lastPartRef.current = part;
26
+ return part;
27
+ },
14
28
  }),
15
29
  });
16
30
 
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { ThreadListRuntimeCore } from "../../runtime/interfaces/thread-list-runtime-core";
3
+ import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
4
+
5
+ describe("RemoteThreadListHookInstanceManager", () => {
6
+ it("rejects a pending start when the thread runtime is stopped", async () => {
7
+ const manager = new RemoteThreadListHookInstanceManager(() => {
8
+ throw new Error("Runtime hook should not render during this test");
9
+ }, {} as ThreadListRuntimeCore);
10
+
11
+ const startPromise = manager.startThreadRuntime("thread-1");
12
+ manager.stopThreadRuntime("thread-1");
13
+
14
+ await expect(startPromise).rejects.toThrow(
15
+ "Thread was deleted before runtime was started",
16
+ );
17
+ });
18
+ });
@@ -85,6 +85,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
85
85
  public stopThreadRuntime(threadId: string) {
86
86
  this.instances.delete(threadId);
87
87
  this.useAliveThreadsKeysChanged.setState({}, true);
88
+ this._notifySubscribers();
88
89
  }
89
90
 
90
91
  public setRuntimeHook(newRuntimeHook: RemoteThreadListHook) {
@@ -44,6 +44,8 @@ export class RemoteThreadListThreadListRuntimeCore
44
44
  private _loadThreadsPromise: Promise<void> | undefined;
45
45
  private _loadMorePromise: Promise<void> | undefined;
46
46
  private _loadGeneration = 0;
47
+ private _switchGeneration = 0;
48
+ private _switchTask: Promise<void> | undefined;
47
49
 
48
50
  private _mainThreadId!: string;
49
51
  private readonly _state = new OptimisticState<RemoteThreadState>({
@@ -307,15 +309,28 @@ export class RemoteThreadListThreadListRuntimeCore
307
309
  return getThreadData(this._state.value, threadIdOrRemoteId);
308
310
  }
309
311
 
310
- public async switchToThread(
312
+ public switchToThread(
311
313
  threadIdOrRemoteId: string,
312
314
  options?: { unarchive?: boolean },
315
+ ): Promise<void> {
316
+ const generation = ++this._switchGeneration;
317
+ const task = this._switchToThread(threadIdOrRemoteId, options, generation);
318
+ this._switchTask = task;
319
+ return task;
320
+ }
321
+
322
+ private async _switchToThread(
323
+ threadIdOrRemoteId: string,
324
+ options: { unarchive?: boolean } | undefined,
325
+ generation: number,
313
326
  ): Promise<void> {
314
327
  let data = this.getItemById(threadIdOrRemoteId);
315
328
 
316
329
  if (!data) {
317
330
  const remoteMetadata =
318
331
  await this._options.adapter.fetch(threadIdOrRemoteId);
332
+ if (generation !== this._switchGeneration) return;
333
+
319
334
  const state = this._state.value;
320
335
  const mappingId = createThreadMappingId(remoteMetadata.remoteId);
321
336
 
@@ -341,22 +356,36 @@ export class RemoteThreadListThreadListRuntimeCore
341
356
  [remoteMetadata.remoteId]: mappingId,
342
357
  };
343
358
 
344
- // Filter both arrays first so a concurrent `list()` can't leave the id
345
- // duplicated or under the wrong status.
359
+ // A concurrent `list()` may already have placed this thread; keep that
360
+ // position and only merge metadata. A genuinely absent thread stays
361
+ // appended: it may live on an unloaded page, and a prepend would pin it
362
+ // above newer threads permanently since `classifyThreads` skips ids it
363
+ // has already seen. Filtering both arrays first still prevents
364
+ // duplication or a wrong-status entry from `list()`.
365
+ const remoteId = remoteMetadata.remoteId;
366
+ const wasInTarget =
367
+ remoteMetadata.status === "regular"
368
+ ? state.threadIds.includes(remoteId)
369
+ : state.archivedThreadIds.includes(remoteId);
370
+
346
371
  const threadIdsWithoutRemote = state.threadIds.filter(
347
- (id) => id !== remoteMetadata.remoteId,
372
+ (id) => id !== remoteId,
348
373
  );
349
374
  const archivedThreadIdsWithoutRemote = state.archivedThreadIds.filter(
350
- (id) => id !== remoteMetadata.remoteId,
375
+ (id) => id !== remoteId,
351
376
  );
352
377
 
353
378
  const newThreadIds =
354
379
  remoteMetadata.status === "regular"
355
- ? [...threadIdsWithoutRemote, remoteMetadata.remoteId]
380
+ ? wasInTarget
381
+ ? state.threadIds
382
+ : [...threadIdsWithoutRemote, remoteId]
356
383
  : threadIdsWithoutRemote;
357
384
  const newArchivedThreadIds =
358
385
  remoteMetadata.status === "archived"
359
- ? [...archivedThreadIdsWithoutRemote, remoteMetadata.remoteId]
386
+ ? wasInTarget
387
+ ? state.archivedThreadIds
388
+ : [...archivedThreadIdsWithoutRemote, remoteId]
360
389
  : archivedThreadIdsWithoutRemote;
361
390
 
362
391
  this._state.update({
@@ -380,8 +409,11 @@ export class RemoteThreadListThreadListRuntimeCore
380
409
  task.then(() => this._notifySubscribers());
381
410
  }
382
411
 
412
+ if (generation !== this._switchGeneration) return;
413
+
383
414
  if (data.status === "archived" && options?.unarchive !== false) {
384
415
  await this.unarchive(data.id);
416
+ if (generation !== this._switchGeneration) return;
385
417
  }
386
418
  this._mainThreadId = data.id;
387
419
 
@@ -389,13 +421,21 @@ export class RemoteThreadListThreadListRuntimeCore
389
421
  this._notifyThreadIdChange();
390
422
  }
391
423
 
392
- public async switchToNewThread(): Promise<void> {
424
+ public switchToNewThread(): Promise<void> {
425
+ const generation = ++this._switchGeneration;
426
+ const task = this._switchToNewThread(generation);
427
+ this._switchTask = task;
428
+ return task;
429
+ }
430
+
431
+ private async _switchToNewThread(generation: number): Promise<void> {
393
432
  // an initialization transaction is in progress, wait for it to settle
394
433
  while (
395
434
  this._state.baseValue.newThreadId !== undefined &&
396
435
  this._state.value.newThreadId === undefined
397
436
  ) {
398
437
  await this._state.waitForUpdate();
438
+ if (generation !== this._switchGeneration) return;
399
439
  }
400
440
 
401
441
  const state = this._state.value;
@@ -427,7 +467,7 @@ export class RemoteThreadListThreadListRuntimeCore
427
467
  });
428
468
  }
429
469
 
430
- return this.switchToThread(id);
470
+ return this._switchToThread(id, undefined, generation);
431
471
  }
432
472
 
433
473
  public initialize = async (threadId: string) => {
@@ -436,10 +476,11 @@ export class RemoteThreadListThreadListRuntimeCore
436
476
  if (!data) throw threadNotFoundError(threadId, "initializing it");
437
477
  if (data.status === "new")
438
478
  throw threadStatusError(threadId, data.status, "be initialized here");
439
- return data.initializeTask;
479
+ const { remoteId, externalId } = await data.initializeTask;
480
+ return { remoteId, externalId };
440
481
  }
441
482
 
442
- return this._state.optimisticUpdate({
483
+ const { remoteId, externalId } = await this._state.optimisticUpdate({
443
484
  execute: () => {
444
485
  return this._options.adapter.initialize(threadId);
445
486
  },
@@ -482,6 +523,7 @@ export class RemoteThreadListThreadListRuntimeCore
482
523
  };
483
524
  },
484
525
  });
526
+ return { remoteId, externalId };
485
527
  };
486
528
 
487
529
  public generateTitle = async (threadId: string) => {
@@ -604,8 +646,21 @@ export class RemoteThreadListThreadListRuntimeCore
604
646
  if (threadId === this.newThreadId)
605
647
  throw new Error("Cannot ensure new thread is not main");
606
648
 
607
- if (threadId === this._mainThreadId) {
608
- await this.switchToNewThread();
649
+ let lastAwaitedTask: Promise<void> | undefined;
650
+
651
+ while (threadId === this._mainThreadId) {
652
+ let switchTask = this._switchTask;
653
+ const startedFallback = !switchTask || switchTask === lastAwaitedTask;
654
+ if (startedFallback) switchTask = this.switchToNewThread();
655
+ lastAwaitedTask = switchTask;
656
+
657
+ try {
658
+ await switchTask;
659
+ } catch (error) {
660
+ if (startedFallback && this._switchTask === switchTask) {
661
+ throw error;
662
+ }
663
+ }
609
664
  }
610
665
  }
611
666
 
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { extractAuiV0 } from "./AssistantCloudThreadHistoryAdapter";
3
+
4
+ const auiV0Message = (status: { type: string; reason?: string }) => ({
5
+ role: "assistant",
6
+ status,
7
+ content: [
8
+ {
9
+ type: "tool-call",
10
+ toolCallId: "call-1",
11
+ toolName: "send_email",
12
+ args: {},
13
+ argsText: "{}",
14
+ },
15
+ ],
16
+ metadata: { steps: [{ usage: { inputTokens: 1, outputTokens: 2 } }] },
17
+ });
18
+
19
+ describe("extractAuiV0", () => {
20
+ it("returns null for a requires-action message so paused writes never report", () => {
21
+ expect(
22
+ extractAuiV0(
23
+ auiV0Message({ type: "requires-action", reason: "tool-calls" }),
24
+ ),
25
+ ).toBeNull();
26
+ });
27
+
28
+ it("reports a terminal message once with its usage", () => {
29
+ const result = extractAuiV0(auiV0Message({ type: "complete" }));
30
+ expect(result?.status).toBe("completed");
31
+ expect(result?.inputTokens).toBe(1);
32
+ expect(result?.outputTokens).toBe(2);
33
+ });
34
+ });
@@ -0,0 +1,69 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { renderHook } from "@testing-library/react";
4
+ import type { AssistantCloud } from "assistant-cloud";
5
+ import { describe, expect, it, vi } from "vitest";
6
+ import { useAssistantCloudThreadHistoryAdapter } from "./AssistantCloudThreadHistoryAdapter";
7
+
8
+ const mocks = vi.hoisted(() => {
9
+ const threadListItem = {
10
+ getState: () => ({ remoteId: "thread-1" }),
11
+ };
12
+
13
+ return {
14
+ assistantClient: {
15
+ threadListItem: () => threadListItem,
16
+ },
17
+ };
18
+ });
19
+
20
+ vi.mock("@assistant-ui/store", async (importOriginal) => ({
21
+ ...(await importOriginal<typeof import("@assistant-ui/store")>()),
22
+ useAui: () => mocks.assistantClient,
23
+ }));
24
+
25
+ const makeCloud = () =>
26
+ ({
27
+ threads: {
28
+ messages: {
29
+ list: vi.fn().mockResolvedValue({ messages: [] }),
30
+ },
31
+ },
32
+ }) as unknown as AssistantCloud;
33
+
34
+ describe("useAssistantCloudThreadHistoryAdapter", () => {
35
+ it("refreshes formatted persistence when the Cloud client changes", async () => {
36
+ const firstCloud = makeCloud();
37
+ const secondCloud = makeCloud();
38
+ const cloudRef = { current: firstCloud };
39
+ const { result } = renderHook(() =>
40
+ useAssistantCloudThreadHistoryAdapter(cloudRef),
41
+ );
42
+ const formatted = result.current.withFormat<
43
+ { id: string },
44
+ Record<string, unknown>
45
+ >({
46
+ format: "test",
47
+ encode: ({ message }) => message,
48
+ decode: ({ parent_id, content }) => ({
49
+ parentId: parent_id,
50
+ message: content as { id: string },
51
+ }),
52
+ getId: (message) => message.id,
53
+ });
54
+
55
+ await formatted.load();
56
+ await formatted.load();
57
+
58
+ expect(firstCloud.threads.messages.list).toHaveBeenCalledTimes(2);
59
+
60
+ cloudRef.current = secondCloud;
61
+ await formatted.load();
62
+
63
+ expect(firstCloud.threads.messages.list).toHaveBeenCalledTimes(2);
64
+ expect(secondCloud.threads.messages.list).toHaveBeenCalledOnce();
65
+ expect(secondCloud.threads.messages.list).toHaveBeenCalledWith("thread-1", {
66
+ format: "test",
67
+ });
68
+ });
69
+ });
@@ -22,17 +22,20 @@ const globalPersistence = new WeakMap<
22
22
  >();
23
23
 
24
24
  class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
25
- constructor(
26
- private cloudRef: RefObject<AssistantCloud>,
27
- private aui: AssistantClient,
28
- ) {}
25
+ private cloudRef: RefObject<AssistantCloud>;
26
+ private aui: AssistantClient;
27
+
28
+ constructor(cloudRef: RefObject<AssistantCloud>, aui: AssistantClient) {
29
+ this.cloudRef = cloudRef;
30
+ this.aui = aui;
31
+ }
29
32
 
30
33
  private get _persistence(): CloudMessagePersistence {
31
34
  const key = this.aui.threadListItem();
32
35
  if (!globalPersistence.has(key)) {
33
36
  globalPersistence.set(
34
37
  key,
35
- new CloudMessagePersistence(this.cloudRef.current),
38
+ new CloudMessagePersistence(() => this.cloudRef.current),
36
39
  );
37
40
  }
38
41
  return globalPersistence.get(key)!;
@@ -102,6 +105,21 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
102
105
  }
103
106
  }
104
107
 
108
+ async update(item: ExportedMessageRepositoryItem) {
109
+ if (!this._persistence.isPersisted(item.message.id)) {
110
+ return this.append(item);
111
+ }
112
+ const { message } = item;
113
+ const remoteId = this.aui.threadListItem().getState().remoteId;
114
+ if (!remoteId) return;
115
+ const encoded = auiV0Encode(message);
116
+ await this._persistence.update(remoteId, message.id, "aui/v0", encoded);
117
+
118
+ if (this.cloudRef.current.telemetry.enabled) {
119
+ this._maybeReportRun(remoteId, "aui/v0", encoded);
120
+ }
121
+ }
122
+
105
123
  async delete() {
106
124
  throw new Error(
107
125
  "Assistant Cloud does not support deleting thread messages yet.",
@@ -341,7 +359,7 @@ const AUI_STATUS_MAP: Record<string, TelemetryData["status"]> = {
341
359
  incomplete: "incomplete",
342
360
  };
343
361
 
344
- function extractAuiV0<T>(content: T): TelemetryData | null {
362
+ export function extractAuiV0<T>(content: T): TelemetryData | null {
345
363
  const msg = content as {
346
364
  role?: string;
347
365
  status?: { type: string };
@@ -369,6 +387,9 @@ function extractAuiV0<T>(content: T): TelemetryData | null {
369
387
  };
370
388
 
371
389
  if (msg.role !== "assistant") return null;
390
+ // A paused (requires-action) write is not a finished run; reporting it would
391
+ // mislabel it "completed" and double-count steps once the terminal write reports.
392
+ if (msg.status?.type === "requires-action") return null;
372
393
 
373
394
  const toolCalls = msg.content
374
395
  ?.filter((p) => p.type === "tool-call" && p.toolName && p.toolCallId)
@@ -57,6 +57,22 @@ describe("CloudFileAttachmentAdapter", () => {
57
57
  ]);
58
58
  });
59
59
 
60
+ it("uploads when Web Crypto is unavailable", async () => {
61
+ vi.stubGlobal("crypto", undefined);
62
+ vi.stubGlobal("fetch", vi.fn().mockResolvedValue({ ok: true }));
63
+ const adapter = new CloudFileAttachmentAdapter(makeCloud());
64
+
65
+ const yields = await drain(adapter);
66
+
67
+ expect(yields).toHaveLength(2);
68
+ expect(yields[0]?.id).toBeTruthy();
69
+ expect(yields[1]?.id).toBe(yields[0]?.id);
70
+ expect(yields.at(-1)?.status).toEqual({
71
+ type: "requires-action",
72
+ reason: "composer-send",
73
+ });
74
+ });
75
+
60
76
  it("marks the attachment incomplete when the upload returns an HTTP error", async () => {
61
77
  const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
62
78
  vi.stubGlobal(
@@ -6,6 +6,7 @@ import type {
6
6
  } from "../../../types/attachment";
7
7
  import type { ThreadUserMessagePart } from "../../../types/message";
8
8
  import type { AttachmentAdapter } from "../../../adapters/attachment";
9
+ import { generateId } from "../../../utils/id";
9
10
 
10
11
  const guessAttachmentType = (
11
12
  contentType: string,
@@ -18,7 +19,11 @@ const guessAttachmentType = (
18
19
  export class CloudFileAttachmentAdapter implements AttachmentAdapter {
19
20
  public accept = "*";
20
21
 
21
- constructor(private cloud: AssistantCloud) {}
22
+ private cloud: AssistantCloud;
23
+
24
+ constructor(cloud: AssistantCloud) {
25
+ this.cloud = cloud;
26
+ }
22
27
 
23
28
  private uploadedUrls = new Map<string, string>();
24
29
 
@@ -27,7 +32,7 @@ export class CloudFileAttachmentAdapter implements AttachmentAdapter {
27
32
  }: {
28
33
  file: File;
29
34
  }): AsyncGenerator<PendingAttachment, void> {
30
- const id = crypto.randomUUID();
35
+ const id = generateId();
31
36
  const type = guessAttachmentType(file.type);
32
37
  let attachment: PendingAttachment = {
33
38
  id,
@@ -6,6 +6,7 @@ import type {
6
6
  SourceProviderMetadata,
7
7
  ThreadMessage,
8
8
  TextMessagePart,
9
+ ToolApprovalOption,
9
10
  Unstable_AudioMessagePart,
10
11
  } from "../../../types/message";
11
12
  import type { CompleteAttachment } from "../../../types/attachment";
@@ -18,6 +19,16 @@ import type {
18
19
  } from "assistant-stream/utils";
19
20
  import type { ExportedMessageRepositoryItem } from "../../../runtime/utils/message-repository";
20
21
 
22
+ type AuiV0ToolApproval = {
23
+ readonly id: string;
24
+ readonly approved?: boolean;
25
+ readonly reason?: string;
26
+ readonly isAutomatic?: boolean;
27
+ readonly options?: readonly ToolApprovalOption[];
28
+ readonly optionId?: string;
29
+ readonly resolution?: "cancelled" | "expired";
30
+ };
31
+
21
32
  type AuiV0MessagePart =
22
33
  | {
23
34
  readonly type: "text";
@@ -51,6 +62,7 @@ type AuiV0MessagePart =
51
62
  readonly args: ReadonlyJSONObject;
52
63
  readonly result?: ReadonlyJSONValue;
53
64
  readonly isError?: true;
65
+ readonly approval?: AuiV0ToolApproval;
54
66
  }
55
67
  | {
56
68
  readonly type: "tool-call";
@@ -59,6 +71,7 @@ type AuiV0MessagePart =
59
71
  readonly argsText: string;
60
72
  readonly result?: ReadonlyJSONValue;
61
73
  readonly isError?: true;
74
+ readonly approval?: AuiV0ToolApproval;
62
75
  }
63
76
  | {
64
77
  readonly type: "image";
@@ -216,6 +229,7 @@ export function auiV0Encode(message: ThreadMessage): AuiV0Message {
216
229
  ? { result: part.result as ReadonlyJSONValue }
217
230
  : undefined),
218
231
  ...(part.isError ? { isError: true } : undefined),
232
+ ...(part.approval ? { approval: part.approval } : undefined),
219
233
  };
220
234
  }
221
235