@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
@@ -35,6 +35,7 @@ const useLocalThreadRuntime = (
35
35
  const [runtime] = useState(() => new LocalRuntimeCore(opt, initialMessages));
36
36
 
37
37
  const threadIdRef = useRef<string | undefined>(undefined);
38
+ const historyLoadPromiseRef = useRef<Promise<void> | undefined>(undefined);
38
39
  threadIdRef.current = useAuiState((s) => s.threadListItem.remoteId);
39
40
 
40
41
  useEffect(() => {
@@ -51,9 +52,20 @@ const useLocalThreadRuntime = (
51
52
 
52
53
  useEffect(() => {
53
54
  runtime.threads.getMainThreadRuntimeCore().__internal_setOptions(opt);
54
- runtime.threads.getMainThreadRuntimeCore().__internal_load();
55
55
  });
56
56
 
57
+ useEffect(() => {
58
+ const loadPromise = runtime.threads
59
+ .getMainThreadRuntimeCore()
60
+ .__internal_load();
61
+ if (historyLoadPromiseRef.current === loadPromise) return;
62
+
63
+ historyLoadPromiseRef.current = loadPromise;
64
+ void loadPromise.catch((error: unknown) => {
65
+ console.error("[assistant-ui] local thread history load failed:", error);
66
+ });
67
+ }, [runtime]);
68
+
57
69
  useEffect(() => {
58
70
  if (!modelContext) return undefined;
59
71
  return runtime.registerModelContextProvider(modelContext);
@@ -31,7 +31,10 @@ export class AssistantRuntimeImpl implements AssistantRuntime {
31
31
 
32
32
  public readonly _thread: ThreadRuntime;
33
33
 
34
- public constructor(private readonly _core: AssistantRuntimeCore) {
34
+ private readonly _core: AssistantRuntimeCore;
35
+
36
+ constructor(_core: AssistantRuntimeCore) {
37
+ this._core = _core;
35
38
  this.threads = new ThreadListRuntimeImpl(_core.threads);
36
39
  this._thread = this.threads.main;
37
40
 
@@ -49,7 +49,10 @@ export abstract class AttachmentRuntimeImpl<
49
49
 
50
50
  public abstract get source(): Source;
51
51
 
52
- constructor(private _core: AttachmentSnapshotBinding<Source>) {
52
+ private _core: AttachmentSnapshotBinding<Source>;
53
+
54
+ constructor(_core: AttachmentSnapshotBinding<Source>) {
55
+ this._core = _core;
53
56
  this.__internal_bindMethods();
54
57
  }
55
58
 
@@ -73,11 +76,14 @@ export abstract class AttachmentRuntimeImpl<
73
76
  abstract class ComposerAttachmentRuntime<
74
77
  Source extends "thread-composer" | "edit-composer",
75
78
  > extends AttachmentRuntimeImpl<Source> {
79
+ private _composerApi: ComposerRuntimeCoreBinding;
80
+
76
81
  constructor(
77
82
  core: AttachmentSnapshotBinding<Source>,
78
- private _composerApi: ComposerRuntimeCoreBinding,
83
+ _composerApi: ComposerRuntimeCoreBinding,
79
84
  ) {
80
85
  super(core);
86
+ this._composerApi = _composerApi;
81
87
  }
82
88
 
83
89
  public remove() {
@@ -251,7 +251,11 @@ export abstract class ComposerRuntimeImpl implements ComposerRuntime {
251
251
 
252
252
  public abstract get type(): "edit" | "thread";
253
253
 
254
- constructor(protected _core: ComposerRuntimeCoreBinding) {}
254
+ protected _core: ComposerRuntimeCoreBinding;
255
+
256
+ constructor(_core: ComposerRuntimeCoreBinding) {
257
+ this._core = _core;
258
+ }
255
259
 
256
260
  protected __internal_bindMethods() {
257
261
  this.setText = this.setText.bind(this);
@@ -483,10 +487,9 @@ export class EditComposerRuntimeImpl
483
487
  }
484
488
 
485
489
  private _getState;
486
- constructor(
487
- core: EditComposerRuntimeCoreBinding,
488
- private _beginEdit: () => void,
489
- ) {
490
+ private _beginEdit: () => void;
491
+
492
+ constructor(core: EditComposerRuntimeCoreBinding, _beginEdit: () => void) {
490
493
  const stateBinding = new LazyMemoizeSubject({
491
494
  path: core.path,
492
495
  getState: () => getEditComposerState(core.getState()),
@@ -498,6 +501,7 @@ export class EditComposerRuntimeImpl
498
501
  getState: () => core.getState(),
499
502
  subscribe: (callback) => stateBinding.subscribe(callback),
500
503
  });
504
+ this._beginEdit = _beginEdit;
501
505
 
502
506
  this._getState = stateBinding.getState.bind(stateBinding);
503
507
 
@@ -40,11 +40,18 @@ export class MessagePartRuntimeImpl implements MessagePartRuntime {
40
40
  return this.contentBinding.path;
41
41
  }
42
42
 
43
+ private contentBinding: MessagePartSnapshotBinding;
44
+ private messageApi: MessageStateBinding | undefined;
45
+ private threadApi: ThreadRuntimeCoreBinding | undefined;
46
+
43
47
  constructor(
44
- private contentBinding: MessagePartSnapshotBinding,
45
- private messageApi?: MessageStateBinding,
46
- private threadApi?: ThreadRuntimeCoreBinding,
48
+ contentBinding: MessagePartSnapshotBinding,
49
+ messageApi?: MessageStateBinding,
50
+ threadApi?: ThreadRuntimeCoreBinding,
47
51
  ) {
52
+ this.contentBinding = contentBinding;
53
+ this.messageApi = messageApi;
54
+ this.threadApi = threadApi;
48
55
  this.__internal_bindMethods();
49
56
  }
50
57
 
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { CompleteAttachment } from "../../types/attachment";
3
+ import type { ThreadRuntimeCoreBinding } from "./thread-runtime";
4
+ import {
5
+ MessageRuntimeImpl,
6
+ type MessageState,
7
+ type MessageStateBinding,
8
+ } from "./message-runtime";
9
+
10
+ const messagePath = {
11
+ ref: "threads.main.messages[0]",
12
+ threadSelector: { type: "main" as const },
13
+ messageSelector: { type: "index" as const, index: 0 },
14
+ };
15
+
16
+ const attachment: CompleteAttachment = {
17
+ id: "attachment-1",
18
+ type: "file",
19
+ name: "notes.txt",
20
+ contentType: "text/plain",
21
+ content: [{ type: "text", text: "notes" }],
22
+ status: { type: "complete" },
23
+ };
24
+
25
+ const message: MessageState = {
26
+ id: "message-1",
27
+ role: "assistant",
28
+ createdAt: new Date(0),
29
+ content: [
30
+ { type: "text", text: "Hello" },
31
+ {
32
+ type: "tool-call",
33
+ toolCallId: "call-1",
34
+ toolName: "weather",
35
+ args: {},
36
+ argsText: "{}",
37
+ },
38
+ ],
39
+ attachments: [attachment],
40
+ status: { type: "complete", reason: "stop" },
41
+ metadata: {
42
+ unstable_state: null,
43
+ unstable_annotations: [],
44
+ unstable_data: [],
45
+ steps: [],
46
+ custom: {},
47
+ },
48
+ parentId: null,
49
+ index: 0,
50
+ isLast: true,
51
+ branchNumber: 1,
52
+ branchCount: 1,
53
+ speech: undefined,
54
+ };
55
+
56
+ const messageBinding: MessageStateBinding = {
57
+ path: messagePath,
58
+ getState: () => message,
59
+ subscribe: () => () => {},
60
+ };
61
+
62
+ const threadBinding = {
63
+ subscribe: () => () => {},
64
+ } as unknown as ThreadRuntimeCoreBinding;
65
+
66
+ describe("MessageRuntimeImpl paths", () => {
67
+ it("appends nested selectors to the message path", () => {
68
+ const runtime = new MessageRuntimeImpl(messageBinding, threadBinding);
69
+
70
+ expect(runtime.composer.path.ref).toBe("threads.main.messages[0].composer");
71
+ expect(runtime.getMessagePartByIndex(0).path.ref).toBe(
72
+ "threads.main.messages[0].content[0]",
73
+ );
74
+ expect(runtime.getMessagePartByToolCallId("call-1").path.ref).toBe(
75
+ 'threads.main.messages[0].content[toolCallId="call-1"]',
76
+ );
77
+ expect(runtime.getAttachmentByIndex(0).path.ref).toBe(
78
+ "threads.main.messages[0].attachments[0]",
79
+ );
80
+ });
81
+ });
@@ -134,15 +134,20 @@ export class MessageRuntimeImpl implements MessageRuntime {
134
134
  return this._core.path;
135
135
  }
136
136
 
137
+ private _core: MessageStateBinding;
138
+ private _threadBinding: ThreadRuntimeCoreBinding;
139
+
137
140
  constructor(
138
- private _core: MessageStateBinding,
139
- private _threadBinding: ThreadRuntimeCoreBinding,
141
+ _core: MessageStateBinding,
142
+ _threadBinding: ThreadRuntimeCoreBinding,
140
143
  ) {
144
+ this._core = _core;
145
+ this._threadBinding = _threadBinding;
141
146
  this.composer = new EditComposerRuntimeImpl(
142
147
  new NestedSubscriptionSubject({
143
148
  path: {
144
149
  ...this.path,
145
- ref: `${this.path.ref}${this.path.ref}.composer`,
150
+ ref: `${this.path.ref}.composer`,
146
151
  composerSource: "edit",
147
152
  },
148
153
  getState: this._getEditComposerRuntimeCore,
@@ -269,7 +274,7 @@ export class MessageRuntimeImpl implements MessageRuntime {
269
274
  new ShallowMemoizeSubject({
270
275
  path: {
271
276
  ...this.path,
272
- ref: `${this.path.ref}${this.path.ref}.content[${idx}]`,
277
+ ref: `${this.path.ref}.content[${idx}]`,
273
278
  messagePartSelector: { type: "index", index: idx },
274
279
  },
275
280
  getState: () => {
@@ -287,9 +292,7 @@ export class MessageRuntimeImpl implements MessageRuntime {
287
292
  new ShallowMemoizeSubject({
288
293
  path: {
289
294
  ...this.path,
290
- ref:
291
- this.path.ref +
292
- `${this.path.ref}.content[toolCallId=${JSON.stringify(toolCallId)}]`,
295
+ ref: `${this.path.ref}.content[toolCallId=${JSON.stringify(toolCallId)}]`,
293
296
  messagePartSelector: { type: "toolCallId", toolCallId },
294
297
  },
295
298
  getState: () => {
@@ -313,7 +316,7 @@ export class MessageRuntimeImpl implements MessageRuntime {
313
316
  new ShallowMemoizeSubject({
314
317
  path: {
315
318
  ...this.path,
316
- ref: `${this.path.ref}${this.path.ref}.attachments[${idx}]`,
319
+ ref: `${this.path.ref}.attachments[${idx}]`,
317
320
  attachmentSource: "message",
318
321
  attachmentSelector: { type: "index", index: idx },
319
322
  },
@@ -2,6 +2,7 @@ import type { Unsubscribe } from "../../types/unsubscribe";
2
2
  import type { SubscribableWithState } from "../../subscribable/subscribable";
3
3
  import type { ThreadListItemRuntimePath } from "./paths";
4
4
  import type { ThreadListRuntimeCoreBinding } from "./thread-list-runtime";
5
+ import { notifyEventListeners } from "../../utils/notify-event-listeners";
5
6
 
6
7
  export type ThreadListItemEventPayload = {
7
8
  /**
@@ -65,10 +66,15 @@ export class ThreadListItemRuntimeImpl implements ThreadListItemRuntime {
65
66
  return this._core.path;
66
67
  }
67
68
 
69
+ private _core: ThreadListItemStateBinding;
70
+ private _threadListBinding: ThreadListRuntimeCoreBinding;
71
+
68
72
  constructor(
69
- private _core: ThreadListItemStateBinding,
70
- private _threadListBinding: ThreadListRuntimeCoreBinding,
73
+ _core: ThreadListItemStateBinding,
74
+ _threadListBinding: ThreadListRuntimeCoreBinding,
71
75
  ) {
76
+ this._core = _core;
77
+ this._threadListBinding = _threadListBinding;
72
78
  this.__internal_bindMethods();
73
79
  }
74
80
 
@@ -162,7 +168,11 @@ export class ThreadListItemRuntimeImpl implements ThreadListItemRuntime {
162
168
 
163
169
  if (event === "switchedTo" && !newIsMain) return;
164
170
  if (event === "switchedAway" && newIsMain) return;
165
- (callback as (payload?: unknown) => void)({});
171
+ notifyEventListeners(
172
+ [callback as (payload?: unknown) => void],
173
+ {},
174
+ `Thread list item "${event}"`,
175
+ );
166
176
  });
167
177
  }
168
178
 
@@ -98,13 +98,21 @@ export type ThreadListRuntimeCoreBinding = ThreadListRuntimeCore;
98
98
 
99
99
  export class ThreadListRuntimeImpl implements ThreadListRuntime {
100
100
  private _getState;
101
+ private _core: ThreadListRuntimeCoreBinding;
102
+ private _runtimeFactory: new (
103
+ binding: ThreadRuntimeCoreBinding,
104
+ threadListItemBinding: ThreadListItemRuntimeBinding,
105
+ ) => ThreadRuntime;
106
+
101
107
  constructor(
102
- private _core: ThreadListRuntimeCoreBinding,
103
- private _runtimeFactory: new (
108
+ _core: ThreadListRuntimeCoreBinding,
109
+ _runtimeFactory: new (
104
110
  binding: ThreadRuntimeCoreBinding,
105
111
  threadListItemBinding: ThreadListItemRuntimeBinding,
106
112
  ) => ThreadRuntime = ThreadRuntimeImpl,
107
113
  ) {
114
+ this._core = _core;
115
+ this._runtimeFactory = _runtimeFactory;
108
116
  const stateBinding = new LazyMemoizeSubject({
109
117
  path: {},
110
118
  getState: () => getThreadListState(_core),
@@ -1,8 +1,9 @@
1
- import type {
2
- Attachment,
3
- CompleteAttachment,
4
- CreateAttachment,
5
- PendingAttachment,
1
+ import {
2
+ isCreateAttachment,
3
+ type Attachment,
4
+ type CompleteAttachment,
5
+ type CreateAttachment,
6
+ type PendingAttachment,
6
7
  } from "../../types/attachment";
7
8
  import type { MessageRole, AppendMessage } from "../../types/message";
8
9
  import type { QuoteInfo } from "../../types/quote";
@@ -28,6 +29,7 @@ import {
28
29
  type QueueItemState,
29
30
  } from "../../store/scopes/queue-item";
30
31
  import { generateId } from "../../utils/id";
32
+ import { notifyEventListeners } from "../../utils/notify-event-listeners";
31
33
 
32
34
  const isAttachmentComplete = (a: Attachment): a is CompleteAttachment =>
33
35
  a.status.type === "complete";
@@ -133,6 +135,10 @@ export abstract class BaseComposerRuntimeCore
133
135
  this._notifySubscribers();
134
136
  }
135
137
 
138
+ protected _isSending = false;
139
+ private _removedDuringSend = new Set<string>();
140
+ private _sendGeneration = 0;
141
+
136
142
  private _emptyTextAndAttachments() {
137
143
  this._attachments = [];
138
144
  this._text = "";
@@ -148,6 +154,14 @@ export abstract class BaseComposerRuntimeCore
148
154
  }
149
155
 
150
156
  public async reset() {
157
+ // A send whose adapter never settles must not brick the composer; reset is
158
+ // the escape hatch that releases the in-flight lock. Bumping the generation
159
+ // invalidates that send entirely so a late-settling upload can neither
160
+ // append the discarded draft nor touch a newer send's lock.
161
+ this._sendGeneration++;
162
+ this._isSending = false;
163
+ this._removedDuringSend.clear();
164
+
151
165
  if (
152
166
  this._attachments.length === 0 &&
153
167
  this._text === "" &&
@@ -175,7 +189,7 @@ export abstract class BaseComposerRuntimeCore
175
189
  }
176
190
 
177
191
  public async send(options?: SendOptions) {
178
- if (!this.canSend) return;
192
+ if (!this.canSend || this._isSending) return;
179
193
 
180
194
  if (this._dictationSession) {
181
195
  this._dictationSession.cancel();
@@ -183,47 +197,75 @@ export abstract class BaseComposerRuntimeCore
183
197
  }
184
198
 
185
199
  const adapter = this.getAttachmentAdapter();
186
- const attachments =
187
- this.attachments.length > 0
188
- ? Promise.all(
189
- this.attachments.map(async (a) => {
190
- if (isAttachmentComplete(a)) return a;
191
- if (!adapter) throw new Error("Attachments are not supported");
192
- const result = await adapter.send(a);
193
- return result as CompleteAttachment;
194
- }),
195
- )
196
- : [];
200
+ const attachmentTasks = this.attachments.map(async (a) => {
201
+ if (isAttachmentComplete(a)) return a;
202
+ if (!adapter) throw new Error("Attachments are not supported");
203
+ const result = await adapter.send(a);
204
+ return result as CompleteAttachment;
205
+ });
197
206
 
198
207
  const originalAttachments = this.attachments;
199
208
  const text = this.text;
200
209
  const quote = this._quote;
201
210
  this._quote = undefined;
202
- this._emptyTextAndAttachments();
211
+ this._text = "";
212
+ this._isSending = true;
213
+ const generation = ++this._sendGeneration;
214
+ this._notifySubscribers();
203
215
 
204
- let resolvedAttachments: Awaited<typeof attachments>;
216
+ let resolvedAttachments: CompleteAttachment[];
205
217
  try {
206
- resolvedAttachments = await attachments;
218
+ resolvedAttachments = await Promise.all(attachmentTasks);
207
219
  } catch (e) {
208
- if (this.isEmpty && this._quote === undefined) {
209
- this._attachments = originalAttachments;
210
- this._text = text;
211
- this._quote = quote;
212
- this._notifySubscribers();
220
+ if (generation === this._sendGeneration) {
221
+ if (!this.text.trim() && this._quote === undefined) {
222
+ this._text = text;
223
+ this._quote = quote;
224
+ this._notifySubscribers();
225
+ }
226
+ // Promise.all rejects on the first failure, but sibling uploads from
227
+ // this batch keep running; the send rejects immediately while the
228
+ // retry lock is held until they settle, or a retry could re-send
229
+ // attachments that are still in flight.
230
+ void Promise.allSettled(attachmentTasks).then(() => {
231
+ if (generation !== this._sendGeneration) return;
232
+ this._removedDuringSend.clear();
233
+ this._isSending = false;
234
+ this._notifySubscribers();
235
+ });
213
236
  }
214
237
  throw e;
215
238
  }
216
239
 
240
+ // A reset during the upload discarded this send's draft; the settled
241
+ // uploads must not append it or touch the lock a newer send may own.
242
+ if (generation !== this._sendGeneration) return;
243
+
244
+ // Drop by id, not wholesale: the user may have added or removed chips while
245
+ // the upload was in flight.
246
+ const sentIds = new Set(originalAttachments.map((a) => a.id));
247
+ this._attachments = this._attachments.filter((a) => !sentIds.has(a.id));
248
+ this._isSending = false;
249
+ this._notifySubscribers();
250
+
251
+ // An attachment removed mid-upload can't be cancelled, but it can still be
252
+ // dropped from the outgoing message instead of silently being sent anyway.
253
+ const finalAttachments = resolvedAttachments.filter(
254
+ (a) => !this._removedDuringSend.has(a.id),
255
+ );
256
+ this._removedDuringSend.clear();
257
+
217
258
  const message: Omit<AppendMessage, "parentId" | "sourceId"> = {
218
259
  createdAt: new Date(),
219
260
  role: this.role,
220
261
  content: text ? [{ type: "text", text }] : [],
221
- attachments: resolvedAttachments,
262
+ attachments: finalAttachments,
222
263
  runConfig: this.runConfig,
223
264
  metadata: { custom: { ...(quote ? { quote } : {}) } },
224
265
  };
225
266
 
226
- this.handleSend(message, options);
267
+ const sendTask = this.handleSend(message, options);
268
+ if (sendTask) void sendTask.catch(() => {});
227
269
  this._notifyEventSubscribers("send", {});
228
270
  }
229
271
 
@@ -241,11 +283,11 @@ export abstract class BaseComposerRuntimeCore
241
283
  protected abstract handleSend(
242
284
  message: Omit<AppendMessage, "parentId" | "sourceId">,
243
285
  options?: SendOptions,
244
- ): void;
286
+ ): void | Promise<void>;
245
287
  protected abstract handleCancel(): void;
246
288
 
247
289
  async addAttachment(fileOrAttachment: File | CreateAttachment) {
248
- if (!(fileOrAttachment instanceof File)) {
290
+ if (isCreateAttachment(fileOrAttachment)) {
249
291
  const adapter = this.getAttachmentAdapter();
250
292
  if (
251
293
  adapter &&
@@ -392,12 +434,16 @@ export abstract class BaseComposerRuntimeCore
392
434
  if (index === -1) throw new Error("Attachment not found");
393
435
  const attachment = this._attachments[index]!;
394
436
 
437
+ // A send in flight may already be uploading this attachment; the upload
438
+ // can't be cancelled, so mark it to be dropped from the outgoing message
439
+ // before any await gives the upload a chance to settle first.
440
+ if (this._isSending) this._removedDuringSend.add(attachmentId);
441
+
395
442
  if (!isAttachmentComplete(attachment)) {
396
443
  const adapter = this.getAttachmentAdapter();
397
444
  if (!adapter) throw new Error("Attachments are not supported");
398
445
  await adapter.remove(attachment);
399
446
  }
400
-
401
447
  this._attachments = this._attachments.filter((a) => a.id !== attachmentId);
402
448
  this._notifySubscribers();
403
449
  }
@@ -563,7 +609,7 @@ export abstract class BaseComposerRuntimeCore
563
609
  const subscribers = this._eventSubscribers.get(event);
564
610
  if (!subscribers) return;
565
611
 
566
- for (const callback of subscribers) callback(payload);
612
+ notifyEventListeners(subscribers, payload, `Composer runtime "${event}"`);
567
613
  }
568
614
 
569
615
  public unstable_on<E extends ComposerRuntimeEventType>(
@@ -34,6 +34,7 @@ import type { FeedbackAdapter } from "../../adapters/feedback";
34
34
  import type { AttachmentAdapter } from "../../adapters/attachment";
35
35
  import type { RealtimeVoiceAdapter } from "../../adapters/voice";
36
36
  import type { ThreadMessageLike } from "../utils/thread-message-like";
37
+ import { notifyEventListeners } from "../../utils/notify-event-listeners";
37
38
 
38
39
  type BaseThreadAdapters = {
39
40
  speech?: SpeechSynthesisAdapter | undefined;
@@ -112,7 +113,11 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
112
113
 
113
114
  public readonly composer = new DefaultThreadComposerRuntimeCore(this);
114
115
 
115
- constructor(private readonly _contextProvider: ModelContextProvider) {}
116
+ private readonly _contextProvider: ModelContextProvider;
117
+
118
+ constructor(_contextProvider: ModelContextProvider) {
119
+ this._contextProvider = _contextProvider;
120
+ }
116
121
 
117
122
  public getModelContext() {
118
123
  return this._contextProvider.getModelContext();
@@ -182,7 +187,7 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
182
187
  const subscribers = this._eventSubscribers.get(event);
183
188
  if (!subscribers) return;
184
189
 
185
- for (const callback of subscribers) callback(payload);
190
+ notifyEventListeners(subscribers, payload, `Thread runtime "${event}"`);
186
191
  }
187
192
 
188
193
  public subscribe(callback: () => void): Unsubscribe {
@@ -15,7 +15,7 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
15
15
  }
16
16
 
17
17
  public get canSend() {
18
- return !this.isEmpty;
18
+ return !this.isEmpty && !this._isSending;
19
19
  }
20
20
 
21
21
  protected getAttachmentAdapter() {
@@ -31,8 +31,18 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
31
31
  private _nonTextPassthrough: readonly ThreadMessage["content"][number][];
32
32
  private _parentId: string | null;
33
33
  private _sourceId: string | null;
34
+ private runtime: ThreadRuntimeCore & {
35
+ adapters?:
36
+ | {
37
+ attachments?: AttachmentAdapter | undefined;
38
+ dictation?: DictationAdapter | undefined;
39
+ }
40
+ | undefined;
41
+ };
42
+ private endEditCallback: () => void;
43
+
34
44
  constructor(
35
- private runtime: ThreadRuntimeCore & {
45
+ runtime: ThreadRuntimeCore & {
36
46
  adapters?:
37
47
  | {
38
48
  attachments?: AttachmentAdapter | undefined;
@@ -40,10 +50,12 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
40
50
  }
41
51
  | undefined;
42
52
  },
43
- private endEditCallback: () => void,
53
+ endEditCallback: () => void,
44
54
  { parentId, message }: { parentId: string | null; message: ThreadMessage },
45
55
  ) {
46
56
  super();
57
+ this.runtime = runtime;
58
+ this.endEditCallback = endEditCallback;
47
59
  this._parentId = parentId;
48
60
  this._sourceId = message.id;
49
61
  this._previousText = getThreadMessageText(message);
@@ -80,6 +92,7 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
80
92
  message: Omit<AppendMessage, "parentId" | "sourceId">,
81
93
  options?: SendOptions,
82
94
  ) {
95
+ let appendTask: void | Promise<void> = undefined;
83
96
  const text = getThreadMessageText(message as AppendMessage);
84
97
  const attachmentsChanged = !attachmentsEqual(
85
98
  message.attachments ?? [],
@@ -114,7 +127,7 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
114
127
  message,
115
128
  composerMetadata,
116
129
  );
117
- this.runtime.append({
130
+ appendTask = this.runtime.append({
118
131
  ...enriched,
119
132
  content,
120
133
  parentId: this._parentId,
@@ -124,6 +137,7 @@ export class DefaultEditComposerRuntimeCore extends BaseComposerRuntimeCore {
124
137
  }
125
138
 
126
139
  this.handleCancel();
140
+ return appendTask;
127
141
  }
128
142
 
129
143
  public handleCancel() {
@@ -23,7 +23,7 @@ export class DefaultThreadComposerRuntimeCore
23
23
  }
24
24
 
25
25
  public get canSend() {
26
- return !this.isEmpty && !this.runtime.isSendDisabled;
26
+ return !this.isEmpty && !this.runtime.isSendDisabled && !this._isSending;
27
27
  }
28
28
 
29
29
  public override get queue(): readonly QueueItemState[] {
@@ -46,8 +46,17 @@ export class DefaultThreadComposerRuntimeCore
46
46
  return this.runtime.adapters?.dictation;
47
47
  }
48
48
 
49
+ private runtime: Omit<ThreadRuntimeCore, "composer"> & {
50
+ adapters?:
51
+ | {
52
+ attachments?: AttachmentAdapter | undefined;
53
+ dictation?: DictationAdapter | undefined;
54
+ }
55
+ | undefined;
56
+ };
57
+
49
58
  constructor(
50
- private runtime: Omit<ThreadRuntimeCore, "composer"> & {
59
+ runtime: Omit<ThreadRuntimeCore, "composer"> & {
51
60
  adapters?:
52
61
  | {
53
62
  attachments?: AttachmentAdapter | undefined;
@@ -57,6 +66,7 @@ export class DefaultThreadComposerRuntimeCore
57
66
  },
58
67
  ) {
59
68
  super();
69
+ this.runtime = runtime;
60
70
  this.connect();
61
71
  }
62
72
 
@@ -94,7 +104,7 @@ export class DefaultThreadComposerRuntimeCore
94
104
  );
95
105
  const enriched = this.enrichWithComposerMetadata(message, composerMetadata);
96
106
 
97
- this.runtime.append({
107
+ return this.runtime.append({
98
108
  ...(enriched as AppendMessage),
99
109
  parentId: this.runtime.messages.at(-1)?.id ?? null,
100
110
  sourceId: null,