@assistant-ui/core 0.3.2 → 0.3.4

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 (169) hide show
  1. package/dist/index.d.ts +4 -4
  2. package/dist/index.js.map +1 -1
  3. package/dist/internal.d.ts +9 -7
  4. package/dist/internal.js +8 -6
  5. package/dist/react/primitive-hooks/useActionBarCopy.js +51 -24
  6. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  7. package/dist/react/primitive-hooks/useSuggestionTrigger.js +29 -25
  8. package/dist/react/primitive-hooks/useSuggestionTrigger.js.map +1 -1
  9. package/dist/react/primitives/message/MessageGroupedParts.d.ts +2 -1
  10. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  11. package/dist/react/primitives/message/MessageGroupedParts.js +2 -2
  12. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  13. package/dist/react/primitives/message/MessageParts.d.ts +6 -1
  14. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  15. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  16. package/dist/react/providers/ReadonlyThreadProvider.d.ts.map +1 -1
  17. package/dist/react/providers/ReadonlyThreadProvider.js +1 -0
  18. package/dist/react/providers/ReadonlyThreadProvider.js.map +1 -1
  19. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +67 -0
  20. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  21. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +76 -15
  22. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  23. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +4 -0
  24. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  25. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +21 -0
  26. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  27. package/dist/react/runtimes/cloud/auiV0.d.ts +26 -2
  28. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  29. package/dist/react/runtimes/cloud/auiV0.js +29 -8
  30. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  31. package/dist/react/types/MessagePartComponentTypes.d.ts +8 -0
  32. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  33. package/dist/runtime/api/bindings.d.ts +5 -0
  34. package/dist/runtime/api/bindings.d.ts.map +1 -1
  35. package/dist/runtime/api/message-runtime.d.ts.map +1 -1
  36. package/dist/runtime/api/message-runtime.js +5 -1
  37. package/dist/runtime/api/message-runtime.js.map +1 -1
  38. package/dist/runtime/api/thread-list-runtime.d.ts +14 -1
  39. package/dist/runtime/api/thread-list-runtime.d.ts.map +1 -1
  40. package/dist/runtime/api/thread-list-runtime.js +6 -1
  41. package/dist/runtime/api/thread-list-runtime.js.map +1 -1
  42. package/dist/runtime/api/thread-runtime.d.ts +7 -1
  43. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  44. package/dist/runtime/api/thread-runtime.js +11 -3
  45. package/dist/runtime/api/thread-runtime.js.map +1 -1
  46. package/dist/runtime/base/base-composer-runtime-core.d.ts +3 -0
  47. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  48. package/dist/runtime/base/base-composer-runtime-core.js +54 -12
  49. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  50. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts +10 -0
  51. package/dist/runtime/interfaces/thread-list-runtime-core.d.ts.map +1 -1
  52. package/dist/runtime/interfaces/thread-runtime-core.d.ts +14 -0
  53. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  54. package/dist/runtime/internal.d.ts +4 -4
  55. package/dist/runtime/internal.js +4 -4
  56. package/dist/runtimes/external-store/external-store-adapter.d.ts +9 -0
  57. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  58. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -0
  59. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  60. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +4 -0
  61. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  62. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +1 -0
  63. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  64. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +6 -0
  65. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  66. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -0
  67. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  68. package/dist/runtimes/local/local-thread-runtime-core.js +1 -0
  69. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  70. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +1 -0
  71. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  72. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +1 -0
  73. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  74. package/dist/runtimes/remote-thread-list/empty-thread-core.js +1 -0
  75. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  76. package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
  77. package/dist/store/clients/chain-of-thought-client.js +29 -23
  78. package/dist/store/clients/chain-of-thought-client.js.map +1 -1
  79. package/dist/store/clients/thread-message-client.d.ts.map +1 -1
  80. package/dist/store/clients/thread-message-client.js +101 -89
  81. package/dist/store/clients/thread-message-client.js.map +1 -1
  82. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts +1 -0
  83. package/dist/store/runtime-clients/thread-list-item-runtime-client.d.ts.map +1 -1
  84. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +66 -45
  85. package/dist/store/runtime-clients/thread-list-item-runtime-client.js.map +1 -1
  86. package/dist/store/runtime-clients/thread-list-runtime-client.d.ts.map +1 -1
  87. package/dist/store/runtime-clients/thread-list-runtime-client.js +91 -75
  88. package/dist/store/runtime-clients/thread-list-runtime-client.js.map +1 -1
  89. package/dist/store/scopes/thread-list-item.d.ts +7 -0
  90. package/dist/store/scopes/thread-list-item.d.ts.map +1 -1
  91. package/dist/store/scopes/threads.d.ts +1 -0
  92. package/dist/store/scopes/threads.d.ts.map +1 -1
  93. package/dist/types/index.d.ts +2 -2
  94. package/dist/types/message.d.ts +22 -1
  95. package/dist/types/message.d.ts.map +1 -1
  96. package/dist/types/message.js.map +1 -1
  97. package/dist/utils/data-url.d.ts +14 -1
  98. package/dist/utils/data-url.d.ts.map +1 -1
  99. package/dist/utils/data-url.js +23 -1
  100. package/dist/utils/data-url.js.map +1 -1
  101. package/dist/utils/getGroupStatus.d.ts +9 -0
  102. package/dist/utils/getGroupStatus.d.ts.map +1 -0
  103. package/dist/utils/getGroupStatus.js +15 -0
  104. package/dist/utils/getGroupStatus.js.map +1 -0
  105. package/dist/utils/image-media-type.d.ts +13 -0
  106. package/dist/utils/image-media-type.d.ts.map +1 -0
  107. package/dist/utils/image-media-type.js +141 -0
  108. package/dist/utils/image-media-type.js.map +1 -0
  109. package/dist/utils/normalizePartStatus.d.ts +8 -0
  110. package/dist/utils/normalizePartStatus.d.ts.map +1 -0
  111. package/dist/utils/normalizePartStatus.js +39 -0
  112. package/dist/utils/normalizePartStatus.js.map +1 -0
  113. package/dist/utils/wire-media.d.ts +35 -0
  114. package/dist/utils/wire-media.d.ts.map +1 -0
  115. package/dist/utils/wire-media.js +55 -0
  116. package/dist/utils/wire-media.js.map +1 -0
  117. package/package.json +4 -4
  118. package/src/index.ts +1 -0
  119. package/src/internal.ts +13 -1
  120. package/src/react/primitive-hooks/useActionBarCopy.test.ts +109 -14
  121. package/src/react/primitive-hooks/useActionBarCopy.ts +27 -2
  122. package/src/react/primitive-hooks/useSuggestionTrigger.test.ts +148 -0
  123. package/src/react/primitive-hooks/useSuggestionTrigger.ts +10 -4
  124. package/src/react/primitives/message/MessageGroupedParts.tsx +4 -4
  125. package/src/react/primitives/message/MessageParts.tsx +6 -1
  126. package/src/react/providers/ReadonlyThreadProvider.tsx +1 -0
  127. package/src/react/runtimes/RemoteThreadListHookInstanceManager.running.test.tsx +157 -0
  128. package/src/react/runtimes/RemoteThreadListHookInstanceManager.test.tsx +160 -0
  129. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +135 -32
  130. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +37 -0
  131. package/src/react/runtimes/cloud/auiV0.ts +59 -14
  132. package/src/react/types/MessagePartComponentTypes.ts +8 -0
  133. package/src/runtime/api/bindings.ts +5 -0
  134. package/src/runtime/api/message-runtime.test.ts +140 -0
  135. package/src/runtime/api/message-runtime.ts +10 -5
  136. package/src/runtime/api/thread-list-runtime.ts +22 -1
  137. package/src/runtime/api/thread-runtime.ts +15 -6
  138. package/src/runtime/base/base-composer-runtime-core.ts +72 -19
  139. package/src/runtime/interfaces/thread-list-runtime-core.ts +11 -0
  140. package/src/runtime/interfaces/thread-runtime-core.ts +15 -0
  141. package/src/runtimes/external-store/external-store-adapter.ts +9 -0
  142. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +9 -0
  143. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +8 -0
  144. package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
  145. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +1 -0
  146. package/src/runtimes/remote-thread-list/empty-thread-core.ts +1 -0
  147. package/src/store/clients/chain-of-thought-client.ts +2 -7
  148. package/src/store/clients/thread-message-client.test.ts +84 -0
  149. package/src/store/clients/thread-message-client.ts +14 -4
  150. package/src/store/runtime-clients/thread-list-item-runtime-client.ts +13 -2
  151. package/src/store/runtime-clients/thread-list-runtime-client.ts +13 -1
  152. package/src/store/scopes/thread-list-item.ts +7 -0
  153. package/src/store/scopes/threads.ts +1 -0
  154. package/src/tests/RemoteThreadListThreadListRuntimeCore-reloadMainThread.test.ts +257 -0
  155. package/src/tests/auiV0Encode.test.ts +218 -1
  156. package/src/tests/base-composer-runtime-core-addAttachment.test.ts +296 -0
  157. package/src/tests/event-subscription-listener-errors.test.ts +1 -0
  158. package/src/tests/external-store-thread-list-runtime-core.test.ts +39 -1
  159. package/src/types/index.ts +1 -0
  160. package/src/types/message.ts +30 -0
  161. package/src/utils/data-url.ts +24 -0
  162. package/src/utils/getGroupStatus.test.ts +36 -0
  163. package/src/utils/getGroupStatus.ts +31 -0
  164. package/src/utils/image-media-type.test.ts +96 -0
  165. package/src/utils/image-media-type.ts +118 -0
  166. package/src/utils/normalizePartStatus.test.ts +59 -0
  167. package/src/utils/normalizePartStatus.ts +55 -0
  168. package/src/utils/wire-media.test.ts +96 -0
  169. package/src/utils/wire-media.ts +70 -0
@@ -0,0 +1,257 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { createCore, makeAdapter } from "./remote-thread-list-test-helpers";
3
+ import type { RemoteThreadListThreadListRuntimeCore } from "../react/runtimes/RemoteThreadListThreadListRuntimeCore";
4
+
5
+ type HookManagerStub = {
6
+ startThreadRuntime: (id: string) => Promise<unknown>;
7
+ __internal_restartThreadRuntime: (id: string) => Promise<unknown>;
8
+ getThreadRuntimeCore: (id: string) =>
9
+ | {
10
+ unstable_refetchThread?: () => Promise<void>;
11
+ capabilities?: { cancel: boolean };
12
+ cancelRun?: () => void;
13
+ }
14
+ | undefined;
15
+ };
16
+
17
+ const hookManagerOf = (core: RemoteThreadListThreadListRuntimeCore) =>
18
+ (core as unknown as { _hookManager: HookManagerStub })._hookManager;
19
+
20
+ const openRegularThread = async () => {
21
+ const core = createCore(
22
+ makeAdapter({
23
+ list: async () => ({
24
+ threads: [
25
+ {
26
+ status: "regular" as const,
27
+ remoteId: "t-1",
28
+ externalId: "t-1",
29
+ title: "Open",
30
+ },
31
+ ],
32
+ }),
33
+ }),
34
+ );
35
+ await core.getLoadThreadsPromise();
36
+ await core.switchToThread("t-1");
37
+ return core;
38
+ };
39
+
40
+ describe("RemoteThreadListThreadListRuntimeCore.reloadMainThread", () => {
41
+ it("restarts the runtime of the thread that is currently open", async () => {
42
+ const core = await openRegularThread();
43
+ const threadId = core.mainThreadId;
44
+
45
+ const restart = vi.fn(async () => ({}));
46
+ hookManagerOf(core).__internal_restartThreadRuntime = restart;
47
+
48
+ await core.reloadMainThread();
49
+
50
+ expect(restart).toHaveBeenCalledExactlyOnceWith(threadId);
51
+ });
52
+
53
+ it("notifies subscribers so the reloaded thread re-renders", async () => {
54
+ const core = await openRegularThread();
55
+ hookManagerOf(core).__internal_restartThreadRuntime = async () => ({});
56
+
57
+ const callback = vi.fn();
58
+ core.subscribe(callback);
59
+ await core.reloadMainThread();
60
+
61
+ expect(callback).toHaveBeenCalled();
62
+ });
63
+
64
+ it("leaves an unsent thread alone, since it holds no remote state", async () => {
65
+ const core = createCore(makeAdapter());
66
+ await core.switchToNewThread();
67
+
68
+ const restart = vi.fn(async () => ({}));
69
+ hookManagerOf(core).__internal_restartThreadRuntime = restart;
70
+
71
+ await expect(core.reloadMainThread()).resolves.toBeUndefined();
72
+ expect(restart).not.toHaveBeenCalled();
73
+ });
74
+
75
+ it("does nothing before the initial thread is open", async () => {
76
+ // the constructor switches to a new thread, so this is the window between
77
+ // construction and that switch settling
78
+ const core = createCore(makeAdapter());
79
+ (core as unknown as { _mainThreadId: string | undefined })._mainThreadId =
80
+ undefined;
81
+
82
+ const restart = vi.fn(async () => ({}));
83
+ hookManagerOf(core).__internal_restartThreadRuntime = restart;
84
+
85
+ await expect(core.reloadMainThread()).resolves.toBeUndefined();
86
+ expect(restart).not.toHaveBeenCalled();
87
+ });
88
+
89
+ it("still notifies when a redundant switch to the same thread lands mid-reload", async () => {
90
+ const core = await openRegularThread();
91
+ const threadId = core.mainThreadId;
92
+
93
+ let releaseRestart!: () => void;
94
+ hookManagerOf(core).__internal_restartThreadRuntime = () =>
95
+ new Promise((resolve) => {
96
+ releaseRestart = () => resolve({});
97
+ });
98
+
99
+ const reloadTask = core.reloadMainThread();
100
+ // bumps the switch generation without changing the main thread
101
+ await core.switchToThread(threadId);
102
+
103
+ const callback = vi.fn();
104
+ core.subscribe(callback);
105
+ releaseRestart();
106
+ await reloadTask;
107
+
108
+ expect(callback).toHaveBeenCalled();
109
+ });
110
+
111
+ it("resolves quietly when the thread is removed mid-reload", async () => {
112
+ const core = await openRegularThread();
113
+
114
+ hookManagerOf(core).__internal_restartThreadRuntime = async () => {
115
+ (core as unknown as { _mainThreadId: string })._mainThreadId = "other";
116
+ throw new Error("Thread was deleted before runtime was started");
117
+ };
118
+
119
+ await expect(core.reloadMainThread()).resolves.toBeUndefined();
120
+ });
121
+
122
+ it("surfaces a restart failure that is not a lifecycle handover", async () => {
123
+ const core = await openRegularThread();
124
+
125
+ hookManagerOf(core).__internal_restartThreadRuntime = async () => {
126
+ throw new Error("boom");
127
+ };
128
+
129
+ await expect(core.reloadMainThread()).rejects.toThrow("boom");
130
+ });
131
+
132
+ it("lets a switch to a different thread win the notification", async () => {
133
+ const core = await openRegularThread();
134
+
135
+ let releaseRestart!: () => void;
136
+ hookManagerOf(core).__internal_restartThreadRuntime = () =>
137
+ new Promise((resolve) => {
138
+ releaseRestart = () => resolve({});
139
+ });
140
+
141
+ const reloadTask = core.reloadMainThread();
142
+ (core as unknown as { _mainThreadId: string })._mainThreadId = "elsewhere";
143
+
144
+ const callback = vi.fn();
145
+ core.subscribe(callback);
146
+ releaseRestart();
147
+ await reloadTask;
148
+
149
+ expect(callback).not.toHaveBeenCalled();
150
+ });
151
+ });
152
+
153
+ describe("RemoteThreadListThreadListRuntimeCore.reloadMainThread capability dispatch", () => {
154
+ it("prefers the runtime's in-place reload over remounting", async () => {
155
+ const core = await openRegularThread();
156
+ const reload = vi.fn(async () => {});
157
+ const restart = vi.fn(async () => ({}));
158
+ hookManagerOf(core).getThreadRuntimeCore = () => ({
159
+ unstable_refetchThread: reload,
160
+ capabilities: { cancel: false },
161
+ });
162
+ hookManagerOf(core).__internal_restartThreadRuntime = restart;
163
+
164
+ await core.reloadMainThread();
165
+
166
+ expect(reload).toHaveBeenCalledOnce();
167
+ expect(restart).not.toHaveBeenCalled();
168
+ });
169
+
170
+ it("leaves a run in progress to the capability implementation", async () => {
171
+ const core = await openRegularThread();
172
+ const cancelRun = vi.fn();
173
+ hookManagerOf(core).getThreadRuntimeCore = () => ({
174
+ unstable_refetchThread: async () => {},
175
+ capabilities: { cancel: true },
176
+ isRunning: true,
177
+ cancelRun,
178
+ });
179
+
180
+ await core.reloadMainThread();
181
+
182
+ // cancelRun returns the trailing user message to the composer, which is
183
+ // the wrong thing to do to a thread that is only being refetched
184
+ expect(cancelRun).not.toHaveBeenCalled();
185
+ });
186
+
187
+ it("notifies subscribers after an in-place reload", async () => {
188
+ const core = await openRegularThread();
189
+ hookManagerOf(core).getThreadRuntimeCore = () => ({
190
+ unstable_refetchThread: async () => {},
191
+ capabilities: { cancel: false },
192
+ });
193
+
194
+ const callback = vi.fn();
195
+ core.subscribe(callback);
196
+ await core.reloadMainThread();
197
+
198
+ expect(callback).toHaveBeenCalled();
199
+ });
200
+
201
+ it("falls back to remounting when the runtime lacks the capability", async () => {
202
+ const core = await openRegularThread();
203
+ const restart = vi.fn(async () => ({}));
204
+ hookManagerOf(core).getThreadRuntimeCore = () => ({});
205
+ hookManagerOf(core).__internal_restartThreadRuntime = restart;
206
+
207
+ await core.reloadMainThread();
208
+
209
+ expect(restart).toHaveBeenCalledExactlyOnceWith(core.mainThreadId);
210
+ });
211
+
212
+ it("falls back to remounting when no runtime is attached yet", async () => {
213
+ const core = await openRegularThread();
214
+ const restart = vi.fn(async () => ({}));
215
+ hookManagerOf(core).getThreadRuntimeCore = () => undefined;
216
+ hookManagerOf(core).__internal_restartThreadRuntime = restart;
217
+
218
+ await core.reloadMainThread();
219
+
220
+ expect(restart).toHaveBeenCalledOnce();
221
+ });
222
+
223
+ it("lets a switch to a different thread win the notification after an in-place reload", async () => {
224
+ const core = await openRegularThread();
225
+
226
+ let releaseReload!: () => void;
227
+ hookManagerOf(core).getThreadRuntimeCore = () => ({
228
+ unstable_refetchThread: () =>
229
+ new Promise<void>((resolve) => {
230
+ releaseReload = resolve;
231
+ }),
232
+ capabilities: { cancel: false },
233
+ });
234
+
235
+ const reloadTask = core.reloadMainThread();
236
+ (core as unknown as { _mainThreadId: string })._mainThreadId = "elsewhere";
237
+
238
+ const callback = vi.fn();
239
+ core.subscribe(callback);
240
+ releaseReload();
241
+ await reloadTask;
242
+
243
+ expect(callback).not.toHaveBeenCalled();
244
+ });
245
+
246
+ it("propagates an in-place reload failure", async () => {
247
+ const core = await openRegularThread();
248
+ hookManagerOf(core).getThreadRuntimeCore = () => ({
249
+ unstable_refetchThread: async () => {
250
+ throw new Error("refetch failed");
251
+ },
252
+ capabilities: { cancel: false },
253
+ });
254
+
255
+ await expect(core.reloadMainThread()).rejects.toThrow("refetch failed");
256
+ });
257
+ });
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it, vi } from "vitest";
2
2
  import { auiV0Decode, auiV0Encode } from "../react/runtimes/cloud/auiV0";
3
3
 
4
4
  describe("auiV0Encode", () => {
@@ -127,6 +127,61 @@ describe("auiV0Encode", () => {
127
127
  ]);
128
128
  });
129
129
 
130
+ it("preserves file sourceType in the core cloud encoder", () => {
131
+ const encoded = auiV0Encode({
132
+ id: "m1",
133
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
134
+ role: "user",
135
+ metadata: { custom: {} },
136
+ content: [
137
+ {
138
+ type: "file",
139
+ data: "file-abc123",
140
+ mimeType: "application/pdf",
141
+ filename: "a.pdf",
142
+ sourceType: "id",
143
+ },
144
+ ],
145
+ attachments: [
146
+ {
147
+ id: "att-1",
148
+ type: "document",
149
+ name: "a.pdf",
150
+ contentType: "application/pdf",
151
+ status: { type: "complete" },
152
+ content: [
153
+ {
154
+ type: "file",
155
+ data: "file-abc123",
156
+ mimeType: "application/pdf",
157
+ filename: "a.pdf",
158
+ sourceType: "id",
159
+ },
160
+ ],
161
+ },
162
+ ],
163
+ });
164
+
165
+ expect(encoded.content).toEqual([
166
+ {
167
+ type: "file",
168
+ data: "file-abc123",
169
+ mimeType: "application/pdf",
170
+ filename: "a.pdf",
171
+ sourceType: "id",
172
+ },
173
+ ]);
174
+ expect(encoded.attachments?.[0]?.content).toEqual([
175
+ {
176
+ type: "file",
177
+ data: "file-abc123",
178
+ mimeType: "application/pdf",
179
+ filename: "a.pdf",
180
+ sourceType: "id",
181
+ },
182
+ ]);
183
+ });
184
+
130
185
  it("omits missing attachment contentType in the core cloud encoder", () => {
131
186
  const encoded = auiV0Encode({
132
187
  id: "m1",
@@ -155,6 +210,99 @@ describe("auiV0Encode", () => {
155
210
  },
156
211
  ]);
157
212
  });
213
+
214
+ it("drops per-part status from message parts in the core cloud encoder", () => {
215
+ const encoded = auiV0Encode({
216
+ id: "m1",
217
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
218
+ role: "assistant",
219
+ status: { type: "complete", reason: "stop" },
220
+ metadata: {
221
+ unstable_state: null,
222
+ unstable_annotations: [],
223
+ unstable_data: [],
224
+ steps: [],
225
+ custom: {},
226
+ },
227
+ content: [
228
+ { type: "reasoning", text: "thinking", status: { type: "complete" } },
229
+ { type: "text", text: "answer", status: { type: "running" } },
230
+ ],
231
+ });
232
+
233
+ expect(encoded.content).toEqual([
234
+ { type: "reasoning", text: "thinking" },
235
+ { type: "text", text: "answer" },
236
+ ]);
237
+ });
238
+
239
+ it("drops per-part status from attachment content in the core cloud encoder", () => {
240
+ const encoded = auiV0Encode({
241
+ id: "m1",
242
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
243
+ role: "user",
244
+ metadata: { custom: {} },
245
+ content: [{ type: "text", text: "please review this" }],
246
+ attachments: [
247
+ {
248
+ id: "att-1",
249
+ type: "document",
250
+ name: "notes.txt",
251
+ status: { type: "complete" },
252
+ content: [
253
+ { type: "text", text: "notes", status: { type: "running" } },
254
+ ],
255
+ },
256
+ ],
257
+ });
258
+
259
+ expect(encoded.attachments?.[0]?.content).toEqual([
260
+ { type: "text", text: "notes" },
261
+ ]);
262
+ });
263
+
264
+ it("preserves every attachment content field the wire shape carries in the core cloud encoder", () => {
265
+ const encoded = auiV0Encode({
266
+ id: "m1",
267
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
268
+ role: "user",
269
+ metadata: { custom: {} },
270
+ content: [{ type: "text", text: "please review these" }],
271
+ attachments: [
272
+ {
273
+ id: "att-1",
274
+ type: "file",
275
+ name: "bundle",
276
+ status: { type: "complete" },
277
+ content: [
278
+ {
279
+ type: "image",
280
+ image: "data:image/png;base64,iVBORw0KGgo=",
281
+ filename: "shot.png",
282
+ },
283
+ {
284
+ type: "audio",
285
+ audio: { data: "data:audio/mp3;base64,SUQzAw==", format: "mp3" },
286
+ },
287
+ { type: "data", name: "telemetry", data: { runs: 3 } },
288
+ ],
289
+ },
290
+ ],
291
+ });
292
+
293
+ expect(encoded.attachments?.[0]?.content).toEqual([
294
+ {
295
+ type: "image",
296
+ image: "data:image/png;base64,iVBORw0KGgo=",
297
+ filename: "shot.png",
298
+ },
299
+ {
300
+ type: "audio",
301
+ audio: { data: "data:audio/mp3;base64,SUQzAw==", format: "mp3" },
302
+ },
303
+ { type: "data", name: "telemetry", data: { runs: 3 } },
304
+ ]);
305
+ });
158
306
  });
159
307
 
160
308
  describe("auiV0Decode", () => {
@@ -259,4 +407,73 @@ describe("auiV0Decode", () => {
259
407
  },
260
408
  ]);
261
409
  });
410
+
411
+ const toolCallMessage = (
412
+ part: Record<string, unknown>,
413
+ ): Parameters<typeof auiV0Encode>[0] => ({
414
+ id: "m1",
415
+ createdAt: new Date("2026-03-15T00:00:00.000Z"),
416
+ role: "assistant",
417
+ status: { type: "complete", reason: "stop" },
418
+ metadata: {
419
+ unstable_state: null,
420
+ unstable_annotations: [],
421
+ unstable_data: [],
422
+ steps: [],
423
+ custom: {},
424
+ },
425
+ content: [
426
+ {
427
+ type: "tool-call",
428
+ toolCallId: "call-1",
429
+ toolName: "check_flag",
430
+ args: {},
431
+ argsText: "{}",
432
+ ...part,
433
+ },
434
+ ],
435
+ });
436
+
437
+ it.each([
438
+ ["false", false],
439
+ ["zero", 0],
440
+ ["an empty string", ""],
441
+ ["null", null],
442
+ ])("preserves %s as a tool-call result", (_label, result) => {
443
+ const encoded = auiV0Encode(toolCallMessage({ result }));
444
+
445
+ const toolCall = encoded.content.find((p) => p.type === "tool-call");
446
+ expect(toolCall).toHaveProperty("result", result);
447
+ });
448
+
449
+ it("omits the result of a tool call that has not settled", () => {
450
+ const encoded = auiV0Encode(toolCallMessage({}));
451
+
452
+ const toolCall = encoded.content.find((p) => p.type === "tool-call");
453
+ expect(toolCall).not.toHaveProperty("result");
454
+ });
455
+
456
+ it("does not warn about a tool call that has not settled", () => {
457
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
458
+ try {
459
+ auiV0Encode(toolCallMessage({}));
460
+ expect(warn).not.toHaveBeenCalled();
461
+ } finally {
462
+ warn.mockRestore();
463
+ }
464
+ });
465
+
466
+ it("carries a falsy tool-call result through a decode round trip", () => {
467
+ const encoded = auiV0Encode(toolCallMessage({ result: false }));
468
+ const { message } = auiV0Decode({
469
+ id: "m1",
470
+ parent_id: null,
471
+ format: "aui/v0",
472
+ content: encoded,
473
+ created_at: new Date("2026-03-15T00:00:00.000Z"),
474
+ } as unknown as Parameters<typeof auiV0Decode>[0]);
475
+
476
+ const toolCall = message.content.find((p) => p.type === "tool-call");
477
+ expect(toolCall).toHaveProperty("result", false);
478
+ });
262
479
  });