@assistant-ui/react 0.15.4 → 0.15.6
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.
- package/dist/client/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +384 -368
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/context/providers/MessageProvider.js +15 -34
- package/dist/context/providers/MessageProvider.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts +6 -1
- package/dist/legacy-runtime/AssistantRuntimeProvider.d.ts.map +1 -1
- package/dist/legacy-runtime/AssistantRuntimeProvider.js +10 -8
- package/dist/legacy-runtime/AssistantRuntimeProvider.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +8 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +5 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +44 -3
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
- package/dist/primitives/attachment/AttachmentThumb.d.ts.map +1 -1
- package/dist/primitives/attachment/AttachmentThumb.js +17 -14
- package/dist/primitives/attachment/AttachmentThumb.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.d.ts.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +64 -50
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +1 -0
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.d.ts.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.js +10 -4
- package/dist/primitives/queueItem/QueueItemText.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.d.ts.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +5 -4
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/utils/createActionButton.js +1 -1
- package/dist/utils/createActionButton.js.map +1 -1
- package/dist/utils/useToolArgsFieldStatus.d.ts +2 -2
- package/package.json +16 -14
- package/src/client/ExternalThread.ts +30 -17
- package/src/context/providers/MessageProvider.tsx +8 -6
- package/src/index.ts +5 -1
- package/src/legacy-runtime/AssistantRuntimeProvider.tsx +13 -3
- package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +14 -1
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +38 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +5 -1
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +7 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +290 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +62 -1
- package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +1 -1
- package/src/primitives/attachment/AttachmentThumb.test.tsx +70 -0
- package/src/primitives/attachment/AttachmentThumb.tsx +8 -4
- package/src/primitives/composer/ComposerAttachmentDropzone.test.tsx +103 -3
- package/src/primitives/composer/ComposerAttachmentDropzone.tsx +18 -3
- package/src/primitives/composer/ComposerInput.test.tsx +53 -2
- package/src/primitives/composer/ComposerInput.tsx +3 -0
- package/src/primitives/queueItem/QueueItemText.tsx +8 -2
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.test.tsx +74 -0
- package/src/primitives/selectionToolbar/SelectionToolbarRoot.tsx +3 -3
- package/src/tests/external-thread-parity.test.tsx +52 -4
- package/src/tests/local-runtime-queue.test.tsx +199 -6
- package/src/utils/createActionButton.test.tsx +18 -0
- package/src/utils/createActionButton.tsx +1 -1
- package/LICENSE +0 -21
|
@@ -34,7 +34,14 @@ const userTexts = (aui: ReturnType<typeof useAui>) =>
|
|
|
34
34
|
m.content.map((p) => (p.type === "text" ? p.text : "")).join(""),
|
|
35
35
|
);
|
|
36
36
|
|
|
37
|
-
const renderWithRuntime = (
|
|
37
|
+
const renderWithRuntime = (
|
|
38
|
+
adapter: ChatModelAdapter,
|
|
39
|
+
enableQueue: boolean,
|
|
40
|
+
options?: {
|
|
41
|
+
unstable_queueClearOnRewind?: boolean;
|
|
42
|
+
unstable_queueClearOnCancel?: boolean;
|
|
43
|
+
},
|
|
44
|
+
) => {
|
|
38
45
|
const captured: { aui?: ReturnType<typeof useAui> } = {};
|
|
39
46
|
const Capture: FC = () => {
|
|
40
47
|
captured.aui = useAui();
|
|
@@ -43,6 +50,7 @@ const renderWithRuntime = (adapter: ChatModelAdapter, enableQueue: boolean) => {
|
|
|
43
50
|
const App: FC = () => {
|
|
44
51
|
const runtime = useLocalRuntime(adapter, {
|
|
45
52
|
unstable_enableMessageQueue: enableQueue,
|
|
53
|
+
...options,
|
|
46
54
|
});
|
|
47
55
|
return (
|
|
48
56
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
@@ -114,6 +122,43 @@ describe("local runtime message queue", () => {
|
|
|
114
122
|
).toEqual(["b"]);
|
|
115
123
|
});
|
|
116
124
|
|
|
125
|
+
it("defaults a mid-run send to steer, ahead of an explicitly queued item", async () => {
|
|
126
|
+
const { adapter, releases, getRunCount } = createCountingAdapter();
|
|
127
|
+
const aui = renderWithRuntime(adapter, true);
|
|
128
|
+
|
|
129
|
+
await send(aui, "first");
|
|
130
|
+
expect(getRunCount()).toBe(1);
|
|
131
|
+
|
|
132
|
+
await act(async () => {
|
|
133
|
+
aui.thread.composer().setText("behind");
|
|
134
|
+
aui.thread.composer().send({ steer: false });
|
|
135
|
+
await flush();
|
|
136
|
+
});
|
|
137
|
+
await send(aui, "next");
|
|
138
|
+
expect(
|
|
139
|
+
aui.thread
|
|
140
|
+
.composer()
|
|
141
|
+
.getState()
|
|
142
|
+
.queue.map((q) => q.prompt),
|
|
143
|
+
).toEqual(["next", "behind"]);
|
|
144
|
+
|
|
145
|
+
await act(async () => {
|
|
146
|
+
releases[0]!();
|
|
147
|
+
await flush();
|
|
148
|
+
await flush();
|
|
149
|
+
});
|
|
150
|
+
expect(getRunCount()).toBe(2);
|
|
151
|
+
expect(userTexts(aui)).toEqual(["first", "next"]);
|
|
152
|
+
|
|
153
|
+
await act(async () => {
|
|
154
|
+
releases[1]!();
|
|
155
|
+
await flush();
|
|
156
|
+
await flush();
|
|
157
|
+
});
|
|
158
|
+
expect(getRunCount()).toBe(3);
|
|
159
|
+
expect(userTexts(aui)).toEqual(["first", "next", "behind"]);
|
|
160
|
+
});
|
|
161
|
+
|
|
117
162
|
it("queueItem(index).remove() drops a queued message", async () => {
|
|
118
163
|
const { adapter } = createCountingAdapter();
|
|
119
164
|
const aui = renderWithRuntime(adapter, true);
|
|
@@ -132,7 +177,7 @@ describe("local runtime message queue", () => {
|
|
|
132
177
|
expect(queue[0]!.prompt).toBe("b");
|
|
133
178
|
});
|
|
134
179
|
|
|
135
|
-
it("clears
|
|
180
|
+
it("clears queued items when the user cancels the run", async () => {
|
|
136
181
|
const { adapter, getRunCount } = createCountingAdapter();
|
|
137
182
|
const aui = renderWithRuntime(adapter, true);
|
|
138
183
|
|
|
@@ -147,13 +192,50 @@ describe("local runtime message queue", () => {
|
|
|
147
192
|
await flush();
|
|
148
193
|
});
|
|
149
194
|
|
|
195
|
+
// Stop means stop: nothing pending, nothing dispatched
|
|
196
|
+
expect(getRunCount()).toBe(1);
|
|
150
197
|
expect(aui.thread.composer().getState().queue).toEqual([]);
|
|
151
|
-
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("keeps queued items on cancel when unstable_queueClearOnCancel is false", async () => {
|
|
201
|
+
const { adapter, getRunCount } = createCountingAdapter();
|
|
202
|
+
const aui = renderWithRuntime(adapter, true, {
|
|
203
|
+
unstable_queueClearOnCancel: false,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await send(aui, "first");
|
|
207
|
+
await send(aui, "a");
|
|
208
|
+
await send(aui, "b");
|
|
209
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(2);
|
|
210
|
+
|
|
211
|
+
await act(async () => {
|
|
212
|
+
aui.thread.cancelRun();
|
|
213
|
+
await flush();
|
|
214
|
+
await flush();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// cancel pauses the queue: items survive, nothing auto-dispatches
|
|
152
218
|
expect(getRunCount()).toBe(1);
|
|
219
|
+
expect(
|
|
220
|
+
aui.thread
|
|
221
|
+
.composer()
|
|
222
|
+
.getState()
|
|
223
|
+
.queue.map((q) => q.prompt),
|
|
224
|
+
).toEqual(["a", "b"]);
|
|
225
|
+
|
|
226
|
+
// the next explicit send drains the head
|
|
227
|
+
await send(aui, "c");
|
|
228
|
+
expect(getRunCount()).toBe(2);
|
|
229
|
+
expect(
|
|
230
|
+
aui.thread
|
|
231
|
+
.composer()
|
|
232
|
+
.getState()
|
|
233
|
+
.queue.map((q) => q.prompt),
|
|
234
|
+
).toEqual(["b", "c"]);
|
|
153
235
|
});
|
|
154
236
|
|
|
155
|
-
it("applies an edit instead of queuing it
|
|
156
|
-
const { adapter, releases } = createCountingAdapter();
|
|
237
|
+
it("applies an edit instead of queuing it and clears the pending items", async () => {
|
|
238
|
+
const { adapter, releases, getRunCount } = createCountingAdapter();
|
|
157
239
|
const aui = renderWithRuntime(adapter, true);
|
|
158
240
|
|
|
159
241
|
await send(aui, "first");
|
|
@@ -174,10 +256,52 @@ describe("local runtime message queue", () => {
|
|
|
174
256
|
message.composer().setText("edited");
|
|
175
257
|
message.composer().send();
|
|
176
258
|
await flush();
|
|
259
|
+
await flush();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// the edit is applied (branches the thread); the rewind clears the queue,
|
|
263
|
+
// so nothing pending dispatches against the new branch
|
|
264
|
+
expect(aui.thread.composer().getState().queue).toEqual([]);
|
|
265
|
+
expect(getRunCount()).toBe(3); // first, second, edit — not "queued"
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("keeps the queue across an edit when unstable_queueClearOnRewind is false", async () => {
|
|
269
|
+
const { adapter, releases, getRunCount } = createCountingAdapter();
|
|
270
|
+
const aui = renderWithRuntime(adapter, true, {
|
|
271
|
+
unstable_queueClearOnRewind: false,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
await send(aui, "first");
|
|
275
|
+
await act(async () => {
|
|
276
|
+
releases[0]!();
|
|
277
|
+
await flush();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
await send(aui, "second");
|
|
281
|
+
await send(aui, "queued");
|
|
282
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(1);
|
|
283
|
+
|
|
284
|
+
await act(async () => {
|
|
285
|
+
const message = aui.thread.message({ index: 0 });
|
|
286
|
+
message.composer().beginEdit();
|
|
287
|
+
message.composer().setText("edited");
|
|
288
|
+
message.composer().send();
|
|
289
|
+
await flush();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// the edit's rerun survives; the queued item waits for it to settle
|
|
293
|
+
expect(getRunCount()).toBe(3);
|
|
294
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(1);
|
|
295
|
+
|
|
296
|
+
await act(async () => {
|
|
297
|
+
releases[2]!();
|
|
298
|
+
await flush();
|
|
177
299
|
});
|
|
178
300
|
|
|
179
|
-
// the
|
|
301
|
+
// the surviving queue drains after the edit's run completes
|
|
302
|
+
expect(getRunCount()).toBe(4);
|
|
180
303
|
expect(aui.thread.composer().getState().queue).toEqual([]);
|
|
304
|
+
expect(userTexts(aui)).toEqual(["edited", "queued"]);
|
|
181
305
|
});
|
|
182
306
|
|
|
183
307
|
it("buffers a send during a regenerate instead of interrupting it", async () => {
|
|
@@ -214,6 +338,75 @@ describe("local runtime message queue", () => {
|
|
|
214
338
|
).toEqual(["Y"]);
|
|
215
339
|
});
|
|
216
340
|
|
|
341
|
+
it("drains the pending head after a regenerate started inside the cancellation window", async () => {
|
|
342
|
+
const { adapter, releases, getRunCount } = createCountingAdapter();
|
|
343
|
+
const aui = renderWithRuntime(adapter, true, {
|
|
344
|
+
unstable_queueClearOnCancel: false,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
await send(aui, "first");
|
|
348
|
+
await act(async () => {
|
|
349
|
+
releases[0]!();
|
|
350
|
+
await flush();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// regenerate, buffer a send, then cancel and regenerate again before
|
|
354
|
+
// the cancelled settle lands
|
|
355
|
+
await act(async () => {
|
|
356
|
+
aui.thread.message({ index: 1 }).reload();
|
|
357
|
+
await flush();
|
|
358
|
+
});
|
|
359
|
+
await send(aui, "pending");
|
|
360
|
+
await act(async () => {
|
|
361
|
+
aui.thread.cancelRun();
|
|
362
|
+
aui.thread.message({ index: 1 }).reload();
|
|
363
|
+
await flush();
|
|
364
|
+
});
|
|
365
|
+
expect(getRunCount()).toBe(3);
|
|
366
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(1);
|
|
367
|
+
|
|
368
|
+
// the cancelled settle must not eat the replacement's: once the
|
|
369
|
+
// replacement settles, the pending head drains exactly once
|
|
370
|
+
await act(async () => {
|
|
371
|
+
releases[2]!();
|
|
372
|
+
await flush();
|
|
373
|
+
});
|
|
374
|
+
expect(getRunCount()).toBe(4);
|
|
375
|
+
expect(aui.thread.composer().getState().queue).toEqual([]);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("keeps a second regenerate alive when the first one's settle arrives", async () => {
|
|
379
|
+
const { adapter, releases, getRunCount } = createCountingAdapter();
|
|
380
|
+
const aui = renderWithRuntime(adapter, true);
|
|
381
|
+
|
|
382
|
+
await send(aui, "first");
|
|
383
|
+
await act(async () => {
|
|
384
|
+
releases[0]!();
|
|
385
|
+
await flush();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
await act(async () => {
|
|
389
|
+
aui.thread.message({ index: 1 }).reload();
|
|
390
|
+
await flush();
|
|
391
|
+
});
|
|
392
|
+
await send(aui, "Y");
|
|
393
|
+
|
|
394
|
+
// the second regenerate aborts the first; its settle must not dispatch
|
|
395
|
+
await act(async () => {
|
|
396
|
+
aui.thread.message({ index: 1 }).reload();
|
|
397
|
+
await flush();
|
|
398
|
+
});
|
|
399
|
+
expect(getRunCount()).toBe(3);
|
|
400
|
+
expect(aui.thread.composer().getState().queue).toHaveLength(1);
|
|
401
|
+
|
|
402
|
+
await act(async () => {
|
|
403
|
+
releases[2]!();
|
|
404
|
+
await flush();
|
|
405
|
+
});
|
|
406
|
+
expect(getRunCount()).toBe(4);
|
|
407
|
+
expect(aui.thread.composer().getState().queue).toEqual([]);
|
|
408
|
+
});
|
|
409
|
+
|
|
217
410
|
it("advances exactly once after a failed run, without deadlocking", async () => {
|
|
218
411
|
const releases: Array<() => void> = [];
|
|
219
412
|
let runCount = 0;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
|
+
import { createActionButton } from "./createActionButton";
|
|
4
|
+
|
|
5
|
+
const TestButton = createActionButton("TestButton", () => () => {});
|
|
6
|
+
|
|
7
|
+
describe("createActionButton", () => {
|
|
8
|
+
it("defaults to type=button", () => {
|
|
9
|
+
const html = renderToStaticMarkup(<TestButton />);
|
|
10
|
+
expect(html).toContain('type="button"');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("allows the caller to override type", () => {
|
|
14
|
+
const html = renderToStaticMarkup(<TestButton type="submit" />);
|
|
15
|
+
expect(html).toContain('type="submit"');
|
|
16
|
+
expect(html).not.toContain('type="button"');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -41,8 +41,8 @@ export const createActionButton = <TProps,>(
|
|
|
41
41
|
const callback = useActionButton(forwardedProps as TProps) ?? undefined;
|
|
42
42
|
return (
|
|
43
43
|
<Primitive.button
|
|
44
|
-
{...primitiveProps}
|
|
45
44
|
type="button"
|
|
45
|
+
{...primitiveProps}
|
|
46
46
|
ref={forwardedRef}
|
|
47
47
|
disabled={primitiveProps.disabled || !callback}
|
|
48
48
|
onClick={composeEventHandlers(primitiveProps.onClick, callback)}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 AgentbaseAI Inc.
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|