@assistant-ui/core 0.3.15 → 0.3.16

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 (251) hide show
  1. package/dist/internal.d.ts +7 -2
  2. package/dist/internal.js +8 -3
  3. package/dist/model-context/frame/host.d.ts.map +1 -1
  4. package/dist/model-context/frame/host.js +3 -2
  5. package/dist/model-context/frame/host.js.map +1 -1
  6. package/dist/model-context/frame/provider.d.ts +2 -0
  7. package/dist/model-context/frame/provider.d.ts.map +1 -1
  8. package/dist/model-context/frame/provider.js +88 -25
  9. package/dist/model-context/frame/provider.js.map +1 -1
  10. package/dist/model-context/interactable-composer-metadata.d.ts +1 -1
  11. package/dist/model-context/interactable-composer-metadata.js +1 -1
  12. package/dist/model-context/interactable-composer-metadata.js.map +1 -1
  13. package/dist/model-context/registry.d.ts +2 -2
  14. package/dist/model-context/registry.d.ts.map +1 -1
  15. package/dist/model-context/registry.js +9 -28
  16. package/dist/model-context/registry.js.map +1 -1
  17. package/dist/react/client/InMemoryThreadList.d.ts.map +1 -1
  18. package/dist/react/client/InMemoryThreadList.js +24 -20
  19. package/dist/react/client/InMemoryThreadList.js.map +1 -1
  20. package/dist/react/client/Interactables.d.ts.map +1 -1
  21. package/dist/react/client/Interactables.js +59 -177
  22. package/dist/react/client/Interactables.js.map +1 -1
  23. package/dist/react/client/RemoteThreadList.d.ts.map +1 -1
  24. package/dist/react/client/RemoteThreadList.js +23 -56
  25. package/dist/react/client/RemoteThreadList.js.map +1 -1
  26. package/dist/react/index.d.ts +2 -1
  27. package/dist/react/interactables-legacy/Interactables.d.ts.map +1 -1
  28. package/dist/react/interactables-legacy/Interactables.js +56 -174
  29. package/dist/react/interactables-legacy/Interactables.js.map +1 -1
  30. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts +24 -0
  31. package/dist/react/interactables-shared/useInteractablePersistenceQueue.d.ts.map +1 -0
  32. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js +138 -0
  33. package/dist/react/interactables-shared/useInteractablePersistenceQueue.js.map +1 -0
  34. package/dist/react/primitives/composer/ComposerIf.js.map +1 -1
  35. package/dist/react/primitives/composer/ComposerQueue.d.ts +2 -1
  36. package/dist/react/primitives/composer/ComposerQueue.d.ts.map +1 -1
  37. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  38. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +1 -1
  39. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +1 -1
  40. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  41. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +23 -55
  42. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  43. package/dist/react/runtimes/assistant-transport/runManager.d.ts.map +1 -1
  44. package/dist/react/runtimes/assistant-transport/runManager.js +2 -12
  45. package/dist/react/runtimes/assistant-transport/runManager.js.map +1 -1
  46. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts +3 -10
  47. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.d.ts.map +1 -1
  48. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +20 -67
  49. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  50. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts +12 -0
  51. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.d.ts.map +1 -0
  52. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js +10 -0
  53. package/dist/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.js.map +1 -0
  54. package/dist/react/runtimes/cloud/useCloudThreadListRuntime.js.map +1 -1
  55. package/dist/react/runtimes/createMessageConverter.d.ts +2 -1
  56. package/dist/react/runtimes/createMessageConverter.d.ts.map +1 -1
  57. package/dist/react/runtimes/external-message-converter.d.ts +6 -25
  58. package/dist/react/runtimes/external-message-converter.d.ts.map +1 -1
  59. package/dist/react/runtimes/external-message-converter.js +33 -233
  60. package/dist/react/runtimes/external-message-converter.js.map +1 -1
  61. package/dist/runtime/api/composer-runtime.d.ts +1 -1
  62. package/dist/runtime/api/composer-runtime.js.map +1 -1
  63. package/dist/runtime/api/thread-runtime.d.ts +1 -2
  64. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  65. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -1
  66. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  67. package/dist/runtime/base/base-composer-runtime-core.js +14 -29
  68. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  69. package/dist/runtime/base/base-thread-runtime-core.d.ts +2 -4
  70. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  71. package/dist/runtime/base/base-thread-runtime-core.js +3 -9
  72. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  73. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -1
  74. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  75. package/dist/runtime/base/default-thread-composer-runtime-core.js +1 -1
  76. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  77. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -1
  78. package/dist/runtime/interfaces/thread-runtime-core.d.ts +1 -1
  79. package/dist/runtime/internal.d.ts +2 -1
  80. package/dist/runtime/internal.js +2 -1
  81. package/dist/runtime/queue/external-thread-queue-adapter.d.ts +1 -1
  82. package/dist/runtime/queue/message-queue.d.ts.map +1 -1
  83. package/dist/runtime/queue/message-queue.js +1 -1
  84. package/dist/runtime/queue/message-queue.js.map +1 -1
  85. package/dist/runtime/queue/queue-item.d.ts +12 -0
  86. package/dist/runtime/queue/queue-item.d.ts.map +1 -0
  87. package/dist/runtime/queue/queue-item.js +6 -0
  88. package/dist/runtime/queue/queue-item.js.map +1 -0
  89. package/dist/runtime/utils/abortable-thread-load.d.ts +15 -0
  90. package/dist/runtime/utils/abortable-thread-load.d.ts.map +1 -0
  91. package/dist/runtime/utils/abortable-thread-load.js +35 -0
  92. package/dist/runtime/utils/abortable-thread-load.js.map +1 -0
  93. package/dist/runtime/utils/attachment-add-operations.d.ts +18 -0
  94. package/dist/runtime/utils/attachment-add-operations.d.ts.map +1 -0
  95. package/dist/runtime/utils/attachment-add-operations.js +43 -0
  96. package/dist/runtime/utils/attachment-add-operations.js.map +1 -0
  97. package/dist/runtime/utils/auto-status.d.ts +6 -1
  98. package/dist/runtime/utils/auto-status.d.ts.map +1 -1
  99. package/dist/runtime/utils/auto-status.js +7 -1
  100. package/dist/runtime/utils/auto-status.js.map +1 -1
  101. package/dist/runtime/utils/external-message-conversion.d.ts +47 -0
  102. package/dist/runtime/utils/external-message-conversion.d.ts.map +1 -0
  103. package/dist/runtime/utils/external-message-conversion.js +221 -0
  104. package/dist/runtime/utils/external-message-conversion.js.map +1 -0
  105. package/dist/runtime/utils/external-store-message.d.ts +2 -1
  106. package/dist/runtime/utils/external-store-message.d.ts.map +1 -1
  107. package/dist/runtime/utils/external-store-message.js +2 -1
  108. package/dist/runtime/utils/external-store-message.js.map +1 -1
  109. package/dist/runtime/utils/message-repository-session.d.ts +30 -0
  110. package/dist/runtime/utils/message-repository-session.d.ts.map +1 -0
  111. package/dist/runtime/utils/message-repository-session.js +130 -0
  112. package/dist/runtime/utils/message-repository-session.js.map +1 -0
  113. package/dist/runtime/utils/message-repository.d.ts.map +1 -1
  114. package/dist/runtime/utils/message-repository.js +3 -4
  115. package/dist/runtime/utils/message-repository.js.map +1 -1
  116. package/dist/runtime/utils/pending-tool-calls.d.ts +20 -0
  117. package/dist/runtime/utils/pending-tool-calls.d.ts.map +1 -0
  118. package/dist/runtime/utils/pending-tool-calls.js +22 -0
  119. package/dist/runtime/utils/pending-tool-calls.js.map +1 -0
  120. package/dist/runtime/utils/thread-message-like.d.ts +2 -1
  121. package/dist/runtime/utils/thread-message-like.d.ts.map +1 -1
  122. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  123. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +2 -5
  124. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  125. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +3 -10
  126. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  127. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +19 -1
  128. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  129. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +122 -20
  130. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  131. package/dist/runtimes/local/local-thread-runtime-core.d.ts +3 -1
  132. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  133. package/dist/runtimes/local/local-thread-runtime-core.js +20 -2
  134. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  135. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts +12 -1
  136. package/dist/runtimes/remote-thread-list/remote-thread-state.d.ts.map +1 -1
  137. package/dist/runtimes/remote-thread-list/remote-thread-state.js +77 -1
  138. package/dist/runtimes/remote-thread-list/remote-thread-state.js.map +1 -1
  139. package/dist/runtimes/tool-invocations/ToolInvocationTracker.d.ts.map +1 -1
  140. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js +2 -0
  141. package/dist/runtimes/tool-invocations/ToolInvocationTracker.js.map +1 -1
  142. package/dist/store/clients/external-thread.d.ts.map +1 -1
  143. package/dist/store/clients/external-thread.js +74 -99
  144. package/dist/store/clients/external-thread.js.map +1 -1
  145. package/dist/store/clients/single-thread-list.d.ts.map +1 -1
  146. package/dist/store/clients/single-thread-list.js +1 -1
  147. package/dist/store/clients/single-thread-list.js.map +1 -1
  148. package/dist/store/clients/suggestions.d.ts.map +1 -1
  149. package/dist/store/clients/suggestions.js +16 -7
  150. package/dist/store/clients/suggestions.js.map +1 -1
  151. package/dist/store/index.d.ts +4 -2
  152. package/dist/store/scope-registration.d.ts +1 -1
  153. package/dist/store/scopes/composer.d.ts +2 -1
  154. package/dist/store/scopes/composer.d.ts.map +1 -1
  155. package/dist/store/scopes/message.d.ts +1 -1
  156. package/dist/store/scopes/queue-item.d.ts +2 -9
  157. package/dist/store/scopes/queue-item.d.ts.map +1 -1
  158. package/dist/store/scopes/queue-item.js +0 -6
  159. package/dist/types/message.d.ts +7 -3
  160. package/dist/types/message.d.ts.map +1 -1
  161. package/dist/types/message.js.map +1 -1
  162. package/dist/utils/data-url.d.ts +14 -1
  163. package/dist/utils/data-url.d.ts.map +1 -1
  164. package/dist/utils/data-url.js +13 -1
  165. package/dist/utils/data-url.js.map +1 -1
  166. package/dist/utils/invoke-user-callback.d.ts +5 -0
  167. package/dist/utils/invoke-user-callback.d.ts.map +1 -0
  168. package/dist/utils/invoke-user-callback.js +19 -0
  169. package/dist/utils/invoke-user-callback.js.map +1 -0
  170. package/dist/utils/normalizePartStatus.js.map +1 -1
  171. package/package.json +10 -10
  172. package/src/internal.ts +12 -0
  173. package/src/model-context/frame/host.test.ts +91 -7
  174. package/src/model-context/frame/host.ts +7 -2
  175. package/src/model-context/frame/provider.test.ts +267 -12
  176. package/src/model-context/frame/provider.ts +113 -31
  177. package/src/model-context/interactable-composer-metadata.ts +1 -1
  178. package/src/model-context/registry.test.ts +44 -0
  179. package/src/model-context/registry.ts +13 -35
  180. package/src/react/client/InMemoryThreadList.test.tsx +49 -0
  181. package/src/react/client/InMemoryThreadList.ts +7 -1
  182. package/src/react/client/Interactables.ts +19 -178
  183. package/src/react/client/RemoteThreadList.test.ts +96 -0
  184. package/src/react/client/RemoteThreadList.ts +27 -59
  185. package/src/react/interactables-legacy/Interactables.ts +20 -177
  186. package/src/react/interactables-shared/useInteractablePersistenceQueue.test.tsx +161 -0
  187. package/src/react/interactables-shared/useInteractablePersistenceQueue.ts +206 -0
  188. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +26 -56
  189. package/src/react/runtimes/assistant-transport/runManager.ts +4 -17
  190. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.test.tsx +92 -0
  191. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +40 -141
  192. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.test.ts +37 -0
  193. package/src/react/runtimes/cloud/createCloudThreadListAdapterCreateFallback.ts +18 -0
  194. package/src/react/runtimes/external-message-converter.test.ts +0 -14
  195. package/src/react/runtimes/external-message-converter.test.tsx +77 -0
  196. package/src/react/runtimes/external-message-converter.ts +58 -481
  197. package/src/runtime/api/composer-runtime.ts +1 -1
  198. package/src/runtime/api/message-runtime.test.ts +28 -1
  199. package/src/runtime/base/base-composer-runtime-core.ts +20 -40
  200. package/src/runtime/base/base-thread-runtime-core.test.ts +16 -0
  201. package/src/runtime/base/base-thread-runtime-core.ts +6 -11
  202. package/src/runtime/base/default-thread-composer-runtime-core.ts +1 -4
  203. package/src/runtime/interfaces/composer-runtime-core.ts +1 -1
  204. package/src/runtime/interfaces/thread-runtime-core.ts +1 -1
  205. package/src/runtime/internal.ts +5 -0
  206. package/src/runtime/queue/external-thread-queue-adapter.ts +1 -1
  207. package/src/runtime/queue/message-queue.ts +1 -4
  208. package/src/runtime/queue/queue-item.ts +10 -0
  209. package/src/runtime/utils/abortable-thread-load.test.ts +87 -0
  210. package/src/runtime/utils/abortable-thread-load.ts +53 -0
  211. package/src/runtime/utils/attachment-add-operations.ts +59 -0
  212. package/src/runtime/utils/auto-status.ts +33 -0
  213. package/src/runtime/utils/external-message-conversion.test.ts +113 -0
  214. package/src/runtime/utils/external-message-conversion.ts +494 -0
  215. package/src/runtime/utils/external-store-message.ts +2 -0
  216. package/src/runtime/utils/message-repository-session.test.ts +195 -0
  217. package/src/runtime/utils/message-repository-session.ts +176 -0
  218. package/src/runtime/utils/message-repository.ts +7 -4
  219. package/src/runtime/utils/pending-tool-calls.test.ts +57 -0
  220. package/src/runtime/utils/pending-tool-calls.ts +35 -0
  221. package/src/runtime/utils/thread-message-like.ts +2 -0
  222. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +6 -14
  223. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +175 -28
  224. package/src/runtimes/local/local-thread-runtime-core.test.ts +172 -1
  225. package/src/runtimes/local/local-thread-runtime-core.ts +48 -3
  226. package/src/runtimes/remote-thread-list/remote-thread-state.test.ts +37 -0
  227. package/src/runtimes/remote-thread-list/remote-thread-state.ts +123 -0
  228. package/src/runtimes/tool-invocations/EDGE_CASES.md +17 -0
  229. package/src/runtimes/tool-invocations/ToolInvocationTracker.test.ts +140 -0
  230. package/src/runtimes/tool-invocations/ToolInvocationTracker.ts +3 -0
  231. package/src/store/clients/external-thread.ts +16 -61
  232. package/src/store/clients/single-thread-list.ts +4 -1
  233. package/src/store/clients/suggestions.test.ts +31 -0
  234. package/src/store/clients/suggestions.ts +18 -19
  235. package/src/store/scopes/message.ts +1 -1
  236. package/src/store/scopes/queue-item.ts +2 -9
  237. package/src/tests/MessageRepository.test.ts +77 -0
  238. package/src/tests/RemoteThreadListThreadListRuntimeCore-load-race.test.ts +194 -0
  239. package/src/tests/RemoteThreadListThreadListRuntimeCore-title-race.test.ts +158 -0
  240. package/src/tests/external-store-thread-runtime-core-adapter.test.ts +164 -0
  241. package/src/tests/external-store-thread-runtime-core.test.ts +642 -0
  242. package/src/tests/external-thread-attachments.test.tsx +173 -0
  243. package/src/tests/single-thread-list-item.test.tsx +47 -0
  244. package/src/tests/thread-message-like.test.ts +39 -1
  245. package/src/types/message.ts +8 -3
  246. package/src/utils/data-url.test.ts +52 -1
  247. package/src/utils/data-url.ts +21 -0
  248. package/src/utils/invoke-user-callback.test.ts +82 -0
  249. package/src/utils/invoke-user-callback.ts +31 -0
  250. package/src/utils/normalizePartStatus.ts +1 -1
  251. package/dist/store/scopes/queue-item.js.map +0 -1
@@ -54,6 +54,83 @@ const renderConverter = (initialProps: Props = {}) =>
54
54
  );
55
55
 
56
56
  describe("useExternalMessageConverter", () => {
57
+ it("refreshes positional fallback ids when an id-less message is prepended", () => {
58
+ const idlessConvert: useExternalMessageConverter.Callback<TestMessage> = (
59
+ message,
60
+ ) => ({
61
+ role: message.role,
62
+ content: [{ type: "text", text: message.text }],
63
+ });
64
+
65
+ const older: TestMessage = { id: "u0", role: "user", text: "older" };
66
+ const newer: TestMessage = { id: "u1", role: "user", text: "newer" };
67
+
68
+ const { result, rerender } = renderHook(
69
+ ({ messages }: { messages: TestMessage[] }) =>
70
+ useExternalMessageConverter<TestMessage>({
71
+ callback: idlessConvert,
72
+ messages,
73
+ isRunning: false,
74
+ metadata: EMPTY,
75
+ }),
76
+ { initialProps: { messages: [newer] } },
77
+ );
78
+
79
+ rerender({ messages: [older, newer] });
80
+
81
+ const ids = result.current.map((message) => message.id);
82
+ expect(ids).toEqual([
83
+ "__external_store_fallback_0",
84
+ "__external_store_fallback_1",
85
+ ]);
86
+ expect(
87
+ result.current.map((message) => (message.content[0] as any).text),
88
+ ).toEqual(["older", "newer"]);
89
+ });
90
+
91
+ it("never rewrites a caller-supplied id that matches the generated shape", () => {
92
+ const explicitConvert: useExternalMessageConverter.Callback<TestMessage> = (
93
+ message,
94
+ ) => ({
95
+ role: message.role,
96
+ id: message.id === "explicit" ? "__external_store_fallback_0" : undefined,
97
+ content: [{ type: "text", text: message.text }],
98
+ });
99
+
100
+ const explicit: TestMessage = {
101
+ id: "explicit",
102
+ role: "user",
103
+ text: "kept",
104
+ };
105
+ const older: TestMessage = { id: "u0", role: "user", text: "older" };
106
+
107
+ const { result, rerender } = renderHook(
108
+ ({ messages }: { messages: TestMessage[] }) =>
109
+ useExternalMessageConverter<TestMessage>({
110
+ callback: explicitConvert,
111
+ messages,
112
+ isRunning: false,
113
+ metadata: EMPTY,
114
+ }),
115
+ { initialProps: { messages: [explicit] } },
116
+ );
117
+
118
+ rerender({ messages: [older, explicit] });
119
+
120
+ // The caller-supplied id survives untouched — at the cost of colliding
121
+ // with the id minted for the prepended message, since the caller chose an
122
+ // id inside the reserved fallback namespace. The full list makes that
123
+ // trade-off explicit.
124
+ expect(result.current.map((message) => message.id)).toEqual([
125
+ "__external_store_fallback_0",
126
+ "__external_store_fallback_0",
127
+ ]);
128
+ const explicitOut = result.current.find(
129
+ (message) => (message.content[0] as any).text === "kept",
130
+ );
131
+ expect(explicitOut?.id).toBe("__external_store_fallback_0");
132
+ });
133
+
57
134
  it("reuses converted messages across rerenders when inputs are unchanged", () => {
58
135
  const { result, rerender } = renderConverter();
59
136
 
@@ -1,432 +1,46 @@
1
1
  "use client";
2
2
 
3
3
  import { useMemo, useState } from "react";
4
- import { ThreadMessageConverter } from "../../runtimes/external-store/thread-message-converter";
5
- import {
6
- getExternalStoreMessages,
7
- symbolInnerMessage,
8
- bindExternalStoreMessage,
9
- } from "../../runtime/utils/external-store-message";
10
4
  import {
11
- fromThreadMessageLike,
12
- type ThreadMessageLike,
13
- } from "../../runtime/utils/thread-message-like";
14
- import { getAutoStatus, isAutoStatus } from "../../runtime/utils/auto-status";
15
- import type { ToolExecutionStatus } from "../../runtimes/tool-invocations/ToolInvocationTracker";
16
- import type { ReadonlyJSONValue } from "assistant-stream/utils";
17
- import { generateErrorMessageId } from "../../utils/id";
18
- import type {
19
- ThreadAssistantMessage,
20
- ThreadMessage,
21
- ToolCallMessagePart,
22
- } from "../../types/message";
23
- import type { MessageTiming } from "../../types/message";
24
-
25
- export type JoinStrategy = "concat-content" | "none";
26
-
27
- type ThreadMessageLikeContentItem = Exclude<
28
- ThreadMessageLike["content"],
29
- string
30
- >[number];
5
+ chunkExternalMessages,
6
+ completeExternalMessageConversion,
7
+ convertExternalMessageCallback,
8
+ convertExternalMessageChunk,
9
+ convertExternalMessages as convertExternalMessagesInternal,
10
+ shallowArrayEqual,
11
+ type ExternalMessageConverterCallback,
12
+ type ExternalMessageConverterCallbackResult,
13
+ type ExternalMessageConverterChunk,
14
+ type ExternalMessageConverterMessage,
15
+ type ExternalMessageConverterMetadata,
16
+ type JoinStrategy,
17
+ } from "../../runtime/utils/external-message-conversion";
18
+ import { bindExternalStoreMessage } from "../../runtime/utils/external-store-message";
19
+ import { ThreadMessageConverter } from "../../runtimes/external-store/thread-message-converter";
20
+ import type { ThreadMessage } from "../../types/message";
31
21
 
32
- const isPendingToolCall = (c: ThreadMessageLikeContentItem): boolean =>
33
- c.type === "tool-call" && c.result === undefined;
22
+ // Generatedness is tracked by identity, not by id shape: a caller-supplied id
23
+ // that happens to match the generated pattern must never be rewritten.
24
+ const generatedFallbackMessages = new WeakSet<object>();
34
25
 
35
- const isInterruptedToolCall = (c: ThreadMessageLikeContentItem): boolean => {
36
- if (c.type !== "tool-call" || c.result !== undefined) return false;
37
- return (
38
- c.interrupt != null ||
39
- (c.approval != null &&
40
- c.approval.approved === undefined &&
41
- c.approval.resolution === undefined)
42
- );
43
- };
26
+ export type { JoinStrategy };
44
27
 
45
28
  export namespace useExternalMessageConverter {
46
- export type Message =
47
- | (ThreadMessageLike & {
48
- readonly convertConfig?: {
49
- readonly joinStrategy?: JoinStrategy;
50
- };
51
- })
52
- | {
53
- role: "tool";
54
- toolCallId: string;
55
- toolName?: string | undefined;
56
- result: any;
57
- artifact?: any;
58
- isError?: boolean;
59
- messages?: readonly ThreadMessage[];
60
- };
61
-
62
- export type Metadata = {
63
- readonly toolStatuses?: Record<string, ToolExecutionStatus>;
64
- readonly error?: ReadonlyJSONValue;
65
- readonly messageTiming?: Record<string, MessageTiming>;
66
- };
67
-
68
- export type Callback<T> = (
69
- message: T,
70
- metadata: Metadata,
71
- ) => Message | Message[];
72
- }
73
-
74
- type CallbackResult<T> = {
75
- input: T;
76
- outputs: useExternalMessageConverter.Message[];
77
- };
78
-
79
- const stringifyForError = (value: unknown) => {
80
- let text;
81
- try {
82
- text = value instanceof Error ? String(value) : JSON.stringify(value);
83
- } catch {
84
- /* circular */
85
- }
86
- text ??= String(value);
87
- return text.length > 200 ? `${text.slice(0, 200)}…` : text;
88
- };
89
-
90
- const toCallbackOutputs = (
91
- output:
92
- | useExternalMessageConverter.Message
93
- | useExternalMessageConverter.Message[],
94
- input: unknown,
95
- ): useExternalMessageConverter.Message[] => {
96
- const outputs = Array.isArray(output) ? output : [output];
97
- for (const o of outputs) {
98
- const valid =
99
- typeof o === "object" &&
100
- o !== null &&
101
- (o.role === "tool" ||
102
- ((o.role === "assistant" || o.role === "user" || o.role === "system") &&
103
- (typeof o.content === "string" || Array.isArray(o.content))));
104
- if (!valid)
105
- throw new Error(
106
- `External message converter: the converter callback returned an invalid message (${stringifyForError(o)}) for input ${stringifyForError(input)}. Return an empty array to skip a message.`,
107
- );
108
- }
109
- return outputs;
110
- };
111
-
112
- type CallbackCacheEntry<T> = CallbackResult<T> & {
113
- metadata: useExternalMessageConverter.Metadata;
114
- callback: useExternalMessageConverter.Callback<T>;
115
- };
116
-
117
- type ChunkResult<T> = {
118
- inputs: T[];
119
- outputs: useExternalMessageConverter.Message[];
120
- };
121
-
122
- type Mutable<T> = {
123
- -readonly [P in keyof T]: T[P];
124
- };
125
-
126
- const mergeInnerMessages = (existing: object, incoming: object) => ({
127
- [symbolInnerMessage]: [
128
- ...((existing as any)[symbolInnerMessage] ?? []),
129
- ...((incoming as any)[symbolInnerMessage] ?? []),
130
- ],
131
- });
132
-
133
- const joinExternalMessages = (
134
- messages: readonly useExternalMessageConverter.Message[],
135
- ): ThreadMessageLike => {
136
- const assistantMessage: Mutable<Omit<ThreadMessageLike, "metadata">> & {
137
- content: Exclude<ThreadMessageLike["content"][0], string>[];
138
- metadata?: Mutable<ThreadMessageLike["metadata"]>;
139
- } = {
140
- role: "assistant",
141
- content: [],
142
- };
143
- for (const output of messages) {
144
- if (output.role === "tool") {
145
- const toolCallIdx = assistantMessage.content.findIndex(
146
- (c) => c.type === "tool-call" && c.toolCallId === output.toolCallId,
147
- );
148
- // Ignore orphaned tool results so one bad tool message does not
149
- // prevent rendering the rest of the conversation.
150
- if (toolCallIdx !== -1) {
151
- const toolCall = assistantMessage.content[
152
- toolCallIdx
153
- ]! as ToolCallMessagePart;
154
- if (output.toolName != null) {
155
- if (toolCall.toolName !== output.toolName)
156
- throw new Error(
157
- `Tool call name ${output.toolCallId} ${output.toolName} does not match existing tool call ${toolCall.toolName}`,
158
- );
159
- }
160
- assistantMessage.content[toolCallIdx] = {
161
- ...toolCall,
162
- ...{
163
- [symbolInnerMessage]: [
164
- ...((toolCall as any)[symbolInnerMessage] ?? []),
165
- output,
166
- ],
167
- },
168
- result: output.result,
169
- artifact: output.artifact,
170
- isError: output.isError,
171
- messages: output.messages,
172
- };
173
- }
174
- } else {
175
- const role = output.role;
176
- const content = (
177
- typeof output.content === "string"
178
- ? [{ type: "text" as const, text: output.content }]
179
- : output.content
180
- ).map((c) => ({
181
- ...c,
182
- ...{ [symbolInnerMessage]: [output] },
183
- }));
184
- switch (role) {
185
- case "system":
186
- case "user":
187
- return {
188
- ...output,
189
- content,
190
- };
191
- case "assistant":
192
- if (assistantMessage.content.length === 0) {
193
- assistantMessage.id = output.id;
194
- assistantMessage.createdAt ??= output.createdAt;
195
- assistantMessage.status ??= output.status;
196
-
197
- if (output.attachments) {
198
- assistantMessage.attachments = [
199
- ...(assistantMessage.attachments ?? []),
200
- ...output.attachments,
201
- ];
202
- }
203
- }
204
-
205
- if (output.metadata) {
206
- assistantMessage.metadata ??= {};
207
- if (output.metadata.unstable_state !== undefined) {
208
- assistantMessage.metadata.unstable_state =
209
- output.metadata.unstable_state;
210
- }
211
- if (output.metadata.unstable_annotations) {
212
- assistantMessage.metadata.unstable_annotations = [
213
- ...(assistantMessage.metadata.unstable_annotations ?? []),
214
- ...output.metadata.unstable_annotations,
215
- ];
216
- }
217
- if (output.metadata.unstable_data) {
218
- assistantMessage.metadata.unstable_data = [
219
- ...(assistantMessage.metadata.unstable_data ?? []),
220
- ...output.metadata.unstable_data,
221
- ];
222
- }
223
- if (output.metadata.steps) {
224
- assistantMessage.metadata.steps = [
225
- ...(assistantMessage.metadata.steps ?? []),
226
- ...output.metadata.steps,
227
- ];
228
- }
229
- if (output.metadata.custom) {
230
- assistantMessage.metadata.custom = {
231
- ...(assistantMessage.metadata.custom ?? {}),
232
- ...output.metadata.custom,
233
- };
234
- }
235
-
236
- if (output.metadata.timing) {
237
- assistantMessage.metadata.timing = output.metadata.timing;
238
- }
239
-
240
- if (output.metadata.submittedFeedback) {
241
- assistantMessage.metadata.submittedFeedback =
242
- output.metadata.submittedFeedback;
243
- }
244
-
245
- if (output.metadata.isOptimistic) {
246
- assistantMessage.metadata.isOptimistic = true;
247
- }
248
- // TODO keep this in sync with ThreadMessageLike["metadata"] / fromThreadMessageLike
249
- }
250
-
251
- // Add content parts, merging reasoning parts with same parentId
252
- for (const part of content) {
253
- if (part.type === "tool-call") {
254
- const existingIdx = assistantMessage.content.findIndex(
255
- (c) =>
256
- c.type === "tool-call" && c.toolCallId === part.toolCallId,
257
- );
258
- if (existingIdx !== -1) {
259
- const existing = assistantMessage.content[
260
- existingIdx
261
- ] as typeof part;
262
- assistantMessage.content[existingIdx] = {
263
- ...existing,
264
- ...part,
265
- ...mergeInnerMessages(existing, part),
266
- };
267
- continue;
268
- }
269
- }
270
-
271
- if (
272
- part.type === "reasoning" &&
273
- "parentId" in part &&
274
- part.parentId
275
- ) {
276
- const existingIdx = assistantMessage.content.findIndex(
277
- (c) =>
278
- c.type === "reasoning" &&
279
- "parentId" in c &&
280
- c.parentId === part.parentId,
281
- );
282
- if (existingIdx !== -1) {
283
- const existing = assistantMessage.content[
284
- existingIdx
285
- ] as typeof part;
286
- assistantMessage.content[existingIdx] = {
287
- ...existing,
288
- text: `${existing.text}\n\n${part.text}`,
289
- ...mergeInnerMessages(existing, part),
290
- };
291
- continue;
292
- }
293
- }
294
- assistantMessage.content.push(part);
295
- }
296
- break;
297
- default: {
298
- const unsupportedRole: never = role;
299
- throw new Error(`Unknown message role: ${unsupportedRole}`);
300
- }
301
- }
302
- }
303
- }
304
- return assistantMessage;
305
- };
306
-
307
- const chunkExternalMessages = <T>(
308
- callbackResults: CallbackResult<T>[],
309
- joinStrategy?: JoinStrategy,
310
- ) => {
311
- const results: ChunkResult<T>[] = [];
312
- let isAssistant = false;
313
- let pendingNone = false; // true if the previous assistant message had joinStrategy "none"
314
- let inputs: T[] = [];
315
- let outputs: useExternalMessageConverter.Message[] = [];
316
-
317
- const flush = () => {
318
- if (outputs.length) {
319
- results.push({
320
- inputs,
321
- outputs,
322
- });
323
- }
324
- inputs = [];
325
- outputs = [];
326
- isAssistant = false;
327
- pendingNone = false;
328
- };
329
-
330
- for (const callbackResult of callbackResults) {
331
- for (const output of callbackResult.outputs) {
332
- if (
333
- (pendingNone && output.role !== "tool") ||
334
- !isAssistant ||
335
- output.role === "user" ||
336
- output.role === "system"
337
- ) {
338
- flush();
339
- }
340
- isAssistant = output.role === "assistant" || output.role === "tool";
341
-
342
- if (inputs.at(-1) !== callbackResult.input) {
343
- inputs.push(callbackResult.input);
344
- }
345
- outputs.push(output);
346
-
347
- if (
348
- output.role === "assistant" &&
349
- (output.convertConfig?.joinStrategy === "none" ||
350
- joinStrategy === "none")
351
- ) {
352
- pendingNone = true;
353
- }
354
- }
355
- }
356
- flush();
357
- return results;
358
- };
359
-
360
- function createErrorAssistantMessage(
361
- error: ReadonlyJSONValue,
362
- ): ThreadAssistantMessage {
363
- const msg: ThreadAssistantMessage = {
364
- id: generateErrorMessageId(),
365
- role: "assistant",
366
- content: [],
367
- status: { type: "incomplete", reason: "error", error },
368
- createdAt: new Date(),
369
- metadata: {
370
- unstable_state: null,
371
- unstable_annotations: [],
372
- unstable_data: [],
373
- custom: {},
374
- steps: [],
375
- },
376
- };
377
- bindExternalStoreMessage(msg, []);
378
- return msg;
29
+ export type Message = ExternalMessageConverterMessage;
30
+ export type Metadata = ExternalMessageConverterMetadata;
31
+ export type Callback<T> = ExternalMessageConverterCallback<T>;
379
32
  }
380
33
 
381
- export const convertExternalMessages = <T extends WeakKey>(
34
+ export const convertExternalMessages: <T extends WeakKey>(
382
35
  messages: T[],
383
36
  callback: useExternalMessageConverter.Callback<T>,
384
37
  isRunning: boolean,
385
38
  metadata: useExternalMessageConverter.Metadata,
386
- ) => {
387
- const callbackResults: CallbackResult<T>[] = [];
388
- for (const message of messages) {
389
- const output = callback(message, metadata);
390
- const outputs = toCallbackOutputs(output, message);
391
- const result = { input: message, outputs };
392
- callbackResults.push(result);
393
- }
394
-
395
- const chunks = chunkExternalMessages(callbackResults);
396
-
397
- const result = chunks.map((message, idx) => {
398
- const isLast = idx === chunks.length - 1;
399
- const joined = joinExternalMessages(message.outputs);
400
- const hasInterruptedToolCalls =
401
- typeof joined.content === "object" &&
402
- joined.content.some(isInterruptedToolCall);
403
- const hasPendingToolCalls =
404
- typeof joined.content === "object" &&
405
- joined.content.some(isPendingToolCall);
406
- const autoStatus = getAutoStatus(
407
- isLast,
408
- isRunning,
409
- hasInterruptedToolCalls,
410
- hasPendingToolCalls,
411
- isLast ? metadata.error : undefined,
412
- );
413
- const newMessage = fromThreadMessageLike(
414
- joined,
415
- idx.toString(),
416
- autoStatus,
417
- );
418
- bindExternalStoreMessage(newMessage, message.inputs);
419
- return newMessage;
420
- });
39
+ ) => ThreadMessage[] = convertExternalMessagesInternal;
421
40
 
422
- if (metadata.error) {
423
- const lastMessage = result.at(-1);
424
- if (!lastMessage || lastMessage.role !== "assistant") {
425
- result.push(createErrorAssistantMessage(metadata.error));
426
- }
427
- }
428
-
429
- return result;
41
+ type CallbackCacheEntry<T> = ExternalMessageConverterCallbackResult<T> & {
42
+ metadata: useExternalMessageConverter.Metadata;
43
+ callback: useExternalMessageConverter.Callback<T>;
430
44
  };
431
45
 
432
46
  export const useExternalMessageConverter = <T extends WeakKey>({
@@ -452,8 +66,8 @@ export const useExternalMessageConverter = <T extends WeakKey>({
452
66
  const [caches] = useState(() => ({
453
67
  callbackCache: new WeakMap<T, CallbackCacheEntry<T>>(),
454
68
  chunkCache: new WeakMap<
455
- useExternalMessageConverter.Message,
456
- ChunkResult<T>
69
+ ExternalMessageConverterMessage,
70
+ ExternalMessageConverterChunk<T>
457
71
  >(),
458
72
  converterCache: new ThreadMessageConverter(),
459
73
  }));
@@ -468,7 +82,7 @@ export const useExternalMessageConverter = <T extends WeakKey>({
468
82
  );
469
83
 
470
84
  return useMemo(() => {
471
- const callbackResults: CallbackResult<T>[] = [];
85
+ const callbackResults: ExternalMessageConverterCallbackResult<T>[] = [];
472
86
  for (const message of messages) {
473
87
  let result = state.callbackCache.get(message);
474
88
  if (
@@ -476,11 +90,12 @@ export const useExternalMessageConverter = <T extends WeakKey>({
476
90
  result.metadata !== state.metadata ||
477
91
  result.callback !== state.callback
478
92
  ) {
479
- const output = state.callback(message, state.metadata);
480
- const outputs = toCallbackOutputs(output, message);
481
93
  result = {
482
- input: message,
483
- outputs,
94
+ ...convertExternalMessageCallback(
95
+ message,
96
+ state.callback,
97
+ state.metadata,
98
+ ),
484
99
  metadata: state.metadata,
485
100
  callback: state.callback,
486
101
  };
@@ -490,77 +105,39 @@ export const useExternalMessageConverter = <T extends WeakKey>({
490
105
  }
491
106
 
492
107
  const chunks = chunkExternalMessages(callbackResults, joinStrategy).map(
493
- (m) => {
494
- const key = m.outputs[0];
495
- if (!key) return m;
108
+ (message) => {
109
+ const key = message.outputs[0];
110
+ if (!key) return message;
496
111
 
497
112
  const cached = state.chunkCache.get(key);
498
- if (cached && shallowArrayEqual(cached.outputs, m.outputs))
113
+ if (cached && shallowArrayEqual(cached.outputs, message.outputs)) {
499
114
  return cached;
500
- state.chunkCache.set(key, m);
501
- return m;
115
+ }
116
+ state.chunkCache.set(key, message);
117
+ return message;
502
118
  },
503
119
  );
504
120
 
505
121
  const threadMessages = state.converterCache.convertMessages(
506
122
  chunks,
507
- (cache, message, idx) => {
508
- const isLast = idx === chunks.length - 1;
509
-
510
- const joined = joinExternalMessages(message.outputs);
511
- const hasInterruptedToolCalls =
512
- typeof joined.content === "object" &&
513
- joined.content.some(isInterruptedToolCall);
514
- const hasPendingToolCalls =
515
- typeof joined.content === "object" &&
516
- joined.content.some(isPendingToolCall);
517
- const autoStatus = getAutoStatus(
518
- isLast,
123
+ (cache, message, idx) =>
124
+ convertExternalMessageChunk(
125
+ message,
126
+ idx,
127
+ chunks.length,
519
128
  isRunning,
520
- hasInterruptedToolCalls,
521
- hasPendingToolCalls,
522
- isLast ? state.metadata.error : undefined,
523
- );
524
-
525
- if (
526
- cache &&
527
- (cache.role !== "assistant" ||
528
- !isAutoStatus(cache.status) ||
529
- cache.status === autoStatus)
530
- ) {
531
- const inputs = getExternalStoreMessages<T>(cache);
532
- if (shallowArrayEqual(inputs, message.inputs)) {
533
- return cache;
534
- }
535
- }
536
-
537
- const newMessage = fromThreadMessageLike(
538
- joined,
539
- idx.toString(),
540
- autoStatus,
541
- );
542
- bindExternalStoreMessage(newMessage, message.inputs);
543
- return newMessage;
544
- },
129
+ state.metadata.error,
130
+ {
131
+ message: cache,
132
+ generatedFallbackMessages,
133
+ },
134
+ ),
545
135
  );
546
136
 
547
137
  bindExternalStoreMessage(threadMessages, messages);
548
-
549
- if (state.metadata.error) {
550
- const lastMessage = threadMessages.at(-1);
551
- if (!lastMessage || lastMessage.role !== "assistant") {
552
- threadMessages.push(createErrorAssistantMessage(state.metadata.error));
553
- }
554
- }
555
-
556
- return threadMessages;
138
+ return completeExternalMessageConversion(
139
+ threadMessages,
140
+ state.metadata.error,
141
+ );
557
142
  }, [state, messages, isRunning, joinStrategy]);
558
143
  };
559
-
560
- const shallowArrayEqual = (a: unknown[], b: unknown[]) => {
561
- if (a.length !== b.length) return false;
562
- for (let i = 0; i < a.length; i++) {
563
- if (a[i] !== b[i]) return false;
564
- }
565
- return true;
566
- };
@@ -3,7 +3,7 @@ import type { MessageRole } from "../../types/message";
3
3
  import type { QuoteInfo } from "../../types/quote";
4
4
  import type { Unsubscribe } from "../../types/unsubscribe";
5
5
  import type { RunConfig } from "../../types/message";
6
- import type { QueueItemState } from "../../store/scopes/queue-item";
6
+ import type { QueueItemState } from "../queue/queue-item";
7
7
  import type { QueuePlacement } from "../queue/external-thread-queue-adapter";
8
8
  import {
9
9
  LazyMemoizeSubject,