@assistant-ui/core 0.3.14 → 0.3.16

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 (341) hide show
  1. package/dist/adapters/voice.js +11 -0
  2. package/dist/adapters/voice.js.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/internal.d.ts +8 -2
  5. package/dist/internal.js +9 -3
  6. package/dist/model-context/frame/host.d.ts +1 -1
  7. package/dist/model-context/frame/host.d.ts.map +1 -1
  8. package/dist/model-context/frame/host.js +20 -5
  9. package/dist/model-context/frame/host.js.map +1 -1
  10. package/dist/model-context/frame/provider.d.ts +6 -0
  11. package/dist/model-context/frame/provider.d.ts.map +1 -1
  12. package/dist/model-context/frame/provider.js +126 -16
  13. package/dist/model-context/frame/provider.js.map +1 -1
  14. package/dist/model-context/frame/types.d.ts +4 -1
  15. package/dist/model-context/frame/types.d.ts.map +1 -1
  16. package/dist/model-context/frame/types.js.map +1 -1
  17. package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
  18. package/dist/model-context/interactable-composer-metadata.js +1 -1
  19. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  20. package/dist/model-context/registry.d.ts +2 -2
  21. package/dist/model-context/registry.d.ts.map +1 -1
  22. package/dist/model-context/registry.js +9 -28
  23. package/dist/model-context/registry.js.map +1 -1
  24. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  25. package/dist/react/client/InMemoryThreadList.js +24 -20
  26. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  27. package/dist/react/client/Interactables.d.ts.map +1 -1
  28. package/dist/react/client/Interactables.js +59 -177
  29. package/dist/react/client/Interactables.js.map +1 -1
  30. package/dist/react/client/RemoteThreadList.d.ts +1 -1
  31. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  32. package/dist/react/client/RemoteThreadList.js +117 -78
  33. package/dist/react/client/RemoteThreadList.js.map +1 -1
  34. package/dist/react/index.d.ts +2 -1
  35. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  36. package/dist/react/interactables-legacy/Interactables.js +56 -174
  37. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  38. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
  39. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
  40. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
  41. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
  42. package/dist/react/primitive-hooks/useActionBarCopy.js +7 -1
  43. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  44. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts +3 -0
  45. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts.map +1 -1
  46. package/dist/react/primitive-hooks/useThreadIsEmpty.js +3 -0
  47. package/dist/react/primitive-hooks/useThreadIsEmpty.js.map +1 -1
  48. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts +3 -0
  49. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts.map +1 -1
  50. package/dist/react/primitive-hooks/useThreadIsRunning.js +3 -0
  51. package/dist/react/primitive-hooks/useThreadIsRunning.js.map +1 -1
  52. package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
  53. package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
  54. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  55. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  56. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +3 -1
  57. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  58. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +6 -0
  59. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  60. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +7 -1
  61. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  62. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +174 -64
  63. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  64. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  65. package/dist/react/runtimes/RemoteThreadResource.js +2 -0
  66. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  67. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
  68. package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
  69. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
  70. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  71. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +1 -1
  72. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  73. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
  74. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  75. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +20 -67
  76. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  77. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
  78. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
  79. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
  80. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
  81. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  82. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +38 -5
  83. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  84. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
  85. package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
  86. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  87. package/dist/react/runtimes/external-message-converter.d.ts +6 -25
  88. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  89. package/dist/react/runtimes/external-message-converter.js +33 -233
  90. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  91. package/dist/runtime/api/composer-runtime.d.ts +1 -1
  92. package/dist/runtime/api/composer-runtime.js.map +1 -1
  93. package/dist/runtime/api/thread-runtime.d.ts +1 -2
  94. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  95. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  96. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  97. package/dist/runtime/base/base-composer-runtime-core.js +14 -29
  98. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  99. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -4
  100. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  101. package/dist/runtime/base/base-thread-runtime-core.js +3 -9
  102. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  103. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  104. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  105. package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
  106. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  107. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
  108. package/dist/runtime/interfaces/thread-runtime-core.d.ts +1 -1
  109. package/dist/runtime/internal.d.ts +2 -1
  110. package/dist/runtime/internal.js +2 -1
  111. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
  112. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  113. package/dist/runtime/queue/message-queue.js +1 -1
  114. package/dist/runtime/queue/message-queue.js.map +1 -1
  115. package/dist/runtime/queue/queue-item.d.ts +12 -0
  116. package/dist/runtime/queue/queue-item.d.ts.map +1 -0
  117. package/dist/runtime/queue/queue-item.js +6 -0
  118. package/dist/runtime/queue/queue-item.js.map +1 -0
  119. package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
  120. package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
  121. package/dist/runtime/utils/abortable-thread-load.js +35 -0
  122. package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
  123. package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
  124. package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
  125. package/dist/runtime/utils/attachment-add-operations.js +43 -0
  126. package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
  127. package/dist/runtime/utils/auto-status.d.ts +6 -1
  128. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  129. package/dist/runtime/utils/auto-status.js +7 -1
  130. package/dist/runtime/utils/auto-status.js.map +1 -1
  131. package/dist/runtime/utils/external-message-conversion.d.ts +47 -0
  132. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
  133. package/dist/runtime/utils/external-message-conversion.js +221 -0
  134. package/dist/runtime/utils/external-message-conversion.js.map +1 -0
  135. package/dist/runtime/utils/external-store-message.d.ts +2 -1
  136. package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
  137. package/dist/runtime/utils/external-store-message.js +2 -1
  138. package/dist/runtime/utils/external-store-message.js.map +1 -1
  139. package/dist/runtime/utils/message-repository-session.d.ts +30 -0
  140. package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
  141. package/dist/runtime/utils/message-repository-session.js +130 -0
  142. package/dist/runtime/utils/message-repository-session.js.map +1 -0
  143. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  144. package/dist/runtime/utils/message-repository.js +30 -22
  145. package/dist/runtime/utils/message-repository.js.map +1 -1
  146. package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
  147. package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
  148. package/dist/runtime/utils/pending-tool-calls.js +22 -0
  149. package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
  150. package/dist/runtime/utils/thread-message-like.d.ts +2 -1
  151. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  152. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  153. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
  154. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  155. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +3 -10
  156. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  157. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +19 -1
  158. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  159. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +124 -20
  160. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  161. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +102 -0
  162. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -0
  163. package/dist/runtimes/inert/InertThreadRuntimeCore.js +156 -0
  164. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -0
  165. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -1
  166. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  167. package/dist/runtimes/local/local-thread-runtime-core.js +45 -8
  168. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  169. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +12 -60
  170. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  171. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +10 -145
  172. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  173. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts +9 -0
  174. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts.map +1 -0
  175. package/dist/runtimes/remote-thread-list/adapter-changed.js +13 -0
  176. package/dist/runtimes/remote-thread-list/adapter-changed.js.map +1 -0
  177. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  178. package/dist/runtimes/remote-thread-list/empty-thread-core.js +16 -155
  179. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  180. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +2 -0
  181. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  182. package/dist/runtimes/remote-thread-list/optimistic-state.js +11 -0
  183. package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
  184. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
  185. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  186. package/dist/runtimes/remote-thread-list/remote-thread-state.js +77 -1
  187. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  188. package/dist/runtimes/remote-thread-list/types.d.ts +3 -0
  189. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  190. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  191. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -0
  192. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  193. package/dist/store/clients/external-thread.d.ts.map +1 -1
  194. package/dist/store/clients/external-thread.js +75 -100
  195. package/dist/store/clients/external-thread.js.map +1 -1
  196. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  197. package/dist/store/clients/single-thread-list.js +1 -1
  198. package/dist/store/clients/single-thread-list.js.map +1 -1
  199. package/dist/store/clients/suggestions.d.ts.map +1 -1
  200. package/dist/store/clients/suggestions.js +16 -7
  201. package/dist/store/clients/suggestions.js.map +1 -1
  202. package/dist/store/index.d.ts +4 -2
  203. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -1
  204. package/dist/store/runtime-clients/handle-runtime-action.js +2 -0
  205. package/dist/store/runtime-clients/handle-runtime-action.js.map +1 -1
  206. package/dist/store/scope-registration.d.ts +1 -1
  207. package/dist/store/scopes/composer.d.ts +2 -1
  208. package/dist/store/scopes/composer.d.ts.map +1 -1
  209. package/dist/store/scopes/message.d.ts +1 -1
  210. package/dist/store/scopes/queue-item.d.ts +2 -9
  211. package/dist/store/scopes/queue-item.d.ts.map +1 -1
  212. package/dist/store/scopes/queue-item.js +0 -6
  213. package/dist/types/message.d.ts +8 -4
  214. package/dist/types/message.d.ts.map +1 -1
  215. package/dist/types/message.js.map +1 -1
  216. package/dist/utils/data-url.d.ts +14 -1
  217. package/dist/utils/data-url.d.ts.map +1 -1
  218. package/dist/utils/data-url.js +13 -1
  219. package/dist/utils/data-url.js.map +1 -1
  220. package/dist/utils/invoke-user-callback.d.ts +5 -0
  221. package/dist/utils/invoke-user-callback.d.ts.map +1 -0
  222. package/dist/utils/invoke-user-callback.js +19 -0
  223. package/dist/utils/invoke-user-callback.js.map +1 -0
  224. package/dist/utils/normalizePartStatus.js.map +1 -1
  225. package/dist/utils/silent-runtime-action.d.ts +6 -0
  226. package/dist/utils/silent-runtime-action.d.ts.map +1 -0
  227. package/dist/utils/silent-runtime-action.js +7 -0
  228. package/dist/utils/silent-runtime-action.js.map +1 -0
  229. package/package.json +10 -10
  230. package/src/adapters/voice.test.ts +66 -1
  231. package/src/adapters/voice.ts +8 -0
  232. package/src/internal.ts +13 -0
  233. package/src/model-context/frame/host.test.ts +152 -9
  234. package/src/model-context/frame/host.ts +23 -5
  235. package/src/model-context/frame/provider.test.ts +601 -19
  236. package/src/model-context/frame/provider.ts +158 -20
  237. package/src/model-context/frame/types.ts +5 -0
  238. package/src/model-context/interactable-composer-metadata.ts +1 -1
  239. package/src/model-context/registry.test.ts +44 -0
  240. package/src/model-context/registry.ts +13 -35
  241. package/src/react/client/InMemoryThreadList.test.tsx +49 -0
  242. package/src/react/client/InMemoryThreadList.ts +7 -1
  243. package/src/react/client/Interactables.ts +19 -178
  244. package/src/react/client/RemoteThreadList.test.ts +292 -0
  245. package/src/react/client/RemoteThreadList.ts +119 -82
  246. package/src/react/interactables-legacy/Interactables.ts +20 -177
  247. package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
  248. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
  249. package/src/react/primitive-hooks/useActionBarCopy.test.ts +15 -0
  250. package/src/react/primitive-hooks/useActionBarCopy.ts +13 -3
  251. package/src/react/primitive-hooks/useThreadIsEmpty.ts +3 -0
  252. package/src/react/primitive-hooks/useThreadIsRunning.ts +3 -0
  253. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +12 -0
  254. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.republish.test.ts +87 -0
  255. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +251 -76
  256. package/src/react/runtimes/RemoteThreadResource.ts +2 -0
  257. package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
  258. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +58 -0
  259. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +3 -1
  260. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +92 -0
  261. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +40 -141
  262. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
  263. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
  264. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +98 -3
  265. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +68 -6
  266. package/src/react/runtimes/external-message-converter.test.ts +0 -14
  267. package/src/react/runtimes/external-message-converter.test.tsx +77 -0
  268. package/src/react/runtimes/external-message-converter.ts +58 -481
  269. package/src/react/runtimes/useLocalRuntime.test.tsx +5 -5
  270. package/src/runtime/api/composer-runtime.ts +1 -1
  271. package/src/runtime/api/message-runtime.test.ts +28 -1
  272. package/src/runtime/base/base-composer-runtime-core.ts +20 -40
  273. package/src/runtime/base/base-thread-runtime-core.test.ts +16 -0
  274. package/src/runtime/base/base-thread-runtime-core.ts +6 -11
  275. package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
  276. package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
  277. package/src/runtime/interfaces/thread-runtime-core.ts +1 -1
  278. package/src/runtime/internal.ts +5 -0
  279. package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
  280. package/src/runtime/queue/message-queue.ts +1 -4
  281. package/src/runtime/queue/queue-item.ts +10 -0
  282. package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
  283. package/src/runtime/utils/abortable-thread-load.ts +53 -0
  284. package/src/runtime/utils/attachment-add-operations.ts +59 -0
  285. package/src/runtime/utils/auto-status.ts +33 -0
  286. package/src/runtime/utils/external-message-conversion.test.ts +113 -0
  287. package/src/runtime/utils/external-message-conversion.ts +494 -0
  288. package/src/runtime/utils/external-store-message.ts +2 -0
  289. package/src/runtime/utils/message-repository-session.test.ts +195 -0
  290. package/src/runtime/utils/message-repository-session.ts +176 -0
  291. package/src/runtime/utils/message-repository.ts +33 -22
  292. package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
  293. package/src/runtime/utils/pending-tool-calls.ts +35 -0
  294. package/src/runtime/utils/thread-message-like.ts +2 -0
  295. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
  296. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +179 -28
  297. package/src/runtimes/inert/InertThreadRuntimeCore.test.ts +253 -0
  298. package/src/runtimes/inert/InertThreadRuntimeCore.ts +232 -0
  299. package/src/runtimes/local/local-thread-runtime-core.test.ts +491 -1
  300. package/src/runtimes/local/local-thread-runtime-core.ts +86 -11
  301. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +17 -209
  302. package/src/runtimes/remote-thread-list/adapter-changed.ts +10 -0
  303. package/src/runtimes/remote-thread-list/empty-thread-core.ts +28 -221
  304. package/src/runtimes/remote-thread-list/optimistic-state.test.ts +18 -0
  305. package/src/runtimes/remote-thread-list/optimistic-state.ts +13 -0
  306. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +37 -0
  307. package/src/runtimes/remote-thread-list/remote-thread-state.ts +123 -0
  308. package/src/runtimes/remote-thread-list/types.ts +4 -0
  309. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -0
  310. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +140 -0
  311. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +3 -0
  312. package/src/store/clients/external-thread.ts +17 -62
  313. package/src/store/clients/single-thread-list.ts +4 -1
  314. package/src/store/clients/suggestions.test.ts +31 -0
  315. package/src/store/clients/suggestions.ts +18 -19
  316. package/src/store/runtime-clients/handle-runtime-action.test.ts +40 -0
  317. package/src/store/runtime-clients/handle-runtime-action.ts +3 -0
  318. package/src/store/scopes/message.ts +1 -1
  319. package/src/store/scopes/queue-item.ts +2 -9
  320. package/src/tests/MessageRepository.test.ts +125 -0
  321. package/src/tests/RemoteThreadListThreadListRuntimeCore-errors.test.ts +2 -2
  322. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +194 -0
  323. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +8 -6
  324. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
  325. package/src/tests/empty-thread-core.test.ts +6 -0
  326. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +184 -0
  327. package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
  328. package/src/tests/external-thread-attachments.test.tsx +173 -0
  329. package/src/tests/external-thread-isEmpty.test.tsx +100 -0
  330. package/src/tests/remote-thread-list-adapter-switch.test.ts +383 -0
  331. package/src/tests/single-thread-list-item.test.tsx +47 -0
  332. package/src/tests/thread-message-like.test.ts +39 -1
  333. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +67 -0
  334. package/src/types/message.ts +9 -4
  335. package/src/utils/data-url.test.ts +52 -1
  336. package/src/utils/data-url.ts +21 -0
  337. package/src/utils/invoke-user-callback.test.ts +82 -0
  338. package/src/utils/invoke-user-callback.ts +31 -0
  339. package/src/utils/normalizePartStatus.ts +1 -1
  340. package/src/utils/silent-runtime-action.ts +6 -0
  341. package/dist/store/scopes/queue-item.js.map +0 -1
@@ -4,6 +4,7 @@ import { act, render, waitFor } from "@testing-library/react";
4
4
  import type { FC } from "react";
5
5
  import { afterEach, describe, expect, it, vi } from "vitest";
6
6
  import { useAui } from "@assistant-ui/store";
7
+ import { ToolResponse } from "assistant-stream";
7
8
  import { AssistantRuntimeProvider } from "../../AssistantRuntimeProvider";
8
9
  import {
9
10
  useAssistantTransportRuntime,
@@ -145,6 +146,63 @@ afterEach(() => {
145
146
  });
146
147
 
147
148
  describe("useAssistantTransportRuntime", () => {
149
+ it.each([false, 0, "", null])(
150
+ "preserves the falsy tool artifact %j",
151
+ async (artifact) => {
152
+ const fetchMock = installFetch();
153
+ const { aui } = mountRuntime({
154
+ converter: (_state, meta) => ({
155
+ messages: [
156
+ {
157
+ id: "a1",
158
+ role: "assistant",
159
+ content: [
160
+ {
161
+ type: "tool-call",
162
+ toolCallId: "tc1",
163
+ toolName: "probe",
164
+ args: {},
165
+ argsText: "{}",
166
+ },
167
+ ],
168
+ createdAt: new Date(0),
169
+ status: { type: "requires-action", reason: "tool-calls" },
170
+ metadata: { custom: {} },
171
+ },
172
+ ],
173
+ isRunning: meta.isSending,
174
+ }),
175
+ });
176
+
177
+ await waitFor(() =>
178
+ expect(
179
+ aui()
180
+ .thread.message({ id: "a1" })
181
+ .part({ toolCallId: "tc1" })
182
+ .getState().type,
183
+ ).toBe("tool-call"),
184
+ );
185
+
186
+ act(() => {
187
+ aui()
188
+ .thread.message({ id: "a1" })
189
+ .part({ toolCallId: "tc1" })
190
+ .addToolResult(new ToolResponse({ result: "ok", artifact }));
191
+ });
192
+
193
+ await waitFor(() => expect(fetchMock.requests).toHaveLength(1));
194
+ expect(fetchMock.requests[0]!.body["commands"][0]).toHaveProperty(
195
+ "artifact",
196
+ artifact,
197
+ );
198
+
199
+ act(() => fetchMock.servers[0]!.close());
200
+ await waitFor(() =>
201
+ expect(aui().thread.getState().isRunning).toBe(false),
202
+ );
203
+ },
204
+ );
205
+
148
206
  it.each(["throws", "rejects"] as const)(
149
207
  "cancels the response body when onResponse %s",
150
208
  async (failureMode) => {
@@ -447,7 +447,9 @@ const useAssistantTransportThreadRuntime = <T>(
447
447
  result: toolOptions.result as ReadonlyJSONObject,
448
448
  toolName: toolOptions.toolName,
449
449
  isError: toolOptions.isError,
450
- ...(toolOptions.artifact && { artifact: toolOptions.artifact }),
450
+ ...(toolOptions.artifact !== undefined && {
451
+ artifact: toolOptions.artifact,
452
+ }),
451
453
  ...(toolOptions.modelContent !== undefined && {
452
454
  modelContent: toolOptions.modelContent,
453
455
  }),
@@ -188,6 +188,98 @@ describe("useAssistantCloudThreadHistoryAdapter", () => {
188
188
  );
189
189
  });
190
190
 
191
+ it("reports frontend and MCP sources for ai-sdk/v6 tool calls", () => {
192
+ mocks.aui = mocks.makeClient("thread-1");
193
+ const cloud = makeCloud();
194
+ const cloudRef = { current: cloud };
195
+ const { result } = renderHook(() =>
196
+ useAssistantCloudThreadHistoryAdapter(cloudRef),
197
+ );
198
+ const formatted = result.current.withFormat({
199
+ format: "ai-sdk/v6",
200
+ encode: ({ message }) => message,
201
+ decode: ({ parent_id, content }) => ({
202
+ parentId: parent_id,
203
+ message: content as { id: string },
204
+ }),
205
+ getId: (message: { id: string }) => message.id,
206
+ });
207
+
208
+ formatted.reportTelemetry([
209
+ {
210
+ parentId: null,
211
+ message: {
212
+ id: "message-1",
213
+ role: "assistant",
214
+ parts: [
215
+ { type: "step-start" },
216
+ {
217
+ type: "tool-search",
218
+ toolCallId: "static-1",
219
+ input: { query: "test" },
220
+ output: { result: "ok" },
221
+ },
222
+ {
223
+ type: "dynamic-tool",
224
+ toolName: "mcp-search",
225
+ toolCallId: "dynamic-1",
226
+ input: { query: "test" },
227
+ output: { result: "ok" },
228
+ },
229
+ { type: "step-start" },
230
+ {
231
+ type: "tool-search",
232
+ toolCallId: "static-2",
233
+ input: { query: "again" },
234
+ output: { result: "ok" },
235
+ },
236
+ ],
237
+ },
238
+ },
239
+ ]);
240
+
241
+ expect(cloud.runs.report).toHaveBeenCalledWith(
242
+ expect.objectContaining({
243
+ tool_calls: [
244
+ expect.objectContaining({
245
+ tool_call_id: "static-1",
246
+ tool_source: "frontend",
247
+ }),
248
+ expect.objectContaining({
249
+ tool_call_id: "dynamic-1",
250
+ tool_source: "mcp",
251
+ }),
252
+ expect.objectContaining({
253
+ tool_call_id: "static-2",
254
+ tool_source: "frontend",
255
+ }),
256
+ ],
257
+ steps: [
258
+ {
259
+ tool_calls: [
260
+ expect.objectContaining({
261
+ tool_call_id: "static-1",
262
+ tool_source: "frontend",
263
+ }),
264
+ expect.objectContaining({
265
+ tool_call_id: "dynamic-1",
266
+ tool_source: "mcp",
267
+ }),
268
+ ],
269
+ },
270
+ {
271
+ tool_calls: [
272
+ expect.objectContaining({
273
+ tool_call_id: "static-2",
274
+ tool_source: "frontend",
275
+ }),
276
+ ],
277
+ },
278
+ ],
279
+ }),
280
+ );
281
+ });
282
+
191
283
  it("initializes the pinned item instead of the new main thread", async () => {
192
284
  mocks.aui = mocks.makeClient(undefined, "new-thread", "thread-1");
193
285
  const cloud = makeCloud();
@@ -9,8 +9,13 @@ import type {
9
9
  import type { ExportedMessageRepositoryItem } from "../../../runtime/utils/message-repository";
10
10
  import {
11
11
  type AssistantCloud,
12
+ type AssistantCloudRunReportToolCall,
12
13
  CloudMessagePersistence,
13
14
  createFormattedPersistence,
15
+ createRunTelemetryToolCall,
16
+ normalizeRunTelemetryUsage,
17
+ type RunTelemetryUsageInit,
18
+ truncateRunTelemetryText,
14
19
  } from "assistant-cloud";
15
20
  import { auiV0Decode, auiV0Encode } from "./auiV0";
16
21
  import { type AssistantClient, getClientId, useAui } from "@assistant-ui/store";
@@ -268,85 +273,12 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
268
273
  }
269
274
  }
270
275
 
271
- const MAX_SPAN_CONTENT = 50_000;
272
-
273
- function truncateStr(value: string): string {
274
- if (value.length <= MAX_SPAN_CONTENT) return value;
275
- return value.slice(0, MAX_SPAN_CONTENT);
276
- }
277
-
278
- function safeStringify(value: unknown): string | undefined {
279
- if (value == null) return undefined;
280
- try {
281
- return truncateStr(JSON.stringify(value));
282
- } catch {
283
- return undefined;
284
- }
285
- }
286
-
287
- type TelemetryToolCall = {
288
- tool_name: string;
289
- tool_call_id: string;
290
- tool_args?: string;
291
- tool_result?: string;
292
- tool_source?: "mcp" | "frontend" | "backend";
293
- };
294
-
295
- const BASE64_PATTERN = /^[A-Za-z0-9+/]{100,}={0,2}$/;
296
-
297
- function summarizeMcpResult(value: unknown): string | undefined {
298
- if (value == null) return undefined;
299
- try {
300
- const parsed = typeof value === "string" ? JSON.parse(value) : value;
301
- if (Array.isArray(parsed)) {
302
- const summarized = parsed.map((item) => {
303
- if (item && typeof item === "object" && item.type) {
304
- if (
305
- (item.type === "image" || item.type === "audio") &&
306
- typeof item.data === "string" &&
307
- BASE64_PATTERN.test(item.data.slice(0, 200))
308
- ) {
309
- const sizeKB = ((item.data.length * 3) / 4 / 1024).toFixed(1);
310
- return { ...item, data: `[${item.type}: ${sizeKB}KB]` };
311
- }
312
- }
313
- return item;
314
- });
315
- return truncateStr(JSON.stringify(summarized));
316
- }
317
- } catch {
318
- // not JSON array, fall through
319
- }
320
- return safeStringify(value);
321
- }
322
-
323
- function buildToolCall(
324
- toolName: string,
325
- toolCallId: string,
326
- args: unknown,
327
- result: unknown,
328
- argsText?: string,
329
- toolSource?: "mcp" | "frontend" | "backend",
330
- ): TelemetryToolCall {
331
- const call: TelemetryToolCall = {
332
- tool_name: toolName,
333
- tool_call_id: toolCallId,
334
- };
335
- const toolArgs = argsText ?? safeStringify(args);
336
- if (toolArgs !== undefined) call.tool_args = toolArgs;
337
- const toolResult =
338
- toolSource === "mcp" ? summarizeMcpResult(result) : safeStringify(result);
339
- if (toolResult !== undefined) call.tool_result = toolResult;
340
- if (toolSource) call.tool_source = toolSource;
341
- return call;
342
- }
343
-
344
276
  type TelemetryStepData = {
345
277
  input_tokens?: number;
346
278
  output_tokens?: number;
347
279
  reasoning_tokens?: number;
348
280
  cached_input_tokens?: number;
349
- tool_calls?: TelemetryToolCall[];
281
+ tool_calls?: AssistantCloudRunReportToolCall[];
350
282
  start_ms?: number;
351
283
  end_ms?: number;
352
284
  };
@@ -369,7 +301,7 @@ function mergeStepTimestamps(
369
301
 
370
302
  type TelemetryData = {
371
303
  status: "completed" | "incomplete" | "error";
372
- toolCalls?: TelemetryToolCall[];
304
+ toolCalls?: AssistantCloudRunReportToolCall[];
373
305
  totalSteps?: number;
374
306
  inputTokens?: number;
375
307
  outputTokens?: number;
@@ -446,13 +378,19 @@ export function extractAuiV0<T>(content: T): TelemetryData | null {
446
378
  const toolCalls = msg.content
447
379
  ?.filter((p) => p.type === "tool-call" && p.toolName && p.toolCallId)
448
380
  .map((p) =>
449
- buildToolCall(p.toolName!, p.toolCallId!, p.args, p.result, p.argsText),
381
+ createRunTelemetryToolCall({
382
+ toolName: p.toolName!,
383
+ toolCallId: p.toolCallId!,
384
+ args: p.args,
385
+ result: p.result,
386
+ argsText: p.argsText,
387
+ }),
450
388
  );
451
389
 
452
390
  const textParts = msg.content?.filter((p) => p.type === "text" && p.text);
453
391
  const outputText =
454
392
  textParts && textParts.length > 0
455
- ? truncateStr(textParts.map((p) => p.text).join(""))
393
+ ? truncateRunTelemetryText(textParts.map((p) => p.text).join(""))
456
394
  : undefined;
457
395
 
458
396
  const steps = msg.metadata?.steps;
@@ -564,29 +502,28 @@ function isDynamicToolPart(p: AiSdkV6Part): boolean {
564
502
  return p.type === "dynamic-tool" || p.type.startsWith("dynamic-tool-");
565
503
  }
566
504
 
567
- function partToToolCall(p: AiSdkV6Part): TelemetryToolCall {
568
- const toolSource: "mcp" | undefined = isDynamicToolPart(p)
505
+ function partToToolCall(p: AiSdkV6Part): AssistantCloudRunReportToolCall {
506
+ const toolSource: "mcp" | "frontend" = isDynamicToolPart(p)
569
507
  ? "mcp"
570
- : undefined;
571
- return buildToolCall(
572
- p.toolName ?? p.type.slice(5),
573
- p.toolCallId!,
574
- p.args ?? p.input,
575
- p.result ?? p.output,
576
- undefined,
508
+ : "frontend";
509
+ return createRunTelemetryToolCall({
510
+ toolName: p.toolName ?? p.type.slice(5),
511
+ toolCallId: p.toolCallId!,
512
+ args: p.args ?? p.input,
513
+ result: p.result ?? p.output,
577
514
  toolSource,
578
- );
515
+ });
579
516
  }
580
517
 
581
518
  function collectAiSdkV6Parts(parts: readonly AiSdkV6Part[]): {
582
519
  textParts: string[];
583
- toolCalls: TelemetryToolCall[];
584
- stepsData: { tool_calls: TelemetryToolCall[] }[];
520
+ toolCalls: AssistantCloudRunReportToolCall[];
521
+ stepsData: { tool_calls: AssistantCloudRunReportToolCall[] }[];
585
522
  } {
586
523
  const textParts: string[] = [];
587
- const toolCalls: TelemetryToolCall[] = [];
588
- const stepsData: { tool_calls: TelemetryToolCall[] }[] = [];
589
- let currentStepToolCalls: TelemetryToolCall[] | null = null;
524
+ const toolCalls: AssistantCloudRunReportToolCall[] = [];
525
+ const stepsData: { tool_calls: AssistantCloudRunReportToolCall[] }[] = [];
526
+ let currentStepToolCalls: AssistantCloudRunReportToolCall[] | null = null;
590
527
 
591
528
  for (const p of parts) {
592
529
  if (p.type === "step-start") {
@@ -624,10 +561,10 @@ function extractModelId(
624
561
 
625
562
  function buildAiSdkV6Result(
626
563
  textParts: string[],
627
- toolCalls: TelemetryToolCall[],
564
+ toolCalls: AssistantCloudRunReportToolCall[],
628
565
  totalSteps: number,
629
566
  metadata?: Record<string, unknown>,
630
- stepsData?: { tool_calls: TelemetryToolCall[] }[],
567
+ stepsData?: { tool_calls: AssistantCloudRunReportToolCall[] }[],
631
568
  usage?: {
632
569
  inputTokens?: number;
633
570
  outputTokens?: number;
@@ -636,7 +573,9 @@ function buildAiSdkV6Result(
636
573
  },
637
574
  ): TelemetryData {
638
575
  const hasText = textParts.length > 0;
639
- const outputText = hasText ? truncateStr(textParts.join("")) : undefined;
576
+ const outputText = hasText
577
+ ? truncateRunTelemetryText(textParts.join(""))
578
+ : undefined;
640
579
  const modelId = extractModelId(metadata);
641
580
 
642
581
  const steps: TelemetryStepData[] | undefined =
@@ -671,46 +610,6 @@ function buildAiSdkV6Result(
671
610
  };
672
611
  }
673
612
 
674
- type UsageFields = {
675
- inputTokens?: number;
676
- outputTokens?: number;
677
- promptTokens?: number;
678
- completionTokens?: number;
679
- reasoningTokens?: number;
680
- cachedInputTokens?: number;
681
- };
682
-
683
- function normalizeUsage(u: UsageFields):
684
- | {
685
- inputTokens?: number;
686
- outputTokens?: number;
687
- reasoningTokens?: number;
688
- cachedInputTokens?: number;
689
- }
690
- | undefined {
691
- const input = u.inputTokens ?? u.promptTokens;
692
- const output = u.outputTokens ?? u.completionTokens;
693
- if (
694
- input == null &&
695
- output == null &&
696
- u.reasoningTokens == null &&
697
- u.cachedInputTokens == null
698
- ) {
699
- return undefined;
700
- }
701
-
702
- return {
703
- ...(input != null ? { inputTokens: input } : undefined),
704
- ...(output != null ? { outputTokens: output } : undefined),
705
- ...(u.reasoningTokens != null
706
- ? { reasoningTokens: u.reasoningTokens }
707
- : undefined),
708
- ...(u.cachedInputTokens != null
709
- ? { cachedInputTokens: u.cachedInputTokens }
710
- : undefined),
711
- };
712
- }
713
-
714
613
  function extractAiSdkV6Usage(metadata?: Record<string, unknown>):
715
614
  | {
716
615
  inputTokens?: number;
@@ -720,15 +619,15 @@ function extractAiSdkV6Usage(metadata?: Record<string, unknown>):
720
619
  }
721
620
  | undefined {
722
621
  // Try top-level metadata.usage
723
- const usage = metadata?.usage as UsageFields | undefined;
622
+ const usage = metadata?.usage as RunTelemetryUsageInit | undefined;
724
623
  if (usage) {
725
- const normalized = normalizeUsage(usage);
624
+ const normalized = normalizeRunTelemetryUsage(usage);
726
625
  if (normalized) return normalized;
727
626
  }
728
627
 
729
628
  // Try aggregating from metadata.steps[].usage
730
629
  const steps = metadata?.steps as
731
- | readonly { usage?: UsageFields }[]
630
+ | readonly { usage?: RunTelemetryUsageInit }[]
732
631
  | undefined;
733
632
  if (steps && steps.length > 0) {
734
633
  let inputTokens = 0;
@@ -742,7 +641,7 @@ function extractAiSdkV6Usage(metadata?: Record<string, unknown>):
742
641
  let hasAny = false;
743
642
  for (const s of steps) {
744
643
  if (!s.usage) continue;
745
- const n = normalizeUsage(s.usage);
644
+ const n = normalizeRunTelemetryUsage(s.usage);
746
645
  if (n) {
747
646
  if (n.inputTokens != null) {
748
647
  inputTokens += n.inputTokens;
@@ -795,8 +694,8 @@ function extractAiSdkV6<T>(content: T): TelemetryData | null {
795
694
 
796
695
  function aggregateAiSdkV6RunSteps<T>(stepMessages: T[]): TelemetryData | null {
797
696
  const allTextParts: string[] = [];
798
- const allToolCalls: TelemetryToolCall[] = [];
799
- const allStepsData: { tool_calls: TelemetryToolCall[] }[] = [];
697
+ const allToolCalls: AssistantCloudRunReportToolCall[] = [];
698
+ const allStepsData: { tool_calls: AssistantCloudRunReportToolCall[] }[] = [];
800
699
  let hasAssistant = false;
801
700
  let metadata: Record<string, unknown> | undefined;
802
701
  let inputTokens = 0;
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { createCloudThreadListAdapterCreateFallback } from "./createCloudThreadListAdapterCreateFallback";
3
+
4
+ describe("createCloudThreadListAdapterCreateFallback", () => {
5
+ it("prefers the custom create function", async () => {
6
+ const create = vi.fn().mockResolvedValue({ externalId: "custom" });
7
+ const initialize = vi.fn().mockResolvedValue({ externalId: "nested" });
8
+ const fallback = createCloudThreadListAdapterCreateFallback(create, {
9
+ source: {},
10
+ initialize,
11
+ });
12
+
13
+ await expect(fallback()).resolves.toEqual({ externalId: "custom" });
14
+ expect(initialize).not.toHaveBeenCalled();
15
+ });
16
+
17
+ it("initializes a sourced thread list item", async () => {
18
+ const result = { externalId: "nested" };
19
+ const fallback = createCloudThreadListAdapterCreateFallback(undefined, {
20
+ source: {},
21
+ initialize: vi.fn().mockResolvedValue(result),
22
+ });
23
+
24
+ await expect(fallback()).resolves.toBe(result);
25
+ });
26
+
27
+ it("falls back to an undefined external ID", async () => {
28
+ const initialize = vi.fn();
29
+ const fallback = createCloudThreadListAdapterCreateFallback(undefined, {
30
+ source: null,
31
+ initialize,
32
+ });
33
+
34
+ await expect(fallback()).resolves.toEqual({ externalId: undefined });
35
+ expect(initialize).not.toHaveBeenCalled();
36
+ });
37
+ });
@@ -0,0 +1,18 @@
1
+ type ThreadData = {
2
+ externalId: string | undefined;
3
+ };
4
+
5
+ type ThreadListItem = {
6
+ source: unknown;
7
+ initialize: () => Promise<ThreadData>;
8
+ };
9
+
10
+ export const createCloudThreadListAdapterCreateFallback = (
11
+ create: (() => Promise<ThreadData>) | undefined,
12
+ threadListItem: ThreadListItem,
13
+ ): (() => Promise<ThreadData>) =>
14
+ async function createThread() {
15
+ if (create) return create();
16
+ if (threadListItem.source) return threadListItem.initialize();
17
+ return { externalId: undefined };
18
+ };
@@ -19,7 +19,38 @@ const makeThread = (id: string) => ({
19
19
  });
20
20
 
21
21
  describe("useCloudThreadListAdapter", () => {
22
- it("exposes full Cloud pages through the remote thread cursor", async () => {
22
+ it("loads archived Cloud threads alongside regular threads", async () => {
23
+ const activeThreads = [makeThread("active-1")];
24
+ const archivedThreads = [
25
+ { ...makeThread("archived-1"), is_archived: true },
26
+ ];
27
+ const list = vi
28
+ .fn()
29
+ .mockResolvedValueOnce({ threads: activeThreads })
30
+ .mockResolvedValueOnce({ threads: archivedThreads });
31
+ const cloud = { threads: { list } } as unknown as AssistantCloud;
32
+ const { result } = renderHook(() => useCloudThreadListAdapter({ cloud }));
33
+
34
+ const page = await result.current.list();
35
+
36
+ expect(list).toHaveBeenNthCalledWith(1, { limit: 20 });
37
+ expect(list).toHaveBeenNthCalledWith(2, {
38
+ is_archived: true,
39
+ limit: 20,
40
+ });
41
+ expect(page.threads.map((thread) => thread.remoteId)).toEqual([
42
+ "active-1",
43
+ "archived-1",
44
+ ]);
45
+ expect(
46
+ page.threads.find((thread) => thread.remoteId === "active-1")?.status,
47
+ ).toBe("regular");
48
+ expect(
49
+ page.threads.find((thread) => thread.remoteId === "archived-1")?.status,
50
+ ).toBe("archived");
51
+ });
52
+
53
+ it("continues the active list once the archived list is exhausted", async () => {
23
54
  const firstPage = Array.from({ length: 20 }, (_, index) =>
24
55
  makeThread(`thread-${index + 1}`),
25
56
  );
@@ -27,17 +58,20 @@ describe("useCloudThreadListAdapter", () => {
27
58
  const list = vi
28
59
  .fn()
29
60
  .mockResolvedValueOnce({ threads: firstPage })
61
+ .mockResolvedValueOnce({ threads: [] })
30
62
  .mockResolvedValueOnce({ threads: secondPage });
31
63
  const cloud = { threads: { list } } as unknown as AssistantCloud;
32
64
  const { result } = renderHook(() => useCloudThreadListAdapter({ cloud }));
33
65
 
34
66
  const first = await result.current.list();
35
67
  expect(list).toHaveBeenNthCalledWith(1, { limit: 20 });
68
+ expect(list).toHaveBeenNthCalledWith(2, { is_archived: true, limit: 20 });
36
69
  expect(first.threads).toHaveLength(20);
37
- expect(first.nextCursor).toBe("thread-20");
70
+ expect(JSON.parse(first.nextCursor!)).toEqual({ a: "thread-20", re: true });
38
71
 
39
72
  const second = await result.current.list({ after: first.nextCursor });
40
- expect(list).toHaveBeenNthCalledWith(2, {
73
+ expect(list).toHaveBeenCalledTimes(3);
74
+ expect(list).toHaveBeenNthCalledWith(3, {
41
75
  limit: 20,
42
76
  after: "thread-20",
43
77
  });
@@ -46,4 +80,65 @@ describe("useCloudThreadListAdapter", () => {
46
80
  ]);
47
81
  expect(second.nextCursor).toBeUndefined();
48
82
  });
83
+
84
+ it("paginates both legs without dropping or duplicating items", async () => {
85
+ const activePage1 = Array.from({ length: 20 }, (_, index) =>
86
+ makeThread(`a-${index + 1}`),
87
+ );
88
+ const archivedPage1 = Array.from({ length: 20 }, (_, index) => ({
89
+ ...makeThread(`r-${index + 1}`),
90
+ is_archived: true,
91
+ }));
92
+ const activePage2 = [makeThread("a-21")];
93
+ const archivedPage2 = [{ ...makeThread("r-21"), is_archived: true }];
94
+ const list = vi
95
+ .fn()
96
+ .mockResolvedValueOnce({ threads: activePage1 })
97
+ .mockResolvedValueOnce({ threads: archivedPage1 })
98
+ .mockResolvedValueOnce({ threads: activePage2 })
99
+ .mockResolvedValueOnce({ threads: archivedPage2 });
100
+ const cloud = { threads: { list } } as unknown as AssistantCloud;
101
+ const { result } = renderHook(() => useCloudThreadListAdapter({ cloud }));
102
+
103
+ const page1 = await result.current.list();
104
+ expect(page1.threads).toHaveLength(40);
105
+ expect(JSON.parse(page1.nextCursor!)).toEqual({ a: "a-20", r: "r-20" });
106
+
107
+ const page2 = await result.current.list({ after: page1.nextCursor });
108
+ expect(list).toHaveBeenNthCalledWith(3, { limit: 20, after: "a-20" });
109
+ expect(list).toHaveBeenNthCalledWith(4, {
110
+ is_archived: true,
111
+ limit: 20,
112
+ after: "r-20",
113
+ });
114
+ expect(page2.threads.map((thread) => thread.remoteId)).toEqual([
115
+ "a-21",
116
+ "r-21",
117
+ ]);
118
+ expect(page2.nextCursor).toBeUndefined();
119
+ });
120
+
121
+ it("accepts a legacy bare-id cursor for the active list", async () => {
122
+ const activeThreads = [makeThread("thread-21")];
123
+ const list = vi
124
+ .fn()
125
+ .mockResolvedValueOnce({ threads: activeThreads })
126
+ .mockResolvedValueOnce({ threads: [] });
127
+ const cloud = { threads: { list } } as unknown as AssistantCloud;
128
+ const { result } = renderHook(() => useCloudThreadListAdapter({ cloud }));
129
+
130
+ const page = await result.current.list({ after: "thread-20" });
131
+
132
+ expect(list).toHaveBeenNthCalledWith(1, {
133
+ limit: 20,
134
+ after: "thread-20",
135
+ });
136
+ expect(list).toHaveBeenNthCalledWith(2, {
137
+ is_archived: true,
138
+ limit: 20,
139
+ });
140
+ expect(page.threads.map((thread) => thread.remoteId)).toEqual([
141
+ "thread-21",
142
+ ]);
143
+ });
49
144
  });