@assistant-ui/ai-sdk 0.0.8 → 0.0.9
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/LICENSE +1 -1
- package/dist/converters/convertMessage.d.ts +2 -0
- package/dist/converters/convertMessage.d.ts.map +1 -1
- package/dist/converters/convertMessage.js +14 -4
- package/dist/converters/convertMessage.js.map +1 -1
- package/dist/runtime/AISDKChat.d.ts.map +1 -1
- package/dist/runtime/AISDKChat.js.map +1 -1
- package/dist/runtime/sdkIdentity.js +1 -1
- package/dist/runtime/useAISDKRuntime.d.ts +8 -1
- package/dist/runtime/useAISDKRuntime.d.ts.map +1 -1
- package/dist/runtime/useAISDKRuntime.js +212 -34
- package/dist/runtime/useAISDKRuntime.js.map +1 -1
- package/dist/runtime/useChatRuntime.js +1 -1
- package/dist/runtime/useChatThread.d.ts.map +1 -1
- package/dist/runtime/useChatThread.js +40 -10
- package/dist/runtime/useChatThread.js.map +1 -1
- package/dist/runtime/useExternalHistory.d.ts.map +1 -1
- package/dist/runtime/useExternalHistory.js +2 -1
- package/dist/runtime/useExternalHistory.js.map +1 -1
- package/dist/transport/AssistantChatTransport.d.ts.map +1 -1
- package/dist/transport/AssistantChatTransport.js +9 -2
- package/dist/transport/AssistantChatTransport.js.map +1 -1
- package/package.json +10 -7
- package/src/converters/convertMessage.test.ts +63 -0
- package/src/converters/convertMessage.ts +24 -2
- package/src/runtime/AISDKChat.ts +0 -4
- package/src/runtime/AISDKThreads.test.ts +26 -0
- package/src/runtime/useAISDKRuntime.approval.integration.test.tsx +1588 -19
- package/src/runtime/useAISDKRuntime.approval.test.tsx +27 -0
- package/src/runtime/useAISDKRuntime.fast-refresh.test.tsx +182 -0
- package/src/runtime/useAISDKRuntime.ts +397 -28
- package/src/runtime/useChatRuntime.fast-refresh.test.tsx +94 -0
- package/src/runtime/useChatRuntime.integration.test.tsx +134 -112
- package/src/runtime/useChatRuntime.test.ts +7 -7
- package/src/runtime/useChatThread.test.ts +166 -2
- package/src/runtime/useChatThread.transport.test.tsx +5 -2
- package/src/runtime/useChatThread.ts +56 -17
- package/src/runtime/useExternalHistory.ts +12 -1
- package/src/transport/AssistantChatTransport.test.ts +164 -0
- package/src/transport/AssistantChatTransport.ts +22 -2
|
@@ -470,6 +470,33 @@ describe("useAISDKRuntime tool approvals", () => {
|
|
|
470
470
|
expect(onRespondToToolApproval).toHaveBeenCalledTimes(2);
|
|
471
471
|
});
|
|
472
472
|
|
|
473
|
+
it("answers through the host past an earlier approval-requested part without an approval", async () => {
|
|
474
|
+
const onRespondToToolApproval = vi.fn(async () => {});
|
|
475
|
+
const { respond, messages } = setupPendingApproval(onRespondToToolApproval);
|
|
476
|
+
messages.unshift({
|
|
477
|
+
id: "message-0",
|
|
478
|
+
role: "assistant",
|
|
479
|
+
parts: [
|
|
480
|
+
{
|
|
481
|
+
type: "tool-legacy",
|
|
482
|
+
toolCallId: "tool-0",
|
|
483
|
+
state: "approval-requested",
|
|
484
|
+
input: {},
|
|
485
|
+
} as (typeof messages)[number]["parts"][number],
|
|
486
|
+
],
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
await act(async () => {
|
|
490
|
+
await respond({ approvalId: "approval-1", approved: true });
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
expect(onRespondToToolApproval).toHaveBeenCalledOnce();
|
|
494
|
+
expect(onRespondToToolApproval).toHaveBeenCalledWith(
|
|
495
|
+
expect.objectContaining({ approvalId: "approval-1" }),
|
|
496
|
+
expect.objectContaining({ toolCallId: "tool-1" }),
|
|
497
|
+
);
|
|
498
|
+
});
|
|
499
|
+
|
|
473
500
|
it("rejects an approval that is not waiting for a response", async () => {
|
|
474
501
|
const onRespondToToolApproval = vi.fn();
|
|
475
502
|
const { respond } = setupPendingApproval(onRespondToToolApproval);
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { Activity, act, version } from "react";
|
|
4
|
+
import type { UIMessage } from "ai";
|
|
5
|
+
import type { SuggestionAdapter } from "@assistant-ui/core";
|
|
6
|
+
import { afterAll, afterEach, expect, it, vi } from "vitest";
|
|
7
|
+
|
|
8
|
+
const onReact18 = version.startsWith("18.");
|
|
9
|
+
|
|
10
|
+
type Family = { current: unknown };
|
|
11
|
+
type RendererInternals = {
|
|
12
|
+
setRefreshHandler: (resolve: (type: unknown) => Family | undefined) => void;
|
|
13
|
+
scheduleRefresh: (
|
|
14
|
+
root: unknown,
|
|
15
|
+
update: { staleFamilies: Set<Family>; updatedFamilies: Set<Family> },
|
|
16
|
+
) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let renderer: RendererInternals | undefined;
|
|
20
|
+
const fiberRoots = new Set<unknown>();
|
|
21
|
+
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
|
|
22
|
+
vi.stubGlobal("__REACT_DEVTOOLS_GLOBAL_HOOK__", {
|
|
23
|
+
supportsFiber: true,
|
|
24
|
+
inject: (internals: RendererInternals) => {
|
|
25
|
+
renderer = internals;
|
|
26
|
+
return 1;
|
|
27
|
+
},
|
|
28
|
+
onScheduleFiberRoot: () => {},
|
|
29
|
+
onCommitFiberRoot: (_id: number, root: unknown) => fiberRoots.add(root),
|
|
30
|
+
onCommitFiberUnmount: () => {},
|
|
31
|
+
});
|
|
32
|
+
const { cleanup, render, waitFor } = await import("@testing-library/react");
|
|
33
|
+
const { useAISDKRuntime } = await import("./useAISDKRuntime");
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
cleanup();
|
|
37
|
+
renderer!.setRefreshHandler(() => undefined);
|
|
38
|
+
fiberRoots.clear();
|
|
39
|
+
});
|
|
40
|
+
afterAll(() => vi.unstubAllGlobals());
|
|
41
|
+
|
|
42
|
+
const refresh = async (Before: unknown, After: unknown) => {
|
|
43
|
+
const family: Family = { current: After };
|
|
44
|
+
renderer!.setRefreshHandler((type) =>
|
|
45
|
+
type === Before || type === After ? family : undefined,
|
|
46
|
+
);
|
|
47
|
+
await act(async () => {
|
|
48
|
+
for (const root of fiberRoots) {
|
|
49
|
+
renderer!.scheduleRefresh(root, {
|
|
50
|
+
staleFamilies: new Set(),
|
|
51
|
+
updatedFamilies: new Set([family]),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
await act(async () => {});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const createChat = () => ({
|
|
59
|
+
id: "chat-1",
|
|
60
|
+
status: "submitted",
|
|
61
|
+
error: null,
|
|
62
|
+
messages: [
|
|
63
|
+
{ id: "u1", role: "user", parts: [{ type: "text", text: "hi" }] },
|
|
64
|
+
] as UIMessage[],
|
|
65
|
+
setMessages: vi.fn(),
|
|
66
|
+
sendMessage: vi.fn(async () => {}),
|
|
67
|
+
regenerate: vi.fn(async () => {}),
|
|
68
|
+
addToolResult: vi.fn(),
|
|
69
|
+
addToolOutput: vi.fn(),
|
|
70
|
+
stop: vi.fn(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const settleChat = (chat: ReturnType<typeof createChat>) => {
|
|
74
|
+
chat.status = "ready";
|
|
75
|
+
chat.messages = [
|
|
76
|
+
...chat.messages,
|
|
77
|
+
{ id: "a1", role: "assistant", parts: [{ type: "text", text: "hello" }] },
|
|
78
|
+
];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const makeGeneration = () => {
|
|
82
|
+
let resolve!: (value: readonly { prompt: string }[]) => void;
|
|
83
|
+
const generate = vi.fn(
|
|
84
|
+
(_input: Parameters<SuggestionAdapter["generate"]>[0]) =>
|
|
85
|
+
new Promise<readonly { prompt: string }[]>((done) => {
|
|
86
|
+
resolve = done;
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
return {
|
|
90
|
+
generate,
|
|
91
|
+
finish: (value: readonly { prompt: string }[]) => resolve(value),
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
it("completes AI SDK suggestion generation after Fast Refresh", async () => {
|
|
96
|
+
const chat = createChat();
|
|
97
|
+
const { generate, finish } = makeGeneration();
|
|
98
|
+
let runtime!: ReturnType<typeof useAISDKRuntime>;
|
|
99
|
+
let rendered = "";
|
|
100
|
+
const Before = () => {
|
|
101
|
+
rendered = "before";
|
|
102
|
+
runtime = useAISDKRuntime(chat as never, {
|
|
103
|
+
adapters: { suggestion: { generate } },
|
|
104
|
+
});
|
|
105
|
+
return null;
|
|
106
|
+
};
|
|
107
|
+
const After = () => {
|
|
108
|
+
rendered = "after";
|
|
109
|
+
runtime = useAISDKRuntime(chat as never, {
|
|
110
|
+
adapters: { suggestion: { generate } },
|
|
111
|
+
});
|
|
112
|
+
return null;
|
|
113
|
+
};
|
|
114
|
+
const view = render(<Before />);
|
|
115
|
+
settleChat(chat);
|
|
116
|
+
view.rerender(<Before />);
|
|
117
|
+
await waitFor(() => expect(generate).toHaveBeenCalledTimes(1));
|
|
118
|
+
const signal = generate.mock.calls[0]![0].signal!;
|
|
119
|
+
|
|
120
|
+
await refresh(Before, After);
|
|
121
|
+
expect(rendered).toBe("after");
|
|
122
|
+
expect(signal.aborted).toBe(false);
|
|
123
|
+
await act(async () => finish([{ prompt: "next" }]));
|
|
124
|
+
await waitFor(() =>
|
|
125
|
+
expect(runtime.thread.getState().suggestions).toEqual([{ prompt: "next" }]),
|
|
126
|
+
);
|
|
127
|
+
view.unmount();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("aborts pending AI SDK suggestion generation on unmount", async () => {
|
|
131
|
+
const chat = createChat();
|
|
132
|
+
const { generate } = makeGeneration();
|
|
133
|
+
const Host = () => {
|
|
134
|
+
useAISDKRuntime(chat as never, { adapters: { suggestion: { generate } } });
|
|
135
|
+
return null;
|
|
136
|
+
};
|
|
137
|
+
const view = render(<Host />);
|
|
138
|
+
settleChat(chat);
|
|
139
|
+
view.rerender(<Host />);
|
|
140
|
+
await waitFor(() => expect(generate).toHaveBeenCalledTimes(1));
|
|
141
|
+
const signal = generate.mock.calls[0]![0].signal!;
|
|
142
|
+
|
|
143
|
+
view.unmount();
|
|
144
|
+
await act(async () => {});
|
|
145
|
+
expect(signal.aborted).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Activity is React 19 only.
|
|
149
|
+
it.skipIf(onReact18)(
|
|
150
|
+
"aborts pending AI SDK suggestion generation when Activity hides",
|
|
151
|
+
async () => {
|
|
152
|
+
const chat = createChat();
|
|
153
|
+
const { generate } = makeGeneration();
|
|
154
|
+
const Host = () => {
|
|
155
|
+
useAISDKRuntime(chat as never, {
|
|
156
|
+
adapters: { suggestion: { generate } },
|
|
157
|
+
});
|
|
158
|
+
return null;
|
|
159
|
+
};
|
|
160
|
+
const view = render(
|
|
161
|
+
<Activity mode="visible">
|
|
162
|
+
<Host />
|
|
163
|
+
</Activity>,
|
|
164
|
+
);
|
|
165
|
+
settleChat(chat);
|
|
166
|
+
view.rerender(
|
|
167
|
+
<Activity mode="visible">
|
|
168
|
+
<Host />
|
|
169
|
+
</Activity>,
|
|
170
|
+
);
|
|
171
|
+
await waitFor(() => expect(generate).toHaveBeenCalledTimes(1));
|
|
172
|
+
const signal = generate.mock.calls[0]![0].signal!;
|
|
173
|
+
|
|
174
|
+
view.rerender(
|
|
175
|
+
<Activity mode="hidden">
|
|
176
|
+
<Host />
|
|
177
|
+
</Activity>,
|
|
178
|
+
);
|
|
179
|
+
await act(async () => {});
|
|
180
|
+
expect(signal.aborted).toBe(true);
|
|
181
|
+
},
|
|
182
|
+
);
|