@assistant-ui/react-ag-ui 0.1.4 → 0.1.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/runtime/AgUiThreadController.d.ts +2 -0
- package/dist/runtime/AgUiThreadController.d.ts.map +1 -1
- package/dist/runtime/AgUiThreadController.js +93 -13
- package/dist/runtime/AgUiThreadController.js.map +1 -1
- package/dist/runtime/adapter/run-aggregator.d.ts +37 -31
- package/dist/runtime/adapter/run-aggregator.d.ts.map +1 -1
- package/dist/runtime/adapter/run-aggregator.js +363 -218
- package/dist/runtime/adapter/run-aggregator.js.map +1 -1
- package/dist/useAgUiRuntime.d.ts.map +1 -1
- package/dist/useAgUiRuntime.js +1 -0
- package/dist/useAgUiRuntime.js.map +1 -1
- package/package.json +2 -2
- package/src/runtime/AgUiThreadController.disconnect.test.ts +198 -3
- package/src/runtime/AgUiThreadController.replay.test.ts +57 -0
- package/src/runtime/AgUiThreadController.snapshot-head-history.test.ts +34 -2
- package/src/runtime/AgUiThreadController.snapshot-head-move.test.ts +510 -0
- package/src/runtime/AgUiThreadController.ts +190 -28
- package/src/runtime/adapter/run-aggregator.routing.test.ts +359 -0
- package/src/runtime/adapter/run-aggregator.ts +539 -258
- package/src/useAgUiRuntime.cloud-run-leases.test.tsx +85 -7
- package/src/useAgUiRuntime.ts +1 -0
|
@@ -4,11 +4,13 @@ import { act, cleanup, render, waitFor } from "@testing-library/react";
|
|
|
4
4
|
import { HttpAgent, type RunAgentInput } from "@ag-ui/client";
|
|
5
5
|
import {
|
|
6
6
|
AssistantRuntimeProvider,
|
|
7
|
+
MessageByIndexProvider,
|
|
8
|
+
useActionBarResume,
|
|
7
9
|
useCloudThreadListAdapter,
|
|
8
10
|
useRemoteThreadListRuntime,
|
|
9
11
|
} from "@assistant-ui/core/react";
|
|
10
12
|
import type { AssistantRuntime } from "@assistant-ui/core";
|
|
11
|
-
import { useAui, type AssistantClient } from "@assistant-ui/store";
|
|
13
|
+
import { useAui, useAuiState, type AssistantClient } from "@assistant-ui/store";
|
|
12
14
|
import { AssistantCloud } from "assistant-cloud";
|
|
13
15
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
16
|
import { useAgUiRuntime } from "./useAgUiRuntime";
|
|
@@ -107,6 +109,13 @@ const createServer = (
|
|
|
107
109
|
if (row) row.content = body.content;
|
|
108
110
|
return json({});
|
|
109
111
|
}
|
|
112
|
+
if (method === "DELETE" && path === "/threads/remote-1/messages") {
|
|
113
|
+
for (const id of body.message_ids as string[]) {
|
|
114
|
+
const index = rows.findIndex((r) => r.id === id);
|
|
115
|
+
if (index !== -1) rows.splice(index, 1);
|
|
116
|
+
}
|
|
117
|
+
return json({});
|
|
118
|
+
}
|
|
110
119
|
if (/^\/threads\/remote-1\/runs\/[^/]+\/heartbeat$/.test(path))
|
|
111
120
|
return json({ heartbeat_interval_ms: heartbeatIntervalMs });
|
|
112
121
|
if (/^\/threads\/remote-1\/runs\/[^/]+\/end$/.test(path)) return json({});
|
|
@@ -192,6 +201,20 @@ const renderApp = async (staleAfter?: number) => {
|
|
|
192
201
|
aui = useAui();
|
|
193
202
|
return null;
|
|
194
203
|
};
|
|
204
|
+
const resume: { current: ReturnType<typeof useActionBarResume> | undefined } =
|
|
205
|
+
{ current: undefined };
|
|
206
|
+
const CaptureResume = () => {
|
|
207
|
+
resume.current = useActionBarResume();
|
|
208
|
+
return null;
|
|
209
|
+
};
|
|
210
|
+
const ReplyResume = () => {
|
|
211
|
+
const hasReply = useAuiState((s) => s.thread.messages.length >= 2);
|
|
212
|
+
return hasReply ? (
|
|
213
|
+
<MessageByIndexProvider index={1}>
|
|
214
|
+
<CaptureResume />
|
|
215
|
+
</MessageByIndexProvider>
|
|
216
|
+
) : null;
|
|
217
|
+
};
|
|
195
218
|
const App = () => {
|
|
196
219
|
const adapter = useCloudThreadListAdapter({
|
|
197
220
|
cloud,
|
|
@@ -206,6 +229,7 @@ const renderApp = async (staleAfter?: number) => {
|
|
|
206
229
|
return (
|
|
207
230
|
<AssistantRuntimeProvider runtime={runtime}>
|
|
208
231
|
<Capture />
|
|
232
|
+
<ReplyResume />
|
|
209
233
|
</AssistantRuntimeProvider>
|
|
210
234
|
);
|
|
211
235
|
};
|
|
@@ -213,19 +237,25 @@ const renderApp = async (staleAfter?: number) => {
|
|
|
213
237
|
await act(async () => {
|
|
214
238
|
await aui.threads.getLoadThreadsPromise();
|
|
215
239
|
});
|
|
216
|
-
return {
|
|
240
|
+
return {
|
|
241
|
+
agent,
|
|
242
|
+
getAui: () => aui,
|
|
243
|
+
getRuntime: () => runtime,
|
|
244
|
+
getResume: () => resume.current,
|
|
245
|
+
};
|
|
217
246
|
};
|
|
218
247
|
|
|
219
248
|
const assistantRow = (
|
|
220
249
|
last_heartbeat_ms_ago: number,
|
|
221
250
|
status: "running" | "ended" = "running",
|
|
251
|
+
content: readonly unknown[] = [{ type: "text", text: "half an answer" }],
|
|
222
252
|
): Row => ({
|
|
223
253
|
id: "a1",
|
|
224
254
|
parent_id: "u1",
|
|
225
255
|
format: "aui/v0",
|
|
226
256
|
content: {
|
|
227
257
|
role: "assistant",
|
|
228
|
-
content: [
|
|
258
|
+
content: [...content],
|
|
229
259
|
status: { type: "incomplete", reason: "cancelled" },
|
|
230
260
|
},
|
|
231
261
|
external_id: "a1",
|
|
@@ -304,7 +334,50 @@ describe("useAgUiRuntime with Assistant Cloud run leases", () => {
|
|
|
304
334
|
expect(reply.id).toBe("agent-msg-1");
|
|
305
335
|
});
|
|
306
336
|
|
|
307
|
-
it("shows a reloaded run quiet for longer than staleAfter as
|
|
337
|
+
it("shows a reloaded empty run quiet for longer than staleAfter as stale; Resume deletes it and runs the agent again from the user message with no sibling", async () => {
|
|
338
|
+
useServer([userRow, assistantRow(45_000, "running", [])]);
|
|
339
|
+
const { agent, getAui, getRuntime, getResume } = await renderApp();
|
|
340
|
+
await act(async () => {
|
|
341
|
+
await getRuntime().threads.switchToThread("remote-1");
|
|
342
|
+
});
|
|
343
|
+
await waitFor(() =>
|
|
344
|
+
expect(getAui().thread.message({ index: 1 }).getState().runState).toBe(
|
|
345
|
+
"stale",
|
|
346
|
+
),
|
|
347
|
+
);
|
|
348
|
+
await waitFor(() => expect(getResume()?.disabled).toBe(false));
|
|
349
|
+
|
|
350
|
+
await act(async () => {
|
|
351
|
+
getResume()!.resume();
|
|
352
|
+
});
|
|
353
|
+
await waitFor(() => expect(agent.runs).toHaveLength(1));
|
|
354
|
+
await waitFor(() =>
|
|
355
|
+
expect(createsWith(server.calls, true)).toHaveLength(1),
|
|
356
|
+
);
|
|
357
|
+
expect(createsWith(server.calls, true)[0]!.body).toMatchObject({
|
|
358
|
+
parent_id: "u1",
|
|
359
|
+
open_run: true,
|
|
360
|
+
});
|
|
361
|
+
await act(async () => {
|
|
362
|
+
agent.runs[0]!.finish();
|
|
363
|
+
});
|
|
364
|
+
await waitFor(() =>
|
|
365
|
+
expect(
|
|
366
|
+
server.calls.some((c) => c.path.endsWith("/runs/lease_1/end")),
|
|
367
|
+
).toBe(true),
|
|
368
|
+
);
|
|
369
|
+
const deletes = server.calls.filter((c) => c.method === "DELETE");
|
|
370
|
+
expect(deletes.map((c) => c.body)).toEqual([{ message_ids: ["a1"] }]);
|
|
371
|
+
expect(server.rows.map((r) => r.id)).toEqual(["u1", "msg_1"]);
|
|
372
|
+
const messages = getAui().thread.getState().messages;
|
|
373
|
+
expect(messages.map((m) => m.role)).toEqual(["user", "assistant"]);
|
|
374
|
+
expect(messages[1]!.id).not.toBe("a1");
|
|
375
|
+
expect(getAui().thread.message({ index: 1 }).getState().branchCount).toBe(
|
|
376
|
+
1,
|
|
377
|
+
);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("shows a reloaded run quiet for longer than staleAfter as interrupted; Regenerate posts a sibling with its own lease and runs the agent again", async () => {
|
|
308
381
|
useServer([userRow, assistantRow(45_000)]);
|
|
309
382
|
const { agent, getAui, getRuntime } = await renderApp();
|
|
310
383
|
await act(async () => {
|
|
@@ -314,7 +387,12 @@ describe("useAgUiRuntime with Assistant Cloud run leases", () => {
|
|
|
314
387
|
expect(getAui().thread.getState().messages).toHaveLength(2),
|
|
315
388
|
);
|
|
316
389
|
const stale = getAui().thread.message({ index: 1 });
|
|
317
|
-
await waitFor(() =>
|
|
390
|
+
await waitFor(() =>
|
|
391
|
+
expect(stale.getState().status).toMatchObject({
|
|
392
|
+
error: { code: "run_stale" },
|
|
393
|
+
}),
|
|
394
|
+
);
|
|
395
|
+
expect(stale.getState().runState).toBeUndefined();
|
|
318
396
|
|
|
319
397
|
await act(async () => {
|
|
320
398
|
stale.reload();
|
|
@@ -363,8 +441,8 @@ describe("useAgUiRuntime with Assistant Cloud run leases", () => {
|
|
|
363
441
|
});
|
|
364
442
|
await waitFor(() =>
|
|
365
443
|
expect(
|
|
366
|
-
short.getAui().thread.message({ index: 1 }).getState().
|
|
367
|
-
).
|
|
444
|
+
short.getAui().thread.message({ index: 1 }).getState().status,
|
|
445
|
+
).toMatchObject({ error: { code: "run_stale" } }),
|
|
368
446
|
);
|
|
369
447
|
});
|
|
370
448
|
|
package/src/useAgUiRuntime.ts
CHANGED
|
@@ -345,6 +345,7 @@ export function useAgUiRuntime(
|
|
|
345
345
|
queueController?.clear();
|
|
346
346
|
return core.reload(parentId, config);
|
|
347
347
|
},
|
|
348
|
+
onDelete: (messageId: string) => core.deleteMessage(messageId),
|
|
348
349
|
onCancel: async () => {
|
|
349
350
|
queueController?.notifyCancelled();
|
|
350
351
|
core.cancel();
|