@assistant-ui/react-langchain 0.0.14 → 0.0.17

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 (66) hide show
  1. package/README.md +1 -1
  2. package/dist/convertMessages.d.ts +37 -3
  3. package/dist/convertMessages.d.ts.map +1 -1
  4. package/dist/convertMessages.js +63 -11
  5. package/dist/convertMessages.js.map +1 -1
  6. package/dist/findForkCheckpointInHistory.d.ts +26 -0
  7. package/dist/findForkCheckpointInHistory.d.ts.map +1 -0
  8. package/dist/findForkCheckpointInHistory.js +34 -0
  9. package/dist/findForkCheckpointInHistory.js.map +1 -0
  10. package/dist/hooks.d.ts +88 -0
  11. package/dist/hooks.d.ts.map +1 -0
  12. package/dist/hooks.js +99 -0
  13. package/dist/hooks.js.map +1 -0
  14. package/dist/index.d.ts +6 -3
  15. package/dist/index.js +4 -2
  16. package/dist/resolveForkCheckpoint.d.ts +17 -0
  17. package/dist/resolveForkCheckpoint.d.ts.map +1 -0
  18. package/dist/resolveForkCheckpoint.js +26 -0
  19. package/dist/resolveForkCheckpoint.js.map +1 -0
  20. package/dist/runtimeExtras.d.ts +7 -0
  21. package/dist/runtimeExtras.d.ts.map +1 -0
  22. package/dist/runtimeExtras.js +7 -0
  23. package/dist/runtimeExtras.js.map +1 -0
  24. package/dist/streamingTiming.d.ts +16 -0
  25. package/dist/streamingTiming.d.ts.map +1 -0
  26. package/dist/streamingTiming.js +53 -0
  27. package/dist/streamingTiming.js.map +1 -0
  28. package/dist/types.d.ts +113 -5
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/uiMessages.d.ts +24 -0
  31. package/dist/uiMessages.d.ts.map +1 -0
  32. package/dist/uiMessages.js +68 -0
  33. package/dist/uiMessages.js.map +1 -0
  34. package/dist/useStreamRuntime.d.ts +15 -76
  35. package/dist/useStreamRuntime.d.ts.map +1 -1
  36. package/dist/useStreamRuntime.js +190 -111
  37. package/dist/useStreamRuntime.js.map +1 -1
  38. package/package.json +9 -9
  39. package/src/__tests__/langChainTestUtils.ts +22 -0
  40. package/src/convertMessages.test.ts +210 -0
  41. package/src/convertMessages.ts +111 -11
  42. package/src/findForkCheckpointInHistory.test.ts +263 -0
  43. package/src/findForkCheckpointInHistory.ts +68 -0
  44. package/src/groupUIMessagesByParent.test.ts +61 -0
  45. package/src/hooks.ts +156 -0
  46. package/src/index.ts +20 -3
  47. package/src/resolveForkCheckpoint.test.ts +202 -0
  48. package/src/resolveForkCheckpoint.ts +49 -0
  49. package/src/runConfigToSubmitOptions.test.ts +24 -0
  50. package/src/runtimeExtras.ts +5 -0
  51. package/src/streamingTiming.test.ts +118 -0
  52. package/src/streamingTiming.ts +85 -0
  53. package/src/types.ts +147 -3
  54. package/src/uiMessages.test.ts +190 -0
  55. package/src/uiMessages.ts +91 -0
  56. package/src/useLangChainError.test.tsx +48 -0
  57. package/src/useLangChainInterrupts.test.tsx +48 -0
  58. package/src/useLangChainRespond.test.tsx +49 -0
  59. package/src/useLangChainRespondAll.test.tsx +52 -0
  60. package/src/useLangChainState.test.tsx +14 -26
  61. package/src/useLangChainStream.test.tsx +42 -0
  62. package/src/useLangChainSubagents.test.tsx +48 -0
  63. package/src/useLangChainSubgraphs.test.tsx +48 -0
  64. package/src/useLangChainToolCalls.test.tsx +48 -0
  65. package/src/useStreamRuntime.test.tsx +232 -0
  66. package/src/useStreamRuntime.ts +311 -234
@@ -0,0 +1,52 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { renderHook } from "@testing-library/react";
5
+ import { makeExtras } from "./__tests__/langChainTestUtils";
6
+
7
+ const { mockUseAui } = vi.hoisted(() => ({
8
+ mockUseAui: vi.fn(),
9
+ }));
10
+
11
+ vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
12
+ const actual = await importOriginal();
13
+ return {
14
+ ...actual,
15
+ useAui: (() => mockUseAui()) as unknown as typeof actual.useAui,
16
+ };
17
+ });
18
+
19
+ import { useLangChainRespondAll } from "./hooks";
20
+
21
+ describe("useLangChainRespondAll", () => {
22
+ it("forwards the responses and options to stream.respondAll", async () => {
23
+ const respondAll = vi.fn().mockResolvedValue(undefined);
24
+ mockUseAui.mockReturnValue({
25
+ thread: () => ({
26
+ getState: () => ({ extras: makeExtras({ respondAll }) }),
27
+ }),
28
+ });
29
+
30
+ const { result } = renderHook(() => useLangChainRespondAll());
31
+ await result.current(
32
+ { a: { approved: true }, b: { approved: false } },
33
+ { metadata: { src: "ui" } },
34
+ );
35
+
36
+ expect(respondAll).toHaveBeenCalledWith(
37
+ { a: { approved: true }, b: { approved: false } },
38
+ { metadata: { src: "ui" } },
39
+ );
40
+ });
41
+
42
+ it("throws when extras are absent or unbranded", () => {
43
+ mockUseAui.mockReturnValue({
44
+ thread: () => ({ getState: () => ({ extras: { respondAll: vi.fn() } }) }),
45
+ });
46
+
47
+ const { result } = renderHook(() => useLangChainRespondAll());
48
+ expect(() => result.current({ a: { approved: true } })).toThrow(
49
+ "useStreamRuntime",
50
+ );
51
+ });
52
+ });
@@ -2,8 +2,12 @@
2
2
 
3
3
  import { describe, expect, it, vi } from "vitest";
4
4
  import { renderHook } from "@testing-library/react";
5
+ import {
6
+ createRunSelectorAgainst,
7
+ makeExtras,
8
+ type Selector,
9
+ } from "./__tests__/langChainTestUtils";
5
10
 
6
- type Selector = (state: unknown) => unknown;
7
11
  const { mockUseAuiState } = vi.hoisted(() => ({
8
12
  mockUseAuiState: vi.fn(),
9
13
  }));
@@ -18,27 +22,9 @@ vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
18
22
  };
19
23
  });
20
24
 
21
- import { useLangChainState } from "./useStreamRuntime";
25
+ import { useLangChainState } from "./hooks";
22
26
 
23
- // The real guard symbol in useStreamRuntime is module-private. To stand in for
24
- // runtime-produced extras, we use a Proxy whose `has` trap claims any symbol
25
- // with the description "langchain-runtime-extras" is present.
26
- const makeExtras = (values: Record<string, unknown>) =>
27
- new Proxy(
28
- { values },
29
- {
30
- has: (target, key) =>
31
- (typeof key === "symbol" &&
32
- key.description === "langchain-runtime-extras") ||
33
- Reflect.has(target, key),
34
- },
35
- );
36
-
37
- const runSelectorAgainst = (extras: unknown) => {
38
- mockUseAuiState.mockImplementationOnce((selector: Selector) =>
39
- selector({ thread: { extras } }),
40
- );
41
- };
27
+ const runSelectorAgainst = createRunSelectorAgainst(mockUseAuiState);
42
28
 
43
29
  describe("useLangChainState", () => {
44
30
  it("returns undefined when extras are absent", () => {
@@ -54,7 +40,9 @@ describe("useLangChainState", () => {
54
40
  });
55
41
 
56
42
  it("reads the value for the given key from extras.values", () => {
57
- runSelectorAgainst(makeExtras({ todos: [{ id: "a" }], count: 7 }));
43
+ runSelectorAgainst(
44
+ makeExtras({ values: { todos: [{ id: "a" }], count: 7 } }),
45
+ );
58
46
  const { result } = renderHook(() =>
59
47
  useLangChainState<Array<{ id: string }>>("todos"),
60
48
  );
@@ -62,26 +50,26 @@ describe("useLangChainState", () => {
62
50
  });
63
51
 
64
52
  it("falls back to defaultValue when the key is missing from values", () => {
65
- runSelectorAgainst(makeExtras({ unrelated: "x" }));
53
+ runSelectorAgainst(makeExtras({ values: { unrelated: "x" } }));
66
54
  const { result } = renderHook(() => useLangChainState<number>("count", 99));
67
55
  expect(result.current).toBe(99);
68
56
  });
69
57
 
70
58
  it("returns undefined when the key is missing and no default is given", () => {
71
- runSelectorAgainst(makeExtras({ unrelated: "x" }));
59
+ runSelectorAgainst(makeExtras({ values: { unrelated: "x" } }));
72
60
  const { result } = renderHook(() => useLangChainState<number>("count"));
73
61
  expect(result.current).toBeUndefined();
74
62
  });
75
63
 
76
64
  it("returns the stored value (not default) when both are present", () => {
77
- runSelectorAgainst(makeExtras({ count: 0 }));
65
+ runSelectorAgainst(makeExtras({ values: { count: 0 } }));
78
66
  const { result } = renderHook(() => useLangChainState<number>("count", 99));
79
67
  // 0 is a valid value; must be preserved over the default.
80
68
  expect(result.current).toBe(0);
81
69
  });
82
70
 
83
71
  it("preserves explicit null over defaultValue", () => {
84
- runSelectorAgainst(makeExtras({ flag: null }));
72
+ runSelectorAgainst(makeExtras({ values: { flag: null } }));
85
73
  const { result } = renderHook(() =>
86
74
  useLangChainState<string | null>("flag", "default"),
87
75
  );
@@ -0,0 +1,42 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { renderHook } from "@testing-library/react";
5
+ import {
6
+ createRunSelectorAgainst,
7
+ makeExtras,
8
+ type Selector,
9
+ } from "./__tests__/langChainTestUtils";
10
+
11
+ const { mockUseAuiState } = vi.hoisted(() => ({
12
+ mockUseAuiState: vi.fn(),
13
+ }));
14
+
15
+ vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
16
+ const actual = await importOriginal();
17
+ return {
18
+ ...actual,
19
+ useAuiState: ((selector: Selector) =>
20
+ mockUseAuiState(selector)) as typeof actual.useAuiState,
21
+ useAui: (() => ({})) as unknown as typeof actual.useAui,
22
+ };
23
+ });
24
+
25
+ import { useLangChainStream } from "./hooks";
26
+
27
+ const runSelectorAgainst = createRunSelectorAgainst(mockUseAuiState);
28
+
29
+ describe("useLangChainStream", () => {
30
+ it("returns undefined when extras are absent", () => {
31
+ runSelectorAgainst(undefined);
32
+ const { result } = renderHook(() => useLangChainStream());
33
+ expect(result.current).toBeUndefined();
34
+ });
35
+
36
+ it("returns the stream handle from extras", () => {
37
+ const stream = { submit: () => {} };
38
+ runSelectorAgainst(makeExtras({ stream }));
39
+ const { result } = renderHook(() => useLangChainStream());
40
+ expect(result.current).toBe(stream);
41
+ });
42
+ });
@@ -0,0 +1,48 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { renderHook } from "@testing-library/react";
5
+ import {
6
+ createRunSelectorAgainst,
7
+ makeExtras,
8
+ type Selector,
9
+ } from "./__tests__/langChainTestUtils";
10
+
11
+ const { mockUseAuiState } = vi.hoisted(() => ({
12
+ mockUseAuiState: vi.fn(),
13
+ }));
14
+
15
+ vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
16
+ const actual = await importOriginal();
17
+ return {
18
+ ...actual,
19
+ useAuiState: ((selector: Selector) =>
20
+ mockUseAuiState(selector)) as typeof actual.useAuiState,
21
+ useAui: (() => ({})) as unknown as typeof actual.useAui,
22
+ };
23
+ });
24
+
25
+ import { useLangChainSubagents } from "./hooks";
26
+
27
+ const runSelectorAgainst = createRunSelectorAgainst(mockUseAuiState);
28
+
29
+ describe("useLangChainSubagents", () => {
30
+ it("returns the stable empty map when extras are absent", () => {
31
+ runSelectorAgainst(undefined);
32
+ const { result } = renderHook(() => useLangChainSubagents());
33
+ expect(result.current.size).toBe(0);
34
+ });
35
+
36
+ it("returns the subagents map from extras", () => {
37
+ const subagents = new Map([["researcher", { namespace: "researcher" }]]);
38
+ runSelectorAgainst(makeExtras({ subagents }));
39
+ const { result } = renderHook(() => useLangChainSubagents());
40
+ expect(result.current).toBe(subagents);
41
+ });
42
+
43
+ it("returns the stable empty map when extras carry no subagents", () => {
44
+ runSelectorAgainst(makeExtras({ subagents: undefined }));
45
+ const { result } = renderHook(() => useLangChainSubagents());
46
+ expect(result.current.size).toBe(0);
47
+ });
48
+ });
@@ -0,0 +1,48 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { renderHook } from "@testing-library/react";
5
+ import {
6
+ createRunSelectorAgainst,
7
+ makeExtras,
8
+ type Selector,
9
+ } from "./__tests__/langChainTestUtils";
10
+
11
+ const { mockUseAuiState } = vi.hoisted(() => ({
12
+ mockUseAuiState: vi.fn(),
13
+ }));
14
+
15
+ vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
16
+ const actual = await importOriginal();
17
+ return {
18
+ ...actual,
19
+ useAuiState: ((selector: Selector) =>
20
+ mockUseAuiState(selector)) as typeof actual.useAuiState,
21
+ useAui: (() => ({})) as unknown as typeof actual.useAui,
22
+ };
23
+ });
24
+
25
+ import { useLangChainSubgraphs } from "./hooks";
26
+
27
+ const runSelectorAgainst = createRunSelectorAgainst(mockUseAuiState);
28
+
29
+ describe("useLangChainSubgraphs", () => {
30
+ it("returns the stable empty map when extras are absent", () => {
31
+ runSelectorAgainst(undefined);
32
+ const { result } = renderHook(() => useLangChainSubgraphs());
33
+ expect(result.current.size).toBe(0);
34
+ });
35
+
36
+ it("returns the subgraphs map from extras", () => {
37
+ const subgraphs = new Map([["planner", { namespace: "planner" }]]);
38
+ runSelectorAgainst(makeExtras({ subgraphs }));
39
+ const { result } = renderHook(() => useLangChainSubgraphs());
40
+ expect(result.current).toBe(subgraphs);
41
+ });
42
+
43
+ it("returns the stable empty map when extras carry no subgraphs", () => {
44
+ runSelectorAgainst(makeExtras({ subgraphs: undefined }));
45
+ const { result } = renderHook(() => useLangChainSubgraphs());
46
+ expect(result.current.size).toBe(0);
47
+ });
48
+ });
@@ -0,0 +1,48 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { renderHook } from "@testing-library/react";
5
+ import {
6
+ createRunSelectorAgainst,
7
+ makeExtras,
8
+ type Selector,
9
+ } from "./__tests__/langChainTestUtils";
10
+
11
+ const { mockUseAuiState } = vi.hoisted(() => ({
12
+ mockUseAuiState: vi.fn(),
13
+ }));
14
+
15
+ vi.mock(import("@assistant-ui/store"), async (importOriginal) => {
16
+ const actual = await importOriginal();
17
+ return {
18
+ ...actual,
19
+ useAuiState: ((selector: Selector) =>
20
+ mockUseAuiState(selector)) as typeof actual.useAuiState,
21
+ useAui: (() => ({})) as unknown as typeof actual.useAui,
22
+ };
23
+ });
24
+
25
+ import { useLangChainToolCalls } from "./hooks";
26
+
27
+ const runSelectorAgainst = createRunSelectorAgainst(mockUseAuiState);
28
+
29
+ describe("useLangChainToolCalls", () => {
30
+ it("returns an empty array when extras are absent", () => {
31
+ runSelectorAgainst(undefined);
32
+ const { result } = renderHook(() => useLangChainToolCalls());
33
+ expect(result.current).toEqual([]);
34
+ });
35
+
36
+ it("returns the assembled tool calls from extras", () => {
37
+ const toolCalls = [{ id: "a", name: "search", args: { q: "x" } }];
38
+ runSelectorAgainst(makeExtras({ toolCalls }));
39
+ const { result } = renderHook(() => useLangChainToolCalls());
40
+ expect(result.current).toBe(toolCalls);
41
+ });
42
+
43
+ it("returns an empty array when extras carry no tool calls", () => {
44
+ runSelectorAgainst(makeExtras({ toolCalls: undefined }));
45
+ const { result } = renderHook(() => useLangChainToolCalls());
46
+ expect(result.current).toEqual([]);
47
+ });
48
+ });
@@ -0,0 +1,232 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { act, renderHook, waitFor } from "@testing-library/react";
4
+ import { describe, expect, it, vi } from "vitest";
5
+ import { AssistantRuntimeProvider } from "@assistant-ui/core/react";
6
+ import { useAui } from "@assistant-ui/store";
7
+ import type { LangChainBaseMessage } from "./types";
8
+ import type { ReactNode } from "react";
9
+
10
+ const { mockUseChannel, mockUseStream, streamController } = vi.hoisted(() => ({
11
+ mockUseChannel: vi.fn(() => []),
12
+ mockUseStream: vi.fn(),
13
+ streamController: Symbol("STREAM_CONTROLLER"),
14
+ }));
15
+
16
+ vi.mock("@langchain/react", () => ({
17
+ STREAM_CONTROLLER: streamController,
18
+ useChannel: mockUseChannel,
19
+ useStream: mockUseStream,
20
+ }));
21
+
22
+ import { useStreamRuntime } from "./useStreamRuntime";
23
+
24
+ type MockStream = {
25
+ messages: LangChainBaseMessage[];
26
+ isLoading: boolean;
27
+ isThreadLoading: boolean;
28
+ values: Record<string, unknown>;
29
+ interrupts: unknown[];
30
+ toolCalls: unknown[];
31
+ subagents: unknown[];
32
+ subgraphs: unknown[];
33
+ error: unknown;
34
+ submit: ReturnType<typeof vi.fn>;
35
+ respond: ReturnType<typeof vi.fn>;
36
+ respondAll: ReturnType<typeof vi.fn>;
37
+ interrupt: ReturnType<typeof vi.fn>;
38
+ stop: ReturnType<typeof vi.fn>;
39
+ client: Record<string, unknown>;
40
+ [streamController]: {
41
+ messageMetadataStore: {
42
+ getSnapshot: ReturnType<typeof vi.fn>;
43
+ };
44
+ };
45
+ };
46
+
47
+ const message = (
48
+ id: string,
49
+ type: "human" | "ai",
50
+ content: string,
51
+ ): LangChainBaseMessage & { id: string } => ({
52
+ id,
53
+ _getType: () => type,
54
+ content,
55
+ });
56
+
57
+ const createMockStream = (
58
+ messages: LangChainBaseMessage[] = [],
59
+ ): MockStream => ({
60
+ messages,
61
+ isLoading: false,
62
+ isThreadLoading: false,
63
+ values: {},
64
+ interrupts: [],
65
+ toolCalls: [],
66
+ subagents: [],
67
+ subgraphs: [],
68
+ error: undefined,
69
+ submit: vi.fn(async () => {}),
70
+ respond: vi.fn(),
71
+ respondAll: vi.fn(),
72
+ interrupt: vi.fn(),
73
+ stop: vi.fn(),
74
+ client: {},
75
+ [streamController]: {
76
+ messageMetadataStore: {
77
+ getSnapshot: vi.fn(),
78
+ },
79
+ },
80
+ });
81
+
82
+ const renderRuntime = (stream: MockStream) => {
83
+ mockUseStream.mockReturnValue(stream);
84
+ return renderHook(() => useStreamRuntime({ apiUrl: "/api" } as never));
85
+ };
86
+
87
+ const renderAui = (stream: MockStream) => {
88
+ const runtimeHook = renderRuntime(stream);
89
+ const Wrapper = ({ children }: { children: ReactNode }) => (
90
+ <AssistantRuntimeProvider runtime={runtimeHook.result.current}>
91
+ {children}
92
+ </AssistantRuntimeProvider>
93
+ );
94
+ Wrapper.displayName = "TestWrapper";
95
+ const auiHook = renderHook(() => useAui(), { wrapper: Wrapper });
96
+ return {
97
+ auiResult: auiHook.result,
98
+ rerender: () => {
99
+ runtimeHook.rerender();
100
+ auiHook.rerender();
101
+ },
102
+ };
103
+ };
104
+
105
+ const getText = (aui: ReturnType<typeof useAui>) =>
106
+ aui
107
+ .thread()
108
+ .getState()
109
+ .messages.map((m) =>
110
+ m.content
111
+ .filter((part) => part.type === "text")
112
+ .map((part) => part.text)
113
+ .join(""),
114
+ );
115
+
116
+ describe("useStreamRuntime staged messages", () => {
117
+ it("stages a new user message without submitting when startRun is false", async () => {
118
+ const stream = createMockStream([message("u1", "human", "earlier")]);
119
+ const { auiResult } = renderAui(stream);
120
+
121
+ await act(async () => {
122
+ auiResult.current.thread().append({
123
+ role: "user",
124
+ content: [{ type: "text", text: "draft" }],
125
+ startRun: false,
126
+ });
127
+ });
128
+
129
+ await waitFor(() => {
130
+ expect(getText(auiResult.current)).toEqual(["earlier", "draft"]);
131
+ });
132
+ expect(stream.submit).not.toHaveBeenCalled();
133
+ });
134
+
135
+ it("keeps a staged edit truncated when stream messages update before promotion", async () => {
136
+ const stream = createMockStream([
137
+ message("u1", "human", "first"),
138
+ message("a1", "ai", "first answer"),
139
+ message("u2", "human", "second"),
140
+ ]);
141
+ const { auiResult, rerender } = renderAui(stream);
142
+
143
+ await act(async () => {
144
+ auiResult.current.thread().append({
145
+ role: "user",
146
+ parentId: "u1",
147
+ content: [{ type: "text", text: "edited" }],
148
+ startRun: false,
149
+ });
150
+ });
151
+
152
+ await waitFor(() => {
153
+ expect(getText(auiResult.current)).toEqual(["first", "edited"]);
154
+ });
155
+
156
+ stream.messages = [
157
+ message("u1", "human", "first"),
158
+ message("a1", "ai", "first answer from refresh"),
159
+ message("u2", "human", "second from refresh"),
160
+ ];
161
+ rerender();
162
+
163
+ await waitFor(() => {
164
+ expect(getText(auiResult.current)).toEqual(["first", "edited"]);
165
+ });
166
+ expect(stream.submit).not.toHaveBeenCalled();
167
+ });
168
+
169
+ it("keeps later staged messages visible after promoting one staged parent", async () => {
170
+ const stream = createMockStream([message("u1", "human", "earlier")]);
171
+ const { auiResult, rerender } = renderAui(stream);
172
+
173
+ await act(async () => {
174
+ auiResult.current.thread().append({
175
+ role: "user",
176
+ content: [{ type: "text", text: "first staged" }],
177
+ startRun: false,
178
+ });
179
+ auiResult.current.thread().append({
180
+ role: "user",
181
+ content: [{ type: "text", text: "second staged" }],
182
+ startRun: false,
183
+ });
184
+ });
185
+
186
+ await waitFor(() => {
187
+ expect(getText(auiResult.current)).toEqual([
188
+ "earlier",
189
+ "first staged",
190
+ "second staged",
191
+ ]);
192
+ });
193
+
194
+ const firstStagedId = auiResult.current.thread().getState().messages[1]!.id;
195
+ await act(async () => {
196
+ await auiResult.current.thread().startRun({
197
+ parentId: firstStagedId,
198
+ sourceId: null,
199
+ runConfig: {},
200
+ });
201
+ });
202
+
203
+ expect(stream.submit).toHaveBeenCalledWith(
204
+ {
205
+ messages: [
206
+ expect.objectContaining({
207
+ id: firstStagedId,
208
+ type: "human",
209
+ content: "first staged",
210
+ }),
211
+ ],
212
+ },
213
+ undefined,
214
+ );
215
+
216
+ stream.messages = [
217
+ message("u1", "human", "earlier"),
218
+ message(firstStagedId, "human", "first staged"),
219
+ message("a1", "ai", "answer"),
220
+ ];
221
+ rerender();
222
+
223
+ await waitFor(() => {
224
+ expect(getText(auiResult.current)).toEqual([
225
+ "earlier",
226
+ "first staged",
227
+ "answer",
228
+ "second staged",
229
+ ]);
230
+ });
231
+ });
232
+ });