@assistant-ui/core 0.3.17 → 0.3.19

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 (470) 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/adapters/speech.d.ts.map +1 -1
  5. package/dist/adapters/speech.js +13 -3
  6. package/dist/adapters/speech.js.map +1 -1
  7. package/dist/adapters/thread-history.d.ts +4 -0
  8. package/dist/adapters/thread-history.d.ts.map +1 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal.d.ts +4 -3
  12. package/dist/internal.js +3 -2
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +35 -14
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/frame/provider.d.ts +2 -0
  17. package/dist/model-context/frame/provider.d.ts.map +1 -1
  18. package/dist/model-context/frame/provider.js +30 -7
  19. package/dist/model-context/frame/provider.js.map +1 -1
  20. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  21. package/dist/model-context/interactable-composer-metadata.js +27 -18
  22. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  23. package/dist/model-context/registry.d.ts.map +1 -1
  24. package/dist/model-context/registry.js +3 -5
  25. package/dist/model-context/registry.js.map +1 -1
  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/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  30. package/dist/react/adapters/LocalStorageThreadListAdapter.js +45 -7
  31. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  32. package/dist/react/client/DataRenderers.d.ts.map +1 -1
  33. package/dist/react/client/DataRenderers.js +20 -15
  34. package/dist/react/client/DataRenderers.js.map +1 -1
  35. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  36. package/dist/react/client/InMemoryThreadList.js +62 -56
  37. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  38. package/dist/react/client/Interactables.d.ts.map +1 -1
  39. package/dist/react/client/Interactables.js +35 -32
  40. package/dist/react/client/Interactables.js.map +1 -1
  41. package/dist/react/client/RemoteThreadList.d.ts +1 -0
  42. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  43. package/dist/react/client/RemoteThreadList.js +154 -143
  44. package/dist/react/client/RemoteThreadList.js.map +1 -1
  45. package/dist/react/client/Tools.d.ts.map +1 -1
  46. package/dist/react/client/Tools.js +18 -16
  47. package/dist/react/client/Tools.js.map +1 -1
  48. package/dist/react/client/interactable-model-context.d.ts.map +1 -1
  49. package/dist/react/client/interactable-model-context.js +3 -2
  50. package/dist/react/client/interactable-model-context.js.map +1 -1
  51. package/dist/react/index.d.ts +2 -2
  52. package/dist/react/index.js +2 -2
  53. package/dist/react/interactables-legacy/Interactables.js +242 -121
  54. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  55. package/dist/react/interactables-legacy/useAssistantInteractable.d.ts.map +1 -1
  56. package/dist/react/interactables-legacy/useAssistantInteractable.js +3 -0
  57. package/dist/react/interactables-legacy/useAssistantInteractable.js.map +1 -1
  58. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  59. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  60. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  61. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  62. package/dist/react/model-context/makeAssistantTool.js +1 -1
  63. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  64. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  65. package/dist/react/model-context/useAssistantTool.js +1 -1
  66. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  67. package/dist/react/model-context/useInteractable.d.ts.map +1 -1
  68. package/dist/react/model-context/useInteractable.js +4 -1
  69. package/dist/react/model-context/useInteractable.js.map +1 -1
  70. package/dist/react/model-context/useInteractableVersions.js +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/primitives/message/MessageGroupedParts.d.ts +11 -2
  75. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  76. package/dist/react/primitives/message/MessageGroupedParts.js +3 -2
  77. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  78. package/dist/react/primitives/part/PartMessages.d.ts +1 -0
  79. package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
  80. package/dist/react/primitives/part/PartMessages.js +1 -0
  81. package/dist/react/primitives/part/PartMessages.js.map +1 -1
  82. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  83. package/dist/react/primitives/thread/ThreadMessages.js +6 -5
  84. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  85. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  86. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +16 -8
  87. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  88. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -1
  89. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +105 -104
  91. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  92. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -0
  93. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  94. package/dist/react/runtimes/RemoteThreadResource.js +13 -11
  95. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  96. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +5 -24
  97. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  98. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +354 -226
  99. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  100. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -0
  101. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  102. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +22 -2
  103. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  104. package/dist/react/runtimes/cloud/auiV0.d.ts +40 -15
  105. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  106. package/dist/react/runtimes/cloud/auiV0.js +62 -15
  107. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  108. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +2 -1
  109. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
  110. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +6 -1
  111. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -1
  112. package/dist/react/runtimes/cloud/sdkIdentity.d.ts +6 -0
  113. package/dist/react/runtimes/cloud/sdkIdentity.d.ts.map +1 -0
  114. package/dist/react/runtimes/cloud/sdkIdentity.js +9 -0
  115. package/dist/react/runtimes/cloud/sdkIdentity.js.map +1 -0
  116. package/dist/react/runtimes/external-message-converter.d.ts +12 -2
  117. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  118. package/dist/react/runtimes/external-message-converter.js +26 -61
  119. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  120. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  121. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  122. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  123. package/dist/react/runtimes/useRemoteThreadListRuntime.js +64 -24
  124. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  125. package/dist/react/utils/groupParts.d.ts +6 -1
  126. package/dist/react/utils/groupParts.d.ts.map +1 -1
  127. package/dist/react/utils/groupParts.js +6 -0
  128. package/dist/react/utils/groupParts.js.map +1 -1
  129. package/dist/react/utils/useJSONEqual.d.ts +3 -1
  130. package/dist/react/utils/useJSONEqual.d.ts.map +1 -1
  131. package/dist/react/utils/useJSONEqual.js +6 -1
  132. package/dist/react/utils/useJSONEqual.js.map +1 -1
  133. package/dist/react/utils/useJSONSchemaDependency.d.ts +7 -0
  134. package/dist/react/utils/useJSONSchemaDependency.d.ts.map +1 -0
  135. package/dist/react/utils/useJSONSchemaDependency.js +20 -0
  136. package/dist/react/utils/useJSONSchemaDependency.js.map +1 -0
  137. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  138. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  139. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  140. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  141. package/dist/runtime/api/thread-list-runtime.d.ts +3 -0
  142. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  143. package/dist/runtime/api/thread-list-runtime.js +18 -14
  144. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  145. package/dist/runtime/api/thread-runtime.d.ts +4 -0
  146. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  147. package/dist/runtime/api/thread-runtime.js +3 -6
  148. package/dist/runtime/api/thread-runtime.js.map +1 -1
  149. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
  150. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  151. package/dist/runtime/base/base-composer-runtime-core.js +62 -22
  152. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  153. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -0
  154. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  155. package/dist/runtime/base/base-thread-runtime-core.js +161 -69
  156. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  157. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  158. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  159. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +4 -1
  160. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  161. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  162. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/queue/message-queue.d.ts +5 -0
  164. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  165. package/dist/runtime/queue/message-queue.js +61 -6
  166. package/dist/runtime/queue/message-queue.js.map +1 -1
  167. package/dist/runtime/utils/auto-status.d.ts +6 -2
  168. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  169. package/dist/runtime/utils/auto-status.js +12 -4
  170. package/dist/runtime/utils/auto-status.js.map +1 -1
  171. package/dist/runtime/utils/external-message-conversion.d.ts +16 -4
  172. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  173. package/dist/runtime/utils/external-message-conversion.js +49 -13
  174. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  175. package/dist/runtime/utils/message-repository.d.ts +1 -0
  176. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  177. package/dist/runtime/utils/message-repository.js +18 -9
  178. package/dist/runtime/utils/message-repository.js.map +1 -1
  179. package/dist/runtime/utils/thread-message-like.d.ts +2 -1
  180. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  181. package/dist/runtime/utils/thread-message-like.js +5 -3
  182. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  183. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  184. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  185. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  186. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  187. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +8 -1
  188. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  189. package/dist/runtimes/internal.d.ts +3 -3
  190. package/dist/runtimes/internal.js +2 -2
  191. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
  192. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  193. package/dist/runtimes/local/local-thread-runtime-core.js +4 -1
  194. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  195. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  196. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  197. package/dist/runtimes/remote-thread-list/remote-thread-state.js +130 -42
  198. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  199. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  200. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  201. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  202. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  203. package/dist/runtimes/remote-thread-list/title.d.ts +3 -1
  204. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -1
  205. package/dist/runtimes/remote-thread-list/title.js +9 -1
  206. package/dist/runtimes/remote-thread-list/title.js.map +1 -1
  207. package/dist/runtimes/remote-thread-list/types.d.ts +10 -0
  208. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  209. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  210. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -1
  211. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  212. package/dist/store/clients/external-thread.d.ts.map +1 -1
  213. package/dist/store/clients/external-thread.js +260 -203
  214. package/dist/store/clients/external-thread.js.map +1 -1
  215. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  216. package/dist/store/clients/model-context-client.js +2 -1
  217. package/dist/store/clients/model-context-client.js.map +1 -1
  218. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  219. package/dist/store/clients/single-thread-list.js +1 -0
  220. package/dist/store/clients/single-thread-list.js.map +1 -1
  221. package/dist/store/clients/suggestions.d.ts.map +1 -1
  222. package/dist/store/clients/suggestions.js +5 -4
  223. package/dist/store/clients/suggestions.js.map +1 -1
  224. package/dist/store/clients/thread-tasks.d.ts +13 -0
  225. package/dist/store/clients/thread-tasks.d.ts.map +1 -0
  226. package/dist/store/clients/thread-tasks.js +69 -0
  227. package/dist/store/clients/thread-tasks.js.map +1 -0
  228. package/dist/store/index.d.ts +3 -2
  229. package/dist/store/index.js +3 -3
  230. package/dist/store/primitive-predicates.d.ts.map +1 -1
  231. package/dist/store/primitive-predicates.js +4 -4
  232. package/dist/store/primitive-predicates.js.map +1 -1
  233. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  234. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  235. package/dist/store/runtime-clients/composer-runtime-client.js +97 -61
  236. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  237. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  238. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  239. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  240. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  241. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  242. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  243. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  244. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  245. package/dist/store/runtime-clients/thread-list-runtime-client.js +54 -52
  246. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  247. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  248. package/dist/store/runtime-clients/thread-runtime-client.js +246 -126
  249. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  250. package/dist/store/scope-registration.d.ts +2 -0
  251. package/dist/store/scope-registration.d.ts.map +1 -1
  252. package/dist/store/scopes/composer.d.ts +10 -2
  253. package/dist/store/scopes/composer.d.ts.map +1 -1
  254. package/dist/store/scopes/message.d.ts +30 -1
  255. package/dist/store/scopes/message.d.ts.map +1 -1
  256. package/dist/store/scopes/task.d.ts +36 -0
  257. package/dist/store/scopes/task.d.ts.map +1 -0
  258. package/dist/store/scopes/task.js +0 -0
  259. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  260. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  261. package/dist/store/scopes/thread.d.ts +28 -0
  262. package/dist/store/scopes/thread.d.ts.map +1 -1
  263. package/dist/store/scopes/threads.d.ts +1 -0
  264. package/dist/store/scopes/threads.d.ts.map +1 -1
  265. package/dist/subscribable/subscribable.d.ts +2 -1
  266. package/dist/subscribable/subscribable.d.ts.map +1 -1
  267. package/dist/subscribable/subscribable.js +23 -15
  268. package/dist/subscribable/subscribable.js.map +1 -1
  269. package/dist/types/attachment.d.ts +2 -1
  270. package/dist/types/attachment.d.ts.map +1 -1
  271. package/dist/types/attachment.js +2 -1
  272. package/dist/types/attachment.js.map +1 -1
  273. package/dist/types/index.d.ts +2 -2
  274. package/dist/types/message.d.ts +8 -1
  275. package/dist/types/message.d.ts.map +1 -1
  276. package/dist/types/message.js.map +1 -1
  277. package/dist/utils/abortable-iterable.d.ts +19 -0
  278. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  279. package/dist/utils/abortable-iterable.js +88 -0
  280. package/dist/utils/abortable-iterable.js.map +1 -0
  281. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  282. package/dist/utils/composite-context-provider.js +20 -2
  283. package/dist/utils/composite-context-provider.js.map +1 -1
  284. package/dist/utils/getGroupStatus.d.ts +18 -2
  285. package/dist/utils/getGroupStatus.d.ts.map +1 -1
  286. package/dist/utils/getGroupStatus.js +32 -7
  287. package/dist/utils/getGroupStatus.js.map +1 -1
  288. package/dist/utils/record.d.ts +11 -0
  289. package/dist/utils/record.d.ts.map +1 -0
  290. package/dist/utils/record.js +12 -0
  291. package/dist/utils/record.js.map +1 -0
  292. package/dist/utils/wire-media.d.ts.map +1 -1
  293. package/dist/utils/wire-media.js +2 -2
  294. package/dist/utils/wire-media.js.map +1 -1
  295. package/package.json +11 -11
  296. package/src/adapters/attachment.test.ts +50 -1
  297. package/src/adapters/attachment.ts +7 -4
  298. package/src/adapters/speech.test.ts +236 -0
  299. package/src/adapters/speech.ts +17 -10
  300. package/src/adapters/thread-history.ts +3 -0
  301. package/src/index.ts +2 -0
  302. package/src/internal.ts +4 -0
  303. package/src/model-context/frame/host.test.ts +137 -1
  304. package/src/model-context/frame/host.ts +49 -16
  305. package/src/model-context/frame/provider.test.ts +71 -0
  306. package/src/model-context/frame/provider.ts +37 -7
  307. package/src/model-context/interactable-composer-metadata.test.ts +58 -0
  308. package/src/model-context/interactable-composer-metadata.ts +19 -13
  309. package/src/model-context/registry.test.ts +12 -0
  310. package/src/model-context/registry.ts +3 -2
  311. package/src/model-context/types.test.ts +25 -0
  312. package/src/model-context/types.ts +9 -4
  313. package/src/react/RuntimeAdapter.test.ts +34 -0
  314. package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +244 -0
  315. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +104 -7
  316. package/src/react/client/DataRenderers.test.tsx +45 -0
  317. package/src/react/client/DataRenderers.ts +13 -9
  318. package/src/react/client/InMemoryThreadList.ts +1 -0
  319. package/src/react/client/Interactables.test.ts +72 -1
  320. package/src/react/client/Interactables.ts +22 -14
  321. package/src/react/client/RemoteThreadList.background.test.ts +148 -6
  322. package/src/react/client/RemoteThreadList.concurrent.test.tsx +42 -1
  323. package/src/react/client/RemoteThreadList.test.ts +386 -4
  324. package/src/react/client/RemoteThreadList.ts +181 -112
  325. package/src/react/client/Tools.ts +22 -11
  326. package/src/react/client/interactable-model-context.test.ts +44 -0
  327. package/src/react/client/interactable-model-context.ts +3 -2
  328. package/src/react/index.ts +5 -1
  329. package/src/react/interactables-legacy/Interactables.ts +16 -16
  330. package/src/react/interactables-legacy/useAssistantInteractable.test.tsx +132 -0
  331. package/src/react/interactables-legacy/useAssistantInteractable.ts +10 -1
  332. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  333. package/src/react/model-context/makeAssistantTool.ts +2 -2
  334. package/src/react/model-context/useAssistantTool.ts +2 -2
  335. package/src/react/model-context/useInteractable.test.tsx +177 -0
  336. package/src/react/model-context/useInteractable.ts +3 -0
  337. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  338. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  339. package/src/react/primitive-hooks/useActionBarCopy.test.ts +0 -1
  340. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +0 -1
  341. package/src/react/primitives/message/MessageGroupedParts.test.tsx +96 -0
  342. package/src/react/primitives/message/MessageGroupedParts.tsx +15 -4
  343. package/src/react/primitives/part/PartMessages.test.tsx +104 -1
  344. package/src/react/primitives/part/PartMessages.tsx +1 -0
  345. package/src/react/primitives/thread/ThreadMessages.test.tsx +142 -0
  346. package/src/react/primitives/thread/ThreadMessages.tsx +8 -5
  347. package/src/react/providers/ReadonlyThreadProvider.test.tsx +56 -0
  348. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  349. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +33 -0
  350. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  351. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +28 -5
  352. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  353. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +105 -0
  354. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +143 -112
  355. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  356. package/src/react/runtimes/RemoteThreadResource.ts +19 -12
  357. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +20 -14
  358. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +703 -6
  359. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +492 -402
  360. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +77 -0
  361. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +27 -1
  362. package/src/react/runtimes/cloud/auiV0.ts +160 -25
  363. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +36 -0
  364. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +8 -1
  365. package/src/react/runtimes/cloud/sdkIdentity.ts +9 -0
  366. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +18 -4
  367. package/src/react/runtimes/external-message-converter.test.ts +136 -1
  368. package/src/react/runtimes/external-message-converter.ts +39 -93
  369. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  370. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  371. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  372. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  373. package/src/react/runtimes/useRemoteThreadListRuntime.ts +34 -1
  374. package/src/react/utils/groupParts.ts +12 -1
  375. package/src/react/utils/useJSONEqual.ts +9 -0
  376. package/src/react/utils/useJSONSchemaDependency.ts +22 -0
  377. package/src/runtime/api/message-runtime.test.ts +75 -0
  378. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  379. package/src/runtime/api/thread-list-runtime.ts +21 -11
  380. package/src/runtime/api/thread-runtime.test.ts +136 -0
  381. package/src/runtime/api/thread-runtime.ts +10 -5
  382. package/src/runtime/base/base-composer-runtime-core.ts +105 -24
  383. package/src/runtime/base/base-thread-runtime-core.test.ts +1063 -2
  384. package/src/runtime/base/base-thread-runtime-core.ts +228 -87
  385. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  386. package/src/runtime/interfaces/thread-list-runtime-core.ts +5 -1
  387. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  388. package/src/runtime/queue/message-queue.ts +68 -6
  389. package/src/runtime/utils/auto-status.test.ts +112 -1
  390. package/src/runtime/utils/auto-status.ts +31 -5
  391. package/src/runtime/utils/external-message-conversion.test.ts +202 -0
  392. package/src/runtime/utils/external-message-conversion.ts +123 -32
  393. package/src/runtime/utils/message-repository.test.ts +49 -0
  394. package/src/runtime/utils/message-repository.ts +32 -20
  395. package/src/runtime/utils/thread-message-like.test.ts +52 -0
  396. package/src/runtime/utils/thread-message-like.ts +5 -1
  397. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  398. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +31 -1
  399. package/src/runtimes/internal.ts +1 -0
  400. package/src/runtimes/local/local-thread-runtime-core.test.ts +21 -0
  401. package/src/runtimes/local/local-thread-runtime-core.ts +11 -1
  402. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +128 -2
  403. package/src/runtimes/remote-thread-list/remote-thread-state.ts +182 -42
  404. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  405. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  406. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  407. package/src/runtimes/remote-thread-list/title.ts +13 -0
  408. package/src/runtimes/remote-thread-list/types.ts +10 -0
  409. package/src/runtimes/tool-invocations/EDGE_CASES.md +2 -6
  410. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +133 -0
  411. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +10 -6
  412. package/src/store/clients/external-thread.ts +67 -4
  413. package/src/store/clients/model-context-client.ts +5 -1
  414. package/src/store/clients/single-thread-list.ts +1 -0
  415. package/src/store/clients/suggestions.test.ts +40 -0
  416. package/src/store/clients/suggestions.ts +13 -10
  417. package/src/store/clients/thread-tasks.test.ts +274 -0
  418. package/src/store/clients/thread-tasks.ts +138 -0
  419. package/src/store/index.ts +8 -0
  420. package/src/store/primitive-predicates.test.ts +48 -5
  421. package/src/store/primitive-predicates.ts +7 -2
  422. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  423. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  424. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  425. package/src/store/runtime-clients/thread-list-runtime-client.ts +1 -0
  426. package/src/store/runtime-clients/thread-runtime-client.ts +89 -11
  427. package/src/store/scope-registration.ts +2 -0
  428. package/src/store/scopes/composer.ts +16 -4
  429. package/src/store/scopes/message.ts +14 -0
  430. package/src/store/scopes/task.ts +34 -0
  431. package/src/store/scopes/thread-list-item.ts +5 -2
  432. package/src/store/scopes/thread.ts +20 -0
  433. package/src/store/scopes/threads.ts +1 -0
  434. package/src/store/thread-tasks.test.tsx +209 -0
  435. package/src/subscribable/subscribable.test.ts +156 -1
  436. package/src/subscribable/subscribable.ts +25 -15
  437. package/src/tests/MessageRepository.test.ts +117 -5
  438. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  439. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  440. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +47 -0
  441. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  442. package/src/tests/attachment-adapters.test.ts +14 -0
  443. package/src/tests/auiV0Encode.test.ts +300 -0
  444. package/src/tests/base-composer-runtime-core-send.test.ts +51 -0
  445. package/src/tests/base-composer-runtime-core.test.ts +353 -2
  446. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  447. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +178 -18
  448. package/src/tests/external-thread-attachments.test.tsx +208 -0
  449. package/src/tests/external-thread-parity.test.tsx +57 -0
  450. package/src/tests/groupParts.test.ts +71 -0
  451. package/src/tests/message-queue.test.ts +253 -0
  452. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  453. package/src/tests/remote-thread-list-reactive-threadId.test.ts +51 -0
  454. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  455. package/src/tests/thread-switch-events.test.tsx +78 -0
  456. package/src/tests/tools-scope-migration.test.tsx +28 -0
  457. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +43 -0
  458. package/src/types/attachment.ts +4 -0
  459. package/src/types/index.ts +1 -0
  460. package/src/types/message.ts +8 -0
  461. package/src/utils/abortable-iterable.test.ts +328 -0
  462. package/src/utils/abortable-iterable.ts +104 -0
  463. package/src/utils/composite-context-provider.test.ts +25 -0
  464. package/src/utils/composite-context-provider.ts +23 -2
  465. package/src/utils/getGroupStatus.test.ts +70 -4
  466. package/src/utils/getGroupStatus.ts +35 -12
  467. package/src/utils/record.test.ts +48 -0
  468. package/src/utils/record.ts +11 -0
  469. package/src/utils/wire-media.test.ts +10 -0
  470. package/src/utils/wire-media.ts +6 -2
@@ -21,7 +21,10 @@ import type {
21
21
  CreateAttachment,
22
22
  PendingAttachment,
23
23
  } from "../../types/attachment";
24
- import { isCreateAttachment } from "../../types/attachment";
24
+ import {
25
+ isAttachmentComplete,
26
+ isCreateAttachment,
27
+ } from "../../types/attachment";
25
28
  import type {
26
29
  AddToolResultOptions,
27
30
  RespondToToolApprovalOptions,
@@ -52,6 +55,7 @@ import { toMessagePartStatus } from "../../utils/normalizePartStatus";
52
55
  import { generateId } from "../../utils/id";
53
56
  import { ModelContext } from "./model-context-client";
54
57
  import { ThreadSuggestions } from "./suggestions";
58
+ import { createTaskDeriver, getTaskKey, TaskClient } from "./thread-tasks";
55
59
  import { Tools } from "../../react/client/Tools";
56
60
  import { DataRenderers } from "../../react/client/DataRenderers";
57
61
  import { SingleThreadList } from "./single-thread-list";
@@ -463,6 +467,35 @@ const useLiveState = <T>(initial: T) => {
463
467
  return [state, set, ref] as const;
464
468
  };
465
469
 
470
+ // A try/catch inside the resource makes the React Compiler bail out of the
471
+ // whole hook, so the adapter call and its failure status live at module scope.
472
+ const removeAttachmentThroughAdapter = async (
473
+ attachment: Attachment,
474
+ attachmentAdapter: AttachmentAdapter | undefined,
475
+ setAttachments: (
476
+ next:
477
+ | readonly Attachment[]
478
+ | ((prev: readonly Attachment[]) => readonly Attachment[]),
479
+ ) => void,
480
+ ) => {
481
+ try {
482
+ await attachmentAdapter?.remove(attachment);
483
+ } catch (error) {
484
+ const message = error instanceof Error ? error.message : String(error);
485
+ setAttachments((prev) =>
486
+ prev.map((candidate) =>
487
+ candidate.id === attachment.id && !isAttachmentComplete(candidate)
488
+ ? {
489
+ ...candidate,
490
+ status: { type: "incomplete", reason: "error", message },
491
+ }
492
+ : candidate,
493
+ ),
494
+ );
495
+ throw error;
496
+ }
497
+ };
498
+
466
499
  // Composer Client - minimal implementation
467
500
  const useComposerClientResource = ({
468
501
  type,
@@ -516,7 +549,13 @@ const useComposerClientResource = ({
516
549
  attachment,
517
550
  onRemove: async () => {
518
551
  attachmentAddOperations.cancel(attachment.id);
519
- await attachmentAdapter?.remove(attachment);
552
+ if (!isAttachmentComplete(attachment)) {
553
+ await removeAttachmentThroughAdapter(
554
+ attachment,
555
+ attachmentAdapter,
556
+ setAttachments,
557
+ );
558
+ }
520
559
  setAttachments((prev) =>
521
560
  prev.filter((a) => a.id !== attachment.id),
522
561
  );
@@ -531,7 +570,7 @@ const useComposerClientResource = ({
531
570
  await Promise.all(
532
571
  removed
533
572
  .filter((a) => a.status.type !== "complete")
534
- .map((a) => attachmentAdapter.remove(a)),
573
+ .map(async (a) => attachmentAdapter.remove(a)),
535
574
  );
536
575
  };
537
576
 
@@ -905,7 +944,11 @@ const useExternalThread = ({
905
944
  : undefined;
906
945
  };
907
946
 
947
+ // `messages` changes identity on every streamed token, and this body fans out
948
+ // to one client per message, so pruning during render would re-run the whole
949
+ // fan-out per token. The effect bails out on an unchanged map instead.
908
950
  useEffect(() => {
951
+ // eslint-disable-next-line react-hooks/set-state-in-effect
909
952
  setSubmittedFeedback((prev) => {
910
953
  const live = Object.entries(prev).filter(([id, entry]) => {
911
954
  const msg = messages.find((m) => m.id === id);
@@ -983,6 +1026,14 @@ const useExternalThread = ({
983
1026
  }),
984
1027
  );
985
1028
 
1029
+ const taskDeriver = useMemo(() => createTaskDeriver(), []);
1030
+ const tasks = useMemo(() => taskDeriver(messages), [taskDeriver, messages]);
1031
+ const taskClients = useClientLookup(
1032
+ tasks.map((task) =>
1033
+ withKey(getTaskKey(task), TaskClient({ task }), [task]),
1034
+ ),
1035
+ );
1036
+
986
1037
  const handleCancelRun = () => {
987
1038
  // Nothing is aborted without a handler, so pausing the queue would hold
988
1039
  // the pending items against a run that keeps going.
@@ -1067,6 +1118,7 @@ const useExternalThread = ({
1067
1118
  queue: hasQueue,
1068
1119
  },
1069
1120
  messages: messageStates,
1121
+ tasks,
1070
1122
  state: threadState ?? {},
1071
1123
  suggestions: EMPTY_SUGGESTIONS,
1072
1124
  extras,
@@ -1092,12 +1144,20 @@ const useExternalThread = ({
1092
1144
  speech,
1093
1145
  messageClients.state,
1094
1146
  composerClient.state,
1147
+ tasks,
1095
1148
  ]);
1096
1149
 
1097
1150
  return {
1098
1151
  getState: () => state,
1099
1152
  composer: () => composerClient.methods,
1100
1153
  suggestions: () => suggestionsClient.methods,
1154
+ task: (selector) => {
1155
+ if ("id" in selector) {
1156
+ const task = tasks.find((candidate) => candidate.id === selector.id);
1157
+ return taskClients.get({ key: task ? getTaskKey(task) : selector.id });
1158
+ }
1159
+ return taskClients.get(selector);
1160
+ },
1101
1161
  append: (message) => {
1102
1162
  const appendMessage: AppendMessage =
1103
1163
  typeof message === "string"
@@ -1113,7 +1173,10 @@ const useExternalThread = ({
1113
1173
  }
1114
1174
  : {
1115
1175
  createdAt: message.createdAt ?? new Date(),
1116
- parentId: message.parentId ?? messages.at(-1)?.id ?? null,
1176
+ parentId:
1177
+ message.parentId === undefined
1178
+ ? (messages.at(-1)?.id ?? null)
1179
+ : message.parentId,
1117
1180
  sourceId: message.sourceId ?? null,
1118
1181
  role: message.role ?? "user",
1119
1182
  content: message.content,
@@ -3,6 +3,7 @@ import { resource } from "@assistant-ui/tap";
3
3
  import type { ClientOutput } from "@assistant-ui/store";
4
4
  import { CompositeContextProvider } from "../../utils/composite-context-provider";
5
5
  import type { ModelContextState } from "../scopes/model-context";
6
+ import { shallowEqual } from "@assistant-ui/store/internal";
6
7
 
7
8
  const EMPTY_TOOL_NAMES: readonly string[] = [];
8
9
 
@@ -12,7 +13,7 @@ const INITIAL_STATE: ModelContextState = {
12
13
  };
13
14
 
14
15
  const toolNamesEqual = (a: readonly string[], b: readonly string[]): boolean =>
15
- a === b || (a.length === b.length && a.every((v, i) => v === b[i]));
16
+ a === b || shallowEqual(a, b);
16
17
 
17
18
  const deriveState = (
18
19
  composite: CompositeContextProvider,
@@ -36,6 +37,9 @@ const useModelContext = (): ClientOutput<"modelContext"> => {
36
37
  );
37
38
 
38
39
  useEffect(() => {
40
+ // The composite is an external store; this catches the snapshot up for
41
+ // providers registered between render and subscribe.
42
+ // eslint-disable-next-line react-hooks/set-state-in-effect
39
43
  setState((prev) => deriveState(composite, prev));
40
44
  return composite.subscribe(() => {
41
45
  setState((prev) => deriveState(composite, prev));
@@ -59,6 +59,7 @@ const useSingleThreadList = ({
59
59
  mainThreadId: THREAD_ID,
60
60
  newThreadId: null,
61
61
  isLoading: false,
62
+ loadError: undefined,
62
63
  isLoadingMore: false,
63
64
  hasMore: false,
64
65
  threadIds: [THREAD_ID],
@@ -151,6 +151,46 @@ describe("ThreadSuggestions", () => {
151
151
  }
152
152
  });
153
153
 
154
+ it("drops sparse holes before the per-suggestion lookup", () => {
155
+ let setSuggestions!: (suggestions: ThreadSuggestion[]) => void;
156
+ const root = createTapRoot(function ThreadSuggestionsRoot() {
157
+ const [suggestions, setValue] = useState<ThreadSuggestion[]>([
158
+ { prompt: "Prompt A" },
159
+ { prompt: "Prompt B" },
160
+ ]);
161
+ setSuggestions = setValue;
162
+ return useResource(ThreadSuggestions(suggestions));
163
+ });
164
+
165
+ try {
166
+ const sparse: ThreadSuggestion[] = new Array(2);
167
+ sparse[1] = { prompt: "Prompt B" };
168
+ flushTapSync(() => setSuggestions(sparse));
169
+
170
+ const state = root.getValue().getState();
171
+ const expected = { title: "Prompt B", label: "", prompt: "Prompt B" };
172
+ expect(state.suggestions).toEqual([expected]);
173
+ expect(Object.keys(state.suggestions)).toEqual(["0"]);
174
+ const firstSuggestion = root
175
+ .getValue()
176
+ .suggestion({ index: 0 })
177
+ .getState();
178
+ expect(firstSuggestion).toEqual(expected);
179
+
180
+ // An equivalent sparse update keeps both identities stable.
181
+ const sparseAgain: ThreadSuggestion[] = new Array(2);
182
+ sparseAgain[1] = { prompt: "Prompt B" };
183
+ flushTapSync(() => setSuggestions(sparseAgain));
184
+
185
+ expect(root.getValue().getState()).toBe(state);
186
+ expect(root.getValue().suggestion({ index: 0 }).getState()).toBe(
187
+ firstSuggestion,
188
+ );
189
+ } finally {
190
+ root.unmount();
191
+ }
192
+ });
193
+
154
194
  it("reuses unchanged suggestion state when one entry changes", () => {
155
195
  let setSuggestions!: (suggestions: ThreadSuggestion[]) => void;
156
196
  const root = createTapRoot(function ThreadSuggestionsRoot() {
@@ -17,18 +17,21 @@ const useStableSuggestionsState = (
17
17
  const cell = useMemo(() => ({}) as { state?: SuggestionsState }, []);
18
18
  const previous = cell.state;
19
19
 
20
- const suggestions = next.suggestions.map((suggestion, index) => {
21
- const previousSuggestion = previous?.suggestions[index];
22
- return previousSuggestion && shallowEqual(previousSuggestion, suggestion)
23
- ? previousSuggestion
24
- : suggestion;
20
+ // forEach skips array holes, so a sparse caller array normalizes to a dense
21
+ // list here, before the per-suggestion resource lookup indexes every slot.
22
+ const suggestions: SuggestionState[] = [];
23
+ next.suggestions.forEach((suggestion) => {
24
+ // Probed at the compacted destination index, not the sparse source index,
25
+ // so an equivalent hole-containing update keeps reusing prior identities.
26
+ const previousSuggestion = previous?.suggestions[suggestions.length];
27
+ suggestions.push(
28
+ previousSuggestion && shallowEqual(previousSuggestion, suggestion)
29
+ ? previousSuggestion
30
+ : suggestion,
31
+ );
25
32
  });
26
33
  const state =
27
- previous &&
28
- previous.suggestions.length === suggestions.length &&
29
- suggestions.every(
30
- (suggestion, index) => suggestion === previous.suggestions[index],
31
- )
34
+ previous && shallowEqual(suggestions, previous.suggestions)
32
35
  ? previous
33
36
  : { suggestions };
34
37
 
@@ -0,0 +1,274 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { ThreadMessage, ToolCallMessagePart } from "../../types/message";
3
+ import { createTaskDeriver, getTaskKey } from "./thread-tasks";
4
+
5
+ const assistantMessage = (
6
+ id: string,
7
+ status:
8
+ | { type: "running" }
9
+ | { type: "complete"; reason: "stop" }
10
+ | { type: "incomplete"; reason: "error"; error: unknown },
11
+ content:
12
+ | readonly ToolCallMessagePart[]
13
+ | readonly { type: "text"; text: string }[],
14
+ ) =>
15
+ ({
16
+ id,
17
+ role: "assistant",
18
+ createdAt: new Date(0),
19
+ content,
20
+ status,
21
+ metadata: {
22
+ unstable_state: {},
23
+ unstable_annotations: [],
24
+ unstable_data: [],
25
+ steps: [],
26
+ custom: {},
27
+ },
28
+ }) as unknown as ThreadMessage;
29
+
30
+ const toolCall = (
31
+ id: string,
32
+ messages?: readonly ThreadMessage[],
33
+ result?: unknown,
34
+ ) =>
35
+ ({
36
+ type: "tool-call",
37
+ toolCallId: id,
38
+ toolName: "delegate",
39
+ args: { id },
40
+ argsText: JSON.stringify({ id }),
41
+ ...(messages !== undefined ? { messages } : undefined),
42
+ ...(result !== undefined ? { result } : undefined),
43
+ }) as ToolCallMessagePart;
44
+
45
+ describe("createTaskDeriver", () => {
46
+ it("derives top-level and nested tasks in document order", () => {
47
+ const nestedTask = toolCall("nested-1", []);
48
+ const nestedMessage = assistantMessage(
49
+ "nested-message",
50
+ { type: "running" },
51
+ [nestedTask],
52
+ );
53
+ const topLevelTask = toolCall("top-level-1", [nestedMessage]);
54
+ const tasks = createTaskDeriver()([
55
+ assistantMessage("outer-message", { type: "running" }, [topLevelTask]),
56
+ ]);
57
+
58
+ expect(tasks.map((task) => task.id)).toEqual(["top-level-1", "nested-1"]);
59
+ expect(tasks[0]).toMatchObject({
60
+ depth: 0,
61
+ parentTaskId: null,
62
+ messageId: "outer-message",
63
+ });
64
+ expect(tasks[1]).toMatchObject({
65
+ depth: 1,
66
+ parentTaskId: "top-level-1",
67
+ messageId: "nested-message",
68
+ });
69
+ });
70
+
71
+ it("uses normalized tool call statuses", () => {
72
+ const tasks = createTaskDeriver()([
73
+ assistantMessage("outer-message", { type: "running" }, [
74
+ toolCall("pending", []),
75
+ toolCall("complete", [], "done"),
76
+ ]),
77
+ ]);
78
+
79
+ expect(tasks.map((task) => task.status)).toEqual([
80
+ { type: "running" },
81
+ { type: "complete" },
82
+ ]);
83
+ });
84
+
85
+ it("excludes tool calls without nested messages", () => {
86
+ const tasks = createTaskDeriver()([
87
+ assistantMessage("outer-message", { type: "running" }, [
88
+ toolCall("not-a-task"),
89
+ toolCall("task", []),
90
+ ]),
91
+ ]);
92
+
93
+ expect(tasks.map((task) => task.id)).toEqual(["task"]);
94
+ });
95
+
96
+ it("preserves the array identity when an unrelated sibling changes", () => {
97
+ const task = toolCall("delegate-1", []);
98
+ const derive = createTaskDeriver();
99
+ const initial = derive([
100
+ assistantMessage("outer-message", { type: "running" }, [
101
+ { type: "text", text: "one" },
102
+ task,
103
+ ] as unknown as readonly ToolCallMessagePart[]),
104
+ ]);
105
+ const updated = derive([
106
+ assistantMessage("outer-message", { type: "running" }, [
107
+ { type: "text", text: "two" },
108
+ task,
109
+ ] as unknown as readonly ToolCallMessagePart[]),
110
+ ]);
111
+
112
+ expect(updated).toBe(initial);
113
+ });
114
+
115
+ it("creates a fresh entry and array when a nested status changes", () => {
116
+ const nestedTask = toolCall("nested-1", []);
117
+ const derive = createTaskDeriver();
118
+ const initial = derive([
119
+ assistantMessage("outer-message", { type: "running" }, [
120
+ toolCall("top-level-1", [
121
+ assistantMessage("nested-message", { type: "running" }, [nestedTask]),
122
+ ]),
123
+ ]),
124
+ ]);
125
+ const updated = derive([
126
+ assistantMessage("outer-message", { type: "running" }, [
127
+ toolCall("top-level-1", [
128
+ assistantMessage(
129
+ "nested-message",
130
+ { type: "complete", reason: "stop" },
131
+ [nestedTask],
132
+ ),
133
+ ]),
134
+ ]),
135
+ ]);
136
+
137
+ expect(updated).not.toBe(initial);
138
+ expect(updated[1]).not.toBe(initial[1]);
139
+ expect(updated[1]?.status).toMatchObject({ type: "complete" });
140
+ });
141
+
142
+ it("creates a fresh entry when an incomplete status changes its error", () => {
143
+ const task = toolCall("failing", []);
144
+ const derive = createTaskDeriver();
145
+ const initial = derive([
146
+ assistantMessage(
147
+ "outer-message",
148
+ { type: "incomplete", reason: "error", error: "first" },
149
+ [task],
150
+ ),
151
+ ]);
152
+ const updated = derive([
153
+ assistantMessage(
154
+ "outer-message",
155
+ { type: "incomplete", reason: "error", error: "second" },
156
+ [task],
157
+ ),
158
+ ]);
159
+
160
+ expect(updated).not.toBe(initial);
161
+ expect(updated[0]).not.toBe(initial[0]);
162
+ expect(updated[0]?.status).toMatchObject({
163
+ type: "incomplete",
164
+ reason: "error",
165
+ error: "second",
166
+ });
167
+ });
168
+
169
+ it("rebuilds an entry when an unchanged part moves to another message", () => {
170
+ const part = toolCall("moving", []);
171
+ const derive = createTaskDeriver();
172
+ const initial = derive([
173
+ assistantMessage("first-message", { type: "running" }, [part]),
174
+ ]);
175
+ const moved = derive([
176
+ assistantMessage("second-message", { type: "running" }, [part]),
177
+ ]);
178
+
179
+ expect(moved[0]).not.toBe(initial[0]);
180
+ expect(moved[0]?.messageId).toBe("second-message");
181
+ });
182
+
183
+ it("stops descending past the depth cap", () => {
184
+ let content: readonly ToolCallMessagePart[] = [toolCall("leaf", [])];
185
+ for (let level = 40; level > 0; level -= 1) {
186
+ content = [
187
+ toolCall(`level-${level}`, [
188
+ assistantMessage(`message-${level}`, { type: "running" }, content),
189
+ ]),
190
+ ];
191
+ }
192
+ const tasks = createTaskDeriver()([
193
+ assistantMessage("root", { type: "running" }, content),
194
+ ]);
195
+
196
+ expect(Math.max(...tasks.map((task) => task.depth))).toBe(32);
197
+ });
198
+
199
+ it("keeps the array identity when nested conversations repeat a toolCallId", () => {
200
+ const nested = (messageId: string) =>
201
+ assistantMessage(messageId, { type: "complete", reason: "stop" }, [
202
+ toolCall("call_1", [], "ok"),
203
+ ]);
204
+ const messages = [
205
+ assistantMessage("outer-message", { type: "complete", reason: "stop" }, [
206
+ toolCall("delegate-a", [nested("nested-a")], "ok"),
207
+ toolCall("delegate-b", [nested("nested-b")], "ok"),
208
+ ]),
209
+ ];
210
+ const derive = createTaskDeriver();
211
+ const first = derive(messages);
212
+ const second = derive(messages);
213
+
214
+ expect(first.map((task) => task.id)).toEqual([
215
+ "delegate-a",
216
+ "call_1",
217
+ "delegate-b",
218
+ "call_1",
219
+ ]);
220
+ expect(second).toBe(first);
221
+ });
222
+
223
+ it("keys tasks by document order when nested payloads repeat every id", () => {
224
+ const replay = () =>
225
+ assistantMessage("nested", { type: "complete", reason: "stop" }, [
226
+ toolCall("call_1", [], "ok"),
227
+ ]);
228
+ const messages = [
229
+ assistantMessage("outer-message", { type: "complete", reason: "stop" }, [
230
+ toolCall("delegate-a", [replay()], "ok"),
231
+ toolCall("delegate-b", [replay()], "ok"),
232
+ ]),
233
+ ];
234
+ const derive = createTaskDeriver();
235
+ const first = derive(messages);
236
+
237
+ expect(new Set(first.map(getTaskKey)).size).toBe(4);
238
+ expect(derive(messages)).toBe(first);
239
+ });
240
+
241
+ it("reads a nested message without a status as complete", () => {
242
+ const unnormalized = {
243
+ ...assistantMessage("nested", { type: "running" }, [
244
+ toolCall("call_1", []),
245
+ ]),
246
+ status: undefined,
247
+ } as unknown as ThreadMessage;
248
+ const [, nestedTask] = createTaskDeriver()([
249
+ assistantMessage("outer-message", { type: "running" }, [
250
+ toolCall("delegate", [unnormalized], "ok"),
251
+ ]),
252
+ ]);
253
+
254
+ expect(nestedTask?.status).toEqual({ type: "complete" });
255
+ });
256
+
257
+ it("preserves unchanged entries when a task is appended", () => {
258
+ const first = toolCall("first", []);
259
+ const derive = createTaskDeriver();
260
+ const initial = derive([
261
+ assistantMessage("outer-message", { type: "running" }, [first]),
262
+ ]);
263
+ const updated = derive([
264
+ assistantMessage("outer-message", { type: "running" }, [
265
+ first,
266
+ toolCall("second", []),
267
+ ]),
268
+ ]);
269
+
270
+ expect(updated).not.toBe(initial);
271
+ expect(updated[0]).toBe(initial[0]);
272
+ expect(updated.map((task) => task.id)).toEqual(["first", "second"]);
273
+ });
274
+ });
@@ -0,0 +1,138 @@
1
+ import { resource } from "@assistant-ui/tap";
2
+ import type { ClientOutput } from "@assistant-ui/store";
3
+ import type {
4
+ ThreadMessage,
5
+ ToolCallMessagePart,
6
+ ToolCallMessagePartStatus,
7
+ } from "../../types/message";
8
+ import {
9
+ COMPLETE_STATUS,
10
+ toMessagePartStatus,
11
+ } from "../../utils/normalizePartStatus";
12
+ import type { TaskState } from "../scopes/task";
13
+
14
+ type TaskEntry = {
15
+ readonly task: TaskState;
16
+ readonly part: ToolCallMessagePart;
17
+ readonly statusType: ToolCallMessagePartStatus["type"];
18
+ readonly statusReason: string | undefined;
19
+ readonly statusError: unknown;
20
+ readonly messages: readonly ThreadMessage[];
21
+ };
22
+
23
+ const getStatusReason = (status: ToolCallMessagePartStatus) =>
24
+ "reason" in status ? status.reason : undefined;
25
+
26
+ const getStatusError = (status: ToolCallMessagePartStatus) =>
27
+ "error" in status ? status.error : undefined;
28
+
29
+ const MAX_TASK_DEPTH = 32;
30
+
31
+ const taskKeys = new WeakMap<TaskState, string>();
32
+
33
+ /** Lookup key for a task client: its document-order index path, unique by construction where ids from nested payloads are not. */
34
+ export const getTaskKey = (task: TaskState) => taskKeys.get(task) ?? task.id;
35
+
36
+ const resolveStatus = (
37
+ message: ThreadMessage,
38
+ partIndex: number,
39
+ part: ToolCallMessagePart,
40
+ ): ToolCallMessagePartStatus =>
41
+ "status" in message && message.status
42
+ ? toMessagePartStatus(message, partIndex, part)
43
+ : COMPLETE_STATUS;
44
+
45
+ export const createTaskDeriver = () => {
46
+ let previous: readonly TaskState[] = [];
47
+ let previousEntries = new Map<string, TaskEntry>();
48
+
49
+ return (messages: readonly ThreadMessage[]): readonly TaskState[] => {
50
+ const tasks: TaskState[] = [];
51
+ const entries = new Map<string, TaskEntry>();
52
+ let allEntriesReused = true;
53
+
54
+ const visit = (
55
+ threadMessages: readonly ThreadMessage[],
56
+ parentTaskId: string | null,
57
+ depth: number,
58
+ path: string,
59
+ ) => {
60
+ if (depth > MAX_TASK_DEPTH) return;
61
+ for (const [messageIndex, message] of threadMessages.entries()) {
62
+ for (const [partIndex, part] of message.content.entries()) {
63
+ if (part.type !== "tool-call" || part.messages === undefined)
64
+ continue;
65
+
66
+ const nestedMessages = part.messages;
67
+ const status = resolveStatus(message, partIndex, part);
68
+ const statusReason = getStatusReason(status);
69
+ const statusError = getStatusError(status);
70
+ const entryKey = `${path}${messageIndex}.${partIndex}`;
71
+ const previousEntry = previousEntries.get(entryKey);
72
+ const task =
73
+ previousEntry?.part === part &&
74
+ previousEntry.statusType === status.type &&
75
+ previousEntry.statusReason === statusReason &&
76
+ Object.is(previousEntry.statusError, statusError) &&
77
+ previousEntry.messages === nestedMessages &&
78
+ previousEntry.task.messageId === message.id &&
79
+ previousEntry.task.parentTaskId === parentTaskId &&
80
+ previousEntry.task.depth === depth
81
+ ? previousEntry.task
82
+ : {
83
+ id: part.toolCallId,
84
+ toolName: part.toolName,
85
+ args: part.args,
86
+ result: part.result,
87
+ ...(part.isError === undefined
88
+ ? undefined
89
+ : { isError: part.isError }),
90
+ status,
91
+ timing: part.timing,
92
+ messageId: message.id,
93
+ parentTaskId,
94
+ depth,
95
+ messages: nestedMessages,
96
+ };
97
+
98
+ if (task !== previousEntry?.task) {
99
+ allEntriesReused = false;
100
+ taskKeys.set(task, entryKey);
101
+ }
102
+ tasks.push(task);
103
+ entries.set(entryKey, {
104
+ task,
105
+ part,
106
+ statusType: status.type,
107
+ statusReason,
108
+ statusError,
109
+ messages: nestedMessages,
110
+ });
111
+ visit(nestedMessages, task.id, depth + 1, `${entryKey}.`);
112
+ }
113
+ }
114
+ };
115
+
116
+ visit(messages, null, 0, "");
117
+
118
+ const result =
119
+ allEntriesReused &&
120
+ tasks.length === previous.length &&
121
+ tasks.every((task, index) => task === previous[index])
122
+ ? previous
123
+ : tasks;
124
+ previous = result;
125
+ previousEntries = entries;
126
+ return result;
127
+ };
128
+ };
129
+
130
+ const useTaskClient = ({
131
+ task,
132
+ }: {
133
+ task: TaskState;
134
+ }): ClientOutput<"task"> => ({
135
+ getState: () => task,
136
+ });
137
+
138
+ export const TaskClient = resource(useTaskClient);
@@ -48,6 +48,12 @@ export type {
48
48
  QueueItemMeta,
49
49
  QueueItemClientSchema,
50
50
  } from "./scopes/queue-item";
51
+ export type {
52
+ TaskState,
53
+ TaskMethods,
54
+ TaskMeta,
55
+ TaskClientSchema,
56
+ } from "./scopes/task";
51
57
  export type {
52
58
  AttachmentState,
53
59
  AttachmentMethods,
@@ -96,6 +102,8 @@ export {
96
102
  export {
97
103
  useExternalMessageConverter,
98
104
  convertExternalMessages,
105
+ createExternalMessageConversionCache,
106
+ type ExternalMessageConversionCache,
99
107
  type JoinStrategy,
100
108
  } from "../react/runtimes/external-message-converter";
101
109
  export {