@assistant-ui/core 0.3.16 → 0.3.18

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 (474) hide show
  1. package/dist/adapters/attachment.d.ts.map +1 -1
  2. package/dist/adapters/attachment.js +5 -4
  3. package/dist/adapters/attachment.js.map +1 -1
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.js +2 -2
  6. package/dist/index.js.map +1 -1
  7. package/dist/internal.d.ts +4 -2
  8. package/dist/internal.js +4 -2
  9. package/dist/model-context/frame/host.d.ts.map +1 -1
  10. package/dist/model-context/frame/host.js +10 -4
  11. package/dist/model-context/frame/host.js.map +1 -1
  12. package/dist/model-context/frame/provider.d.ts +5 -0
  13. package/dist/model-context/frame/provider.d.ts.map +1 -1
  14. package/dist/model-context/frame/provider.js +101 -28
  15. package/dist/model-context/frame/provider.js.map +1 -1
  16. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  17. package/dist/model-context/interactable-composer-metadata.js +16 -9
  18. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  19. package/dist/model-context/registry.d.ts.map +1 -1
  20. package/dist/model-context/registry.js +3 -5
  21. package/dist/model-context/registry.js.map +1 -1
  22. package/dist/model-context/tool-call-text.d.ts +26 -0
  23. package/dist/model-context/tool-call-text.d.ts.map +1 -0
  24. package/dist/model-context/tool-call-text.js +18 -0
  25. package/dist/model-context/tool-call-text.js.map +1 -0
  26. package/dist/model-context/types.d.ts.map +1 -1
  27. package/dist/model-context/types.js +5 -4
  28. package/dist/model-context/types.js.map +1 -1
  29. package/dist/react/RuntimeAdapter.js +1 -1
  30. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  31. package/dist/react/adapters/LocalStorageThreadListAdapter.js +1 -1
  32. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  33. package/dist/react/client/AdaptedRemoteThread.js +1 -1
  34. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  35. package/dist/react/client/InMemoryThreadList.js +63 -57
  36. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  37. package/dist/react/client/Interactables.d.ts.map +1 -1
  38. package/dist/react/client/Interactables.js +25 -28
  39. package/dist/react/client/Interactables.js.map +1 -1
  40. package/dist/react/client/RemoteThreadList.d.ts +16 -0
  41. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  42. package/dist/react/client/RemoteThreadList.js +354 -242
  43. package/dist/react/client/RemoteThreadList.js.map +1 -1
  44. package/dist/react/client/Tools.d.ts.map +1 -1
  45. package/dist/react/client/Tools.js +24 -18
  46. package/dist/react/client/Tools.js.map +1 -1
  47. package/dist/react/index.d.ts +2 -1
  48. package/dist/react/index.js +2 -1
  49. package/dist/react/interactables-legacy/Interactables.js +246 -125
  50. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  51. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  52. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  53. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  54. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  55. package/dist/react/model-context/makeAssistantTool.js +1 -1
  56. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  57. package/dist/react/model-context/toolbox.d.ts +2 -14
  58. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  59. package/dist/react/model-context/toolbox.js +1 -13
  60. package/dist/react/model-context/toolbox.js.map +1 -1
  61. package/dist/react/model-context/useAssistantContext.js +37 -7
  62. package/dist/react/model-context/useAssistantContext.js.map +1 -1
  63. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  64. package/dist/react/model-context/useAssistantTool.js +1 -1
  65. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  66. package/dist/react/model-context/useAuiToolOverrides.js +31 -5
  67. package/dist/react/model-context/useAuiToolOverrides.js.map +1 -1
  68. package/dist/react/model-context/useInlineRender.d.ts.map +1 -1
  69. package/dist/react/model-context/useInlineRender.js +10 -36
  70. package/dist/react/model-context/useInlineRender.js.map +1 -1
  71. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  72. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  73. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  74. package/dist/react/primitive-hooks/useMessageError.d.ts.map +1 -1
  75. package/dist/react/primitive-hooks/useMessageError.js +2 -8
  76. package/dist/react/primitive-hooks/useMessageError.js.map +1 -1
  77. package/dist/react/primitive-hooks/useThreadListLoadMore.d.ts.map +1 -1
  78. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -4
  79. package/dist/react/primitive-hooks/useThreadListLoadMore.js.map +1 -1
  80. package/dist/react/primitives/composer/ComposerAttachments.d.ts.map +1 -1
  81. package/dist/react/primitives/composer/ComposerAttachments.js +4 -3
  82. package/dist/react/primitives/composer/ComposerAttachments.js.map +1 -1
  83. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  84. package/dist/react/primitives/composer/ComposerQueue.js +4 -3
  85. package/dist/react/primitives/composer/ComposerQueue.js.map +1 -1
  86. package/dist/react/primitives/message/MessageAttachments.d.ts.map +1 -1
  87. package/dist/react/primitives/message/MessageAttachments.js +7 -6
  88. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  89. package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
  90. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  91. package/dist/react/primitives/message/MessageParts.js +3 -3
  92. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  93. package/dist/react/primitives/threadList/ThreadListItems.d.ts.map +1 -1
  94. package/dist/react/primitives/threadList/ThreadListItems.js +5 -4
  95. package/dist/react/primitives/threadList/ThreadListItems.js.map +1 -1
  96. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +6 -4
  97. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  98. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +59 -29
  99. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  100. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +11 -6
  101. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  102. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +160 -143
  103. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  104. package/dist/react/runtimes/RemoteThreadResource.d.ts +2 -6
  105. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  106. package/dist/react/runtimes/RemoteThreadResource.js +15 -13
  107. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  108. package/dist/react/runtimes/assistant-transport/useLatestRef.js +2 -2
  109. package/dist/react/runtimes/assistant-transport/useLatestRef.js.map +1 -1
  110. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +4 -1
  111. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  112. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +424 -52
  113. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  114. package/dist/react/runtimes/cloud/auiV0.d.ts +5 -1
  115. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  116. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  117. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts +29 -0
  118. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -0
  119. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +160 -0
  120. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -0
  121. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts +1 -9
  122. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.d.ts.map +1 -1
  123. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js +16 -153
  124. package/dist/react/runtimes/cloud/useCloudThreadListAdapter.js.map +1 -1
  125. package/dist/react/runtimes/external-message-converter.js +1 -1
  126. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  127. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  128. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  129. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  130. package/dist/react/runtimes/useRemoteThreadListRuntime.js +77 -29
  131. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  132. package/dist/react/types/MessagePartComponentTypes.d.ts +7 -3
  133. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  134. package/dist/react/types/scopes/tools.d.ts +2 -0
  135. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  136. package/dist/runtime/api/message-part-runtime.d.ts +2 -2
  137. package/dist/runtime/api/message-part-runtime.d.ts.map +1 -1
  138. package/dist/runtime/api/message-part-runtime.js +1 -1
  139. package/dist/runtime/api/message-part-runtime.js.map +1 -1
  140. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  141. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  142. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  143. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  144. package/dist/runtime/api/thread-list-runtime.d.ts +12 -1
  145. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  146. package/dist/runtime/api/thread-list-runtime.js +23 -14
  147. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  148. package/dist/runtime/api/thread-runtime.d.ts +5 -1
  149. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  150. package/dist/runtime/api/thread-runtime.js +1 -1
  151. package/dist/runtime/api/thread-runtime.js.map +1 -1
  152. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  153. package/dist/runtime/base/base-composer-runtime-core.js +16 -12
  154. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  155. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -2
  156. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  157. package/dist/runtime/base/base-thread-runtime-core.js +8 -7
  158. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  159. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  160. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  161. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +18 -3
  162. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  163. package/dist/runtime/interfaces/thread-runtime-core.d.ts +15 -1
  164. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  165. package/dist/runtime/utils/auto-status.d.ts +1 -1
  166. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  167. package/dist/runtime/utils/auto-status.js +6 -2
  168. package/dist/runtime/utils/auto-status.js.map +1 -1
  169. package/dist/runtime/utils/external-message-conversion.d.ts +3 -2
  170. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -1
  171. package/dist/runtime/utils/external-message-conversion.js +5 -4
  172. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  173. package/dist/runtime/utils/message-repository.d.ts +1 -0
  174. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  175. package/dist/runtime/utils/message-repository.js +15 -6
  176. package/dist/runtime/utils/message-repository.js.map +1 -1
  177. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts +6 -3
  178. package/dist/runtime/utils/resolveToolApprovalResponse.d.ts.map +1 -1
  179. package/dist/runtime/utils/resolveToolApprovalResponse.js +12 -3
  180. package/dist/runtime/utils/resolveToolApprovalResponse.js.map +1 -1
  181. package/dist/runtime/utils/thread-message-like.d.ts +2 -10
  182. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  183. package/dist/runtime/utils/thread-message-like.js +1 -1
  184. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  185. package/dist/runtime/utils/tool-call-tree.d.ts +39 -0
  186. package/dist/runtime/utils/tool-call-tree.d.ts.map +1 -0
  187. package/dist/runtime/utils/tool-call-tree.js +85 -0
  188. package/dist/runtime/utils/tool-call-tree.js.map +1 -0
  189. package/dist/runtimes/external-store/external-store-adapter.d.ts +29 -2
  190. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  191. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  192. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  193. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  194. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -1
  195. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  196. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +34 -28
  197. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  198. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts +1 -1
  199. package/dist/runtimes/inert/InertThreadRuntimeCore.d.ts.map +1 -1
  200. package/dist/runtimes/inert/InertThreadRuntimeCore.js.map +1 -1
  201. package/dist/runtimes/internal.d.ts +2 -2
  202. package/dist/runtimes/internal.js +2 -2
  203. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -1
  204. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  205. package/dist/runtimes/local/local-thread-runtime-core.js +3 -1
  206. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  207. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  208. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  209. package/dist/runtimes/remote-thread-list/remote-thread-state.js +148 -43
  210. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  211. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  212. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  213. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  214. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  215. package/dist/runtimes/remote-thread-list/title.d.ts +11 -0
  216. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -0
  217. package/dist/runtimes/remote-thread-list/title.js +14 -0
  218. package/dist/runtimes/remote-thread-list/title.js.map +1 -0
  219. package/dist/runtimes/remote-thread-list/types.d.ts +16 -5
  220. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  221. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts +31 -8
  222. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  223. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +97 -60
  224. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  225. package/dist/store/clients/external-thread.d.ts +1 -1
  226. package/dist/store/clients/external-thread.d.ts.map +1 -1
  227. package/dist/store/clients/external-thread.js +30 -7
  228. package/dist/store/clients/external-thread.js.map +1 -1
  229. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  230. package/dist/store/clients/model-context-client.js +2 -1
  231. package/dist/store/clients/model-context-client.js.map +1 -1
  232. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  233. package/dist/store/clients/single-thread-list.js +2 -1
  234. package/dist/store/clients/single-thread-list.js.map +1 -1
  235. package/dist/store/clients/suggestions.d.ts.map +1 -1
  236. package/dist/store/clients/suggestions.js +19 -6
  237. package/dist/store/clients/suggestions.js.map +1 -1
  238. package/dist/store/clients/thread-message-client.js +1 -1
  239. package/dist/store/clients/thread-selection-events.js +1 -1
  240. package/dist/store/index.d.ts +2 -1
  241. package/dist/store/index.js +2 -1
  242. package/dist/store/internal.d.ts +2 -2
  243. package/dist/store/internal.js +2 -2
  244. package/dist/store/primitive-predicates.d.ts +4 -1
  245. package/dist/store/primitive-predicates.d.ts.map +1 -1
  246. package/dist/store/primitive-predicates.js +9 -1
  247. package/dist/store/primitive-predicates.js.map +1 -1
  248. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  249. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  250. package/dist/store/runtime-clients/composer-runtime-client.js +98 -62
  251. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  252. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  253. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  254. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  255. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  256. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  257. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +2 -2
  258. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  259. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  260. package/dist/store/runtime-clients/thread-list-runtime-client.js +128 -107
  261. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  262. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  263. package/dist/store/runtime-clients/thread-runtime-client.js +194 -122
  264. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  265. package/dist/store/runtime-clients/useSubscribable.d.ts +3 -1
  266. package/dist/store/runtime-clients/useSubscribable.d.ts.map +1 -1
  267. package/dist/store/runtime-clients/useSubscribable.js +1 -1
  268. package/dist/store/runtime-clients/useSubscribable.js.map +1 -1
  269. package/dist/store/scopes/composer.d.ts +10 -2
  270. package/dist/store/scopes/composer.d.ts.map +1 -1
  271. package/dist/store/scopes/message.d.ts +30 -1
  272. package/dist/store/scopes/message.d.ts.map +1 -1
  273. package/dist/store/scopes/part.d.ts +6 -3
  274. package/dist/store/scopes/part.d.ts.map +1 -1
  275. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  276. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  277. package/dist/store/scopes/thread.d.ts +8 -0
  278. package/dist/store/scopes/thread.d.ts.map +1 -1
  279. package/dist/store/scopes/threads.d.ts +1 -0
  280. package/dist/store/scopes/threads.d.ts.map +1 -1
  281. package/dist/subscribable/subscribable.d.ts +8 -1
  282. package/dist/subscribable/subscribable.d.ts.map +1 -1
  283. package/dist/subscribable/subscribable.js +24 -16
  284. package/dist/subscribable/subscribable.js.map +1 -1
  285. package/dist/types/attachment.d.ts +2 -1
  286. package/dist/types/attachment.d.ts.map +1 -1
  287. package/dist/types/attachment.js +2 -1
  288. package/dist/types/attachment.js.map +1 -1
  289. package/dist/types/message.d.ts +40 -1
  290. package/dist/types/message.d.ts.map +1 -1
  291. package/dist/types/message.js +7 -1
  292. package/dist/types/message.js.map +1 -1
  293. package/dist/utils/abortable-iterable.d.ts +19 -0
  294. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  295. package/dist/utils/abortable-iterable.js +72 -0
  296. package/dist/utils/abortable-iterable.js.map +1 -0
  297. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  298. package/dist/utils/composite-context-provider.js +20 -2
  299. package/dist/utils/composite-context-provider.js.map +1 -1
  300. package/dist/utils/record.d.ts +11 -0
  301. package/dist/utils/record.d.ts.map +1 -0
  302. package/dist/utils/record.js +12 -0
  303. package/dist/utils/record.js.map +1 -0
  304. package/package.json +12 -17
  305. package/src/adapters/attachment.test.ts +50 -1
  306. package/src/adapters/attachment.ts +7 -4
  307. package/src/index.ts +7 -1
  308. package/src/internal.ts +12 -0
  309. package/src/model-context/frame/host.test.ts +24 -0
  310. package/src/model-context/frame/host.ts +10 -4
  311. package/src/model-context/frame/provider.test.ts +288 -1
  312. package/src/model-context/frame/provider.ts +148 -30
  313. package/src/model-context/interactable-composer-metadata.test.ts +28 -0
  314. package/src/model-context/interactable-composer-metadata.ts +9 -3
  315. package/src/model-context/registry.test.ts +12 -0
  316. package/src/model-context/registry.ts +3 -2
  317. package/src/model-context/tool-call-text.test.ts +67 -0
  318. package/src/model-context/tool-call-text.ts +67 -0
  319. package/src/model-context/types.test.ts +25 -0
  320. package/src/model-context/types.ts +9 -4
  321. package/src/react/AssistantRuntimeProvider.test.tsx +66 -2
  322. package/src/react/RuntimeAdapter.test.ts +34 -0
  323. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +1 -3
  324. package/src/react/client/InMemoryThreadList.ts +1 -0
  325. package/src/react/client/Interactables.test.ts +28 -0
  326. package/src/react/client/Interactables.ts +15 -13
  327. package/src/react/client/RemoteThreadList.background.test.ts +526 -0
  328. package/src/react/client/RemoteThreadList.concurrent.test.tsx +315 -0
  329. package/src/react/client/RemoteThreadList.test.ts +617 -3
  330. package/src/react/client/RemoteThreadList.ts +433 -167
  331. package/src/react/client/Tools.ts +29 -12
  332. package/src/react/index.ts +4 -0
  333. package/src/react/interactables-legacy/Interactables.ts +16 -16
  334. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  335. package/src/react/model-context/makeAssistantTool.ts +2 -2
  336. package/src/react/model-context/modelContextHooks.test.tsx +131 -0
  337. package/src/react/model-context/toolbox.test.ts +9 -9
  338. package/src/react/model-context/toolbox.ts +9 -36
  339. package/src/react/model-context/useAssistantContext.ts +4 -2
  340. package/src/react/model-context/useAssistantTool.ts +2 -2
  341. package/src/react/model-context/useAuiToolOverrides.ts +4 -2
  342. package/src/react/model-context/useInlineRender.test.tsx +48 -0
  343. package/src/react/model-context/useInlineRender.ts +14 -10
  344. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  345. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  346. package/src/react/primitive-hooks/useMessageError.ts +2 -19
  347. package/src/react/primitive-hooks/useThreadListLoadMore.ts +2 -3
  348. package/src/react/primitives/composer/ComposerAttachments.test.tsx +76 -0
  349. package/src/react/primitives/composer/ComposerAttachments.tsx +9 -4
  350. package/src/react/primitives/composer/ComposerQueue.test.tsx +72 -0
  351. package/src/react/primitives/composer/ComposerQueue.tsx +7 -4
  352. package/src/react/primitives/message/MessageAttachments.test.tsx +107 -37
  353. package/src/react/primitives/message/MessageAttachments.tsx +10 -7
  354. package/src/react/primitives/message/MessageGroupedParts.tsx +2 -2
  355. package/src/react/primitives/message/MessageParts.tsx +3 -3
  356. package/src/react/primitives/threadList/ThreadListItems.test.tsx +99 -0
  357. package/src/react/primitives/threadList/ThreadListItems.tsx +8 -5
  358. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  359. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +127 -1
  360. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  361. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +99 -35
  362. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  363. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +274 -0
  364. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +223 -152
  365. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  366. package/src/react/runtimes/RemoteThreadResource.ts +20 -16
  367. package/src/react/runtimes/assistant-transport/transport-scheduling.test.ts +51 -2
  368. package/src/react/runtimes/assistant-transport/useLatestRef.ts +2 -2
  369. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.ts +45 -0
  370. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +546 -5
  371. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +546 -103
  372. package/src/react/runtimes/cloud/auiV0.ts +5 -0
  373. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +103 -0
  374. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +237 -0
  375. package/src/react/runtimes/cloud/useCloudThreadListAdapter.test.tsx +90 -1
  376. package/src/react/runtimes/cloud/useCloudThreadListAdapter.tsx +33 -238
  377. package/src/react/runtimes/external-message-converter.ts +1 -0
  378. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  379. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  380. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  381. package/src/react/runtimes/useRemoteThreadListRuntime.concurrent.test.tsx +216 -0
  382. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  383. package/src/react/runtimes/useRemoteThreadListRuntime.ts +57 -6
  384. package/src/react/types/MessagePartComponentTypes.ts +7 -3
  385. package/src/react/types/scopes/tools.ts +2 -0
  386. package/src/runtime/api/message-part-runtime.ts +3 -3
  387. package/src/runtime/api/message-runtime.test.ts +75 -0
  388. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  389. package/src/runtime/api/thread-list-runtime.ts +46 -12
  390. package/src/runtime/api/thread-runtime.test.ts +78 -0
  391. package/src/runtime/api/thread-runtime.ts +8 -1
  392. package/src/runtime/base/base-composer-runtime-core.ts +36 -8
  393. package/src/runtime/base/base-thread-runtime-core.test.ts +149 -4
  394. package/src/runtime/base/base-thread-runtime-core.ts +16 -8
  395. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  396. package/src/runtime/interfaces/thread-list-runtime-core.ts +25 -2
  397. package/src/runtime/interfaces/thread-runtime-core.ts +17 -1
  398. package/src/runtime/utils/auto-status.test.ts +57 -0
  399. package/src/runtime/utils/auto-status.ts +13 -1
  400. package/src/runtime/utils/external-message-conversion.test.ts +165 -0
  401. package/src/runtime/utils/external-message-conversion.ts +18 -2
  402. package/src/runtime/utils/message-repository.ts +29 -17
  403. package/src/runtime/utils/resolveToolApprovalResponse.test.ts +73 -0
  404. package/src/runtime/utils/resolveToolApprovalResponse.ts +27 -6
  405. package/src/runtime/utils/thread-message-like.ts +3 -11
  406. package/src/runtime/utils/tool-call-tree.test.ts +191 -0
  407. package/src/runtime/utils/tool-call-tree.ts +97 -0
  408. package/src/runtimes/external-store/external-store-adapter.ts +38 -2
  409. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  410. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +56 -33
  411. package/src/runtimes/inert/InertThreadRuntimeCore.ts +1 -1
  412. package/src/runtimes/internal.ts +1 -0
  413. package/src/runtimes/local/local-thread-runtime-core.test.ts +29 -3
  414. package/src/runtimes/local/local-thread-runtime-core.ts +5 -1
  415. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +238 -0
  416. package/src/runtimes/remote-thread-list/remote-thread-state.ts +223 -44
  417. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  418. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  419. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  420. package/src/runtimes/remote-thread-list/title.ts +16 -0
  421. package/src/runtimes/remote-thread-list/types.ts +16 -5
  422. package/src/runtimes/tool-invocations/EDGE_CASES.md +110 -5
  423. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +750 -136
  424. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +144 -91
  425. package/src/store/clients/external-thread.ts +62 -11
  426. package/src/store/clients/model-context-client.ts +5 -1
  427. package/src/store/clients/single-thread-list.ts +1 -0
  428. package/src/store/clients/suggestions.test.ts +195 -1
  429. package/src/store/clients/suggestions.ts +45 -34
  430. package/src/store/index.ts +4 -0
  431. package/src/store/internal.ts +2 -0
  432. package/src/store/primitive-predicates.ts +35 -0
  433. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  434. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  435. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  436. package/src/store/runtime-clients/thread-list-runtime-client.ts +17 -2
  437. package/src/store/runtime-clients/thread-runtime-client.ts +58 -10
  438. package/src/store/runtime-clients/useSubscribable.ts +8 -2
  439. package/src/store/scopes/composer.ts +16 -4
  440. package/src/store/scopes/message.ts +14 -0
  441. package/src/store/scopes/part.ts +6 -3
  442. package/src/store/scopes/thread-list-item.ts +5 -2
  443. package/src/store/scopes/thread.ts +4 -0
  444. package/src/store/scopes/threads.ts +1 -0
  445. package/src/subscribable/subscribable.test.ts +50 -0
  446. package/src/subscribable/subscribable.ts +33 -21
  447. package/src/tests/MessageRepository.test.ts +116 -0
  448. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  449. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  450. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +158 -0
  451. package/src/tests/RemoteThreadListThreadListRuntimeCore-loadMore.test.ts +78 -0
  452. package/src/tests/RemoteThreadListThreadListRuntimeCore-reload.test.ts +52 -0
  453. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  454. package/src/tests/attachment-adapters.test.ts +14 -0
  455. package/src/tests/base-composer-runtime-core.test.ts +39 -0
  456. package/src/tests/external-store-optimistic-id.test.ts +79 -0
  457. package/src/tests/external-store-repository-instance.test.ts +166 -0
  458. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  459. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +49 -34
  460. package/src/tests/external-thread-attachments.test.tsx +224 -1
  461. package/src/tests/external-thread-parity.test.tsx +23 -0
  462. package/src/tests/remote-thread-list-adapter-switch.test.ts +66 -1
  463. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  464. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  465. package/src/tests/thread-switch-events.test.tsx +475 -2
  466. package/src/tests/tools-scope-migration.test.tsx +28 -0
  467. package/src/types/attachment.ts +4 -0
  468. package/src/types/message.ts +50 -2
  469. package/src/utils/abortable-iterable.test.ts +213 -0
  470. package/src/utils/abortable-iterable.ts +93 -0
  471. package/src/utils/composite-context-provider.test.ts +25 -0
  472. package/src/utils/composite-context-provider.ts +23 -2
  473. package/src/utils/record.test.ts +48 -0
  474. package/src/utils/record.ts +11 -0
@@ -83,6 +83,74 @@ describe("chunkExternalMessages", () => {
83
83
  });
84
84
 
85
85
  describe("convertExternalMessageChunk", () => {
86
+ it("keeps separate tool calls without IDs", () => {
87
+ const result = convertExternalMessageChunk(
88
+ {
89
+ inputs: [{}],
90
+ outputs: [
91
+ {
92
+ role: "assistant",
93
+ content: [
94
+ { type: "tool-call", toolName: "weather", args: {} },
95
+ { type: "tool-call", toolName: "search", args: {} },
96
+ ],
97
+ },
98
+ ],
99
+ },
100
+ 0,
101
+ 1,
102
+ false,
103
+ undefined,
104
+ );
105
+
106
+ expect(result.content).toMatchObject([
107
+ { type: "tool-call", toolName: "weather" },
108
+ { type: "tool-call", toolName: "search" },
109
+ ]);
110
+ const calls = result.content.filter((part) => part.type === "tool-call");
111
+ expect(calls[0]!.toolCallId).not.toBe(calls[1]!.toolCallId);
112
+ expect(calls.map((call) => call.toolCallId)).not.toContain("");
113
+ });
114
+
115
+ it("keeps separate tool calls with empty IDs", () => {
116
+ const result = convertExternalMessageChunk(
117
+ {
118
+ inputs: [{}],
119
+ outputs: [
120
+ {
121
+ role: "assistant",
122
+ content: [
123
+ {
124
+ type: "tool-call",
125
+ toolCallId: "",
126
+ toolName: "weather",
127
+ args: {},
128
+ },
129
+ {
130
+ type: "tool-call",
131
+ toolCallId: "",
132
+ toolName: "search",
133
+ args: {},
134
+ },
135
+ ],
136
+ },
137
+ ],
138
+ },
139
+ 0,
140
+ 1,
141
+ false,
142
+ undefined,
143
+ );
144
+
145
+ expect(result.content).toMatchObject([
146
+ { type: "tool-call", toolName: "weather" },
147
+ { type: "tool-call", toolName: "search" },
148
+ ]);
149
+ const calls = result.content.filter((part) => part.type === "tool-call");
150
+ expect(calls[0]?.toolCallId).not.toBe(calls[1]?.toolCallId);
151
+ expect(calls.map((call) => call.toolCallId)).not.toContain("");
152
+ });
153
+
86
154
  it("reuses a cached message when the error status is unchanged", () => {
87
155
  const input = {};
88
156
  const chunk = {
@@ -110,4 +178,101 @@ describe("convertExternalMessageChunk", () => {
110
178
 
111
179
  expect(second).toBe(first);
112
180
  });
181
+
182
+ it("replaces a cached complete status after cancellation", () => {
183
+ const input = {};
184
+ const chunk = {
185
+ inputs: [input],
186
+ outputs: [{ id: "m1", role: "assistant" as const, content: "partial" }],
187
+ };
188
+ const cancelled = new Set(["m1"]);
189
+ const generatedFallbackMessages = new WeakSet<object>();
190
+ const first = convertExternalMessageChunk(chunk, 0, 1, false, undefined, {
191
+ message: undefined,
192
+ generatedFallbackMessages,
193
+ });
194
+
195
+ const second = convertExternalMessageChunk(
196
+ chunk,
197
+ 0,
198
+ 1,
199
+ false,
200
+ undefined,
201
+ { message: first, generatedFallbackMessages },
202
+ cancelled,
203
+ );
204
+
205
+ expect(first.status).toMatchObject({ type: "complete", reason: "unknown" });
206
+ expect(second).not.toBe(first);
207
+ expect(second.status).toMatchObject({
208
+ type: "incomplete",
209
+ reason: "cancelled",
210
+ });
211
+
212
+ const failed = convertExternalMessageChunk(
213
+ chunk,
214
+ 0,
215
+ 1,
216
+ false,
217
+ "failed",
218
+ { message: second, generatedFallbackMessages },
219
+ cancelled,
220
+ );
221
+ expect(failed.status).toMatchObject({
222
+ type: "incomplete",
223
+ reason: "error",
224
+ error: "failed",
225
+ });
226
+ });
227
+
228
+ it("cancels a joined chunk when any joined message was stopped", () => {
229
+ const generatedFallbackMessages = new WeakSet<object>();
230
+ const result = convertExternalMessageChunk(
231
+ {
232
+ inputs: [{}, {}],
233
+ outputs: [
234
+ { id: "m1", role: "assistant" as const, content: "first" },
235
+ { id: "m2", role: "assistant" as const, content: "second" },
236
+ ],
237
+ },
238
+ 0,
239
+ 1,
240
+ false,
241
+ undefined,
242
+ { message: undefined, generatedFallbackMessages },
243
+ new Set(["m2"]),
244
+ );
245
+
246
+ expect(result.status).toMatchObject({
247
+ type: "incomplete",
248
+ reason: "cancelled",
249
+ });
250
+ });
251
+
252
+ it("cancels an earlier message that is no longer the last one", () => {
253
+ const cancelled = new Set(["m1"]);
254
+ const generatedFallbackMessages = new WeakSet<object>();
255
+ const build = (id: string, idx: number) =>
256
+ convertExternalMessageChunk(
257
+ {
258
+ inputs: [{}],
259
+ outputs: [{ id, role: "assistant" as const, content: "text" }],
260
+ },
261
+ idx,
262
+ 2,
263
+ false,
264
+ undefined,
265
+ { message: undefined, generatedFallbackMessages },
266
+ cancelled,
267
+ );
268
+
269
+ expect(build("m1", 0).status).toMatchObject({
270
+ type: "incomplete",
271
+ reason: "cancelled",
272
+ });
273
+ expect(build("m2", 1).status).toMatchObject({
274
+ type: "complete",
275
+ reason: "unknown",
276
+ });
277
+ });
113
278
  });
@@ -46,6 +46,7 @@ export type ExternalMessageConverterMessage =
46
46
  export type ExternalMessageConverterMetadata = {
47
47
  readonly toolStatuses?: Record<string, ToolExecutionStatus>;
48
48
  readonly error?: ReadonlyJSONValue;
49
+ readonly cancelledMessageIds?: ReadonlySet<string>;
49
50
  readonly messageTiming?: Record<string, MessageTiming>;
50
51
  };
51
52
 
@@ -251,7 +252,7 @@ export const joinExternalMessages = (
251
252
 
252
253
  // Add content parts, merging reasoning parts with same parentId
253
254
  for (const part of content) {
254
- if (part.type === "tool-call") {
255
+ if (part.type === "tool-call" && part.toolCallId) {
255
256
  const existingIdx = assistantMessage.content.findIndex(
256
257
  (c) =>
257
258
  c.type === "tool-call" && c.toolCallId === part.toolCallId,
@@ -358,7 +359,10 @@ export const chunkExternalMessages = <T>(
358
359
  return results;
359
360
  };
360
361
 
361
- export const shallowArrayEqual = (a: unknown[], b: unknown[]) => {
362
+ export const shallowArrayEqual = (
363
+ a: readonly unknown[],
364
+ b: readonly unknown[],
365
+ ) => {
362
366
  if (a.length !== b.length) return false;
363
367
  for (let i = 0; i < a.length; i++) {
364
368
  if (a[i] !== b[i]) return false;
@@ -378,9 +382,18 @@ export const convertExternalMessageChunk = <T>(
378
382
  isRunning: boolean,
379
383
  error: ReadonlyJSONValue | undefined,
380
384
  cache?: ExternalMessageConversionCache,
385
+ cancelledMessageIds?: ReadonlySet<string>,
381
386
  ) => {
382
387
  const isLast = idx === chunkCount - 1;
383
388
  const joined = joinExternalMessages(message.outputs);
389
+ const isCancelled =
390
+ cancelledMessageIds !== undefined &&
391
+ message.outputs.some(
392
+ (output) =>
393
+ output.role !== "tool" &&
394
+ output.id != null &&
395
+ cancelledMessageIds.has(output.id),
396
+ );
384
397
  const hasInterruptedToolCalls =
385
398
  typeof joined.content === "object" &&
386
399
  joined.content.some(isInterruptedToolCall);
@@ -393,6 +406,7 @@ export const convertExternalMessageChunk = <T>(
393
406
  hasInterruptedToolCalls,
394
407
  hasPendingToolCalls,
395
408
  isLast ? error : undefined,
409
+ isCancelled,
396
410
  );
397
411
  const fallbackId = `${FALLBACK_ID_PREFIX}${idx}`;
398
412
 
@@ -488,6 +502,8 @@ export const convertExternalMessages = <T extends WeakKey>(
488
502
  chunks.length,
489
503
  isRunning,
490
504
  metadata.error,
505
+ undefined,
506
+ metadata.cancelledMessageIds,
491
507
  ),
492
508
  );
493
509
  return completeExternalMessageConversion(result, metadata.error);
@@ -134,6 +134,16 @@ export class MessageRepository {
134
134
  }
135
135
  }
136
136
 
137
+ private selectPathTo(message: RepositoryMessage) {
138
+ for (
139
+ let current: RepositoryMessage | null = message;
140
+ current;
141
+ current = current.prev
142
+ ) {
143
+ (current.prev ?? this.root).next = current;
144
+ }
145
+ }
146
+
137
147
  private performOp(
138
148
  newParent: RepositoryMessage | null,
139
149
  child: RepositoryMessage,
@@ -182,12 +192,17 @@ export class MessageRepository {
182
192
  child.current.id,
183
193
  ];
184
194
 
185
- if (findHead(child) === this.head || newParentOrRoot.next === null) {
195
+ child.prev = newParent;
196
+
197
+ if (findHead(child) === this.head) {
198
+ this.selectPathTo(child);
199
+ } else if (newParentOrRoot.next === null) {
186
200
  newParentOrRoot.next = child;
201
+ if (this.head === newParentOrRoot) {
202
+ this.head = findHead(child);
203
+ }
187
204
  }
188
205
 
189
- child.prev = newParent;
190
-
191
206
  const newLevel = newParent ? newParent.level + 1 : 0;
192
207
  this.updateLevels(child, newLevel);
193
208
  }
@@ -381,8 +396,7 @@ export class MessageRepository {
381
396
  );
382
397
 
383
398
  const previousHead = this.head;
384
- const prevOrRoot = message.prev ?? this.root;
385
- prevOrRoot.next = message;
399
+ this.selectPathTo(message);
386
400
 
387
401
  this.head = findHead(message);
388
402
 
@@ -423,17 +437,7 @@ export class MessageRepository {
423
437
  }
424
438
 
425
439
  this.head = message;
426
- for (
427
- let current: RepositoryMessage | null = message;
428
- current;
429
- current = current.prev
430
- ) {
431
- if (current.prev) {
432
- current.prev.next = current;
433
- } else {
434
- this.root.next = current;
435
- }
436
- }
440
+ this.selectPathTo(message);
437
441
 
438
442
  this.evictOffBranchOptimisticMessages(previousHead, this.head);
439
443
 
@@ -456,7 +460,15 @@ export class MessageRepository {
456
460
  // Optimistic messages are ephemeral and never persisted. A persisted child
457
461
  // of an optimistic node is re-parented onto its nearest persisted ancestor
458
462
  // so the exported tree never references a skipped id.
459
- for (const [, message] of this.messages) {
463
+ // Import and external-state conversion require parents before children, so
464
+ // the tree is walked in pre-order rather than iterated in insertion order.
465
+ const pending = [...this.root.children].reverse();
466
+ while (pending.length > 0) {
467
+ const message = this.messages.get(pending.pop()!);
468
+ if (!message) continue;
469
+ for (let i = message.children.length - 1; i >= 0; i--) {
470
+ pending.push(message.children[i]!);
471
+ }
460
472
  if (message.current.metadata?.isOptimistic) continue;
461
473
  let prev = message.prev;
462
474
  while (prev && prev.current.metadata?.isOptimistic) {
@@ -88,4 +88,77 @@ describe("resolveToolApprovalResponse", () => {
88
88
  resolveToolApprovalResponse({ id: "a2" }, { optionId: "once" }),
89
89
  ).toThrow('no option with id "once"');
90
90
  });
91
+
92
+ it("resolves a free-form answer as approved and carries the text", () => {
93
+ expect(
94
+ resolveToolApprovalResponse(
95
+ { id: "q1", display: "text" },
96
+ { text: "staging" },
97
+ ),
98
+ ).toEqual({ approvalId: "q1", approved: true, text: "staging" });
99
+ });
100
+
101
+ it("carries the text alongside a chosen option", () => {
102
+ expect(
103
+ resolveToolApprovalResponse(
104
+ {
105
+ id: "q2",
106
+ display: "select",
107
+ allowFreeform: true,
108
+ options: [{ id: "other", kind: "_other" }],
109
+ },
110
+ { optionId: "other", approved: true, text: "somewhere else" },
111
+ ),
112
+ ).toEqual({
113
+ approvalId: "q2",
114
+ approved: true,
115
+ optionId: "other",
116
+ text: "somewhere else",
117
+ });
118
+ });
119
+
120
+ it("throws when the request does not accept a free-form answer", () => {
121
+ expect(() =>
122
+ resolveToolApprovalResponse(approval, { text: "anything" }),
123
+ ).toThrow("does not accept a free-form answer");
124
+ expect(() =>
125
+ resolveToolApprovalResponse(
126
+ { id: "a4", display: "select" },
127
+ { approved: true, text: "anything" },
128
+ ),
129
+ ).toThrow("does not accept a free-form answer");
130
+ });
131
+
132
+ it("refuses to infer approval from a bare answer on a decision", () => {
133
+ expect(() =>
134
+ resolveToolApprovalResponse(
135
+ { id: "a5", display: "decision", allowFreeform: true },
136
+ { text: "not this one" },
137
+ ),
138
+ ).toThrow("is a decision, not a question");
139
+ expect(() =>
140
+ resolveToolApprovalResponse(
141
+ { id: "a6", allowFreeform: true },
142
+ { text: "not this one" },
143
+ ),
144
+ ).toThrow("is a decision, not a question");
145
+ });
146
+
147
+ it("records the answer alongside an explicit decision on a gate", () => {
148
+ expect(
149
+ resolveToolApprovalResponse(
150
+ { id: "a7", display: "decision", allowFreeform: true },
151
+ { approved: false, text: "not this one" },
152
+ ),
153
+ ).toEqual({ approvalId: "a7", approved: false, text: "not this one" });
154
+ });
155
+
156
+ it("keeps a refusal a refusal when the request accepts text", () => {
157
+ expect(
158
+ resolveToolApprovalResponse(
159
+ { id: "q3", allowFreeform: true },
160
+ { approved: false, reason: "not answering" },
161
+ ),
162
+ ).toEqual({ approvalId: "q3", approved: false, reason: "not answering" });
163
+ });
91
164
  });
@@ -1,6 +1,8 @@
1
- import type {
2
- ToolApprovalOption,
3
- ToolApprovalResponse,
1
+ import {
2
+ toolApprovalAcceptsText,
3
+ type ToolApprovalDisplay,
4
+ type ToolApprovalOption,
5
+ type ToolApprovalResponse,
4
6
  } from "../../types/message";
5
7
  import type { RespondToToolApprovalOptions } from "../interfaces/thread-runtime-core";
6
8
 
@@ -12,16 +14,25 @@ const APPROVED_BY_KIND: Record<string, boolean> = {
12
14
  };
13
15
 
14
16
  /**
15
- * Resolves a renderer-facing approval response (boolean or optionId form)
16
- * against the approval's option list into the runtime-facing decision shape.
17
+ * Resolves a renderer-facing approval response (boolean, optionId, or
18
+ * free-form answer) against the approval's request shape into the
19
+ * runtime-facing decision shape.
17
20
  */
18
21
  export const resolveToolApprovalResponse = (
19
22
  approval: {
20
23
  readonly id: string;
24
+ readonly display?: ToolApprovalDisplay;
25
+ readonly allowFreeform?: boolean;
21
26
  readonly options?: readonly ToolApprovalOption[];
22
27
  },
23
28
  response: ToolApprovalResponse,
24
29
  ): RespondToToolApprovalOptions => {
30
+ const text = response.text;
31
+ if (text !== undefined && !toolApprovalAcceptsText(approval))
32
+ throw new Error(
33
+ `Tool approval "${approval.id}" does not accept a free-form answer; the request must declare display "text" or allowFreeform`,
34
+ );
35
+
25
36
  let approved: boolean;
26
37
  let optionId: string | undefined;
27
38
 
@@ -42,14 +53,24 @@ export const resolveToolApprovalResponse = (
42
53
  approved = APPROVED_BY_KIND[option.kind]!;
43
54
  }
44
55
  optionId = option.id;
45
- } else {
56
+ } else if ("approved" in response) {
46
57
  approved = response.approved;
58
+ } else {
59
+ // A bare answer resolves a question, where answering is not refusing. On a
60
+ // decision the approval itself is the authorization, so inferring one from
61
+ // a typed note would let the note authorize the call.
62
+ if (approval.display !== "text" && approval.display !== "select")
63
+ throw new Error(
64
+ `Tool approval "${approval.id}" is a decision, not a question; respond with an explicit approved value, optionally alongside the answer`,
65
+ );
66
+ approved = true;
47
67
  }
48
68
 
49
69
  return {
50
70
  approvalId: approval.id,
51
71
  approved,
52
72
  ...(optionId !== undefined && { optionId }),
73
+ ...(text !== undefined && { text }),
53
74
  ...(response.reason != null && { reason: response.reason }),
54
75
  };
55
76
  };
@@ -23,8 +23,8 @@ import type {
23
23
  MessageTiming,
24
24
  PartProviderMetadata,
25
25
  TextMessagePart,
26
- ToolApprovalOption,
27
26
  ToolCallTiming,
27
+ ToolCallMessagePart,
28
28
  ToolCallMessagePartMcpMetadata,
29
29
  } from "../../types/message";
30
30
  import type {
@@ -66,15 +66,7 @@ export type ThreadMessageLike = {
66
66
  readonly timing?: ToolCallTiming;
67
67
  readonly mcp?: ToolCallMessagePartMcpMetadata;
68
68
  readonly providerMetadata?: PartProviderMetadata;
69
- readonly approval?: {
70
- readonly id: string;
71
- readonly approved?: boolean;
72
- readonly reason?: string;
73
- readonly isAutomatic?: boolean;
74
- readonly options?: readonly ToolApprovalOption[];
75
- readonly optionId?: string;
76
- readonly resolution?: "cancelled" | "expired";
77
- };
69
+ readonly approval?: NonNullable<ToolCallMessagePart["approval"]>;
78
70
  }
79
71
  )[];
80
72
  readonly id?: string | undefined;
@@ -187,7 +179,7 @@ export const fromThreadMessageLike = (
187
179
  const { parentId, messages, ...basePart } = part;
188
180
  const commonProps = {
189
181
  ...basePart,
190
- toolCallId: part.toolCallId ?? `tool-${generateId()}`,
182
+ toolCallId: part.toolCallId || `tool-${generateId()}`,
191
183
  ...(parentId !== undefined && { parentId }),
192
184
  ...(messages !== undefined && { messages }),
193
185
  };
@@ -0,0 +1,191 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type {
3
+ ThreadAssistantMessage,
4
+ ThreadAssistantMessagePart,
5
+ ThreadMessage,
6
+ ToolCallMessagePart,
7
+ } from "../../types/message";
8
+ import {
9
+ iterateToolCallParts,
10
+ mapToolCallPartsDeep,
11
+ walkToolCallTree,
12
+ } from "./tool-call-tree";
13
+
14
+ const toolCall = (
15
+ toolCallId: string,
16
+ messages?: readonly ThreadMessage[],
17
+ ): ToolCallMessagePart => ({
18
+ type: "tool-call",
19
+ toolCallId,
20
+ toolName: "t",
21
+ argsText: "{}",
22
+ args: {},
23
+ ...(messages !== undefined && { messages }),
24
+ });
25
+
26
+ const assistant = (
27
+ id: string,
28
+ content: readonly ThreadAssistantMessagePart[],
29
+ ): ThreadAssistantMessage =>
30
+ ({
31
+ id,
32
+ role: "assistant",
33
+ content,
34
+ status: { type: "complete", reason: "unknown" },
35
+ metadata: {
36
+ unstable_state: null,
37
+ unstable_annotations: [],
38
+ unstable_data: [],
39
+ steps: [],
40
+ custom: {},
41
+ },
42
+ createdAt: new Date(0),
43
+ attachments: [],
44
+ }) as unknown as ThreadAssistantMessage;
45
+
46
+ const user = (id: string): ThreadMessage =>
47
+ ({
48
+ id,
49
+ role: "user",
50
+ content: [{ type: "text", text: "hi" }],
51
+ metadata: { custom: {} },
52
+ createdAt: new Date(0),
53
+ attachments: [],
54
+ }) as unknown as ThreadMessage;
55
+
56
+ // root
57
+ // └─ a ─ nested message "m2"
58
+ // ├─ b ─ nested message "m3"
59
+ // │ └─ c
60
+ // └─ d
61
+ const tree = () => {
62
+ const c = toolCall("c");
63
+ const b = toolCall("b", [assistant("m3", [c])]);
64
+ const d = toolCall("d");
65
+ const a = toolCall("a", [assistant("m2", [b, d])]);
66
+ return { a, b, c, d };
67
+ };
68
+
69
+ describe("walkToolCallTree", () => {
70
+ it("yields every part in document order, each ahead of its descendants", () => {
71
+ const { a } = tree();
72
+ const entries = [
73
+ ...walkToolCallTree([user("m0"), assistant("m1", [a, toolCall("e")])]),
74
+ ];
75
+
76
+ expect(entries.map((entry) => entry.part.toolCallId)).toEqual([
77
+ "a",
78
+ "b",
79
+ "c",
80
+ "d",
81
+ "e",
82
+ ]);
83
+ });
84
+
85
+ it("reports the message that directly holds each part", () => {
86
+ const { a } = tree();
87
+ const entries = [...walkToolCallTree([assistant("m1", [a])])];
88
+
89
+ expect(
90
+ Object.fromEntries(
91
+ entries.map((entry) => [entry.part.toolCallId, entry.messageId]),
92
+ ),
93
+ ).toEqual({ a: "m1", b: "m2", c: "m3", d: "m2" });
94
+ });
95
+
96
+ it("skips messages whose content is not an array", () => {
97
+ const malformed = {
98
+ id: "m1",
99
+ role: "assistant",
100
+ } as unknown as ThreadMessage;
101
+ expect([...walkToolCallTree([malformed])]).toEqual([]);
102
+ });
103
+
104
+ it("skips a non-assistant message carrying a tool-call part", () => {
105
+ const malformed = {
106
+ ...user("m2"),
107
+ content: [toolCall("b")],
108
+ } as unknown as ThreadMessage;
109
+ const a = toolCall("a", [malformed]);
110
+
111
+ expect(
112
+ [...walkToolCallTree([assistant("m1", [a])])].map(
113
+ (entry) => entry.part.toolCallId,
114
+ ),
115
+ ).toEqual(["a"]);
116
+ });
117
+ });
118
+
119
+ describe("iterateToolCallParts", () => {
120
+ it("matches walkToolCallTree over the same content", () => {
121
+ const { a } = tree();
122
+ const content = [a, toolCall("e")];
123
+
124
+ expect([...iterateToolCallParts(content)].map((p) => p.toolCallId)).toEqual(
125
+ [...walkToolCallTree([assistant("m1", content)])].map(
126
+ (entry) => entry.part.toolCallId,
127
+ ),
128
+ );
129
+ });
130
+
131
+ it("ignores non tool-call parts", () => {
132
+ expect([
133
+ ...iterateToolCallParts([
134
+ { type: "text", text: "hi" } as ThreadAssistantMessagePart,
135
+ toolCall("a"),
136
+ ]),
137
+ ]).toHaveLength(1);
138
+ });
139
+ });
140
+
141
+ describe("mapToolCallPartsDeep", () => {
142
+ it("rewrites nested parts and reports the change", () => {
143
+ const { a } = tree();
144
+ const content = [a];
145
+ const { content: next, changed } = mapToolCallPartsDeep(content, (part) =>
146
+ part.toolCallId === "c" ? { ...part, isError: true } : part,
147
+ );
148
+
149
+ expect(changed).toBe(true);
150
+ const parts = [...iterateToolCallParts(next)];
151
+ expect(parts.find((part) => part.toolCallId === "c")?.isError).toBe(true);
152
+ expect(
153
+ parts.find((part) => part.toolCallId === "d")?.isError,
154
+ ).toBeUndefined();
155
+ });
156
+
157
+ it("skips a nested message whose content is not an array", () => {
158
+ const malformed = {
159
+ id: "m2",
160
+ role: "assistant",
161
+ } as unknown as ThreadMessage;
162
+ const a = toolCall("a", [malformed]);
163
+
164
+ expect(() =>
165
+ mapToolCallPartsDeep([a], (part) => ({ ...part, isError: true })),
166
+ ).not.toThrow();
167
+ });
168
+
169
+ it("keeps the original content identity when nothing changed", () => {
170
+ const { a } = tree();
171
+ const content = [a, toolCall("e")];
172
+ const result = mapToolCallPartsDeep(content, (part) => part);
173
+
174
+ expect(result.changed).toBe(false);
175
+ expect(result.content).toBe(content);
176
+ });
177
+
178
+ it("preserves untouched sibling messages by identity", () => {
179
+ const nested = assistant("m2", [toolCall("b")]);
180
+ const sibling = user("m3");
181
+ const a = toolCall("a", [nested, sibling]);
182
+ const { content: next } = mapToolCallPartsDeep([a], (part) =>
183
+ part.toolCallId === "b" ? { ...part, isError: true } : part,
184
+ );
185
+
186
+ const mapped = next[0] as ToolCallMessagePart;
187
+ expect(mapped).not.toBe(a);
188
+ expect(mapped.messages?.[1]).toBe(sibling);
189
+ expect(mapped.messages?.[0]).not.toBe(nested);
190
+ });
191
+ });