@assistant-ui/core 0.2.0 → 0.2.3

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 (201) hide show
  1. package/README.md +45 -0
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/model-context/tool.d.ts +25 -0
  7. package/dist/model-context/tool.d.ts.map +1 -1
  8. package/dist/model-context/tool.js +25 -0
  9. package/dist/model-context/tool.js.map +1 -1
  10. package/dist/react/AssistantRuntimeProvider.d.ts +33 -0
  11. package/dist/react/AssistantRuntimeProvider.d.ts.map +1 -1
  12. package/dist/react/AssistantRuntimeProvider.js +22 -0
  13. package/dist/react/AssistantRuntimeProvider.js.map +1 -1
  14. package/dist/react/client/DataRenderers.d.ts +7 -0
  15. package/dist/react/client/DataRenderers.d.ts.map +1 -1
  16. package/dist/react/client/DataRenderers.js +7 -0
  17. package/dist/react/client/DataRenderers.js.map +1 -1
  18. package/dist/react/client/Tools.d.ts +18 -1
  19. package/dist/react/client/Tools.d.ts.map +1 -1
  20. package/dist/react/client/Tools.js +24 -19
  21. package/dist/react/client/Tools.js.map +1 -1
  22. package/dist/react/index.d.ts +2 -1
  23. package/dist/react/index.d.ts.map +1 -1
  24. package/dist/react/index.js +1 -0
  25. package/dist/react/index.js.map +1 -1
  26. package/dist/react/model-context/makeAssistantDataUI.d.ts +13 -0
  27. package/dist/react/model-context/makeAssistantDataUI.d.ts.map +1 -1
  28. package/dist/react/model-context/makeAssistantDataUI.js +6 -0
  29. package/dist/react/model-context/makeAssistantDataUI.js.map +1 -1
  30. package/dist/react/model-context/makeAssistantTool.d.ts +15 -0
  31. package/dist/react/model-context/makeAssistantTool.d.ts.map +1 -1
  32. package/dist/react/model-context/makeAssistantTool.js +8 -0
  33. package/dist/react/model-context/makeAssistantTool.js.map +1 -1
  34. package/dist/react/model-context/makeAssistantToolUI.d.ts +15 -0
  35. package/dist/react/model-context/makeAssistantToolUI.d.ts.map +1 -1
  36. package/dist/react/model-context/makeAssistantToolUI.js +8 -0
  37. package/dist/react/model-context/makeAssistantToolUI.js.map +1 -1
  38. package/dist/react/model-context/toolbox.d.ts +29 -0
  39. package/dist/react/model-context/toolbox.d.ts.map +1 -1
  40. package/dist/react/model-context/useAssistantDataUI.d.ts +9 -0
  41. package/dist/react/model-context/useAssistantDataUI.d.ts.map +1 -1
  42. package/dist/react/model-context/useAssistantDataUI.js +6 -0
  43. package/dist/react/model-context/useAssistantDataUI.js.map +1 -1
  44. package/dist/react/model-context/useAssistantTool.d.ts +34 -0
  45. package/dist/react/model-context/useAssistantTool.d.ts.map +1 -1
  46. package/dist/react/model-context/useAssistantTool.js +30 -0
  47. package/dist/react/model-context/useAssistantTool.js.map +1 -1
  48. package/dist/react/model-context/useAssistantToolUI.d.ts +12 -0
  49. package/dist/react/model-context/useAssistantToolUI.d.ts.map +1 -1
  50. package/dist/react/model-context/useAssistantToolUI.js +9 -0
  51. package/dist/react/model-context/useAssistantToolUI.js.map +1 -1
  52. package/dist/react/model-context/useToolArgsStatus.d.ts +29 -0
  53. package/dist/react/model-context/useToolArgsStatus.d.ts.map +1 -1
  54. package/dist/react/model-context/useToolArgsStatus.js +24 -0
  55. package/dist/react/model-context/useToolArgsStatus.js.map +1 -1
  56. package/dist/react/primitive-hooks/useActionBarCopy.d.ts.map +1 -1
  57. package/dist/react/primitive-hooks/useActionBarCopy.js +4 -3
  58. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  59. package/dist/react/primitive-hooks/useComposerSend.d.ts.map +1 -1
  60. package/dist/react/primitive-hooks/useComposerSend.js +2 -3
  61. package/dist/react/primitive-hooks/useComposerSend.js.map +1 -1
  62. package/dist/react/primitives/message/MessageAttachments.js +1 -1
  63. package/dist/react/primitives/message/MessageAttachments.js.map +1 -1
  64. package/dist/react/primitives/message/MessageParts.d.ts.map +1 -1
  65. package/dist/react/primitives/message/MessageParts.js +14 -10
  66. package/dist/react/primitives/message/MessageParts.js.map +1 -1
  67. package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts +6 -0
  68. package/dist/react/primitives/messagePart/MessagePartInProgress.d.ts.map +1 -0
  69. package/dist/react/primitives/messagePart/MessagePartInProgress.js +7 -0
  70. package/dist/react/primitives/messagePart/MessagePartInProgress.js.map +1 -0
  71. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts +2 -0
  72. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  73. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +2 -0
  74. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  75. package/dist/react/runtimes/cloud/auiV0.d.ts +10 -1
  76. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  77. package/dist/react/runtimes/cloud/auiV0.js +21 -3
  78. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  79. package/dist/react/runtimes/useToolInvocations.d.ts +11 -1
  80. package/dist/react/runtimes/useToolInvocations.d.ts.map +1 -1
  81. package/dist/react/runtimes/useToolInvocations.js +325 -256
  82. package/dist/react/runtimes/useToolInvocations.js.map +1 -1
  83. package/dist/react/types/MessagePartComponentTypes.d.ts +11 -0
  84. package/dist/react/types/MessagePartComponentTypes.d.ts.map +1 -1
  85. package/dist/react/types/scopes/tools.d.ts +4 -0
  86. package/dist/react/types/scopes/tools.d.ts.map +1 -1
  87. package/dist/runtime/api/composer-runtime.d.ts +1 -0
  88. package/dist/runtime/api/composer-runtime.d.ts.map +1 -1
  89. package/dist/runtime/api/composer-runtime.js +2 -0
  90. package/dist/runtime/api/composer-runtime.js.map +1 -1
  91. package/dist/runtime/api/thread-runtime.d.ts +2 -0
  92. package/dist/runtime/api/thread-runtime.d.ts.map +1 -1
  93. package/dist/runtime/base/base-composer-runtime-core.d.ts +1 -0
  94. package/dist/runtime/base/base-composer-runtime-core.d.ts.map +1 -1
  95. package/dist/runtime/base/base-composer-runtime-core.js +1 -1
  96. package/dist/runtime/base/base-composer-runtime-core.js.map +1 -1
  97. package/dist/runtime/base/base-thread-runtime-core.d.ts +1 -0
  98. package/dist/runtime/base/base-thread-runtime-core.d.ts.map +1 -1
  99. package/dist/runtime/base/base-thread-runtime-core.js.map +1 -1
  100. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts +1 -0
  101. package/dist/runtime/base/default-edit-composer-runtime-core.d.ts.map +1 -1
  102. package/dist/runtime/base/default-edit-composer-runtime-core.js +3 -0
  103. package/dist/runtime/base/default-edit-composer-runtime-core.js.map +1 -1
  104. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts +1 -0
  105. package/dist/runtime/base/default-thread-composer-runtime-core.d.ts.map +1 -1
  106. package/dist/runtime/base/default-thread-composer-runtime-core.js +12 -1
  107. package/dist/runtime/base/default-thread-composer-runtime-core.js.map +1 -1
  108. package/dist/runtime/interfaces/composer-runtime-core.d.ts +1 -0
  109. package/dist/runtime/interfaces/composer-runtime-core.d.ts.map +1 -1
  110. package/dist/runtime/interfaces/thread-runtime-core.d.ts +6 -0
  111. package/dist/runtime/interfaces/thread-runtime-core.d.ts.map +1 -1
  112. package/dist/runtimes/external-store/external-store-adapter.d.ts +15 -0
  113. package/dist/runtimes/external-store/external-store-adapter.d.ts.map +1 -1
  114. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts +1 -1
  115. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.d.ts.map +1 -1
  116. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js +14 -12
  117. package/dist/runtimes/external-store/external-store-thread-list-runtime-core.js.map +1 -1
  118. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts +2 -0
  119. package/dist/runtimes/external-store/external-store-thread-runtime-core.d.ts.map +1 -1
  120. package/dist/runtimes/external-store/external-store-thread-runtime-core.js +13 -0
  121. package/dist/runtimes/external-store/external-store-thread-runtime-core.js.map +1 -1
  122. package/dist/runtimes/local/local-thread-runtime-core.d.ts +1 -0
  123. package/dist/runtimes/local/local-thread-runtime-core.d.ts.map +1 -1
  124. package/dist/runtimes/local/local-thread-runtime-core.js +1 -0
  125. package/dist/runtimes/local/local-thread-runtime-core.js.map +1 -1
  126. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts +2 -0
  127. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.d.ts.map +1 -1
  128. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js +2 -0
  129. package/dist/runtimes/readonly/ReadonlyThreadRuntimeCore.js.map +1 -1
  130. package/dist/runtimes/remote-thread-list/empty-thread-core.d.ts.map +1 -1
  131. package/dist/runtimes/remote-thread-list/empty-thread-core.js +2 -0
  132. package/dist/runtimes/remote-thread-list/empty-thread-core.js.map +1 -1
  133. package/dist/store/clients/model-context-client.d.ts.map +1 -1
  134. package/dist/store/clients/model-context-client.js +24 -4
  135. package/dist/store/clients/model-context-client.js.map +1 -1
  136. package/dist/store/clients/no-op-composer-client.d.ts.map +1 -1
  137. package/dist/store/clients/no-op-composer-client.js +1 -0
  138. package/dist/store/clients/no-op-composer-client.js.map +1 -1
  139. package/dist/store/runtime-clients/composer-runtime-client.d.ts.map +1 -1
  140. package/dist/store/runtime-clients/composer-runtime-client.js +1 -0
  141. package/dist/store/runtime-clients/composer-runtime-client.js.map +1 -1
  142. package/dist/store/scopes/composer.d.ts +9 -0
  143. package/dist/store/scopes/composer.d.ts.map +1 -1
  144. package/dist/store/scopes/model-context.d.ts +4 -1
  145. package/dist/store/scopes/model-context.d.ts.map +1 -1
  146. package/dist/types/index.d.ts +1 -1
  147. package/dist/types/index.d.ts.map +1 -1
  148. package/dist/types/message.d.ts +50 -1
  149. package/dist/types/message.d.ts.map +1 -1
  150. package/dist/types/message.js +2 -1
  151. package/dist/types/message.js.map +1 -1
  152. package/package.json +7 -7
  153. package/src/index.ts +6 -0
  154. package/src/model-context/tool.ts +25 -0
  155. package/src/react/AssistantRuntimeProvider.tsx +33 -0
  156. package/src/react/client/DataRenderers.ts +7 -0
  157. package/src/react/client/Tools.ts +56 -22
  158. package/src/react/index.ts +2 -1
  159. package/src/react/model-context/makeAssistantDataUI.ts +13 -0
  160. package/src/react/model-context/makeAssistantTool.ts +15 -0
  161. package/src/react/model-context/makeAssistantToolUI.ts +15 -0
  162. package/src/react/model-context/toolbox.ts +32 -1
  163. package/src/react/model-context/useAssistantDataUI.ts +9 -0
  164. package/src/react/model-context/useAssistantTool.ts +34 -0
  165. package/src/react/model-context/useAssistantToolUI.ts +12 -0
  166. package/src/react/model-context/useToolArgsStatus.ts +29 -0
  167. package/src/react/primitive-hooks/useActionBarCopy.ts +9 -5
  168. package/src/react/primitive-hooks/useComposerSend.ts +2 -3
  169. package/src/react/primitives/message/MessageAttachments.test.tsx +50 -0
  170. package/src/react/primitives/message/MessageAttachments.tsx +1 -1
  171. package/src/react/primitives/message/MessageParts.tsx +20 -9
  172. package/src/react/primitives/messagePart/MessagePartInProgress.ts +15 -0
  173. package/src/react/runtimes/cloud/auiV0.ts +37 -4
  174. package/src/react/runtimes/useToolInvocations.ts +422 -333
  175. package/src/react/types/MessagePartComponentTypes.ts +11 -0
  176. package/src/react/types/scopes/tools.ts +5 -0
  177. package/src/runtime/api/composer-runtime.ts +3 -0
  178. package/src/runtime/base/base-composer-runtime-core.ts +2 -1
  179. package/src/runtime/base/base-thread-runtime-core.ts +1 -0
  180. package/src/runtime/base/default-edit-composer-runtime-core.ts +4 -0
  181. package/src/runtime/base/default-thread-composer-runtime-core.ts +12 -1
  182. package/src/runtime/interfaces/composer-runtime-core.ts +1 -0
  183. package/src/runtime/interfaces/thread-runtime-core.ts +6 -0
  184. package/src/runtimes/external-store/external-store-adapter.ts +15 -0
  185. package/src/runtimes/external-store/external-store-thread-list-runtime-core.ts +15 -9
  186. package/src/runtimes/external-store/external-store-thread-runtime-core.ts +13 -0
  187. package/src/runtimes/local/local-thread-runtime-core.ts +1 -0
  188. package/src/runtimes/readonly/ReadonlyThreadRuntimeCore.ts +2 -0
  189. package/src/runtimes/remote-thread-list/empty-thread-core.ts +2 -0
  190. package/src/store/clients/model-context-client.test.ts +108 -0
  191. package/src/store/clients/model-context-client.ts +36 -6
  192. package/src/store/clients/no-op-composer-client.ts +1 -0
  193. package/src/store/runtime-clients/composer-runtime-client.ts +1 -0
  194. package/src/store/scopes/composer.ts +9 -0
  195. package/src/store/scopes/model-context.ts +4 -1
  196. package/src/tests/auiV0Encode.test.ts +55 -0
  197. package/src/tests/composer-can-send.test.ts +112 -0
  198. package/src/tests/external-store-thread-list-runtime-core.test.ts +34 -0
  199. package/src/tests/external-store-thread-runtime-core.test.ts +113 -0
  200. package/src/types/index.ts +2 -0
  201. package/src/types/message.ts +66 -7
@@ -0,0 +1,112 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { DefaultThreadComposerRuntimeCore } from "../runtime/base/default-thread-composer-runtime-core";
3
+ import { DefaultEditComposerRuntimeCore } from "../runtime/base/default-edit-composer-runtime-core";
4
+ import type { ThreadRuntimeCore } from "../runtime/interfaces/thread-runtime-core";
5
+ import type { ThreadMessage } from "../types/message";
6
+
7
+ type ThreadRuntimeStub = Omit<ThreadRuntimeCore, "composer"> & {
8
+ notify: () => void;
9
+ };
10
+
11
+ const makeRuntimeStub = (
12
+ overrides: Partial<ThreadRuntimeCore> = {},
13
+ ): ThreadRuntimeStub => {
14
+ const subscribers = new Set<() => void>();
15
+ const stub = {
16
+ append: vi.fn(),
17
+ cancelRun: vi.fn(),
18
+ subscribe: (cb: () => void) => {
19
+ subscribers.add(cb);
20
+ return () => subscribers.delete(cb);
21
+ },
22
+ capabilities: { cancel: false },
23
+ messages: [],
24
+ isDisabled: false,
25
+ isSendDisabled: false,
26
+ isLoading: false,
27
+ composer: { runConfig: {} },
28
+ notify: () => {
29
+ for (const cb of subscribers) cb();
30
+ },
31
+ ...overrides,
32
+ } as unknown as ThreadRuntimeStub;
33
+ return stub;
34
+ };
35
+
36
+ const makeUserMessage = (text = "old"): ThreadMessage =>
37
+ ({
38
+ id: "msg-1",
39
+ role: "user",
40
+ createdAt: new Date(),
41
+ content: [{ type: "text", text }],
42
+ attachments: [],
43
+ metadata: { custom: {} },
44
+ }) as ThreadMessage;
45
+
46
+ describe("DefaultThreadComposerRuntimeCore.canSend", () => {
47
+ it("is false when the composer is empty", () => {
48
+ const composer = new DefaultThreadComposerRuntimeCore(makeRuntimeStub());
49
+ expect(composer.canSend).toBe(false);
50
+ });
51
+
52
+ it("is true when in editing mode with non-empty text", () => {
53
+ const composer = new DefaultThreadComposerRuntimeCore(makeRuntimeStub());
54
+ composer.setText("hi");
55
+ expect(composer.canSend).toBe(true);
56
+ });
57
+
58
+ it("is false when the runtime reports isSendDisabled", () => {
59
+ const composer = new DefaultThreadComposerRuntimeCore(
60
+ makeRuntimeStub({ isSendDisabled: true }),
61
+ );
62
+ composer.setText("hi");
63
+ expect(composer.canSend).toBe(false);
64
+ });
65
+
66
+ it("notifies subscribers when isSendDisabled flips", () => {
67
+ const stub = makeRuntimeStub();
68
+ const composer = new DefaultThreadComposerRuntimeCore(stub);
69
+ composer.setText("hi");
70
+ const onChange = vi.fn();
71
+ composer.subscribe(onChange);
72
+
73
+ (stub as { isSendDisabled: boolean }).isSendDisabled = true;
74
+ stub.notify();
75
+ expect(onChange).toHaveBeenCalled();
76
+ expect(composer.canSend).toBe(false);
77
+ });
78
+ });
79
+
80
+ describe("BaseComposerRuntimeCore.send", () => {
81
+ it("is a no-op when canSend is false because of isSendDisabled", async () => {
82
+ const stub = makeRuntimeStub({ isSendDisabled: true });
83
+ const composer = new DefaultThreadComposerRuntimeCore(stub);
84
+ composer.setText("hi");
85
+
86
+ await composer.send();
87
+
88
+ expect(stub.append).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it("dispatches when canSend is true", async () => {
92
+ const stub = makeRuntimeStub();
93
+ const composer = new DefaultThreadComposerRuntimeCore(stub);
94
+ composer.setText("hi");
95
+
96
+ await composer.send();
97
+
98
+ expect(stub.append).toHaveBeenCalledTimes(1);
99
+ });
100
+ });
101
+
102
+ describe("DefaultEditComposerRuntimeCore.canSend", () => {
103
+ it("ignores runtime.isSendDisabled (thread-scoped flag does not block edits)", () => {
104
+ const stub = makeRuntimeStub({ isSendDisabled: true });
105
+ const composer = new DefaultEditComposerRuntimeCore(stub, () => {}, {
106
+ parentId: null,
107
+ message: makeUserMessage("seed"),
108
+ });
109
+
110
+ expect(composer.canSend).toBe(true);
111
+ });
112
+ });
@@ -146,6 +146,33 @@ describe("ExternalStoreThreadListRuntimeCore - __internal_setAdapter", () => {
146
146
  core.__internal_setAdapter(makeAdapter({ threadId: "thread-beta" }));
147
147
  expect(callback).toHaveBeenCalled();
148
148
  });
149
+
150
+ it("synthesizes mainThreadId entry after a switch to a threadId not in the threads list (regression: #3971)", () => {
151
+ const core = new ExternalStoreThreadListRuntimeCore(
152
+ makeAdapter({ threadId: "thread-alpha" }),
153
+ makeFactory(),
154
+ );
155
+ core.__internal_setAdapter(makeAdapter({ threadId: "thread-beta" }));
156
+ const item = core.getItemById("thread-beta");
157
+ expect(item).toBeDefined();
158
+ expect(item?.id).toBe("thread-beta");
159
+ });
160
+
161
+ it("does not retain stale synthesized entries across mainThreadId switches (regression: #3971)", () => {
162
+ const core = new ExternalStoreThreadListRuntimeCore(
163
+ makeAdapter({ threadId: "thread-alpha" }),
164
+ makeFactory(),
165
+ );
166
+ core.__internal_setAdapter(makeAdapter({ threadId: "thread-beta" }));
167
+ core.__internal_setAdapter(makeAdapter({ threadId: "thread-gamma" }));
168
+ expect(core.getItemById("thread-alpha")).toBeUndefined();
169
+ expect(core.getItemById("thread-beta")).toBeUndefined();
170
+ expect(core.getItemById("thread-gamma")).toBeDefined();
171
+ expect(Object.keys(core.threadItems).sort()).toEqual([
172
+ "DEFAULT_THREAD_ID",
173
+ "thread-gamma",
174
+ ]);
175
+ });
149
176
  });
150
177
 
151
178
  describe("ExternalStoreThreadListRuntimeCore - isMain via ThreadListRuntimeImpl", () => {
@@ -187,6 +214,13 @@ describe("ExternalStoreThreadListRuntimeCore - isMain via ThreadListRuntimeImpl"
187
214
  expect(state.mainThreadId).toBe("thread-alpha");
188
215
  expect(state.threadIds).toEqual(["thread-alpha", "thread-beta"]);
189
216
  });
217
+
218
+ it("does not throw when adapter.threadId has no matching threads entry (regression: #3971)", () => {
219
+ const impl = buildImpl({ threadId: "thread-alpha" });
220
+ expect(impl.mainItem.getState().id).toBe("thread-alpha");
221
+ expect(impl.mainItem.getState().isMain).toBe(true);
222
+ expect(impl.mainItem.getState().status).toBe("regular");
223
+ });
190
224
  });
191
225
 
192
226
  describe("ExternalStoreThreadListRuntimeCore - switchToThread", () => {
@@ -150,3 +150,116 @@ describe("ExternalStoreThreadRuntimeCore - state reference stability", () => {
150
150
  expect(runtime.capabilities).toBe(capsBefore);
151
151
  });
152
152
  });
153
+
154
+ describe("ExternalStoreThreadRuntimeCore - messages reconciliation", () => {
155
+ const user = { id: "u", role: "user" as const, content: [] };
156
+
157
+ it("drops ids that disappear between syncs (same length, swapped assistant id)", () => {
158
+ const a1 = { id: "a1", role: "assistant" as const, content: [] };
159
+ const a2 = { id: "a2", role: "assistant" as const, content: [] };
160
+
161
+ const runtime = new ExternalStoreThreadRuntimeCore(
162
+ mockContextProvider,
163
+ makeStore({ messages: [user, a1] }),
164
+ );
165
+
166
+ runtime.__internal_setAdapter(makeStore({ messages: [user, a2] }));
167
+
168
+ const exported = runtime.export();
169
+ expect(exported.messages.map((m) => m.message.id)).toEqual(["u", "a2"]);
170
+ const userChildren = exported.messages
171
+ .filter((m) => m.parentId === "u")
172
+ .map((m) => m.message.id);
173
+ expect(userChildren).toEqual(["a2"]);
174
+ });
175
+
176
+ it("keeps prior ids when they remain in the new sync", () => {
177
+ const a = { id: "a", role: "assistant" as const, content: [] };
178
+
179
+ const runtime = new ExternalStoreThreadRuntimeCore(
180
+ mockContextProvider,
181
+ makeStore({ messages: [user, a] }),
182
+ );
183
+
184
+ runtime.__internal_setAdapter(makeStore({ messages: [user, a] }));
185
+
186
+ expect(runtime.export().messages.map((m) => m.message.id)).toEqual([
187
+ "u",
188
+ "a",
189
+ ]);
190
+ });
191
+
192
+ it("removes trailing messages dropped from the new sync", () => {
193
+ const a = { id: "a", role: "assistant" as const, content: [] };
194
+ const u2 = { id: "u2", role: "user" as const, content: [] };
195
+
196
+ const runtime = new ExternalStoreThreadRuntimeCore(
197
+ mockContextProvider,
198
+ makeStore({ messages: [user, a, u2] }),
199
+ );
200
+
201
+ runtime.__internal_setAdapter(makeStore({ messages: [user, a] }));
202
+
203
+ expect(runtime.export().messages.map((m) => m.message.id)).toEqual([
204
+ "u",
205
+ "a",
206
+ ]);
207
+ });
208
+
209
+ it("does not crash on the next sync after cancelRun removes a leaf user", () => {
210
+ const userWithText = {
211
+ id: "u",
212
+ role: "user" as const,
213
+ content: [{ type: "text" as const, text: "hi" }],
214
+ };
215
+
216
+ const runtime = new ExternalStoreThreadRuntimeCore(
217
+ mockContextProvider,
218
+ makeStore({
219
+ messages: [userWithText],
220
+ onCancel: vi.fn(),
221
+ isRunning: true,
222
+ }),
223
+ );
224
+
225
+ runtime.cancelRun();
226
+
227
+ expect(() => {
228
+ runtime.__internal_setAdapter(makeStore({ messages: [] }));
229
+ }).not.toThrow();
230
+ });
231
+
232
+ it("drops phantom sibling when convertMessage swaps the assistant id", () => {
233
+ type Raw = { id: string; role: "user" | "assistant"; text: string };
234
+ const rawU: Raw = { id: "u", role: "user", text: "hi" };
235
+ const rawA1: Raw = { id: "client_id", role: "assistant", text: "" };
236
+ const rawA2: Raw = { id: "server_id", role: "assistant", text: "" };
237
+
238
+ const convertMessage = (m: Raw) => ({
239
+ id: m.id,
240
+ role: m.role,
241
+ content: [{ type: "text" as const, text: m.text }],
242
+ });
243
+
244
+ const runtime = new ExternalStoreThreadRuntimeCore(
245
+ mockContextProvider,
246
+ makeStore({
247
+ messages: [rawU, rawA1] as any,
248
+ convertMessage: convertMessage as any,
249
+ }),
250
+ );
251
+
252
+ runtime.__internal_setAdapter(
253
+ makeStore({
254
+ messages: [rawU, rawA2] as any,
255
+ convertMessage: convertMessage as any,
256
+ }),
257
+ );
258
+
259
+ const userChildren = runtime
260
+ .export()
261
+ .messages.filter((m) => m.parentId === "u")
262
+ .map((m) => m.message.id);
263
+ expect(userChildren).toEqual(["server_id"]);
264
+ });
265
+ });
@@ -2,12 +2,14 @@ export type {
2
2
  // Message parts
3
3
  TextMessagePart,
4
4
  ReasoningMessagePart,
5
+ SourceProviderMetadata,
5
6
  SourceMessagePart,
6
7
  ImageMessagePart,
7
8
  FileMessagePart,
8
9
  DataMessagePart,
9
10
  Unstable_AudioMessagePart,
10
11
  ToolCallMessagePart,
12
+ ToolModelContentPart,
11
13
  ThreadUserMessagePart,
12
14
  ThreadAssistantMessagePart,
13
15
  // Message status
@@ -2,8 +2,11 @@ import type {
2
2
  ReadonlyJSONObject,
3
3
  ReadonlyJSONValue,
4
4
  } from "assistant-stream/utils";
5
+ import type { ToolModelContentPart } from "assistant-stream";
5
6
  import type { CompleteAttachment } from "./attachment";
6
7
 
8
+ export type { ToolModelContentPart };
9
+
7
10
  export type TextMessagePart = {
8
11
  readonly type: "text";
9
12
  readonly text: string;
@@ -16,15 +19,32 @@ export type ReasoningMessagePart = {
16
19
  readonly parentId?: string;
17
20
  };
18
21
 
19
- export type SourceMessagePart = {
20
- readonly type: "source";
21
- readonly sourceType: "url";
22
- readonly id: string;
23
- readonly url: string;
24
- readonly title?: string;
25
- readonly parentId?: string;
22
+ export type SourceProviderMetadata = {
23
+ readonly [providerName: string]: ReadonlyJSONObject;
26
24
  };
27
25
 
26
+ export type SourceMessagePart =
27
+ | {
28
+ readonly type: "source";
29
+ readonly sourceType: "url";
30
+ readonly id: string;
31
+ readonly url: string;
32
+ readonly title?: string;
33
+ readonly providerMetadata?: SourceProviderMetadata;
34
+ readonly parentId?: string;
35
+ }
36
+ | {
37
+ readonly type: "source";
38
+ readonly sourceType: "document";
39
+ readonly id: string;
40
+ readonly url?: undefined;
41
+ readonly title: string;
42
+ readonly mediaType: string;
43
+ readonly filename?: string;
44
+ readonly providerMetadata?: SourceProviderMetadata;
45
+ readonly parentId?: string;
46
+ };
47
+
28
48
  export type ImageMessagePart = {
29
49
  readonly type: "image";
30
50
  readonly image: string;
@@ -53,20 +73,57 @@ export type DataMessagePart<T = any> = {
53
73
  readonly data: T;
54
74
  };
55
75
 
76
+ export type McpAppMetadata = {
77
+ readonly resourceUri: string;
78
+ readonly mimeType?: string;
79
+ readonly visibility?: readonly ("model" | "app")[];
80
+ };
81
+
82
+ export const MCP_APP_URI_SCHEME = "ui://";
83
+
84
+ export const isMcpAppUri = (uri: string | undefined): boolean =>
85
+ !!uri?.startsWith(MCP_APP_URI_SCHEME);
86
+
87
+ export type ToolCallMessagePartMcpMetadata = {
88
+ readonly app?: McpAppMetadata;
89
+ };
90
+
56
91
  export type ToolCallMessagePart<
57
92
  TArgs = ReadonlyJSONObject,
58
93
  TResult = unknown,
59
94
  > = {
95
+ /** Identifies this part as a tool call. */
60
96
  readonly type: "tool-call";
97
+ /** Stable identifier for this invocation of the tool. */
61
98
  readonly toolCallId: string;
99
+ /** Name of the tool requested by the model. */
62
100
  readonly toolName: string;
101
+ /**
102
+ * Arguments supplied by the model. During streaming this is a partial parse:
103
+ * fields may be missing or incomplete. From a tool-call renderer, use
104
+ * `useToolArgsStatus` to detect which fields are still arriving.
105
+ */
63
106
  readonly args: TArgs;
107
+ /** Result returned by the tool, if it has completed. */
64
108
  readonly result?: TResult | undefined;
109
+ /** Whether the result represents a tool execution error. */
65
110
  readonly isError?: boolean | undefined;
111
+ /** Raw JSON argument text streamed by the model. */
66
112
  readonly argsText: string;
113
+ /** UI-only artifact associated with the tool result. */
67
114
  readonly artifact?: unknown;
115
+ /** MCP app metadata associated with this tool call, when present. */
116
+ readonly mcp?: ToolCallMessagePartMcpMetadata;
117
+ /** Content returned to the model for this tool result. */
118
+ readonly modelContent?: readonly ToolModelContentPart[] | undefined;
119
+ /** Human-input request that must be resolved before the run can continue. */
68
120
  readonly interrupt?: { type: "human"; payload: unknown };
121
+ /** Parent message-part ID when this part belongs to a nested structure. */
69
122
  readonly parentId?: string;
123
+ /**
124
+ * Nested thread messages produced by this tool call, for example a sub-agent
125
+ * conversation.
126
+ */
70
127
  readonly messages?: readonly ThreadMessage[];
71
128
  };
72
129
 
@@ -106,7 +163,9 @@ export type MessagePartStatus =
106
163
 
107
164
  export type ToolCallMessagePartStatus =
108
165
  | {
166
+ /** The tool call is waiting for UI or human input before continuing. */
109
167
  readonly type: "requires-action";
168
+ /** Reason the tool call requires action. */
110
169
  readonly reason: "interrupt";
111
170
  }
112
171
  | MessagePartStatus;