@assistant-ui/react 0.15.2 → 0.15.3

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 (42) hide show
  1. package/dist/client/ExternalThread.d.ts.map +1 -1
  2. package/dist/client/ExternalThread.js +1 -0
  3. package/dist/client/ExternalThread.js.map +1 -1
  4. package/dist/client/InMemoryThreadList.d.ts.map +1 -1
  5. package/dist/client/InMemoryThreadList.js +199 -176
  6. package/dist/client/InMemoryThreadList.js.map +1 -1
  7. package/dist/client/SingleThreadList.d.ts.map +1 -1
  8. package/dist/client/SingleThreadList.js +56 -48
  9. package/dist/client/SingleThreadList.js.map +1 -1
  10. package/dist/legacy-runtime/cloud/auiV0.d.ts +2 -0
  11. package/dist/legacy-runtime/cloud/auiV0.d.ts.map +1 -1
  12. package/dist/legacy-runtime/cloud/auiV0.js +6 -4
  13. package/dist/legacy-runtime/cloud/auiV0.js.map +1 -1
  14. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  15. package/dist/mcp-apps/McpAppRenderer.js +25 -14
  16. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  17. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  18. package/dist/mcp-apps/McpAppsRemoteHost.js +17 -6
  19. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  20. package/dist/primitives/message/MessagePartsGrouped.d.ts +6 -1
  21. package/dist/primitives/message/MessagePartsGrouped.d.ts.map +1 -1
  22. package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
  23. package/dist/primitives/suggestion/SuggestionTrigger.d.ts +4 -2
  24. package/dist/primitives/suggestion/SuggestionTrigger.d.ts.map +1 -1
  25. package/dist/primitives/suggestion/SuggestionTrigger.js +21 -42
  26. package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
  27. package/dist/primitives/thread/ThreadSuggestion.d.ts +4 -2
  28. package/dist/primitives/thread/ThreadSuggestion.d.ts.map +1 -1
  29. package/dist/primitives/thread/ThreadSuggestion.js.map +1 -1
  30. package/package.json +5 -5
  31. package/src/client/ExternalThread.ts +1 -0
  32. package/src/client/InMemoryThreadList.ts +27 -7
  33. package/src/client/SingleThreadList.ts +10 -2
  34. package/src/legacy-runtime/cloud/auiV0.ts +8 -2
  35. package/src/mcp-apps/McpAppRenderer.test.tsx +262 -3
  36. package/src/mcp-apps/McpAppRenderer.tsx +27 -15
  37. package/src/mcp-apps/McpAppsRemoteHost.ts +18 -9
  38. package/src/primitives/message/MessagePartsGrouped.tsx +6 -1
  39. package/src/primitives/suggestion/SuggestionTrigger.ts +11 -32
  40. package/src/primitives/thread/ThreadSuggestion.ts +2 -1
  41. package/src/tests/RemoteThreadListRuntime.reloadMainThread.test.tsx +115 -0
  42. package/src/tests/threadListItemIsRunning.test.tsx +198 -0
@@ -0,0 +1,198 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, render, screen, waitFor } from "@testing-library/react";
4
+ import { type FC, useEffect, useReducer } from "react";
5
+ import { describe, expect, it } from "vitest";
6
+ import { useRemoteThreadListRuntime } from "@assistant-ui/core/react";
7
+ import { makeAdapter } from "./remote-thread-list-test-helpers";
8
+ import { AssistantRuntimeProvider } from "../context";
9
+ import * as ThreadListPrimitive from "../primitives/threadList";
10
+ import {
11
+ useAui,
12
+ useAuiState,
13
+ type AssistantRuntime,
14
+ type ChatModelAdapter,
15
+ } from "../index";
16
+ import { useExternalStoreRuntime } from "../legacy-runtime/runtime-cores/external-store/useExternalStoreRuntime";
17
+ import { useLocalRuntime } from "../legacy-runtime/runtime-cores/local/useLocalRuntime";
18
+
19
+ const Probe: FC = () => {
20
+ const id = useAuiState((s) => s.threadListItem.id);
21
+ const isRunning = useAuiState((s) => s.threadListItem.isRunning);
22
+ return (
23
+ <span data-testid={`item-${id}`}>{isRunning ? "running" : "idle"}</span>
24
+ );
25
+ };
26
+
27
+ const Items: FC = () => (
28
+ <ThreadListPrimitive.Items components={{ ThreadListItem: Probe }} />
29
+ );
30
+
31
+ describe("threadListItem.isRunning", () => {
32
+ // LocalThreadListRuntimeCore reports no per-thread run state, so the item
33
+ // reaches its own run state only through the open thread.
34
+ it("tracks the open thread's run on a thread list with no per-thread capability", async () => {
35
+ let release!: () => void;
36
+ const adapter: ChatModelAdapter = {
37
+ async *run() {
38
+ await new Promise<void>((resolve) => {
39
+ release = resolve;
40
+ });
41
+ yield { content: [{ type: "text", text: "done" }] };
42
+ },
43
+ };
44
+
45
+ const capture: { runtime: AssistantRuntime | null } = { runtime: null };
46
+ const Inner: FC = () => {
47
+ const runtime = useLocalRuntime(adapter);
48
+ capture.runtime = runtime;
49
+ return (
50
+ <AssistantRuntimeProvider runtime={runtime}>
51
+ <Probe />
52
+ </AssistantRuntimeProvider>
53
+ );
54
+ };
55
+
56
+ await act(async () => {
57
+ render(<Inner />);
58
+ });
59
+ const testId = `item-${capture.runtime!.threads.mainItem.getState().id}`;
60
+ expect(screen.getByTestId(testId).textContent).toBe("idle");
61
+
62
+ await act(async () => {
63
+ capture.runtime!.thread.append({
64
+ role: "user",
65
+ content: [{ type: "text", text: "hi" }],
66
+ });
67
+ });
68
+ await waitFor(() =>
69
+ expect(screen.getByTestId(testId).textContent).toBe("running"),
70
+ );
71
+
72
+ await act(async () => {
73
+ release();
74
+ });
75
+ await waitFor(() =>
76
+ expect(screen.getByTestId(testId).textContent).toBe("idle"),
77
+ );
78
+ });
79
+
80
+ it("reports the run of a single-thread runtime that cannot observe background threads", async () => {
81
+ const Inner: FC<{ isRunning: boolean }> = ({ isRunning }) => {
82
+ const runtime = useExternalStoreRuntime({
83
+ messages: [],
84
+ isRunning,
85
+ onNew: async () => {},
86
+ });
87
+ return (
88
+ <AssistantRuntimeProvider runtime={runtime}>
89
+ <Items />
90
+ </AssistantRuntimeProvider>
91
+ );
92
+ };
93
+
94
+ const view = render(<Inner isRunning={false} />);
95
+ await waitFor(() =>
96
+ expect(screen.getByTestId("item-DEFAULT_THREAD_ID").textContent).toBe(
97
+ "idle",
98
+ ),
99
+ );
100
+
101
+ await act(async () => {
102
+ view.rerender(<Inner isRunning />);
103
+ });
104
+
105
+ expect(screen.getByTestId("item-DEFAULT_THREAD_ID").textContent).toBe(
106
+ "running",
107
+ );
108
+ });
109
+
110
+ it("keeps reporting a thread the user switched away from as running", async () => {
111
+ const running = new Map<string, boolean>();
112
+ const listeners = new Set<() => void>();
113
+ const setRunning = (threadId: string, value: boolean) => {
114
+ running.set(threadId, value);
115
+ for (const listener of listeners) listener();
116
+ };
117
+
118
+ const useTestRuntimeHook = () => {
119
+ const aui = useAui();
120
+ const threadId = aui.threadListItem.getState().id;
121
+ const [, forceUpdate] = useReducer((n: number) => n + 1, 0);
122
+ useEffect(() => {
123
+ listeners.add(forceUpdate);
124
+ return () => {
125
+ listeners.delete(forceUpdate);
126
+ };
127
+ }, []);
128
+
129
+ return useExternalStoreRuntime({
130
+ messages: [],
131
+ isRunning: running.get(threadId) ?? false,
132
+ onNew: async () => {},
133
+ });
134
+ };
135
+
136
+ const adapter = makeAdapter({
137
+ list: async () => ({
138
+ threads: [
139
+ {
140
+ status: "regular" as const,
141
+ remoteId: "t-1",
142
+ externalId: "t-1",
143
+ title: "First",
144
+ },
145
+ {
146
+ status: "regular" as const,
147
+ remoteId: "t-2",
148
+ externalId: "t-2",
149
+ title: "Second",
150
+ },
151
+ ],
152
+ }),
153
+ });
154
+
155
+ const capture: { runtime: AssistantRuntime | null } = { runtime: null };
156
+ const Inner: FC = () => {
157
+ const runtime = useRemoteThreadListRuntime({
158
+ runtimeHook: useTestRuntimeHook,
159
+ adapter,
160
+ });
161
+ capture.runtime = runtime;
162
+ return (
163
+ <AssistantRuntimeProvider runtime={runtime}>
164
+ <Items />
165
+ </AssistantRuntimeProvider>
166
+ );
167
+ };
168
+
169
+ await act(async () => {
170
+ render(<Inner />);
171
+ });
172
+ await waitFor(() => expect(screen.getByTestId("item-t-1")).toBeTruthy());
173
+
174
+ await act(async () => {
175
+ await capture.runtime!.threads.switchToThread("t-1");
176
+ });
177
+ await act(async () => {
178
+ setRunning("t-1", true);
179
+ });
180
+ await waitFor(() =>
181
+ expect(screen.getByTestId("item-t-1").textContent).toBe("running"),
182
+ );
183
+
184
+ await act(async () => {
185
+ await capture.runtime!.threads.switchToThread("t-2");
186
+ });
187
+
188
+ expect(screen.getByTestId("item-t-1").textContent).toBe("running");
189
+ expect(screen.getByTestId("item-t-2").textContent).toBe("idle");
190
+
191
+ await act(async () => {
192
+ setRunning("t-1", false);
193
+ });
194
+ await waitFor(() =>
195
+ expect(screen.getByTestId("item-t-1").textContent).toBe("idle"),
196
+ );
197
+ });
198
+ });