@assistant-ui/core 0.3.17 → 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 (284) 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 +2 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/internal.d.ts +3 -2
  7. package/dist/internal.js +3 -2
  8. package/dist/model-context/frame/host.d.ts.map +1 -1
  9. package/dist/model-context/frame/host.js +10 -4
  10. package/dist/model-context/frame/host.js.map +1 -1
  11. package/dist/model-context/frame/provider.d.ts +2 -0
  12. package/dist/model-context/frame/provider.d.ts.map +1 -1
  13. package/dist/model-context/frame/provider.js +30 -7
  14. package/dist/model-context/frame/provider.js.map +1 -1
  15. package/dist/model-context/interactable-composer-metadata.d.ts.map +1 -1
  16. package/dist/model-context/interactable-composer-metadata.js +16 -9
  17. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  18. package/dist/model-context/registry.d.ts.map +1 -1
  19. package/dist/model-context/registry.js +3 -5
  20. package/dist/model-context/registry.js.map +1 -1
  21. package/dist/model-context/types.d.ts.map +1 -1
  22. package/dist/model-context/types.js +5 -4
  23. package/dist/model-context/types.js.map +1 -1
  24. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  25. package/dist/react/client/InMemoryThreadList.js +62 -56
  26. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  27. package/dist/react/client/Interactables.d.ts.map +1 -1
  28. package/dist/react/client/Interactables.js +24 -27
  29. package/dist/react/client/Interactables.js.map +1 -1
  30. package/dist/react/client/RemoteThreadList.d.ts +1 -0
  31. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  32. package/dist/react/client/RemoteThreadList.js +151 -141
  33. package/dist/react/client/RemoteThreadList.js.map +1 -1
  34. package/dist/react/client/Tools.d.ts.map +1 -1
  35. package/dist/react/client/Tools.js +18 -16
  36. package/dist/react/client/Tools.js.map +1 -1
  37. package/dist/react/interactables-legacy/Interactables.js +242 -121
  38. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  39. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -1
  40. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +11 -9
  41. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -1
  42. package/dist/react/model-context/makeAssistantTool.d.ts +2 -2
  43. package/dist/react/model-context/makeAssistantTool.js +1 -1
  44. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  45. package/dist/react/model-context/useAssistantTool.d.ts +2 -2
  46. package/dist/react/model-context/useAssistantTool.js +1 -1
  47. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  48. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  49. package/dist/react/model-context/useToolArgsStatus.js +2 -1
  50. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  51. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  52. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +16 -8
  53. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  54. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +5 -1
  55. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  56. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +103 -103
  57. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  58. package/dist/react/runtimes/RemoteThreadResource.d.ts +1 -0
  59. package/dist/react/runtimes/RemoteThreadResource.d.ts.map +1 -1
  60. package/dist/react/runtimes/RemoteThreadResource.js +13 -11
  61. package/dist/react/runtimes/RemoteThreadResource.js.map +1 -1
  62. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +4 -1
  63. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  64. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +401 -34
  65. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  66. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.d.ts.map +1 -1
  67. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js +2 -1
  68. package/dist/react/runtimes/cloud/createCloudThreadListAdapter.js.map +1 -1
  69. package/dist/react/runtimes/useExternalStoreRuntime.js +75 -46
  70. package/dist/react/runtimes/useExternalStoreRuntime.js.map +1 -1
  71. package/dist/react/runtimes/useRemoteThreadListRuntime.d.ts.map +1 -1
  72. package/dist/react/runtimes/useRemoteThreadListRuntime.js +63 -23
  73. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  74. package/dist/runtime/api/thread-list-item-runtime.d.ts +7 -3
  75. package/dist/runtime/api/thread-list-item-runtime.d.ts.map +1 -1
  76. package/dist/runtime/api/thread-list-item-runtime.js +2 -2
  77. package/dist/runtime/api/thread-list-item-runtime.js.map +1 -1
  78. package/dist/runtime/api/thread-list-runtime.d.ts +3 -0
  79. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  80. package/dist/runtime/api/thread-list-runtime.js +18 -14
  81. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  82. package/dist/runtime/api/thread-runtime.d.ts +4 -0
  83. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  84. package/dist/runtime/api/thread-runtime.js +1 -1
  85. package/dist/runtime/api/thread-runtime.js.map +1 -1
  86. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  87. package/dist/runtime/base/base-composer-runtime-core.js +16 -12
  88. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  89. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  90. package/dist/runtime/base/base-thread-runtime-core.js +6 -5
  91. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  92. package/dist/runtime/interfaces/composer-runtime-core.d.ts +12 -5
  93. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  94. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +4 -1
  95. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  96. package/dist/runtime/utils/external-message-conversion.js +1 -1
  97. package/dist/runtime/utils/external-message-conversion.js.map +1 -1
  98. package/dist/runtime/utils/message-repository.d.ts +1 -0
  99. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  100. package/dist/runtime/utils/message-repository.js +15 -6
  101. package/dist/runtime/utils/message-repository.js.map +1 -1
  102. package/dist/runtime/utils/thread-message-like.js +1 -1
  103. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  104. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  105. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +21 -27
  106. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  107. package/dist/runtimes/internal.d.ts +2 -2
  108. package/dist/runtimes/internal.js +2 -2
  109. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +18 -1
  110. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  111. package/dist/runtimes/remote-thread-list/remote-thread-state.js +130 -42
  112. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  113. package/dist/runtimes/remote-thread-list/title-generation.d.ts +49 -0
  114. package/dist/runtimes/remote-thread-list/title-generation.d.ts.map +1 -0
  115. package/dist/runtimes/remote-thread-list/title-generation.js +197 -0
  116. package/dist/runtimes/remote-thread-list/title-generation.js.map +1 -0
  117. package/dist/runtimes/remote-thread-list/title.d.ts +3 -1
  118. package/dist/runtimes/remote-thread-list/title.d.ts.map +1 -1
  119. package/dist/runtimes/remote-thread-list/title.js +9 -1
  120. package/dist/runtimes/remote-thread-list/title.js.map +1 -1
  121. package/dist/runtimes/remote-thread-list/types.d.ts +10 -0
  122. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  123. package/dist/store/clients/external-thread.d.ts.map +1 -1
  124. package/dist/store/clients/external-thread.js +19 -3
  125. package/dist/store/clients/external-thread.js.map +1 -1
  126. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  127. package/dist/store/clients/model-context-client.js +2 -1
  128. package/dist/store/clients/model-context-client.js.map +1 -1
  129. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  130. package/dist/store/clients/single-thread-list.js +1 -0
  131. package/dist/store/clients/single-thread-list.js.map +1 -1
  132. package/dist/store/clients/suggestions.d.ts.map +1 -1
  133. package/dist/store/clients/suggestions.js +5 -4
  134. package/dist/store/clients/suggestions.js.map +1 -1
  135. package/dist/store/runtime-clients/composer-runtime-client.d.ts +1 -0
  136. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  137. package/dist/store/runtime-clients/composer-runtime-client.js +97 -61
  138. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  139. package/dist/store/runtime-clients/message-runtime-client.d.ts +1 -0
  140. package/dist/store/runtime-clients/message-runtime-client.d.ts.map +1 -1
  141. package/dist/store/runtime-clients/message-runtime-client.js +221 -144
  142. package/dist/store/runtime-clients/message-runtime-client.js.map +1 -1
  143. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  144. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +1 -1
  145. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  146. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  147. package/dist/store/runtime-clients/thread-list-runtime-client.js +54 -52
  148. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  149. package/dist/store/runtime-clients/thread-runtime-client.d.ts.map +1 -1
  150. package/dist/store/runtime-clients/thread-runtime-client.js +193 -121
  151. package/dist/store/runtime-clients/thread-runtime-client.js.map +1 -1
  152. package/dist/store/scopes/composer.d.ts +10 -2
  153. package/dist/store/scopes/composer.d.ts.map +1 -1
  154. package/dist/store/scopes/message.d.ts +30 -1
  155. package/dist/store/scopes/message.d.ts.map +1 -1
  156. package/dist/store/scopes/thread-list-item.d.ts +2 -2
  157. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  158. package/dist/store/scopes/thread.d.ts +8 -0
  159. package/dist/store/scopes/thread.d.ts.map +1 -1
  160. package/dist/store/scopes/threads.d.ts +1 -0
  161. package/dist/store/scopes/threads.d.ts.map +1 -1
  162. package/dist/types/attachment.d.ts +2 -1
  163. package/dist/types/attachment.d.ts.map +1 -1
  164. package/dist/types/attachment.js +2 -1
  165. package/dist/types/attachment.js.map +1 -1
  166. package/dist/utils/abortable-iterable.d.ts +19 -0
  167. package/dist/utils/abortable-iterable.d.ts.map +1 -0
  168. package/dist/utils/abortable-iterable.js +72 -0
  169. package/dist/utils/abortable-iterable.js.map +1 -0
  170. package/dist/utils/composite-context-provider.d.ts.map +1 -1
  171. package/dist/utils/composite-context-provider.js +20 -2
  172. package/dist/utils/composite-context-provider.js.map +1 -1
  173. package/dist/utils/record.d.ts +11 -0
  174. package/dist/utils/record.d.ts.map +1 -0
  175. package/dist/utils/record.js +12 -0
  176. package/dist/utils/record.js.map +1 -0
  177. package/package.json +10 -10
  178. package/src/adapters/attachment.test.ts +50 -1
  179. package/src/adapters/attachment.ts +7 -4
  180. package/src/index.ts +1 -0
  181. package/src/internal.ts +4 -0
  182. package/src/model-context/frame/host.test.ts +24 -0
  183. package/src/model-context/frame/host.ts +10 -4
  184. package/src/model-context/frame/provider.test.ts +71 -0
  185. package/src/model-context/frame/provider.ts +37 -7
  186. package/src/model-context/interactable-composer-metadata.test.ts +28 -0
  187. package/src/model-context/interactable-composer-metadata.ts +9 -3
  188. package/src/model-context/registry.test.ts +12 -0
  189. package/src/model-context/registry.ts +3 -2
  190. package/src/model-context/types.test.ts +25 -0
  191. package/src/model-context/types.ts +9 -4
  192. package/src/react/RuntimeAdapter.test.ts +34 -0
  193. package/src/react/client/InMemoryThreadList.ts +1 -0
  194. package/src/react/client/Interactables.test.ts +28 -0
  195. package/src/react/client/Interactables.ts +15 -13
  196. package/src/react/client/RemoteThreadList.background.test.ts +148 -6
  197. package/src/react/client/RemoteThreadList.concurrent.test.tsx +42 -1
  198. package/src/react/client/RemoteThreadList.test.ts +345 -3
  199. package/src/react/client/RemoteThreadList.ts +166 -110
  200. package/src/react/client/Tools.ts +22 -11
  201. package/src/react/interactables-legacy/Interactables.ts +16 -16
  202. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +9 -10
  203. package/src/react/model-context/makeAssistantTool.ts +2 -2
  204. package/src/react/model-context/useAssistantTool.ts +2 -2
  205. package/src/react/model-context/useToolArgsStatus.test.tsx +30 -0
  206. package/src/react/model-context/useToolArgsStatus.ts +2 -1
  207. package/src/react/runtimes/RemoteThreadListHookInstanceManager.destroy.test.tsx +172 -0
  208. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +33 -0
  209. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +7 -1
  210. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +28 -5
  211. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.load-error.test.ts +132 -0
  212. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.switch-delete.test.ts +105 -0
  213. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +136 -111
  214. package/src/react/runtimes/RemoteThreadResource.test.ts +9 -0
  215. package/src/react/runtimes/RemoteThreadResource.ts +19 -12
  216. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +483 -6
  217. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +504 -68
  218. package/src/react/runtimes/cloud/createCloudThreadListAdapter.test.ts +15 -0
  219. package/src/react/runtimes/cloud/createCloudThreadListAdapter.ts +1 -0
  220. package/src/react/runtimes/useExternalStoreRuntime.lifecycle.test.tsx +41 -0
  221. package/src/react/runtimes/useExternalStoreRuntime.ts +10 -4
  222. package/src/react/runtimes/useLocalRuntime.test.tsx +20 -0
  223. package/src/react/runtimes/useRemoteThreadListRuntime.load-error.test.tsx +108 -0
  224. package/src/react/runtimes/useRemoteThreadListRuntime.ts +33 -0
  225. package/src/runtime/api/message-runtime.test.ts +75 -0
  226. package/src/runtime/api/thread-list-item-runtime.ts +10 -3
  227. package/src/runtime/api/thread-list-runtime.ts +21 -11
  228. package/src/runtime/api/thread-runtime.test.ts +78 -0
  229. package/src/runtime/api/thread-runtime.ts +8 -1
  230. package/src/runtime/base/base-composer-runtime-core.ts +36 -8
  231. package/src/runtime/base/base-thread-runtime-core.test.ts +55 -1
  232. package/src/runtime/base/base-thread-runtime-core.ts +6 -4
  233. package/src/runtime/interfaces/composer-runtime-core.ts +10 -5
  234. package/src/runtime/interfaces/thread-list-runtime-core.ts +5 -1
  235. package/src/runtime/utils/external-message-conversion.test.ts +68 -0
  236. package/src/runtime/utils/external-message-conversion.ts +1 -1
  237. package/src/runtime/utils/message-repository.ts +29 -17
  238. package/src/runtime/utils/thread-message-like.ts +1 -1
  239. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +18 -13
  240. package/src/runtimes/internal.ts +1 -0
  241. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +128 -2
  242. package/src/runtimes/remote-thread-list/remote-thread-state.ts +182 -42
  243. package/src/runtimes/remote-thread-list/title-generation.test.ts +602 -0
  244. package/src/runtimes/remote-thread-list/title-generation.ts +329 -0
  245. package/src/runtimes/remote-thread-list/title.test.ts +62 -0
  246. package/src/runtimes/remote-thread-list/title.ts +13 -0
  247. package/src/runtimes/remote-thread-list/types.ts +10 -0
  248. package/src/store/clients/external-thread.ts +48 -3
  249. package/src/store/clients/model-context-client.ts +5 -1
  250. package/src/store/clients/single-thread-list.ts +1 -0
  251. package/src/store/clients/suggestions.test.ts +40 -0
  252. package/src/store/clients/suggestions.ts +13 -10
  253. package/src/store/runtime-clients/composer-runtime-client.ts +42 -9
  254. package/src/store/runtime-clients/message-runtime-client.ts +54 -6
  255. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +4 -2
  256. package/src/store/runtime-clients/thread-list-runtime-client.ts +1 -0
  257. package/src/store/runtime-clients/thread-runtime-client.ts +58 -10
  258. package/src/store/scopes/composer.ts +16 -4
  259. package/src/store/scopes/message.ts +14 -0
  260. package/src/store/scopes/thread-list-item.ts +5 -2
  261. package/src/store/scopes/thread.ts +4 -0
  262. package/src/store/scopes/threads.ts +1 -0
  263. package/src/tests/MessageRepository.test.ts +116 -0
  264. package/src/tests/OptimisticState-list-race.test.ts +1 -0
  265. package/src/tests/RemoteThreadListThreadListRuntimeCore-initialize-adapter-change.test.ts +38 -0
  266. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +47 -0
  267. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +89 -0
  268. package/src/tests/attachment-adapters.test.ts +14 -0
  269. package/src/tests/base-composer-runtime-core.test.ts +39 -0
  270. package/src/tests/external-store-thread-list-runtime-core.test.ts +83 -0
  271. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +27 -18
  272. package/src/tests/external-thread-attachments.test.tsx +139 -0
  273. package/src/tests/external-thread-parity.test.tsx +23 -0
  274. package/src/tests/remote-thread-list-isLoading.test.ts +1 -0
  275. package/src/tests/thread-list-runtime-getLoadThreadsPromise.test.ts +1 -0
  276. package/src/tests/thread-switch-events.test.tsx +78 -0
  277. package/src/tests/tools-scope-migration.test.tsx +28 -0
  278. package/src/types/attachment.ts +4 -0
  279. package/src/utils/abortable-iterable.test.ts +213 -0
  280. package/src/utils/abortable-iterable.ts +93 -0
  281. package/src/utils/composite-context-provider.test.ts +25 -0
  282. package/src/utils/composite-context-provider.ts +23 -2
  283. package/src/utils/record.test.ts +48 -0
  284. package/src/utils/record.ts +11 -0
@@ -12,7 +12,7 @@ import {
12
12
  * Props used to register a tool from React.
13
13
  *
14
14
  * @deprecated Use a toolkit with `Tools({ toolkit })` and register it via
15
- * `useAui({ tools: Tools({ toolkit }) })` instead. See
15
+ * `AuiConfig({ tools: Tools({ toolkit }) })` on the provider's `config` prop instead. See
16
16
  * https://assistant-ui.com/docs/migrations/toolkit-tools.
17
17
  */
18
18
  export type AssistantToolProps<
@@ -39,7 +39,7 @@ export type AssistantToolProps<
39
39
  * @param tool - Tool definition and name to register.
40
40
  *
41
41
  * @deprecated Use a toolkit with `Tools({ toolkit })` and register it via
42
- * `useAui({ tools: Tools({ toolkit }) })` instead. See
42
+ * `AuiConfig({ tools: Tools({ toolkit }) })` on the provider's `config` prop instead. See
43
43
  * https://assistant-ui.com/docs/migrations/toolkit-tools.
44
44
  *
45
45
  * @example
@@ -0,0 +1,30 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { renderHook } from "@testing-library/react";
4
+ import { describe, expect, it, vi } from "vitest";
5
+
6
+ const part = vi.hoisted(() => ({
7
+ type: "tool-call" as const,
8
+ status: { type: "complete" as const },
9
+ args: JSON.parse('{"__proto__":"value"}') as Record<string, unknown>,
10
+ }));
11
+
12
+ vi.mock("@assistant-ui/store", async (importOriginal) => {
13
+ const actual = await importOriginal<typeof import("@assistant-ui/store")>();
14
+ return {
15
+ ...actual,
16
+ useAuiState: (selector: (state: { part: typeof part }) => unknown) =>
17
+ selector({ part }),
18
+ };
19
+ });
20
+
21
+ import { useToolArgsStatus } from "./useToolArgsStatus";
22
+
23
+ describe("useToolArgsStatus", () => {
24
+ it("reports status for an argument named __proto__", () => {
25
+ const { result } = renderHook(() => useToolArgsStatus());
26
+
27
+ expect(Object.hasOwn(result.current.propStatus, "__proto__")).toBe(true);
28
+ expect(result.current.propStatus.__proto__).toBe("complete");
29
+ });
30
+ });
@@ -4,6 +4,7 @@ import {
4
4
  getPartialJsonObjectFieldState,
5
5
  getPartialJsonObjectMeta,
6
6
  } from "assistant-stream/utils";
7
+ import { nullProtoRecord } from "../../utils/record";
7
8
 
8
9
  type PropFieldStatus = "streaming" | "complete";
9
10
 
@@ -60,7 +61,7 @@ export const useToolArgsStatus = <
60
61
  const isStreaming = statusType === "running";
61
62
  const args = part.args as Record<string, unknown>;
62
63
  const meta = getPartialJsonObjectMeta(args as Record<symbol, unknown>);
63
- const propStatus: Partial<Record<string, PropFieldStatus>> = {};
64
+ const propStatus = nullProtoRecord<PropFieldStatus>();
64
65
 
65
66
  for (const key of Object.keys(args)) {
66
67
  if (meta) {
@@ -0,0 +1,172 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { ThreadListRuntimeCore } from "../../runtime/interfaces/thread-list-runtime-core";
3
+ import type { ThreadRuntimeCore } from "../../runtime/interfaces/thread-runtime-core";
4
+ import { RemoteThreadListHookInstanceManager } from "./RemoteThreadListHookInstanceManager";
5
+
6
+ const makeManager = () =>
7
+ new RemoteThreadListHookInstanceManager(
8
+ () => ({}) as never,
9
+ {} as ThreadListRuntimeCore,
10
+ );
11
+
12
+ // No AdapterSink attaches a runtime here, so the start and restart promises
13
+ // stay pending or reject on stop; neither is what these tests are about.
14
+ const start = (manager: RemoteThreadListHookInstanceManager, id: string) => {
15
+ manager.startThreadRuntime(id).catch(() => {});
16
+ };
17
+ const restart = (manager: RemoteThreadListHookInstanceManager, id: string) => {
18
+ manager.__internal_restartThreadRuntime(id).catch(() => {});
19
+ };
20
+
21
+ const publishedSignal = (
22
+ manager: RemoteThreadListHookInstanceManager,
23
+ id: string,
24
+ ) => {
25
+ const { hostStore } = manager as unknown as {
26
+ hostStore: {
27
+ getState: () => {
28
+ threads: readonly { id: string; destroySignal: AbortSignal }[];
29
+ };
30
+ };
31
+ };
32
+ return hostStore.getState().threads.find((thread) => thread.id === id)
33
+ ?.destroySignal;
34
+ };
35
+
36
+ describe("RemoteThreadListHookInstanceManager destroy signal", () => {
37
+ it("publishes a live signal for each started thread", () => {
38
+ const manager = makeManager();
39
+ start(manager, "thread-1");
40
+
41
+ expect(publishedSignal(manager, "thread-1")?.aborted).toBe(false);
42
+ });
43
+
44
+ it("aborts the thread's signal when its runtime stops", () => {
45
+ const manager = makeManager();
46
+ start(manager, "thread-1");
47
+ const signal = publishedSignal(manager, "thread-1")!;
48
+
49
+ manager.stopThreadRuntime("thread-1");
50
+
51
+ expect(signal.aborted).toBe(true);
52
+ });
53
+
54
+ it("leaves sibling threads untouched when one stops", () => {
55
+ const manager = makeManager();
56
+ start(manager, "thread-1");
57
+ start(manager, "thread-2");
58
+ const sibling = publishedSignal(manager, "thread-2")!;
59
+
60
+ manager.stopThreadRuntime("thread-1");
61
+
62
+ expect(sibling.aborted).toBe(false);
63
+ });
64
+
65
+ it("hands the next generation a fresh signal on restart", () => {
66
+ const manager = makeManager();
67
+ start(manager, "thread-1");
68
+ const first = publishedSignal(manager, "thread-1")!;
69
+
70
+ restart(manager, "thread-1");
71
+ const second = publishedSignal(manager, "thread-1")!;
72
+
73
+ expect(first.aborted).toBe(true);
74
+ expect(second).not.toBe(first);
75
+ expect(second.aborted).toBe(false);
76
+ });
77
+
78
+ it("aborts every thread when the manager is disposed", () => {
79
+ const manager = makeManager();
80
+ start(manager, "thread-1");
81
+ start(manager, "thread-2");
82
+ const signals = [
83
+ publishedSignal(manager, "thread-1")!,
84
+ publishedSignal(manager, "thread-2")!,
85
+ ];
86
+
87
+ manager.__internal_dispose();
88
+
89
+ expect(signals.map((signal) => signal.aborted)).toEqual([true, true]);
90
+ });
91
+ });
92
+
93
+ const makeRunningRuntime = () => {
94
+ const subscribers = new Set<() => void>();
95
+ const eventListeners = new Map<string, Set<() => void>>();
96
+ const runtime = {
97
+ isRunning: true,
98
+ messages: [],
99
+ subscribe: (callback: () => void) => {
100
+ subscribers.add(callback);
101
+ return () => subscribers.delete(callback);
102
+ },
103
+ unstable_on: (event: string, callback: () => void) => {
104
+ let listeners = eventListeners.get(event);
105
+ if (!listeners) {
106
+ listeners = new Set();
107
+ eventListeners.set(event, listeners);
108
+ }
109
+ listeners.add(callback);
110
+ return () => listeners.delete(callback);
111
+ },
112
+ } as unknown as ThreadRuntimeCore & { isRunning: boolean };
113
+
114
+ return {
115
+ runtime,
116
+ // what a stopOnClientDestroy consumer does when the destroy signal fires
117
+ stop: () => {
118
+ runtime.isRunning = false;
119
+ for (const callback of eventListeners.get("runEnd") ?? []) callback();
120
+ for (const callback of subscribers) callback();
121
+ },
122
+ };
123
+ };
124
+
125
+ const publish = (
126
+ manager: RemoteThreadListHookInstanceManager,
127
+ threadId: string,
128
+ runtime: ThreadRuntimeCore,
129
+ ) => {
130
+ const internals = manager as unknown as {
131
+ instances: Map<string, { generation: number }>;
132
+ _publishThreadRuntime: (
133
+ threadId: string,
134
+ runtime: ThreadRuntimeCore,
135
+ generation: number,
136
+ ) => void;
137
+ };
138
+ internals._publishThreadRuntime(
139
+ threadId,
140
+ runtime,
141
+ internals.instances.get(threadId)!.generation,
142
+ );
143
+ };
144
+
145
+ describe("RemoteThreadListHookInstanceManager restart teardown", () => {
146
+ it("keeps the outgoing generation's terminal events off the thread's subscribers", () => {
147
+ const manager = makeManager();
148
+ start(manager, "thread-1");
149
+ const { runtime, stop } = makeRunningRuntime();
150
+ publish(manager, "thread-1", runtime);
151
+
152
+ const signal = publishedSignal(manager, "thread-1")!;
153
+ signal.addEventListener("abort", stop, { once: true });
154
+
155
+ const events: string[] = [];
156
+ manager.__internal_subscribeThreadEvents((event) =>
157
+ events.push(event.type),
158
+ );
159
+ const runningChanges: boolean[] = [];
160
+ manager.__internal_subscribeRunningChanged(() =>
161
+ runningChanges.push(manager.__internal_isThreadRunning("thread-1")),
162
+ );
163
+ expect(manager.__internal_isThreadRunning("thread-1")).toBe(true);
164
+
165
+ restart(manager, "thread-1");
166
+
167
+ expect(signal.aborted).toBe(true);
168
+ expect(events).toEqual([]);
169
+ expect(runningChanges).toEqual([]);
170
+ expect(manager.__internal_isThreadRunning("thread-1")).toBe(true);
171
+ });
172
+ });
@@ -155,6 +155,39 @@ describe("RemoteThreadListHookInstanceManager run tracking", () => {
155
155
  expect(onChange).toHaveBeenCalledTimes(2);
156
156
  });
157
157
 
158
+ it("notifies every running subscriber when one throws", () => {
159
+ const manager = makeManager();
160
+ start(manager, "thread-1");
161
+ const thread = makeRuntime({ isRunning: false });
162
+ publish(manager, "thread-1", thread.runtime);
163
+ const error = new Error("listener failed");
164
+ manager.__internal_subscribeRunningChanged(() => {
165
+ throw error;
166
+ });
167
+ const laterSubscriber = vi.fn();
168
+ manager.__internal_subscribeRunningChanged(laterSubscriber);
169
+
170
+ expect(() => thread.setRunning(true)).toThrow(error);
171
+ expect(laterSubscriber).toHaveBeenCalledOnce();
172
+ });
173
+
174
+ it("notifies every replacement subscriber when one throws", () => {
175
+ const manager = makeManager();
176
+ start(manager, "thread-1");
177
+ publish(manager, "thread-1", makeRuntime().runtime);
178
+ const error = new Error("listener failed");
179
+ manager.__internal_subscribeRuntimeReplaced(() => {
180
+ throw error;
181
+ });
182
+ const laterSubscriber = vi.fn();
183
+ manager.__internal_subscribeRuntimeReplaced(laterSubscriber);
184
+
185
+ expect(() => publish(manager, "thread-1", makeRuntime().runtime)).toThrow(
186
+ error,
187
+ );
188
+ expect(laterSubscriber).toHaveBeenCalledOnce();
189
+ });
190
+
158
191
  it("moves tracking to the runtime a restart publishes", () => {
159
192
  const manager = makeManager();
160
193
  start(manager, "thread-1");
@@ -37,13 +37,19 @@ describe("RemoteThreadListHookInstanceManager", () => {
37
37
  const internals = manager as unknown as {
38
38
  instances: Map<
39
39
  string,
40
- { runtime: typeof runtime; generation: number; isRunning: boolean }
40
+ {
41
+ runtime: typeof runtime;
42
+ generation: number;
43
+ isRunning: boolean;
44
+ destroy: AbortController;
45
+ }
41
46
  >;
42
47
  };
43
48
  internals.instances.set("thread-1", {
44
49
  runtime,
45
50
  generation: 0,
46
51
  isRunning: false,
52
+ destroy: new AbortController(),
47
53
  });
48
54
 
49
55
  const appendPromise = runtime.append({
@@ -24,6 +24,7 @@ import type {
24
24
  import type { Unsubscribe } from "../../types/unsubscribe";
25
25
  import {
26
26
  BaseSubscribable,
27
+ notifySubscribers,
27
28
  WritableSubscribable,
28
29
  } from "../../subscribable/subscribable";
29
30
  import { useSubscribable } from "../../store/runtime-clients/useSubscribable";
@@ -53,6 +54,9 @@ type RemoteThreadListHookInstance = {
53
54
  generation: number;
54
55
  isRunning: boolean;
55
56
  unsubscribeRunning?: Unsubscribe | undefined;
57
+ // Permanent teardown of the thread's runtime, aborted when the thread is
58
+ // stopped or its runtime restarted. A soft unmount leaves it armed.
59
+ destroy: AbortController;
56
60
  };
57
61
 
58
62
  type AdapterSnapshot = {
@@ -61,7 +65,11 @@ type AdapterSnapshot = {
61
65
  };
62
66
 
63
67
  type HostSnapshot = {
64
- threads: readonly { id: string; generation: number }[];
68
+ threads: readonly {
69
+ id: string;
70
+ generation: number;
71
+ destroySignal: AbortSignal;
72
+ }[];
65
73
  hookEpoch: number;
66
74
  };
67
75
 
@@ -129,6 +137,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
129
137
  this.instances.set(threadId, {
130
138
  generation,
131
139
  isRunning: false,
140
+ destroy: new AbortController(),
132
141
  });
133
142
  this._syncHostThreads();
134
143
  }
@@ -141,6 +150,14 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
141
150
  if (!instance) return this.startThreadRuntime(threadId);
142
151
 
143
152
  if (instance.runtime) invalidateThreadRuntime(instance.runtime);
153
+ // Detach before aborting, as stopThreadRuntime does: the abort runs the
154
+ // destroy listeners synchronously, and a listener that stops the outgoing
155
+ // runtime would otherwise emit that generation's terminal events through
156
+ // the subscription the next generation is about to reuse.
157
+ instance.unsubscribeRunning?.();
158
+ instance.unsubscribeRunning = undefined;
159
+ instance.destroy.abort();
160
+ instance.destroy = new AbortController();
144
161
  instance.generation = this.nextGeneration++;
145
162
  this._syncHostThreads();
146
163
  this._notifySubscribers();
@@ -202,7 +219,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
202
219
  }
203
220
  this._notifySubscribers();
204
221
  if (previousRuntime !== undefined && previousRuntime !== runtime) {
205
- for (const callback of this.replacedSubscribers) callback();
222
+ notifySubscribers(this.replacedSubscribers);
206
223
  }
207
224
  }
208
225
 
@@ -254,13 +271,14 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
254
271
  ) {
255
272
  if (instance.isRunning === isRunning) return;
256
273
  instance.isRunning = isRunning;
257
- for (const callback of this.runningSubscribers) callback();
274
+ notifySubscribers(this.runningSubscribers);
258
275
  }
259
276
 
260
277
  public stopThreadRuntime(threadId: string) {
261
278
  const instance = this.instances.get(threadId);
262
279
  if (instance?.runtime) invalidateThreadRuntime(instance.runtime);
263
280
  instance?.unsubscribeRunning?.();
281
+ instance?.destroy.abort();
264
282
  this.instances.delete(threadId);
265
283
  this.pendingThreadAdapters.delete(threadId);
266
284
  this._syncHostThreads();
@@ -302,7 +320,11 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
302
320
  this.hostStore.setState({
303
321
  ...host,
304
322
  threads: Array.from(this.instances.entries()).map(
305
- ([id, { generation }]) => ({ id, generation }),
323
+ ([id, { generation, destroy }]) => ({
324
+ id,
325
+ generation,
326
+ destroySignal: destroy.signal,
327
+ }),
306
328
  ),
307
329
  });
308
330
  }
@@ -312,7 +334,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
312
334
  const adapters = useSubscribable(this.adapterStore);
313
335
  const runtimeHook = this.runtimeHook;
314
336
  return useResources(
315
- threads.map(({ id, generation }) => {
337
+ threads.map(({ id, generation, destroySignal }) => {
316
338
  const threadAdapters = this.pendingThreadAdapters.has(id)
317
339
  ? this.pendingThreadAdapters.get(id)!
318
340
  : (adapters.threadAdapters.get(id) ?? adapters.defaultAdapters);
@@ -326,6 +348,7 @@ export class RemoteThreadListHookInstanceManager extends BaseSubscribable {
326
348
  parentClient,
327
349
  adapters: threadAdapters,
328
350
  publish: this._publish,
351
+ destroySignal,
329
352
  }),
330
353
  );
331
354
  }),
@@ -0,0 +1,132 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import type { RemoteThreadListResponse } from "../../runtimes/remote-thread-list/types";
3
+ import {
4
+ createCore,
5
+ deferred,
6
+ makeAdapter,
7
+ } from "../../tests/remote-thread-list-test-helpers";
8
+
9
+ const loadedThread = {
10
+ status: "regular" as const,
11
+ remoteId: "thread-1",
12
+ externalId: "thread-1",
13
+ title: "Thread 1",
14
+ };
15
+
16
+ describe("RemoteThreadListThreadListRuntimeCore load errors", () => {
17
+ afterEach(() => {
18
+ vi.restoreAllMocks();
19
+ });
20
+
21
+ it("keeps loaded threads and reports a later load failure", async () => {
22
+ vi.spyOn(console, "error").mockImplementation(() => {});
23
+ const error = new Error("network error");
24
+ const list = vi
25
+ .fn<() => Promise<RemoteThreadListResponse>>()
26
+ .mockResolvedValueOnce({ threads: [loadedThread] })
27
+ .mockRejectedValueOnce(error);
28
+ const core = createCore(makeAdapter({ list }));
29
+
30
+ await core.getLoadThreadsPromise();
31
+ await core.reload();
32
+
33
+ expect(core.threadIds).toEqual(["thread-1"]);
34
+ expect(core.loadError).toBe(error);
35
+ expect(core.isLoading).toBe(false);
36
+ });
37
+
38
+ it("removes the previous adapter's threads when its replacement load fails", async () => {
39
+ vi.spyOn(console, "error").mockImplementation(() => {});
40
+ const error = new Error("replacement failed");
41
+ const core = createCore(
42
+ makeAdapter({
43
+ list: vi.fn(async () => ({ threads: [loadedThread] })),
44
+ }),
45
+ );
46
+ await core.getLoadThreadsPromise();
47
+
48
+ const replacement = makeAdapter({
49
+ list: vi.fn(async () => {
50
+ throw error;
51
+ }),
52
+ });
53
+ core.__internal_setOptions({
54
+ adapter: replacement,
55
+ runtimeHook: () => ({}) as never,
56
+ });
57
+ await core.getLoadThreadsPromise();
58
+
59
+ expect(core.threadIds).toEqual([]);
60
+ expect(core.archivedThreadIds).toEqual([]);
61
+ expect(core.getItemById("thread-1")).toBeUndefined();
62
+ expect(core.loadError).toBe(error);
63
+ });
64
+
65
+ it("clears the previous adapter's error before loading its replacement", async () => {
66
+ vi.spyOn(console, "error").mockImplementation(() => {});
67
+ const error = new Error("network error");
68
+ const core = createCore(
69
+ makeAdapter({
70
+ list: vi.fn(async () => {
71
+ throw error;
72
+ }),
73
+ }),
74
+ );
75
+ await core.getLoadThreadsPromise();
76
+ expect(core.loadError).toBe(error);
77
+
78
+ const replacementLoad = deferred<RemoteThreadListResponse>();
79
+ const replacementList = vi.fn(() => replacementLoad.promise);
80
+ core.__internal_setOptions({
81
+ adapter: makeAdapter({ list: replacementList }),
82
+ runtimeHook: () => ({}) as never,
83
+ });
84
+
85
+ expect(replacementList).not.toHaveBeenCalled();
86
+ expect(core.loadError).toBeUndefined();
87
+
88
+ const loadPromise = core.getLoadThreadsPromise();
89
+ replacementLoad.resolve({ threads: [] });
90
+ await loadPromise;
91
+ });
92
+
93
+ it("clears the error when a reload resolves", async () => {
94
+ vi.spyOn(console, "error").mockImplementation(() => {});
95
+ const error = new Error("network error");
96
+ const list = vi
97
+ .fn<() => Promise<RemoteThreadListResponse>>()
98
+ .mockRejectedValueOnce(error)
99
+ .mockResolvedValueOnce({ threads: [loadedThread] });
100
+ const core = createCore(makeAdapter({ list }));
101
+
102
+ await core.getLoadThreadsPromise();
103
+ expect(core.loadError).toBe(error);
104
+
105
+ await core.reload();
106
+
107
+ expect(core.loadError).toBeUndefined();
108
+ expect(core.threadIds).toEqual(["thread-1"]);
109
+ });
110
+
111
+ it("clears the error while a later load is in progress", async () => {
112
+ vi.spyOn(console, "error").mockImplementation(() => {});
113
+ const error = new Error("network error");
114
+ const retry = deferred<RemoteThreadListResponse>();
115
+ const list = vi
116
+ .fn<() => Promise<RemoteThreadListResponse>>()
117
+ .mockRejectedValueOnce(error)
118
+ .mockReturnValueOnce(retry.promise);
119
+ const core = createCore(makeAdapter({ list }));
120
+
121
+ await core.getLoadThreadsPromise();
122
+ expect(core.loadError).toBe(error);
123
+
124
+ const retryPromise = core.reload();
125
+
126
+ expect(core.isLoading).toBe(true);
127
+ expect(core.loadError).toBeUndefined();
128
+
129
+ retry.resolve({ threads: [] });
130
+ await retryPromise;
131
+ });
132
+ });
@@ -120,6 +120,111 @@ describe("RemoteThreadListThreadListRuntimeCore switch/delete ordering", () => {
120
120
  expect(core.getItemById(core.mainThreadId!)).toBeDefined();
121
121
  });
122
122
 
123
+ it("preserves thread cleanup state when remote deletion fails", async () => {
124
+ const error = new Error("delete failed");
125
+ const adapter = makeAdapter({
126
+ list: vi.fn(async () => ({
127
+ threads: [
128
+ {
129
+ status: "regular" as const,
130
+ remoteId: "thread-b",
131
+ externalId: "thread-b",
132
+ title: "Thread B",
133
+ },
134
+ ],
135
+ })),
136
+ delete: vi.fn(async () => {
137
+ throw error;
138
+ }),
139
+ });
140
+ const core = createCore(adapter);
141
+ await core.getLoadThreadsPromise();
142
+ const data = core.getItemById("thread-b")!;
143
+ const internals = core as unknown as {
144
+ _hookManager: { stopThreadRuntime: (id: string) => void };
145
+ _titleStates: Map<string, unknown>;
146
+ };
147
+ const stopThreadRuntime = vi.spyOn(
148
+ internals._hookManager,
149
+ "stopThreadRuntime",
150
+ );
151
+ const titleState = {};
152
+ internals._titleStates.set(data.id, titleState);
153
+
154
+ await expect(core.delete(data.id)).rejects.toBe(error);
155
+
156
+ expect(core.getItemById(data.id)).toBeDefined();
157
+ expect(stopThreadRuntime).not.toHaveBeenCalled();
158
+ expect(internals._titleStates.get(data.id)).toBe(titleState);
159
+ });
160
+
161
+ it("clears the deleted thread's title state on successful deletion", async () => {
162
+ const adapter = makeAdapter({
163
+ list: vi.fn(async () => ({
164
+ threads: [
165
+ {
166
+ status: "regular" as const,
167
+ remoteId: "thread-b",
168
+ externalId: "thread-b",
169
+ title: "Thread B",
170
+ },
171
+ ],
172
+ })),
173
+ });
174
+ const core = createCore(adapter);
175
+ await core.getLoadThreadsPromise();
176
+ const data = core.getItemById("thread-b")!;
177
+ const internals = core as unknown as {
178
+ _titleStates: Map<string, unknown>;
179
+ };
180
+ internals._titleStates.set(data.id, {});
181
+
182
+ await core.delete(data.id);
183
+
184
+ expect(core.getItemById(data.id)).toBeUndefined();
185
+ expect(internals._titleStates.has(data.id)).toBe(false);
186
+ });
187
+
188
+ it("stops the thread runtime when the adapter changes during a successful deletion", async () => {
189
+ const removal = deferred<void>();
190
+ const adapter = makeAdapter({
191
+ list: vi.fn(async () => ({
192
+ threads: [
193
+ {
194
+ status: "regular" as const,
195
+ remoteId: "thread-b",
196
+ externalId: "thread-b",
197
+ title: "Thread B",
198
+ },
199
+ ],
200
+ })),
201
+ delete: vi.fn(() => removal.promise),
202
+ });
203
+ const core = createCore(adapter);
204
+ await core.getLoadThreadsPromise();
205
+ const internals = core as unknown as {
206
+ _hookManager: { stopThreadRuntime: (id: string) => void };
207
+ };
208
+ const stopThreadRuntime = vi.spyOn(
209
+ internals._hookManager,
210
+ "stopThreadRuntime",
211
+ );
212
+
213
+ const deletion = core.delete("thread-b");
214
+ await vi.waitFor(() => {
215
+ expect(adapter.delete).toHaveBeenCalledWith("thread-b");
216
+ });
217
+ core.__internal_setOptions({
218
+ adapter: makeAdapter(),
219
+ runtimeHook: () => ({}) as never,
220
+ });
221
+ removal.resolve();
222
+ await deletion;
223
+
224
+ expect(core.getItemById("thread-b")).toBeUndefined();
225
+ expect(stopThreadRuntime).toHaveBeenCalledWith("thread-b");
226
+ });
227
+
123
228
  it("does not unarchive again when the target became regular during initialization", async () => {
124
229
  const initialization = deferred<{
125
230
  remoteId: string;