@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
@@ -1,4 +1,5 @@
1
1
  import {
2
+ isAttachmentComplete,
2
3
  isCreateAttachment,
3
4
  type Attachment,
4
5
  type CompleteAttachment,
@@ -34,9 +35,6 @@ import {
34
35
  drainAttachmentAdd,
35
36
  } from "../utils/attachment-add-operations";
36
37
 
37
- const isAttachmentComplete = (a: Attachment): a is CompleteAttachment =>
38
- a.status.type === "complete";
39
-
40
38
  export abstract class BaseComposerRuntimeCore
41
39
  extends BaseSubscribable
42
40
  implements ComposerRuntimeCore
@@ -161,6 +159,7 @@ export abstract class BaseComposerRuntimeCore
161
159
  private _emptyTextAndAttachments() {
162
160
  this._attachments = [];
163
161
  this._text = "";
162
+ this._rebaseDictation("");
164
163
  this._notifySubscribers();
165
164
  }
166
165
 
@@ -301,7 +300,10 @@ export abstract class BaseComposerRuntimeCore
301
300
  void sendTask.catch((error) => {
302
301
  this._restoreUnsentDraft(error, generation, draft);
303
302
  });
304
- this._notifyEventSubscribers("send", {});
303
+ this._notifyEventSubscribers("send", {
304
+ chars: text.length,
305
+ attachments: finalAttachments.length,
306
+ });
305
307
  }
306
308
 
307
309
  /**
@@ -417,6 +419,7 @@ export abstract class BaseComposerRuntimeCore
417
419
  message,
418
420
  undefined,
419
421
  err,
422
+ fileOrAttachment.contentType,
420
423
  );
421
424
  throw err;
422
425
  }
@@ -431,7 +434,9 @@ export abstract class BaseComposerRuntimeCore
431
434
  };
432
435
  this._attachments = [...this._attachments, a];
433
436
  this._notifySubscribers();
434
- this._notifyEventSubscribers("attachmentAdd", {});
437
+ this._notifyEventSubscribers("attachmentAdd", {
438
+ ...(a.contentType ? { contentType: a.contentType } : undefined),
439
+ });
435
440
  return;
436
441
  }
437
442
 
@@ -439,7 +444,13 @@ export abstract class BaseComposerRuntimeCore
439
444
  if (!adapter) {
440
445
  const message = "Attachments are not supported";
441
446
  const err = new Error(message);
442
- this._safeEmitAttachmentAddError("no-adapter", message, undefined, err);
447
+ this._safeEmitAttachmentAddError(
448
+ "no-adapter",
449
+ message,
450
+ undefined,
451
+ err,
452
+ fileOrAttachment.type,
453
+ );
443
454
  throw err;
444
455
  }
445
456
 
@@ -451,7 +462,13 @@ export abstract class BaseComposerRuntimeCore
451
462
  ) {
452
463
  const message = `File type ${fileOrAttachment.type || "unknown"} is not accepted. Accepted types: ${adapter.accept}`;
453
464
  const err = new Error(message);
454
- this._safeEmitAttachmentAddError("not-accepted", message, undefined, err);
465
+ this._safeEmitAttachmentAddError(
466
+ "not-accepted",
467
+ message,
468
+ undefined,
469
+ err,
470
+ fileOrAttachment.type,
471
+ );
455
472
  throw err;
456
473
  }
457
474
 
@@ -501,6 +518,7 @@ export abstract class BaseComposerRuntimeCore
501
518
  e instanceof Error ? e.message : String(e),
502
519
  lastAttachment?.id,
503
520
  e instanceof Error ? e : undefined,
521
+ lastAttachment?.contentType || fileOrAttachment.type,
504
522
  );
505
523
  throw e;
506
524
  } finally {
@@ -517,9 +535,17 @@ export abstract class BaseComposerRuntimeCore
517
535
  lastAttachment.status.message ??
518
536
  "Attachment upload did not complete successfully.",
519
537
  lastAttachment.id,
538
+ undefined,
539
+ lastAttachment.contentType || fileOrAttachment.type,
520
540
  );
521
541
  } else {
522
- this._notifyEventSubscribers("attachmentAdd", {});
542
+ this._notifyEventSubscribers("attachmentAdd", {
543
+ ...(lastAttachment?.contentType
544
+ ? { contentType: lastAttachment.contentType }
545
+ : fileOrAttachment.type
546
+ ? { contentType: fileOrAttachment.type }
547
+ : undefined),
548
+ });
523
549
  }
524
550
  }
525
551
 
@@ -528,6 +554,7 @@ export abstract class BaseComposerRuntimeCore
528
554
  message: string,
529
555
  attachmentId?: string,
530
556
  error?: Error,
557
+ contentType?: string,
531
558
  ) {
532
559
  try {
533
560
  this._notifyEventSubscribers("attachmentAddError", {
@@ -535,6 +562,7 @@ export abstract class BaseComposerRuntimeCore
535
562
  message,
536
563
  ...(attachmentId !== undefined && { attachmentId }),
537
564
  ...(error !== undefined && { error }),
565
+ ...(contentType ? { contentType } : undefined),
538
566
  });
539
567
  } catch (subscriberError) {
540
568
  console.error(
@@ -16,6 +16,9 @@ import { BaseThreadRuntimeCore } from "./base-thread-runtime-core";
16
16
 
17
17
  const createVoiceAdapter = () => {
18
18
  let volumeCallback: ((volume: number) => void) | undefined;
19
+ let transcriptCallback:
20
+ | ((transcript: RealtimeVoiceAdapter.TranscriptItem) => void)
21
+ | undefined;
19
22
  const session: RealtimeVoiceAdapter.Session = {
20
23
  status: { type: "running" },
21
24
  isMuted: false,
@@ -23,7 +26,12 @@ const createVoiceAdapter = () => {
23
26
  mute: vi.fn(),
24
27
  unmute: vi.fn(),
25
28
  onStatusChange: () => () => {},
26
- onTranscript: () => () => {},
29
+ onTranscript: (callback) => {
30
+ transcriptCallback = callback;
31
+ return () => {
32
+ transcriptCallback = undefined;
33
+ };
34
+ },
27
35
  onModeChange: () => () => {},
28
36
  onVolumeChange: (callback) => {
29
37
  volumeCallback = callback;
@@ -36,10 +44,13 @@ const createVoiceAdapter = () => {
36
44
  return {
37
45
  adapter: { connect: () => session },
38
46
  emitVolume: (volume: number) => volumeCallback?.(volume),
47
+ emitTranscript: (transcript: RealtimeVoiceAdapter.TranscriptItem) =>
48
+ transcriptCallback?.(transcript),
39
49
  session,
40
50
  } satisfies {
41
51
  adapter: RealtimeVoiceAdapter;
42
52
  emitVolume: (volume: number) => void;
53
+ emitTranscript: (transcript: RealtimeVoiceAdapter.TranscriptItem) => void;
43
54
  session: RealtimeVoiceAdapter.Session;
44
55
  };
45
56
  };
@@ -296,3 +307,46 @@ describe("BaseThreadRuntimeCore voice volume subscriptions", () => {
296
307
  });
297
308
  });
298
309
  });
310
+
311
+ describe("BaseThreadRuntimeCore voice transcripts", () => {
312
+ it("completes a final-only reply before the next streamed reply", () => {
313
+ const voiceAdapter = createVoiceAdapter();
314
+ const runtime = new TestRuntime(voiceAdapter);
315
+ runtime.connectVoice();
316
+
317
+ try {
318
+ voiceAdapter.emitTranscript({
319
+ role: "assistant",
320
+ text: "Finished reply",
321
+ isFinal: true,
322
+ });
323
+ expect(runtime.messages).toMatchObject([
324
+ {
325
+ content: [{ type: "text", text: "Finished reply" }],
326
+ status: { type: "complete", reason: "stop" },
327
+ },
328
+ ]);
329
+
330
+ voiceAdapter.emitTranscript({ role: "assistant", text: "Next" });
331
+ expect(runtime.messages.at(-1)?.status).toEqual({ type: "running" });
332
+
333
+ voiceAdapter.emitTranscript({
334
+ role: "assistant",
335
+ text: "Next reply",
336
+ isFinal: true,
337
+ });
338
+ expect(runtime.messages).toMatchObject([
339
+ {
340
+ content: [{ type: "text", text: "Finished reply" }],
341
+ status: { type: "complete", reason: "stop" },
342
+ },
343
+ {
344
+ content: [{ type: "text", text: "Next reply" }],
345
+ status: { type: "complete", reason: "stop" },
346
+ },
347
+ ]);
348
+ } finally {
349
+ runtime.disconnectVoice();
350
+ }
351
+ });
352
+ });
@@ -393,6 +393,10 @@ export abstract class BaseThreadRuntimeCore
393
393
  this._notifySubscribers();
394
394
  }
395
395
  } else {
396
+ const status: ThreadAssistantMessage["status"] = transcript.isFinal
397
+ ? { type: "complete", reason: "stop" }
398
+ : { type: "running" };
399
+
396
400
  if (!this._currentAssistantMsg) {
397
401
  this._currentAssistantMsg = {
398
402
  id: generateId(),
@@ -405,7 +409,7 @@ export abstract class BaseThreadRuntimeCore
405
409
  steps: [],
406
410
  custom: {},
407
411
  },
408
- status: { type: "running" },
412
+ status,
409
413
  createdAt: new Date(),
410
414
  };
411
415
  this._voiceMessages.push(this._currentAssistantMsg);
@@ -415,9 +419,7 @@ export abstract class BaseThreadRuntimeCore
415
419
  const updated: ThreadAssistantMessage = {
416
420
  ...this._currentAssistantMsg,
417
421
  content: [{ type: "text", text: transcript.text }],
418
- ...(transcript.isFinal
419
- ? { status: { type: "complete", reason: "stop" } }
420
- : {}),
422
+ status,
421
423
  };
422
424
  this._voiceMessages[idx] = updated;
423
425
  this._currentAssistantMsg = updated;
@@ -21,16 +21,21 @@ export type AttachmentAddErrorEvent = {
21
21
 
22
22
  export type ComposerRuntimeEventPayload = {
23
23
  /**
24
- * @deprecated State-derivable. Observe `state.text` clearing via
25
- * `subscribe` + `getState` instead. Kept for backward compatibility.
24
+ * Fired after a send with the size of what went out. The composer state is
25
+ * already cleared when it fires, so the counts are only available here.
26
26
  */
27
- send: Record<string, never>;
27
+ send: {
28
+ readonly chars: number;
29
+ readonly attachments: number;
30
+ };
28
31
  /**
29
32
  * @deprecated State-derivable. Observe `state.attachments` via `subscribe` +
30
33
  * `getState` instead. Kept for backward compatibility.
31
34
  */
32
- attachmentAdd: Record<string, never>;
33
- attachmentAddError: AttachmentAddErrorEvent;
35
+ attachmentAdd: { readonly contentType?: string | undefined };
36
+ attachmentAddError: AttachmentAddErrorEvent & {
37
+ readonly contentType?: string | undefined;
38
+ };
34
39
  };
35
40
 
36
41
  export type ComposerRuntimeEventType = keyof ComposerRuntimeEventPayload;
@@ -26,6 +26,7 @@ export type ThreadListRuntimeEvent = {
26
26
 
27
27
  export type ThreadListRuntimeCore = {
28
28
  readonly isLoading: boolean;
29
+ readonly loadError?: unknown;
29
30
  readonly isLoadingMore?: boolean;
30
31
  readonly hasMore?: boolean;
31
32
  mainThreadId: string;
@@ -86,7 +87,10 @@ export type ThreadListRuntimeCore = {
86
87
  initialize(
87
88
  threadId: string,
88
89
  ): Promise<{ remoteId: string; externalId: string | undefined }>;
89
- generateTitle(threadId: string): Promise<void>;
90
+ generateTitle(
91
+ threadId: string,
92
+ options?: { automatic?: boolean },
93
+ ): Promise<void>;
90
94
 
91
95
  subscribe(callback: () => void): Unsubscribe;
92
96
  };
@@ -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 = {
@@ -252,7 +252,7 @@ export const joinExternalMessages = (
252
252
 
253
253
  // Add content parts, merging reasoning parts with same parentId
254
254
  for (const part of content) {
255
- if (part.type === "tool-call") {
255
+ if (part.type === "tool-call" && part.toolCallId) {
256
256
  const existingIdx = assistantMessage.content.findIndex(
257
257
  (c) =>
258
258
  c.type === "tool-call" && c.toolCallId === part.toolCallId,
@@ -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) {
@@ -179,7 +179,7 @@ export const fromThreadMessageLike = (
179
179
  const { parentId, messages, ...basePart } = part;
180
180
  const commonProps = {
181
181
  ...basePart,
182
- toolCallId: part.toolCallId ?? `tool-${generateId()}`,
182
+ toolCallId: part.toolCallId || `tool-${generateId()}`,
183
183
  ...(parentId !== undefined && { parentId }),
184
184
  ...(messages !== undefined && { messages }),
185
185
  };
@@ -6,6 +6,7 @@ import type {
6
6
  import type { ExternalStoreThreadListAdapter } from "./external-store-adapter";
7
7
  import { invalidateThreadRuntime } from "../../runtime/utils/thread-runtime-lifecycle";
8
8
  import { BaseSubscribable } from "../../subscribable/subscribable";
9
+ import { nullProtoRecord } from "../../utils/record";
9
10
 
10
11
  export type ExternalStoreThreadFactory = () => ExternalStoreThreadRuntimeCore;
11
12
 
@@ -19,9 +20,11 @@ const DEFAULT_THREAD = Object.freeze({
19
20
  status: "regular",
20
21
  });
21
22
  const RESOLVED_PROMISE = Promise.resolve();
22
- const DEFAULT_THREAD_DATA = Object.freeze({
23
- [DEFAULT_THREAD_ID]: DEFAULT_THREAD,
24
- });
23
+ const DEFAULT_THREAD_DATA = Object.freeze(
24
+ nullProtoRecord<ThreadListItemCoreState>({
25
+ [DEFAULT_THREAD_ID]: DEFAULT_THREAD,
26
+ }),
27
+ );
25
28
 
26
29
  export class ExternalStoreThreadListRuntimeCore
27
30
  extends BaseSubscribable
@@ -84,7 +87,9 @@ export class ExternalStoreThreadListRuntimeCore
84
87
  }
85
88
 
86
89
  public getItemById(threadId: string) {
87
- return this._threadData[threadId];
90
+ return Object.hasOwn(this._threadData, threadId)
91
+ ? this._threadData[threadId]
92
+ : undefined;
88
93
  }
89
94
 
90
95
  public __internal_setAdapter(
@@ -105,6 +110,7 @@ export class ExternalStoreThreadListRuntimeCore
105
110
 
106
111
  if (
107
112
  !initialLoad &&
113
+ (previousAdapter.isLoading ?? false) === (adapter.isLoading ?? false) &&
108
114
  previousThreadId === newThreadId &&
109
115
  previousThreads === newThreads &&
110
116
  previousArchivedThreads === newArchivedThreads
@@ -117,9 +123,9 @@ export class ExternalStoreThreadListRuntimeCore
117
123
  previousArchivedThreads !== newArchivedThreads ||
118
124
  previousThreadId !== newThreadId
119
125
  ) {
120
- this._threadData = {
121
- ...DEFAULT_THREAD_DATA,
122
- ...Object.fromEntries(
126
+ this._threadData = nullProtoRecord(
127
+ DEFAULT_THREAD_DATA,
128
+ Object.fromEntries(
123
129
  adapter.threads?.map((t) => [
124
130
  t.id,
125
131
  {
@@ -130,7 +136,7 @@ export class ExternalStoreThreadListRuntimeCore
130
136
  },
131
137
  ]) ?? [],
132
138
  ),
133
- ...Object.fromEntries(
139
+ Object.fromEntries(
134
140
  adapter.archivedThreads?.map((t) => [
135
141
  t.id,
136
142
  {
@@ -141,7 +147,7 @@ export class ExternalStoreThreadListRuntimeCore
141
147
  },
142
148
  ]) ?? [],
143
149
  ),
144
- };
150
+ );
145
151
  }
146
152
 
147
153
  if (previousThreads !== newThreads) {
@@ -160,16 +166,15 @@ export class ExternalStoreThreadListRuntimeCore
160
166
  this._mainThread = this.threadFactory();
161
167
  }
162
168
 
163
- if (!this._threadData[this._mainThreadId]) {
164
- this._threadData = {
165
- ...this._threadData,
169
+ if (!Object.hasOwn(this._threadData, this._mainThreadId)) {
170
+ this._threadData = nullProtoRecord(this._threadData, {
166
171
  [this._mainThreadId]: {
167
172
  id: this._mainThreadId,
168
173
  remoteId: undefined,
169
174
  externalId: undefined,
170
175
  status: "regular",
171
176
  },
172
- };
177
+ });
173
178
  }
174
179
 
175
180
  this._notifySubscribers();
@@ -31,6 +31,7 @@ export type {
31
31
  export {
32
32
  createThreadMappingId,
33
33
  getThreadData,
34
+ promoteNewThreadReducer,
34
35
  updateStatusReducer,
35
36
  } from "./remote-thread-list/remote-thread-state";
36
37
  export type {