@assistant-ui/react 0.14.29 → 0.15.1

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 (142) 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/hooks/useToolCallElapsed.js +24 -29
  8. package/dist/hooks/useToolCallElapsed.js.map +1 -1
  9. package/dist/index.d.ts +1 -8
  10. package/dist/index.js +1 -8
  11. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +3 -3
  12. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
  13. package/dist/mcp-apps/McpAppRenderer.js +1 -1
  14. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  15. package/dist/model-context/makeAssistantVisible.js +26 -25
  16. package/dist/model-context/makeAssistantVisible.js.map +1 -1
  17. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +3 -3
  18. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  19. package/dist/primitives/attachment/AttachmentRemove.js +3 -3
  20. package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
  21. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +3 -3
  22. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
  23. package/dist/primitives/composer/ComposerAddAttachment.js +3 -3
  24. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  25. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  26. package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
  27. package/dist/primitives/composer/ComposerInput.js +13 -13
  28. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  29. package/dist/primitives/composer/ComposerQuote.js +1 -1
  30. package/dist/primitives/composer/ComposerQuote.js.map +1 -1
  31. package/dist/primitives/composer/ComposerStopDictation.js +3 -3
  32. package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
  33. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  34. package/dist/primitives/composer/trigger/triggerSelectionResource.js +3 -3
  35. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  36. package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
  37. package/dist/primitives/message/MessagePartsGrouped.js +67 -89
  38. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  39. package/dist/primitives/message/MessageRoot.js +3 -3
  40. package/dist/primitives/message/MessageRoot.js.map +1 -1
  41. package/dist/primitives/queueItem/QueueItemRemove.js +3 -3
  42. package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
  43. package/dist/primitives/queueItem/QueueItemSteer.js +3 -3
  44. package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
  45. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  46. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
  47. package/dist/primitives/suggestion/SuggestionTrigger.js +16 -15
  48. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  49. package/dist/unstable/useComposerInput.js +2 -2
  50. package/dist/unstable/useComposerInput.js.map +1 -1
  51. package/dist/unstable/useComposerInputHistory.js +6 -6
  52. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  53. package/dist/unstable/useMentionAdapter.js +1 -1
  54. package/dist/unstable/useMentionAdapter.js.map +1 -1
  55. package/dist/utils/smooth/SmoothContext.js +3 -3
  56. package/dist/utils/smooth/SmoothContext.js.map +1 -1
  57. package/dist/utils/smooth/useSmooth.js +1 -1
  58. package/dist/utils/smooth/useSmooth.js.map +1 -1
  59. package/dist/utils/useToolArgsFieldStatus.d.ts +2 -2
  60. package/package.json +5 -5
  61. package/src/client/ExternalThread.ts +5 -3
  62. package/src/client/InMemoryThreadList.ts +3 -3
  63. package/src/context/index.ts +0 -44
  64. package/src/hooks/useToolCallElapsed.ts +9 -12
  65. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +5 -9
  66. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +1 -1
  67. package/src/mcp-apps/McpAppRenderer.tsx +1 -1
  68. package/src/model-context/makeAssistantVisible.test.tsx +1 -1
  69. package/src/model-context/makeAssistantVisible.tsx +1 -1
  70. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
  71. package/src/primitives/attachment/AttachmentRemove.ts +1 -1
  72. package/src/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.ts +1 -1
  73. package/src/primitives/composer/ComposerAddAttachment.test.tsx +2 -2
  74. package/src/primitives/composer/ComposerAddAttachment.ts +1 -1
  75. package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +2 -2
  76. package/src/primitives/composer/ComposerAttachmentDropzone.tsx +1 -1
  77. package/src/primitives/composer/ComposerInput.test.tsx +7 -7
  78. package/src/primitives/composer/ComposerInput.tsx +13 -13
  79. package/src/primitives/composer/ComposerQuote.tsx +1 -1
  80. package/src/primitives/composer/ComposerStopDictation.ts +1 -1
  81. package/src/primitives/composer/trigger/triggerSelectionResource.ts +7 -9
  82. package/src/primitives/message/MessagePartsGrouped.tsx +6 -8
  83. package/src/primitives/message/MessageRoot.tsx +1 -1
  84. package/src/primitives/queueItem/QueueItemRemove.ts +1 -1
  85. package/src/primitives/queueItem/QueueItemSteer.ts +1 -1
  86. package/src/primitives/selectionToolbar/SelectionToolbarQuote.tsx +1 -1
  87. package/src/primitives/suggestion/SuggestionTrigger.ts +9 -9
  88. package/src/tests/DataRenderers.test.tsx +1 -1
  89. package/src/tests/RemoteThreadListRuntime.deferredProvider.test.tsx +3 -3
  90. package/src/tests/external-thread-attachments.test.tsx +21 -23
  91. package/src/tests/external-thread-branches.test.tsx +10 -10
  92. package/src/tests/external-thread-parity.test.tsx +18 -20
  93. package/src/tests/local-runtime-queue.test.tsx +29 -34
  94. package/src/tests/messagePartSwitchRace.test.tsx +30 -13
  95. package/src/unstable/useComposerInput.test.tsx +2 -2
  96. package/src/unstable/useComposerInput.ts +2 -2
  97. package/src/unstable/useComposerInputHistory.test.tsx +3 -3
  98. package/src/unstable/useComposerInputHistory.ts +6 -6
  99. package/src/unstable/useMentionAdapter.ts +1 -1
  100. package/src/utils/smooth/SmoothContext.tsx +1 -1
  101. package/src/utils/smooth/useSmooth.test.tsx +1 -1
  102. package/src/utils/smooth/useSmooth.ts +1 -1
  103. package/dist/context/react/utils/createStateHookForRuntime.d.ts +0 -34
  104. package/dist/context/react/utils/createStateHookForRuntime.d.ts.map +0 -1
  105. package/dist/context/react/utils/createStateHookForRuntime.js +0 -21
  106. package/dist/context/react/utils/createStateHookForRuntime.js.map +0 -1
  107. package/dist/legacy-runtime/hooks/AssistantContext.d.ts +0 -74
  108. package/dist/legacy-runtime/hooks/AssistantContext.d.ts.map +0 -1
  109. package/dist/legacy-runtime/hooks/AssistantContext.js +0 -29
  110. package/dist/legacy-runtime/hooks/AssistantContext.js.map +0 -1
  111. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts +0 -1007
  112. package/dist/legacy-runtime/hooks/AttachmentContext.d.ts.map +0 -1
  113. package/dist/legacy-runtime/hooks/AttachmentContext.js +0 -47
  114. package/dist/legacy-runtime/hooks/AttachmentContext.js.map +0 -1
  115. package/dist/legacy-runtime/hooks/ComposerContext.d.ts +0 -141
  116. package/dist/legacy-runtime/hooks/ComposerContext.d.ts.map +0 -1
  117. package/dist/legacy-runtime/hooks/ComposerContext.js +0 -65
  118. package/dist/legacy-runtime/hooks/ComposerContext.js.map +0 -1
  119. package/dist/legacy-runtime/hooks/MessageContext.d.ts +0 -155
  120. package/dist/legacy-runtime/hooks/MessageContext.d.ts.map +0 -1
  121. package/dist/legacy-runtime/hooks/MessageContext.js +0 -68
  122. package/dist/legacy-runtime/hooks/MessageContext.js.map +0 -1
  123. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts +0 -44
  124. package/dist/legacy-runtime/hooks/MessagePartContext.d.ts.map +0 -1
  125. package/dist/legacy-runtime/hooks/MessagePartContext.js +0 -26
  126. package/dist/legacy-runtime/hooks/MessagePartContext.js.map +0 -1
  127. package/dist/legacy-runtime/hooks/ThreadContext.d.ts +0 -139
  128. package/dist/legacy-runtime/hooks/ThreadContext.d.ts.map +0 -1
  129. package/dist/legacy-runtime/hooks/ThreadContext.js +0 -84
  130. package/dist/legacy-runtime/hooks/ThreadContext.js.map +0 -1
  131. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts +0 -44
  132. package/dist/legacy-runtime/hooks/ThreadListItemContext.d.ts.map +0 -1
  133. package/dist/legacy-runtime/hooks/ThreadListItemContext.js +0 -26
  134. package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +0 -1
  135. package/src/context/react/utils/createStateHookForRuntime.ts +0 -82
  136. package/src/legacy-runtime/hooks/AssistantContext.ts +0 -67
  137. package/src/legacy-runtime/hooks/AttachmentContext.ts +0 -99
  138. package/src/legacy-runtime/hooks/ComposerContext.ts +0 -131
  139. package/src/legacy-runtime/hooks/MessageContext.ts +0 -127
  140. package/src/legacy-runtime/hooks/MessagePartContext.ts +0 -32
  141. package/src/legacy-runtime/hooks/ThreadContext.ts +0 -120
  142. package/src/legacy-runtime/hooks/ThreadListItemContext.ts +0 -36
@@ -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 {
@@ -27,8 +27,7 @@ const createCountingAdapter = () => {
27
27
  };
28
28
 
29
29
  const userTexts = (aui: ReturnType<typeof useAui>) =>
30
- aui
31
- .thread()
30
+ aui.thread
32
31
  .getState()
33
32
  .messages.filter((m) => m.role === "user")
34
33
  .map((m) =>
@@ -57,8 +56,8 @@ const renderWithRuntime = (adapter: ChatModelAdapter, enableQueue: boolean) => {
57
56
 
58
57
  const send = async (aui: ReturnType<typeof useAui>, text: string) => {
59
58
  await act(async () => {
60
- aui.thread().composer().setText(text);
61
- aui.thread().composer().send();
59
+ aui.thread.composer().setText(text);
60
+ aui.thread.composer().send();
62
61
  await flush();
63
62
  });
64
63
  };
@@ -69,13 +68,12 @@ describe("local runtime message queue", () => {
69
68
  const aui = renderWithRuntime(adapter, true);
70
69
 
71
70
  await send(aui, "first");
72
- expect(aui.thread().getState().isRunning).toBe(true);
73
- expect(aui.thread().getState().capabilities.queue).toBe(true);
71
+ expect(aui.thread.getState().isRunning).toBe(true);
72
+ expect(aui.thread.getState().capabilities.queue).toBe(true);
74
73
 
75
74
  await send(aui, "second");
76
75
  expect(
77
- aui
78
- .thread()
76
+ aui.thread
79
77
  .composer()
80
78
  .getState()
81
79
  .queue.map((q) => q.prompt),
@@ -87,7 +85,7 @@ describe("local runtime message queue", () => {
87
85
  await flush();
88
86
  await flush();
89
87
  });
90
- expect(aui.thread().composer().getState().queue).toEqual([]);
88
+ expect(aui.thread.composer().getState().queue).toEqual([]);
91
89
  expect(userTexts(aui)).toContain("second");
92
90
  });
93
91
 
@@ -100,7 +98,7 @@ describe("local runtime message queue", () => {
100
98
 
101
99
  await send(aui, "a");
102
100
  await send(aui, "b");
103
- expect(aui.thread().composer().getState().queue).toHaveLength(2);
101
+ expect(aui.thread.composer().getState().queue).toHaveLength(2);
104
102
 
105
103
  await act(async () => {
106
104
  releases[0]!();
@@ -109,8 +107,7 @@ describe("local runtime message queue", () => {
109
107
  });
110
108
  expect(getRunCount()).toBe(2);
111
109
  expect(
112
- aui
113
- .thread()
110
+ aui.thread
114
111
  .composer()
115
112
  .getState()
116
113
  .queue.map((q) => q.prompt),
@@ -124,13 +121,13 @@ describe("local runtime message queue", () => {
124
121
  await send(aui, "first");
125
122
  await send(aui, "a");
126
123
  await send(aui, "b");
127
- expect(aui.thread().composer().getState().queue).toHaveLength(2);
124
+ expect(aui.thread.composer().getState().queue).toHaveLength(2);
128
125
 
129
126
  await act(async () => {
130
- aui.thread().composer().queueItem({ index: 0 }).remove();
127
+ aui.thread.composer().queueItem({ index: 0 }).remove();
131
128
  await flush();
132
129
  });
133
- const queue = aui.thread().composer().getState().queue;
130
+ const queue = aui.thread.composer().getState().queue;
134
131
  expect(queue).toHaveLength(1);
135
132
  expect(queue[0]!.prompt).toBe("b");
136
133
  });
@@ -142,15 +139,15 @@ describe("local runtime message queue", () => {
142
139
  await send(aui, "first");
143
140
  await send(aui, "a");
144
141
  await send(aui, "b");
145
- expect(aui.thread().composer().getState().queue).toHaveLength(2);
142
+ expect(aui.thread.composer().getState().queue).toHaveLength(2);
146
143
 
147
144
  await act(async () => {
148
- aui.thread().cancelRun();
145
+ aui.thread.cancelRun();
149
146
  await flush();
150
147
  await flush();
151
148
  });
152
149
 
153
- expect(aui.thread().composer().getState().queue).toEqual([]);
150
+ expect(aui.thread.composer().getState().queue).toEqual([]);
154
151
  // cancelling must not start the next queued message
155
152
  expect(getRunCount()).toBe(1);
156
153
  });
@@ -168,11 +165,11 @@ describe("local runtime message queue", () => {
168
165
  // start a second run and queue a message behind it
169
166
  await send(aui, "second");
170
167
  await send(aui, "queued");
171
- expect(aui.thread().composer().getState().queue).toHaveLength(1);
168
+ expect(aui.thread.composer().getState().queue).toHaveLength(1);
172
169
 
173
170
  // edit the first message while the run is in progress
174
171
  await act(async () => {
175
- const message = aui.thread().message({ index: 0 });
172
+ const message = aui.thread.message({ index: 0 });
176
173
  message.composer().beginEdit();
177
174
  message.composer().setText("edited");
178
175
  message.composer().send();
@@ -180,7 +177,7 @@ describe("local runtime message queue", () => {
180
177
  });
181
178
 
182
179
  // the edit is applied (branches the thread) and the stale queue is cleared
183
- expect(aui.thread().composer().getState().queue).toEqual([]);
180
+ expect(aui.thread.composer().getState().queue).toEqual([]);
184
181
  });
185
182
 
186
183
  it("buffers a send during a regenerate instead of interrupting it", async () => {
@@ -196,22 +193,21 @@ describe("local runtime message queue", () => {
196
193
 
197
194
  // regenerate the assistant message: a run started outside the queue
198
195
  await act(async () => {
199
- aui.thread().message({ index: 1 }).reload();
196
+ aui.thread.message({ index: 1 }).reload();
200
197
  await flush();
201
198
  });
202
199
  expect(getRunCount()).toBe(2);
203
- expect(aui.thread().getState().isRunning).toBe(true);
200
+ expect(aui.thread.getState().isRunning).toBe(true);
204
201
 
205
202
  // sending now must buffer, not interrupt the regenerate
206
203
  await act(async () => {
207
- aui.thread().composer().setText("Y");
208
- aui.thread().composer().send();
204
+ aui.thread.composer().setText("Y");
205
+ aui.thread.composer().send();
209
206
  await flush();
210
207
  });
211
208
  expect(getRunCount()).toBe(2);
212
209
  expect(
213
- aui
214
- .thread()
210
+ aui.thread
215
211
  .composer()
216
212
  .getState()
217
213
  .queue.map((q) => q.prompt),
@@ -239,7 +235,7 @@ describe("local runtime message queue", () => {
239
235
  await send(aui, "first");
240
236
  await send(aui, "a");
241
237
  await send(aui, "b");
242
- expect(aui.thread().composer().getState().queue).toHaveLength(2);
238
+ expect(aui.thread.composer().getState().queue).toHaveLength(2);
243
239
 
244
240
  // run 1 settles -> "a" drains (run 2 throws) -> "b" drains (run 3)
245
241
  await act(async () => {
@@ -248,18 +244,17 @@ describe("local runtime message queue", () => {
248
244
  await flush();
249
245
  });
250
246
  expect(runCount).toBe(3);
251
- expect(aui.thread().composer().getState().queue).toEqual([]);
247
+ expect(aui.thread.composer().getState().queue).toEqual([]);
252
248
 
253
249
  // "b" is running; a new send must buffer behind it, not interrupt it
254
250
  await act(async () => {
255
- aui.thread().composer().setText("c");
256
- aui.thread().composer().send();
251
+ aui.thread.composer().setText("c");
252
+ aui.thread.composer().send();
257
253
  await flush();
258
254
  });
259
255
  expect(runCount).toBe(3);
260
256
  expect(
261
- aui
262
- .thread()
257
+ aui.thread
263
258
  .composer()
264
259
  .getState()
265
260
  .queue.map((q) => q.prompt),
@@ -269,7 +264,7 @@ describe("local runtime message queue", () => {
269
264
  it("does not expose the queue capability when the flag is off", async () => {
270
265
  const { adapter } = createCountingAdapter();
271
266
  const aui = renderWithRuntime(adapter, false);
272
- expect(aui.thread().getState().capabilities.queue).toBe(false);
267
+ expect(aui.thread.getState().capabilities.queue).toBe(false);
273
268
  });
274
269
 
275
270
  it("tears the queue down when the flag is toggled off at runtime", async () => {
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { act, cleanup, render, waitFor } from "@testing-library/react";
4
4
  import { flushSync } from "react-dom";
5
- import { useEffect, useState, type FC } from "react";
5
+ import { Component, useEffect, useState, type FC, type ReactNode } from "react";
6
6
  import { afterEach, describe, expect, it, vi } from "vitest";
7
7
  import { useAui, useAuiState } from "@assistant-ui/store";
8
8
  import { AssistantRuntimeProvider, PartByIndexProvider } from "../context";
@@ -85,16 +85,33 @@ const ChildrenMessage: FC = () => (
85
85
  const InvalidPartReader: FC = () => {
86
86
  const part = useAuiState((s) => s.part);
87
87
  return (
88
- <p data-testid="clamped-part">
88
+ <p data-testid="invalid-part">
89
89
  {part.type === "text" ? part.text : part.type}
90
90
  </p>
91
91
  );
92
92
  };
93
93
 
94
+ class PartErrorBoundary extends Component<
95
+ { children: ReactNode },
96
+ { error: Error | null }
97
+ > {
98
+ override state: { error: Error | null } = { error: null };
99
+ static getDerivedStateFromError(error: Error) {
100
+ return { error };
101
+ }
102
+ override render() {
103
+ if (this.state.error)
104
+ return <p data-testid="part-error">{this.state.error.message}</p>;
105
+ return this.props.children;
106
+ }
107
+ }
108
+
94
109
  const InvalidPartMessage: FC = () => (
95
- <PartByIndexProvider index={2}>
96
- <InvalidPartReader />
97
- </PartByIndexProvider>
110
+ <PartErrorBoundary>
111
+ <PartByIndexProvider index={2}>
112
+ <InvalidPartReader />
113
+ </PartByIndexProvider>
114
+ </PartErrorBoundary>
98
115
  );
99
116
 
100
117
  const App: FC<{ MessageComponent?: FC }> = ({ MessageComponent = Message }) => {
@@ -152,21 +169,21 @@ describe("part hooks under a thread-switch race", () => {
152
169
  },
153
170
  );
154
171
 
155
- it("warns and clamps an index that was never valid", async () => {
156
- const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
172
+ it("throws to the error boundary for an index that was never valid", async () => {
173
+ const consoleError = vi
174
+ .spyOn(console, "error")
175
+ .mockImplementation(() => {});
157
176
  try {
158
177
  let view!: ReturnType<typeof render>;
159
178
  await act(async () => {
160
179
  view = render(<App MessageComponent={InvalidPartMessage} />);
161
180
  });
162
- expect(warn).toHaveBeenCalledWith(
163
- expect.stringContaining(
164
- "useClientLookup: Clamped stale index 2 to 1 (length: 2)",
165
- ),
181
+ expect(view.queryByTestId("invalid-part")).toBeNull();
182
+ expect(view.getByTestId("part-error").textContent).toContain(
183
+ "useClientLookup: index 2 out of bounds (length: 2)",
166
184
  );
167
- expect(view.getByTestId("clamped-part").textContent).toBe("world");
168
185
  } finally {
169
- warn.mockRestore();
186
+ consoleError.mockRestore();
170
187
  }
171
188
  });
172
189
  });
@@ -18,11 +18,11 @@ const flushTapSync = vi.fn((fn: () => void) => fn());
18
18
 
19
19
  vi.mock("@assistant-ui/store", () => ({
20
20
  useAui: () => ({
21
- composer: () => ({
21
+ composer: {
22
22
  getState: () => ({ isEditing: fixture.composer.isEditing }),
23
23
  setText: composerSetText,
24
24
  send: composerSend,
25
- }),
25
+ },
26
26
  }),
27
27
  useAuiState: (selector: (s: unknown) => unknown) =>
28
28
  selector({
@@ -83,9 +83,9 @@ export function unstable_useComposerInput(
83
83
 
84
84
  const setText = useCallback(
85
85
  (text: string) => {
86
- if (!aui.composer().getState().isEditing) return;
86
+ if (!aui.composer.getState().isEditing) return;
87
87
  flushTapSync(() => {
88
- aui.composer().setText(text);
88
+ aui.composer.setText(text);
89
89
  });
90
90
  },
91
91
  [aui],
@@ -12,11 +12,11 @@ const setText = vi.fn();
12
12
 
13
13
  vi.mock("@assistant-ui/store", () => ({
14
14
  useAui: () => ({
15
- composer: () => ({
15
+ composer: {
16
16
  getState: () => ({ type: fixture.composerType }),
17
17
  setText,
18
- }),
19
- thread: () => ({ getState: () => ({ messages: fixture.messages }) }),
18
+ },
19
+ thread: { getState: () => ({ messages: fixture.messages }) },
20
20
  on: (_event: string, cb: () => void) => {
21
21
  fixture.switchedToHandlers.push(cb);
22
22
  return () => {};
@@ -70,7 +70,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
70
70
  const browseRef = useRef<BrowseState | null>(null);
71
71
 
72
72
  useEffect(() => {
73
- if (aui.composer().getState().type !== "thread") return undefined;
73
+ if (aui.composer.getState().type !== "thread") return undefined;
74
74
 
75
75
  return aui.on("threadListItem.switchedTo", () => {
76
76
  browseRef.current = null;
@@ -84,7 +84,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
84
84
  if (e.nativeEvent.isComposing) return;
85
85
  if (e.shiftKey || e.ctrlKey || e.metaKey || e.altKey) return;
86
86
  if (popoverCtx && popoverCtx.getActiveAria() !== null) return;
87
- if (aui.composer().getState().type !== "thread") return;
87
+ if (aui.composer.getState().type !== "thread") return;
88
88
 
89
89
  const textarea = e.currentTarget;
90
90
  const { selectionStart, selectionEnd, value } = textarea;
@@ -96,7 +96,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
96
96
  const browse = browseRef.current;
97
97
 
98
98
  const commitText = (text: string): void => {
99
- flushTapSync(() => aui.composer().setText(text));
99
+ flushTapSync(() => aui.composer.setText(text));
100
100
  // React's controlled-value commit restores the pre-recall caret;
101
101
  // reposition after the commit, before paint.
102
102
  requestAnimationFrame(() => {
@@ -124,13 +124,13 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
124
124
 
125
125
  if (!browse) {
126
126
  if (value.trim() !== "") return;
127
- const history = deriveHistory(aui.thread().getState().messages);
127
+ const history = deriveHistory(aui.thread.getState().messages);
128
128
  if (history.length === 0) return;
129
129
  recall(history, 0, value);
130
130
  return;
131
131
  }
132
132
 
133
- const history = deriveHistory(aui.thread().getState().messages);
133
+ const history = deriveHistory(aui.thread.getState().messages);
134
134
  const next = browse.cursor + 1;
135
135
  if (next >= history.length) {
136
136
  e.preventDefault();
@@ -150,7 +150,7 @@ export function unstable_useComposerInputHistory(): Unstable_ComposerInputHistor
150
150
  return;
151
151
  }
152
152
 
153
- const history = deriveHistory(aui.thread().getState().messages);
153
+ const history = deriveHistory(aui.thread.getState().messages);
154
154
  recall(history, next, browse.draftSnapshot);
155
155
  },
156
156
  [aui, popoverCtx],
@@ -107,7 +107,7 @@ export function unstable_useMentionAdapter(
107
107
  const adapter = useMemo<Unstable_TriggerAdapter>(() => {
108
108
  const getModelContextTools = (): Unstable_TriggerItem[] => {
109
109
  if (!wantsTools) return [];
110
- const ctx = aui.thread().getModelContext();
110
+ const ctx = aui.thread.getModelContext();
111
111
  const tools = ctx.tools;
112
112
  if (!tools) return [];
113
113
  const formatLabel = toolsConfig?.formatLabel;
@@ -38,7 +38,7 @@ export const SmoothContextProvider: FC<PropsWithChildren> = ({ children }) => {
38
38
  const aui = useAui();
39
39
 
40
40
  const [context] = useState(() =>
41
- makeSmoothContext(aui.part().getState().status),
41
+ makeSmoothContext(aui.part.getState().status),
42
42
  );
43
43
 
44
44
  // do not wrap if there is an outer SmoothContextProvider
@@ -9,7 +9,7 @@ import type {
9
9
 
10
10
  const part = {};
11
11
  vi.mock("@assistant-ui/store", () => ({
12
- useAui: () => ({ part: () => part }),
12
+ useAui: () => ({ part }),
13
13
  useAuiState: (selector: () => unknown) => selector(),
14
14
  }));
15
15
  vi.mock("./SmoothContext", () => ({
@@ -181,7 +181,7 @@ export const useSmooth = (
181
181
  // of relying on a render-time proxy reference that may be stable
182
182
  // across thread swaps.
183
183
  const aui = useAui();
184
- const part = useAuiState(() => aui.part());
184
+ const part = useAuiState(() => aui.part);
185
185
  const [prevPart, setPrevPart] = useState(part);
186
186
  if (part !== prevPart || !text.startsWith(displayedText)) {
187
187
  setPrevPart(part);
@@ -1,34 +0,0 @@
1
- import { SubscribableRuntime } from "./useRuntimeState.js";
2
- //#region src/context/react/utils/createStateHookForRuntime.d.ts
3
- declare function createStateHookForRuntime<TState>(useRuntime: (options: {
4
- optional: boolean | undefined;
5
- }) => SubscribableRuntime<TState> | null): {
6
- (): TState;
7
- <TSelected>(selector: (state: TState) => TSelected): TSelected;
8
- <TSelected>(selector: ((state: TState) => TSelected) | undefined): TSelected | TState;
9
- (options: {
10
- optional?: false | undefined;
11
- }): TState;
12
- (options: {
13
- optional?: boolean | undefined;
14
- }): TState | null;
15
- <TSelected>(options: {
16
- optional?: false | undefined;
17
- selector: (state: TState) => TSelected;
18
- }): TSelected;
19
- <TSelected>(options: {
20
- optional?: false | undefined;
21
- selector: ((state: TState) => TSelected) | undefined;
22
- }): TSelected | TState;
23
- <TSelected>(options: {
24
- optional?: boolean | undefined;
25
- selector: (state: TState) => TSelected;
26
- }): TSelected | null;
27
- <TSelected>(options: {
28
- optional?: boolean | undefined;
29
- selector: ((state: TState) => TSelected) | undefined;
30
- }): TSelected | TState | null;
31
- };
32
- //#endregion
33
- export { createStateHookForRuntime };
34
- //# sourceMappingURL=createStateHookForRuntime.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createStateHookForRuntime.d.ts","names":[],"sources":["../../../../src/context/react/utils/createStateHookForRuntime.ts"],"mappings":";;iBAKgB,0BAA0B,QACxC,aAAa;EACX;MACI,oBAAoB;MAGD;GAGH,WAAS,WAClB,OAAO,WAAW,YAC5B;GAGmB,WAAS,YACjB,OAAO,WAAW,yBAC7B,YAAY;GAGgB;IAAE;MAAiC;GAGnC;IAC7B;MACE;GAGkB,WAAS;IAC7B;IACA,WAAW,OAAO,WAAW;MAC3B;GAGkB,WAAS;IAC7B;IACA,YAAY,OAAO,WAAW;MAC5B,YAAY;GAGM,WAAS;IAC7B;IACA,WAAW,OAAO,WAAW;MAC3B;GAGkB,WAAS;IAC7B;IACA,YAAY,OAAO,WAAW;MAC5B,YAAY"}
@@ -1,21 +0,0 @@
1
- import { useRuntimeStateInternal } from "./useRuntimeState.js";
2
- //#region src/context/react/utils/createStateHookForRuntime.ts
3
- function createStateHookForRuntime(useRuntime) {
4
- function useStoreHook(param) {
5
- let optional = false;
6
- let selector;
7
- if (typeof param === "function") selector = param;
8
- else if (param) {
9
- optional = !!param.optional;
10
- selector = param.selector;
11
- }
12
- const store = useRuntime({ optional });
13
- if (!store) return null;
14
- return useRuntimeStateInternal(store, selector);
15
- }
16
- return useStoreHook;
17
- }
18
- //#endregion
19
- export { createStateHookForRuntime };
20
-
21
- //# sourceMappingURL=createStateHookForRuntime.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createStateHookForRuntime.js","names":["SubscribableRuntime","useRuntimeStateInternal","createStateHookForRuntime","useRuntime","options","optional","TState","useStoreHook","selector","state","TSelected","param","store"],"sources":["../../../../src/context/react/utils/createStateHookForRuntime.ts"],"sourcesContent":["import {\n type SubscribableRuntime,\n useRuntimeStateInternal,\n} from \"./useRuntimeState\";\n\nexport function createStateHookForRuntime<TState>(\n useRuntime: (options: {\n optional: boolean | undefined;\n }) => SubscribableRuntime<TState> | null,\n) {\n // empty\n function useStoreHook(): TState;\n\n // selector\n function useStoreHook<TSelected>(\n selector: (state: TState) => TSelected,\n ): TSelected;\n\n // selector?\n function useStoreHook<TSelected>(\n selector: ((state: TState) => TSelected) | undefined,\n ): TSelected | TState;\n\n // optional=false\n function useStoreHook(options: { optional?: false | undefined }): TState;\n\n // optional?\n function useStoreHook(options: {\n optional?: boolean | undefined;\n }): TState | null;\n\n // optional=false, selector\n function useStoreHook<TSelected>(options: {\n optional?: false | undefined;\n selector: (state: TState) => TSelected;\n }): TSelected;\n\n // optional=false, selector?\n function useStoreHook<TSelected>(options: {\n optional?: false | undefined;\n selector: ((state: TState) => TSelected) | undefined;\n }): TSelected | TState;\n\n // optional?, selector\n function useStoreHook<TSelected>(options: {\n optional?: boolean | undefined;\n selector: (state: TState) => TSelected;\n }): TSelected | null;\n\n // optional?, selector?\n function useStoreHook<TSelected>(options: {\n optional?: boolean | undefined;\n selector: ((state: TState) => TSelected) | undefined;\n }): TSelected | TState | null;\n\n function useStoreHook<TSelected>(\n param?:\n | ((state: TState) => TSelected)\n | {\n optional?: boolean | undefined;\n selector?: ((state: TState) => TSelected) | undefined;\n },\n ): TSelected | TState | null {\n let optional = false;\n let selector: ((state: TState) => TSelected) | undefined;\n\n if (typeof param === \"function\") {\n selector = param;\n } else if (param) {\n optional = !!param.optional;\n selector = param.selector;\n }\n\n const store = useRuntime({ optional });\n if (!store) return null;\n\n // it is ok to call useRuntimeStateInternal conditionally because it will never become null if its available\n return useRuntimeStateInternal(store, selector);\n }\n\n return useStoreHook;\n}\n"],"mappings":";;AAKA,SAAgBE,0BACdC,YAGA;CA8CA,SAASI,aACPI,OAM2B;EAC3B,IAAIN,WAAW;EACf,IAAIG;EAEJ,IAAI,OAAOG,UAAU,YACnBH,WAAWG;OACN,IAAIA,OAAO;GAChBN,WAAW,CAAC,CAACM,MAAMN;GACnBG,WAAWG,MAAMH;EACnB;EAEA,MAAMI,QAAQT,WAAW,EAAEE,SAAS,CAAC;EACrC,IAAI,CAACO,OAAO,OAAO;EAGnB,OAAOX,wBAAwBW,OAAOJ,QAAQ;CAChD;CAEA,OAAOD;AACT"}
@@ -1,74 +0,0 @@
1
- import { AssistantRuntime } from "../runtime/AssistantRuntime.js";
2
- //#region src/legacy-runtime/hooks/AssistantContext.d.ts
3
- /**
4
- * @deprecated Use {@link useAui} instead. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
5
- *
6
- * Hook to access the AssistantRuntime from the current context.
7
- *
8
- * The AssistantRuntime provides access to the top-level assistant state and actions,
9
- * including thread management, tool registration, and configuration.
10
- *
11
- * @param options Configuration options
12
- * @param options.optional Whether the hook should return null if no context is found
13
- * @returns The AssistantRuntime instance, or null if optional is true and no context exists
14
- *
15
- * @example
16
- * ```tsx
17
- * // Before:
18
- * function MyComponent() {
19
- * const runtime = useAssistantRuntime();
20
- * const handleNewThread = () => {
21
- * runtime.switchToNewThread();
22
- * };
23
- * return <button onClick={handleNewThread}>New Thread</button>;
24
- * }
25
- *
26
- * // After:
27
- * function MyComponent() {
28
- * const aui = useAui();
29
- * const handleNewThread = () => {
30
- * aui.threads().switchToNewThread();
31
- * };
32
- * return <button onClick={handleNewThread}>New Thread</button>;
33
- * }
34
- * ```
35
- */
36
- declare function useAssistantRuntime(options?: {
37
- optional?: false | undefined;
38
- }): AssistantRuntime;
39
- declare function useAssistantRuntime(options?: {
40
- optional?: boolean | undefined;
41
- }): AssistantRuntime | null;
42
- /**
43
- * @deprecated Use {@link useAuiState}: `useAuiState((s) => s.threads)`. See the {@link https://assistant-ui.com/docs/migrations/v0-12 migration guide}.
44
- */
45
- declare const useThreadList: {
46
- (): import("@assistant-ui/core").ThreadListState;
47
- <TSelected>(selector: (state: import("@assistant-ui/core").ThreadListState) => TSelected): TSelected;
48
- <TSelected>(selector: ((state: import("@assistant-ui/core").ThreadListState) => TSelected) | undefined): import("@assistant-ui/core").ThreadListState | TSelected;
49
- (options: {
50
- optional?: false | undefined;
51
- }): import("@assistant-ui/core").ThreadListState;
52
- (options: {
53
- optional?: boolean | undefined;
54
- }): import("@assistant-ui/core").ThreadListState | null;
55
- <TSelected>(options: {
56
- optional?: false | undefined;
57
- selector: (state: import("@assistant-ui/core").ThreadListState) => TSelected;
58
- }): TSelected;
59
- <TSelected>(options: {
60
- optional?: false | undefined;
61
- selector: ((state: import("@assistant-ui/core").ThreadListState) => TSelected) | undefined;
62
- }): import("@assistant-ui/core").ThreadListState | TSelected;
63
- <TSelected>(options: {
64
- optional?: boolean | undefined;
65
- selector: (state: import("@assistant-ui/core").ThreadListState) => TSelected;
66
- }): TSelected | null;
67
- <TSelected>(options: {
68
- optional?: boolean | undefined;
69
- selector: ((state: import("@assistant-ui/core").ThreadListState) => TSelected) | undefined;
70
- }): import("@assistant-ui/core").ThreadListState | TSelected | null;
71
- };
72
- //#endregion
73
- export { useAssistantRuntime, useThreadList };
74
- //# sourceMappingURL=AssistantContext.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AssistantContext.d.ts","names":[],"sources":["../../../src/legacy-runtime/hooks/AssistantContext.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwCgB,oBAAoB;EAClC;IACE;iBACY,oBAAoB;EAClC;IACE;;;;cAqBS"}