@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
@@ -1,432 +1,46 @@
1
1
  "use client";
2
2
 
3
3
  import { useMemo, useState } from "react";
4
- import { ThreadMessageConverter } from "../../runtimes/external-store/thread-message-converter";
5
- import {
6
- getExternalStoreMessages,
7
- symbolInnerMessage,
8
- bindExternalStoreMessage,
9
- } from "../../runtime/utils/external-store-message";
10
4
  import {
11
- fromThreadMessageLike,
12
- type ThreadMessageLike,
13
- } from "../../runtime/utils/thread-message-like";
14
- import { getAutoStatus, isAutoStatus } from "../../runtime/utils/auto-status";
15
- import type { ToolExecutionStatus } from "../../runtimes/tool-invocations/ToolInvocationTracker";
16
- import type { ReadonlyJSONValue } from "assistant-stream/utils";
17
- import { generateErrorMessageId } from "../../utils/id";
18
- import type {
19
- ThreadAssistantMessage,
20
- ThreadMessage,
21
- ToolCallMessagePart,
22
- } from "../../types/message";
23
- import type { MessageTiming } from "../../types/message";
24
-
25
- export type JoinStrategy = "concat-content" | "none";
26
-
27
- type ThreadMessageLikeContentItem = Exclude<
28
- ThreadMessageLike["content"],
29
- string
30
- >[number];
5
+ chunkExternalMessages,
6
+ completeExternalMessageConversion,
7
+ convertExternalMessageCallback,
8
+ convertExternalMessageChunk,
9
+ convertExternalMessages as convertExternalMessagesInternal,
10
+ shallowArrayEqual,
11
+ type ExternalMessageConverterCallback,
12
+ type ExternalMessageConverterCallbackResult,
13
+ type ExternalMessageConverterChunk,
14
+ type ExternalMessageConverterMessage,
15
+ type ExternalMessageConverterMetadata,
16
+ type JoinStrategy,
17
+ } from "../../runtime/utils/external-message-conversion";
18
+ import { bindExternalStoreMessage } from "../../runtime/utils/external-store-message";
19
+ import { ThreadMessageConverter } from "../../runtimes/external-store/thread-message-converter";
20
+ import type { ThreadMessage } from "../../types/message";
31
21
 
32
- const isPendingToolCall = (c: ThreadMessageLikeContentItem): boolean =>
33
- c.type === "tool-call" && c.result === undefined;
22
+ // Generatedness is tracked by identity, not by id shape: a caller-supplied id
23
+ // that happens to match the generated pattern must never be rewritten.
24
+ const generatedFallbackMessages = new WeakSet<object>();
34
25
 
35
- const isInterruptedToolCall = (c: ThreadMessageLikeContentItem): boolean => {
36
- if (c.type !== "tool-call" || c.result !== undefined) return false;
37
- return (
38
- c.interrupt != null ||
39
- (c.approval != null &&
40
- c.approval.approved === undefined &&
41
- c.approval.resolution === undefined)
42
- );
43
- };
26
+ export type { JoinStrategy };
44
27
 
45
28
  export namespace useExternalMessageConverter {
46
- export type Message =
47
- | (ThreadMessageLike & {
48
- readonly convertConfig?: {
49
- readonly joinStrategy?: JoinStrategy;
50
- };
51
- })
52
- | {
53
- role: "tool";
54
- toolCallId: string;
55
- toolName?: string | undefined;
56
- result: any;
57
- artifact?: any;
58
- isError?: boolean;
59
- messages?: readonly ThreadMessage[];
60
- };
61
-
62
- export type Metadata = {
63
- readonly toolStatuses?: Record<string, ToolExecutionStatus>;
64
- readonly error?: ReadonlyJSONValue;
65
- readonly messageTiming?: Record<string, MessageTiming>;
66
- };
67
-
68
- export type Callback<T> = (
69
- message: T,
70
- metadata: Metadata,
71
- ) => Message | Message[];
72
- }
73
-
74
- type CallbackResult<T> = {
75
- input: T;
76
- outputs: useExternalMessageConverter.Message[];
77
- };
78
-
79
- const stringifyForError = (value: unknown) => {
80
- let text;
81
- try {
82
- text = value instanceof Error ? String(value) : JSON.stringify(value);
83
- } catch {
84
- /* circular */
85
- }
86
- text ??= String(value);
87
- return text.length > 200 ? `${text.slice(0, 200)}…` : text;
88
- };
89
-
90
- const toCallbackOutputs = (
91
- output:
92
- | useExternalMessageConverter.Message
93
- | useExternalMessageConverter.Message[],
94
- input: unknown,
95
- ): useExternalMessageConverter.Message[] => {
96
- const outputs = Array.isArray(output) ? output : [output];
97
- for (const o of outputs) {
98
- const valid =
99
- typeof o === "object" &&
100
- o !== null &&
101
- (o.role === "tool" ||
102
- ((o.role === "assistant" || o.role === "user" || o.role === "system") &&
103
- (typeof o.content === "string" || Array.isArray(o.content))));
104
- if (!valid)
105
- throw new Error(
106
- `External message converter: the converter callback returned an invalid message (${stringifyForError(o)}) for input ${stringifyForError(input)}. Return an empty array to skip a message.`,
107
- );
108
- }
109
- return outputs;
110
- };
111
-
112
- type CallbackCacheEntry<T> = CallbackResult<T> & {
113
- metadata: useExternalMessageConverter.Metadata;
114
- callback: useExternalMessageConverter.Callback<T>;
115
- };
116
-
117
- type ChunkResult<T> = {
118
- inputs: T[];
119
- outputs: useExternalMessageConverter.Message[];
120
- };
121
-
122
- type Mutable<T> = {
123
- -readonly [P in keyof T]: T[P];
124
- };
125
-
126
- const mergeInnerMessages = (existing: object, incoming: object) => ({
127
- [symbolInnerMessage]: [
128
- ...((existing as any)[symbolInnerMessage] ?? []),
129
- ...((incoming as any)[symbolInnerMessage] ?? []),
130
- ],
131
- });
132
-
133
- const joinExternalMessages = (
134
- messages: readonly useExternalMessageConverter.Message[],
135
- ): ThreadMessageLike => {
136
- const assistantMessage: Mutable<Omit<ThreadMessageLike, "metadata">> & {
137
- content: Exclude<ThreadMessageLike["content"][0], string>[];
138
- metadata?: Mutable<ThreadMessageLike["metadata"]>;
139
- } = {
140
- role: "assistant",
141
- content: [],
142
- };
143
- for (const output of messages) {
144
- if (output.role === "tool") {
145
- const toolCallIdx = assistantMessage.content.findIndex(
146
- (c) => c.type === "tool-call" && c.toolCallId === output.toolCallId,
147
- );
148
- // Ignore orphaned tool results so one bad tool message does not
149
- // prevent rendering the rest of the conversation.
150
- if (toolCallIdx !== -1) {
151
- const toolCall = assistantMessage.content[
152
- toolCallIdx
153
- ]! as ToolCallMessagePart;
154
- if (output.toolName != null) {
155
- if (toolCall.toolName !== output.toolName)
156
- throw new Error(
157
- `Tool call name ${output.toolCallId} ${output.toolName} does not match existing tool call ${toolCall.toolName}`,
158
- );
159
- }
160
- assistantMessage.content[toolCallIdx] = {
161
- ...toolCall,
162
- ...{
163
- [symbolInnerMessage]: [
164
- ...((toolCall as any)[symbolInnerMessage] ?? []),
165
- output,
166
- ],
167
- },
168
- result: output.result,
169
- artifact: output.artifact,
170
- isError: output.isError,
171
- messages: output.messages,
172
- };
173
- }
174
- } else {
175
- const role = output.role;
176
- const content = (
177
- typeof output.content === "string"
178
- ? [{ type: "text" as const, text: output.content }]
179
- : output.content
180
- ).map((c) => ({
181
- ...c,
182
- ...{ [symbolInnerMessage]: [output] },
183
- }));
184
- switch (role) {
185
- case "system":
186
- case "user":
187
- return {
188
- ...output,
189
- content,
190
- };
191
- case "assistant":
192
- if (assistantMessage.content.length === 0) {
193
- assistantMessage.id = output.id;
194
- assistantMessage.createdAt ??= output.createdAt;
195
- assistantMessage.status ??= output.status;
196
-
197
- if (output.attachments) {
198
- assistantMessage.attachments = [
199
- ...(assistantMessage.attachments ?? []),
200
- ...output.attachments,
201
- ];
202
- }
203
- }
204
-
205
- if (output.metadata) {
206
- assistantMessage.metadata ??= {};
207
- if (output.metadata.unstable_state !== undefined) {
208
- assistantMessage.metadata.unstable_state =
209
- output.metadata.unstable_state;
210
- }
211
- if (output.metadata.unstable_annotations) {
212
- assistantMessage.metadata.unstable_annotations = [
213
- ...(assistantMessage.metadata.unstable_annotations ?? []),
214
- ...output.metadata.unstable_annotations,
215
- ];
216
- }
217
- if (output.metadata.unstable_data) {
218
- assistantMessage.metadata.unstable_data = [
219
- ...(assistantMessage.metadata.unstable_data ?? []),
220
- ...output.metadata.unstable_data,
221
- ];
222
- }
223
- if (output.metadata.steps) {
224
- assistantMessage.metadata.steps = [
225
- ...(assistantMessage.metadata.steps ?? []),
226
- ...output.metadata.steps,
227
- ];
228
- }
229
- if (output.metadata.custom) {
230
- assistantMessage.metadata.custom = {
231
- ...(assistantMessage.metadata.custom ?? {}),
232
- ...output.metadata.custom,
233
- };
234
- }
235
-
236
- if (output.metadata.timing) {
237
- assistantMessage.metadata.timing = output.metadata.timing;
238
- }
239
-
240
- if (output.metadata.submittedFeedback) {
241
- assistantMessage.metadata.submittedFeedback =
242
- output.metadata.submittedFeedback;
243
- }
244
-
245
- if (output.metadata.isOptimistic) {
246
- assistantMessage.metadata.isOptimistic = true;
247
- }
248
- // TODO keep this in sync with ThreadMessageLike["metadata"] / fromThreadMessageLike
249
- }
250
-
251
- // Add content parts, merging reasoning parts with same parentId
252
- for (const part of content) {
253
- if (part.type === "tool-call") {
254
- const existingIdx = assistantMessage.content.findIndex(
255
- (c) =>
256
- c.type === "tool-call" && c.toolCallId === part.toolCallId,
257
- );
258
- if (existingIdx !== -1) {
259
- const existing = assistantMessage.content[
260
- existingIdx
261
- ] as typeof part;
262
- assistantMessage.content[existingIdx] = {
263
- ...existing,
264
- ...part,
265
- ...mergeInnerMessages(existing, part),
266
- };
267
- continue;
268
- }
269
- }
270
-
271
- if (
272
- part.type === "reasoning" &&
273
- "parentId" in part &&
274
- part.parentId
275
- ) {
276
- const existingIdx = assistantMessage.content.findIndex(
277
- (c) =>
278
- c.type === "reasoning" &&
279
- "parentId" in c &&
280
- c.parentId === part.parentId,
281
- );
282
- if (existingIdx !== -1) {
283
- const existing = assistantMessage.content[
284
- existingIdx
285
- ] as typeof part;
286
- assistantMessage.content[existingIdx] = {
287
- ...existing,
288
- text: `${existing.text}\n\n${part.text}`,
289
- ...mergeInnerMessages(existing, part),
290
- };
291
- continue;
292
- }
293
- }
294
- assistantMessage.content.push(part);
295
- }
296
- break;
297
- default: {
298
- const unsupportedRole: never = role;
299
- throw new Error(`Unknown message role: ${unsupportedRole}`);
300
- }
301
- }
302
- }
303
- }
304
- return assistantMessage;
305
- };
306
-
307
- const chunkExternalMessages = <T>(
308
- callbackResults: CallbackResult<T>[],
309
- joinStrategy?: JoinStrategy,
310
- ) => {
311
- const results: ChunkResult<T>[] = [];
312
- let isAssistant = false;
313
- let pendingNone = false; // true if the previous assistant message had joinStrategy "none"
314
- let inputs: T[] = [];
315
- let outputs: useExternalMessageConverter.Message[] = [];
316
-
317
- const flush = () => {
318
- if (outputs.length) {
319
- results.push({
320
- inputs,
321
- outputs,
322
- });
323
- }
324
- inputs = [];
325
- outputs = [];
326
- isAssistant = false;
327
- pendingNone = false;
328
- };
329
-
330
- for (const callbackResult of callbackResults) {
331
- for (const output of callbackResult.outputs) {
332
- if (
333
- (pendingNone && output.role !== "tool") ||
334
- !isAssistant ||
335
- output.role === "user" ||
336
- output.role === "system"
337
- ) {
338
- flush();
339
- }
340
- isAssistant = output.role === "assistant" || output.role === "tool";
341
-
342
- if (inputs.at(-1) !== callbackResult.input) {
343
- inputs.push(callbackResult.input);
344
- }
345
- outputs.push(output);
346
-
347
- if (
348
- output.role === "assistant" &&
349
- (output.convertConfig?.joinStrategy === "none" ||
350
- joinStrategy === "none")
351
- ) {
352
- pendingNone = true;
353
- }
354
- }
355
- }
356
- flush();
357
- return results;
358
- };
359
-
360
- function createErrorAssistantMessage(
361
- error: ReadonlyJSONValue,
362
- ): ThreadAssistantMessage {
363
- const msg: ThreadAssistantMessage = {
364
- id: generateErrorMessageId(),
365
- role: "assistant",
366
- content: [],
367
- status: { type: "incomplete", reason: "error", error },
368
- createdAt: new Date(),
369
- metadata: {
370
- unstable_state: null,
371
- unstable_annotations: [],
372
- unstable_data: [],
373
- custom: {},
374
- steps: [],
375
- },
376
- };
377
- bindExternalStoreMessage(msg, []);
378
- return msg;
29
+ export type Message = ExternalMessageConverterMessage;
30
+ export type Metadata = ExternalMessageConverterMetadata;
31
+ export type Callback<T> = ExternalMessageConverterCallback<T>;
379
32
  }
380
33
 
381
- export const convertExternalMessages = <T extends WeakKey>(
34
+ export const convertExternalMessages: <T extends WeakKey>(
382
35
  messages: T[],
383
36
  callback: useExternalMessageConverter.Callback<T>,
384
37
  isRunning: boolean,
385
38
  metadata: useExternalMessageConverter.Metadata,
386
- ) => {
387
- const callbackResults: CallbackResult<T>[] = [];
388
- for (const message of messages) {
389
- const output = callback(message, metadata);
390
- const outputs = toCallbackOutputs(output, message);
391
- const result = { input: message, outputs };
392
- callbackResults.push(result);
393
- }
394
-
395
- const chunks = chunkExternalMessages(callbackResults);
396
-
397
- const result = chunks.map((message, idx) => {
398
- const isLast = idx === chunks.length - 1;
399
- const joined = joinExternalMessages(message.outputs);
400
- const hasInterruptedToolCalls =
401
- typeof joined.content === "object" &&
402
- joined.content.some(isInterruptedToolCall);
403
- const hasPendingToolCalls =
404
- typeof joined.content === "object" &&
405
- joined.content.some(isPendingToolCall);
406
- const autoStatus = getAutoStatus(
407
- isLast,
408
- isRunning,
409
- hasInterruptedToolCalls,
410
- hasPendingToolCalls,
411
- isLast ? metadata.error : undefined,
412
- );
413
- const newMessage = fromThreadMessageLike(
414
- joined,
415
- idx.toString(),
416
- autoStatus,
417
- );
418
- bindExternalStoreMessage(newMessage, message.inputs);
419
- return newMessage;
420
- });
39
+ ) => ThreadMessage[] = convertExternalMessagesInternal;
421
40
 
422
- if (metadata.error) {
423
- const lastMessage = result.at(-1);
424
- if (!lastMessage || lastMessage.role !== "assistant") {
425
- result.push(createErrorAssistantMessage(metadata.error));
426
- }
427
- }
428
-
429
- return result;
41
+ type CallbackCacheEntry<T> = ExternalMessageConverterCallbackResult<T> & {
42
+ metadata: useExternalMessageConverter.Metadata;
43
+ callback: useExternalMessageConverter.Callback<T>;
430
44
  };
431
45
 
432
46
  export const useExternalMessageConverter = <T extends WeakKey>({
@@ -452,8 +66,8 @@ export const useExternalMessageConverter = <T extends WeakKey>({
452
66
  const [caches] = useState(() => ({
453
67
  callbackCache: new WeakMap<T, CallbackCacheEntry<T>>(),
454
68
  chunkCache: new WeakMap<
455
- useExternalMessageConverter.Message,
456
- ChunkResult<T>
69
+ ExternalMessageConverterMessage,
70
+ ExternalMessageConverterChunk<T>
457
71
  >(),
458
72
  converterCache: new ThreadMessageConverter(),
459
73
  }));
@@ -468,7 +82,7 @@ export const useExternalMessageConverter = <T extends WeakKey>({
468
82
  );
469
83
 
470
84
  return useMemo(() => {
471
- const callbackResults: CallbackResult<T>[] = [];
85
+ const callbackResults: ExternalMessageConverterCallbackResult<T>[] = [];
472
86
  for (const message of messages) {
473
87
  let result = state.callbackCache.get(message);
474
88
  if (
@@ -476,11 +90,12 @@ export const useExternalMessageConverter = <T extends WeakKey>({
476
90
  result.metadata !== state.metadata ||
477
91
  result.callback !== state.callback
478
92
  ) {
479
- const output = state.callback(message, state.metadata);
480
- const outputs = toCallbackOutputs(output, message);
481
93
  result = {
482
- input: message,
483
- outputs,
94
+ ...convertExternalMessageCallback(
95
+ message,
96
+ state.callback,
97
+ state.metadata,
98
+ ),
484
99
  metadata: state.metadata,
485
100
  callback: state.callback,
486
101
  };
@@ -490,77 +105,39 @@ export const useExternalMessageConverter = <T extends WeakKey>({
490
105
  }
491
106
 
492
107
  const chunks = chunkExternalMessages(callbackResults, joinStrategy).map(
493
- (m) => {
494
- const key = m.outputs[0];
495
- if (!key) return m;
108
+ (message) => {
109
+ const key = message.outputs[0];
110
+ if (!key) return message;
496
111
 
497
112
  const cached = state.chunkCache.get(key);
498
- if (cached && shallowArrayEqual(cached.outputs, m.outputs))
113
+ if (cached && shallowArrayEqual(cached.outputs, message.outputs)) {
499
114
  return cached;
500
- state.chunkCache.set(key, m);
501
- return m;
115
+ }
116
+ state.chunkCache.set(key, message);
117
+ return message;
502
118
  },
503
119
  );
504
120
 
505
121
  const threadMessages = state.converterCache.convertMessages(
506
122
  chunks,
507
- (cache, message, idx) => {
508
- const isLast = idx === chunks.length - 1;
509
-
510
- const joined = joinExternalMessages(message.outputs);
511
- const hasInterruptedToolCalls =
512
- typeof joined.content === "object" &&
513
- joined.content.some(isInterruptedToolCall);
514
- const hasPendingToolCalls =
515
- typeof joined.content === "object" &&
516
- joined.content.some(isPendingToolCall);
517
- const autoStatus = getAutoStatus(
518
- isLast,
123
+ (cache, message, idx) =>
124
+ convertExternalMessageChunk(
125
+ message,
126
+ idx,
127
+ chunks.length,
519
128
  isRunning,
520
- hasInterruptedToolCalls,
521
- hasPendingToolCalls,
522
- isLast ? state.metadata.error : undefined,
523
- );
524
-
525
- if (
526
- cache &&
527
- (cache.role !== "assistant" ||
528
- !isAutoStatus(cache.status) ||
529
- cache.status === autoStatus)
530
- ) {
531
- const inputs = getExternalStoreMessages<T>(cache);
532
- if (shallowArrayEqual(inputs, message.inputs)) {
533
- return cache;
534
- }
535
- }
536
-
537
- const newMessage = fromThreadMessageLike(
538
- joined,
539
- idx.toString(),
540
- autoStatus,
541
- );
542
- bindExternalStoreMessage(newMessage, message.inputs);
543
- return newMessage;
544
- },
129
+ state.metadata.error,
130
+ {
131
+ message: cache,
132
+ generatedFallbackMessages,
133
+ },
134
+ ),
545
135
  );
546
136
 
547
137
  bindExternalStoreMessage(threadMessages, messages);
548
-
549
- if (state.metadata.error) {
550
- const lastMessage = threadMessages.at(-1);
551
- if (!lastMessage || lastMessage.role !== "assistant") {
552
- threadMessages.push(createErrorAssistantMessage(state.metadata.error));
553
- }
554
- }
555
-
556
- return threadMessages;
138
+ return completeExternalMessageConversion(
139
+ threadMessages,
140
+ state.metadata.error,
141
+ );
557
142
  }, [state, messages, isRunning, joinStrategy]);
558
143
  };
559
-
560
- const shallowArrayEqual = (a: unknown[], b: unknown[]) => {
561
- if (a.length !== b.length) return false;
562
- for (let i = 0; i < a.length; i++) {
563
- if (a[i] !== b[i]) return false;
564
- }
565
- return true;
566
- };
@@ -83,17 +83,17 @@ describe("useLocalRuntime", () => {
83
83
  const { rerender } = render(<App cloud={firstCloud} label="first" />);
84
84
 
85
85
  await waitFor(() => {
86
- expect(firstCloud.threads.list).toHaveBeenCalledOnce();
86
+ expect(firstCloud.threads.list).toHaveBeenCalledTimes(2);
87
87
  });
88
88
 
89
89
  rerender(<App cloud={firstCloud} label="second" />);
90
- expect(firstCloud.threads.list).toHaveBeenCalledOnce();
90
+ expect(firstCloud.threads.list).toHaveBeenCalledTimes(2);
91
91
 
92
92
  rerender(<App cloud={secondCloud} label="third" />);
93
93
  await waitFor(() => {
94
- expect(secondCloud.threads.list).toHaveBeenCalledOnce();
94
+ expect(secondCloud.threads.list).toHaveBeenCalledTimes(2);
95
95
  });
96
- expect(firstCloud.threads.list).toHaveBeenCalledOnce();
96
+ expect(firstCloud.threads.list).toHaveBeenCalledTimes(2);
97
97
  });
98
98
 
99
99
  it("uses the current Cloud client for attachment uploads", async () => {
@@ -118,7 +118,7 @@ describe("useLocalRuntime", () => {
118
118
  const { rerender } = render(<App cloud={firstCloud} />);
119
119
 
120
120
  await waitFor(() => {
121
- expect(firstCloud.threads.list).toHaveBeenCalledOnce();
121
+ expect(firstCloud.threads.list).toHaveBeenCalledTimes(2);
122
122
  });
123
123
 
124
124
  rerender(<App cloud={secondCloud} />);
@@ -3,7 +3,7 @@ import type { MessageRole } from "../../types/message";
3
3
  import type { QuoteInfo } from "../../types/quote";
4
4
  import type { Unsubscribe } from "../../types/unsubscribe";
5
5
  import type { RunConfig } from "../../types/message";
6
- import type { QueueItemState } from "../../store/scopes/queue-item";
6
+ import type { QueueItemState } from "../queue/queue-item";
7
7
  import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
8
8
  import {
9
9
  LazyMemoizeSubject,
@@ -1,6 +1,9 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import type { CompleteAttachment } from "../../types/attachment";
3
- import type { ThreadAssistantMessage } from "../../types/message";
3
+ import type {
4
+ ThreadAssistantMessage,
5
+ ToolCallMessagePartStatus,
6
+ } from "../../types/message";
4
7
  import type { ThreadRuntimeCoreBinding } from "./thread-runtime";
5
8
  import {
6
9
  MessageRuntimeImpl,
@@ -173,6 +176,30 @@ describe("toMessagePartStatus", () => {
173
176
  });
174
177
  });
175
178
 
179
+ it("preserves incomplete tool-call status on unresolved tool calls", () => {
180
+ const incompleteStatus = {
181
+ type: "incomplete",
182
+ reason: "tool-calls",
183
+ error: { message: "Tool execution did not finish" },
184
+ } satisfies ToolCallMessagePartStatus;
185
+ const message = createAssistantMessage(
186
+ [
187
+ {
188
+ type: "tool-call",
189
+ toolCallId: "call-1",
190
+ toolName: "weather",
191
+ args: {},
192
+ argsText: "{}",
193
+ },
194
+ ],
195
+ incompleteStatus,
196
+ );
197
+
198
+ expect(toMessagePartStatus(message, 0, message.content[0]!)).toEqual(
199
+ incompleteStatus,
200
+ );
201
+ });
202
+
176
203
  it.each([
177
204
  ["false", false],
178
205
  ["zero", 0],