@assistant-ui/core 0.3.16 → 0.3.18

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 (474) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/attachment.js +5 -4
  3. package/dist/adapters/attachment.js.map +1 -1
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.js +2 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/internal.d.ts +4 -2
  8. package/dist/internal.js +4 -2
  9. package/dist/model-context/frame/host.d.ts.map +1 -1
  10. package/dist/model-context/frame/host.js +10 -4
  11. package/dist/model-context/frame/host.js.map +1 -1
  12. package/dist/model-context/frame/provider.d.ts +5 -0
  13. package/dist/model-context/frame/provider.d.ts.map +1 -1
  14. package/dist/model-context/frame/provider.js +101 -28
  15. package/dist/model-context/frame/provider.js.map +1 -1
  16. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  17. package/dist/model-context/interactable-composer-metadata.js +16 -9
  18. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  19. package/dist/model-context/registry.d.ts.map +1 -1
  20. package/dist/model-context/registry.js +3 -5
  21. package/dist/model-context/registry.js.map +1 -1
  22. package/dist/model-context/tool-call-text.d.ts +26 -0
  23. package/dist/model-context/tool-call-text.d.ts.map +1 -0
  24. package/dist/model-context/tool-call-text.js +18 -0
  25. package/dist/model-context/tool-call-text.js.map +1 -0
  26. package/dist/model-context/types.d.ts.map +1 -1
  27. package/dist/model-context/types.js +5 -4
  28. package/dist/model-context/types.js.map +1 -1
  29. package/dist/react/RuntimeAdapter.js +1 -1
  30. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  31. package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -1
  32. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  33. package/dist/react/client/AdaptedRemoteThread.js +1 -1
  34. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  35. package/dist/react/client/InMemoryThreadList.js +63 -57
  36. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  37. package/dist/react/client/Interactables.d.ts.map +1 -1
  38. package/dist/react/client/Interactables.js +25 -28
  39. package/dist/react/client/Interactables.js.map +1 -1
  40. package/dist/react/client/RemoteThreadList.d.ts +16 -0
  41. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  42. package/dist/react/client/RemoteThreadList.js +354 -242
  43. package/dist/react/client/RemoteThreadList.js.map +1 -1
  44. package/dist/react/client/Tools.d.ts.map +1 -1
  45. package/dist/react/client/Tools.js +24 -18
  46. package/dist/react/client/Tools.js.map +1 -1
  47. package/dist/react/index.d.ts +2 -1
  48. package/dist/react/index.js +2 -1
  49. package/dist/react/interactables-legacy/Interactables.js +246 -125
  50. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  51. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  52. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  53. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  54. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  55. package/dist/react/model-context/makeAssistantTool.js +1 -1
  56. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  57. package/dist/react/model-context/toolbox.d.ts +2 -14
  58. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  59. package/dist/react/model-context/toolbox.js +1 -13
  60. package/dist/react/model-context/toolbox.js.map +1 -1
  61. package/dist/react/model-context/useAssistantContext.js +37 -7
  62. package/dist/react/model-context/useAssistantContext.js.map +1 -1
  63. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  64. package/dist/react/model-context/useAssistantTool.js +1 -1
  65. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  66. package/dist/react/model-context/useAuiToolOverrides.js +31 -5
  67. package/dist/react/model-context/useAuiToolOverrides.js.map +1 -1
  68. package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
  69. package/dist/react/model-context/useInlineRender.js +10 -36
  70. package/dist/react/model-context/useInlineRender.js.map +1 -1
  71. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  72. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  73. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  74. package/dist/react/primitive-hooks/useMessageError.d.ts.map +1 -1
  75. package/dist/react/primitive-hooks/useMessageError.js +2 -8
  76. package/dist/react/primitive-hooks/useMessageError.js.map +1 -1
  77. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
  78. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -4
  79. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  80. package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  81. package/dist/react/primitives/composer/ComposerAttachments.js +4 -3
  82. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  83. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  84. package/dist/react/primitives/composer/ComposerQueue.js +4 -3
  85. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  86. package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
  87. package/dist/react/primitives/message/MessageAttachments.js +7 -6
  88. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  89. package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
  90. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  91. package/dist/react/primitives/message/MessageParts.js +3 -3
  92. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  93. package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  94. package/dist/react/primitives/threadList/ThreadListItems.js +5 -4
  95. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  96. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +6 -4
  97. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  98. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +59 -29
  99. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  100. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +11 -6
  101. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  102. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +160 -143
  103. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  104. package/dist/react/runtimes/RemoteThreadResource.d.ts +2 -6
  105. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  106. package/dist/react/runtimes/RemoteThreadResource.js +15 -13
  107. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  108. package/dist/react/runtimes/assistant-transport/useLatestRef.js +2 -2
  109. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -1
  110. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +4 -1
  111. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  112. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +424 -52
  113. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  114. package/dist/react/runtimes/cloud/auiV0.d.ts +5 -1
  115. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  116. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  117. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +29 -0
  118. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -0
  119. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +160 -0
  120. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -0
  121. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +1 -9
  122. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +16 -153
  124. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  125. package/dist/react/runtimes/external-message-converter.js +1 -1
  126. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  127. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  128. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  129. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  130. package/dist/react/runtimes/useRemoteThreadListRuntime.js +77 -29
  131. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  132. package/dist/react/types/MessagePartComponentTypes.d.ts +7 -3
  133. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  134. package/dist/react/types/scopes/tools.d.ts +2 -0
  135. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  136. package/dist/runtime/api/message-part-runtime.d.ts +2 -2
  137. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  138. package/dist/runtime/api/message-part-runtime.js +1 -1
  139. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  140. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  141. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  142. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  143. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  144. package/dist/runtime/api/thread-list-runtime.d.ts +12 -1
  145. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  146. package/dist/runtime/api/thread-list-runtime.js +23 -14
  147. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  148. package/dist/runtime/api/thread-runtime.d.ts +5 -1
  149. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  150. package/dist/runtime/api/thread-runtime.js +1 -1
  151. package/dist/runtime/api/thread-runtime.js.map +1 -1
  152. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  153. package/dist/runtime/base/base-composer-runtime-core.js +16 -12
  154. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  155. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -2
  156. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  157. package/dist/runtime/base/base-thread-runtime-core.js +8 -7
  158. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  159. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  160. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  161. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +18 -3
  162. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/interfaces/thread-runtime-core.d.ts +15 -1
  164. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  165. package/dist/runtime/utils/auto-status.d.ts +1 -1
  166. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  167. package/dist/runtime/utils/auto-status.js +6 -2
  168. package/dist/runtime/utils/auto-status.js.map +1 -1
  169. package/dist/runtime/utils/external-message-conversion.d.ts +3 -2
  170. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  171. package/dist/runtime/utils/external-message-conversion.js +5 -4
  172. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  173. package/dist/runtime/utils/message-repository.d.ts +1 -0
  174. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  175. package/dist/runtime/utils/message-repository.js +15 -6
  176. package/dist/runtime/utils/message-repository.js.map +1 -1
  177. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +6 -3
  178. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
  179. package/dist/runtime/utils/resolveToolApprovalResponse.js +12 -3
  180. package/dist/runtime/utils/resolveToolApprovalResponse.js.map +1 -1
  181. package/dist/runtime/utils/thread-message-like.d.ts +2 -10
  182. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  183. package/dist/runtime/utils/thread-message-like.js +1 -1
  184. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  185. package/dist/runtime/utils/tool-call-tree.d.ts +39 -0
  186. package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -0
  187. package/dist/runtime/utils/tool-call-tree.js +85 -0
  188. package/dist/runtime/utils/tool-call-tree.js.map +1 -0
  189. package/dist/runtimes/external-store/external-store-adapter.d.ts +29 -2
  190. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  191. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  192. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  193. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  194. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -1
  195. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  196. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +34 -28
  197. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  198. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +1 -1
  199. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
  200. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
  201. package/dist/runtimes/internal.d.ts +2 -2
  202. package/dist/runtimes/internal.js +2 -2
  203. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
  204. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  205. package/dist/runtimes/local/local-thread-runtime-core.js +3 -1
  206. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  207. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  208. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  209. package/dist/runtimes/remote-thread-list/remote-thread-state.js +148 -43
  210. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  211. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  212. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  213. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  214. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  215. package/dist/runtimes/remote-thread-list/title.d.ts +11 -0
  216. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -0
  217. package/dist/runtimes/remote-thread-list/title.js +14 -0
  218. package/dist/runtimes/remote-thread-list/title.js.map +1 -0
  219. package/dist/runtimes/remote-thread-list/types.d.ts +16 -5
  220. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  221. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +31 -8
  222. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  223. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +97 -60
  224. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  225. package/dist/store/clients/external-thread.d.ts +1 -1
  226. package/dist/store/clients/external-thread.d.ts.map +1 -1
  227. package/dist/store/clients/external-thread.js +30 -7
  228. package/dist/store/clients/external-thread.js.map +1 -1
  229. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  230. package/dist/store/clients/model-context-client.js +2 -1
  231. package/dist/store/clients/model-context-client.js.map +1 -1
  232. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  233. package/dist/store/clients/single-thread-list.js +2 -1
  234. package/dist/store/clients/single-thread-list.js.map +1 -1
  235. package/dist/store/clients/suggestions.d.ts.map +1 -1
  236. package/dist/store/clients/suggestions.js +19 -6
  237. package/dist/store/clients/suggestions.js.map +1 -1
  238. package/dist/store/clients/thread-message-client.js +1 -1
  239. package/dist/store/clients/thread-selection-events.js +1 -1
  240. package/dist/store/index.d.ts +2 -1
  241. package/dist/store/index.js +2 -1
  242. package/dist/store/internal.d.ts +2 -2
  243. package/dist/store/internal.js +2 -2
  244. package/dist/store/primitive-predicates.d.ts +4 -1
  245. package/dist/store/primitive-predicates.d.ts.map +1 -1
  246. package/dist/store/primitive-predicates.js +9 -1
  247. package/dist/store/primitive-predicates.js.map +1 -1
  248. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  249. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  250. package/dist/store/runtime-clients/composer-runtime-client.js +98 -62
  251. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  252. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  253. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  254. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  255. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  256. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  257. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +2 -2
  258. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  259. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  260. package/dist/store/runtime-clients/thread-list-runtime-client.js +128 -107
  261. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  262. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  263. package/dist/store/runtime-clients/thread-runtime-client.js +194 -122
  264. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  265. package/dist/store/runtime-clients/useSubscribable.d.ts +3 -1
  266. package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
  267. package/dist/store/runtime-clients/useSubscribable.js +1 -1
  268. package/dist/store/runtime-clients/useSubscribable.js.map +1 -1
  269. package/dist/store/scopes/composer.d.ts +10 -2
  270. package/dist/store/scopes/composer.d.ts.map +1 -1
  271. package/dist/store/scopes/message.d.ts +30 -1
  272. package/dist/store/scopes/message.d.ts.map +1 -1
  273. package/dist/store/scopes/part.d.ts +6 -3
  274. package/dist/store/scopes/part.d.ts.map +1 -1
  275. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  276. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  277. package/dist/store/scopes/thread.d.ts +8 -0
  278. package/dist/store/scopes/thread.d.ts.map +1 -1
  279. package/dist/store/scopes/threads.d.ts +1 -0
  280. package/dist/store/scopes/threads.d.ts.map +1 -1
  281. package/dist/subscribable/subscribable.d.ts +8 -1
  282. package/dist/subscribable/subscribable.d.ts.map +1 -1
  283. package/dist/subscribable/subscribable.js +24 -16
  284. package/dist/subscribable/subscribable.js.map +1 -1
  285. package/dist/types/attachment.d.ts +2 -1
  286. package/dist/types/attachment.d.ts.map +1 -1
  287. package/dist/types/attachment.js +2 -1
  288. package/dist/types/attachment.js.map +1 -1
  289. package/dist/types/message.d.ts +40 -1
  290. package/dist/types/message.d.ts.map +1 -1
  291. package/dist/types/message.js +7 -1
  292. package/dist/types/message.js.map +1 -1
  293. package/dist/utils/abortable-iterable.d.ts +19 -0
  294. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  295. package/dist/utils/abortable-iterable.js +72 -0
  296. package/dist/utils/abortable-iterable.js.map +1 -0
  297. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  298. package/dist/utils/composite-context-provider.js +20 -2
  299. package/dist/utils/composite-context-provider.js.map +1 -1
  300. package/dist/utils/record.d.ts +11 -0
  301. package/dist/utils/record.d.ts.map +1 -0
  302. package/dist/utils/record.js +12 -0
  303. package/dist/utils/record.js.map +1 -0
  304. package/package.json +12 -17
  305. package/src/adapters/attachment.test.ts +50 -1
  306. package/src/adapters/attachment.ts +7 -4
  307. package/src/index.ts +7 -1
  308. package/src/internal.ts +12 -0
  309. package/src/model-context/frame/host.test.ts +24 -0
  310. package/src/model-context/frame/host.ts +10 -4
  311. package/src/model-context/frame/provider.test.ts +288 -1
  312. package/src/model-context/frame/provider.ts +148 -30
  313. package/src/model-context/interactable-composer-metadata.test.ts +28 -0
  314. package/src/model-context/interactable-composer-metadata.ts +9 -3
  315. package/src/model-context/registry.test.ts +12 -0
  316. package/src/model-context/registry.ts +3 -2
  317. package/src/model-context/tool-call-text.test.ts +67 -0
  318. package/src/model-context/tool-call-text.ts +67 -0
  319. package/src/model-context/types.test.ts +25 -0
  320. package/src/model-context/types.ts +9 -4
  321. package/src/react/AssistantRuntimeProvider.test.tsx +66 -2
  322. package/src/react/RuntimeAdapter.test.ts +34 -0
  323. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +1 -3
  324. package/src/react/client/InMemoryThreadList.ts +1 -0
  325. package/src/react/client/Interactables.test.ts +28 -0
  326. package/src/react/client/Interactables.ts +15 -13
  327. package/src/react/client/RemoteThreadList.background.test.ts +526 -0
  328. package/src/react/client/RemoteThreadList.concurrent.test.tsx +315 -0
  329. package/src/react/client/RemoteThreadList.test.ts +617 -3
  330. package/src/react/client/RemoteThreadList.ts +433 -167
  331. package/src/react/client/Tools.ts +29 -12
  332. package/src/react/index.ts +4 -0
  333. package/src/react/interactables-legacy/Interactables.ts +16 -16
  334. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  335. package/src/react/model-context/makeAssistantTool.ts +2 -2
  336. package/src/react/model-context/modelContextHooks.test.tsx +131 -0
  337. package/src/react/model-context/toolbox.test.ts +9 -9
  338. package/src/react/model-context/toolbox.ts +9 -36
  339. package/src/react/model-context/useAssistantContext.ts +4 -2
  340. package/src/react/model-context/useAssistantTool.ts +2 -2
  341. package/src/react/model-context/useAuiToolOverrides.ts +4 -2
  342. package/src/react/model-context/useInlineRender.test.tsx +48 -0
  343. package/src/react/model-context/useInlineRender.ts +14 -10
  344. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  345. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  346. package/src/react/primitive-hooks/useMessageError.ts +2 -19
  347. package/src/react/primitive-hooks/useThreadListLoadMore.ts +2 -3
  348. package/src/react/primitives/composer/ComposerAttachments.test.tsx +76 -0
  349. package/src/react/primitives/composer/ComposerAttachments.tsx +9 -4
  350. package/src/react/primitives/composer/ComposerQueue.test.tsx +72 -0
  351. package/src/react/primitives/composer/ComposerQueue.tsx +7 -4
  352. package/src/react/primitives/message/MessageAttachments.test.tsx +107 -37
  353. package/src/react/primitives/message/MessageAttachments.tsx +10 -7
  354. package/src/react/primitives/message/MessageGroupedParts.tsx +2 -2
  355. package/src/react/primitives/message/MessageParts.tsx +3 -3
  356. package/src/react/primitives/threadList/ThreadListItems.test.tsx +99 -0
  357. package/src/react/primitives/threadList/ThreadListItems.tsx +8 -5
  358. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  359. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +127 -1
  360. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  361. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +99 -35
  362. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  363. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +274 -0
  364. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +223 -152
  365. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  366. package/src/react/runtimes/RemoteThreadResource.ts +20 -16
  367. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +51 -2
  368. package/src/react/runtimes/assistant-transport/useLatestRef.ts +2 -2
  369. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +45 -0
  370. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +546 -5
  371. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +546 -103
  372. package/src/react/runtimes/cloud/auiV0.ts +5 -0
  373. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +103 -0
  374. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +237 -0
  375. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +90 -1
  376. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +33 -238
  377. package/src/react/runtimes/external-message-converter.ts +1 -0
  378. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  379. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  380. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  381. package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +216 -0
  382. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  383. package/src/react/runtimes/useRemoteThreadListRuntime.ts +57 -6
  384. package/src/react/types/MessagePartComponentTypes.ts +7 -3
  385. package/src/react/types/scopes/tools.ts +2 -0
  386. package/src/runtime/api/message-part-runtime.ts +3 -3
  387. package/src/runtime/api/message-runtime.test.ts +75 -0
  388. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  389. package/src/runtime/api/thread-list-runtime.ts +46 -12
  390. package/src/runtime/api/thread-runtime.test.ts +78 -0
  391. package/src/runtime/api/thread-runtime.ts +8 -1
  392. package/src/runtime/base/base-composer-runtime-core.ts +36 -8
  393. package/src/runtime/base/base-thread-runtime-core.test.ts +149 -4
  394. package/src/runtime/base/base-thread-runtime-core.ts +16 -8
  395. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  396. package/src/runtime/interfaces/thread-list-runtime-core.ts +25 -2
  397. package/src/runtime/interfaces/thread-runtime-core.ts +17 -1
  398. package/src/runtime/utils/auto-status.test.ts +57 -0
  399. package/src/runtime/utils/auto-status.ts +13 -1
  400. package/src/runtime/utils/external-message-conversion.test.ts +165 -0
  401. package/src/runtime/utils/external-message-conversion.ts +18 -2
  402. package/src/runtime/utils/message-repository.ts +29 -17
  403. package/src/runtime/utils/resolveToolApprovalResponse.test.ts +73 -0
  404. package/src/runtime/utils/resolveToolApprovalResponse.ts +27 -6
  405. package/src/runtime/utils/thread-message-like.ts +3 -11
  406. package/src/runtime/utils/tool-call-tree.test.ts +191 -0
  407. package/src/runtime/utils/tool-call-tree.ts +97 -0
  408. package/src/runtimes/external-store/external-store-adapter.ts +38 -2
  409. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  410. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +56 -33
  411. package/src/runtimes/inert/InertThreadRuntimeCore.ts +1 -1
  412. package/src/runtimes/internal.ts +1 -0
  413. package/src/runtimes/local/local-thread-runtime-core.test.ts +29 -3
  414. package/src/runtimes/local/local-thread-runtime-core.ts +5 -1
  415. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +238 -0
  416. package/src/runtimes/remote-thread-list/remote-thread-state.ts +223 -44
  417. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  418. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  419. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  420. package/src/runtimes/remote-thread-list/title.ts +16 -0
  421. package/src/runtimes/remote-thread-list/types.ts +16 -5
  422. package/src/runtimes/tool-invocations/EDGE_CASES.md +110 -5
  423. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +750 -136
  424. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +144 -91
  425. package/src/store/clients/external-thread.ts +62 -11
  426. package/src/store/clients/model-context-client.ts +5 -1
  427. package/src/store/clients/single-thread-list.ts +1 -0
  428. package/src/store/clients/suggestions.test.ts +195 -1
  429. package/src/store/clients/suggestions.ts +45 -34
  430. package/src/store/index.ts +4 -0
  431. package/src/store/internal.ts +2 -0
  432. package/src/store/primitive-predicates.ts +35 -0
  433. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  434. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  435. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  436. package/src/store/runtime-clients/thread-list-runtime-client.ts +17 -2
  437. package/src/store/runtime-clients/thread-runtime-client.ts +58 -10
  438. package/src/store/runtime-clients/useSubscribable.ts +8 -2
  439. package/src/store/scopes/composer.ts +16 -4
  440. package/src/store/scopes/message.ts +14 -0
  441. package/src/store/scopes/part.ts +6 -3
  442. package/src/store/scopes/thread-list-item.ts +5 -2
  443. package/src/store/scopes/thread.ts +4 -0
  444. package/src/store/scopes/threads.ts +1 -0
  445. package/src/subscribable/subscribable.test.ts +50 -0
  446. package/src/subscribable/subscribable.ts +33 -21
  447. package/src/tests/MessageRepository.test.ts +116 -0
  448. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  449. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  450. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +158 -0
  451. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +78 -0
  452. package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +52 -0
  453. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  454. package/src/tests/attachment-adapters.test.ts +14 -0
  455. package/src/tests/base-composer-runtime-core.test.ts +39 -0
  456. package/src/tests/external-store-optimistic-id.test.ts +79 -0
  457. package/src/tests/external-store-repository-instance.test.ts +166 -0
  458. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  459. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +49 -34
  460. package/src/tests/external-thread-attachments.test.tsx +224 -1
  461. package/src/tests/external-thread-parity.test.tsx +23 -0
  462. package/src/tests/remote-thread-list-adapter-switch.test.ts +66 -1
  463. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  464. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  465. package/src/tests/thread-switch-events.test.tsx +475 -2
  466. package/src/tests/tools-scope-migration.test.tsx +28 -0
  467. package/src/types/attachment.ts +4 -0
  468. package/src/types/message.ts +50 -2
  469. package/src/utils/abortable-iterable.test.ts +213 -0
  470. package/src/utils/abortable-iterable.ts +93 -0
  471. package/src/utils/composite-context-provider.test.ts +25 -0
  472. package/src/utils/composite-context-provider.ts +23 -2
  473. package/src/utils/record.test.ts +48 -0
  474. package/src/utils/record.ts +11 -0
@@ -0,0 +1,166 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { ExternalStoreThreadRuntimeCore } from "../runtimes/external-store/external-store-thread-runtime-core";
3
+ import type { ExternalStoreAdapter } from "../runtimes/external-store/external-store-adapter";
4
+ import type { ModelContextProvider } from "../model-context/types";
5
+ import type { ThreadMessage } from "../types/message";
6
+ import { MessageRepository } from "../runtime/utils/message-repository";
7
+
8
+ const createContextProvider = (): ModelContextProvider => ({
9
+ getModelContext: () => ({}),
10
+ });
11
+
12
+ const createUserMessage = (id: string, text = "Hello"): ThreadMessage =>
13
+ ({
14
+ id,
15
+ role: "user" as const,
16
+ createdAt: new Date(),
17
+ content: [{ type: "text" as const, text }],
18
+ attachments: [],
19
+ metadata: {
20
+ custom: {},
21
+ },
22
+ }) as ThreadMessage;
23
+
24
+ const createAssistantMessage = (id: string, text = "Hi there"): ThreadMessage =>
25
+ ({
26
+ id,
27
+ role: "assistant" as const,
28
+ createdAt: new Date(),
29
+ content: [{ type: "text" as const, text }],
30
+ status: { type: "complete" as const, reason: "stop" as const },
31
+ metadata: {
32
+ unstable_state: null,
33
+ unstable_annotations: [],
34
+ unstable_data: [],
35
+ steps: [],
36
+ custom: {},
37
+ },
38
+ }) as ThreadMessage;
39
+
40
+ const createAdapter = (
41
+ messages: readonly ThreadMessage[],
42
+ repository: MessageRepository,
43
+ ): ExternalStoreAdapter<ThreadMessage> => ({
44
+ messages,
45
+ onNew: vi.fn(async () => {}),
46
+ unstable_messageRepositoryInstance: repository,
47
+ });
48
+
49
+ describe("ExternalStoreThreadRuntimeCore repository instance swap", () => {
50
+ it("keeps each conversation's history in its own repository across swaps", () => {
51
+ const repoA = new MessageRepository();
52
+ const repoB = new MessageRepository();
53
+ const threadA = [
54
+ createUserMessage("a-u1", "thread a question"),
55
+ createAssistantMessage("a-m1", "thread a answer"),
56
+ ];
57
+ const threadB = [
58
+ createUserMessage("b-u1", "thread b question"),
59
+ createAssistantMessage("b-m1", "thread b answer"),
60
+ ];
61
+
62
+ const core = new ExternalStoreThreadRuntimeCore(
63
+ createContextProvider(),
64
+ createAdapter(threadA, repoA),
65
+ );
66
+ expect(core.messages.map((m) => m.id)).toEqual(["a-u1", "a-m1"]);
67
+
68
+ core.__internal_setAdapter(createAdapter(threadB, repoB));
69
+ expect(core.messages.map((m) => m.id)).toEqual(["b-u1", "b-m1"]);
70
+ expect(core.getBranches("b-u1")).toEqual(["b-u1"]);
71
+
72
+ core.__internal_setAdapter(createAdapter(threadA, repoA));
73
+ expect(core.messages.map((m) => m.id)).toEqual(["a-u1", "a-m1"]);
74
+ expect(core.getBranches("a-u1")).toEqual(["a-u1"]);
75
+ expect(repoB.getMessages().map((m) => m.id)).toEqual(["b-u1", "b-m1"]);
76
+ });
77
+
78
+ it("preserves intra-thread branches across a swap through an empty conversation", () => {
79
+ const repoA = new MessageRepository();
80
+ const repoB = new MessageRepository();
81
+ const original = [
82
+ createUserMessage("a-u1"),
83
+ createAssistantMessage("a-m1"),
84
+ ];
85
+ const edited = [
86
+ createUserMessage("a-u2", "edited question"),
87
+ createAssistantMessage("a-m2", "edited answer"),
88
+ ];
89
+
90
+ const core = new ExternalStoreThreadRuntimeCore(
91
+ createContextProvider(),
92
+ createAdapter(original, repoA),
93
+ );
94
+ repoA.addOrUpdateMessage(null, edited[0]!);
95
+ repoA.addOrUpdateMessage("a-u2", edited[1]!);
96
+ core.__internal_setAdapter(createAdapter(edited, repoA));
97
+ expect(core.getBranches("a-u2")).toEqual(["a-u1", "a-u2"]);
98
+
99
+ core.__internal_setAdapter(createAdapter([], repoB));
100
+ expect(core.messages).toHaveLength(0);
101
+
102
+ core.__internal_setAdapter(createAdapter(edited, repoA));
103
+ expect(core.messages.map((m) => m.id)).toEqual(["a-u2", "a-m2"]);
104
+ expect(core.getBranches("a-u2")).toEqual(["a-u1", "a-u2"]);
105
+ });
106
+
107
+ it("does not adopt anything when the instance is omitted", () => {
108
+ const messages = [createUserMessage("u1")];
109
+ const core = new ExternalStoreThreadRuntimeCore(createContextProvider(), {
110
+ messages,
111
+ onNew: vi.fn(async () => {}),
112
+ });
113
+ expect(core.messages.map((m) => m.id)).toEqual(["u1"]);
114
+
115
+ const next = [createUserMessage("u1"), createAssistantMessage("m1")];
116
+ core.__internal_setAdapter({
117
+ messages: next,
118
+ onNew: vi.fn(async () => {}),
119
+ });
120
+ expect(core.messages.map((m) => m.id)).toEqual(["u1", "m1"]);
121
+ });
122
+
123
+ it("reconciles a same-reference messages array when only the repository swapped", () => {
124
+ const repoA = new MessageRepository();
125
+ const repoB = new MessageRepository();
126
+ const shared = [createUserMessage("s-u1")];
127
+
128
+ const core = new ExternalStoreThreadRuntimeCore(
129
+ createContextProvider(),
130
+ createAdapter(shared, repoA),
131
+ );
132
+ expect(repoA.getMessages().map((m) => m.id)).toEqual(["s-u1"]);
133
+ expect(repoB.getMessages()).toHaveLength(0);
134
+
135
+ core.__internal_setAdapter(createAdapter(shared, repoB));
136
+ expect(repoB.getMessages().map((m) => m.id)).toEqual(["s-u1"]);
137
+ expect(core.messages.map((m) => m.id)).toEqual(["s-u1"]);
138
+ });
139
+
140
+ it("re-imports a same-reference repository snapshot when the instance swapped", () => {
141
+ const repoA = new MessageRepository();
142
+ const repoB = new MessageRepository();
143
+ const snapshot = {
144
+ headId: "s-m1",
145
+ messages: [
146
+ { parentId: null, message: createUserMessage("s-u1") },
147
+ { parentId: "s-u1", message: createAssistantMessage("s-m1") },
148
+ ],
149
+ };
150
+
151
+ const core = new ExternalStoreThreadRuntimeCore(createContextProvider(), {
152
+ messageRepository: snapshot,
153
+ onNew: vi.fn(async () => {}),
154
+ unstable_messageRepositoryInstance: repoA,
155
+ });
156
+ expect(core.messages.map((m) => m.id)).toEqual(["s-u1", "s-m1"]);
157
+
158
+ core.__internal_setAdapter({
159
+ messageRepository: snapshot,
160
+ onNew: vi.fn(async () => {}),
161
+ unstable_messageRepositoryInstance: repoB,
162
+ });
163
+ expect(core.messages.map((m) => m.id)).toEqual(["s-u1", "s-m1"]);
164
+ expect(repoB.getMessages().map((m) => m.id)).toEqual(["s-u1", "s-m1"]);
165
+ });
166
+ });
@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from "vitest";
2
2
  import { ExternalStoreThreadListRuntimeCore } from "../runtimes/external-store/external-store-thread-list-runtime-core";
3
3
  import type { ExternalStoreThreadRuntimeCore } from "../runtimes/external-store/external-store-thread-runtime-core";
4
4
  import { ExternalStoreThreadRuntimeCore as ExternalStoreThreadRuntimeCoreImpl } from "../runtimes/external-store/external-store-thread-runtime-core";
5
+ import { ExternalStoreRuntimeCore } from "../runtimes/external-store/external-store-runtime-core";
5
6
  import type { ExternalStoreThreadListAdapter } from "../runtimes/external-store/external-store-adapter";
6
7
  import type { ExternalStoreAdapter } from "../runtimes/external-store/external-store-adapter";
7
8
  import type { ModelContextProvider } from "../model-context/types";
@@ -109,10 +110,56 @@ describe("ExternalStoreThreadListRuntimeCore - construction", () => {
109
110
  // Two empty-adapter constructions should share the frozen DEFAULT_THREAD_DATA
110
111
  // singleton rather than each getting a fresh `{ ... }` clone.
111
112
  expect(a.threadItems).toBe(b.threadItems);
113
+ expect(Object.getPrototypeOf(a.threadItems)).toBeNull();
112
114
  });
115
+
116
+ it.each(["__proto__", "constructor", "toString"])(
117
+ "returns undefined for an absent prototype-named thread id %s",
118
+ (threadId) => {
119
+ const core = new ExternalStoreThreadListRuntimeCore(
120
+ makeAdapter(),
121
+ makeFactory(),
122
+ );
123
+
124
+ expect(core.getItemById(threadId)).toBeUndefined();
125
+ expect(Object.hasOwn(core.threadItems, threadId)).toBe(false);
126
+ },
127
+ );
113
128
  });
114
129
 
115
130
  describe("ExternalStoreThreadListRuntimeCore - __internal_setAdapter", () => {
131
+ it("updates subscribed loading state when thread ids and arrays stay unchanged", () => {
132
+ const threadList = makeAdapter({
133
+ threadId: "thread-alpha",
134
+ threads: [{ id: "thread-alpha", status: "regular" }],
135
+ archivedThreads: [],
136
+ isLoading: true,
137
+ });
138
+ const adapter: ExternalStoreAdapter = {
139
+ messages: [],
140
+ onNew: async () => {},
141
+ adapters: { threadList },
142
+ };
143
+ const core = new ExternalStoreRuntimeCore(adapter);
144
+ const runtime = new ThreadListRuntimeImpl(core.threads);
145
+ const seen: boolean[] = [];
146
+ const unsubscribe = runtime.subscribe(() => {
147
+ seen.push(runtime.getState().isLoading);
148
+ });
149
+ expect(runtime.getState().isLoading).toBe(true);
150
+
151
+ for (const isLoading of [false, true, undefined, false]) {
152
+ core.setAdapter({
153
+ ...adapter,
154
+ adapters: { threadList: { ...threadList, isLoading } },
155
+ });
156
+ }
157
+
158
+ expect(seen).toEqual([false, true, false]);
159
+ expect(runtime.getState().isLoading).toBe(false);
160
+ unsubscribe();
161
+ });
162
+
116
163
  it("updates mainThreadId when adapter.threadId changes", () => {
117
164
  const core = new ExternalStoreThreadListRuntimeCore(
118
165
  makeAdapter({ threadId: "thread-alpha" }),
@@ -198,6 +245,42 @@ describe("ExternalStoreThreadListRuntimeCore - __internal_setAdapter", () => {
198
245
  expect(item?.id).toBe("thread-beta");
199
246
  });
200
247
 
248
+ it.each(["__proto__", "constructor", "toString"])(
249
+ "synthesizes a main entry for a prototype-named thread id %s",
250
+ (threadId) => {
251
+ const core = new ExternalStoreThreadListRuntimeCore(
252
+ makeAdapter({ threadId }),
253
+ makeFactory(),
254
+ );
255
+
256
+ expect(core.getItemById(threadId)).toEqual(
257
+ expect.objectContaining({ id: threadId, status: "regular" }),
258
+ );
259
+ expect(Object.hasOwn(core.threadItems, threadId)).toBe(true);
260
+ expect(Object.getPrototypeOf(core.threadItems)).toBeNull();
261
+ },
262
+ );
263
+
264
+ it.each(["regular", "archived"] as const)(
265
+ "preserves a prototype-named %s adapter entry",
266
+ (status) => {
267
+ const adapter: ExternalStoreThreadListAdapter =
268
+ status === "regular"
269
+ ? { threads: [{ id: "__proto__", status }] }
270
+ : { archivedThreads: [{ id: "__proto__", status }] };
271
+ const core = new ExternalStoreThreadListRuntimeCore(
272
+ makeAdapter(adapter),
273
+ makeFactory(),
274
+ );
275
+
276
+ expect(core.getItemById("__proto__")).toEqual(
277
+ expect.objectContaining({ id: "__proto__", status }),
278
+ );
279
+ expect(Object.hasOwn(core.threadItems, "__proto__")).toBe(true);
280
+ expect(Object.getPrototypeOf(core.threadItems)).toBeNull();
281
+ },
282
+ );
283
+
201
284
  it("does not retain stale synthesized entries across mainThreadId switches", () => {
202
285
  const core = new ExternalStoreThreadListRuntimeCore(
203
286
  makeAdapter({ threadId: "thread-alpha" }),
@@ -160,26 +160,35 @@ describe("ExternalStoreThreadRuntimeCore adapter contract", () => {
160
160
  expect(onNew).not.toHaveBeenCalled();
161
161
  });
162
162
 
163
- it("throws when adapter has no onEdit and parentId differs from head", async () => {
164
- const messages = [createUserMessage("u1"), createAssistantMessage("a1")];
165
- const adapter = createBaseAdapter({ messages });
166
- const core = new ExternalStoreThreadRuntimeCore(contextProvider, adapter);
163
+ it.each(["u1", null])(
164
+ "rejects parent %s without onEdit",
165
+ async (parentId) => {
166
+ const messages = [
167
+ createUserMessage("u1"),
168
+ createAssistantMessage("a1"),
169
+ ];
170
+ const adapter = createBaseAdapter({ messages });
171
+ const core = new ExternalStoreThreadRuntimeCore(
172
+ contextProvider,
173
+ adapter,
174
+ );
167
175
 
168
- const appendMessage: AppendMessage = {
169
- role: "user",
170
- content: [{ type: "text", text: "Edit" }],
171
- attachments: [],
172
- createdAt: new Date(),
173
- parentId: "u1",
174
- sourceId: null,
175
- runConfig: undefined,
176
- metadata: { custom: {} },
177
- } as AppendMessage;
176
+ const appendMessage: AppendMessage = {
177
+ role: "user",
178
+ content: [{ type: "text", text: "Edit" }],
179
+ attachments: [],
180
+ createdAt: new Date(),
181
+ parentId,
182
+ sourceId: null,
183
+ runConfig: undefined,
184
+ metadata: { custom: {} },
185
+ } as AppendMessage;
178
186
 
179
- await expect(core.append(appendMessage)).rejects.toThrow(
180
- "Runtime does not support editing messages.",
181
- );
182
- });
187
+ await expect(core.append(appendMessage)).rejects.toThrow(
188
+ "Runtime does not support editing messages.",
189
+ );
190
+ },
191
+ );
183
192
  });
184
193
 
185
194
  describe("startRun", () => {
@@ -998,7 +1007,7 @@ describe("ExternalStoreThreadRuntimeCore adapter contract", () => {
998
1007
  core.__internal_setAdapter(
999
1008
  createBaseAdapter({
1000
1009
  unstable_enableToolInvocations: true,
1001
- isRunning: true,
1010
+ isRunning: false,
1002
1011
  messages: [
1003
1012
  {
1004
1013
  ...createAssistantMessage("a1"),
@@ -1068,11 +1077,8 @@ describe("ExternalStoreThreadRuntimeCore adapter contract", () => {
1068
1077
  expect(rejections).toEqual([]);
1069
1078
  });
1070
1079
 
1071
- it("handles rejected onRespondToToolApproval callbacks", async () => {
1080
+ it("hands a rejected onRespondToToolApproval back to the caller", async () => {
1072
1081
  const error = new Error("approval failed");
1073
- const consoleError = vi
1074
- .spyOn(console, "error")
1075
- .mockImplementation(() => {});
1076
1082
  let callbackCalls = 0;
1077
1083
  const core = new ExternalStoreThreadRuntimeCore(
1078
1084
  contextProvider,
@@ -1085,23 +1091,32 @@ describe("ExternalStoreThreadRuntimeCore adapter contract", () => {
1085
1091
  );
1086
1092
 
1087
1093
  const rejections = await captureUnhandledRejections(async () => {
1088
- core.respondToToolApproval({
1089
- approvalId: "approval-1",
1090
- approved: true,
1091
- });
1092
-
1093
- await vi.waitFor(() =>
1094
- expect(consoleError).toHaveBeenCalledWith(
1095
- "[ExternalStoreThreadRuntimeCore] onRespondToToolApproval callback rejected",
1096
- error,
1097
- ),
1098
- );
1094
+ await expect(
1095
+ core.respondToToolApproval({
1096
+ approvalId: "approval-1",
1097
+ approved: true,
1098
+ }),
1099
+ ).rejects.toBe(error);
1099
1100
  });
1100
1101
 
1101
1102
  expect(callbackCalls).toBe(1);
1102
1103
  expect(rejections).toEqual([]);
1103
1104
  });
1104
1105
 
1106
+ it("resolves when a synchronous onRespondToToolApproval accepts", async () => {
1107
+ const core = new ExternalStoreThreadRuntimeCore(
1108
+ contextProvider,
1109
+ createBaseAdapter({ onRespondToToolApproval: () => {} }),
1110
+ );
1111
+
1112
+ await expect(
1113
+ core.respondToToolApproval({
1114
+ approvalId: "approval-1",
1115
+ approved: true,
1116
+ }),
1117
+ ).resolves.toBeUndefined();
1118
+ });
1119
+
1105
1120
  it("handles rejected onCancel callbacks", async () => {
1106
1121
  const error = new Error("cancel failed");
1107
1122
  const consoleError = vi
@@ -2,8 +2,9 @@
2
2
 
3
3
  import { act, render, waitFor } from "@testing-library/react";
4
4
  import type { FC } from "react";
5
- import { afterEach, describe, expect, it, vi } from "vitest";
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
6
  import { AuiProvider, useAui } from "@assistant-ui/store";
7
+ import { CompositeAttachmentAdapter } from "../adapters/attachment";
7
8
  import type {
8
9
  ExternalThreadMessage,
9
10
  ExternalThreadProps,
@@ -14,6 +15,24 @@ import type {
14
15
  PendingAttachment,
15
16
  } from "../types/attachment";
16
17
 
18
+ const { mockGenerateId, realGenerateId } = vi.hoisted(() => {
19
+ const realGenerateId = { current: (): string => "" };
20
+ return {
21
+ realGenerateId,
22
+ mockGenerateId: vi.fn(() => realGenerateId.current()),
23
+ };
24
+ });
25
+
26
+ vi.mock("../utils/id", async (importOriginal) => {
27
+ const actual = await importOriginal<typeof import("../utils/id")>();
28
+ realGenerateId.current = actual.generateId;
29
+ return { ...actual, generateId: mockGenerateId };
30
+ });
31
+
32
+ beforeEach(() => {
33
+ mockGenerateId.mockReset();
34
+ });
35
+
17
36
  const renderThreadWithProps = (props: Partial<ExternalThreadProps>) => {
18
37
  const captured: { aui?: ReturnType<typeof useAui> } = {};
19
38
  const Capture: FC = () => {
@@ -57,6 +76,72 @@ const renderThread = () => {
57
76
  };
58
77
 
59
78
  describe("ExternalThread attachments", () => {
79
+ it("uses generated IDs unless a prepared attachment supplies one", async () => {
80
+ const aui = renderThread();
81
+ mockGenerateId
82
+ .mockReturnValueOnce("generated-file-id")
83
+ .mockReturnValueOnce("generated-prepared-id");
84
+
85
+ await act(async () => {
86
+ await aui()
87
+ .thread()
88
+ .composer()
89
+ .addAttachment(new File(["data"], "photo.png", { type: "image/png" }));
90
+ await aui()
91
+ .thread()
92
+ .composer()
93
+ .addAttachment({
94
+ name: "notes.txt",
95
+ contentType: "text/plain",
96
+ content: [{ type: "text", text: "hello" }],
97
+ });
98
+ await aui().thread().composer().addAttachment({
99
+ id: "provided-id",
100
+ name: "report.pdf",
101
+ contentType: "application/pdf",
102
+ content: [],
103
+ });
104
+ });
105
+
106
+ await waitFor(() => {
107
+ expect(
108
+ aui()
109
+ .thread()
110
+ .composer()
111
+ .getState()
112
+ .attachments.map((attachment) => attachment.id),
113
+ ).toEqual(["generated-file-id", "generated-prepared-id", "provided-id"]);
114
+ });
115
+ expect(mockGenerateId).toHaveBeenCalledTimes(2);
116
+ });
117
+
118
+ it("generates distinct IDs for attachments without one", async () => {
119
+ const aui = renderThread();
120
+
121
+ await act(async () => {
122
+ await aui()
123
+ .thread.composer()
124
+ .addAttachment(new File(["data"], "photo.png", { type: "image/png" }));
125
+ await aui()
126
+ .thread.composer()
127
+ .addAttachment({
128
+ name: "notes.txt",
129
+ contentType: "text/plain",
130
+ content: [{ type: "text", text: "hello" }],
131
+ });
132
+ });
133
+
134
+ await waitFor(() =>
135
+ expect(aui().thread.composer().getState().attachments).toHaveLength(2),
136
+ );
137
+ const ids = aui()
138
+ .thread.composer()
139
+ .getState()
140
+ .attachments.map((attachment) => attachment.id);
141
+ expect(ids.every((id) => id.length > 0)).toBe(true);
142
+ expect(new Set(ids).size).toBe(2);
143
+ });
144
+
60
145
  it("adds prepared attachments when File is unavailable", async () => {
61
146
  const aui = renderThread();
62
147
  const fileConstructor = globalThis.File;
@@ -320,6 +405,53 @@ describe("ExternalThread attachments", () => {
320
405
  },
321
406
  );
322
407
 
408
+ it("preserves pending attachments when adapter removal fails", async () => {
409
+ const error = new Error("remove failed");
410
+ const remove = vi.fn(async () => {
411
+ throw error;
412
+ });
413
+ const file = new File(["data"], "notes.txt", { type: "text/plain" });
414
+ const adapter = {
415
+ accept: "*",
416
+ add: async () => ({
417
+ id: "att-1",
418
+ type: "file" as const,
419
+ name: file.name,
420
+ contentType: file.type,
421
+ file,
422
+ status: {
423
+ type: "requires-action" as const,
424
+ reason: "composer-send" as const,
425
+ },
426
+ }),
427
+ send: async () => ({}) as never,
428
+ remove,
429
+ };
430
+ const aui = renderThreadWithProps({ attachmentAdapter: adapter });
431
+ const composer = () => aui().thread.composer();
432
+
433
+ await act(() => composer().addAttachment(file));
434
+ await waitFor(() =>
435
+ expect(composer().getState().attachments).toHaveLength(1),
436
+ );
437
+
438
+ await expect(
439
+ act(() => composer().attachment({ id: "att-1" }).remove()),
440
+ ).rejects.toBe(error);
441
+
442
+ expect(remove).toHaveBeenCalledWith(
443
+ expect.objectContaining({ id: "att-1" }),
444
+ );
445
+ expect(composer().getState().attachments).toHaveLength(1);
446
+ await waitFor(() =>
447
+ expect(composer().getState().attachments[0]?.status).toEqual({
448
+ type: "incomplete",
449
+ reason: "error",
450
+ message: "remove failed",
451
+ }),
452
+ );
453
+ });
454
+
323
455
  it.each([
324
456
  [
325
457
  "clearAttachments",
@@ -526,6 +658,97 @@ describe("ExternalThread attachments", () => {
526
658
  });
527
659
  });
528
660
 
661
+ it("removes complete edit attachments without adapter cleanup", async () => {
662
+ const remove = vi.fn(async () => {});
663
+ const completeAttachment = {
664
+ id: "image-1",
665
+ type: "image",
666
+ name: "image",
667
+ content: [{ type: "image", image: "data:image/png;base64,data" }],
668
+ status: { type: "complete" },
669
+ } as unknown as CompleteAttachment;
670
+ const aui = renderThreadWithProps({
671
+ messages: [
672
+ {
673
+ id: "u1",
674
+ role: "user",
675
+ content: [{ type: "text", text: "hi" }],
676
+ createdAt: new Date(0),
677
+ attachments: [completeAttachment],
678
+ metadata: { custom: {} },
679
+ } as unknown as ExternalThreadMessage,
680
+ ],
681
+ onEdit: () => {},
682
+ attachmentAdapter: new CompositeAttachmentAdapter([
683
+ {
684
+ accept: "image/*",
685
+ add: async () => {
686
+ throw new Error("not used");
687
+ },
688
+ send: async () => {
689
+ throw new Error("not used");
690
+ },
691
+ remove,
692
+ },
693
+ ]),
694
+ });
695
+ const composer = () => aui().thread.message({ id: "u1" }).composer();
696
+
697
+ await act(async () => composer().beginEdit());
698
+ await waitFor(() =>
699
+ expect(composer().getState().attachments).toHaveLength(1),
700
+ );
701
+ await act(() => composer().attachment({ id: "image-1" }).remove());
702
+
703
+ expect(remove).not.toHaveBeenCalled();
704
+ expect(composer().getState().attachments).toEqual([]);
705
+ });
706
+
707
+ it("calls adapter.remove for pending edit attachments", async () => {
708
+ const remove = vi.fn(async () => {});
709
+ const file = new File(["data"], "notes.txt", { type: "text/plain" });
710
+ const aui = renderThreadWithProps({
711
+ messages: [
712
+ {
713
+ id: "u1",
714
+ role: "user",
715
+ content: [{ type: "text", text: "hi" }],
716
+ createdAt: new Date(0),
717
+ attachments: [],
718
+ metadata: { custom: {} },
719
+ } as unknown as ExternalThreadMessage,
720
+ ],
721
+ onEdit: () => {},
722
+ attachmentAdapter: {
723
+ accept: "*",
724
+ add: async () => ({
725
+ id: "pending-1",
726
+ type: "document" as const,
727
+ name: file.name,
728
+ contentType: file.type,
729
+ file,
730
+ status: {
731
+ type: "requires-action" as const,
732
+ reason: "composer-send" as const,
733
+ },
734
+ }),
735
+ send: async () => ({}) as never,
736
+ remove,
737
+ },
738
+ });
739
+ const composer = () => aui().thread.message({ id: "u1" }).composer();
740
+
741
+ await act(async () => composer().beginEdit());
742
+ await act(() => composer().addAttachment(file));
743
+ await act(() => composer().attachment({ id: "pending-1" }).remove());
744
+
745
+ expect(remove).toHaveBeenCalledTimes(1);
746
+ expect(remove).toHaveBeenCalledWith(
747
+ expect.objectContaining({ id: "pending-1" }),
748
+ );
749
+ expect(composer().getState().attachments).toEqual([]);
750
+ });
751
+
529
752
  it("merges the failed send into a draft the user already modified", async () => {
530
753
  vi.spyOn(console, "error").mockImplementation(() => {});
531
754
  let rejectSend!: (reason: unknown) => void;
@@ -229,6 +229,29 @@ describe("ExternalThread unset optional callbacks", () => {
229
229
  });
230
230
  });
231
231
 
232
+ describe("ExternalThread append parent selection", () => {
233
+ it("passes an explicit null parent to the host", () => {
234
+ const onNew = vi.fn();
235
+ const { aui } = renderThread({
236
+ messages: [assistantMessage({ type: "complete", reason: "stop" })],
237
+ onNew,
238
+ });
239
+
240
+ aui().thread.append({
241
+ parentId: null,
242
+ content: [{ type: "text", text: "new root" }],
243
+ startRun: false,
244
+ });
245
+
246
+ expect(onNew).toHaveBeenCalledExactlyOnceWith(
247
+ expect.objectContaining({
248
+ parentId: null,
249
+ content: [{ type: "text", text: "new root" }],
250
+ }),
251
+ );
252
+ });
253
+ });
254
+
232
255
  describe("ExternalThread composer", () => {
233
256
  it("dispatches a synchronous setText + send sequence", async () => {
234
257
  const onNew = vi.fn();