@assistant-ui/react 0.14.29 → 0.15.0

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 (138) hide show
  1. package/dist/client/ExternalThread.js +4 -3
  2. package/dist/client/ExternalThread.js.map +1 -1
  3. package/dist/client/InMemoryThreadList.js +3 -3
  4. package/dist/client/InMemoryThreadList.js.map +1 -1
  5. package/dist/context/index.d.ts +1 -8
  6. package/dist/context/index.js +1 -8
  7. package/dist/index.d.ts +1 -8
  8. package/dist/index.js +1 -8
  9. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +3 -3
  10. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  11. package/dist/mcp-apps/McpAppRenderer.js +1 -1
  12. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  13. package/dist/model-context/makeAssistantVisible.js +26 -25
  14. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  15. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
  16. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  17. package/dist/primitives/attachment/AttachmentRemove.js +3 -3
  18. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  19. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +3 -3
  20. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  21. package/dist/primitives/composer/ComposerAddAttachment.js +3 -3
  22. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  23. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  24. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  25. package/dist/primitives/composer/ComposerInput.js +13 -13
  26. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  27. package/dist/primitives/composer/ComposerQuote.js +1 -1
  28. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  29. package/dist/primitives/composer/ComposerStopDictation.js +3 -3
  30. package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
  31. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  32. package/dist/primitives/composer/trigger/triggerSelectionResource.js +3 -3
  33. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  34. package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
  35. package/dist/primitives/message/MessagePartsGrouped.js +67 -89
  36. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  37. package/dist/primitives/message/MessageRoot.js +3 -3
  38. package/dist/primitives/message/MessageRoot.js.map +1 -1
  39. package/dist/primitives/queueItem/QueueItemRemove.js +3 -3
  40. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  41. package/dist/primitives/queueItem/QueueItemSteer.js +3 -3
  42. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  43. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  44. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  45. package/dist/primitives/suggestion/SuggestionTrigger.js +16 -15
  46. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  47. package/dist/unstable/useComposerInput.js +2 -2
  48. package/dist/unstable/useComposerInput.js.map +1 -1
  49. package/dist/unstable/useComposerInputHistory.js +6 -6
  50. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  51. package/dist/unstable/useMentionAdapter.js +1 -1
  52. package/dist/unstable/useMentionAdapter.js.map +1 -1
  53. package/dist/utils/smooth/SmoothContext.js +3 -3
  54. package/dist/utils/smooth/SmoothContext.js.map +1 -1
  55. package/dist/utils/smooth/useSmooth.js +1 -1
  56. package/dist/utils/smooth/useSmooth.js.map +1 -1
  57. package/package.json +5 -5
  58. package/src/client/ExternalThread.ts +5 -3
  59. package/src/client/InMemoryThreadList.ts +3 -3
  60. package/src/context/index.ts +0 -44
  61. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +5 -9
  62. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +1 -1
  63. package/src/mcp-apps/McpAppRenderer.tsx +1 -1
  64. package/src/model-context/makeAssistantVisible.test.tsx +1 -1
  65. package/src/model-context/makeAssistantVisible.tsx +1 -1
  66. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
  67. package/src/primitives/attachment/AttachmentRemove.ts +1 -1
  68. package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.ts +1 -1
  69. package/src/primitives/composer/ComposerAddAttachment.test.tsx +2 -2
  70. package/src/primitives/composer/ComposerAddAttachment.ts +1 -1
  71. package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +2 -2
  72. package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
  73. package/src/primitives/composer/ComposerInput.test.tsx +7 -7
  74. package/src/primitives/composer/ComposerInput.tsx +13 -13
  75. package/src/primitives/composer/ComposerQuote.tsx +1 -1
  76. package/src/primitives/composer/ComposerStopDictation.ts +1 -1
  77. package/src/primitives/composer/trigger/triggerSelectionResource.ts +7 -9
  78. package/src/primitives/message/MessagePartsGrouped.tsx +6 -8
  79. package/src/primitives/message/MessageRoot.tsx +1 -1
  80. package/src/primitives/queueItem/QueueItemRemove.ts +1 -1
  81. package/src/primitives/queueItem/QueueItemSteer.ts +1 -1
  82. package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
  83. package/src/primitives/suggestion/SuggestionTrigger.ts +9 -9
  84. package/src/tests/DataRenderers.test.tsx +1 -1
  85. package/src/tests/RemoteThreadListRuntime.deferredProvider.test.tsx +3 -3
  86. package/src/tests/external-thread-attachments.test.tsx +21 -23
  87. package/src/tests/external-thread-branches.test.tsx +10 -10
  88. package/src/tests/external-thread-parity.test.tsx +18 -20
  89. package/src/tests/local-runtime-queue.test.tsx +29 -34
  90. package/src/tests/messagePartSwitchRace.test.tsx +30 -13
  91. package/src/unstable/useComposerInput.test.tsx +2 -2
  92. package/src/unstable/useComposerInput.ts +2 -2
  93. package/src/unstable/useComposerInputHistory.test.tsx +3 -3
  94. package/src/unstable/useComposerInputHistory.ts +6 -6
  95. package/src/unstable/useMentionAdapter.ts +1 -1
  96. package/src/utils/smooth/SmoothContext.tsx +1 -1
  97. package/src/utils/smooth/useSmooth.test.tsx +1 -1
  98. package/src/utils/smooth/useSmooth.ts +1 -1
  99. package/dist/context/react/utils/createStateHookForRuntime.d.ts +0 -34
  100. package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +0 -1
  101. package/dist/context/react/utils/createStateHookForRuntime.js +0 -21
  102. package/dist/context/react/utils/createStateHookForRuntime.js.map +0 -1
  103. package/dist/legacy-runtime/hooks/AssistantContext.d.ts +0 -74
  104. package/dist/legacy-runtime/hooks/AssistantContext.d.ts.map +0 -1
  105. package/dist/legacy-runtime/hooks/AssistantContext.js +0 -29
  106. package/dist/legacy-runtime/hooks/AssistantContext.js.map +0 -1
  107. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts +0 -1007
  108. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts.map +0 -1
  109. package/dist/legacy-runtime/hooks/AttachmentContext.js +0 -47
  110. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +0 -1
  111. package/dist/legacy-runtime/hooks/ComposerContext.d.ts +0 -141
  112. package/dist/legacy-runtime/hooks/ComposerContext.d.ts.map +0 -1
  113. package/dist/legacy-runtime/hooks/ComposerContext.js +0 -65
  114. package/dist/legacy-runtime/hooks/ComposerContext.js.map +0 -1
  115. package/dist/legacy-runtime/hooks/MessageContext.d.ts +0 -155
  116. package/dist/legacy-runtime/hooks/MessageContext.d.ts.map +0 -1
  117. package/dist/legacy-runtime/hooks/MessageContext.js +0 -68
  118. package/dist/legacy-runtime/hooks/MessageContext.js.map +0 -1
  119. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts +0 -44
  120. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts.map +0 -1
  121. package/dist/legacy-runtime/hooks/MessagePartContext.js +0 -26
  122. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +0 -1
  123. package/dist/legacy-runtime/hooks/ThreadContext.d.ts +0 -139
  124. package/dist/legacy-runtime/hooks/ThreadContext.d.ts.map +0 -1
  125. package/dist/legacy-runtime/hooks/ThreadContext.js +0 -84
  126. package/dist/legacy-runtime/hooks/ThreadContext.js.map +0 -1
  127. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts +0 -44
  128. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts.map +0 -1
  129. package/dist/legacy-runtime/hooks/ThreadListItemContext.js +0 -26
  130. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +0 -1
  131. package/src/context/react/utils/createStateHookForRuntime.ts +0 -82
  132. package/src/legacy-runtime/hooks/AssistantContext.ts +0 -67
  133. package/src/legacy-runtime/hooks/AttachmentContext.ts +0 -99
  134. package/src/legacy-runtime/hooks/ComposerContext.ts +0 -131
  135. package/src/legacy-runtime/hooks/MessageContext.ts +0 -127
  136. package/src/legacy-runtime/hooks/MessagePartContext.ts +0 -32
  137. package/src/legacy-runtime/hooks/ThreadContext.ts +0 -120
  138. package/src/legacy-runtime/hooks/ThreadListItemContext.ts +0 -36
@@ -72,7 +72,7 @@ const useTriggerSelectionResource = ({
72
72
  return;
73
73
  }
74
74
 
75
- const currentText = aui.composer().getState().text;
75
+ const currentText = aui.composer.getState().text;
76
76
  const before = currentText.slice(0, trigger.offset);
77
77
  const after = currentText.slice(
78
78
  trigger.offset + triggerChar.length + trigger.query.length,
@@ -80,11 +80,9 @@ const useTriggerSelectionResource = ({
80
80
 
81
81
  const insertDirective = () => {
82
82
  const directive = behavior.formatter.serialize(item);
83
- aui
84
- .composer()
85
- .setText(
86
- before + directive + (after.startsWith(" ") ? after : ` ${after}`),
87
- );
83
+ aui.composer.setText(
84
+ before + directive + (after.startsWith(" ") ? after : ` ${after}`),
85
+ );
88
86
  };
89
87
 
90
88
  if (behavior.kind === "directive") {
@@ -92,9 +90,9 @@ const useTriggerSelectionResource = ({
92
90
  behavior.onInserted?.(item);
93
91
  } else {
94
92
  if (behavior.removeOnExecute) {
95
- aui
96
- .composer()
97
- .setText(before + (after.startsWith(" ") ? after.slice(1) : after));
93
+ aui.composer.setText(
94
+ before + (after.startsWith(" ") ? after.slice(1) : after),
95
+ );
98
96
  } else {
99
97
  // Leave directive chip in the composer as an audit trail
100
98
  insertDirective();
@@ -230,11 +230,9 @@ const ToolUIDisplay = ({
230
230
  }: {
231
231
  Fallback: ToolCallMessagePartComponent | undefined;
232
232
  } & ToolCallMessagePartProps) => {
233
- const Render = useAuiState((s) => {
234
- const Render = s.tools.tools[props.toolName] ?? Fallback;
235
- if (Array.isArray(Render)) return Render[0] ?? Fallback;
236
- return Render;
237
- });
233
+ const Render = useAuiState(
234
+ (s) => s.tools.toolUIs[props.toolName]?.[0]?.render ?? Fallback,
235
+ );
238
236
  if (!Render) return null;
239
237
  return <Render {...props} />;
240
238
  };
@@ -292,9 +290,9 @@ const MessagePartComponent: FC<MessagePartComponentProps> = ({
292
290
 
293
291
  const type = part.type;
294
292
  if (type === "tool-call") {
295
- const addResult = aui.part().addToolResult;
296
- const resume = aui.part().resumeToolCall;
297
- const respondToApproval = aui.part().respondToToolApproval;
293
+ const addResult = aui.part.addToolResult;
294
+ const resume = aui.part.resumeToolCall;
295
+ const respondToApproval = aui.part.respondToToolApproval;
298
296
  if ("Override" in tools)
299
297
  return (
300
298
  <tools.Override
@@ -23,7 +23,7 @@ type ThreadViewportStore = NonNullable<
23
23
 
24
24
  const useIsHoveringRef = () => {
25
25
  const aui = useAui();
26
- const message = useAuiState(() => aui.message());
26
+ const message = useAuiState(() => aui.message);
27
27
 
28
28
  const callbackRef = useCallback(
29
29
  (el: HTMLElement) => {
@@ -12,7 +12,7 @@ const useQueueItemRemove = () => {
12
12
  const aui = useAui();
13
13
 
14
14
  const callback = useCallback(() => {
15
- aui.queueItem().remove();
15
+ aui.queueItem.remove();
16
16
  }, [aui]);
17
17
 
18
18
  return callback;
@@ -12,7 +12,7 @@ const useQueueItemSteer = () => {
12
12
  const aui = useAui();
13
13
 
14
14
  const callback = useCallback(() => {
15
- aui.queueItem().steer();
15
+ aui.queueItem.steer();
16
16
  }, [aui]);
17
17
 
18
18
  return callback;
@@ -40,7 +40,7 @@ export const SelectionToolbarPrimitiveQuote = forwardRef<
40
40
  const handleClick = useCallback(() => {
41
41
  if (!info) return;
42
42
 
43
- aui.thread().composer().setQuote({
43
+ aui.thread.composer().setQuote({
44
44
  text: info.text,
45
45
  messageId: info.messageId,
46
46
  });
@@ -34,24 +34,24 @@ const useSuggestionTrigger = ({
34
34
  const resolvedSend = send ?? false;
35
35
 
36
36
  const callback = useCallback(() => {
37
- const isRunning = aui.thread().getState().isRunning;
37
+ const isRunning = aui.thread.getState().isRunning;
38
38
 
39
39
  if (resolvedSend && !isRunning) {
40
- aui.thread().append({
40
+ aui.thread.append({
41
41
  content: [{ type: "text", text: prompt }],
42
- runConfig: aui.composer().getState().runConfig,
42
+ runConfig: aui.composer.getState().runConfig,
43
43
  });
44
44
  if (clearComposer) {
45
- aui.composer().setText("");
45
+ aui.composer.setText("");
46
46
  }
47
47
  } else {
48
48
  if (clearComposer) {
49
- aui.composer().setText(prompt);
49
+ aui.composer.setText(prompt);
50
50
  } else {
51
- const currentText = aui.composer().getState().text;
52
- aui
53
- .composer()
54
- .setText(currentText.trim() ? `${currentText} ${prompt}` : prompt);
51
+ const currentText = aui.composer.getState().text;
52
+ aui.composer.setText(
53
+ currentText.trim() ? `${currentText} ${prompt}` : prompt,
54
+ );
55
55
  }
56
56
  }
57
57
  }, [aui, resolvedSend, clearComposer, prompt]);
@@ -66,7 +66,7 @@ const GlobalFallbackDataUI: FC<{ render: DataMessagePartComponent }> = ({
66
66
  render,
67
67
  }) => {
68
68
  const aui = useAui();
69
- useEffect(() => aui.dataRenderers().setFallbackDataUI(render), [aui, render]);
69
+ useEffect(() => aui.dataRenderers.setFallbackDataUI(render), [aui, render]);
70
70
  return null;
71
71
  };
72
72
 
@@ -6,9 +6,9 @@ import { afterEach, describe, expect, it, vi } from "vitest";
6
6
  import { useRemoteThreadListRuntime } from "@assistant-ui/core/react";
7
7
  import { makeAdapter } from "./remote-thread-list-test-helpers";
8
8
  import type { AssistantRuntime } from "@assistant-ui/core";
9
+ import { useAui } from "@assistant-ui/store";
9
10
  import { AssistantRuntimeProvider } from "../context";
10
11
  import { useLocalRuntime } from "../legacy-runtime/runtime-cores/local/useLocalRuntime";
11
- import { useAssistantRuntime } from "../legacy-runtime/hooks/AssistantContext";
12
12
  import type { ChatModelAdapter, RemoteThreadListAdapter } from "../index";
13
13
 
14
14
  const noOpAdapter: ChatModelAdapter = {
@@ -34,8 +34,8 @@ const RuntimeProvider: FC<
34
34
  const RuntimeCapture: FC<{
35
35
  runtimeRef: { current: AssistantRuntime | null };
36
36
  }> = ({ runtimeRef }) => {
37
- const runtime = useAssistantRuntime();
38
- runtimeRef.current = runtime;
37
+ const aui = useAui();
38
+ runtimeRef.current = aui.threads.__internal_getAssistantRuntime?.() ?? null;
39
39
  return null;
40
40
  };
41
41
 
@@ -74,14 +74,12 @@ describe("ExternalThread attachments", () => {
74
74
  }
75
75
 
76
76
  await waitFor(() => {
77
- expect(aui().thread().composer().getState().attachments[0]).toMatchObject(
78
- {
79
- type: "document",
80
- name: "notes.txt",
81
- contentType: "text/plain",
82
- content: [{ type: "text", text: "hello" }],
83
- },
84
- );
77
+ expect(aui().thread.composer().getState().attachments[0]).toMatchObject({
78
+ type: "document",
79
+ name: "notes.txt",
80
+ contentType: "text/plain",
81
+ content: [{ type: "text", text: "hello" }],
82
+ });
85
83
  });
86
84
  });
87
85
 
@@ -94,9 +92,9 @@ describe("ExternalThread attachments", () => {
94
92
  content: [{ type: "text", text: "implementation detail" }],
95
93
  } as File;
96
94
 
97
- await act(() => aui().thread().composer().addAttachment(foreignFile));
95
+ await act(() => aui().thread.composer().addAttachment(foreignFile));
98
96
 
99
- expect(aui().thread().composer().getState().attachments[0]).toMatchObject({
97
+ expect(aui().thread.composer().getState().attachments[0]).toMatchObject({
100
98
  type: "file",
101
99
  name: "photo.png",
102
100
  contentType: "image/png",
@@ -126,7 +124,7 @@ describe("ExternalThread attachments", () => {
126
124
  "File type application/zip is not accepted. Accepted types: image/*",
127
125
  );
128
126
  expect(add).not.toHaveBeenCalled();
129
- expect(aui().thread().composer().getState().attachments).toHaveLength(0);
127
+ expect(aui().thread.composer().getState().attachments).toHaveLength(0);
130
128
  });
131
129
 
132
130
  it("rejects prepared attachments that do not match the adapter accept string", async () => {
@@ -151,7 +149,7 @@ describe("ExternalThread attachments", () => {
151
149
  ).rejects.toThrow(
152
150
  "File type text/plain is not accepted. Accepted types: .pdf",
153
151
  );
154
- expect(aui().thread().composer().getState().attachments).toHaveLength(0);
152
+ expect(aui().thread.composer().getState().attachments).toHaveLength(0);
155
153
  });
156
154
 
157
155
  it("restores the draft when an attachment upload fails on send", async () => {
@@ -192,21 +190,21 @@ describe("ExternalThread attachments", () => {
192
190
  .addAttachment(new File(["data"], "notes.txt", { type: "text/plain" })),
193
191
  );
194
192
  await act(async () => {
195
- aui().thread().composer().setText("hello");
193
+ aui().thread.composer().setText("hello");
196
194
  });
197
195
  await act(async () => {
198
- aui().thread().composer().send();
196
+ aui().thread.composer().send();
199
197
  });
200
198
 
201
199
  // The draft is cleared optimistically while the upload runs.
202
- expect(aui().thread().composer().getState().text).toBe("");
200
+ expect(aui().thread.composer().getState().text).toBe("");
203
201
 
204
202
  await act(async () => {
205
203
  rejectSend(new Error("upload failed"));
206
204
  });
207
205
 
208
206
  await waitFor(() => {
209
- const state = aui().thread().composer().getState();
207
+ const state = aui().thread.composer().getState();
210
208
  expect(state.text).toBe("hello");
211
209
  expect(state.attachments).toHaveLength(1);
212
210
  });
@@ -255,13 +253,13 @@ describe("ExternalThread attachments", () => {
255
253
  ),
256
254
  );
257
255
 
258
- await act(() => invoke(aui().thread().composer()));
256
+ await act(() => invoke(aui().thread.composer()));
259
257
 
260
258
  expect(remove).toHaveBeenCalledTimes(1);
261
259
  expect(remove).toHaveBeenCalledWith(
262
260
  expect.objectContaining({ id: "att-1" }),
263
261
  );
264
- expect(aui().thread().composer().getState().attachments).toHaveLength(0);
262
+ expect(aui().thread.composer().getState().attachments).toHaveLength(0);
265
263
  },
266
264
  );
267
265
 
@@ -297,7 +295,7 @@ describe("ExternalThread attachments", () => {
297
295
  },
298
296
  });
299
297
 
300
- const composer = () => aui().thread().message({ id: "u1" }).composer();
298
+ const composer = () => aui().thread.message({ id: "u1" }).composer();
301
299
  await act(async () => {
302
300
  composer().beginEdit();
303
301
  });
@@ -349,18 +347,18 @@ describe("ExternalThread attachments", () => {
349
347
  .addAttachment(new File(["data"], "notes.txt", { type: "text/plain" })),
350
348
  );
351
349
  await act(async () => {
352
- aui().thread().composer().setText("hello");
353
- aui().thread().composer().send();
350
+ aui().thread.composer().setText("hello");
351
+ aui().thread.composer().send();
354
352
  });
355
353
  await act(async () => {
356
- aui().thread().composer().setText("meanwhile");
354
+ aui().thread.composer().setText("meanwhile");
357
355
  });
358
356
 
359
357
  await act(async () => {
360
358
  rejectSend(new Error("upload failed"));
361
359
  });
362
360
 
363
- const state = aui().thread().composer().getState();
361
+ const state = aui().thread.composer().getState();
364
362
  expect(state.text).toBe("hello\nmeanwhile");
365
363
  expect(state.attachments).toHaveLength(1);
366
364
  expect(state.attachments[0]).toMatchObject({ id: "att-1" });
@@ -66,18 +66,18 @@ const adapterFor = (
66
66
  describe("ExternalThread branches", () => {
67
67
  it("defaults to single-branch state without an adapter", () => {
68
68
  const { aui } = renderThread(baseProps());
69
- const state = aui().thread().getState();
69
+ const state = aui().thread.getState();
70
70
  expect(state.messages[1]!.branchNumber).toBe(1);
71
71
  expect(state.messages[1]!.branchCount).toBe(1);
72
72
  expect(state.capabilities.switchToBranch).toBe(false);
73
73
  expect(() =>
74
- aui().thread().message({ index: 1 }).switchToBranch({ position: "next" }),
74
+ aui().thread.message({ index: 1 }).switchToBranch({ position: "next" }),
75
75
  ).not.toThrow();
76
76
  });
77
77
 
78
78
  it("derives branchNumber and branchCount from the adapter", () => {
79
79
  const { aui } = renderThread(baseProps(adapterFor(["a1", "a2", "a3"])));
80
- const state = aui().thread().getState();
80
+ const state = aui().thread.getState();
81
81
  expect(state.messages[1]!.branchNumber).toBe(2);
82
82
  expect(state.messages[1]!.branchCount).toBe(3);
83
83
  expect(state.capabilities.switchToBranch).toBe(true);
@@ -88,7 +88,7 @@ describe("ExternalThread branches", () => {
88
88
  const { aui } = renderThread(
89
89
  baseProps(adapterFor(["a1", "a2", "a3"], switchToBranch)),
90
90
  );
91
- const msg = () => aui().thread().message({ index: 1 });
91
+ const msg = () => aui().thread.message({ index: 1 });
92
92
 
93
93
  msg().switchToBranch({ position: "previous" });
94
94
  expect(switchToBranch).toHaveBeenLastCalledWith("a1");
@@ -101,7 +101,7 @@ describe("ExternalThread branches", () => {
101
101
  isRunning: false,
102
102
  branches: adapterFor(["a1", "a2"], switchToBranch),
103
103
  });
104
- edge.aui().thread().message({ index: 1 }).switchToBranch({
104
+ edge.aui().thread.message({ index: 1 }).switchToBranch({
105
105
  position: "previous",
106
106
  });
107
107
  expect(switchToBranch).not.toHaveBeenCalled();
@@ -112,7 +112,7 @@ describe("ExternalThread branches", () => {
112
112
  const { aui } = renderThread(
113
113
  baseProps(adapterFor(["a1", "a2"], switchToBranch)),
114
114
  );
115
- const msg = () => aui().thread().message({ index: 1 });
115
+ const msg = () => aui().thread.message({ index: 1 });
116
116
 
117
117
  msg().switchToBranch({ branchId: "not-in-the-list" });
118
118
  expect(switchToBranch).toHaveBeenLastCalledWith("not-in-the-list");
@@ -130,11 +130,11 @@ describe("ExternalThread branches", () => {
130
130
  switchToBranch,
131
131
  }),
132
132
  );
133
- const state = aui().thread().getState();
133
+ const state = aui().thread.getState();
134
134
  expect(state.messages[1]!.branchNumber).toBe(1);
135
135
  expect(state.messages[1]!.branchCount).toBe(1);
136
136
 
137
- aui().thread().message({ index: 1 }).switchToBranch({ position: "next" });
137
+ aui().thread.message({ index: 1 }).switchToBranch({ position: "next" });
138
138
  expect(switchToBranch).not.toHaveBeenCalled();
139
139
  });
140
140
 
@@ -145,14 +145,14 @@ describe("ExternalThread branches", () => {
145
145
  isRunning: false,
146
146
  branches: adapterFor(["a1", "a2", "a3"]),
147
147
  });
148
- const before = aui().thread().getState().messages[1];
148
+ const before = aui().thread.getState().messages[1];
149
149
 
150
150
  rerender({
151
151
  messages,
152
152
  isRunning: false,
153
153
  branches: adapterFor(["a1", "a2", "a3"]),
154
154
  });
155
- const after = aui().thread().getState().messages[1];
155
+ const after = aui().thread.getState().messages[1];
156
156
 
157
157
  expect(after!.branchNumber).toBe(2);
158
158
  expect(before!.parts[0]).toBe(after!.parts[0]);
@@ -66,7 +66,7 @@ describe("ExternalThread part status", () => {
66
66
  isRunning: true,
67
67
  });
68
68
  const part = (toolCallId: string) =>
69
- aui().thread().message({ id: "a1" }).part({ toolCallId }).getState();
69
+ aui().thread.message({ id: "a1" }).part({ toolCallId }).getState();
70
70
  expect(part("tc1").status).toEqual({ type: "running" });
71
71
  });
72
72
 
@@ -75,7 +75,7 @@ describe("ExternalThread part status", () => {
75
75
  messages: [assistantMessage({ type: "running" }, "ok")],
76
76
  isRunning: true,
77
77
  });
78
- const state = aui().thread().message({ id: "a1" }).getState();
78
+ const state = aui().thread.message({ id: "a1" }).getState();
79
79
  expect(state.parts[0]!.status).toEqual({ type: "complete" });
80
80
  expect(state.parts[1]!.status).toEqual({ type: "complete" });
81
81
  });
@@ -96,7 +96,7 @@ describe("ExternalThread part status", () => {
96
96
  isRunning: false,
97
97
  });
98
98
  expect(
99
- aui().thread().message({ id: "u1" }).part({ index: 0 }).getState().status,
99
+ aui().thread.message({ id: "u1" }).part({ index: 0 }).getState().status,
100
100
  ).toEqual({ type: "complete" });
101
101
  expect(
102
102
  aui()
@@ -106,7 +106,7 @@ describe("ExternalThread part status", () => {
106
106
  .getState().status,
107
107
  ).toEqual({ type: "requires-action", reason: "tool-calls" });
108
108
  expect(
109
- aui().thread().message({ id: "a1" }).part({ index: 0 }).getState().status,
109
+ aui().thread.message({ id: "a1" }).part({ index: 0 }).getState().status,
110
110
  ).toEqual({ type: "complete" });
111
111
  });
112
112
  });
@@ -120,7 +120,7 @@ describe("ExternalThread unset optional callbacks", () => {
120
120
  isRunning: false,
121
121
  });
122
122
  const part = () =>
123
- aui().thread().message({ id: "a1" }).part({ toolCallId: "tc1" });
123
+ aui().thread.message({ id: "a1" }).part({ toolCallId: "tc1" });
124
124
 
125
125
  expect(() => part().addToolResult("ok")).toThrow(
126
126
  "Runtime does not support tool results (onAddToolResult is not set).",
@@ -128,10 +128,10 @@ describe("ExternalThread unset optional callbacks", () => {
128
128
  expect(() => part().resumeToolCall(undefined)).toThrow(
129
129
  "Runtime does not support resuming tool calls (onResumeToolCall is not set).",
130
130
  );
131
- expect(() => aui().thread().resumeRun()).toThrow(
131
+ expect(() => aui().thread.resumeRun()).toThrow(
132
132
  "Runtime does not support resuming runs (onResume is not set).",
133
133
  );
134
- expect(() => aui().thread().importExternalState({})).toThrow(
134
+ expect(() => aui().thread.importExternalState({})).toThrow(
135
135
  "Runtime does not support importing external states (onLoadExternalState is not set).",
136
136
  );
137
137
  });
@@ -142,16 +142,14 @@ describe("ExternalThread composer", () => {
142
142
  const onNew = vi.fn();
143
143
  const { aui } = renderThread({ messages: [], isRunning: false, onNew });
144
144
 
145
- aui().thread().composer().setText("hello");
146
- aui().thread().composer().send();
145
+ aui().thread.composer().setText("hello");
146
+ aui().thread.composer().send();
147
147
 
148
148
  await waitFor(() => expect(onNew).toHaveBeenCalledTimes(1));
149
149
  expect(onNew.mock.calls[0]![0].content).toEqual([
150
150
  { type: "text", text: "hello" },
151
151
  ]);
152
- await waitFor(() =>
153
- expect(aui().thread().getState().composer.text).toBe(""),
154
- );
152
+ await waitFor(() => expect(aui().thread.getState().composer.text).toBe(""));
155
153
  });
156
154
 
157
155
  it("stamps the thread head as parentId on queue-adapter sends", async () => {
@@ -177,8 +175,8 @@ describe("ExternalThread composer", () => {
177
175
  },
178
176
  });
179
177
 
180
- aui().thread().composer().setText("queued");
181
- aui().thread().composer().send();
178
+ aui().thread.composer().setText("queued");
179
+ aui().thread.composer().send();
182
180
 
183
181
  await waitFor(() => expect(enqueue).toHaveBeenCalledTimes(1));
184
182
  expect(enqueue.mock.calls[0]![0].parentId).toBe("u1");
@@ -188,10 +186,10 @@ describe("ExternalThread composer", () => {
188
186
  const onNew = vi.fn();
189
187
  const { aui } = renderThread({ messages: [], isRunning: false, onNew });
190
188
 
191
- aui().thread().composer().send();
192
- aui().thread().composer().setText("first");
193
- aui().thread().composer().send();
194
- aui().thread().composer().send();
189
+ aui().thread.composer().send();
190
+ aui().thread.composer().setText("first");
191
+ aui().thread.composer().send();
192
+ aui().thread.composer().send();
195
193
 
196
194
  await new Promise((resolve) => setTimeout(resolve, 0));
197
195
  expect(onNew).toHaveBeenCalledTimes(1);
@@ -219,10 +217,10 @@ describe("ExternalThread duplicate message ids", () => {
219
217
  ],
220
218
  });
221
219
 
222
- const state = aui().thread().getState();
220
+ const state = aui().thread.getState();
223
221
  expect(state.messages.map((m) => m.id)).toEqual(["u1", "dup"]);
224
222
 
225
- const dup = aui().thread().message({ id: "dup" }).getState();
223
+ const dup = aui().thread.message({ id: "dup" }).getState();
226
224
  expect(dup.parts[0]).toMatchObject({ type: "text", text: "fresh" });
227
225
  expect(warn).toHaveBeenCalledWith(expect.stringContaining('"dup"'));
228
226
  } finally {