@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
@@ -26,21 +26,17 @@ import type {
26
26
  } from "../interfaces/composer-runtime-core";
27
27
  import type { DictationAdapter } from "../../adapters/speech";
28
28
  import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
29
- import {
30
- EMPTY_QUEUE_ITEMS,
31
- type QueueItemState,
32
- } from "../../store/scopes/queue-item";
29
+ import { EMPTY_QUEUE_ITEMS, type QueueItemState } from "../queue/queue-item";
33
30
  import { generateId } from "../../utils/id";
34
31
  import { notifyEventListeners } from "../../utils/notify-event-listeners";
32
+ import {
33
+ AttachmentAddOperations,
34
+ drainAttachmentAdd,
35
+ } from "../utils/attachment-add-operations";
35
36
 
36
37
  const isAttachmentComplete = (a: Attachment): a is CompleteAttachment =>
37
38
  a.status.type === "complete";
38
39
 
39
- type AttachmentAddOperation = {
40
- cancelled: boolean;
41
- attachmentIds: Set<string>;
42
- };
43
-
44
40
  export abstract class BaseComposerRuntimeCore
45
41
  extends BaseSubscribable
46
42
  implements ComposerRuntimeCore
@@ -152,21 +148,14 @@ export abstract class BaseComposerRuntimeCore
152
148
  protected _isSending = false;
153
149
  private _removedDuringSend = new Set<string>();
154
150
  private _sendGeneration = 0;
155
- private _attachmentAddOperations = new Set<AttachmentAddOperation>();
151
+ private _attachmentAddOperations = new AttachmentAddOperations();
156
152
 
157
153
  private _cancelAttachmentAdd(attachmentId: string) {
158
- for (const operation of [...this._attachmentAddOperations]) {
159
- if (!operation.attachmentIds.has(attachmentId)) continue;
160
- operation.cancelled = true;
161
- this._attachmentAddOperations.delete(operation);
162
- }
154
+ this._attachmentAddOperations.cancel(attachmentId);
163
155
  }
164
156
 
165
157
  private _cancelAllAttachmentAdds() {
166
- for (const operation of this._attachmentAddOperations) {
167
- operation.cancelled = true;
168
- }
169
- this._attachmentAddOperations.clear();
158
+ this._attachmentAddOperations.cancelAll();
170
159
  }
171
160
 
172
161
  private _emptyTextAndAttachments() {
@@ -466,16 +455,10 @@ export abstract class BaseComposerRuntimeCore
466
455
  throw err;
467
456
  }
468
457
 
469
- const operation: AttachmentAddOperation = {
470
- cancelled: false,
471
- attachmentIds: new Set(),
472
- };
473
- const operations = this._attachmentAddOperations;
474
- operations.add(operation);
458
+ const operation = this._attachmentAddOperations.start();
475
459
  const upsertAttachment = (a: PendingAttachment) => {
476
- if (operation.cancelled) return false;
460
+ if (!this._attachmentAddOperations.accept(operation, a.id)) return false;
477
461
 
478
- operation.attachmentIds.add(a.id);
479
462
  const idx = this._attachments.findIndex(
480
463
  (attachment) => attachment.id === a.id,
481
464
  );
@@ -494,18 +477,15 @@ export abstract class BaseComposerRuntimeCore
494
477
  };
495
478
  let lastAttachment: PendingAttachment | undefined;
496
479
  try {
497
- const promiseOrGenerator = adapter.add({ file: fileOrAttachment });
498
- if (Symbol.asyncIterator in promiseOrGenerator) {
499
- for await (const r of promiseOrGenerator) {
500
- lastAttachment = r;
501
- if (!upsertAttachment(r)) break;
502
- }
503
- } else {
504
- lastAttachment = await promiseOrGenerator;
505
- upsertAttachment(lastAttachment);
506
- }
480
+ await drainAttachmentAdd(
481
+ adapter.add({ file: fileOrAttachment }),
482
+ (attachment) => {
483
+ lastAttachment = attachment;
484
+ return upsertAttachment(attachment);
485
+ },
486
+ );
507
487
  } catch (e) {
508
- if (operation.cancelled) return;
488
+ if (this._attachmentAddOperations.isCancelled(operation)) return;
509
489
  if (lastAttachment) {
510
490
  upsertAttachment({
511
491
  ...lastAttachment,
@@ -524,10 +504,10 @@ export abstract class BaseComposerRuntimeCore
524
504
  );
525
505
  throw e;
526
506
  } finally {
527
- operations.delete(operation);
507
+ this._attachmentAddOperations.finish(operation);
528
508
  }
529
509
 
530
- if (operation.cancelled) return;
510
+ if (this._attachmentAddOperations.isCancelled(operation)) return;
531
511
  if (
532
512
  lastAttachment?.status.type === "incomplete" &&
533
513
  lastAttachment.status.reason === "error"
@@ -112,6 +112,22 @@ afterEach(() => {
112
112
  vi.restoreAllMocks();
113
113
  });
114
114
 
115
+ describe("BaseThreadRuntimeCore subscriptions", () => {
116
+ it("notifies later subscribers when an earlier subscriber throws", () => {
117
+ const runtime = new TestRuntime(createVoiceAdapter());
118
+ const error = new Error("subscriber failed");
119
+ const laterSubscriber = vi.fn();
120
+
121
+ runtime.subscribe(() => {
122
+ throw error;
123
+ });
124
+ runtime.subscribe(laterSubscriber);
125
+
126
+ expect(() => runtime.reset()).toThrow(error);
127
+ expect(laterSubscriber).toHaveBeenCalledOnce();
128
+ });
129
+ });
130
+
115
131
  describe("BaseThreadRuntimeCore voice volume subscriptions", () => {
116
132
  it("continues notifying subscribers when one throws", () => {
117
133
  const consoleError = vi
@@ -36,6 +36,7 @@ import type { RealtimeVoiceAdapter } from "../../adapters/voice";
36
36
  import type { ThreadMessageLike } from "../utils/thread-message-like";
37
37
  import { notifyEventListeners } from "../../utils/notify-event-listeners";
38
38
  import { gateInteractableComposerMetadata } from "../../model-context/interactable-composer-metadata";
39
+ import { BaseSubscribable } from "../../subscribable/subscribable";
39
40
 
40
41
  type BaseThreadAdapters = {
41
42
  speech?: SpeechSynthesisAdapter | undefined;
@@ -44,8 +45,10 @@ type BaseThreadAdapters = {
44
45
  voice?: RealtimeVoiceAdapter | undefined;
45
46
  };
46
47
 
47
- export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
48
- private _subscriptions = new Set<() => void>();
48
+ export abstract class BaseThreadRuntimeCore
49
+ extends BaseSubscribable
50
+ implements ThreadRuntimeCore
51
+ {
49
52
  private _isInitialized = false;
50
53
 
51
54
  protected readonly repository = new MessageRepository();
@@ -118,6 +121,7 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
118
121
  private readonly _contextProvider: ModelContextProvider;
119
122
 
120
123
  constructor(_contextProvider: ModelContextProvider) {
124
+ super();
121
125
  this._contextProvider = _contextProvider;
122
126
  }
123
127
 
@@ -213,10 +217,6 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
213
217
  this._notifySubscribers();
214
218
  }
215
219
 
216
- protected _notifySubscribers() {
217
- for (const callback of this._subscriptions) callback();
218
- }
219
-
220
220
  public _notifyEventSubscribers<E extends ThreadRuntimeEventType>(
221
221
  event: E,
222
222
  payload: ThreadRuntimeEventPayload[E],
@@ -227,11 +227,6 @@ export abstract class BaseThreadRuntimeCore implements ThreadRuntimeCore {
227
227
  notifyEventListeners(subscribers, payload, `Thread runtime "${event}"`);
228
228
  }
229
229
 
230
- public subscribe(callback: () => void): Unsubscribe {
231
- this._subscriptions.add(callback);
232
- return () => this._subscriptions.delete(callback);
233
- }
234
-
235
230
  public submitFeedback({ messageId, type }: SubmitFeedbackOptions) {
236
231
  const adapter = this.adapters?.feedback;
237
232
  if (!adapter) throw new Error("Feedback adapter not configured");
@@ -8,10 +8,7 @@ import type {
8
8
  import type { ThreadRuntimeCore } from "../interfaces/thread-runtime-core";
9
9
  import { getThreadRuntimeCoreIsRunning } from "../api/thread-runtime";
10
10
  import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
11
- import {
12
- EMPTY_QUEUE_ITEMS,
13
- type QueueItemState,
14
- } from "../../store/scopes/queue-item";
11
+ import { EMPTY_QUEUE_ITEMS, type QueueItemState } from "../queue/queue-item";
15
12
  import { BaseComposerRuntimeCore } from "./base-composer-runtime-core";
16
13
 
17
14
  const isCancelable = (runtime: Omit<ThreadRuntimeCore, "composer">) => {
@@ -4,7 +4,7 @@ import type { Attachment, CreateAttachment } from "../../types/attachment";
4
4
  import type { Unsubscribe } from "../../types/unsubscribe";
5
5
  import type { RunConfig } from "../../types/message";
6
6
  import type { DictationAdapter } from "../../adapters/speech";
7
- import type { QueueItemState } from "../../store/scopes/queue-item";
7
+ import type { QueueItemState } from "../queue/queue-item";
8
8
  import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
9
9
 
10
10
  export type AttachmentAddErrorReason =
@@ -16,7 +16,7 @@ import type {
16
16
  EditComposerRuntimeCore,
17
17
  ThreadComposerRuntimeCore,
18
18
  } from "./composer-runtime-core";
19
- import type { QueueItemState } from "../../store/scopes/queue-item";
19
+ import type { QueueItemState } from "../queue/queue-item";
20
20
  import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
21
21
 
22
22
  export type RuntimeCapabilities = {
@@ -56,3 +56,8 @@ export {
56
56
  MessageRepository,
57
57
  } from "./utils/message-repository";
58
58
  export type { ExportedMessageRepositoryItem } from "./utils/message-repository";
59
+ export { createMessageRepositorySession } from "./utils/message-repository-session";
60
+ export type {
61
+ MessageRepositorySession,
62
+ MessageRepositorySessionOptions,
63
+ } from "./utils/message-repository-session";
@@ -1,5 +1,5 @@
1
1
  import type { AppendMessage } from "../../types/message";
2
- import type { QueueItemState } from "../../store/scopes/queue-item";
2
+ import type { QueueItemState } from "./queue-item";
3
3
 
4
4
  export type QueuePlacement = {
5
5
  readonly lane?: "queue" | "steer";
@@ -3,10 +3,7 @@ import type {
3
3
  FileMessagePart,
4
4
  TextMessagePart,
5
5
  } from "../../types/message";
6
- import {
7
- EMPTY_QUEUE_ITEMS,
8
- type QueueItemState,
9
- } from "../../store/scopes/queue-item";
6
+ import { EMPTY_QUEUE_ITEMS, type QueueItemState } from "./queue-item";
10
7
  import { generateId } from "../../utils/id";
11
8
  import { notifyEventListeners } from "../../utils/notify-event-listeners";
12
9
  import { getThreadMessageText } from "../../utils/text";
@@ -0,0 +1,10 @@
1
+ import type { FileMessagePart, TextMessagePart } from "../../types/message";
2
+
3
+ export type QueueItemState = {
4
+ readonly id: string;
5
+ /** @deprecated Derive from the text parts of `parts` instead. Removal after 2026-11-05. */
6
+ readonly prompt: string;
7
+ readonly parts: readonly (FileMessagePart | TextMessagePart)[];
8
+ };
9
+
10
+ export const EMPTY_QUEUE_ITEMS: readonly QueueItemState[] = Object.freeze([]);
@@ -0,0 +1,87 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { createAbortableThreadLoad } from "./abortable-thread-load";
3
+
4
+ const callbacks = () => ({
5
+ onSettled: vi.fn(),
6
+ onInitialError: vi.fn(),
7
+ });
8
+
9
+ describe("createAbortableThreadLoad", () => {
10
+ it("defers a reload to an in-flight initial load", async () => {
11
+ let resolveInitial!: () => void;
12
+ const initialLoad = vi.fn(
13
+ () =>
14
+ new Promise<void>((resolve) => {
15
+ resolveInitial = resolve;
16
+ }),
17
+ );
18
+ const reloadLoad = vi.fn(() => Promise.resolve());
19
+ const controller = createAbortableThreadLoad();
20
+
21
+ const initial = controller.run({
22
+ purpose: "initial",
23
+ load: initialLoad,
24
+ ...callbacks(),
25
+ });
26
+ const reload = controller.run({
27
+ purpose: "reload",
28
+ load: reloadLoad,
29
+ ...callbacks(),
30
+ });
31
+
32
+ expect(reloadLoad).not.toHaveBeenCalled();
33
+ resolveInitial();
34
+ await Promise.all([initial, reload]);
35
+ });
36
+
37
+ it("aborts a superseded load and swallows its failure", async () => {
38
+ let rejectFirst!: (error: Error) => void;
39
+ let firstSignal!: AbortSignal;
40
+ const controller = createAbortableThreadLoad();
41
+ const first = controller.run({
42
+ purpose: "reload",
43
+ load: (signal) => {
44
+ firstSignal = signal;
45
+ return new Promise<void>((_, reject) => {
46
+ rejectFirst = reject;
47
+ });
48
+ },
49
+ ...callbacks(),
50
+ });
51
+
52
+ const second = controller.run({
53
+ purpose: "initial",
54
+ load: () => Promise.resolve(),
55
+ ...callbacks(),
56
+ });
57
+ rejectFirst(new Error("aborted"));
58
+
59
+ expect(firstSignal.aborted).toBe(true);
60
+ await expect(first).resolves.toBeUndefined();
61
+ await second;
62
+ });
63
+
64
+ it("swallows initial failures and surfaces reload failures", async () => {
65
+ const controller = createAbortableThreadLoad();
66
+ const initialCallbacks = callbacks();
67
+ const initialError = new Error("initial");
68
+
69
+ await expect(
70
+ controller.run({
71
+ purpose: "initial",
72
+ load: () => Promise.reject(initialError),
73
+ ...initialCallbacks,
74
+ }),
75
+ ).resolves.toBeUndefined();
76
+ expect(initialCallbacks.onInitialError).toHaveBeenCalledWith(initialError);
77
+
78
+ const reloadError = new Error("reload");
79
+ await expect(
80
+ controller.run({
81
+ purpose: "reload",
82
+ load: () => Promise.reject(reloadError),
83
+ ...callbacks(),
84
+ }),
85
+ ).rejects.toBe(reloadError);
86
+ });
87
+ });
@@ -0,0 +1,53 @@
1
+ export type AbortableThreadLoadPurpose = "initial" | "reload";
2
+
3
+ type AbortableThreadLoadOptions = {
4
+ purpose?: AbortableThreadLoadPurpose;
5
+ load: (signal: AbortSignal) => Promise<void>;
6
+ onSettled: () => void;
7
+ onInitialError: (error: unknown) => void;
8
+ };
9
+
10
+ export const createAbortableThreadLoad = () => {
11
+ let current: {
12
+ controller: AbortController;
13
+ purpose: AbortableThreadLoadPurpose;
14
+ promise?: Promise<void>;
15
+ } | null = null;
16
+
17
+ return {
18
+ abort(purpose?: AbortableThreadLoadPurpose) {
19
+ if (purpose !== undefined && current?.purpose !== purpose) return;
20
+ current?.controller.abort();
21
+ },
22
+ run({
23
+ purpose = "initial",
24
+ load,
25
+ onSettled,
26
+ onInitialError,
27
+ }: AbortableThreadLoadOptions): Promise<void> {
28
+ if (purpose === "reload" && current?.purpose === "initial") {
29
+ return current.promise ?? Promise.resolve();
30
+ }
31
+
32
+ current?.controller.abort();
33
+ const controller = new AbortController();
34
+ const record: NonNullable<typeof current> = { controller, purpose };
35
+ current = record;
36
+
37
+ const task = load(controller.signal)
38
+ .catch((error: unknown) => {
39
+ if (controller.signal.aborted) return;
40
+ throw error;
41
+ })
42
+ .finally(() => {
43
+ if (current?.controller === controller) current = null;
44
+ if (controller.signal.aborted) return;
45
+ onSettled();
46
+ });
47
+ record.promise = task;
48
+
49
+ if (purpose === "reload") return task;
50
+ return task.catch(onInitialError);
51
+ },
52
+ };
53
+ };
@@ -0,0 +1,59 @@
1
+ export type AttachmentAddOperation = {
2
+ cancelled: boolean;
3
+ attachmentIds: Set<string>;
4
+ };
5
+
6
+ export class AttachmentAddOperations {
7
+ private readonly operations = new Set<AttachmentAddOperation>();
8
+
9
+ start() {
10
+ const operation: AttachmentAddOperation = {
11
+ cancelled: false,
12
+ attachmentIds: new Set(),
13
+ };
14
+ this.operations.add(operation);
15
+ return operation;
16
+ }
17
+
18
+ accept(operation: AttachmentAddOperation, attachmentId: string) {
19
+ if (operation.cancelled) return false;
20
+ operation.attachmentIds.add(attachmentId);
21
+ return true;
22
+ }
23
+
24
+ finish(operation: AttachmentAddOperation) {
25
+ this.operations.delete(operation);
26
+ }
27
+
28
+ isCancelled(operation: AttachmentAddOperation) {
29
+ return operation.cancelled;
30
+ }
31
+
32
+ cancel(attachmentId: string) {
33
+ for (const operation of [...this.operations]) {
34
+ if (!operation.attachmentIds.has(attachmentId)) continue;
35
+ operation.cancelled = true;
36
+ this.operations.delete(operation);
37
+ }
38
+ }
39
+
40
+ cancelAll() {
41
+ for (const operation of this.operations) {
42
+ operation.cancelled = true;
43
+ }
44
+ this.operations.clear();
45
+ }
46
+ }
47
+
48
+ export const drainAttachmentAdd = async <T>(
49
+ result: Promise<T> | AsyncIterable<T>,
50
+ accept: (attachment: T) => boolean,
51
+ ) => {
52
+ if (Symbol.asyncIterator in result) {
53
+ for await (const attachment of result) {
54
+ if (!accept(attachment)) break;
55
+ }
56
+ } else {
57
+ accept(await result);
58
+ }
59
+ };
@@ -1,5 +1,26 @@
1
1
  import type { MessageStatus } from "../../types/message";
2
2
  import type { ReadonlyJSONValue } from "assistant-stream/utils";
3
+ import type { ThreadMessageLike } from "./thread-message-like";
4
+
5
+ type ThreadMessageLikeContentItem = Exclude<
6
+ ThreadMessageLike["content"],
7
+ string
8
+ >[number];
9
+
10
+ export const isPendingToolCall = (c: ThreadMessageLikeContentItem): boolean =>
11
+ c.type === "tool-call" && c.result === undefined;
12
+
13
+ export const isInterruptedToolCall = (
14
+ c: ThreadMessageLikeContentItem,
15
+ ): boolean => {
16
+ if (c.type !== "tool-call" || c.result !== undefined) return false;
17
+ return (
18
+ c.interrupt != null ||
19
+ (c.approval != null &&
20
+ c.approval.approved === undefined &&
21
+ c.approval.resolution === undefined)
22
+ );
23
+ };
3
24
 
4
25
  const symbolAutoStatus = Symbol("autoStatus");
5
26
 
@@ -65,3 +86,15 @@ export const getAutoStatus = (
65
86
  ? AUTO_STATUS_PENDING
66
87
  : AUTO_STATUS_COMPLETE;
67
88
  };
89
+
90
+ export const getContentAutoStatus = (
91
+ content: ThreadMessageLike["content"],
92
+ isLast: boolean,
93
+ isRunning: boolean,
94
+ ): MessageStatus =>
95
+ getAutoStatus(
96
+ isLast,
97
+ isRunning,
98
+ typeof content !== "string" && content.some(isInterruptedToolCall),
99
+ typeof content !== "string" && content.some(isPendingToolCall),
100
+ );
@@ -0,0 +1,113 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import {
3
+ chunkExternalMessages,
4
+ convertExternalMessageCallback,
5
+ convertExternalMessageChunk,
6
+ type ExternalMessageConverterCallback,
7
+ type ExternalMessageConverterCallbackResult,
8
+ } from "./external-message-conversion";
9
+
10
+ describe("convertExternalMessageCallback", () => {
11
+ it.each([
12
+ ["a missing toolCallId", { role: "tool", result: "ok" }],
13
+ ["an empty toolCallId", { role: "tool", toolCallId: "", result: "ok" }],
14
+ ])("warns and drops a tool message with %s", (_label, message) => {
15
+ const input = { id: "m1" };
16
+ const callback = (() =>
17
+ message) as unknown as ExternalMessageConverterCallback<typeof input>;
18
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
19
+
20
+ try {
21
+ const result = convertExternalMessageCallback(input, callback, {});
22
+ expect(result.outputs).toHaveLength(0);
23
+ expect(warn).toHaveBeenCalledOnce();
24
+ expect(warn.mock.calls[0]![0]).toMatch(
25
+ /dropping a tool result without a toolCallId/,
26
+ );
27
+ } finally {
28
+ warn.mockRestore();
29
+ }
30
+ });
31
+ });
32
+
33
+ describe("chunkExternalMessages", () => {
34
+ it("keeps a tool result with a non-joining assistant before starting the next chunk", () => {
35
+ const toolCall = {};
36
+ const toolResult = {};
37
+ const answer = {};
38
+ const callbackResults: ExternalMessageConverterCallbackResult<object>[] = [
39
+ {
40
+ input: toolCall,
41
+ outputs: [
42
+ {
43
+ role: "assistant",
44
+ content: [
45
+ {
46
+ type: "tool-call",
47
+ toolCallId: "call-1",
48
+ toolName: "search",
49
+ args: {},
50
+ },
51
+ ],
52
+ convertConfig: { joinStrategy: "none" },
53
+ },
54
+ ],
55
+ },
56
+ {
57
+ input: toolResult,
58
+ outputs: [
59
+ {
60
+ role: "tool",
61
+ toolCallId: "call-1",
62
+ toolName: "search",
63
+ result: "result",
64
+ },
65
+ ],
66
+ },
67
+ {
68
+ input: answer,
69
+ outputs: [{ role: "assistant", content: "answer" }],
70
+ },
71
+ ];
72
+
73
+ const chunks = chunkExternalMessages(callbackResults);
74
+
75
+ expect(chunks).toHaveLength(2);
76
+ expect(chunks[0]?.inputs).toEqual([toolCall, toolResult]);
77
+ expect(chunks[0]?.outputs).toEqual([
78
+ callbackResults[0]?.outputs[0],
79
+ callbackResults[1]?.outputs[0],
80
+ ]);
81
+ expect(chunks[1]?.inputs).toEqual([answer]);
82
+ });
83
+ });
84
+
85
+ describe("convertExternalMessageChunk", () => {
86
+ it("reuses a cached message when the error status is unchanged", () => {
87
+ const input = {};
88
+ const chunk = {
89
+ inputs: [input],
90
+ outputs: [{ role: "assistant" as const, content: "failed" }],
91
+ };
92
+ const generatedFallbackMessages = new WeakSet<object>();
93
+ const first = convertExternalMessageChunk(
94
+ chunk,
95
+ 0,
96
+ 1,
97
+ false,
98
+ { message: "failed" },
99
+ { message: undefined, generatedFallbackMessages },
100
+ );
101
+
102
+ const second = convertExternalMessageChunk(
103
+ chunk,
104
+ 0,
105
+ 1,
106
+ true,
107
+ { message: "failed" },
108
+ { message: first, generatedFallbackMessages },
109
+ );
110
+
111
+ expect(second).toBe(first);
112
+ });
113
+ });