@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
@@ -9,17 +9,26 @@ import type {
9
9
  import type { ExportedMessageRepositoryItem } from "../../../runtime/utils/message-repository";
10
10
  import {
11
11
  type AssistantCloud,
12
+ type AssistantCloudEvent,
12
13
  type AssistantCloudRunReportToolCall,
13
14
  CloudMessagePersistence,
14
15
  createFormattedPersistence,
16
+ createRunReport,
15
17
  createRunTelemetryToolCall,
18
+ deriveRunOutcome,
19
+ describeRunError,
20
+ extractRunTelemetryModelId,
16
21
  normalizeRunTelemetryUsage,
22
+ type RunReportOutcome,
23
+ type RunReportStepInit,
24
+ type RunTelemetryUsage,
17
25
  type RunTelemetryUsageInit,
18
26
  truncateRunTelemetryText,
19
27
  } from "assistant-cloud";
20
28
  import { auiV0Decode, auiV0Encode } from "./auiV0";
21
29
  import { type AssistantClient, getClientId, useAui } from "@assistant-ui/store";
22
30
  import type { ThreadListItemMethods } from "../../../store/scopes/thread-list-item";
31
+ import type { FeedbackAdapter } from "../../../adapters/feedback";
23
32
 
24
33
  type CloudThreadListItem = Pick<
25
34
  ThreadListItemMethods,
@@ -64,6 +73,72 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
64
73
  return this.getPersistence();
65
74
  }
66
75
 
76
+ /**
77
+ * A send is the moment the runtime creates the remote thread, so that one
78
+ * event waits for the id; every other event reads the id that already
79
+ * exists, because initializing a thread nobody has written to would create
80
+ * an empty remote thread just to attribute an event.
81
+ */
82
+ public async resolveEngagementEventIds(
83
+ threadId: string,
84
+ messageId?: string,
85
+ options?: { awaitThread?: boolean },
86
+ ): Promise<Pick<AssistantCloudEvent, "thread_id" | "message_id">> {
87
+ const threadListItem = this.getThreadListItem(threadId);
88
+ if (!threadListItem) return {};
89
+
90
+ let remoteThreadId = threadListItem.getState().remoteId;
91
+ if (!remoteThreadId && options?.awaitThread) {
92
+ remoteThreadId = await threadListItem
93
+ .initialize()
94
+ .then((result) => result.remoteId)
95
+ .catch(() => undefined);
96
+ }
97
+ const remoteMessageId = messageId
98
+ ? this.getPersistence(threadListItem).getResolvedRemoteId(messageId)
99
+ : undefined;
100
+ return {
101
+ ...(remoteThreadId ? { thread_id: remoteThreadId } : undefined),
102
+ ...(remoteMessageId ? { message_id: remoteMessageId } : undefined),
103
+ };
104
+ }
105
+
106
+ public readonly feedback: FeedbackAdapter = {
107
+ submit: ({ message, type }) => {
108
+ void (async () => {
109
+ const threadListItem = this.tryGetKeyedThreadListItem();
110
+ const remoteThreadId = threadListItem?.getState().remoteId;
111
+ if (!threadListItem || !remoteThreadId) {
112
+ console.warn(
113
+ `[assistant-ui] Skipping feedback for message ${message.id}: the thread has no remote id.`,
114
+ );
115
+ return;
116
+ }
117
+
118
+ const cloudMessageId = await this.getPersistence(
119
+ threadListItem,
120
+ ).getRemoteId(message.id);
121
+ if (!cloudMessageId) {
122
+ console.warn(
123
+ `[assistant-ui] Skipping feedback for message ${message.id}: no cloud message id is mapped.`,
124
+ );
125
+ return;
126
+ }
127
+
128
+ await this.cloudRef.current.threads.messages.feedback(
129
+ remoteThreadId,
130
+ cloudMessageId,
131
+ { type },
132
+ );
133
+ })().catch((error: unknown) => {
134
+ console.error(
135
+ "[assistant-ui] Cloud feedback submission failed:",
136
+ error,
137
+ );
138
+ });
139
+ },
140
+ };
141
+
67
142
  private tryGetKeyedThreadListItem(): CloudThreadListItem | undefined {
68
143
  const live = this.aui.threadListItem;
69
144
  if (!live.source) return undefined;
@@ -77,6 +152,21 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
77
152
  return listed ? this.aui.threads.item({ id }) : live;
78
153
  }
79
154
 
155
+ private getThreadListItem(threadId: string): CloudThreadListItem | undefined {
156
+ const current = this.aui.threadListItem;
157
+ const currentState = current.getState();
158
+ if (currentState.id === threadId || currentState.remoteId === threadId) {
159
+ return current;
160
+ }
161
+
162
+ const listed = this.aui.threads
163
+ .getState()
164
+ .threadItems.find(
165
+ (item) => item.id === threadId || item.remoteId === threadId,
166
+ );
167
+ return listed ? this.aui.threads.item({ id: listed.id }) : undefined;
168
+ }
169
+
80
170
  withFormat<TMessage, TStorageFormat extends Record<string, unknown>>(
81
171
  formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>,
82
172
  ): GenericThreadHistoryAdapter<TMessage> {
@@ -135,6 +225,7 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
135
225
  encodedRunMessages,
136
226
  options,
137
227
  resolvePinned(),
228
+ extractLastRunMessageInfo(items, formatAdapter),
138
229
  );
139
230
  },
140
231
  async load(): Promise<MessageFormatRepository<TMessage>> {
@@ -162,7 +253,12 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
162
253
  );
163
254
 
164
255
  if (this.cloudRef.current.telemetry.enabled) {
165
- this._maybeReportRun(remoteId, "aui/v0", encoded);
256
+ this._maybeReportRun(
257
+ remoteId,
258
+ "aui/v0",
259
+ encoded,
260
+ extractRunMessageInfo(message, "aui/v0"),
261
+ );
166
262
  }
167
263
  }
168
264
 
@@ -177,7 +273,12 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
177
273
  await this._persistence.update(remoteId, message.id, "aui/v0", encoded);
178
274
 
179
275
  if (this.cloudRef.current.telemetry.enabled) {
180
- this._maybeReportRun(remoteId, "aui/v0", encoded);
276
+ this._maybeReportRun(
277
+ remoteId,
278
+ "aui/v0",
279
+ encoded,
280
+ extractRunMessageInfo(message, "aui/v0"),
281
+ );
181
282
  }
182
283
  }
183
284
 
@@ -209,11 +310,12 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
209
310
  stepTimestamps?: StepTimestamp[];
210
311
  },
211
312
  threadListItem?: CloudThreadListItem,
313
+ messageInfo?: RunMessageInfo,
212
314
  ) {
213
315
  if (!this.cloudRef.current.telemetry.enabled) return;
214
316
 
215
- const remoteId = (threadListItem ?? this.aui.threadListItem).getState()
216
- .remoteId;
317
+ const item = threadListItem ?? this.aui.threadListItem;
318
+ const remoteId = item.getState().remoteId;
217
319
  if (!remoteId) return;
218
320
 
219
321
  const extracted = extractRunTelemetry(format, runMessages);
@@ -224,14 +326,21 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
224
326
  extracted,
225
327
  options?.durationMs,
226
328
  options?.stepTimestamps,
329
+ messageInfo,
330
+ this.getPersistence(item),
227
331
  );
228
332
  }
229
333
 
230
- private _maybeReportRun<T>(remoteId: string, format: string, content: T) {
334
+ private _maybeReportRun<T>(
335
+ remoteId: string,
336
+ format: string,
337
+ content: T,
338
+ messageInfo?: RunMessageInfo,
339
+ ) {
231
340
  const extracted = extractTelemetry(format, content);
232
341
  if (!extracted) return;
233
342
 
234
- this._sendReport(remoteId, extracted);
343
+ this._sendReport(remoteId, extracted, undefined, undefined, messageInfo);
235
344
  }
236
345
 
237
346
  private _sendReport(
@@ -239,37 +348,38 @@ class AssistantCloudThreadHistoryAdapter implements ThreadHistoryAdapter {
239
348
  data: TelemetryData,
240
349
  durationMs?: number,
241
350
  stepTimestamps?: StepTimestamp[],
351
+ messageInfo?: RunMessageInfo,
352
+ persistence = this._persistence,
242
353
  ) {
243
354
  const mergedSteps = mergeStepTimestamps(data.steps, stepTimestamps);
244
- // Keep in sync with assistant-cloud createRunSchema
245
- // (apps/aui-cloud-api/src/endpoints/runs/create.ts).
246
- const initial: Parameters<typeof this.cloudRef.current.runs.report>[0] = {
247
- thread_id: remoteId,
248
- status: data.status,
249
- ...(data.totalSteps != null
250
- ? { total_steps: data.totalSteps }
251
- : undefined),
252
- ...(data.toolCalls ? { tool_calls: data.toolCalls } : undefined),
253
- ...(mergedSteps ? { steps: mergedSteps } : undefined),
254
- ...(data.inputTokens != null
255
- ? { input_tokens: data.inputTokens }
256
- : undefined),
257
- ...(data.outputTokens != null
258
- ? { output_tokens: data.outputTokens }
259
- : undefined),
260
- ...(data.reasoningTokens != null
261
- ? { reasoning_tokens: data.reasoningTokens }
262
- : undefined),
263
- ...(data.cachedInputTokens != null
264
- ? { cached_input_tokens: data.cachedInputTokens }
265
- : undefined),
266
- ...(durationMs != null ? { duration_ms: durationMs } : undefined),
267
- ...(data.outputText != null
268
- ? { output_text: data.outputText }
269
- : undefined),
270
- ...(data.metadata ? { metadata: data.metadata } : undefined),
271
- ...(data.modelId ? { model_id: data.modelId } : undefined),
272
- };
355
+ const messageId = messageInfo?.localMessageId
356
+ ? persistence.getResolvedRemoteId(messageInfo.localMessageId)
357
+ : undefined;
358
+ const initial = createRunReport({
359
+ threadId: remoteId,
360
+ status: messageInfo?.status ?? data.status,
361
+ outcome: messageInfo?.outcomeType,
362
+ error: messageInfo?.error,
363
+ errorCode: messageInfo?.errorCode,
364
+ messageId,
365
+ traceId: messageInfo?.traceId,
366
+ modelId: data.modelId,
367
+ provider: messageInfo?.provider,
368
+ usage: {
369
+ inputTokens: data.inputTokens,
370
+ outputTokens: data.outputTokens,
371
+ reasoningTokens: data.reasoningTokens,
372
+ cachedInputTokens: data.cachedInputTokens,
373
+ },
374
+ steps: toRunReportSteps(mergedSteps),
375
+ totalSteps: data.totalSteps,
376
+ toolCalls: data.toolCalls,
377
+ durationMs,
378
+ firstTokenMs: messageInfo?.firstTokenMs,
379
+ outputText: data.outputText,
380
+ metadata: data.metadata,
381
+ telemetry: this.cloudRef.current.telemetry,
382
+ });
273
383
 
274
384
  const { beforeReport } = this.cloudRef.current.telemetry;
275
385
  const report = beforeReport ? beforeReport(initial) : initial;
@@ -291,6 +401,23 @@ type TelemetryStepData = {
291
401
 
292
402
  type StepTimestamp = { start_ms: number; end_ms: number };
293
403
 
404
+ function toRunReportSteps(
405
+ steps: TelemetryStepData[] | undefined,
406
+ ): RunReportStepInit[] | undefined {
407
+ if (!steps) return undefined;
408
+ return steps.map((step) => ({
409
+ usage: {
410
+ inputTokens: step.input_tokens,
411
+ outputTokens: step.output_tokens,
412
+ reasoningTokens: step.reasoning_tokens,
413
+ cachedInputTokens: step.cached_input_tokens,
414
+ },
415
+ toolCalls: step.tool_calls,
416
+ startMs: step.start_ms,
417
+ endMs: step.end_ms,
418
+ }));
419
+ }
420
+
294
421
  function mergeStepTimestamps(
295
422
  steps: TelemetryStepData[] | undefined,
296
423
  timestamps: StepTimestamp[] | undefined,
@@ -319,6 +446,101 @@ type TelemetryData = {
319
446
  modelId?: string;
320
447
  };
321
448
 
449
+ type RunMessageInfo = {
450
+ localMessageId?: string;
451
+ status?: "error";
452
+ outcomeType?: RunReportOutcome;
453
+ error?: string;
454
+ errorCode?: string;
455
+ firstTokenMs?: number;
456
+ traceId?: string;
457
+ provider?: string;
458
+ };
459
+
460
+ function extractLastRunMessageInfo<
461
+ TMessage,
462
+ TStorageFormat extends Record<string, unknown>,
463
+ >(
464
+ items: MessageFormatItem<TMessage>[],
465
+ formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>,
466
+ ): RunMessageInfo | undefined {
467
+ for (let i = items.length - 1; i >= 0; i--) {
468
+ const item = items[i]!;
469
+ const info = extractRunMessageInfo(
470
+ item.message,
471
+ formatAdapter.format,
472
+ formatAdapter.getId(item.message),
473
+ );
474
+ if (info) return info;
475
+ }
476
+ return undefined;
477
+ }
478
+
479
+ function extractRunMessageInfo(
480
+ message: unknown,
481
+ format: string,
482
+ localMessageId?: string,
483
+ ): RunMessageInfo | undefined {
484
+ if (!isRecord(message) || message.role !== "assistant") return undefined;
485
+
486
+ const status = isRecord(message.status) ? message.status : undefined;
487
+ const metadata = isRecord(message.metadata) ? message.metadata : undefined;
488
+ const custom = isRecord(metadata?.custom) ? metadata.custom : undefined;
489
+ const timing = isRecord(metadata?.timing) ? metadata.timing : undefined;
490
+ const streamStartTime = timing?.streamStartTime;
491
+ const firstTokenTime = timing?.firstTokenTime;
492
+ const firstTokenMs =
493
+ typeof streamStartTime === "number" &&
494
+ Number.isFinite(streamStartTime) &&
495
+ typeof firstTokenTime === "number" &&
496
+ Number.isFinite(firstTokenTime)
497
+ ? Math.round(firstTokenTime - streamStartTime)
498
+ : undefined;
499
+ const finishReason =
500
+ status?.type === "incomplete"
501
+ ? status.reason
502
+ : typeof metadata?.finishReason === "string"
503
+ ? metadata.finishReason
504
+ : undefined;
505
+ const failed = status?.type === "incomplete" && status.reason === "error";
506
+ const outcome = deriveRunOutcome({
507
+ finishReason: typeof finishReason === "string" ? finishReason : undefined,
508
+ isError: failed,
509
+ });
510
+ const outcomeType = outcome.outcome;
511
+ const failure = failed ? describeRunError(status.error) : {};
512
+ const messageId =
513
+ localMessageId ?? (typeof message.id === "string" ? message.id : undefined);
514
+ const traceId =
515
+ format === "aui/v0"
516
+ ? custom?.traceId
517
+ : format === "ai-sdk/v6"
518
+ ? metadata?.traceId
519
+ : undefined;
520
+ const provider =
521
+ typeof custom?.provider === "string"
522
+ ? custom.provider
523
+ : typeof metadata?.provider === "string"
524
+ ? metadata.provider
525
+ : undefined;
526
+
527
+ return {
528
+ ...(messageId ? { localMessageId: messageId } : undefined),
529
+ ...(outcome.status === "error" ? { status: "error" as const } : undefined),
530
+ ...(outcomeType ? { outcomeType } : undefined),
531
+ ...failure,
532
+ ...(firstTokenMs != null && firstTokenMs >= 0
533
+ ? { firstTokenMs }
534
+ : undefined),
535
+ ...(typeof traceId === "string" ? { traceId } : undefined),
536
+ ...(provider !== undefined ? { provider } : undefined),
537
+ };
538
+ }
539
+
540
+ function isRecord(value: unknown): value is Record<string, unknown> {
541
+ return value !== null && typeof value === "object";
542
+ }
543
+
322
544
  function extractTelemetry<T>(format: string, content: T): TelemetryData | null {
323
545
  switch (format) {
324
546
  case "aui/v0":
@@ -352,7 +574,7 @@ const AUI_STATUS_MAP: Record<string, TelemetryData["status"]> = {
352
574
  export function extractAuiV0<T>(content: T): TelemetryData | null {
353
575
  const msg = content as {
354
576
  role?: string;
355
- status?: { type: string };
577
+ status?: { type: string; reason?: string };
356
578
  content?: readonly {
357
579
  type: string;
358
580
  text?: string;
@@ -435,14 +657,14 @@ export function extractAuiV0<T>(content: T): TelemetryData | null {
435
657
 
436
658
  const statusType = msg.status?.type;
437
659
  const status: TelemetryData["status"] =
438
- (statusType && AUI_STATUS_MAP[statusType]) || "completed";
660
+ statusType === "incomplete" && msg.status?.reason === "error"
661
+ ? "error"
662
+ : (statusType && AUI_STATUS_MAP[statusType]) || "completed";
439
663
 
440
664
  const metadata = msg.metadata?.custom as Record<string, unknown> | undefined;
441
- const modelId =
442
- msg.metadata?.modelId ??
443
- (typeof msg.metadata?.custom?.modelId === "string"
444
- ? msg.metadata.custom.modelId
445
- : undefined);
665
+ const modelId = extractRunTelemetryModelId(
666
+ msg.metadata as Record<string, unknown> | undefined,
667
+ );
446
668
 
447
669
  const telemetrySteps: TelemetryStepData[] | undefined =
448
670
  steps && steps.length > 1
@@ -556,34 +778,19 @@ function collectAiSdkV6Parts(parts: readonly AiSdkV6Part[]): {
556
778
  return { textParts, toolCalls, stepsData };
557
779
  }
558
780
 
559
- function extractModelId(
560
- metadata?: Record<string, unknown>,
561
- ): string | undefined {
562
- if (!metadata) return undefined;
563
- if (typeof metadata.modelId === "string") return metadata.modelId;
564
- const custom = metadata.custom as Record<string, unknown> | undefined;
565
- if (typeof custom?.modelId === "string") return custom.modelId;
566
- return undefined;
567
- }
568
-
569
781
  function buildAiSdkV6Result(
570
782
  textParts: string[],
571
783
  toolCalls: AssistantCloudRunReportToolCall[],
572
784
  totalSteps: number,
573
785
  metadata?: Record<string, unknown>,
574
786
  stepsData?: { tool_calls: AssistantCloudRunReportToolCall[] }[],
575
- usage?: {
576
- inputTokens?: number;
577
- outputTokens?: number;
578
- reasoningTokens?: number;
579
- cachedInputTokens?: number;
580
- },
787
+ usage?: RunTelemetryUsage,
581
788
  ): TelemetryData {
582
789
  const hasText = textParts.length > 0;
583
790
  const outputText = hasText
584
791
  ? truncateRunTelemetryText(textParts.join(""))
585
792
  : undefined;
586
- const modelId = extractModelId(metadata);
793
+ const modelId = extractRunTelemetryModelId(metadata);
587
794
 
588
795
  const steps: TelemetryStepData[] | undefined =
589
796
  stepsData && stepsData.length > 1
@@ -617,14 +824,9 @@ function buildAiSdkV6Result(
617
824
  };
618
825
  }
619
826
 
620
- function extractAiSdkV6Usage(metadata?: Record<string, unknown>):
621
- | {
622
- inputTokens?: number;
623
- outputTokens?: number;
624
- reasoningTokens?: number;
625
- cachedInputTokens?: number;
626
- }
627
- | undefined {
827
+ function extractAiSdkV6Usage(
828
+ metadata?: Record<string, unknown>,
829
+ ): RunTelemetryUsage | undefined {
628
830
  // Try top-level metadata.usage
629
831
  const usage = metadata?.usage as RunTelemetryUsageInit | undefined;
630
832
  if (usage) {
@@ -766,7 +968,7 @@ function aggregateAiSdkV6RunSteps<T>(stepMessages: T[]): TelemetryData | null {
766
968
 
767
969
  export function useAssistantCloudThreadHistoryAdapter(
768
970
  cloudRef: RefObject<AssistantCloud>,
769
- ): ThreadHistoryAdapter {
971
+ ): ThreadHistoryAdapter & { readonly feedback: FeedbackAdapter } {
770
972
  const aui = useAui();
771
973
  // Not useEffectEvent: history adapter methods run during render (SSR load).
772
974
  const auiRef = useRef(aui);
@@ -777,5 +979,239 @@ export function useAssistantCloudThreadHistoryAdapter(
777
979
  () =>
778
980
  new AssistantCloudThreadHistoryAdapter(cloudRef, () => auiRef.current),
779
981
  );
982
+ useAssistantCloudEngagementEvents(cloudRef, adapter, aui);
780
983
  return adapter;
781
984
  }
985
+
986
+ type RootEngagementTracker = {
987
+ count: number;
988
+ adapter: AssistantCloudThreadHistoryAdapter;
989
+ cloudRef: RefObject<AssistantCloud>;
990
+ dispose: () => void;
991
+ };
992
+
993
+ const rootEngagementTrackers = new WeakMap<
994
+ AssistantClient,
995
+ RootEngagementTracker
996
+ >();
997
+
998
+ /**
999
+ * Thread switches are a thread list event, so one subscription per assistant
1000
+ * client reports them; every thread runtime mounted under it shares the
1001
+ * subscription and the last one to unmount removes it.
1002
+ */
1003
+ const useRootEngagementEvents = (
1004
+ cloudRef: RefObject<AssistantCloud>,
1005
+ adapter: AssistantCloudThreadHistoryAdapter,
1006
+ aui: AssistantClient,
1007
+ ) => {
1008
+ useEffect(() => {
1009
+ let tracker = rootEngagementTrackers.get(aui);
1010
+ if (!tracker) {
1011
+ const created: RootEngagementTracker = {
1012
+ count: 0,
1013
+ adapter,
1014
+ cloudRef,
1015
+ dispose: () => {},
1016
+ };
1017
+ created.dispose = aui.on(
1018
+ { scope: "*", event: "threads.selectionChanged" },
1019
+ (payload) => {
1020
+ void created.adapter
1021
+ .resolveEngagementEventIds(payload.threadId)
1022
+ .then((ids) => {
1023
+ if (!ids.thread_id) return;
1024
+ created.cloudRef.current.events?.track({
1025
+ kind: "thread_switched",
1026
+ ...ids,
1027
+ });
1028
+ })
1029
+ .catch(() => {});
1030
+ },
1031
+ );
1032
+ rootEngagementTrackers.set(aui, created);
1033
+ tracker = created;
1034
+ }
1035
+ const active = tracker;
1036
+ active.adapter = adapter;
1037
+ active.cloudRef = cloudRef;
1038
+ active.count += 1;
1039
+ return () => {
1040
+ active.count -= 1;
1041
+ if (active.count === 0) {
1042
+ active.dispose();
1043
+ rootEngagementTrackers.delete(aui);
1044
+ }
1045
+ };
1046
+ }, [adapter, aui, cloudRef]);
1047
+ };
1048
+
1049
+ const useAssistantCloudEngagementEvents = (
1050
+ cloudRef: RefObject<AssistantCloud>,
1051
+ adapter: AssistantCloudThreadHistoryAdapter,
1052
+ aui: AssistantClient,
1053
+ ) => {
1054
+ useRootEngagementEvents(cloudRef, adapter, aui);
1055
+ const runStartedAt = useRef(new Map<string, number>());
1056
+ const runEndedAt = useRef(new Map<string, number>());
1057
+ const shownSuggestions = useRef(new Set<string>());
1058
+
1059
+ useEffect(() => {
1060
+ const track = (
1061
+ event: AssistantCloudEvent,
1062
+ threadId: string,
1063
+ messageId?: string,
1064
+ options?: { awaitThread?: boolean },
1065
+ ) => {
1066
+ void adapter
1067
+ .resolveEngagementEventIds(threadId, messageId, options)
1068
+ .then((ids) => {
1069
+ cloudRef.current.events?.track({ ...event, ...ids });
1070
+ })
1071
+ .catch(() => {});
1072
+ };
1073
+ const trackRunStopped = (threadId: string) => {
1074
+ const startedAt = runStartedAt.current.get(threadId);
1075
+ if (startedAt === undefined) return;
1076
+ runStartedAt.current.delete(threadId);
1077
+ track(
1078
+ { kind: "run_stopped", value: Math.max(0, Date.now() - startedAt) },
1079
+ threadId,
1080
+ );
1081
+ };
1082
+
1083
+ const unsubscribers = [
1084
+ aui.on({ scope: "thread", event: "composer.send" }, (payload) => {
1085
+ const previousRunEndedAt = runEndedAt.current.get(payload.threadId);
1086
+ track(
1087
+ {
1088
+ kind: payload.messageId ? "message_edited" : "message_sent",
1089
+ ...(payload.messageId
1090
+ ? { props: { chars: payload.chars } }
1091
+ : {
1092
+ props: {
1093
+ chars: payload.chars,
1094
+ attachments: payload.attachments,
1095
+ },
1096
+ }),
1097
+ ...(!payload.messageId && previousRunEndedAt !== undefined
1098
+ ? { value: Math.max(0, Date.now() - previousRunEndedAt) }
1099
+ : undefined),
1100
+ },
1101
+ payload.threadId,
1102
+ payload.messageId,
1103
+ { awaitThread: !payload.messageId },
1104
+ );
1105
+ if (payload.suggestion) {
1106
+ track({ kind: "suggestion_clicked" }, payload.threadId);
1107
+ }
1108
+ }),
1109
+ aui.on(
1110
+ { scope: "thread", event: "composer.attachmentAdd" },
1111
+ (payload) => {
1112
+ track(
1113
+ {
1114
+ kind: "attachment_added",
1115
+ ...(payload.contentType
1116
+ ? { props: { type: payload.contentType } }
1117
+ : undefined),
1118
+ },
1119
+ payload.threadId,
1120
+ payload.messageId,
1121
+ );
1122
+ },
1123
+ ),
1124
+ aui.on(
1125
+ { scope: "thread", event: "composer.attachmentAddError" },
1126
+ (payload) => {
1127
+ track(
1128
+ {
1129
+ kind: "attachment_failed",
1130
+ ...(payload.contentType
1131
+ ? { props: { type: payload.contentType } }
1132
+ : undefined),
1133
+ },
1134
+ payload.threadId,
1135
+ payload.messageId,
1136
+ );
1137
+ },
1138
+ ),
1139
+ aui.on({ scope: "thread", event: "composer.cancel" }, (payload) => {
1140
+ trackRunStopped(payload.threadId);
1141
+ }),
1142
+ aui.on({ scope: "thread", event: "thread.runStart" }, (payload) => {
1143
+ runStartedAt.current.set(payload.threadId, Date.now());
1144
+ }),
1145
+ aui.on({ scope: "thread", event: "thread.runEnd" }, (payload) => {
1146
+ runStartedAt.current.delete(payload.threadId);
1147
+ runEndedAt.current.set(payload.threadId, Date.now());
1148
+ }),
1149
+ aui.on({ scope: "thread", event: "thread.cancelRun" }, (payload) => {
1150
+ trackRunStopped(payload.threadId);
1151
+ }),
1152
+ aui.on({ scope: "thread", event: "thread.voiceStarted" }, (payload) => {
1153
+ track({ kind: "voice_started" }, payload.threadId);
1154
+ }),
1155
+ aui.on({ scope: "thread", event: "message.reload" }, (payload) => {
1156
+ track(
1157
+ { kind: "message_regenerated" },
1158
+ payload.threadId,
1159
+ payload.messageId,
1160
+ );
1161
+ }),
1162
+ aui.on(
1163
+ { scope: "thread", event: "message.branchSwitched" },
1164
+ (payload) => {
1165
+ track(
1166
+ { kind: "branch_switched" },
1167
+ payload.threadId,
1168
+ payload.messageId,
1169
+ );
1170
+ },
1171
+ ),
1172
+ aui.on({ scope: "thread", event: "message.copied" }, (payload) => {
1173
+ track({ kind: "message_copied" }, payload.threadId, payload.messageId);
1174
+ }),
1175
+ aui.on({ scope: "thread", event: "message.speak" }, (payload) => {
1176
+ track({ kind: "speech_started" }, payload.threadId, payload.messageId);
1177
+ }),
1178
+ aui.on({ scope: "thread", event: "message.error" }, (payload) => {
1179
+ track(
1180
+ { kind: "error_shown", props: { reason: payload.reason } },
1181
+ payload.threadId,
1182
+ payload.messageId,
1183
+ );
1184
+ }),
1185
+ ];
1186
+
1187
+ return () => {
1188
+ for (const unsubscribe of unsubscribers) unsubscribe();
1189
+ };
1190
+ }, [adapter, aui, cloudRef]);
1191
+
1192
+ useEffect(() => {
1193
+ const reportSuggestions = () => {
1194
+ const { mainThreadId } = aui.threads.getState();
1195
+ if (aui.threadListItem.getState().id !== mainThreadId) return;
1196
+ const { isEmpty, suggestions } = aui.thread.getState();
1197
+ if (
1198
+ !isEmpty ||
1199
+ suggestions.length === 0 ||
1200
+ shownSuggestions.current.has(mainThreadId)
1201
+ ) {
1202
+ return;
1203
+ }
1204
+ shownSuggestions.current.add(mainThreadId);
1205
+ void adapter.resolveEngagementEventIds(mainThreadId).then((ids) => {
1206
+ cloudRef.current.events?.track({
1207
+ kind: "suggestions_shown",
1208
+ value: suggestions.length,
1209
+ ...ids,
1210
+ });
1211
+ });
1212
+ };
1213
+
1214
+ reportSuggestions();
1215
+ return aui.subscribe(reportSuggestions);
1216
+ }, [adapter, aui, cloudRef]);
1217
+ };