@assistant-ui/core 0.3.17 → 0.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (470) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/attachment.js +5 -4
  3. package/dist/adapters/attachment.js.map +1 -1
  4. package/dist/adapters/speech.d.ts.map +1 -1
  5. package/dist/adapters/speech.js +13 -3
  6. package/dist/adapters/speech.js.map +1 -1
  7. package/dist/adapters/thread-history.d.ts +4 -0
  8. package/dist/adapters/thread-history.d.ts.map +1 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js.map +1 -1
  11. package/dist/internal.d.ts +4 -3
  12. package/dist/internal.js +3 -2
  13. package/dist/model-context/frame/host.d.ts.map +1 -1
  14. package/dist/model-context/frame/host.js +35 -14
  15. package/dist/model-context/frame/host.js.map +1 -1
  16. package/dist/model-context/frame/provider.d.ts +2 -0
  17. package/dist/model-context/frame/provider.d.ts.map +1 -1
  18. package/dist/model-context/frame/provider.js +30 -7
  19. package/dist/model-context/frame/provider.js.map +1 -1
  20. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  21. package/dist/model-context/interactable-composer-metadata.js +27 -18
  22. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  23. package/dist/model-context/registry.d.ts.map +1 -1
  24. package/dist/model-context/registry.js +3 -5
  25. package/dist/model-context/registry.js.map +1 -1
  26. package/dist/model-context/types.d.ts.map +1 -1
  27. package/dist/model-context/types.js +5 -4
  28. package/dist/model-context/types.js.map +1 -1
  29. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  30. package/dist/react/adapters/LocalStorageThreadListAdapter.js +45 -7
  31. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  32. package/dist/react/client/DataRenderers.d.ts.map +1 -1
  33. package/dist/react/client/DataRenderers.js +20 -15
  34. package/dist/react/client/DataRenderers.js.map +1 -1
  35. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  36. package/dist/react/client/InMemoryThreadList.js +62 -56
  37. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  38. package/dist/react/client/Interactables.d.ts.map +1 -1
  39. package/dist/react/client/Interactables.js +35 -32
  40. package/dist/react/client/Interactables.js.map +1 -1
  41. package/dist/react/client/RemoteThreadList.d.ts +1 -0
  42. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  43. package/dist/react/client/RemoteThreadList.js +154 -143
  44. package/dist/react/client/RemoteThreadList.js.map +1 -1
  45. package/dist/react/client/Tools.d.ts.map +1 -1
  46. package/dist/react/client/Tools.js +18 -16
  47. package/dist/react/client/Tools.js.map +1 -1
  48. package/dist/react/client/interactable-model-context.d.ts.map +1 -1
  49. package/dist/react/client/interactable-model-context.js +3 -2
  50. package/dist/react/client/interactable-model-context.js.map +1 -1
  51. package/dist/react/index.d.ts +2 -2
  52. package/dist/react/index.js +2 -2
  53. package/dist/react/interactables-legacy/Interactables.js +242 -121
  54. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  55. package/dist/react/interactables-legacy/useAssistantInteractable.d.ts.map +1 -1
  56. package/dist/react/interactables-legacy/useAssistantInteractable.js +3 -0
  57. package/dist/react/interactables-legacy/useAssistantInteractable.js.map +1 -1
  58. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  59. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  60. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  61. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  62. package/dist/react/model-context/makeAssistantTool.js +1 -1
  63. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  64. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  65. package/dist/react/model-context/useAssistantTool.js +1 -1
  66. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  67. package/dist/react/model-context/useInteractable.d.ts.map +1 -1
  68. package/dist/react/model-context/useInteractable.js +4 -1
  69. package/dist/react/model-context/useInteractable.js.map +1 -1
  70. package/dist/react/model-context/useInteractableVersions.js +1 -1
  71. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  72. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  73. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  74. package/dist/react/primitives/message/MessageGroupedParts.d.ts +11 -2
  75. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  76. package/dist/react/primitives/message/MessageGroupedParts.js +3 -2
  77. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  78. package/dist/react/primitives/part/PartMessages.d.ts +1 -0
  79. package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
  80. package/dist/react/primitives/part/PartMessages.js +1 -0
  81. package/dist/react/primitives/part/PartMessages.js.map +1 -1
  82. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  83. package/dist/react/primitives/thread/ThreadMessages.js +6 -5
  84. package/dist/react/primitives/thread/ThreadMessages.js.map +1 -1
  85. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  86. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +16 -8
  87. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  88. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -1
  89. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +105 -104
  91. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  92. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -0
  93. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  94. package/dist/react/runtimes/RemoteThreadResource.js +13 -11
  95. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  96. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +5 -24
  97. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  98. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +354 -226
  99. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  100. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +1 -0
  101. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  102. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +22 -2
  103. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  104. package/dist/react/runtimes/cloud/auiV0.d.ts +40 -15
  105. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  106. package/dist/react/runtimes/cloud/auiV0.js +62 -15
  107. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  108. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +2 -1
  109. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
  110. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +6 -1
  111. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -1
  112. package/dist/react/runtimes/cloud/sdkIdentity.d.ts +6 -0
  113. package/dist/react/runtimes/cloud/sdkIdentity.d.ts.map +1 -0
  114. package/dist/react/runtimes/cloud/sdkIdentity.js +9 -0
  115. package/dist/react/runtimes/cloud/sdkIdentity.js.map +1 -0
  116. package/dist/react/runtimes/external-message-converter.d.ts +12 -2
  117. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  118. package/dist/react/runtimes/external-message-converter.js +26 -61
  119. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  120. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  121. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  122. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  123. package/dist/react/runtimes/useRemoteThreadListRuntime.js +64 -24
  124. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  125. package/dist/react/utils/groupParts.d.ts +6 -1
  126. package/dist/react/utils/groupParts.d.ts.map +1 -1
  127. package/dist/react/utils/groupParts.js +6 -0
  128. package/dist/react/utils/groupParts.js.map +1 -1
  129. package/dist/react/utils/useJSONEqual.d.ts +3 -1
  130. package/dist/react/utils/useJSONEqual.d.ts.map +1 -1
  131. package/dist/react/utils/useJSONEqual.js +6 -1
  132. package/dist/react/utils/useJSONEqual.js.map +1 -1
  133. package/dist/react/utils/useJSONSchemaDependency.d.ts +7 -0
  134. package/dist/react/utils/useJSONSchemaDependency.d.ts.map +1 -0
  135. package/dist/react/utils/useJSONSchemaDependency.js +20 -0
  136. package/dist/react/utils/useJSONSchemaDependency.js.map +1 -0
  137. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  138. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  139. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  140. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  141. package/dist/runtime/api/thread-list-runtime.d.ts +3 -0
  142. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  143. package/dist/runtime/api/thread-list-runtime.js +18 -14
  144. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  145. package/dist/runtime/api/thread-runtime.d.ts +4 -0
  146. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  147. package/dist/runtime/api/thread-runtime.js +3 -6
  148. package/dist/runtime/api/thread-runtime.js.map +1 -1
  149. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
  150. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  151. package/dist/runtime/base/base-composer-runtime-core.js +62 -22
  152. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  153. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -0
  154. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  155. package/dist/runtime/base/base-thread-runtime-core.js +161 -69
  156. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  157. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  158. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  159. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +4 -1
  160. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  161. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  162. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/queue/message-queue.d.ts +5 -0
  164. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  165. package/dist/runtime/queue/message-queue.js +61 -6
  166. package/dist/runtime/queue/message-queue.js.map +1 -1
  167. package/dist/runtime/utils/auto-status.d.ts +6 -2
  168. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  169. package/dist/runtime/utils/auto-status.js +12 -4
  170. package/dist/runtime/utils/auto-status.js.map +1 -1
  171. package/dist/runtime/utils/external-message-conversion.d.ts +16 -4
  172. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  173. package/dist/runtime/utils/external-message-conversion.js +49 -13
  174. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  175. package/dist/runtime/utils/message-repository.d.ts +1 -0
  176. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  177. package/dist/runtime/utils/message-repository.js +18 -9
  178. package/dist/runtime/utils/message-repository.js.map +1 -1
  179. package/dist/runtime/utils/thread-message-like.d.ts +2 -1
  180. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  181. package/dist/runtime/utils/thread-message-like.js +5 -3
  182. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  183. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  184. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  185. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  186. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  187. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +8 -1
  188. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  189. package/dist/runtimes/internal.d.ts +3 -3
  190. package/dist/runtimes/internal.js +2 -2
  191. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
  192. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  193. package/dist/runtimes/local/local-thread-runtime-core.js +4 -1
  194. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  195. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  196. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  197. package/dist/runtimes/remote-thread-list/remote-thread-state.js +130 -42
  198. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  199. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  200. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  201. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  202. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  203. package/dist/runtimes/remote-thread-list/title.d.ts +3 -1
  204. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -1
  205. package/dist/runtimes/remote-thread-list/title.js +9 -1
  206. package/dist/runtimes/remote-thread-list/title.js.map +1 -1
  207. package/dist/runtimes/remote-thread-list/types.d.ts +10 -0
  208. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  209. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  210. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -1
  211. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  212. package/dist/store/clients/external-thread.d.ts.map +1 -1
  213. package/dist/store/clients/external-thread.js +260 -203
  214. package/dist/store/clients/external-thread.js.map +1 -1
  215. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  216. package/dist/store/clients/model-context-client.js +2 -1
  217. package/dist/store/clients/model-context-client.js.map +1 -1
  218. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  219. package/dist/store/clients/single-thread-list.js +1 -0
  220. package/dist/store/clients/single-thread-list.js.map +1 -1
  221. package/dist/store/clients/suggestions.d.ts.map +1 -1
  222. package/dist/store/clients/suggestions.js +5 -4
  223. package/dist/store/clients/suggestions.js.map +1 -1
  224. package/dist/store/clients/thread-tasks.d.ts +13 -0
  225. package/dist/store/clients/thread-tasks.d.ts.map +1 -0
  226. package/dist/store/clients/thread-tasks.js +69 -0
  227. package/dist/store/clients/thread-tasks.js.map +1 -0
  228. package/dist/store/index.d.ts +3 -2
  229. package/dist/store/index.js +3 -3
  230. package/dist/store/primitive-predicates.d.ts.map +1 -1
  231. package/dist/store/primitive-predicates.js +4 -4
  232. package/dist/store/primitive-predicates.js.map +1 -1
  233. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  234. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  235. package/dist/store/runtime-clients/composer-runtime-client.js +97 -61
  236. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  237. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  238. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  239. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  240. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  241. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  242. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  243. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  244. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  245. package/dist/store/runtime-clients/thread-list-runtime-client.js +54 -52
  246. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  247. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  248. package/dist/store/runtime-clients/thread-runtime-client.js +246 -126
  249. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  250. package/dist/store/scope-registration.d.ts +2 -0
  251. package/dist/store/scope-registration.d.ts.map +1 -1
  252. package/dist/store/scopes/composer.d.ts +10 -2
  253. package/dist/store/scopes/composer.d.ts.map +1 -1
  254. package/dist/store/scopes/message.d.ts +30 -1
  255. package/dist/store/scopes/message.d.ts.map +1 -1
  256. package/dist/store/scopes/task.d.ts +36 -0
  257. package/dist/store/scopes/task.d.ts.map +1 -0
  258. package/dist/store/scopes/task.js +0 -0
  259. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  260. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  261. package/dist/store/scopes/thread.d.ts +28 -0
  262. package/dist/store/scopes/thread.d.ts.map +1 -1
  263. package/dist/store/scopes/threads.d.ts +1 -0
  264. package/dist/store/scopes/threads.d.ts.map +1 -1
  265. package/dist/subscribable/subscribable.d.ts +2 -1
  266. package/dist/subscribable/subscribable.d.ts.map +1 -1
  267. package/dist/subscribable/subscribable.js +23 -15
  268. package/dist/subscribable/subscribable.js.map +1 -1
  269. package/dist/types/attachment.d.ts +2 -1
  270. package/dist/types/attachment.d.ts.map +1 -1
  271. package/dist/types/attachment.js +2 -1
  272. package/dist/types/attachment.js.map +1 -1
  273. package/dist/types/index.d.ts +2 -2
  274. package/dist/types/message.d.ts +8 -1
  275. package/dist/types/message.d.ts.map +1 -1
  276. package/dist/types/message.js.map +1 -1
  277. package/dist/utils/abortable-iterable.d.ts +19 -0
  278. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  279. package/dist/utils/abortable-iterable.js +88 -0
  280. package/dist/utils/abortable-iterable.js.map +1 -0
  281. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  282. package/dist/utils/composite-context-provider.js +20 -2
  283. package/dist/utils/composite-context-provider.js.map +1 -1
  284. package/dist/utils/getGroupStatus.d.ts +18 -2
  285. package/dist/utils/getGroupStatus.d.ts.map +1 -1
  286. package/dist/utils/getGroupStatus.js +32 -7
  287. package/dist/utils/getGroupStatus.js.map +1 -1
  288. package/dist/utils/record.d.ts +11 -0
  289. package/dist/utils/record.d.ts.map +1 -0
  290. package/dist/utils/record.js +12 -0
  291. package/dist/utils/record.js.map +1 -0
  292. package/dist/utils/wire-media.d.ts.map +1 -1
  293. package/dist/utils/wire-media.js +2 -2
  294. package/dist/utils/wire-media.js.map +1 -1
  295. package/package.json +11 -11
  296. package/src/adapters/attachment.test.ts +50 -1
  297. package/src/adapters/attachment.ts +7 -4
  298. package/src/adapters/speech.test.ts +236 -0
  299. package/src/adapters/speech.ts +17 -10
  300. package/src/adapters/thread-history.ts +3 -0
  301. package/src/index.ts +2 -0
  302. package/src/internal.ts +4 -0
  303. package/src/model-context/frame/host.test.ts +137 -1
  304. package/src/model-context/frame/host.ts +49 -16
  305. package/src/model-context/frame/provider.test.ts +71 -0
  306. package/src/model-context/frame/provider.ts +37 -7
  307. package/src/model-context/interactable-composer-metadata.test.ts +58 -0
  308. package/src/model-context/interactable-composer-metadata.ts +19 -13
  309. package/src/model-context/registry.test.ts +12 -0
  310. package/src/model-context/registry.ts +3 -2
  311. package/src/model-context/types.test.ts +25 -0
  312. package/src/model-context/types.ts +9 -4
  313. package/src/react/RuntimeAdapter.test.ts +34 -0
  314. package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +244 -0
  315. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +104 -7
  316. package/src/react/client/DataRenderers.test.tsx +45 -0
  317. package/src/react/client/DataRenderers.ts +13 -9
  318. package/src/react/client/InMemoryThreadList.ts +1 -0
  319. package/src/react/client/Interactables.test.ts +72 -1
  320. package/src/react/client/Interactables.ts +22 -14
  321. package/src/react/client/RemoteThreadList.background.test.ts +148 -6
  322. package/src/react/client/RemoteThreadList.concurrent.test.tsx +42 -1
  323. package/src/react/client/RemoteThreadList.test.ts +386 -4
  324. package/src/react/client/RemoteThreadList.ts +181 -112
  325. package/src/react/client/Tools.ts +22 -11
  326. package/src/react/client/interactable-model-context.test.ts +44 -0
  327. package/src/react/client/interactable-model-context.ts +3 -2
  328. package/src/react/index.ts +5 -1
  329. package/src/react/interactables-legacy/Interactables.ts +16 -16
  330. package/src/react/interactables-legacy/useAssistantInteractable.test.tsx +132 -0
  331. package/src/react/interactables-legacy/useAssistantInteractable.ts +10 -1
  332. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  333. package/src/react/model-context/makeAssistantTool.ts +2 -2
  334. package/src/react/model-context/useAssistantTool.ts +2 -2
  335. package/src/react/model-context/useInteractable.test.tsx +177 -0
  336. package/src/react/model-context/useInteractable.ts +3 -0
  337. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  338. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  339. package/src/react/primitive-hooks/useActionBarCopy.test.ts +0 -1
  340. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +0 -1
  341. package/src/react/primitives/message/MessageGroupedParts.test.tsx +96 -0
  342. package/src/react/primitives/message/MessageGroupedParts.tsx +15 -4
  343. package/src/react/primitives/part/PartMessages.test.tsx +104 -1
  344. package/src/react/primitives/part/PartMessages.tsx +1 -0
  345. package/src/react/primitives/thread/ThreadMessages.test.tsx +142 -0
  346. package/src/react/primitives/thread/ThreadMessages.tsx +8 -5
  347. package/src/react/providers/ReadonlyThreadProvider.test.tsx +56 -0
  348. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  349. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +33 -0
  350. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  351. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +28 -5
  352. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  353. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +105 -0
  354. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +143 -112
  355. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  356. package/src/react/runtimes/RemoteThreadResource.ts +19 -12
  357. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +20 -14
  358. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +703 -6
  359. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +492 -402
  360. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +77 -0
  361. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +27 -1
  362. package/src/react/runtimes/cloud/auiV0.ts +160 -25
  363. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +36 -0
  364. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +8 -1
  365. package/src/react/runtimes/cloud/sdkIdentity.ts +9 -0
  366. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +18 -4
  367. package/src/react/runtimes/external-message-converter.test.ts +136 -1
  368. package/src/react/runtimes/external-message-converter.ts +39 -93
  369. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  370. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  371. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  372. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  373. package/src/react/runtimes/useRemoteThreadListRuntime.ts +34 -1
  374. package/src/react/utils/groupParts.ts +12 -1
  375. package/src/react/utils/useJSONEqual.ts +9 -0
  376. package/src/react/utils/useJSONSchemaDependency.ts +22 -0
  377. package/src/runtime/api/message-runtime.test.ts +75 -0
  378. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  379. package/src/runtime/api/thread-list-runtime.ts +21 -11
  380. package/src/runtime/api/thread-runtime.test.ts +136 -0
  381. package/src/runtime/api/thread-runtime.ts +10 -5
  382. package/src/runtime/base/base-composer-runtime-core.ts +105 -24
  383. package/src/runtime/base/base-thread-runtime-core.test.ts +1063 -2
  384. package/src/runtime/base/base-thread-runtime-core.ts +228 -87
  385. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  386. package/src/runtime/interfaces/thread-list-runtime-core.ts +5 -1
  387. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  388. package/src/runtime/queue/message-queue.ts +68 -6
  389. package/src/runtime/utils/auto-status.test.ts +112 -1
  390. package/src/runtime/utils/auto-status.ts +31 -5
  391. package/src/runtime/utils/external-message-conversion.test.ts +202 -0
  392. package/src/runtime/utils/external-message-conversion.ts +123 -32
  393. package/src/runtime/utils/message-repository.test.ts +49 -0
  394. package/src/runtime/utils/message-repository.ts +32 -20
  395. package/src/runtime/utils/thread-message-like.test.ts +52 -0
  396. package/src/runtime/utils/thread-message-like.ts +5 -1
  397. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  398. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +31 -1
  399. package/src/runtimes/internal.ts +1 -0
  400. package/src/runtimes/local/local-thread-runtime-core.test.ts +21 -0
  401. package/src/runtimes/local/local-thread-runtime-core.ts +11 -1
  402. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +128 -2
  403. package/src/runtimes/remote-thread-list/remote-thread-state.ts +182 -42
  404. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  405. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  406. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  407. package/src/runtimes/remote-thread-list/title.ts +13 -0
  408. package/src/runtimes/remote-thread-list/types.ts +10 -0
  409. package/src/runtimes/tool-invocations/EDGE_CASES.md +2 -6
  410. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +133 -0
  411. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +10 -6
  412. package/src/store/clients/external-thread.ts +67 -4
  413. package/src/store/clients/model-context-client.ts +5 -1
  414. package/src/store/clients/single-thread-list.ts +1 -0
  415. package/src/store/clients/suggestions.test.ts +40 -0
  416. package/src/store/clients/suggestions.ts +13 -10
  417. package/src/store/clients/thread-tasks.test.ts +274 -0
  418. package/src/store/clients/thread-tasks.ts +138 -0
  419. package/src/store/index.ts +8 -0
  420. package/src/store/primitive-predicates.test.ts +48 -5
  421. package/src/store/primitive-predicates.ts +7 -2
  422. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  423. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  424. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  425. package/src/store/runtime-clients/thread-list-runtime-client.ts +1 -0
  426. package/src/store/runtime-clients/thread-runtime-client.ts +89 -11
  427. package/src/store/scope-registration.ts +2 -0
  428. package/src/store/scopes/composer.ts +16 -4
  429. package/src/store/scopes/message.ts +14 -0
  430. package/src/store/scopes/task.ts +34 -0
  431. package/src/store/scopes/thread-list-item.ts +5 -2
  432. package/src/store/scopes/thread.ts +20 -0
  433. package/src/store/scopes/threads.ts +1 -0
  434. package/src/store/thread-tasks.test.tsx +209 -0
  435. package/src/subscribable/subscribable.test.ts +156 -1
  436. package/src/subscribable/subscribable.ts +25 -15
  437. package/src/tests/MessageRepository.test.ts +117 -5
  438. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  439. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  440. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +47 -0
  441. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  442. package/src/tests/attachment-adapters.test.ts +14 -0
  443. package/src/tests/auiV0Encode.test.ts +300 -0
  444. package/src/tests/base-composer-runtime-core-send.test.ts +51 -0
  445. package/src/tests/base-composer-runtime-core.test.ts +353 -2
  446. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  447. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +178 -18
  448. package/src/tests/external-thread-attachments.test.tsx +208 -0
  449. package/src/tests/external-thread-parity.test.tsx +57 -0
  450. package/src/tests/groupParts.test.ts +71 -0
  451. package/src/tests/message-queue.test.ts +253 -0
  452. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  453. package/src/tests/remote-thread-list-reactive-threadId.test.ts +51 -0
  454. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  455. package/src/tests/thread-switch-events.test.tsx +78 -0
  456. package/src/tests/tools-scope-migration.test.tsx +28 -0
  457. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +43 -0
  458. package/src/types/attachment.ts +4 -0
  459. package/src/types/index.ts +1 -0
  460. package/src/types/message.ts +8 -0
  461. package/src/utils/abortable-iterable.test.ts +328 -0
  462. package/src/utils/abortable-iterable.ts +104 -0
  463. package/src/utils/composite-context-provider.test.ts +25 -0
  464. package/src/utils/composite-context-provider.ts +23 -2
  465. package/src/utils/getGroupStatus.test.ts +70 -4
  466. package/src/utils/getGroupStatus.ts +35 -12
  467. package/src/utils/record.test.ts +48 -0
  468. package/src/utils/record.ts +11 -0
  469. package/src/utils/wire-media.test.ts +10 -0
  470. package/src/utils/wire-media.ts +6 -2
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { ThreadMessage } from "../../types/message";
3
+ import { ExportedMessageRepository } from "./message-repository";
4
+
5
+ const nestedRunningAssistant: ThreadMessage = {
6
+ id: "nested-assistant",
7
+ createdAt: new Date(0),
8
+ role: "assistant",
9
+ content: [],
10
+ status: { type: "running" },
11
+ metadata: {
12
+ unstable_state: {},
13
+ unstable_annotations: [],
14
+ unstable_data: [],
15
+ steps: [],
16
+ custom: {},
17
+ },
18
+ };
19
+
20
+ const delegating = {
21
+ id: "assistant-1",
22
+ role: "assistant" as const,
23
+ content: [
24
+ {
25
+ type: "tool-call" as const,
26
+ toolCallId: "delegate-1",
27
+ toolName: "delegate",
28
+ args: {},
29
+ argsText: "",
30
+ messages: [nestedRunningAssistant],
31
+ },
32
+ ],
33
+ };
34
+
35
+ describe("ExportedMessageRepository", () => {
36
+ it("imports a message saved mid-delegation as pending, not running", () => {
37
+ const fromArray = ExportedMessageRepository.fromArray([delegating]);
38
+ const fromBranchable = ExportedMessageRepository.fromBranchableArray([
39
+ { message: delegating, parentId: null },
40
+ ]);
41
+
42
+ for (const repository of [fromArray, fromBranchable]) {
43
+ expect(repository.messages[0]?.message.status).toMatchObject({
44
+ type: "requires-action",
45
+ reason: "tool-calls",
46
+ });
47
+ }
48
+ });
49
+ });
@@ -2,7 +2,7 @@ import type { ThreadMessage } from "../../types/message";
2
2
  import type { RunConfig } from "../../types/message";
3
3
  import { generateId } from "../../utils/id";
4
4
  import type { ThreadMessageLike } from "./thread-message-like";
5
- import { getContentAutoStatus } from "./auto-status";
5
+ import { getRepositoryContentAutoStatus } from "./auto-status";
6
6
  import { fromThreadMessageLike } from "./thread-message-like";
7
7
 
8
8
  export type ExportedMessageRepositoryItem = {
@@ -28,7 +28,7 @@ export const ExportedMessageRepository = {
28
28
  fromThreadMessageLike(
29
29
  m,
30
30
  generateId(),
31
- getContentAutoStatus(m.content, false, false),
31
+ getRepositoryContentAutoStatus(m.content),
32
32
  ),
33
33
  );
34
34
 
@@ -62,7 +62,7 @@ export const ExportedMessageRepository = {
62
62
  message: fromThreadMessageLike(
63
63
  message,
64
64
  message.id,
65
- getContentAutoStatus(message.content, false, false),
65
+ getRepositoryContentAutoStatus(message.content),
66
66
  ),
67
67
  };
68
68
  }),
@@ -134,6 +134,16 @@ export class MessageRepository {
134
134
  }
135
135
  }
136
136
 
137
+ private selectPathTo(message: RepositoryMessage) {
138
+ for (
139
+ let current: RepositoryMessage | null = message;
140
+ current;
141
+ current = current.prev
142
+ ) {
143
+ (current.prev ?? this.root).next = current;
144
+ }
145
+ }
146
+
137
147
  private performOp(
138
148
  newParent: RepositoryMessage | null,
139
149
  child: RepositoryMessage,
@@ -182,12 +192,17 @@ export class MessageRepository {
182
192
  child.current.id,
183
193
  ];
184
194
 
185
- if (findHead(child) === this.head || newParentOrRoot.next === null) {
195
+ child.prev = newParent;
196
+
197
+ if (findHead(child) === this.head) {
198
+ this.selectPathTo(child);
199
+ } else if (newParentOrRoot.next === null) {
186
200
  newParentOrRoot.next = child;
201
+ if (this.head === newParentOrRoot) {
202
+ this.head = findHead(child);
203
+ }
187
204
  }
188
205
 
189
- child.prev = newParent;
190
-
191
206
  const newLevel = newParent ? newParent.level + 1 : 0;
192
207
  this.updateLevels(child, newLevel);
193
208
  }
@@ -381,8 +396,7 @@ export class MessageRepository {
381
396
  );
382
397
 
383
398
  const previousHead = this.head;
384
- const prevOrRoot = message.prev ?? this.root;
385
- prevOrRoot.next = message;
399
+ this.selectPathTo(message);
386
400
 
387
401
  this.head = findHead(message);
388
402
 
@@ -423,17 +437,7 @@ export class MessageRepository {
423
437
  }
424
438
 
425
439
  this.head = message;
426
- for (
427
- let current: RepositoryMessage | null = message;
428
- current;
429
- current = current.prev
430
- ) {
431
- if (current.prev) {
432
- current.prev.next = current;
433
- } else {
434
- this.root.next = current;
435
- }
436
- }
440
+ this.selectPathTo(message);
437
441
 
438
442
  this.evictOffBranchOptimisticMessages(previousHead, this.head);
439
443
 
@@ -456,7 +460,15 @@ export class MessageRepository {
456
460
  // Optimistic messages are ephemeral and never persisted. A persisted child
457
461
  // of an optimistic node is re-parented onto its nearest persisted ancestor
458
462
  // so the exported tree never references a skipped id.
459
- for (const [, message] of this.messages) {
463
+ // Import and external-state conversion require parents before children, so
464
+ // the tree is walked in pre-order rather than iterated in insertion order.
465
+ const pending = [...this.root.children].reverse();
466
+ while (pending.length > 0) {
467
+ const message = this.messages.get(pending.pop()!);
468
+ if (!message) continue;
469
+ for (let i = message.children.length - 1; i >= 0; i--) {
470
+ pending.push(message.children[i]!);
471
+ }
460
472
  if (message.current.metadata?.isOptimistic) continue;
461
473
  let prev = message.prev;
462
474
  while (prev && prev.current.metadata?.isOptimistic) {
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { fromThreadMessageLike } from "./thread-message-like";
3
+
4
+ describe("fromThreadMessageLike", () => {
5
+ it("preserves modality on user and assistant messages", () => {
6
+ const user = fromThreadMessageLike(
7
+ {
8
+ role: "user",
9
+ content: "Hello",
10
+ metadata: { modality: "voice" },
11
+ },
12
+ "user-id",
13
+ { type: "complete", reason: "unknown" },
14
+ );
15
+ const assistant = fromThreadMessageLike(
16
+ {
17
+ role: "assistant",
18
+ content: "Hi",
19
+ metadata: { modality: "voice" },
20
+ },
21
+ "assistant-id",
22
+ { type: "complete", reason: "unknown" },
23
+ );
24
+
25
+ expect(user.metadata.modality).toBe("voice");
26
+ expect(assistant.metadata.modality).toBe("voice");
27
+ });
28
+
29
+ it("leaves modality absent when the input has none", () => {
30
+ const message = fromThreadMessageLike(
31
+ { role: "user", content: "Hello" },
32
+ "user-id",
33
+ { type: "complete", reason: "unknown" },
34
+ );
35
+
36
+ expect(message.metadata).not.toHaveProperty("modality");
37
+ });
38
+
39
+ it("ignores modality on system messages", () => {
40
+ const message = fromThreadMessageLike(
41
+ {
42
+ role: "system",
43
+ content: "Instructions",
44
+ metadata: { modality: "voice" },
45
+ },
46
+ "system-id",
47
+ { type: "complete", reason: "unknown" },
48
+ );
49
+
50
+ expect(message.metadata).not.toHaveProperty("modality");
51
+ });
52
+ });
@@ -20,6 +20,7 @@ import type {
20
20
  } from "../../types/message";
21
21
  import type { CompleteAttachment } from "../../types/attachment";
22
22
  import type {
23
+ MessageModality,
23
24
  MessageTiming,
24
25
  PartProviderMetadata,
25
26
  TextMessagePart,
@@ -88,6 +89,7 @@ export type ThreadMessageLike = {
88
89
  readonly timing?: MessageTiming | undefined;
89
90
  readonly submittedFeedback?: { readonly type: "positive" | "negative" };
90
91
  readonly isOptimistic?: boolean | undefined;
92
+ readonly modality?: MessageModality | undefined;
91
93
  readonly custom?: Record<string, unknown> | undefined;
92
94
  }
93
95
  | undefined;
@@ -179,7 +181,7 @@ export const fromThreadMessageLike = (
179
181
  const { parentId, messages, ...basePart } = part;
180
182
  const commonProps = {
181
183
  ...basePart,
182
- toolCallId: part.toolCallId ?? `tool-${generateId()}`,
184
+ toolCallId: part.toolCallId || `tool-${generateId()}`,
183
185
  ...(parentId !== undefined && { parentId }),
184
186
  ...(messages !== undefined && { messages }),
185
187
  };
@@ -223,6 +225,7 @@ export const fromThreadMessageLike = (
223
225
  submittedFeedback: metadata.submittedFeedback,
224
226
  }),
225
227
  ...(metadata?.isOptimistic && { isOptimistic: true }),
228
+ ...(metadata?.modality && { modality: metadata.modality }),
226
229
  },
227
230
  } satisfies ThreadAssistantMessage;
228
231
 
@@ -263,6 +266,7 @@ export const fromThreadMessageLike = (
263
266
  metadata: {
264
267
  custom: metadata?.custom ?? {},
265
268
  ...(metadata?.isOptimistic && { isOptimistic: true }),
269
+ ...(metadata?.modality && { modality: metadata.modality }),
266
270
  },
267
271
  } satisfies ThreadUserMessage;
268
272
 
@@ -6,6 +6,7 @@ import type {
6
6
  import type { ExternalStoreThreadListAdapter } from "./external-store-adapter";
7
7
  import { invalidateThreadRuntime } from "../../runtime/utils/thread-runtime-lifecycle";
8
8
  import { BaseSubscribable } from "../../subscribable/subscribable";
9
+ import { nullProtoRecord } from "../../utils/record";
9
10
 
10
11
  export type ExternalStoreThreadFactory = () => ExternalStoreThreadRuntimeCore;
11
12
 
@@ -19,9 +20,11 @@ const DEFAULT_THREAD = Object.freeze({
19
20
  status: "regular",
20
21
  });
21
22
  const RESOLVED_PROMISE = Promise.resolve();
22
- const DEFAULT_THREAD_DATA = Object.freeze({
23
- [DEFAULT_THREAD_ID]: DEFAULT_THREAD,
24
- });
23
+ const DEFAULT_THREAD_DATA = Object.freeze(
24
+ nullProtoRecord<ThreadListItemCoreState>({
25
+ [DEFAULT_THREAD_ID]: DEFAULT_THREAD,
26
+ }),
27
+ );
25
28
 
26
29
  export class ExternalStoreThreadListRuntimeCore
27
30
  extends BaseSubscribable
@@ -84,7 +87,9 @@ export class ExternalStoreThreadListRuntimeCore
84
87
  }
85
88
 
86
89
  public getItemById(threadId: string) {
87
- return this._threadData[threadId];
90
+ return Object.hasOwn(this._threadData, threadId)
91
+ ? this._threadData[threadId]
92
+ : undefined;
88
93
  }
89
94
 
90
95
  public __internal_setAdapter(
@@ -105,6 +110,7 @@ export class ExternalStoreThreadListRuntimeCore
105
110
 
106
111
  if (
107
112
  !initialLoad &&
113
+ (previousAdapter.isLoading ?? false) === (adapter.isLoading ?? false) &&
108
114
  previousThreadId === newThreadId &&
109
115
  previousThreads === newThreads &&
110
116
  previousArchivedThreads === newArchivedThreads
@@ -117,9 +123,9 @@ export class ExternalStoreThreadListRuntimeCore
117
123
  previousArchivedThreads !== newArchivedThreads ||
118
124
  previousThreadId !== newThreadId
119
125
  ) {
120
- this._threadData = {
121
- ...DEFAULT_THREAD_DATA,
122
- ...Object.fromEntries(
126
+ this._threadData = nullProtoRecord(
127
+ DEFAULT_THREAD_DATA,
128
+ Object.fromEntries(
123
129
  adapter.threads?.map((t) => [
124
130
  t.id,
125
131
  {
@@ -130,7 +136,7 @@ export class ExternalStoreThreadListRuntimeCore
130
136
  },
131
137
  ]) ?? [],
132
138
  ),
133
- ...Object.fromEntries(
139
+ Object.fromEntries(
134
140
  adapter.archivedThreads?.map((t) => [
135
141
  t.id,
136
142
  {
@@ -141,7 +147,7 @@ export class ExternalStoreThreadListRuntimeCore
141
147
  },
142
148
  ]) ?? [],
143
149
  ),
144
- };
150
+ );
145
151
  }
146
152
 
147
153
  if (previousThreads !== newThreads) {
@@ -160,16 +166,15 @@ export class ExternalStoreThreadListRuntimeCore
160
166
  this._mainThread = this.threadFactory();
161
167
  }
162
168
 
163
- if (!this._threadData[this._mainThreadId]) {
164
- this._threadData = {
165
- ...this._threadData,
169
+ if (!Object.hasOwn(this._threadData, this._mainThreadId)) {
170
+ this._threadData = nullProtoRecord(this._threadData, {
166
171
  [this._mainThreadId]: {
167
172
  id: this._mainThreadId,
168
173
  remoteId: undefined,
169
174
  externalId: undefined,
170
175
  status: "regular",
171
176
  },
172
- };
177
+ });
173
178
  }
174
179
 
175
180
  this._notifySubscribers();
@@ -637,6 +637,8 @@ export class ExternalStoreThreadRuntimeCore
637
637
  }
638
638
 
639
639
  public async append(rawMessage: AppendMessage): Promise<void> {
640
+ if (this._isVoiceMessage(rawMessage.sourceId))
641
+ throw new Error("Voice transcript messages cannot be edited");
640
642
  // sourceId marks an edit send; the parent may coincide with the head
641
643
  // after a resync (e.g. cancelRun dropped the edited message).
642
644
  const isEdit =
@@ -786,6 +788,8 @@ export class ExternalStoreThreadRuntimeCore
786
788
  public async startRun(config: StartRunConfig): Promise<void> {
787
789
  if (!this._store.onReload)
788
790
  throw new Error("Runtime does not support reloading messages.");
791
+ if (this._isVoiceMessage(config.sourceId))
792
+ throw new Error("Voice transcript messages cannot be reloaded");
789
793
 
790
794
  this._pendingDeleteEvictions.clear();
791
795
 
@@ -800,6 +804,8 @@ export class ExternalStoreThreadRuntimeCore
800
804
  public async resumeRun(config: ResumeRunConfig): Promise<void> {
801
805
  if (!this._store.onResume)
802
806
  throw new Error("Runtime does not support resuming runs.");
807
+ if (this._isVoiceMessage(config.sourceId))
808
+ throw new Error("Voice transcript messages cannot be reloaded");
803
809
 
804
810
  await this._store.onResume(config);
805
811
  }
@@ -961,8 +967,32 @@ export class ExternalStoreThreadRuntimeCore
961
967
  ): Promise<void> {
962
968
  if (!this._store.onRespondToToolApproval)
963
969
  throw new Error("Runtime does not support tool approvals.");
970
+ const message = this.messages.findLast(
971
+ (candidate) =>
972
+ candidate.role === "assistant" &&
973
+ candidate.content.some(
974
+ (part) =>
975
+ part.type === "tool-call" &&
976
+ part.approval?.id === options.approvalId,
977
+ ),
978
+ );
979
+ const toolCall = message?.content.find(
980
+ (part) =>
981
+ part.type === "tool-call" && part.approval?.id === options.approvalId,
982
+ );
964
983
  try {
965
- return Promise.resolve(this._store.onRespondToToolApproval(options));
984
+ return Promise.resolve(this._store.onRespondToToolApproval(options)).then(
985
+ () => {
986
+ if (message && toolCall?.type === "tool-call") {
987
+ this._notifyToolApprovalAnswered(
988
+ message.id,
989
+ toolCall.toolCallId,
990
+ toolCall.toolName,
991
+ options.approved,
992
+ );
993
+ }
994
+ },
995
+ );
966
996
  } catch (error) {
967
997
  return Promise.reject(error);
968
998
  }
@@ -31,6 +31,7 @@ export type {
31
31
  export {
32
32
  createThreadMappingId,
33
33
  getThreadData,
34
+ promoteNewThreadReducer,
34
35
  updateStatusReducer,
35
36
  } from "./remote-thread-list/remote-thread-state";
36
37
  export type {
@@ -427,6 +427,27 @@ describe("LocalThreadRuntimeCore state", () => {
427
427
  });
428
428
 
429
429
  describe("LocalThreadRuntimeCore tool approvals", () => {
430
+ it("emits a decision with its message and tool call IDs", async () => {
431
+ const { thread } = createApprovalThread(
432
+ toolCallResult("send_email", { id: "a1" }),
433
+ );
434
+ const answered = vi.fn();
435
+ thread.unstable_on("toolApprovalAnswered", answered);
436
+
437
+ await thread.append(userMessage("send an email"));
438
+ await flush();
439
+ const messageId = thread.messages.at(-1)?.id;
440
+
441
+ await thread.respondToToolApproval({ approvalId: "a1", approved: false });
442
+
443
+ expect(answered).toHaveBeenCalledWith({
444
+ messageId,
445
+ toolCallId: "call-send_email",
446
+ toolName: "send_email",
447
+ approved: false,
448
+ });
449
+ });
450
+
430
451
  it("pauses the run while an approval is pending, even for unlisted tools", async () => {
431
452
  const { thread, runs } = createApprovalThread(
432
453
  toolCallResult("deploy", { id: "a1" }),
@@ -342,6 +342,8 @@ export class LocalThreadRuntimeCore
342
342
  }
343
343
 
344
344
  public async append(message: AppendMessage): Promise<void> {
345
+ if (this._isVoiceMessage(message.sourceId))
346
+ throw new Error("Voice transcript messages cannot be edited");
345
347
  const isTail = message.parentId === (this.messages.at(-1)?.id ?? null);
346
348
  const willRun = message.startRun ?? message.role === "user";
347
349
  if (this._queue && willRun && isTail) {
@@ -488,10 +490,12 @@ export class LocalThreadRuntimeCore
488
490
  }
489
491
 
490
492
  public async startRun(
491
- { parentId, runConfig }: StartRunConfig,
493
+ { parentId, sourceId, runConfig }: StartRunConfig,
492
494
  runCallback?: ChatModelAdapter["run"],
493
495
  ): Promise<void> {
494
496
  this.ensureInitialized();
497
+ if (this._isVoiceMessage(sourceId))
498
+ throw new Error("Voice transcript messages cannot be reloaded");
495
499
 
496
500
  // add assistant message
497
501
  const id = generateId();
@@ -961,6 +965,12 @@ export class LocalThreadRuntimeCore
961
965
  const { parentId } = this.repository.getMessage(message.id);
962
966
  this.repository.addOrUpdateMessage(parentId, message);
963
967
  this._notifySubscribers();
968
+ this._notifyToolApprovalAnswered(
969
+ message.id,
970
+ target.toolCallId,
971
+ target.toolName,
972
+ approved,
973
+ );
964
974
 
965
975
  if (
966
976
  this.repository.headId === message.id &&
@@ -3,6 +3,9 @@ import {
3
3
  classifyThreads,
4
4
  createEmptyRemoteThreadState,
5
5
  createThreadMappingId,
6
+ getThreadData,
7
+ promoteNewThreadReducer,
8
+ reconcileInitializedThread,
6
9
  seedNewThread,
7
10
  updateStatusReducer,
8
11
  } from "./remote-thread-state";
@@ -13,12 +16,16 @@ import type {
13
16
 
14
17
  const initializedDraft = () => {
15
18
  const seeded = seedNewThread(createEmptyRemoteThreadState());
16
- const regular = updateStatusReducer(seeded.state, seeded.id, "regular");
17
- const mappingId = regular.threadIdMap[seeded.id]!;
18
19
  const initializeTask = Promise.resolve({
19
20
  remoteId: "remote-1",
20
21
  externalId: "remote-1",
21
22
  });
23
+ const regular = promoteNewThreadReducer(
24
+ seeded.state,
25
+ seeded.id,
26
+ initializeTask,
27
+ );
28
+ const mappingId = regular.threadIdMap[seeded.id]!;
22
29
  return {
23
30
  id: seeded.id,
24
31
  mappingId,
@@ -54,6 +61,7 @@ describe("remote thread state", () => {
54
61
  it("creates an empty state", () => {
55
62
  expect(createEmptyRemoteThreadState()).toEqual({
56
63
  isLoading: true,
64
+ loadError: undefined,
57
65
  isLoadingMore: false,
58
66
  cursor: undefined,
59
67
  newThreadId: undefined,
@@ -80,6 +88,39 @@ describe("remote thread state", () => {
80
88
  expect(Object.keys(second.state.threadData)).toEqual([first.id, second.id]);
81
89
  });
82
90
 
91
+ it("carries the initialization task onto the promoted slot", () => {
92
+ const seeded = seedNewThread(createEmptyRemoteThreadState());
93
+ const initializeTask = Promise.resolve({
94
+ remoteId: "remote-1",
95
+ externalId: "external-1",
96
+ });
97
+
98
+ const promoted = promoteNewThreadReducer(
99
+ seeded.state,
100
+ seeded.id,
101
+ initializeTask,
102
+ );
103
+ const data = getThreadData(promoted, seeded.id);
104
+
105
+ expect(data?.status).toBe("regular");
106
+ expect(data?.status === "new" ? undefined : data?.initializeTask).toBe(
107
+ initializeTask,
108
+ );
109
+ expect(promoted.newThreadId).toBeUndefined();
110
+ expect(promoted.threadIds).toEqual([seeded.id]);
111
+ });
112
+
113
+ it.each(["regular", "archived"] as const)(
114
+ "leaves a new thread untouched when moved to %s without an initialization task",
115
+ (newStatus) => {
116
+ const seeded = seedNewThread(createEmptyRemoteThreadState());
117
+
118
+ expect(updateStatusReducer(seeded.state, seeded.id, newStatus)).toBe(
119
+ seeded.state,
120
+ );
121
+ },
122
+ );
123
+
83
124
  it("refreshes the local slot when a listed thread already has a mapping", () => {
84
125
  const draft = initializedDraft();
85
126
 
@@ -131,6 +172,91 @@ describe("remote thread state", () => {
131
172
  expect(listed.threadData[createThreadMappingId("a")]?.title).toBe("second");
132
173
  });
133
174
 
175
+ it("retains a listed external id when that slot survives reconciliation", async () => {
176
+ const seeded = seedNewThread(createEmptyRemoteThreadState());
177
+ const regular = promoteNewThreadReducer(
178
+ seeded.state,
179
+ seeded.id,
180
+ Promise.resolve({ remoteId: "remote-1", externalId: "external-1" }),
181
+ );
182
+ const classified = classifyThreads(
183
+ [
184
+ {
185
+ status: "regular",
186
+ remoteId: "remote-1",
187
+ externalId: "external-1",
188
+ },
189
+ ],
190
+ {
191
+ threadIds: [...regular.threadIds],
192
+ archivedThreadIds: [...regular.archivedThreadIds],
193
+ threadIdMap: { ...regular.threadIdMap },
194
+ threadData: { ...regular.threadData },
195
+ },
196
+ );
197
+ const state = { ...regular, ...classified };
198
+
199
+ const reconciled = reconcileInitializedThread(
200
+ state,
201
+ seeded.id,
202
+ "remote-1",
203
+ undefined,
204
+ "remote-1",
205
+ );
206
+ const survivor = reconciled.state.threadData[reconciled.survivorMappingId]!;
207
+
208
+ expect(survivor.externalId).toBe("external-1");
209
+ if (survivor.status === "new") throw new Error("Expected initialized slot");
210
+ await expect(survivor.initializeTask).resolves.toEqual({
211
+ remoteId: "remote-1",
212
+ externalId: "external-1",
213
+ });
214
+ });
215
+
216
+ it.each(["__proto__", "constructor", "toString"])(
217
+ "handles a prototype-named remote id %s",
218
+ (remoteId) => {
219
+ const classified = classifyThreads(
220
+ [
221
+ {
222
+ status: "regular",
223
+ remoteId,
224
+ externalId: undefined,
225
+ title: `title-${remoteId}`,
226
+ },
227
+ {
228
+ status: "regular",
229
+ remoteId: "ok",
230
+ externalId: undefined,
231
+ title: "title-ok",
232
+ },
233
+ ],
234
+ {
235
+ threadIds: [],
236
+ archivedThreadIds: [],
237
+ threadIdMap: {},
238
+ threadData: {},
239
+ },
240
+ );
241
+ const listed: RemoteThreadState = {
242
+ ...createEmptyRemoteThreadState(),
243
+ threadIds: classified.threadIds,
244
+ archivedThreadIds: classified.archivedThreadIds,
245
+ threadIdMap: classified.threadIdMap,
246
+ threadData: classified.threadData,
247
+ };
248
+
249
+ expect(Object.keys(listed.threadIdMap)).toEqual([remoteId, "ok"]);
250
+ expect(typeof listed.threadIdMap[remoteId]).toBe("string");
251
+ expect(Object.keys(listed.threadData)).toEqual([remoteId, "ok"]);
252
+
253
+ const deleted = updateStatusReducer(listed, remoteId, "deleted");
254
+ expect(Object.keys(deleted.threadIdMap)).toEqual(["ok"]);
255
+ expect(Object.keys(deleted.threadData)).toEqual(["ok"]);
256
+ expect(deleted.threadIds).toEqual(["ok"]);
257
+ },
258
+ );
259
+
134
260
  it("deletes every alias of an identity, by either id", () => {
135
261
  for (const target of ["remote-1", "local"] as const) {
136
262
  const draft = initializedDraft();