@assistant-ui/core 0.3.19 → 0.3.21

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 (934) hide show
  1. package/dist/adapters/attachment.d.ts +50 -43
  2. package/dist/adapters/attachment.d.ts.map +1 -1
  3. package/dist/adapters/attachment.js +52 -21
  4. package/dist/adapters/attachment.js.map +1 -1
  5. package/dist/adapters/directive-formatter.d.ts +2 -5
  6. package/dist/adapters/directive-formatter.d.ts.map +1 -1
  7. package/dist/adapters/feedback.d.ts +7 -8
  8. package/dist/adapters/feedback.d.ts.map +1 -1
  9. package/dist/adapters/index.d.ts +14 -9
  10. package/dist/adapters/index.d.ts.map +1 -0
  11. package/dist/adapters/index.js +1 -1
  12. package/dist/adapters/speech.d.ts +63 -65
  13. package/dist/adapters/speech.d.ts.map +1 -1
  14. package/dist/adapters/speech.js +16 -2
  15. package/dist/adapters/speech.js.map +1 -1
  16. package/dist/adapters/suggestion.d.ts +19 -22
  17. package/dist/adapters/suggestion.d.ts.map +1 -1
  18. package/dist/adapters/thread-history.d.ts +60 -58
  19. package/dist/adapters/thread-history.d.ts.map +1 -1
  20. package/dist/adapters/trigger.d.ts +8 -11
  21. package/dist/adapters/trigger.d.ts.map +1 -1
  22. package/dist/adapters/voice.d.ts +50 -45
  23. package/dist/adapters/voice.d.ts.map +1 -1
  24. package/dist/adapters/voice.js +17 -3
  25. package/dist/adapters/voice.js.map +1 -1
  26. package/dist/index.d.ts +70 -56
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +6 -6
  29. package/dist/index.js.map +1 -1
  30. package/dist/internal/duplicate-detection.d.ts +1 -4
  31. package/dist/internal/duplicate-detection.d.ts.map +1 -1
  32. package/dist/internal.d.ts +27 -58
  33. package/dist/internal.d.ts.map +1 -0
  34. package/dist/internal.js +8 -6
  35. package/dist/model-context/frame/host.d.ts +20 -23
  36. package/dist/model-context/frame/host.d.ts.map +1 -1
  37. package/dist/model-context/frame/host.js +3 -1
  38. package/dist/model-context/frame/host.js.map +1 -1
  39. package/dist/model-context/frame/provider.d.ts +28 -30
  40. package/dist/model-context/frame/provider.d.ts.map +1 -1
  41. package/dist/model-context/frame/provider.js +6 -0
  42. package/dist/model-context/frame/provider.js.map +1 -1
  43. package/dist/model-context/frame/types.d.ts +24 -27
  44. package/dist/model-context/frame/types.d.ts.map +1 -1
  45. package/dist/model-context/frame/validate.d.ts +3 -0
  46. package/dist/model-context/frame/validate.d.ts.map +1 -0
  47. package/dist/model-context/frame/validate.js +26 -0
  48. package/dist/model-context/frame/validate.js.map +1 -0
  49. package/dist/model-context/interactable-composer-metadata.d.ts +51 -39
  50. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  51. package/dist/model-context/interactable-composer-metadata.js +30 -6
  52. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  53. package/dist/model-context/registry-handles.d.ts +9 -12
  54. package/dist/model-context/registry-handles.d.ts.map +1 -1
  55. package/dist/model-context/registry.d.ts +15 -18
  56. package/dist/model-context/registry.d.ts.map +1 -1
  57. package/dist/model-context/tool-call-text.d.ts +15 -17
  58. package/dist/model-context/tool-call-text.d.ts.map +1 -1
  59. package/dist/model-context/tool.d.ts +12 -14
  60. package/dist/model-context/tool.d.ts.map +1 -1
  61. package/dist/model-context/types.d.ts +40 -43
  62. package/dist/model-context/types.d.ts.map +1 -1
  63. package/dist/react/AssistantProvider.d.ts +9 -12
  64. package/dist/react/AssistantProvider.d.ts.map +1 -1
  65. package/dist/react/AssistantRuntimeProvider.d.ts +23 -26
  66. package/dist/react/AssistantRuntimeProvider.d.ts.map +1 -1
  67. package/dist/react/RuntimeAdapter.d.ts +4 -8
  68. package/dist/react/RuntimeAdapter.d.ts.map +1 -1
  69. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts +28 -23
  70. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  71. package/dist/react/adapters/LocalStorageThreadListAdapter.js +77 -58
  72. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  73. package/dist/react/adapters/TitleGenerationAdapter.d.ts +4 -7
  74. package/dist/react/adapters/TitleGenerationAdapter.d.ts.map +1 -1
  75. package/dist/react/client/AdaptedRemoteThread.d.ts +6 -9
  76. package/dist/react/client/AdaptedRemoteThread.d.ts.map +1 -1
  77. package/dist/react/client/DataRenderers.d.ts +2 -5
  78. package/dist/react/client/DataRenderers.d.ts.map +1 -1
  79. package/dist/react/client/InMemoryThreadList.d.ts +9 -12
  80. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  81. package/dist/react/client/Interactables.d.ts +3 -6
  82. package/dist/react/client/Interactables.d.ts.map +1 -1
  83. package/dist/react/client/Interactables.js +306 -101
  84. package/dist/react/client/Interactables.js.map +1 -1
  85. package/dist/react/client/RemoteThreadList.d.ts +34 -37
  86. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  87. package/dist/react/client/RemoteThreadList.js +2 -1
  88. package/dist/react/client/RemoteThreadList.js.map +1 -1
  89. package/dist/react/client/Tools.d.ts +10 -12
  90. package/dist/react/client/Tools.d.ts.map +1 -1
  91. package/dist/react/client/Tools.js +4 -8
  92. package/dist/react/client/Tools.js.map +1 -1
  93. package/dist/react/client/interactable-model-context.d.ts +9 -11
  94. package/dist/react/client/interactable-model-context.d.ts.map +1 -1
  95. package/dist/react/client/interactable-model-context.js +32 -22
  96. package/dist/react/client/interactable-model-context.js.map +1 -1
  97. package/dist/react/index.d.ts +160 -122
  98. package/dist/react/index.d.ts.map +1 -0
  99. package/dist/react/index.js +5 -5
  100. package/dist/react/interactables-legacy/Interactables.d.ts +2 -5
  101. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  102. package/dist/react/interactables-legacy/Interactables.js +15 -8
  103. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  104. package/dist/react/interactables-legacy/interactable-model-context.d.ts +6 -9
  105. package/dist/react/interactables-legacy/interactable-model-context.d.ts.map +1 -1
  106. package/dist/react/interactables-legacy/interactable-model-context.js +13 -14
  107. package/dist/react/interactables-legacy/interactable-model-context.js.map +1 -1
  108. package/dist/react/interactables-legacy/scopes.d.ts +42 -46
  109. package/dist/react/interactables-legacy/scopes.d.ts.map +1 -1
  110. package/dist/react/interactables-legacy/useAssistantInteractable.d.ts +8 -11
  111. package/dist/react/interactables-legacy/useAssistantInteractable.d.ts.map +1 -1
  112. package/dist/react/interactables-legacy/useInteractableState.d.ts +7 -9
  113. package/dist/react/interactables-legacy/useInteractableState.d.ts.map +1 -1
  114. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +23 -14
  115. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  116. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +54 -25
  117. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  118. package/dist/react/model-context/define-mcp-toolkit.d.ts +15 -18
  119. package/dist/react/model-context/define-mcp-toolkit.d.ts.map +1 -1
  120. package/dist/react/model-context/define-toolkit.d.ts +13 -6
  121. package/dist/react/model-context/define-toolkit.d.ts.map +1 -1
  122. package/dist/react/model-context/external-tool.d.ts +1 -4
  123. package/dist/react/model-context/external-tool.d.ts.map +1 -1
  124. package/dist/react/model-context/human-tool.d.ts +3 -6
  125. package/dist/react/model-context/human-tool.d.ts.map +1 -1
  126. package/dist/react/model-context/interactableTool.d.ts +26 -29
  127. package/dist/react/model-context/interactableTool.d.ts.map +1 -1
  128. package/dist/react/model-context/makeAssistantDataUI.d.ts +6 -9
  129. package/dist/react/model-context/makeAssistantDataUI.d.ts.map +1 -1
  130. package/dist/react/model-context/makeAssistantTool.d.ts +6 -9
  131. package/dist/react/model-context/makeAssistantTool.d.ts.map +1 -1
  132. package/dist/react/model-context/makeAssistantToolUI.d.ts +6 -9
  133. package/dist/react/model-context/makeAssistantToolUI.d.ts.map +1 -1
  134. package/dist/react/model-context/provider-tool.d.ts +5 -7
  135. package/dist/react/model-context/provider-tool.d.ts.map +1 -1
  136. package/dist/react/model-context/stub-tool.d.ts +1 -4
  137. package/dist/react/model-context/stub-tool.d.ts.map +1 -1
  138. package/dist/react/model-context/toolbox.d.ts +44 -32
  139. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  140. package/dist/react/model-context/useAssistantContext.d.ts +3 -6
  141. package/dist/react/model-context/useAssistantContext.d.ts.map +1 -1
  142. package/dist/react/model-context/useAssistantDataUI.d.ts +7 -10
  143. package/dist/react/model-context/useAssistantDataUI.d.ts.map +1 -1
  144. package/dist/react/model-context/useAssistantInstructions.d.ts +3 -6
  145. package/dist/react/model-context/useAssistantInstructions.d.ts.map +1 -1
  146. package/dist/react/model-context/useAssistantTool.d.ts +9 -13
  147. package/dist/react/model-context/useAssistantTool.d.ts.map +1 -1
  148. package/dist/react/model-context/useAssistantToolUI.d.ts +13 -16
  149. package/dist/react/model-context/useAssistantToolUI.d.ts.map +1 -1
  150. package/dist/react/model-context/useAuiToolOverrides.d.ts +3 -5
  151. package/dist/react/model-context/useAuiToolOverrides.d.ts.map +1 -1
  152. package/dist/react/model-context/useInlineRender.d.ts +3 -6
  153. package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
  154. package/dist/react/model-context/useInteractable.d.ts +40 -40
  155. package/dist/react/model-context/useInteractable.d.ts.map +1 -1
  156. package/dist/react/model-context/useInteractableState.d.ts +10 -9
  157. package/dist/react/model-context/useInteractableState.d.ts.map +1 -1
  158. package/dist/react/model-context/useInteractableVersions.d.ts +4 -7
  159. package/dist/react/model-context/useInteractableVersions.d.ts.map +1 -1
  160. package/dist/react/model-context/useToolArgsStatus.d.ts +7 -9
  161. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  162. package/dist/react/primitive-hooks/useActionBarCopy.d.ts +7 -10
  163. package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
  164. package/dist/react/primitive-hooks/useActionBarEdit.d.ts +3 -6
  165. package/dist/react/primitive-hooks/useActionBarEdit.d.ts.map +1 -1
  166. package/dist/react/primitive-hooks/useActionBarFeedback.d.ts +6 -9
  167. package/dist/react/primitive-hooks/useActionBarFeedback.d.ts.map +1 -1
  168. package/dist/react/primitive-hooks/useActionBarReload.d.ts +3 -6
  169. package/dist/react/primitive-hooks/useActionBarReload.d.ts.map +1 -1
  170. package/dist/react/primitive-hooks/useActionBarSpeak.d.ts +3 -6
  171. package/dist/react/primitive-hooks/useActionBarSpeak.d.ts.map +1 -1
  172. package/dist/react/primitive-hooks/useActionBarStopSpeaking.d.ts +3 -6
  173. package/dist/react/primitive-hooks/useActionBarStopSpeaking.d.ts.map +1 -1
  174. package/dist/react/primitive-hooks/useBranchPickerNext.d.ts +3 -6
  175. package/dist/react/primitive-hooks/useBranchPickerNext.d.ts.map +1 -1
  176. package/dist/react/primitive-hooks/useBranchPickerPrevious.d.ts +3 -6
  177. package/dist/react/primitive-hooks/useBranchPickerPrevious.d.ts.map +1 -1
  178. package/dist/react/primitive-hooks/useComposerAddAttachment.d.ts +4 -7
  179. package/dist/react/primitive-hooks/useComposerAddAttachment.d.ts.map +1 -1
  180. package/dist/react/primitive-hooks/useComposerCancel.d.ts +3 -6
  181. package/dist/react/primitive-hooks/useComposerCancel.d.ts.map +1 -1
  182. package/dist/react/primitive-hooks/useComposerDictate.d.ts +3 -6
  183. package/dist/react/primitive-hooks/useComposerDictate.d.ts.map +1 -1
  184. package/dist/react/primitive-hooks/useComposerSend.d.ts +4 -7
  185. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  186. package/dist/react/primitive-hooks/useEditComposerCancel.d.ts +2 -5
  187. package/dist/react/primitive-hooks/useEditComposerCancel.d.ts.map +1 -1
  188. package/dist/react/primitive-hooks/useEditComposerSend.d.ts +3 -6
  189. package/dist/react/primitive-hooks/useEditComposerSend.d.ts.map +1 -1
  190. package/dist/react/primitive-hooks/useMessageBranching.d.ts +5 -8
  191. package/dist/react/primitive-hooks/useMessageBranching.d.ts.map +1 -1
  192. package/dist/react/primitive-hooks/useMessageError.d.ts +1 -4
  193. package/dist/react/primitive-hooks/useMessageError.d.ts.map +1 -1
  194. package/dist/react/primitive-hooks/useMessageReload.d.ts +3 -6
  195. package/dist/react/primitive-hooks/useMessageReload.d.ts.map +1 -1
  196. package/dist/react/primitive-hooks/useSuggestionTrigger.d.ts +7 -10
  197. package/dist/react/primitive-hooks/useSuggestionTrigger.d.ts.map +1 -1
  198. package/dist/react/primitive-hooks/useSuggestionTrigger.js +4 -4
  199. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  200. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts +1 -4
  201. package/dist/react/primitive-hooks/useThreadIsEmpty.d.ts.map +1 -1
  202. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts +1 -4
  203. package/dist/react/primitive-hooks/useThreadIsRunning.d.ts.map +1 -1
  204. package/dist/react/primitive-hooks/useThreadListItemArchive.d.ts +2 -5
  205. package/dist/react/primitive-hooks/useThreadListItemArchive.d.ts.map +1 -1
  206. package/dist/react/primitive-hooks/useThreadListItemDelete.d.ts +2 -5
  207. package/dist/react/primitive-hooks/useThreadListItemDelete.d.ts.map +1 -1
  208. package/dist/react/primitive-hooks/useThreadListItemTrigger.d.ts +2 -5
  209. package/dist/react/primitive-hooks/useThreadListItemTrigger.d.ts.map +1 -1
  210. package/dist/react/primitive-hooks/useThreadListItemUnarchive.d.ts +2 -5
  211. package/dist/react/primitive-hooks/useThreadListItemUnarchive.d.ts.map +1 -1
  212. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts +3 -6
  213. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
  214. package/dist/react/primitive-hooks/useThreadListNew.d.ts +2 -5
  215. package/dist/react/primitive-hooks/useThreadListNew.d.ts.map +1 -1
  216. package/dist/react/primitive-hooks/useThreadMessageIds.d.ts +1 -4
  217. package/dist/react/primitive-hooks/useThreadMessageIds.d.ts.map +1 -1
  218. package/dist/react/primitive-hooks/useThreadMessages.d.ts +2 -5
  219. package/dist/react/primitive-hooks/useThreadMessages.d.ts.map +1 -1
  220. package/dist/react/primitive-hooks/useVoice.d.ts +8 -11
  221. package/dist/react/primitive-hooks/useVoice.d.ts.map +1 -1
  222. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.d.ts +27 -29
  223. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.d.ts.map +1 -1
  224. package/dist/react/primitives/composer/ComposerAttachments.d.ts +26 -28
  225. package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  226. package/dist/react/primitives/composer/ComposerIf.d.ts +13 -13
  227. package/dist/react/primitives/composer/ComposerIf.d.ts.map +1 -1
  228. package/dist/react/primitives/composer/ComposerQueue.d.ts +13 -17
  229. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  230. package/dist/react/primitives/generativeUI/GenerativeUI.d.ts +30 -33
  231. package/dist/react/primitives/generativeUI/GenerativeUI.d.ts.map +1 -1
  232. package/dist/react/primitives/generativeUI/GenerativeUI.js +42 -33
  233. package/dist/react/primitives/generativeUI/GenerativeUI.js.map +1 -1
  234. package/dist/react/primitives/message/MessageAttachments.d.ts +30 -28
  235. package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
  236. package/dist/react/primitives/message/MessageAttachments.js +14 -15
  237. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  238. package/dist/react/primitives/message/MessageGroupedParts.d.ts +122 -120
  239. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  240. package/dist/react/primitives/message/MessageGroupedParts.js +3 -3
  241. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  242. package/dist/react/primitives/message/MessageParts.d.ts +208 -194
  243. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  244. package/dist/react/primitives/message/MessageParts.js +124 -82
  245. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  246. package/dist/react/primitives/message/MessageQuote.d.ts +8 -11
  247. package/dist/react/primitives/message/MessageQuote.d.ts.map +1 -1
  248. package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts +4 -7
  249. package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts.map +1 -1
  250. package/dist/react/primitives/part/PartMessages.d.ts +16 -19
  251. package/dist/react/primitives/part/PartMessages.d.ts.map +1 -1
  252. package/dist/react/primitives/thread/ThreadMessages.d.ts +61 -64
  253. package/dist/react/primitives/thread/ThreadMessages.d.ts.map +1 -1
  254. package/dist/react/primitives/thread/ThreadSuggestions.d.ts +25 -27
  255. package/dist/react/primitives/thread/ThreadSuggestions.d.ts.map +1 -1
  256. package/dist/react/primitives/thread/ThreadSuggestions.js +24 -5
  257. package/dist/react/primitives/thread/ThreadSuggestions.js.map +1 -1
  258. package/dist/react/primitives/threadList/ThreadListItems.d.ts +26 -28
  259. package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  260. package/dist/react/primitives/threadListItem/ThreadListItemTitle.d.ts +6 -9
  261. package/dist/react/primitives/threadListItem/ThreadListItemTitle.d.ts.map +1 -1
  262. package/dist/react/providers/AttachmentByIndexProvider.d.ts +5 -8
  263. package/dist/react/providers/AttachmentByIndexProvider.d.ts.map +1 -1
  264. package/dist/react/providers/ChainOfThoughtByIndicesProvider.d.ts +4 -7
  265. package/dist/react/providers/ChainOfThoughtByIndicesProvider.d.ts.map +1 -1
  266. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.d.ts +3 -6
  267. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.d.ts.map +1 -1
  268. package/dist/react/providers/MessageByIdProvider.d.ts +3 -6
  269. package/dist/react/providers/MessageByIdProvider.d.ts.map +1 -1
  270. package/dist/react/providers/MessageByIndexProvider.d.ts +3 -6
  271. package/dist/react/providers/MessageByIndexProvider.d.ts.map +1 -1
  272. package/dist/react/providers/PartByIndexProvider.d.ts +3 -6
  273. package/dist/react/providers/PartByIndexProvider.d.ts.map +1 -1
  274. package/dist/react/providers/QueueItemByIndexProvider.d.ts +4 -7
  275. package/dist/react/providers/QueueItemByIndexProvider.d.ts.map +1 -1
  276. package/dist/react/providers/ReadonlyThreadProvider.d.ts +7 -10
  277. package/dist/react/providers/ReadonlyThreadProvider.d.ts.map +1 -1
  278. package/dist/react/providers/ReadonlyThreadProvider.js.map +1 -1
  279. package/dist/react/providers/SuggestionByIndexProvider.d.ts +4 -7
  280. package/dist/react/providers/SuggestionByIndexProvider.d.ts.map +1 -1
  281. package/dist/react/providers/TextMessagePartProvider.d.ts +4 -7
  282. package/dist/react/providers/TextMessagePartProvider.d.ts.map +1 -1
  283. package/dist/react/providers/TextMessagePartProvider.js +5 -1
  284. package/dist/react/providers/TextMessagePartProvider.js.map +1 -1
  285. package/dist/react/providers/ThreadListItemByIndexProvider.d.ts +4 -7
  286. package/dist/react/providers/ThreadListItemByIndexProvider.d.ts.map +1 -1
  287. package/dist/react/providers/ThreadListItemRuntimeProvider.d.ts +4 -8
  288. package/dist/react/providers/ThreadListItemRuntimeProvider.d.ts.map +1 -1
  289. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +210 -219
  290. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  291. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +70 -46
  292. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  293. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +186 -198
  294. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  295. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +13 -4
  296. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  297. package/dist/react/runtimes/RemoteThreadResource.d.ts +17 -23
  298. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  299. package/dist/react/runtimes/RemoteThreadResource.js +3 -1
  300. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  301. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts +10 -11
  302. package/dist/react/runtimes/RuntimeAdapterProvider.d.ts.map +1 -1
  303. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts +11 -14
  304. package/dist/react/runtimes/assistant-transport/commandQueue.d.ts.map +1 -1
  305. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts +6 -8
  306. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.d.ts.map +1 -1
  307. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js +9 -2
  308. package/dist/react/runtimes/assistant-transport/replayBoundaryStream.js.map +1 -1
  309. package/dist/react/runtimes/assistant-transport/runManager.d.ts +9 -12
  310. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
  311. package/dist/react/runtimes/assistant-transport/runManager.js +4 -1
  312. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
  313. package/dist/react/runtimes/assistant-transport/types.d.ts +123 -118
  314. package/dist/react/runtimes/assistant-transport/types.d.ts.map +1 -1
  315. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts +7 -10
  316. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
  317. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js +17 -6
  318. package/dist/react/runtimes/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  319. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts +3 -6
  320. package/dist/react/runtimes/assistant-transport/useConvertedState.d.ts.map +1 -1
  321. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts +2 -5
  322. package/dist/react/runtimes/assistant-transport/useLatestRef.d.ts.map +1 -1
  323. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +7 -10
  324. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  325. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +426 -216
  326. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  327. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts +15 -18
  328. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.d.ts.map +1 -1
  329. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js +8 -4
  330. package/dist/react/runtimes/cloud/CloudFileAttachmentAdapter.js.map +1 -1
  331. package/dist/react/runtimes/cloud/auiV0.d.ts +146 -125
  332. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  333. package/dist/react/runtimes/cloud/auiV0.js +120 -8
  334. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  335. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +13 -15
  336. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
  337. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +5 -7
  338. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -1
  339. package/dist/react/runtimes/cloud/sdkIdentity.d.ts +2 -5
  340. package/dist/react/runtimes/cloud/sdkIdentity.d.ts.map +1 -1
  341. package/dist/react/runtimes/cloud/sdkIdentity.js +1 -1
  342. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +3 -6
  343. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  344. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts +9 -11
  345. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.d.ts.map +1 -1
  346. package/dist/react/runtimes/createMessageConverter.d.ts +15 -19
  347. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  348. package/dist/react/runtimes/createMessageConverter.js.map +1 -1
  349. package/dist/react/runtimes/createRuntimeExtras.d.ts +14 -17
  350. package/dist/react/runtimes/createRuntimeExtras.d.ts.map +1 -1
  351. package/dist/react/runtimes/external-message-converter.d.ts +17 -21
  352. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  353. package/dist/react/runtimes/external-store-history-copy.d.ts +25 -0
  354. package/dist/react/runtimes/external-store-history-copy.d.ts.map +1 -0
  355. package/dist/react/runtimes/external-store-history-copy.js +148 -0
  356. package/dist/react/runtimes/external-store-history-copy.js.map +1 -0
  357. package/dist/react/runtimes/useExternalStoreRuntime.d.ts +3 -6
  358. package/dist/react/runtimes/useExternalStoreRuntime.d.ts.map +1 -1
  359. package/dist/react/runtimes/useExternalStoreRuntime.js +72 -35
  360. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  361. package/dist/react/runtimes/useExternalStoreSharedOptions.d.ts +2 -5
  362. package/dist/react/runtimes/useExternalStoreSharedOptions.d.ts.map +1 -1
  363. package/dist/react/runtimes/useLocalRuntime.d.ts +29 -41
  364. package/dist/react/runtimes/useLocalRuntime.d.ts.map +1 -1
  365. package/dist/react/runtimes/useLocalRuntime.js +142 -34
  366. package/dist/react/runtimes/useLocalRuntime.js.map +1 -1
  367. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts +3 -6
  368. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  369. package/dist/react/runtimes/useRemoteThreadListRuntime.js +2 -2
  370. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  371. package/dist/react/runtimes/useRuntimeAdapters.d.ts +5 -7
  372. package/dist/react/runtimes/useRuntimeAdapters.d.ts.map +1 -1
  373. package/dist/react/runtimes/useStreamingTiming.d.ts +4 -6
  374. package/dist/react/runtimes/useStreamingTiming.d.ts.map +1 -1
  375. package/dist/react/types/MessagePartComponentTypes.d.ts +72 -70
  376. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  377. package/dist/react/types/scopes/dataRenderers.d.ts +11 -15
  378. package/dist/react/types/scopes/dataRenderers.d.ts.map +1 -1
  379. package/dist/react/types/scopes/interactables.d.ts +60 -60
  380. package/dist/react/types/scopes/interactables.d.ts.map +1 -1
  381. package/dist/react/types/scopes/tools.d.ts +21 -24
  382. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  383. package/dist/react/types/store-augmentation.d.ts +10 -11
  384. package/dist/react/types/store-augmentation.d.ts.map +1 -1
  385. package/dist/react/utils/getMessageQuote.d.ts +6 -8
  386. package/dist/react/utils/getMessageQuote.d.ts.map +1 -1
  387. package/dist/react/utils/groupParts.d.ts +35 -37
  388. package/dist/react/utils/groupParts.d.ts.map +1 -1
  389. package/dist/react/utils/useJSONEqual.d.ts +3 -6
  390. package/dist/react/utils/useJSONEqual.d.ts.map +1 -1
  391. package/dist/react/utils/useJSONSchemaDependency.d.ts +3 -6
  392. package/dist/react/utils/useJSONSchemaDependency.d.ts.map +1 -1
  393. package/dist/runtime/api/assistant-runtime.d.ts +28 -31
  394. package/dist/runtime/api/assistant-runtime.d.ts.map +1 -1
  395. package/dist/runtime/api/attachment-runtime.d.ts +51 -49
  396. package/dist/runtime/api/attachment-runtime.d.ts.map +1 -1
  397. package/dist/runtime/api/attachment-runtime.js.map +1 -1
  398. package/dist/runtime/api/bindings.d.ts +35 -35
  399. package/dist/runtime/api/bindings.d.ts.map +1 -1
  400. package/dist/runtime/api/composer-runtime.d.ts +215 -201
  401. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  402. package/dist/runtime/api/composer-runtime.js +4 -0
  403. package/dist/runtime/api/composer-runtime.js.map +1 -1
  404. package/dist/runtime/api/message-part-runtime.d.ts +31 -30
  405. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  406. package/dist/runtime/api/message-part-runtime.js +25 -2
  407. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  408. package/dist/runtime/api/message-runtime.d.ts +87 -82
  409. package/dist/runtime/api/message-runtime.d.ts.map +1 -1
  410. package/dist/runtime/api/message-runtime.js +4 -3
  411. package/dist/runtime/api/message-runtime.js.map +1 -1
  412. package/dist/runtime/api/paths.d.ts +54 -57
  413. package/dist/runtime/api/paths.d.ts.map +1 -1
  414. package/dist/runtime/api/thread-list-item-runtime.d.ts +70 -72
  415. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  416. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  417. package/dist/runtime/api/thread-list-runtime.d.ts +77 -81
  418. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  419. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  420. package/dist/runtime/api/thread-runtime.d.ts +306 -308
  421. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  422. package/dist/runtime/api/thread-runtime.js +2 -1
  423. package/dist/runtime/api/thread-runtime.js.map +1 -1
  424. package/dist/runtime/base/base-assistant-runtime-core.d.ts +9 -12
  425. package/dist/runtime/base/base-assistant-runtime-core.d.ts.map +1 -1
  426. package/dist/runtime/base/base-composer-runtime-core.d.ts +150 -101
  427. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  428. package/dist/runtime/base/base-composer-runtime-core.js +400 -122
  429. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  430. package/dist/runtime/base/base-thread-runtime-core.d.ts +121 -104
  431. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  432. package/dist/runtime/base/base-thread-runtime-core.js +174 -62
  433. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  434. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +30 -31
  435. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  436. package/dist/runtime/base/default-edit-composer-runtime-core.js +21 -2
  437. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  438. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +28 -31
  439. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  440. package/dist/runtime/base/default-thread-composer-runtime-core.js +23 -2
  441. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  442. package/dist/runtime/branch/external-thread-branch-adapter.d.ts +15 -18
  443. package/dist/runtime/branch/external-thread-branch-adapter.d.ts.map +1 -1
  444. package/dist/runtime/interfaces/assistant-runtime-core.d.ts +8 -11
  445. package/dist/runtime/interfaces/assistant-runtime-core.d.ts.map +1 -1
  446. package/dist/runtime/interfaces/composer-runtime-core.d.ts +98 -83
  447. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  448. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +67 -70
  449. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  450. package/dist/runtime/interfaces/thread-runtime-core.d.ts +225 -211
  451. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  452. package/dist/runtime/internal.d.ts +25 -20
  453. package/dist/runtime/internal.d.ts.map +1 -0
  454. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +37 -40
  455. package/dist/runtime/queue/external-thread-queue-adapter.d.ts.map +1 -1
  456. package/dist/runtime/queue/message-queue.d.ts +32 -33
  457. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  458. package/dist/runtime/queue/message-queue.js +10 -2
  459. package/dist/runtime/queue/message-queue.js.map +1 -1
  460. package/dist/runtime/queue/queue-item.d.ts +7 -10
  461. package/dist/runtime/queue/queue-item.d.ts.map +1 -1
  462. package/dist/runtime/utils/abortable-thread-load.d.ts +9 -11
  463. package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -1
  464. package/dist/runtime/utils/attachment-add-operations.d.ts +16 -15
  465. package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -1
  466. package/dist/runtime/utils/attachment-add-operations.js +24 -2
  467. package/dist/runtime/utils/attachment-add-operations.js.map +1 -1
  468. package/dist/runtime/utils/attachment-send-operations.d.ts +12 -0
  469. package/dist/runtime/utils/attachment-send-operations.d.ts.map +1 -0
  470. package/dist/runtime/utils/attachment-send-operations.js +34 -0
  471. package/dist/runtime/utils/attachment-send-operations.js.map +1 -0
  472. package/dist/runtime/utils/auto-status.d.ts +11 -13
  473. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  474. package/dist/runtime/utils/auto-status.js +3 -5
  475. package/dist/runtime/utils/auto-status.js.map +1 -1
  476. package/dist/runtime/utils/chat-model-adapter.d.ts +31 -33
  477. package/dist/runtime/utils/chat-model-adapter.d.ts.map +1 -1
  478. package/dist/runtime/utils/external-message-conversion.d.ts +47 -48
  479. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  480. package/dist/runtime/utils/external-message-conversion.js +6 -5
  481. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  482. package/dist/runtime/utils/external-store-message.d.ts +6 -9
  483. package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
  484. package/dist/runtime/utils/message-repository-session.d.ts +24 -27
  485. package/dist/runtime/utils/message-repository-session.d.ts.map +1 -1
  486. package/dist/runtime/utils/message-repository.d.ts +52 -54
  487. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  488. package/dist/runtime/utils/message-repository.js +8 -7
  489. package/dist/runtime/utils/message-repository.js.map +1 -1
  490. package/dist/runtime/utils/pending-tool-calls.d.ts +12 -14
  491. package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -1
  492. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +7 -10
  493. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
  494. package/dist/runtime/utils/runtime-extras-brand.d.ts +6 -9
  495. package/dist/runtime/utils/runtime-extras-brand.d.ts.map +1 -1
  496. package/dist/runtime/utils/stored-message-parts.d.ts +21 -0
  497. package/dist/runtime/utils/stored-message-parts.d.ts.map +1 -0
  498. package/dist/runtime/utils/stored-message-parts.js +103 -0
  499. package/dist/runtime/utils/stored-message-parts.js.map +1 -0
  500. package/dist/runtime/utils/streaming-timing.d.ts +23 -26
  501. package/dist/runtime/utils/streaming-timing.d.ts.map +1 -1
  502. package/dist/runtime/utils/thread-message-like.d.ts +38 -34
  503. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  504. package/dist/runtime/utils/thread-message-like.js +16 -6
  505. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  506. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts +5 -7
  507. package/dist/runtime/utils/thread-runtime-lifecycle.d.ts.map +1 -1
  508. package/dist/runtime/utils/thread-runtime-lifecycle.js +31 -4
  509. package/dist/runtime/utils/thread-runtime-lifecycle.js.map +1 -1
  510. package/dist/runtime/utils/tool-call-tree.d.ts +10 -13
  511. package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -1
  512. package/dist/runtime/utils/tool-interactions.d.ts +10 -0
  513. package/dist/runtime/utils/tool-interactions.d.ts.map +1 -0
  514. package/dist/runtime/utils/tool-interactions.js +92 -0
  515. package/dist/runtime/utils/tool-interactions.js.map +1 -0
  516. package/dist/runtimes/assistant-transport/utils.d.ts +1 -4
  517. package/dist/runtimes/assistant-transport/utils.d.ts.map +1 -1
  518. package/dist/runtimes/external-store/external-store-adapter.d.ts +220 -200
  519. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  520. package/dist/runtimes/external-store/external-store-runtime-core.d.ts +6 -8
  521. package/dist/runtimes/external-store/external-store-runtime-core.d.ts.map +1 -1
  522. package/dist/runtimes/external-store/external-store-runtime-core.js +4 -1
  523. package/dist/runtimes/external-store/external-store-runtime-core.js.map +1 -1
  524. package/dist/runtimes/external-store/external-store-shared-options.d.ts +3 -6
  525. package/dist/runtimes/external-store/external-store-shared-options.d.ts.map +1 -1
  526. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +40 -43
  527. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  528. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +2 -2
  529. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  530. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +119 -125
  531. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  532. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +71 -18
  533. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  534. package/dist/runtimes/external-store/thread-message-converter.d.ts +5 -8
  535. package/dist/runtimes/external-store/thread-message-converter.d.ts.map +1 -1
  536. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +96 -98
  537. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
  538. package/dist/runtimes/inert/InertThreadRuntimeCore.js +3 -0
  539. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
  540. package/dist/runtimes/internal.d.ts +20 -16
  541. package/dist/runtimes/internal.d.ts.map +1 -0
  542. package/dist/runtimes/local/local-runtime-core.d.ts +7 -10
  543. package/dist/runtimes/local/local-runtime-core.d.ts.map +1 -1
  544. package/dist/runtimes/local/local-runtime-options.d.ts +41 -44
  545. package/dist/runtimes/local/local-runtime-options.d.ts.map +1 -1
  546. package/dist/runtimes/local/local-thread-list-runtime-core.d.ts +42 -45
  547. package/dist/runtimes/local/local-thread-list-runtime-core.d.ts.map +1 -1
  548. package/dist/runtimes/local/local-thread-runtime-core.d.ts +94 -93
  549. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  550. package/dist/runtimes/local/local-thread-runtime-core.js +220 -29
  551. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  552. package/dist/runtimes/local/should-continue.d.ts +2 -5
  553. package/dist/runtimes/local/should-continue.d.ts.map +1 -1
  554. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +77 -55
  555. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  556. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +28 -1
  557. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  558. package/dist/runtimes/remote-thread-list/adapter/in-memory.d.ts +12 -15
  559. package/dist/runtimes/remote-thread-list/adapter/in-memory.d.ts.map +1 -1
  560. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts +3 -6
  561. package/dist/runtimes/remote-thread-list/adapter-changed.d.ts.map +1 -1
  562. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts +2 -5
  563. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  564. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts +34 -31
  565. package/dist/runtimes/remote-thread-list/optimistic-state.d.ts.map +1 -1
  566. package/dist/runtimes/remote-thread-list/optimistic-state.js +8 -3
  567. package/dist/runtimes/remote-thread-list/optimistic-state.js.map +1 -1
  568. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +60 -63
  569. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  570. package/dist/runtimes/remote-thread-list/remote-thread-state.js +1 -1
  571. package/dist/runtimes/remote-thread-list/title-generation.d.ts +31 -33
  572. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -1
  573. package/dist/runtimes/remote-thread-list/title.d.ts +5 -8
  574. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -1
  575. package/dist/runtimes/remote-thread-list/types.d.ts +119 -121
  576. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  577. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +147 -149
  578. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  579. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +22 -7
  580. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  581. package/dist/store/clients/chain-of-thought-client.d.ts +8 -11
  582. package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
  583. package/dist/store/clients/external-thread.d.ts +56 -58
  584. package/dist/store/clients/external-thread.d.ts.map +1 -1
  585. package/dist/store/clients/external-thread.js +1270 -741
  586. package/dist/store/clients/external-thread.js.map +1 -1
  587. package/dist/store/clients/model-context-client.d.ts +2 -5
  588. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  589. package/dist/store/clients/no-op-composer-client.d.ts +3 -6
  590. package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
  591. package/dist/store/clients/no-op-composer-client.js +3 -1
  592. package/dist/store/clients/no-op-composer-client.js.map +1 -1
  593. package/dist/store/clients/runtime-adapter.d.ts +2 -5
  594. package/dist/store/clients/runtime-adapter.d.ts.map +1 -1
  595. package/dist/store/clients/single-thread-list.d.ts +4 -6
  596. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  597. package/dist/store/clients/submission-message.d.ts +8 -0
  598. package/dist/store/clients/submission-message.d.ts.map +1 -0
  599. package/dist/store/clients/submission-message.js +22 -0
  600. package/dist/store/clients/submission-message.js.map +1 -0
  601. package/dist/store/clients/suggestions.d.ts +8 -11
  602. package/dist/store/clients/suggestions.d.ts.map +1 -1
  603. package/dist/store/clients/thread-message-client.d.ts +12 -12
  604. package/dist/store/clients/thread-message-client.d.ts.map +1 -1
  605. package/dist/store/clients/thread-message-client.js +81 -82
  606. package/dist/store/clients/thread-message-client.js.map +1 -1
  607. package/dist/store/clients/thread-selection-events.d.ts +1 -4
  608. package/dist/store/clients/thread-selection-events.d.ts.map +1 -1
  609. package/dist/store/clients/thread-tasks.d.ts +7 -10
  610. package/dist/store/clients/thread-tasks.d.ts.map +1 -1
  611. package/dist/store/env.d.ts +1 -4
  612. package/dist/store/env.d.ts.map +1 -1
  613. package/dist/store/index.d.ts +33 -36
  614. package/dist/store/index.d.ts.map +1 -0
  615. package/dist/store/index.js +5 -5
  616. package/dist/store/internal.d.ts +12 -12
  617. package/dist/store/internal.d.ts.map +1 -0
  618. package/dist/store/internal.js +2 -2
  619. package/dist/store/primitive-predicates.d.ts +14 -16
  620. package/dist/store/primitive-predicates.d.ts.map +1 -1
  621. package/dist/store/primitive-predicates.js +10 -5
  622. package/dist/store/primitive-predicates.js.map +1 -1
  623. package/dist/store/runtime-clients/attachment-runtime-client.d.ts +4 -7
  624. package/dist/store/runtime-clients/attachment-runtime-client.d.ts.map +1 -1
  625. package/dist/store/runtime-clients/composer-runtime-client.d.ts +11 -14
  626. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  627. package/dist/store/runtime-clients/composer-runtime-client.js +65 -62
  628. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  629. package/dist/store/runtime-clients/handle-runtime-action.d.ts +1 -4
  630. package/dist/store/runtime-clients/handle-runtime-action.d.ts.map +1 -1
  631. package/dist/store/runtime-clients/handle-thread-list-action.d.ts +1 -4
  632. package/dist/store/runtime-clients/handle-thread-list-action.d.ts.map +1 -1
  633. package/dist/store/runtime-clients/liveRef.d.ts +2 -5
  634. package/dist/store/runtime-clients/liveRef.d.ts.map +1 -1
  635. package/dist/store/runtime-clients/message-part-runtime-client.d.ts +4 -7
  636. package/dist/store/runtime-clients/message-part-runtime-client.d.ts.map +1 -1
  637. package/dist/store/runtime-clients/message-part-runtime-client.js +26 -16
  638. package/dist/store/runtime-clients/message-part-runtime-client.js.map +1 -1
  639. package/dist/store/runtime-clients/message-runtime-client.d.ts +10 -11
  640. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  641. package/dist/store/runtime-clients/message-runtime-client.js +121 -113
  642. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  643. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts +5 -8
  644. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  645. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts +6 -9
  646. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  647. package/dist/store/runtime-clients/thread-runtime-client.d.ts +4 -7
  648. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  649. package/dist/store/runtime-clients/thread-runtime-client.js +221 -158
  650. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  651. package/dist/store/runtime-clients/useSubscribable.d.ts +3 -6
  652. package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
  653. package/dist/store/scope-registration.d.ts +28 -29
  654. package/dist/store/scope-registration.d.ts.map +1 -1
  655. package/dist/store/scopes/attachment.d.ts +19 -22
  656. package/dist/store/scopes/attachment.d.ts.map +1 -1
  657. package/dist/store/scopes/chain-of-thought.d.ts +33 -35
  658. package/dist/store/scopes/chain-of-thought.d.ts.map +1 -1
  659. package/dist/store/scopes/composer.d.ts +147 -135
  660. package/dist/store/scopes/composer.d.ts.map +1 -1
  661. package/dist/store/scopes/message.d.ts +119 -112
  662. package/dist/store/scopes/message.d.ts.map +1 -1
  663. package/dist/store/scopes/model-context.d.ts +10 -13
  664. package/dist/store/scopes/model-context.d.ts.map +1 -1
  665. package/dist/store/scopes/part.d.ts +53 -50
  666. package/dist/store/scopes/part.d.ts.map +1 -1
  667. package/dist/store/scopes/queue-item.d.ts +21 -23
  668. package/dist/store/scopes/queue-item.d.ts.map +1 -1
  669. package/dist/store/scopes/suggestion.d.ts +14 -17
  670. package/dist/store/scopes/suggestion.d.ts.map +1 -1
  671. package/dist/store/scopes/suggestions.d.ts +18 -21
  672. package/dist/store/scopes/suggestions.d.ts.map +1 -1
  673. package/dist/store/scopes/task.d.ts +27 -30
  674. package/dist/store/scopes/task.d.ts.map +1 -1
  675. package/dist/store/scopes/thread-list-item.d.ts +68 -71
  676. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  677. package/dist/store/scopes/thread.d.ts +197 -200
  678. package/dist/store/scopes/thread.d.ts.map +1 -1
  679. package/dist/store/scopes/threads.d.ts +49 -52
  680. package/dist/store/scopes/threads.d.ts.map +1 -1
  681. package/dist/subscribable/subscribable.d.ts +73 -69
  682. package/dist/subscribable/subscribable.d.ts.map +1 -1
  683. package/dist/subscribable/subscribable.js +35 -7
  684. package/dist/subscribable/subscribable.js.map +1 -1
  685. package/dist/types/attachment.d.ts +35 -37
  686. package/dist/types/attachment.d.ts.map +1 -1
  687. package/dist/types/augmentations.d.ts +7 -8
  688. package/dist/types/augmentations.d.ts.map +1 -1
  689. package/dist/types/directive.d.ts +13 -16
  690. package/dist/types/directive.d.ts.map +1 -1
  691. package/dist/types/error.d.ts +15 -17
  692. package/dist/types/error.d.ts.map +1 -1
  693. package/dist/types/index.d.ts +7 -7
  694. package/dist/types/index.d.ts.map +1 -0
  695. package/dist/types/message.d.ts +358 -318
  696. package/dist/types/message.d.ts.map +1 -1
  697. package/dist/types/message.js.map +1 -1
  698. package/dist/types/quote.d.ts +3 -6
  699. package/dist/types/quote.d.ts.map +1 -1
  700. package/dist/types/trigger.d.ts +10 -13
  701. package/dist/types/trigger.d.ts.map +1 -1
  702. package/dist/types/unsubscribe.d.ts +1 -4
  703. package/dist/types/unsubscribe.d.ts.map +1 -1
  704. package/dist/utils/abortable-iterable.d.ts +2 -5
  705. package/dist/utils/abortable-iterable.d.ts.map +1 -1
  706. package/dist/utils/composite-context-provider.d.ts +9 -13
  707. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  708. package/dist/utils/data-url.d.ts +16 -19
  709. package/dist/utils/data-url.d.ts.map +1 -1
  710. package/dist/utils/getGroupStatus.d.ts +19 -21
  711. package/dist/utils/getGroupStatus.d.ts.map +1 -1
  712. package/dist/utils/id.d.ts +3 -6
  713. package/dist/utils/id.d.ts.map +1 -1
  714. package/dist/utils/image-media-type.d.ts +1 -4
  715. package/dist/utils/image-media-type.d.ts.map +1 -1
  716. package/dist/utils/invoke-user-callback.d.ts +1 -4
  717. package/dist/utils/invoke-user-callback.d.ts.map +1 -1
  718. package/dist/utils/json/is-json-equal.d.ts +1 -4
  719. package/dist/utils/json/is-json-equal.d.ts.map +1 -1
  720. package/dist/utils/json/is-json.d.ts +5 -8
  721. package/dist/utils/json/is-json.d.ts.map +1 -1
  722. package/dist/utils/json/stable-stringify-tool-args.d.ts +3 -6
  723. package/dist/utils/json/stable-stringify-tool-args.d.ts.map +1 -1
  724. package/dist/utils/normalizePartStatus.d.ts +6 -8
  725. package/dist/utils/normalizePartStatus.d.ts.map +1 -1
  726. package/dist/utils/normalizePartStatus.js +3 -2
  727. package/dist/utils/normalizePartStatus.js.map +1 -1
  728. package/dist/utils/notify-event-listeners.d.ts +2 -4
  729. package/dist/utils/notify-event-listeners.d.ts.map +1 -1
  730. package/dist/utils/record.d.ts +1 -4
  731. package/dist/utils/record.d.ts.map +1 -1
  732. package/dist/utils/silent-runtime-action.d.ts +2 -5
  733. package/dist/utils/silent-runtime-action.d.ts.map +1 -1
  734. package/dist/utils/text.d.ts +2 -5
  735. package/dist/utils/text.d.ts.map +1 -1
  736. package/dist/utils/wire-media.d.ts +3 -6
  737. package/dist/utils/wire-media.d.ts.map +1 -1
  738. package/package.json +15 -13
  739. package/src/adapters/attachment.test.ts +98 -1
  740. package/src/adapters/attachment.ts +77 -20
  741. package/src/adapters/feedback.ts +1 -0
  742. package/src/adapters/speech.test.ts +94 -0
  743. package/src/adapters/speech.ts +16 -2
  744. package/src/adapters/thread-history.ts +16 -5
  745. package/src/adapters/voice.test.ts +169 -0
  746. package/src/adapters/voice.ts +25 -4
  747. package/src/index.ts +14 -2
  748. package/src/internal.ts +7 -0
  749. package/src/model-context/frame/host.test.ts +51 -1
  750. package/src/model-context/frame/host.ts +3 -1
  751. package/src/model-context/frame/provider.test.ts +106 -20
  752. package/src/model-context/frame/provider.ts +8 -1
  753. package/src/model-context/frame/validate.ts +57 -0
  754. package/src/model-context/interactable-composer-metadata.test.ts +82 -0
  755. package/src/model-context/interactable-composer-metadata.ts +45 -2
  756. package/src/react/RuntimeAdapter.test.ts +4 -1
  757. package/src/react/adapters/LocalStorageThreadListAdapter.test.ts +711 -0
  758. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +125 -109
  759. package/src/react/client/Interactables.test.ts +865 -8
  760. package/src/react/client/Interactables.ts +325 -45
  761. package/src/react/client/RemoteThreadList.concurrent.test.tsx +9 -3
  762. package/src/react/client/RemoteThreadList.initialize-failure.test.ts +124 -0
  763. package/src/react/client/RemoteThreadList.test.ts +5 -6
  764. package/src/react/client/RemoteThreadList.ts +6 -0
  765. package/src/react/client/Tools.ts +2 -3
  766. package/src/react/client/interactable-model-context.test.ts +56 -13
  767. package/src/react/client/interactable-model-context.ts +48 -23
  768. package/src/react/index.ts +2 -2
  769. package/src/react/interactables-legacy/Interactables.test.ts +221 -4
  770. package/src/react/interactables-legacy/Interactables.ts +25 -14
  771. package/src/react/interactables-legacy/interactable-model-context.ts +26 -24
  772. package/src/react/interactables-legacy/useAssistantInteractable.test.tsx +40 -22
  773. package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +22 -2
  774. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +63 -11
  775. package/src/react/model-context/useInteractable.test.tsx +56 -29
  776. package/src/react/primitive-hooks/useActionBarCopy.test.ts +5 -5
  777. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +33 -0
  778. package/src/react/primitive-hooks/useSuggestionTrigger.ts +7 -4
  779. package/src/react/primitives/generativeUI/GenerativeUI.test.tsx +170 -0
  780. package/src/react/primitives/generativeUI/GenerativeUI.tsx +43 -28
  781. package/src/react/primitives/message/MessageAttachments.test.tsx +2 -6
  782. package/src/react/primitives/message/MessageAttachments.tsx +14 -8
  783. package/src/react/primitives/message/MessageGroupedParts.test.tsx +155 -8
  784. package/src/react/primitives/message/MessageGroupedParts.tsx +19 -6
  785. package/src/react/primitives/message/MessageParts.test.tsx +84 -0
  786. package/src/react/primitives/message/MessageParts.tool-interaction.test.tsx +69 -0
  787. package/src/react/primitives/message/MessageParts.tsx +29 -8
  788. package/src/react/primitives/thread/ThreadMessages.test.tsx +25 -7
  789. package/src/react/primitives/thread/ThreadSuggestions.test.tsx +97 -0
  790. package/src/react/primitives/thread/ThreadSuggestions.tsx +29 -6
  791. package/src/react/providers/ReadonlyThreadProvider.tsx +2 -2
  792. package/src/react/providers/TextMessagePartProvider.tsx +3 -0
  793. package/src/react/runtimes/RemoteThreadListHookInstanceManager.layout.test.tsx +62 -0
  794. package/src/react/runtimes/RemoteThreadListHookInstanceManager.retry.test.tsx +2 -2
  795. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +93 -0
  796. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +181 -21
  797. package/src/react/runtimes/RemoteThreadListHookInstanceManager.title.test.tsx +62 -17
  798. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +112 -77
  799. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +19 -3
  800. package/src/react/runtimes/RemoteThreadResource.ts +5 -2
  801. package/src/react/runtimes/assistant-transport/replayBoundaryStream.test.ts +3 -1
  802. package/src/react/runtimes/assistant-transport/replayBoundaryStream.ts +10 -2
  803. package/src/react/runtimes/assistant-transport/runManager.ts +3 -0
  804. package/src/react/runtimes/assistant-transport/transport-parentid.test.tsx +57 -0
  805. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +73 -46
  806. package/src/react/runtimes/assistant-transport/transport-thread-id.test.tsx +571 -0
  807. package/src/react/runtimes/assistant-transport/types.ts +9 -1
  808. package/src/react/runtimes/assistant-transport/useAssistantTransport.spec.md +10 -0
  809. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.test.tsx +108 -1
  810. package/src/react/runtimes/assistant-transport/useAssistantTransportRuntime.ts +33 -9
  811. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.copy.integration.test.tsx +289 -0
  812. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.copy.test.tsx +471 -0
  813. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.engagement.test.tsx +330 -0
  814. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +78 -2
  815. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +463 -2
  816. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +595 -227
  817. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.test.ts +33 -4
  818. package/src/react/runtimes/cloud/CloudFileAttachmentAdapter.ts +7 -3
  819. package/src/react/runtimes/cloud/auiV0.ts +275 -6
  820. package/src/react/runtimes/cloud/auiV0DecodeSafely.test.ts +873 -0
  821. package/src/react/runtimes/createMessageConverter.ts +9 -3
  822. package/src/react/runtimes/external-message-converter.test.ts +7 -3
  823. package/src/react/runtimes/external-store-history-copy.ts +223 -0
  824. package/src/react/runtimes/remote-thread-hook-on-tap.test.ts +2 -1
  825. package/src/react/runtimes/useExternalStoreRuntime.history-copy.test.tsx +483 -0
  826. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +84 -3
  827. package/src/react/runtimes/useExternalStoreRuntime.suggestions.test.tsx +6 -2
  828. package/src/react/runtimes/useExternalStoreRuntime.ts +25 -5
  829. package/src/react/runtimes/useLocalRuntime.test.tsx +145 -1
  830. package/src/react/runtimes/useLocalRuntime.ts +8 -5
  831. package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +84 -4
  832. package/src/react/runtimes/useRemoteThreadListRuntime.ts +5 -1
  833. package/src/react/types/MessagePartComponentTypes.ts +10 -0
  834. package/src/react/types/scopes/interactables.ts +3 -0
  835. package/src/runtime/api/attachment-runtime.ts +10 -5
  836. package/src/runtime/api/bindings.ts +6 -1
  837. package/src/runtime/api/composer-runtime.ts +29 -6
  838. package/src/runtime/api/message-part-runtime.test.ts +215 -0
  839. package/src/runtime/api/message-part-runtime.ts +44 -1
  840. package/src/runtime/api/message-runtime.test.ts +112 -2
  841. package/src/runtime/api/message-runtime.ts +25 -7
  842. package/src/runtime/api/thread-list-item-runtime.ts +5 -5
  843. package/src/runtime/api/thread-list-runtime.ts +3 -3
  844. package/src/runtime/api/thread-runtime.test.ts +59 -0
  845. package/src/runtime/api/thread-runtime.ts +21 -11
  846. package/src/runtime/base/base-composer-runtime-core.ts +548 -153
  847. package/src/runtime/base/base-thread-runtime-core.test.ts +1614 -136
  848. package/src/runtime/base/base-thread-runtime-core.ts +236 -46
  849. package/src/runtime/base/default-edit-composer-runtime-core.ts +24 -2
  850. package/src/runtime/base/default-thread-composer-runtime-core.ts +33 -3
  851. package/src/runtime/interfaces/composer-runtime-core.ts +19 -0
  852. package/src/runtime/interfaces/thread-runtime-core.ts +24 -1
  853. package/src/runtime/queue/message-queue.ts +11 -1
  854. package/src/runtime/utils/attachment-add-operations.ts +45 -2
  855. package/src/runtime/utils/attachment-send-operations.ts +54 -0
  856. package/src/runtime/utils/auto-status.test.ts +54 -0
  857. package/src/runtime/utils/auto-status.ts +3 -10
  858. package/src/runtime/utils/external-message-conversion.test.ts +91 -0
  859. package/src/runtime/utils/external-message-conversion.ts +9 -6
  860. package/src/runtime/utils/message-repository.test.ts +69 -1
  861. package/src/runtime/utils/message-repository.ts +14 -7
  862. package/src/runtime/utils/stored-message-parts.ts +216 -0
  863. package/src/runtime/utils/thread-message-like.test.ts +58 -0
  864. package/src/runtime/utils/thread-message-like.ts +79 -48
  865. package/src/runtime/utils/thread-runtime-lifecycle.test.ts +67 -0
  866. package/src/runtime/utils/thread-runtime-lifecycle.ts +42 -8
  867. package/src/runtime/utils/tool-interactions.test.ts +193 -0
  868. package/src/runtime/utils/tool-interactions.ts +147 -0
  869. package/src/runtimes/external-store/external-store-adapter.ts +25 -0
  870. package/src/runtimes/external-store/external-store-runtime-core.ts +10 -2
  871. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +2 -2
  872. package/src/runtimes/external-store/external-store-thread-runtime-core.test.ts +49 -0
  873. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +114 -32
  874. package/src/runtimes/inert/InertThreadRuntimeCore.test.ts +127 -56
  875. package/src/runtimes/inert/InertThreadRuntimeCore.ts +10 -1
  876. package/src/runtimes/local/local-thread-runtime-core.edit-cancellation.test.ts +70 -0
  877. package/src/runtimes/local/local-thread-runtime-core.test.ts +1866 -169
  878. package/src/runtimes/local/local-thread-runtime-core.ts +365 -40
  879. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +53 -1
  880. package/src/runtimes/remote-thread-list/optimistic-state.ts +8 -3
  881. package/src/runtimes/tool-invocations/EDGE_CASES.md +19 -4
  882. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +392 -39
  883. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +31 -8
  884. package/src/store/clients/external-thread.test.ts +145 -0
  885. package/src/store/clients/external-thread.ts +669 -144
  886. package/src/store/clients/model-context-client.test.ts +8 -8
  887. package/src/store/clients/no-op-composer-client.ts +2 -0
  888. package/src/store/clients/submission-message.ts +27 -0
  889. package/src/store/clients/thread-message-client.test.ts +39 -0
  890. package/src/store/clients/thread-message-client.ts +13 -6
  891. package/src/store/index.ts +2 -2
  892. package/src/store/internal.ts +1 -0
  893. package/src/store/primitive-predicates.test.ts +119 -0
  894. package/src/store/primitive-predicates.ts +19 -4
  895. package/src/store/runtime-clients/composer-runtime-client.ts +4 -3
  896. package/src/store/runtime-clients/message-part-runtime-client.test.ts +42 -0
  897. package/src/store/runtime-clients/message-part-runtime-client.ts +4 -0
  898. package/src/store/runtime-clients/message-runtime-client.ts +5 -0
  899. package/src/store/runtime-clients/thread-runtime-client.test.tsx +243 -0
  900. package/src/store/runtime-clients/thread-runtime-client.ts +46 -9
  901. package/src/store/scopes/composer.ts +22 -4
  902. package/src/store/scopes/message.ts +12 -1
  903. package/src/store/scopes/part.ts +8 -0
  904. package/src/subscribable/subscribable.test.ts +205 -3
  905. package/src/subscribable/subscribable.ts +49 -7
  906. package/src/tests/RemoteThreadListThreadListRuntimeCore-errors.test.ts +95 -1
  907. package/src/tests/attachment-adapters.test.ts +16 -1
  908. package/src/tests/auiV0Encode.test.ts +412 -1
  909. package/src/tests/base-composer-runtime-core-addAttachment.test.ts +6 -6
  910. package/src/tests/base-composer-runtime-core-send.test.ts +722 -29
  911. package/src/tests/base-composer-runtime-core.test.ts +212 -5
  912. package/src/tests/composer-can-cancel.test.ts +35 -6
  913. package/src/tests/composer-can-send.test.ts +120 -1
  914. package/src/tests/composer-submission.test.tsx +796 -0
  915. package/src/tests/default-edit-composer-runtime-core.test.ts +149 -2
  916. package/src/tests/external-store-thread-list-runtime-core.test.ts +111 -0
  917. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +810 -28
  918. package/src/tests/external-store-thread-runtime-core.test.ts +217 -0
  919. package/src/tests/external-thread-attachments.test.tsx +763 -26
  920. package/src/tests/external-thread-feedback.test.tsx +20 -7
  921. package/src/tests/external-thread-isLast.test.tsx +69 -0
  922. package/src/tests/external-thread-parity.test.tsx +1 -1
  923. package/src/tests/get-thread-state-isRunning.test.ts +2 -2
  924. package/src/tests/message-queue.test.ts +45 -10
  925. package/src/tests/remote-thread-list-adapter-switch.test.ts +45 -1
  926. package/src/tests/thread-switch-events.test.tsx +11 -7
  927. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +2 -1
  928. package/src/types/message.test.ts +19 -0
  929. package/src/types/message.ts +65 -10
  930. package/src/utils/normalizePartStatus.ts +14 -1
  931. package/dist/tests/remote-thread-list-test-helpers.d.ts +0 -16
  932. package/dist/tests/remote-thread-list-test-helpers.d.ts.map +0 -1
  933. package/dist/tests/remote-thread-list-test-helpers.js +0 -59
  934. package/dist/tests/remote-thread-list-test-helpers.js.map +0 -1
@@ -5,7 +5,12 @@ import type {
5
5
  ChatModelRunOptions,
6
6
  ChatModelRunResult,
7
7
  } from "../../runtime/utils/chat-model-adapter";
8
- import type { AppendMessage, ToolCallMessagePart } from "../../types/message";
8
+ import type {
9
+ AppendMessage,
10
+ ThreadMessage,
11
+ ToolCallMessagePart,
12
+ } from "../../types/message";
13
+ import type { ThreadHistoryAdapter } from "../../adapters/thread-history";
9
14
  import type { LocalRuntimeOptionsBase } from "./local-runtime-options";
10
15
  import {
11
16
  ExportedMessageRepository,
@@ -150,260 +155,1636 @@ describe("LocalThreadRuntimeCore events", () => {
150
155
  });
151
156
 
152
157
  describe("LocalThreadRuntimeCore history persistence", () => {
153
- it("surfaces failed user persistence without abandoning the run", async () => {
154
- const persistenceError = new Error("history unavailable");
155
- const run = vi.fn(async () => ({
156
- content: [{ type: "text" as const, text: "done" }],
157
- }));
158
- const append = vi.fn(async (item: ExportedMessageRepositoryItem) => {
159
- if (item.message.role === "user") throw persistenceError;
158
+ it("keeps streaming after feedback arrives and persists the completed message", async () => {
159
+ let releaseStream!: () => void;
160
+ const gate = new Promise<void>((resolve) => {
161
+ releaseStream = resolve;
160
162
  });
163
+ const appendHistory = vi.fn(
164
+ async (_item: ExportedMessageRepositoryItem) => {},
165
+ );
161
166
  const thread = createThread(
162
- { run },
167
+ {
168
+ async *run() {
169
+ yield { content: [{ type: "text", text: "a" }] };
170
+ await gate;
171
+ yield { content: [{ type: "text", text: "ab" }] };
172
+ },
173
+ },
163
174
  {
164
175
  history: {
165
176
  async load() {
166
177
  return { messages: [] };
167
178
  },
168
- append,
179
+ append: appendHistory,
180
+ async update() {},
169
181
  },
170
182
  },
171
183
  );
172
184
 
173
- await expect(thread.append(userMessage("hello"))).rejects.toBe(
174
- persistenceError,
185
+ const send = thread.append(userMessage("hello"));
186
+ await vi.waitFor(() =>
187
+ expect(thread.messages.at(-1)?.content).toHaveLength(1),
175
188
  );
189
+ const messageId = thread.messages.at(-1)!.id;
190
+ thread.submitFeedback({ messageId, type: "positive" });
191
+ releaseStream();
192
+ await send;
176
193
 
177
- expect(append).toHaveBeenCalledTimes(2);
178
- expect(run).toHaveBeenCalledOnce();
194
+ const assistant = thread.messages.at(-1);
195
+ expect(assistant?.status?.type).toBe("complete");
196
+ expect(assistant?.content).toEqual([{ type: "text", text: "ab" }]);
197
+ expect(assistant?.metadata.submittedFeedback).toEqual({
198
+ type: "positive",
199
+ });
200
+ expect(appendHistory.mock.calls.at(-1)?.[0].message).toEqual(assistant);
179
201
  });
180
202
 
181
- it("surfaces failed persistence for messages that do not start a run", async () => {
182
- const persistenceError = new Error("history unavailable");
183
- const run = vi.fn();
203
+ it("persists the completed message when feedback lands before the adapter returns", async () => {
204
+ let releaseTeardown!: () => void;
205
+ const teardown = new Promise<void>((resolve) => {
206
+ releaseTeardown = resolve;
207
+ });
208
+ const appendHistory = vi.fn(
209
+ async (_item: ExportedMessageRepositoryItem) => {},
210
+ );
184
211
  const thread = createThread(
185
- { run },
212
+ {
213
+ async *run() {
214
+ yield {
215
+ content: [{ type: "text", text: "done" }],
216
+ status: { type: "complete", reason: "stop" },
217
+ } satisfies ChatModelRunResult;
218
+ await teardown;
219
+ },
220
+ },
186
221
  {
187
222
  history: {
188
223
  async load() {
189
224
  return { messages: [] };
190
225
  },
191
- async append() {
192
- throw persistenceError;
193
- },
226
+ append: appendHistory,
227
+ async update() {},
194
228
  },
195
229
  },
196
230
  );
197
231
 
198
- await expect(
199
- thread.append({ ...userMessage("hello"), startRun: false }),
200
- ).rejects.toBe(persistenceError);
232
+ const send = thread.append(userMessage("hello"));
233
+ await vi.waitFor(() =>
234
+ expect(thread.messages.at(-1)?.content).toEqual([
235
+ { type: "text", text: "done" },
236
+ ]),
237
+ );
238
+ thread.submitFeedback({
239
+ messageId: thread.messages.at(-1)!.id,
240
+ type: "positive",
241
+ });
242
+ releaseTeardown();
243
+ await send;
201
244
 
202
- expect(run).not.toHaveBeenCalled();
245
+ const assistant = thread.messages.at(-1);
246
+ expect(assistant?.metadata.submittedFeedback).toEqual({
247
+ type: "positive",
248
+ });
249
+ expect(appendHistory.mock.calls.at(-1)?.[0].message).toEqual(assistant);
203
250
  });
204
251
 
205
- it("handles failed persistence before notifying no-run subscribers", async () => {
206
- const persistenceError = new Error("history unavailable");
207
- const listenerError = new Error("listener unavailable");
252
+ it.each(["feedback", "tool result"])(
253
+ "keeps streaming when %s arrives first and the other writer follows",
254
+ async (first) => {
255
+ let releaseStream!: () => void;
256
+ const gate = new Promise<void>((resolve) => {
257
+ releaseStream = resolve;
258
+ });
259
+ const thread = createThread({
260
+ async *run() {
261
+ yield { content: [toolCallPart("lookup_weather")] };
262
+ await gate;
263
+ yield {
264
+ content: [
265
+ toolCallPart("lookup_weather"),
266
+ { type: "text", text: "It is sunny." },
267
+ ],
268
+ } satisfies ChatModelRunResult;
269
+ },
270
+ });
271
+
272
+ const send = thread.append(userMessage("weather"));
273
+ await vi.waitFor(() =>
274
+ expect(thread.messages.at(-1)?.content).toHaveLength(1),
275
+ );
276
+ const messageId = thread.messages.at(-1)!.id;
277
+ const submitFeedback = () =>
278
+ thread.submitFeedback({ messageId, type: "positive" });
279
+ const addToolResult = () =>
280
+ thread.addToolResult({
281
+ messageId,
282
+ toolCallId: "call-lookup_weather",
283
+ toolName: "lookup_weather",
284
+ result: { temperature: 21 },
285
+ isError: false,
286
+ });
287
+ if (first === "feedback") {
288
+ submitFeedback();
289
+ addToolResult();
290
+ } else {
291
+ addToolResult();
292
+ submitFeedback();
293
+ }
294
+ releaseStream();
295
+ await send;
296
+
297
+ const assistant = thread.messages.at(-1);
298
+ expect(assistant?.status?.type).toBe("complete");
299
+ expect(assistant?.content).toEqual([
300
+ expect.objectContaining({ result: { temperature: 21 } }),
301
+ { type: "text", text: "It is sunny." },
302
+ ]);
303
+ expect(assistant?.metadata.submittedFeedback).toEqual({
304
+ type: "positive",
305
+ });
306
+ },
307
+ );
308
+
309
+ it("keeps streaming after a tool result arrives and persists the completed message", async () => {
310
+ let releaseStream!: () => void;
311
+ const gate = new Promise<void>((resolve) => {
312
+ releaseStream = resolve;
313
+ });
314
+ const appendHistory = vi.fn(
315
+ async (_item: ExportedMessageRepositoryItem) => {},
316
+ );
208
317
  const thread = createThread(
209
- { run: vi.fn() },
318
+ {
319
+ async *run() {
320
+ yield { content: [toolCallPart("lookup_weather")] };
321
+ await gate;
322
+ yield {
323
+ content: [
324
+ {
325
+ ...toolCallPart("lookup_weather"),
326
+ result: { temperature: 20 },
327
+ isPreliminary: true,
328
+ artifact: { preview: true },
329
+ modelContent: [{ type: "text", text: "preview" }],
330
+ },
331
+ { type: "text", text: "It is sunny." },
332
+ ],
333
+ } satisfies ChatModelRunResult;
334
+ },
335
+ },
210
336
  {
211
337
  history: {
212
338
  async load() {
213
339
  return { messages: [] };
214
340
  },
215
- async append() {
216
- throw persistenceError;
217
- },
341
+ append: appendHistory,
342
+ async update() {},
218
343
  },
219
344
  },
220
345
  );
221
- thread.subscribe(() => {
222
- throw listenerError;
346
+
347
+ const send = thread.append(userMessage("weather"));
348
+ await vi.waitFor(() =>
349
+ expect(thread.messages.at(-1)?.content).toHaveLength(1),
350
+ );
351
+ const messageId = thread.messages.at(-1)!.id;
352
+ thread.addToolResult({
353
+ messageId,
354
+ toolCallId: "call-lookup_weather",
355
+ toolName: "lookup_weather",
356
+ result: { temperature: 21 },
357
+ isError: false,
223
358
  });
359
+ releaseStream();
360
+ await send;
224
361
 
225
- await expect(
226
- thread.append({ ...userMessage("hello"), startRun: false }),
227
- ).rejects.toBe(listenerError);
228
- await flush();
362
+ const assistant = thread.messages.at(-1);
363
+ expect(assistant?.status?.type).toBe("complete");
364
+ expect(assistant?.content).toEqual([
365
+ expect.objectContaining({ result: { temperature: 21 } }),
366
+ { type: "text", text: "It is sunny." },
367
+ ]);
368
+ expect(assistant?.content[0]).not.toHaveProperty("artifact");
369
+ expect(assistant?.content[0]).not.toHaveProperty("modelContent");
370
+ expect(appendHistory.mock.calls.at(-1)?.[0].message).toEqual(assistant);
229
371
  });
230
- });
231
372
 
232
- describe("LocalThreadRuntimeCore - detach", () => {
233
- it("drops a pending append when detached", async () => {
234
- let resolveInitialization!: () => void;
235
- const initialization = new Promise<void>((resolve) => {
236
- resolveInitialization = resolve;
237
- });
238
- const run = vi.fn(async () => ({
239
- content: [{ type: "text" as const, text: "done" }],
240
- }));
241
- const thread = createThread({ run });
242
- thread.__internal_setGetInitializePromise(() => initialization);
373
+ it("does not settle a preliminary tool result from a later stream chunk", async () => {
374
+ const thread = createThread(
375
+ {
376
+ async *run() {
377
+ yield {
378
+ content: [
379
+ {
380
+ ...toolCallPart("lookup_weather"),
381
+ result: { temperature: 20 },
382
+ isPreliminary: true,
383
+ },
384
+ ],
385
+ };
386
+ yield { content: [toolCallPart("lookup_weather")] };
387
+ },
388
+ },
389
+ { maxSteps: 1 },
390
+ );
243
391
 
244
- const appendPromise = thread.append(userMessage("hello"));
245
- await Promise.resolve();
246
- thread.detach();
247
- resolveInitialization();
392
+ await thread.append(userMessage("weather"));
248
393
 
249
- await appendPromise;
250
- expect(run).not.toHaveBeenCalled();
251
- expect(thread.messages).toEqual([]);
394
+ expect(thread.messages.at(-1)?.content[0]).not.toHaveProperty("result");
395
+ expect(thread.messages.at(-1)?.content[0]).not.toHaveProperty(
396
+ "isPreliminary",
397
+ );
252
398
  });
253
- });
254
-
255
- describe("LocalThreadRuntimeCore optimistic append", () => {
256
- it("paints the appended message before initialization resolves", async () => {
257
- let resolveInitialization!: () => void;
258
- const initialization = new Promise<void>((resolve) => {
259
- resolveInitialization = resolve;
260
- });
261
- const run = vi.fn(async () => ({
262
- content: [{ type: "text" as const, text: "done" }],
263
- }));
264
- const thread = createThread({ run });
265
- thread.__internal_setGetInitializePromise(() => initialization);
266
- const onUpdate = vi.fn();
267
- thread.subscribe(onUpdate);
268
399
 
269
- const appendPromise = thread.append(userMessage("hello"));
270
- await Promise.resolve();
400
+ it.each([false, true])(
401
+ "does not carry adapter-only results into a later snapshot (preliminary: %s)",
402
+ async (preliminary) => {
403
+ const nextPart = {
404
+ ...toolCallPart("lookup_weather"),
405
+ ...(preliminary && {
406
+ result: "preview",
407
+ isPreliminary: true,
408
+ artifact: { preview: true },
409
+ modelContent: [{ type: "text" as const, text: "preview" }],
410
+ }),
411
+ };
412
+ const thread = createThread({
413
+ async *run() {
414
+ yield {
415
+ content: [{ ...toolCallPart("lookup_weather"), result: "first" }],
416
+ };
417
+ yield { content: [nextPart] };
418
+ },
419
+ });
271
420
 
272
- expect(thread.messages).toHaveLength(1);
273
- expect(thread.messages[0]?.role).toBe("user");
274
- expect(onUpdate).toHaveBeenCalled();
275
- expect(run).not.toHaveBeenCalled();
421
+ await thread.append(userMessage("weather"));
276
422
 
277
- resolveInitialization();
278
- await appendPromise;
279
- expect(run).toHaveBeenCalledTimes(1);
280
- });
423
+ expect(thread.messages.at(-1)?.content).toEqual([nextPart]);
424
+ },
425
+ );
281
426
 
282
- it("silently drops a detached append even when initialization rejects", async () => {
283
- let rejectInitialization!: (error: unknown) => void;
284
- const initialization = new Promise<void>((_, reject) => {
285
- rejectInitialization = reject;
427
+ it("keeps external results matched to their own tool-call occurrence", async () => {
428
+ let releaseStream!: () => void;
429
+ const gate = new Promise<void>((resolve) => {
430
+ releaseStream = resolve;
286
431
  });
287
- const run = vi.fn(async () => ({
288
- content: [{ type: "text" as const, text: "done" }],
289
- }));
290
- const thread = createThread({ run });
291
- thread.__internal_setGetInitializePromise(() => initialization);
432
+ const thread = createThread(
433
+ {
434
+ async *run() {
435
+ yield {
436
+ content: [
437
+ toolCallPart("lookup_weather"),
438
+ { ...toolCallPart("lookup_time"), result: "noon" },
439
+ ],
440
+ };
441
+ await gate;
442
+ yield {
443
+ content: [
444
+ toolCallPart("lookup_weather"),
445
+ toolCallPart("lookup_weather"),
446
+ toolCallPart("lookup_time"),
447
+ ],
448
+ };
449
+ yield {
450
+ content: [
451
+ toolCallPart("lookup_weather"),
452
+ toolCallPart("lookup_weather"),
453
+ toolCallPart("lookup_time"),
454
+ { type: "text", text: "done" },
455
+ ],
456
+ };
457
+ },
458
+ },
459
+ { maxSteps: 1 },
460
+ );
292
461
 
293
- const appendPromise = thread.append(userMessage("hello"));
294
- await Promise.resolve();
295
- thread.detach();
296
- rejectInitialization(new Error("initialization failed"));
462
+ const send = thread.append(userMessage("weather"));
463
+ await vi.waitFor(() =>
464
+ expect(thread.messages.at(-1)?.content).toHaveLength(2),
465
+ );
466
+ thread.addToolResult({
467
+ messageId: thread.messages.at(-1)!.id,
468
+ toolCallId: "call-lookup_weather",
469
+ toolName: "lookup_weather",
470
+ result: "first",
471
+ isError: false,
472
+ });
473
+ releaseStream();
474
+ await send;
297
475
 
298
- await expect(appendPromise).resolves.toBeUndefined();
299
- expect(thread.messages).toEqual([]);
300
- expect(run).not.toHaveBeenCalled();
476
+ expect(thread.messages.at(-1)?.content[0]).toMatchObject({
477
+ result: "first",
478
+ });
479
+ expect(thread.messages.at(-1)?.content[1]).not.toHaveProperty("result");
480
+ expect(thread.messages.at(-1)?.content[2]).not.toHaveProperty("result");
301
481
  });
302
482
 
303
- it("rolls the optimistic message back when initialization rejects", async () => {
304
- const initializationError = new Error("initialization failed");
305
- const run = vi.fn(async () => ({
306
- content: [{ type: "text" as const, text: "done" }],
307
- }));
308
- const thread = createThread({ run });
309
- thread.__internal_setGetInitializePromise(() =>
310
- Promise.reject(initializationError),
483
+ it("keeps an external result on a tool call id reused by a later roundtrip", async () => {
484
+ let releaseStream!: () => void;
485
+ const gate = new Promise<void>((resolve) => {
486
+ releaseStream = resolve;
487
+ });
488
+ let calls = 0;
489
+ const thread = createThread({
490
+ async *run() {
491
+ calls++;
492
+ if (calls === 1) {
493
+ yield {
494
+ content: [{ ...toolCallPart("lookup_weather"), result: "first" }],
495
+ status: { type: "requires-action", reason: "tool-calls" },
496
+ } satisfies ChatModelRunResult;
497
+ return;
498
+ }
499
+ yield { content: [toolCallPart("lookup_weather")] };
500
+ await gate;
501
+ yield { content: [toolCallPart("lookup_weather")] };
502
+ yield {
503
+ content: [
504
+ toolCallPart("lookup_weather"),
505
+ { type: "text", text: "done" },
506
+ ],
507
+ };
508
+ },
509
+ });
510
+
511
+ const send = thread.append(userMessage("weather"));
512
+ await vi.waitFor(() =>
513
+ expect(thread.messages.at(-1)?.content).toHaveLength(2),
311
514
  );
515
+ thread.addToolResult({
516
+ messageId: thread.messages.at(-1)!.id,
517
+ toolCallId: "call-lookup_weather",
518
+ toolName: "lookup_weather",
519
+ result: "second",
520
+ isError: false,
521
+ });
522
+ releaseStream();
523
+ await send;
312
524
 
313
- const error = await thread.append(userMessage("hello")).then(
314
- () => {
315
- throw new Error("expected the append to reject");
525
+ expect(thread.messages.at(-1)?.content).toEqual([
526
+ expect.objectContaining({ result: "first" }),
527
+ expect.objectContaining({ result: "second" }),
528
+ { type: "text", text: "done" },
529
+ ]);
530
+ });
531
+
532
+ it("records an interaction on a settled tool call and persists it", async () => {
533
+ const update = vi.fn(async (_item: ExportedMessageRepositoryItem) => {});
534
+ const interaction = {
535
+ type: "action" as const,
536
+ occurredAt: 1,
537
+ payload: { $input: "confirm" },
538
+ };
539
+ const thread = createThread(
540
+ {
541
+ async run() {
542
+ return { content: [toolCallPart("lookup_weather")] };
543
+ },
544
+ },
545
+ {
546
+ history: {
547
+ async load() {
548
+ return { messages: [] };
549
+ },
550
+ async append() {},
551
+ update,
552
+ },
316
553
  },
317
- (e: unknown) => e,
318
554
  );
319
- expect(isMessageNotSentError(error)).toBe(true);
320
- expect((error as Error).cause).toBe(initializationError);
321
- expect(thread.messages).toEqual([]);
322
- expect(run).not.toHaveBeenCalled();
555
+
556
+ await thread.append(userMessage("what is the weather"));
557
+ const assistantMessage = thread.messages.at(-1)!;
558
+
559
+ await thread.unstable_recordToolInteraction({
560
+ messageId: assistantMessage.id,
561
+ toolCallId: "call-lookup_weather",
562
+ interaction,
563
+ });
564
+
565
+ expect(thread.messages.at(-1)?.content[0]).toMatchObject({
566
+ unstable_interactions: { entries: [interaction] },
567
+ });
568
+ await vi.waitFor(() => expect(update).toHaveBeenCalledOnce());
569
+ expect(update.mock.calls[0]?.[0].message.content[0]).toMatchObject({
570
+ unstable_interactions: { entries: [interaction] },
571
+ });
323
572
  });
324
- });
325
573
 
326
- describe("LocalThreadRuntimeCore human-in-the-loop tools", () => {
327
- it("pauses on requires-action while a listed tool call has no result", async () => {
328
- const { thread, runs } = createApprovalThread(toolCallResult("send_email"));
574
+ it("keeps a recorded interaction through the next streamed update", async () => {
575
+ let releaseStream!: () => void;
576
+ const gate = new Promise<void>((resolve) => {
577
+ releaseStream = resolve;
578
+ });
579
+ const append = vi.fn(async (_item: ExportedMessageRepositoryItem) => {});
580
+ const interaction = {
581
+ type: "human-response" as const,
582
+ occurredAt: 1,
583
+ payload: "confirm",
584
+ };
585
+ const thread = createThread(
586
+ {
587
+ async *run() {
588
+ yield { content: [toolCallPart("lookup_weather")] };
589
+ await gate;
590
+ yield {
591
+ content: [
592
+ toolCallPart("lookup_weather"),
593
+ { type: "text", text: "It is sunny." },
594
+ ],
595
+ };
596
+ },
597
+ },
598
+ {
599
+ history: {
600
+ async load() {
601
+ return { messages: [] };
602
+ },
603
+ append,
604
+ },
605
+ },
606
+ );
607
+
608
+ const send = thread.append(userMessage("what is the weather"));
609
+ await vi.waitFor(() =>
610
+ expect(thread.messages.at(-1)?.content).toHaveLength(1),
611
+ );
612
+ const messageId = thread.messages.at(-1)!.id;
613
+
614
+ await thread.unstable_recordToolInteraction({
615
+ messageId,
616
+ toolCallId: "call-lookup_weather",
617
+ interaction,
618
+ });
619
+ expect(thread.messages.at(-1)?.content[0]).toMatchObject({
620
+ unstable_interactions: { entries: [interaction] },
621
+ });
622
+
623
+ releaseStream();
624
+ await send;
625
+
626
+ expect(thread.messages.at(-1)?.content[0]).toMatchObject({
627
+ unstable_interactions: { entries: [interaction] },
628
+ });
629
+ expect(append.mock.calls.at(-1)?.[0].message.content[0]).toMatchObject({
630
+ unstable_interactions: { entries: [interaction] },
631
+ });
632
+ });
633
+
634
+ it("rejects interactions for unknown messages or tool calls", async () => {
635
+ const thread = createThread({
636
+ async run() {
637
+ return { content: [toolCallPart("lookup_weather")] };
638
+ },
639
+ });
640
+ const interaction = {
641
+ type: "action" as const,
642
+ occurredAt: 1,
643
+ payload: { $input: "confirm" },
644
+ };
645
+
646
+ await thread.append(userMessage("what is the weather"));
647
+ const assistantMessage = thread.messages.at(-1)!;
648
+
649
+ await expect(
650
+ thread.unstable_recordToolInteraction({
651
+ messageId: "missing",
652
+ toolCallId: "call-lookup_weather",
653
+ interaction,
654
+ }),
655
+ ).rejects.toThrow("non-existing message");
656
+ await expect(
657
+ thread.unstable_recordToolInteraction({
658
+ messageId: assistantMessage.id,
659
+ toolCallId: "missing",
660
+ interaction,
661
+ }),
662
+ ).rejects.toThrow("non-existing tool call");
663
+ });
664
+
665
+ it("excludes recorded interactions from model input", async () => {
666
+ const runs: ChatModelRunOptions[] = [];
667
+ const interaction = {
668
+ type: "action" as const,
669
+ occurredAt: 1,
670
+ payload: { $input: "confirm" },
671
+ };
672
+ const nestedMessage: ThreadMessage = {
673
+ id: "nested-message",
674
+ role: "assistant",
675
+ createdAt: new Date(0),
676
+ content: [
677
+ {
678
+ ...toolCallPart("nested_weather"),
679
+ unstable_interactions: { entries: [interaction] },
680
+ },
681
+ ],
682
+ status: { type: "complete", reason: "stop" },
683
+ metadata: {
684
+ unstable_state: null,
685
+ unstable_annotations: [],
686
+ unstable_data: [],
687
+ steps: [],
688
+ custom: {},
689
+ },
690
+ };
691
+ const thread = createThread({
692
+ async run(options) {
693
+ runs.push(options);
694
+ return runs.length === 1
695
+ ? {
696
+ content: [
697
+ {
698
+ ...toolCallPart("lookup_weather"),
699
+ messages: [nestedMessage],
700
+ },
701
+ ],
702
+ }
703
+ : { content: [{ type: "text", text: "done" }] };
704
+ },
705
+ });
706
+
707
+ await thread.append(userMessage("what is the weather"));
708
+ const assistantMessage = thread.messages.at(-1)!;
709
+ await thread.unstable_recordToolInteraction({
710
+ messageId: assistantMessage.id,
711
+ toolCallId: "call-lookup_weather",
712
+ interaction,
713
+ });
714
+ await thread.append({
715
+ ...userMessage("and tomorrow"),
716
+ parentId: assistantMessage.id,
717
+ });
718
+
719
+ const modelToolCall = runs[1]?.messages
720
+ .find((message) => message.role === "assistant")
721
+ ?.content.find((part) => part.type === "tool-call");
722
+ expect(modelToolCall).not.toHaveProperty("unstable_interactions");
723
+ expect(modelToolCall?.messages?.[0]?.content[0]).not.toHaveProperty(
724
+ "unstable_interactions",
725
+ );
726
+ });
727
+
728
+ it("exposes a tool result added mid-run to the adapter before its next chunk", async () => {
729
+ let releaseStream!: () => void;
730
+ const gate = new Promise<void>((resolve) => {
731
+ releaseStream = resolve;
732
+ });
733
+ let seen: unknown;
734
+ const thread = createThread({
735
+ async *run({ unstable_getMessage }) {
736
+ yield { content: [toolCallPart("lookup_weather")] };
737
+ await gate;
738
+ seen = unstable_getMessage().content;
739
+ yield {
740
+ content: [
741
+ toolCallPart("lookup_weather"),
742
+ { type: "text", text: "done" },
743
+ ],
744
+ };
745
+ },
746
+ });
747
+
748
+ const send = thread.append(userMessage("weather"));
749
+ await vi.waitFor(() =>
750
+ expect(thread.messages.at(-1)?.content).toHaveLength(1),
751
+ );
752
+ thread.addToolResult({
753
+ messageId: thread.messages.at(-1)!.id,
754
+ toolCallId: "call-lookup_weather",
755
+ toolName: "lookup_weather",
756
+ result: { temperature: 21 },
757
+ isError: false,
758
+ });
759
+ releaseStream();
760
+ await send;
761
+
762
+ expect(seen).toEqual([
763
+ expect.objectContaining({ result: { temperature: 21 } }),
764
+ ]);
765
+ });
766
+
767
+ it("carries multiple external results through a replacement chain", async () => {
768
+ let releaseStream!: () => void;
769
+ const gate = new Promise<void>((resolve) => {
770
+ releaseStream = resolve;
771
+ });
772
+ const thread = createThread({
773
+ async *run() {
774
+ const content = [
775
+ toolCallPart("lookup_weather"),
776
+ toolCallPart("lookup_time"),
777
+ ];
778
+ yield { content };
779
+ await gate;
780
+ yield { content: [...content, { type: "text", text: "done" }] };
781
+ },
782
+ });
783
+
784
+ const send = thread.append(userMessage("weather and time"));
785
+ await vi.waitFor(() =>
786
+ expect(thread.messages.at(-1)?.content).toHaveLength(2),
787
+ );
788
+ const messageId = thread.messages.at(-1)!.id;
789
+ for (const toolName of ["lookup_weather", "lookup_time"]) {
790
+ thread.addToolResult({
791
+ messageId,
792
+ toolCallId: `call-${toolName}`,
793
+ toolName,
794
+ result: `${toolName} result`,
795
+ isError: false,
796
+ artifact: { toolName },
797
+ modelContent: [{ type: "text", text: toolName }],
798
+ });
799
+ }
800
+ releaseStream();
801
+ await send;
802
+
803
+ expect(thread.messages.at(-1)?.content).toEqual([
804
+ ...["lookup_weather", "lookup_time"].map((toolName) => ({
805
+ ...toolCallPart(toolName),
806
+ result: `${toolName} result`,
807
+ isError: false,
808
+ artifact: { toolName },
809
+ modelContent: [{ type: "text", text: toolName }],
810
+ })),
811
+ { type: "text", text: "done" },
812
+ ]);
813
+ });
814
+
815
+ it("persists a tool result added after a completed message", async () => {
816
+ const update = vi.fn(async (_item: ExportedMessageRepositoryItem) => {});
817
+ const thread = createThread(
818
+ {
819
+ async run() {
820
+ return { content: [toolCallPart("lookup_weather")] };
821
+ },
822
+ },
823
+ {
824
+ history: {
825
+ async load() {
826
+ return { messages: [] };
827
+ },
828
+ async append() {},
829
+ update,
830
+ },
831
+ },
832
+ );
833
+
834
+ await thread.append(userMessage("what is the weather"));
835
+ const assistantMessage = thread.messages.at(-1)!;
836
+ expect(assistantMessage.status?.type).toBe("complete");
837
+
838
+ thread.addToolResult({
839
+ messageId: assistantMessage.id,
840
+ toolCallId: "call-lookup_weather",
841
+ toolName: "lookup_weather",
842
+ result: { temperature: 21 },
843
+ isError: false,
844
+ });
845
+ await flush();
846
+
847
+ expect(update).toHaveBeenCalledOnce();
848
+ expect(update.mock.calls[0]?.[0].message.content).toEqual([
849
+ expect.objectContaining({
850
+ result: { temperature: 21 },
851
+ isError: false,
852
+ }),
853
+ ]);
854
+ });
855
+
856
+ it("persists a tool result added after an incomplete message", async () => {
857
+ const update = vi.fn(async (_item: ExportedMessageRepositoryItem) => {});
858
+ const thread = createThread(
859
+ {
860
+ async run() {
861
+ return {
862
+ content: [toolCallPart("lookup_weather")],
863
+ status: { type: "incomplete", reason: "cancelled" },
864
+ };
865
+ },
866
+ },
867
+ {
868
+ history: {
869
+ async load() {
870
+ return { messages: [] };
871
+ },
872
+ async append() {},
873
+ update,
874
+ },
875
+ },
876
+ );
877
+
878
+ await thread.append(userMessage("what is the weather"));
879
+ const assistantMessage = thread.messages.at(-1)!;
880
+ expect(assistantMessage.status?.type).toBe("incomplete");
881
+
882
+ thread.addToolResult({
883
+ messageId: assistantMessage.id,
884
+ toolCallId: "call-lookup_weather",
885
+ toolName: "lookup_weather",
886
+ result: { temperature: 21 },
887
+ isError: false,
888
+ });
889
+ await flush();
890
+
891
+ expect(update).toHaveBeenCalledOnce();
892
+ expect(update.mock.calls[0]?.[0].message.content).toEqual([
893
+ expect.objectContaining({ result: { temperature: 21 } }),
894
+ ]);
895
+ });
896
+
897
+ it("persists a tool result added after later turns follow its message", async () => {
898
+ const update = vi.fn(async (_item: ExportedMessageRepositoryItem) => {});
899
+ const thread = createThread(
900
+ {
901
+ async run() {
902
+ return { content: [toolCallPart("lookup_weather")] };
903
+ },
904
+ },
905
+ {
906
+ history: {
907
+ async load() {
908
+ return { messages: [] };
909
+ },
910
+ async append() {},
911
+ update,
912
+ },
913
+ },
914
+ );
915
+
916
+ await thread.append(userMessage("what is the weather"));
917
+ const [question, answer] = thread.messages;
918
+ await thread.append({
919
+ ...userMessage("and tomorrow?"),
920
+ parentId: answer!.id,
921
+ });
922
+ expect(thread.messages).toHaveLength(4);
923
+
924
+ thread.addToolResult({
925
+ messageId: answer!.id,
926
+ toolCallId: "call-lookup_weather",
927
+ toolName: "lookup_weather",
928
+ result: { temperature: 21 },
929
+ isError: false,
930
+ });
931
+ await flush();
932
+
933
+ expect(update).toHaveBeenCalledOnce();
934
+ expect(update.mock.calls[0]?.[0]).toMatchObject({
935
+ parentId: question!.id,
936
+ message: {
937
+ id: answer!.id,
938
+ content: [expect.objectContaining({ result: { temperature: 21 } })],
939
+ },
940
+ });
941
+ });
942
+
943
+ it("writes a result a subscriber adds in response after the late result", async () => {
944
+ const update = vi.fn(async (_item: ExportedMessageRepositoryItem) => {});
945
+ const thread = createThread(
946
+ {
947
+ async run() {
948
+ return {
949
+ content: [
950
+ toolCallPart("lookup_weather"),
951
+ toolCallPart("lookup_time"),
952
+ ],
953
+ };
954
+ },
955
+ },
956
+ {
957
+ history: {
958
+ async load() {
959
+ return { messages: [] };
960
+ },
961
+ async append() {},
962
+ update,
963
+ },
964
+ },
965
+ );
966
+
967
+ await thread.append(userMessage("weather and time"));
968
+ const assistantMessage = thread.messages.at(-1)!;
969
+ expect(assistantMessage.status?.type).toBe("complete");
970
+
971
+ const unsubscribe = thread.subscribe(() => {
972
+ const message = thread.messages.find((m) => m.id === assistantMessage.id);
973
+ const [weather, time] = message?.content ?? [];
974
+ if (
975
+ weather?.type === "tool-call" &&
976
+ weather.result !== undefined &&
977
+ time?.type === "tool-call" &&
978
+ time.result === undefined
979
+ ) {
980
+ thread.addToolResult({
981
+ messageId: assistantMessage.id,
982
+ toolCallId: "call-lookup_time",
983
+ toolName: "lookup_time",
984
+ result: { hour: 9 },
985
+ isError: false,
986
+ });
987
+ }
988
+ });
989
+ thread.addToolResult({
990
+ messageId: assistantMessage.id,
991
+ toolCallId: "call-lookup_weather",
992
+ toolName: "lookup_weather",
993
+ result: { temperature: 21 },
994
+ isError: false,
995
+ });
996
+ unsubscribe();
997
+ await flush();
998
+
999
+ expect(update).toHaveBeenCalledTimes(2);
1000
+ expect(update.mock.calls.at(-1)?.[0].message.content).toEqual([
1001
+ expect.objectContaining({ result: { temperature: 21 } }),
1002
+ expect.objectContaining({ result: { hour: 9 } }),
1003
+ ]);
1004
+ });
1005
+
1006
+ it("writes a decision a subscriber adds in response after the one it answers", async () => {
1007
+ const update = vi.fn(async (_item: ExportedMessageRepositoryItem) => {});
1008
+ let runs = 0;
1009
+ const thread = createThread(
1010
+ {
1011
+ async run() {
1012
+ runs++;
1013
+ if (runs > 1) return { content: [{ type: "text", text: "ok" }] };
1014
+ return {
1015
+ content: [
1016
+ {
1017
+ ...toolCallPart("send_email", { id: "a1" }),
1018
+ toolCallId: "call-1",
1019
+ },
1020
+ {
1021
+ ...toolCallPart("send_email", { id: "a2" }),
1022
+ toolCallId: "call-2",
1023
+ },
1024
+ ],
1025
+ status: { type: "requires-action", reason: "tool-calls" },
1026
+ };
1027
+ },
1028
+ },
1029
+ {
1030
+ history: {
1031
+ async load() {
1032
+ return { messages: [] };
1033
+ },
1034
+ async append() {},
1035
+ update,
1036
+ },
1037
+ },
1038
+ );
1039
+
1040
+ await thread.append(userMessage("send two emails"));
1041
+ const paused = thread.messages.at(-1)!;
1042
+ await thread.append({ ...userMessage("and then"), parentId: paused.id });
1043
+ await flush();
1044
+
1045
+ const unsubscribe = thread.subscribe(() => {
1046
+ const message = thread.messages.find((m) => m.id === paused.id);
1047
+ const [first, second] = message?.content ?? [];
1048
+ if (
1049
+ first?.type === "tool-call" &&
1050
+ first.approval?.approved !== undefined &&
1051
+ second?.type === "tool-call" &&
1052
+ second.approval?.approved === undefined
1053
+ ) {
1054
+ void thread.respondToToolApproval({ approvalId: "a2", approved: true });
1055
+ }
1056
+ });
1057
+ void thread.respondToToolApproval({ approvalId: "a1", approved: true });
1058
+ unsubscribe();
1059
+ await flush();
1060
+
1061
+ expect(update).toHaveBeenCalledTimes(2);
1062
+ expect(update.mock.calls.at(-1)?.[0].message.content).toEqual([
1063
+ expect.objectContaining({
1064
+ approval: expect.objectContaining({ approved: true }),
1065
+ }),
1066
+ expect.objectContaining({
1067
+ approval: expect.objectContaining({ approved: true }),
1068
+ }),
1069
+ ]);
1070
+ });
1071
+
1072
+ it("does not rewrite a message whose run is still streaming", async () => {
1073
+ const update = vi.fn(async (_item: ExportedMessageRepositoryItem) => {});
1074
+ let release!: () => void;
1075
+ const released = new Promise<void>((resolve) => {
1076
+ release = resolve;
1077
+ });
1078
+ const thread = createThread(
1079
+ {
1080
+ async *run() {
1081
+ yield { content: [toolCallPart("lookup_weather")] };
1082
+ await released;
1083
+ },
1084
+ },
1085
+ {
1086
+ history: {
1087
+ async load() {
1088
+ return { messages: [] };
1089
+ },
1090
+ async append() {},
1091
+ update,
1092
+ },
1093
+ },
1094
+ );
1095
+
1096
+ const appended = thread.append(userMessage("what is the weather"));
1097
+ await flush();
1098
+ const assistantMessage = thread.messages.at(-1)!;
1099
+ expect(assistantMessage.status?.type).toBe("running");
1100
+
1101
+ thread.addToolResult({
1102
+ messageId: assistantMessage.id,
1103
+ toolCallId: "call-lookup_weather",
1104
+ toolName: "lookup_weather",
1105
+ result: { temperature: 21 },
1106
+ isError: false,
1107
+ });
1108
+ await flush();
1109
+ release();
1110
+ await appended;
1111
+
1112
+ expect(update).not.toHaveBeenCalled();
1113
+ });
1114
+
1115
+ it("persists a final result that replaces a preliminary result", async () => {
1116
+ const update = vi.fn(async (_item: ExportedMessageRepositoryItem) => {});
1117
+ const thread = createThread(
1118
+ {
1119
+ async run() {
1120
+ return {
1121
+ content: [
1122
+ {
1123
+ ...toolCallPart("lookup_weather"),
1124
+ result: { temperature: 20 },
1125
+ isPreliminary: true,
1126
+ },
1127
+ ],
1128
+ };
1129
+ },
1130
+ },
1131
+ {
1132
+ history: {
1133
+ async load() {
1134
+ return { messages: [] };
1135
+ },
1136
+ async append() {},
1137
+ update,
1138
+ },
1139
+ },
1140
+ );
1141
+
1142
+ await thread.append(userMessage("what is the weather"));
1143
+ const assistantMessage = thread.messages.at(-1)!;
1144
+ expect(assistantMessage.status?.type).toBe("complete");
1145
+
1146
+ thread.addToolResult({
1147
+ messageId: assistantMessage.id,
1148
+ toolCallId: "call-lookup_weather",
1149
+ toolName: "lookup_weather",
1150
+ result: { temperature: 21 },
1151
+ isError: false,
1152
+ });
1153
+ await flush();
1154
+
1155
+ expect(update).toHaveBeenCalledOnce();
1156
+ const updatedToolCall = update.mock.calls[0]?.[0].message.content[0];
1157
+ expect(updatedToolCall).toMatchObject({ result: { temperature: 21 } });
1158
+ expect(updatedToolCall).not.toHaveProperty("isPreliminary");
1159
+ });
1160
+
1161
+ it("surfaces failed user persistence without abandoning the run", async () => {
1162
+ const persistenceError = new Error("history unavailable");
1163
+ const run = vi.fn(async () => ({
1164
+ content: [{ type: "text" as const, text: "done" }],
1165
+ }));
1166
+ const append = vi.fn(async (item: ExportedMessageRepositoryItem) => {
1167
+ if (item.message.role === "user") throw persistenceError;
1168
+ });
1169
+ const thread = createThread(
1170
+ { run },
1171
+ {
1172
+ history: {
1173
+ async load() {
1174
+ return { messages: [] };
1175
+ },
1176
+ append,
1177
+ },
1178
+ },
1179
+ );
1180
+
1181
+ await expect(thread.append(userMessage("hello"))).rejects.toBe(
1182
+ persistenceError,
1183
+ );
1184
+
1185
+ expect(append).toHaveBeenCalledTimes(2);
1186
+ expect(run).toHaveBeenCalledOnce();
1187
+ });
1188
+
1189
+ it("surfaces failed persistence for messages that do not start a run", async () => {
1190
+ const persistenceError = new Error("history unavailable");
1191
+ const run = vi.fn();
1192
+ const thread = createThread(
1193
+ { run },
1194
+ {
1195
+ history: {
1196
+ async load() {
1197
+ return { messages: [] };
1198
+ },
1199
+ async append() {
1200
+ throw persistenceError;
1201
+ },
1202
+ },
1203
+ },
1204
+ );
1205
+
1206
+ await expect(
1207
+ thread.append({ ...userMessage("hello"), startRun: false }),
1208
+ ).rejects.toBe(persistenceError);
1209
+
1210
+ expect(run).not.toHaveBeenCalled();
1211
+ });
1212
+
1213
+ it("handles failed persistence before notifying no-run subscribers", async () => {
1214
+ const persistenceError = new Error("history unavailable");
1215
+ const listenerError = new Error("listener unavailable");
1216
+ const thread = createThread(
1217
+ { run: vi.fn() },
1218
+ {
1219
+ history: {
1220
+ async load() {
1221
+ return { messages: [] };
1222
+ },
1223
+ async append() {
1224
+ throw persistenceError;
1225
+ },
1226
+ },
1227
+ },
1228
+ );
1229
+ thread.subscribe(() => {
1230
+ throw listenerError;
1231
+ });
1232
+
1233
+ await expect(
1234
+ thread.append({ ...userMessage("hello"), startRun: false }),
1235
+ ).rejects.toBe(listenerError);
1236
+ await flush();
1237
+ });
1238
+ });
1239
+
1240
+ describe("LocalThreadRuntimeCore - detach", () => {
1241
+ it("drops a pending append when detached", async () => {
1242
+ let resolveInitialization!: () => void;
1243
+ const initialization = new Promise<void>((resolve) => {
1244
+ resolveInitialization = resolve;
1245
+ });
1246
+ const run = vi.fn(async () => ({
1247
+ content: [{ type: "text" as const, text: "done" }],
1248
+ }));
1249
+ const thread = createThread({ run });
1250
+ thread.__internal_setGetInitializePromise(() => initialization);
1251
+
1252
+ const appendPromise = thread.append(userMessage("hello"));
1253
+ await Promise.resolve();
1254
+ thread.detach();
1255
+ resolveInitialization();
1256
+
1257
+ await appendPromise;
1258
+ expect(run).not.toHaveBeenCalled();
1259
+ expect(thread.messages).toEqual([]);
1260
+ });
1261
+ });
1262
+
1263
+ describe("LocalThreadRuntimeCore optimistic append", () => {
1264
+ it("paints the appended message before initialization resolves", async () => {
1265
+ let resolveInitialization!: () => void;
1266
+ const initialization = new Promise<void>((resolve) => {
1267
+ resolveInitialization = resolve;
1268
+ });
1269
+ const run = vi.fn(async () => ({
1270
+ content: [{ type: "text" as const, text: "done" }],
1271
+ }));
1272
+ const thread = createThread({ run });
1273
+ thread.__internal_setGetInitializePromise(() => initialization);
1274
+ const onUpdate = vi.fn();
1275
+ thread.subscribe(onUpdate);
1276
+
1277
+ const appendPromise = thread.append(userMessage("hello"));
1278
+ await Promise.resolve();
1279
+
1280
+ expect(thread.messages).toHaveLength(1);
1281
+ expect(thread.messages[0]?.role).toBe("user");
1282
+ expect(onUpdate).toHaveBeenCalled();
1283
+ expect(run).not.toHaveBeenCalled();
1284
+
1285
+ resolveInitialization();
1286
+ await appendPromise;
1287
+ expect(run).toHaveBeenCalledTimes(1);
1288
+ });
1289
+
1290
+ it("silently drops a detached append even when initialization rejects", async () => {
1291
+ let rejectInitialization!: (error: unknown) => void;
1292
+ const initialization = new Promise<void>((_, reject) => {
1293
+ rejectInitialization = reject;
1294
+ });
1295
+ const run = vi.fn(async () => ({
1296
+ content: [{ type: "text" as const, text: "done" }],
1297
+ }));
1298
+ const thread = createThread({ run });
1299
+ thread.__internal_setGetInitializePromise(() => initialization);
1300
+
1301
+ const appendPromise = thread.append(userMessage("hello"));
1302
+ await Promise.resolve();
1303
+ thread.detach();
1304
+ rejectInitialization(new Error("initialization failed"));
1305
+
1306
+ await expect(appendPromise).resolves.toBeUndefined();
1307
+ expect(thread.messages).toEqual([]);
1308
+ expect(run).not.toHaveBeenCalled();
1309
+ });
1310
+
1311
+ it("rolls the optimistic message back when initialization rejects", async () => {
1312
+ const initializationError = new Error("initialization failed");
1313
+ const run = vi.fn(async () => ({
1314
+ content: [{ type: "text" as const, text: "done" }],
1315
+ }));
1316
+ const thread = createThread({ run });
1317
+ thread.__internal_setGetInitializePromise(() =>
1318
+ Promise.reject(initializationError),
1319
+ );
1320
+
1321
+ const error = await thread.append(userMessage("hello")).then(
1322
+ () => {
1323
+ throw new Error("expected the append to reject");
1324
+ },
1325
+ (e: unknown) => e,
1326
+ );
1327
+ expect(isMessageNotSentError(error)).toBe(true);
1328
+ expect((error as Error).cause).toBe(initializationError);
1329
+ expect(thread.messages).toEqual([]);
1330
+ expect(run).not.toHaveBeenCalled();
1331
+ });
1332
+ });
1333
+
1334
+ describe("LocalThreadRuntimeCore append during a history load", () => {
1335
+ const persistedMessage = {
1336
+ id: "persisted",
1337
+ role: "user" as const,
1338
+ content: [{ type: "text" as const, text: "persisted" }],
1339
+ attachments: [],
1340
+ createdAt: new Date(0),
1341
+ metadata: { custom: {} },
1342
+ };
1343
+
1344
+ const createLoadingThread = (
1345
+ load: ThreadHistoryAdapter["load"],
1346
+ run: ChatModelAdapter["run"] = async () => ({
1347
+ content: [{ type: "text", text: "done" }],
1348
+ }),
1349
+ ) => {
1350
+ const appended: ExportedMessageRepositoryItem[] = [];
1351
+ const thread = createThread(
1352
+ { run },
1353
+ {
1354
+ history: {
1355
+ load,
1356
+ async append(item) {
1357
+ appended.push(item);
1358
+ },
1359
+ },
1360
+ },
1361
+ );
1362
+ return { thread, appended };
1363
+ };
1364
+
1365
+ it("lands a message typed during a load after the imported history", async () => {
1366
+ let releaseLoad!: () => void;
1367
+ const loadBarrier = new Promise<void>((resolve) => {
1368
+ releaseLoad = resolve;
1369
+ });
1370
+ const { thread, appended } = createLoadingThread(() =>
1371
+ loadBarrier.then(() => ({
1372
+ headId: "persisted",
1373
+ messages: [{ parentId: null, message: persistedMessage }],
1374
+ })),
1375
+ );
1376
+
1377
+ thread.__internal_load();
1378
+ expect(thread.isLoading).toBe(true);
1379
+
1380
+ const appendPromise = thread.append(userMessage("typed during load"));
1381
+ releaseLoad();
1382
+ await appendPromise;
1383
+ await flush();
1384
+
1385
+ expect(thread.messages.map((message) => message.content)).toEqual([
1386
+ [{ type: "text", text: "persisted" }],
1387
+ [{ type: "text", text: "typed during load" }],
1388
+ [{ type: "text", text: "done" }],
1389
+ ]);
1390
+ expect(appended.map((item) => [item.parentId, item.message.role])).toEqual([
1391
+ ["persisted", "user"],
1392
+ [thread.messages[1]!.id, "assistant"],
1393
+ ]);
1394
+ });
1395
+
1396
+ // The tail re-point must not move an append that named its parent, or an
1397
+ // edit issued during a load would silently reattach to the imported tail.
1398
+ it("keeps an explicit parent for a message appended during a load", async () => {
1399
+ const olderMessage = {
1400
+ ...persistedMessage,
1401
+ id: "older",
1402
+ content: [{ type: "text" as const, text: "older" }],
1403
+ };
1404
+ let releaseLoad!: () => void;
1405
+ const loadBarrier = new Promise<void>((resolve) => {
1406
+ releaseLoad = resolve;
1407
+ });
1408
+ const { thread, appended } = createLoadingThread(() =>
1409
+ loadBarrier.then(() => ({
1410
+ headId: "persisted",
1411
+ messages: [
1412
+ { parentId: null, message: olderMessage },
1413
+ { parentId: "older", message: persistedMessage },
1414
+ ],
1415
+ })),
1416
+ );
1417
+
1418
+ thread.__internal_load();
1419
+ expect(thread.isLoading).toBe(true);
1420
+
1421
+ const appendPromise = thread.append({
1422
+ ...userMessage("branched during load"),
1423
+ parentId: "older",
1424
+ });
1425
+ releaseLoad();
1426
+ await appendPromise;
1427
+ await flush();
1428
+
1429
+ expect(appended.map((item) => [item.parentId, item.message.role])).toEqual([
1430
+ ["older", "user"],
1431
+ [thread.messages.at(-2)!.id, "assistant"],
1432
+ ]);
1433
+ });
1434
+
1435
+ it("keeps a message typed during a rejected load", async () => {
1436
+ let rejectLoad!: (error: unknown) => void;
1437
+ const loadBarrier = new Promise<never>((_, reject) => {
1438
+ rejectLoad = reject;
1439
+ });
1440
+ const { thread, appended } = createLoadingThread(() => loadBarrier);
1441
+
1442
+ thread.__internal_load().catch(() => {});
1443
+ const appendPromise = thread.append(userMessage("typed during load"));
1444
+ rejectLoad(new Error("history unavailable"));
1445
+ await appendPromise;
1446
+ await flush();
1447
+
1448
+ expect(thread.messages.map((message) => message.content)).toEqual([
1449
+ [{ type: "text", text: "typed during load" }],
1450
+ [{ type: "text", text: "done" }],
1451
+ ]);
1452
+ expect(appended[0]?.parentId).toBe(null);
1453
+ });
1454
+
1455
+ it("drops a message typed during a load on a detached thread", async () => {
1456
+ let releaseLoad!: () => void;
1457
+ const loadBarrier = new Promise<void>((resolve) => {
1458
+ releaseLoad = resolve;
1459
+ });
1460
+ const run = vi.fn(async () => ({
1461
+ content: [{ type: "text" as const, text: "done" }],
1462
+ }));
1463
+ const { thread, appended } = createLoadingThread(
1464
+ () =>
1465
+ loadBarrier.then(() => ({
1466
+ headId: "persisted",
1467
+ messages: [{ parentId: null, message: persistedMessage }],
1468
+ })),
1469
+ run,
1470
+ );
1471
+
1472
+ thread.__internal_load();
1473
+ const appendPromise = thread.append(userMessage("typed during load"));
1474
+ thread.detach();
1475
+ releaseLoad();
1476
+ await appendPromise;
1477
+ await flush();
1478
+
1479
+ expect(run).not.toHaveBeenCalled();
1480
+ expect(appended).toEqual([]);
1481
+ expect(thread.messages.map((message) => message.id)).toEqual(["persisted"]);
1482
+ });
1483
+ });
1484
+
1485
+ describe("LocalThreadRuntimeCore human-in-the-loop tools", () => {
1486
+ it("pauses on requires-action while a listed tool call has no result", async () => {
1487
+ const { thread, runs } = createApprovalThread(toolCallResult("send_email"));
1488
+
1489
+ await thread.append(userMessage("send an email"));
1490
+ await flush();
1491
+
1492
+ expect(runs).toHaveLength(1);
1493
+ expect(thread.messages.at(-1)?.status?.type).toBe("requires-action");
1494
+ });
1495
+
1496
+ it("does not hold the run for unlisted tool calls", async () => {
1497
+ const { thread, runs } = createApprovalThread(
1498
+ toolCallResult("lookup_weather"),
1499
+ );
1500
+
1501
+ await thread.append(userMessage("what is the weather"));
1502
+ await flush();
1503
+
1504
+ expect(runs).toHaveLength(2);
1505
+ expect(thread.messages.at(-1)?.status?.type).toBe("complete");
1506
+ });
1507
+
1508
+ it("resumes via addToolResult and exposes the result to the adapter", async () => {
1509
+ const { thread, runs } = createApprovalThread(toolCallResult("send_email"));
1510
+
1511
+ await thread.append(userMessage("send an email"));
1512
+ await flush();
1513
+
1514
+ const assistantMessage = thread.messages.at(-1)!;
1515
+ thread.addToolResult({
1516
+ messageId: assistantMessage.id,
1517
+ toolCallId: "call-send_email",
1518
+ toolName: "send_email",
1519
+ result: { approved: true },
1520
+ isError: false,
1521
+ });
1522
+ await flush();
1523
+
1524
+ expect(runs).toHaveLength(2);
1525
+ const resumed = runs[1]!;
1526
+ expect(resumed.messages.at(-1)?.role).toBe("user");
1527
+ const toolCall = resumed
1528
+ .unstable_getMessage()
1529
+ .content.find((part) => part.type === "tool-call");
1530
+ expect(toolCall?.result).toEqual({ approved: true });
1531
+
1532
+ const finalMessage = thread.messages.at(-1)!;
1533
+ expect(finalMessage.status?.type).toBe("complete");
1534
+ expect(finalMessage.content.map((part) => part.type)).toEqual([
1535
+ "tool-call",
1536
+ "text",
1537
+ ]);
1538
+ });
1539
+
1540
+ it.each([
1541
+ ["false", false],
1542
+ ["zero", 0],
1543
+ ["an empty string", ""],
1544
+ ["null", null],
1545
+ ])("resumes when the tool result is %s", async (_label, result) => {
1546
+ const { thread, runs } = createApprovalThread(toolCallResult("send_email"));
1547
+
1548
+ await thread.append(userMessage("send an email"));
1549
+ await flush();
1550
+
1551
+ thread.addToolResult({
1552
+ messageId: thread.messages.at(-1)!.id,
1553
+ toolCallId: "call-send_email",
1554
+ toolName: "send_email",
1555
+ result,
1556
+ isError: false,
1557
+ });
1558
+ await flush();
1559
+
1560
+ expect(runs).toHaveLength(2);
1561
+ const toolCall = runs[1]!
1562
+ .unstable_getMessage()
1563
+ .content.find((part) => part.type === "tool-call");
1564
+ expect(toolCall?.result).toEqual(result);
1565
+ expect(thread.messages.at(-1)?.status?.type).toBe("complete");
1566
+ });
1567
+
1568
+ it.each(["tool result", "approval"])(
1569
+ "resumes with feedback a subscriber adds while the %s is notified",
1570
+ async (answer) => {
1571
+ const runs: ChatModelRunOptions[] = [];
1572
+ const thread = createThread({
1573
+ async run(options) {
1574
+ runs.push(options);
1575
+ if (runs.length === 1)
1576
+ return answer === "approval"
1577
+ ? toolCallResult("deploy", { id: "approval-1" })
1578
+ : toolCallResult("send_email");
1579
+ if (runs.length > 2) throw new Error("resumed more than once");
1580
+ return { content: [{ type: "text", text: "done" }] };
1581
+ },
1582
+ });
1583
+
1584
+ await thread.append(userMessage("send"));
1585
+ const messageId = thread.messages.at(-1)!.id;
1586
+ const unsubscribe = thread.subscribe(() => {
1587
+ const message = thread.messages.find((m) => m.id === messageId);
1588
+ if (
1589
+ message?.status?.type === "requires-action" &&
1590
+ message.metadata.submittedFeedback === undefined
1591
+ ) {
1592
+ thread.submitFeedback({ messageId, type: "positive" });
1593
+ }
1594
+ });
1595
+ if (answer === "approval") {
1596
+ void thread.respondToToolApproval({
1597
+ approvalId: "approval-1",
1598
+ approved: true,
1599
+ });
1600
+ } else {
1601
+ thread.addToolResult({
1602
+ messageId,
1603
+ toolCallId: "call-send_email",
1604
+ toolName: "send_email",
1605
+ result: { sent: true },
1606
+ isError: false,
1607
+ });
1608
+ }
1609
+ unsubscribe();
1610
+ await flush();
1611
+
1612
+ expect(runs).toHaveLength(2);
1613
+ const message = thread.messages.at(-1);
1614
+ expect(message?.status?.type).toBe("complete");
1615
+ expect(message?.metadata.submittedFeedback).toEqual({
1616
+ type: "positive",
1617
+ });
1618
+ },
1619
+ );
1620
+
1621
+ it("continues from feedback that lands before a paused roundtrip returns", async () => {
1622
+ let releaseTeardown!: () => void;
1623
+ const teardown = new Promise<void>((resolve) => {
1624
+ releaseTeardown = resolve;
1625
+ });
1626
+ const runs: ChatModelRunOptions[] = [];
1627
+ const thread = createThread({
1628
+ run(options) {
1629
+ runs.push(options);
1630
+ return (async function* () {
1631
+ if (runs.length > 2) throw new Error("continued more than once");
1632
+ if (runs.length === 2) {
1633
+ yield { content: [{ type: "text", text: "done" }] };
1634
+ return;
1635
+ }
1636
+ yield toolCallResult("lookup_weather");
1637
+ await teardown;
1638
+ })();
1639
+ },
1640
+ });
1641
+
1642
+ const send = thread.append(userMessage("weather"));
1643
+ await flush();
1644
+ const messageId = thread.messages.at(-1)!.id;
1645
+ thread.submitFeedback({ messageId, type: "positive" });
1646
+ releaseTeardown();
1647
+ await send;
1648
+
1649
+ expect(runs).toHaveLength(2);
1650
+ const message = thread.messages.at(-1);
1651
+ expect(message?.status?.type).toBe("complete");
1652
+ expect(message?.metadata.submittedFeedback).toEqual({ type: "positive" });
1653
+ });
1654
+
1655
+ it("continues from feedback that lands while a paused roundtrip returns", async () => {
1656
+ const runs: ChatModelRunOptions[] = [];
1657
+ const thread = createThread({
1658
+ run(options) {
1659
+ runs.push(options);
1660
+ return (async function* () {
1661
+ if (runs.length > 2) throw new Error("continued more than once");
1662
+ if (runs.length === 2) {
1663
+ yield { content: [{ type: "text", text: "done" }] };
1664
+ return;
1665
+ }
1666
+ yield toolCallResult("lookup_weather");
1667
+ const messageId = options.unstable_assistantMessageId!;
1668
+ let depth = 0;
1669
+ const submitFeedback = () => {
1670
+ thread.submitFeedback({ messageId, type: "positive" });
1671
+ if (++depth < 10) queueMicrotask(submitFeedback);
1672
+ };
1673
+ queueMicrotask(submitFeedback);
1674
+ })();
1675
+ },
1676
+ });
329
1677
 
330
- await thread.append(userMessage("send an email"));
331
- await flush();
1678
+ await thread.append(userMessage("weather"));
332
1679
 
333
- expect(runs).toHaveLength(1);
334
- expect(thread.messages.at(-1)?.status?.type).toBe("requires-action");
1680
+ expect(runs).toHaveLength(2);
1681
+ const message = thread.messages.at(-1);
1682
+ expect(message?.status?.type).toBe("complete");
1683
+ expect(message?.metadata.submittedFeedback).toEqual({ type: "positive" });
335
1684
  });
336
1685
 
337
- it("does not hold the run for unlisted tool calls", async () => {
338
- const { thread, runs } = createApprovalThread(
339
- toolCallResult("lookup_weather"),
340
- );
1686
+ it("ends the run when anything but feedback replaces a paused message", async () => {
1687
+ let releaseTeardown!: () => void;
1688
+ const teardown = new Promise<void>((resolve) => {
1689
+ releaseTeardown = resolve;
1690
+ });
1691
+ const runs: ChatModelRunOptions[] = [];
1692
+ const thread = createThread({
1693
+ run(options) {
1694
+ runs.push(options);
1695
+ return (async function* () {
1696
+ if (runs.length > 1)
1697
+ throw new Error("continued after its message was replaced");
1698
+ yield toolCallResult("lookup_weather");
1699
+ await teardown;
1700
+ })();
1701
+ },
1702
+ });
341
1703
 
342
- await thread.append(userMessage("what is the weather"));
1704
+ const send = thread.append(userMessage("weather"));
343
1705
  await flush();
1706
+ const exported = thread.export();
1707
+ thread.import({
1708
+ ...exported,
1709
+ messages: exported.messages.map((item) => ({
1710
+ ...item,
1711
+ message: { ...item.message },
1712
+ })),
1713
+ });
1714
+ releaseTeardown();
1715
+ await send;
344
1716
 
345
- expect(runs).toHaveLength(2);
346
- expect(thread.messages.at(-1)?.status?.type).toBe("complete");
1717
+ expect(runs).toHaveLength(1);
347
1718
  });
1719
+ });
348
1720
 
349
- it("resumes via addToolResult and exposes the result to the adapter", async () => {
1721
+ describe("LocalThreadRuntimeCore addToolResult content", () => {
1722
+ it("stores modelContent and forwards it to the resumed adapter", async () => {
350
1723
  const { thread, runs } = createApprovalThread(toolCallResult("send_email"));
351
1724
 
352
1725
  await thread.append(userMessage("send an email"));
353
1726
  await flush();
354
1727
 
355
- const assistantMessage = thread.messages.at(-1)!;
356
1728
  thread.addToolResult({
357
- messageId: assistantMessage.id,
1729
+ messageId: thread.messages.at(-1)!.id,
358
1730
  toolCallId: "call-send_email",
359
1731
  toolName: "send_email",
360
1732
  result: { approved: true },
361
1733
  isError: false,
1734
+ modelContent: [{ type: "text", text: "Email sent." }],
362
1735
  });
363
1736
  await flush();
364
1737
 
365
- expect(runs).toHaveLength(2);
366
- const resumed = runs[1]!;
367
- expect(resumed.messages.at(-1)?.role).toBe("user");
368
- const toolCall = resumed
1738
+ const resumedToolCall = runs[1]!
369
1739
  .unstable_getMessage()
370
1740
  .content.find((part) => part.type === "tool-call");
371
- expect(toolCall?.result).toEqual({ approved: true });
1741
+ expect(resumedToolCall?.modelContent).toEqual([
1742
+ { type: "text", text: "Email sent." },
1743
+ ]);
372
1744
 
373
- const finalMessage = thread.messages.at(-1)!;
374
- expect(finalMessage.status?.type).toBe("complete");
375
- expect(finalMessage.content.map((part) => part.type)).toEqual([
376
- "tool-call",
377
- "text",
1745
+ const storedToolCall = thread.messages
1746
+ .at(-1)!
1747
+ .content.find((part) => part.type === "tool-call");
1748
+ expect(storedToolCall?.modelContent).toEqual([
1749
+ { type: "text", text: "Email sent." },
378
1750
  ]);
379
1751
  });
380
1752
 
381
- it.each([
382
- ["false", false],
383
- ["zero", 0],
384
- ["an empty string", ""],
385
- ["null", null],
386
- ])("resumes when the tool result is %s", async (_label, result) => {
387
- const { thread, runs } = createApprovalThread(toolCallResult("send_email"));
1753
+ it("keeps a stored artifact when a later result omits it", async () => {
1754
+ const { thread } = createApprovalThread(toolCallResult("send_email"));
388
1755
 
389
1756
  await thread.append(userMessage("send an email"));
390
1757
  await flush();
391
1758
 
1759
+ const messageId = thread.messages.at(-1)!.id;
392
1760
  thread.addToolResult({
393
- messageId: thread.messages.at(-1)!.id,
1761
+ messageId,
394
1762
  toolCallId: "call-send_email",
395
1763
  toolName: "send_email",
396
- result,
1764
+ result: { approved: true },
397
1765
  isError: false,
1766
+ artifact: { draftId: "d-1" },
398
1767
  });
399
1768
  await flush();
400
1769
 
401
- expect(runs).toHaveLength(2);
402
- const toolCall = runs[1]!
403
- .unstable_getMessage()
404
- .content.find((part) => part.type === "tool-call");
405
- expect(toolCall?.result).toEqual(result);
406
- expect(thread.messages.at(-1)?.status?.type).toBe("complete");
1770
+ thread.addToolResult({
1771
+ messageId,
1772
+ toolCallId: "call-send_email",
1773
+ toolName: "send_email",
1774
+ result: { approved: true, sent: true },
1775
+ isError: false,
1776
+ });
1777
+ await flush();
1778
+
1779
+ const storedToolCall = thread.messages
1780
+ .map((message) =>
1781
+ message.content.find(
1782
+ (part): part is ToolCallMessagePart =>
1783
+ part.type === "tool-call" && part.toolCallId === "call-send_email",
1784
+ ),
1785
+ )
1786
+ .find((part): part is ToolCallMessagePart => part !== undefined);
1787
+ expect(storedToolCall?.artifact).toEqual({ draftId: "d-1" });
407
1788
  });
408
1789
  });
409
1790
 
@@ -856,7 +2237,7 @@ describe("LocalThreadRuntimeCore cancellation", () => {
856
2237
  const toolCall = thread.messages
857
2238
  .find((item) => item.id === messageId)
858
2239
  ?.content.find(
859
- (part) =>
2240
+ (part): part is ToolCallMessagePart =>
860
2241
  part.type === "tool-call" && part.toolCallId === "call-send_email",
861
2242
  );
862
2243
  expect(toolCall?.result).toEqual({ approved: true });
@@ -864,9 +2245,10 @@ describe("LocalThreadRuntimeCore cancellation", () => {
864
2245
 
865
2246
  resolveSecond({ content: [{ type: "text", text: "replacement" }] });
866
2247
  await vi.waitFor(() => {
867
- expect(
868
- thread.messages.find((item) => item.id === messageId)?.status.type,
869
- ).toBe("complete");
2248
+ const message = thread.messages.find((item) => item.id === messageId);
2249
+ if (message?.role !== "assistant")
2250
+ throw new Error("expected assistant message");
2251
+ expect(message.status.type).toBe("complete");
870
2252
  });
871
2253
  });
872
2254
 
@@ -915,16 +2297,88 @@ describe("LocalThreadRuntimeCore cancellation", () => {
915
2297
  await appendPromise;
916
2298
 
917
2299
  const message = thread.messages.find((item) => item.id === messageId);
918
- const sendEmail = message?.content.find(
919
- (part) =>
2300
+ if (message?.role !== "assistant")
2301
+ throw new Error("expected assistant message");
2302
+ const sendEmail = message.content.find(
2303
+ (part): part is ToolCallMessagePart =>
920
2304
  part.type === "tool-call" && part.toolCallId === "call-send_email",
921
2305
  );
922
- const deploy = message?.content.find(
923
- (part) => part.type === "tool-call" && part.toolCallId === "call-deploy",
2306
+ const deploy = message.content.find(
2307
+ (part): part is ToolCallMessagePart =>
2308
+ part.type === "tool-call" && part.toolCallId === "call-deploy",
924
2309
  );
925
2310
  expect(sendEmail?.result).toEqual({ approved: true });
926
2311
  expect(deploy?.approval).toEqual({ id: "approval-1" });
927
- expect(message?.status.type).toBe("requires-action");
2312
+ expect(message.status.type).toBe("requires-action");
2313
+ });
2314
+
2315
+ it("keeps a paused run's later chunks after feedback", async () => {
2316
+ let releaseTeardown!: () => void;
2317
+ const teardown = new Promise<void>((resolve) => {
2318
+ releaseTeardown = resolve;
2319
+ });
2320
+ const thread = createThread({
2321
+ async *run() {
2322
+ yield toolCallResult("send_email");
2323
+ await teardown;
2324
+ yield {
2325
+ content: [
2326
+ toolCallPart("send_email"),
2327
+ { type: "text", text: "Waiting for approval." },
2328
+ ],
2329
+ } satisfies ChatModelRunResult;
2330
+ },
2331
+ });
2332
+
2333
+ const appendPromise = thread.append(userMessage("send"));
2334
+ await flush();
2335
+ thread.submitFeedback({
2336
+ messageId: thread.messages.at(-1)!.id,
2337
+ type: "positive",
2338
+ });
2339
+ releaseTeardown();
2340
+ await appendPromise;
2341
+
2342
+ const message = thread.messages.at(-1);
2343
+ expect(message?.status?.type).toBe("requires-action");
2344
+ expect(message?.content).toEqual([
2345
+ toolCallPart("send_email"),
2346
+ { type: "text", text: "Waiting for approval." },
2347
+ ]);
2348
+ expect(message?.metadata.submittedFeedback).toEqual({ type: "positive" });
2349
+ });
2350
+
2351
+ it("cancels a paused run that received feedback", async () => {
2352
+ const runs: ChatModelRunOptions[] = [];
2353
+ const thread = createThread({
2354
+ run(options) {
2355
+ runs.push(options);
2356
+ return (async function* () {
2357
+ if (runs.length > 1) throw new Error("continued after cancel");
2358
+ yield toolCallResult("lookup_weather");
2359
+ await new Promise((resolve) =>
2360
+ options.abortSignal.addEventListener("abort", resolve),
2361
+ );
2362
+ })();
2363
+ },
2364
+ });
2365
+
2366
+ const appendPromise = thread.append(userMessage("weather"));
2367
+ await flush();
2368
+ thread.submitFeedback({
2369
+ messageId: thread.messages.at(-1)!.id,
2370
+ type: "positive",
2371
+ });
2372
+ thread.cancelRun();
2373
+ await appendPromise;
2374
+
2375
+ expect(runs).toHaveLength(1);
2376
+ const message = thread.messages.at(-1);
2377
+ expect(message?.status).toEqual({
2378
+ type: "incomplete",
2379
+ reason: "cancelled",
2380
+ });
2381
+ expect(message?.metadata.submittedFeedback).toEqual({ type: "positive" });
928
2382
  });
929
2383
 
930
2384
  it("ignores a superseded result after its message is removed", async () => {
@@ -1318,6 +2772,38 @@ describe("LocalThreadRuntimeCore tool approval persistence", () => {
1318
2772
  );
1319
2773
  };
1320
2774
 
2775
+ it("persists feedback submitted after the assistant run settles", async () => {
2776
+ const { history, appended, updated } = createHistory();
2777
+ const thread = createThread(
2778
+ {
2779
+ async run() {
2780
+ return { content: [{ type: "text", text: "hello" }] };
2781
+ },
2782
+ },
2783
+ { history },
2784
+ );
2785
+
2786
+ await thread.append(userMessage("hi"));
2787
+ await flush();
2788
+
2789
+ const assistant = appended.find(
2790
+ (item) => item.message.role === "assistant",
2791
+ );
2792
+ if (!assistant) throw new Error("expected persisted assistant message");
2793
+
2794
+ thread.submitFeedback({
2795
+ messageId: assistant.message.id,
2796
+ type: "positive",
2797
+ });
2798
+ await flush();
2799
+
2800
+ expect(updated).toHaveLength(1);
2801
+ expect(updated[0]?.message.id).toBe(assistant.message.id);
2802
+ expect(updated[0]?.message.metadata.submittedFeedback).toEqual({
2803
+ type: "positive",
2804
+ });
2805
+ });
2806
+
1321
2807
  it("persists a run paused for approval and rewrites it once the run finishes", async () => {
1322
2808
  const { history, appended, updated } = createHistory();
1323
2809
  const thread = createApprovalThreadWithHistory(history);
@@ -1339,6 +2825,39 @@ describe("LocalThreadRuntimeCore tool approval persistence", () => {
1339
2825
  expect(updated.at(-1)?.message.status?.type).toBe("complete");
1340
2826
  });
1341
2827
 
2828
+ it("persists an approval answer and feedback given after later turns follow the message", async () => {
2829
+ const { history, updated } = createHistory();
2830
+ const thread = createApprovalThreadWithHistory(history);
2831
+
2832
+ await thread.append(userMessage("send an email"));
2833
+ await flush();
2834
+ const [question, paused] = thread.messages;
2835
+ expect(paused?.status?.type).toBe("requires-action");
2836
+ await thread.append({
2837
+ ...userMessage("and cc my manager"),
2838
+ parentId: paused!.id,
2839
+ });
2840
+ await flush();
2841
+ expect(thread.messages).toHaveLength(4);
2842
+
2843
+ thread.respondToToolApproval({ approvalId: "a1", approved: true });
2844
+ thread.submitFeedback({ messageId: paused!.id, type: "positive" });
2845
+ await flush();
2846
+
2847
+ expect(updated.map((i) => i.message.id)).toEqual([paused!.id, paused!.id]);
2848
+ expect(updated.at(-1)).toMatchObject({
2849
+ parentId: question!.id,
2850
+ message: {
2851
+ content: [
2852
+ expect.objectContaining({
2853
+ approval: expect.objectContaining({ id: "a1", approved: true }),
2854
+ }),
2855
+ ],
2856
+ metadata: { submittedFeedback: { type: "positive" } },
2857
+ },
2858
+ });
2859
+ });
2860
+
1342
2861
  it("keeps the append-only behavior for adapters without update", async () => {
1343
2862
  const { history, appended } = createHistory({ update: false });
1344
2863
  const thread = createApprovalThreadWithHistory(history);
@@ -1851,7 +3370,7 @@ describe("LocalThreadRuntimeCore runs", () => {
1851
3370
  ],
1852
3371
  status: { type: "requires-action", reason: "tool-calls" },
1853
3372
  metadata: {
1854
- steps: [{ usage: { promptTokens: 10, completionTokens: 5 } }],
3373
+ steps: [{ usage: { inputTokens: 10, outputTokens: 5 } }],
1855
3374
  },
1856
3375
  }));
1857
3376
  const thread = createPlainThread({ run }, { maxSteps: 1 });
@@ -1913,7 +3432,7 @@ describe("LocalThreadRuntimeCore runs", () => {
1913
3432
  run: async () => ({ content: [] }),
1914
3433
  };
1915
3434
  const thread = createPlainThread(adapter);
1916
- const load = vi.fn(async () => ({
3435
+ const load = vi.fn<ThreadHistoryAdapter["load"]>(async () => ({
1917
3436
  headId: "restored",
1918
3437
  messages: [
1919
3438
  {
@@ -1922,6 +3441,7 @@ describe("LocalThreadRuntimeCore runs", () => {
1922
3441
  id: "restored",
1923
3442
  role: "user" as const,
1924
3443
  content: [{ type: "text" as const, text: "hello" }],
3444
+ attachments: [],
1925
3445
  createdAt: new Date(0),
1926
3446
  metadata: { custom: {} },
1927
3447
  },
@@ -1950,7 +3470,7 @@ describe("LocalThreadRuntimeCore runs", () => {
1950
3470
  run: async () => ({ content: [] }),
1951
3471
  };
1952
3472
  const thread = createPlainThread(adapter);
1953
- const load = vi.fn(async () => ({
3473
+ const load = vi.fn<ThreadHistoryAdapter["load"]>(async () => ({
1954
3474
  headId: "restored",
1955
3475
  messages: [
1956
3476
  {
@@ -1959,6 +3479,7 @@ describe("LocalThreadRuntimeCore runs", () => {
1959
3479
  id: "restored",
1960
3480
  role: "user" as const,
1961
3481
  content: [{ type: "text" as const, text: "hello" }],
3482
+ attachments: [],
1962
3483
  createdAt: new Date(0),
1963
3484
  metadata: { custom: {} },
1964
3485
  },
@@ -2219,4 +3740,180 @@ describe("LocalThreadRuntimeCore imported approvals", () => {
2219
3740
  expect(runs).toHaveLength(1);
2220
3741
  expect(thread.messages.at(-1)?.status?.type).toBe("complete");
2221
3742
  });
3743
+
3744
+ it("resumes an imported preliminary tool call and clears its marker", async () => {
3745
+ const { thread, runs } = createImportedThread([
3746
+ { role: "user", content: [{ type: "text", text: "send an email" }] },
3747
+ {
3748
+ role: "assistant",
3749
+ status: { type: "requires-action", reason: "tool-calls" },
3750
+ content: [
3751
+ {
3752
+ type: "tool-call",
3753
+ toolCallId: "call-send_email",
3754
+ toolName: "send_email",
3755
+ args: {},
3756
+ argsText: "{}",
3757
+ result: { preview: true },
3758
+ isPreliminary: true,
3759
+ },
3760
+ ],
3761
+ },
3762
+ ]);
3763
+
3764
+ thread.addToolResult({
3765
+ messageId: thread.messages.at(-1)!.id,
3766
+ toolCallId: "call-send_email",
3767
+ toolName: "send_email",
3768
+ result: { sent: true },
3769
+ isError: false,
3770
+ });
3771
+ await flush();
3772
+
3773
+ expect(runs).toHaveLength(1);
3774
+ const toolCall = runs[0]!
3775
+ .unstable_getMessage()
3776
+ .content.find((part) => part.type === "tool-call");
3777
+ expect(toolCall).toMatchObject({ result: { sent: true } });
3778
+ expect(toolCall?.type === "tool-call" && toolCall.isPreliminary).toBe(
3779
+ undefined,
3780
+ );
3781
+ });
3782
+
3783
+ it("persists a final result for a preliminary tool call that remains paused", async () => {
3784
+ const updated: ExportedMessageRepositoryItem[] = [];
3785
+ const { thread } = createImportedThread(
3786
+ [
3787
+ { role: "user", content: [{ type: "text", text: "send an email" }] },
3788
+ {
3789
+ role: "assistant",
3790
+ status: { type: "requires-action", reason: "tool-calls" },
3791
+ content: [
3792
+ {
3793
+ type: "tool-call",
3794
+ toolCallId: "call-send_email",
3795
+ toolName: "send_email",
3796
+ args: {},
3797
+ argsText: "{}",
3798
+ result: { preview: true },
3799
+ isPreliminary: true,
3800
+ },
3801
+ {
3802
+ type: "tool-call",
3803
+ toolCallId: "call-other",
3804
+ toolName: "send_email",
3805
+ args: {},
3806
+ argsText: "{}",
3807
+ },
3808
+ ],
3809
+ },
3810
+ ],
3811
+ {
3812
+ async load() {
3813
+ return { messages: [] };
3814
+ },
3815
+ async append() {},
3816
+ async update(item) {
3817
+ updated.push(item);
3818
+ },
3819
+ },
3820
+ );
3821
+
3822
+ thread.addToolResult({
3823
+ messageId: thread.messages.at(-1)!.id,
3824
+ toolCallId: "call-send_email",
3825
+ toolName: "send_email",
3826
+ result: { sent: true },
3827
+ isError: false,
3828
+ });
3829
+ await flush();
3830
+
3831
+ expect(updated).toHaveLength(1);
3832
+ const toolCall = updated[0]!.message.content.find(
3833
+ (part) => part.type === "tool-call",
3834
+ );
3835
+ expect(toolCall).toMatchObject({ result: { sent: true } });
3836
+ expect(toolCall?.type === "tool-call" && toolCall.isPreliminary).toBe(
3837
+ undefined,
3838
+ );
3839
+ });
3840
+ });
3841
+
3842
+ describe("LocalThreadRuntimeCore message queue", () => {
3843
+ const createQueuedThread = () => {
3844
+ const dispatched: string[] = [];
3845
+ let release!: () => void;
3846
+ let gate = new Promise<void>((resolve) => (release = resolve));
3847
+
3848
+ const core = new LocalRuntimeCore(
3849
+ {
3850
+ adapters: {
3851
+ chatModel: {
3852
+ async run(options) {
3853
+ const last = options.messages.at(-1);
3854
+ const text = last?.content
3855
+ .filter((part) => part.type === "text")
3856
+ .map((part) => (part as { text: string }).text)
3857
+ .join("");
3858
+ dispatched.push(text ?? "");
3859
+ await gate;
3860
+ return { content: [{ type: "text", text: "ok" }] };
3861
+ },
3862
+ },
3863
+ },
3864
+ unstable_enableMessageQueue: true,
3865
+ },
3866
+ undefined,
3867
+ );
3868
+
3869
+ // the in-flight run already awaits the current gate, so the next gate has
3870
+ // to be installed before releasing it or the run dispatched next resolves
3871
+ // against an already-settled promise
3872
+ const releaseRun = async () => {
3873
+ const releaseCurrent = release;
3874
+ gate = new Promise<void>((resolve) => (release = resolve));
3875
+ releaseCurrent();
3876
+ await flush();
3877
+ };
3878
+
3879
+ return {
3880
+ thread: core.threads.getMainThreadRuntimeCore(),
3881
+ dispatched,
3882
+ releaseRun,
3883
+ };
3884
+ };
3885
+
3886
+ it("keeps the steer lane for implicit sends during back-to-back queued runs", async () => {
3887
+ const { thread, dispatched, releaseRun } = createQueuedThread();
3888
+ // the queue lane is only taken for a message appended onto the tail
3889
+ const appendToTail = (text: string, steer?: boolean) =>
3890
+ void thread.append({
3891
+ ...userMessage(text),
3892
+ parentId: thread.messages.at(-1)?.id ?? null,
3893
+ ...(steer !== undefined && { steer }),
3894
+ });
3895
+
3896
+ appendToTail("first");
3897
+ await flush();
3898
+ expect(dispatched).toEqual(["first"]);
3899
+
3900
+ // buffers behind the running dispatch, so releasing "first" dispatches it
3901
+ appendToTail("second", false);
3902
+ await flush();
3903
+ await releaseRun();
3904
+ expect(dispatched).toEqual(["first", "second"]);
3905
+
3906
+ // "second" is a queue-dispatched run in flight, so an implicit send steers
3907
+ // ahead of a bulk item even though it is appended after it
3908
+ appendToTail("bulk", false);
3909
+ appendToTail("implicit");
3910
+ await flush();
3911
+ await releaseRun();
3912
+
3913
+ expect(dispatched).toEqual(["first", "second", "implicit"]);
3914
+
3915
+ await releaseRun();
3916
+ expect(dispatched).toEqual(["first", "second", "implicit", "bulk"]);
3917
+ await releaseRun();
3918
+ });
2222
3919
  });