@assistant-ui/react 0.15.13 → 0.15.15

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 (160) hide show
  1. package/dist/assistant-transport.d.ts +32 -0
  2. package/dist/assistant-transport.d.ts.map +1 -0
  3. package/dist/assistant-transport.js +27 -0
  4. package/dist/assistant-transport.js.map +1 -0
  5. package/dist/context/providers/MessageProvider.js +1 -1
  6. package/dist/hooks/useMessageQuote.js +1 -1
  7. package/dist/hooks/useToolCallElapsed.js +1 -1
  8. package/dist/index.d.ts +5 -8
  9. package/dist/index.js +7 -9
  10. package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
  11. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  12. package/dist/mcp-apps/McpAppsRemoteHost.js +18 -2
  13. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  14. package/dist/mcp-apps/bridge.js +39 -24
  15. package/dist/mcp-apps/bridge.js.map +1 -1
  16. package/dist/model-context/makeAssistantVisible.js +1 -1
  17. package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
  18. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -1
  19. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
  20. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
  21. package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -1
  22. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +24 -37
  23. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
  24. package/dist/primitives/actionBar/useActionBarFloatStatus.js +1 -1
  25. package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
  26. package/dist/primitives/attachment/AttachmentName.js +1 -1
  27. package/dist/primitives/attachment/AttachmentRemove.js +1 -1
  28. package/dist/primitives/attachment/AttachmentThumb.js +1 -1
  29. package/dist/primitives/branchPicker/BranchPickerCount.js +1 -1
  30. package/dist/primitives/branchPicker/BranchPickerNumber.js +1 -1
  31. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
  32. package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
  33. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  34. package/dist/primitives/composer/ComposerDictationTranscript.js +1 -1
  35. package/dist/primitives/composer/ComposerInput.js +2 -2
  36. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  37. package/dist/primitives/composer/ComposerQuote.js +1 -1
  38. package/dist/primitives/composer/ComposerRoot.js +1 -1
  39. package/dist/primitives/composer/ComposerStopDictation.js +1 -1
  40. package/dist/primitives/composer/trigger/TriggerPopover.js +1 -1
  41. package/dist/primitives/composer/useComposerInputState.js +1 -1
  42. package/dist/primitives/message/MessageIf.js +1 -1
  43. package/dist/primitives/message/MessagePartsGrouped.js +1 -1
  44. package/dist/primitives/message/MessageRoot.js +1 -1
  45. package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
  46. package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
  47. package/dist/primitives/queueItem/QueueItemText.js +1 -1
  48. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  49. package/dist/primitives/suggestion/SuggestionDescription.js +1 -1
  50. package/dist/primitives/suggestion/SuggestionTitle.js +1 -1
  51. package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
  52. package/dist/primitives/thread/ThreadIf.js +1 -1
  53. package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
  54. package/dist/primitives/thread/ThreadRoot.js +41 -9
  55. package/dist/primitives/thread/ThreadRoot.js.map +1 -1
  56. package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
  57. package/dist/primitives/thread/ThreadViewport.js +2 -13
  58. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  59. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  60. package/dist/primitives/thread/useThreadViewportAutoScroll.js +23 -8
  61. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  62. package/dist/primitives/threadList/ThreadListNew.js +1 -1
  63. package/dist/sandbox-host/SandboxHost.js +2 -0
  64. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  65. package/dist/unstable/useComposerInput.d.ts +2 -2
  66. package/dist/unstable/useComposerInput.js +1 -1
  67. package/dist/unstable/useComposerInput.js.map +1 -1
  68. package/dist/unstable/useComposerInputHistory.js +1 -1
  69. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  70. package/dist/unstable/useLiveCompletionAdapter.d.ts +5 -0
  71. package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
  72. package/dist/unstable/useLiveCompletionAdapter.js +16 -3
  73. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
  74. package/dist/unstable/useMentionAdapter.d.ts +1 -1
  75. package/dist/utils/smooth/SmoothContext.js +1 -1
  76. package/dist/utils/useToolArgsFieldStatus.js +1 -1
  77. package/package.json +9 -9
  78. package/src/assistant-transport.ts +94 -0
  79. package/src/index.ts +9 -4
  80. package/src/mcp-apps/McpAppsRemoteHost.test.ts +29 -3
  81. package/src/mcp-apps/McpAppsRemoteHost.ts +42 -7
  82. package/src/mcp-apps/bridge.test.ts +132 -0
  83. package/src/mcp-apps/bridge.ts +54 -30
  84. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +0 -9
  85. package/src/primitives/composer/ComposerInput.tsx +1 -1
  86. package/src/primitives/thread/ThreadRoot.test.tsx +251 -0
  87. package/src/primitives/thread/ThreadRoot.tsx +30 -0
  88. package/src/primitives/thread/ThreadViewport.tsx +1 -8
  89. package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +152 -0
  90. package/src/primitives/thread/useThreadViewportAutoScroll.ts +26 -19
  91. package/src/sandbox-host/SandboxHost.test.tsx +29 -0
  92. package/src/sandbox-host/SandboxHost.tsx +2 -0
  93. package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +24 -3
  94. package/src/tests/RemoteThreadListRuntime.reloadMainThread.test.tsx +1 -2
  95. package/src/tests/augmentations.test.ts +49 -0
  96. package/src/tests/in-memory-thread-list.test.tsx +87 -3
  97. package/src/tests/threadMessageById.test.tsx +61 -1
  98. package/src/unstable/useComposerInput.ts +2 -2
  99. package/src/unstable/useComposerInputHistory.test.tsx +4 -4
  100. package/src/unstable/useComposerInputHistory.ts +1 -1
  101. package/src/unstable/useLiveCompletionAdapter.test.tsx +100 -0
  102. package/src/unstable/useLiveCompletionAdapter.ts +41 -17
  103. package/dist/client/InMemoryThreadList.d.ts +0 -12
  104. package/dist/client/InMemoryThreadList.d.ts.map +0 -1
  105. package/dist/client/InMemoryThreadList.js +0 -365
  106. package/dist/client/InMemoryThreadList.js.map +0 -1
  107. package/dist/legacy-runtime/cloud/auiV0.d.ts +0 -119
  108. package/dist/legacy-runtime/cloud/auiV0.d.ts.map +0 -1
  109. package/dist/legacy-runtime/cloud/auiV0.js +0 -137
  110. package/dist/legacy-runtime/cloud/auiV0.js.map +0 -1
  111. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts +0 -16
  112. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts.map +0 -1
  113. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js +0 -36
  114. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js.map +0 -1
  115. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +0 -17
  116. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +0 -1
  117. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +0 -55
  118. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +0 -1
  119. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts +0 -11
  120. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts.map +0 -1
  121. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +0 -141
  122. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +0 -1
  123. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts +0 -15
  124. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +0 -1
  125. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +0 -81
  126. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +0 -1
  127. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +0 -126
  128. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +0 -1
  129. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.js +0 -0
  130. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts +0 -14
  131. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +0 -1
  132. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +0 -290
  133. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +0 -1
  134. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +0 -7
  135. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +0 -1
  136. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +0 -25
  137. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +0 -1
  138. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts +0 -7
  139. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts.map +0 -1
  140. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +0 -27
  141. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js.map +0 -1
  142. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts +0 -2
  143. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js +0 -2
  144. package/src/client/InMemoryThreadList.ts +0 -241
  145. package/src/legacy-runtime/cloud/auiV0.ts +0 -331
  146. package/src/legacy-runtime/cloud/useCloudThreadListRuntime.ts +0 -31
  147. package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +0 -68
  148. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +0 -430
  149. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +0 -146
  150. package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +0 -113
  151. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +0 -79
  152. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +0 -97
  153. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +0 -307
  154. package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +0 -170
  155. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +0 -141
  156. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +0 -585
  157. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +0 -476
  158. package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +0 -20
  159. package/src/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.ts +0 -9
  160. package/src/legacy-runtime/runtime-cores/assistant-transport/utils.ts +0 -1
@@ -1,79 +0,0 @@
1
- // @vitest-environment jsdom
2
-
3
- import { act, render, waitFor } from "@testing-library/react";
4
- import { afterEach, describe, expect, it, vi } from "vitest";
5
- import type { FC } from "react";
6
- import { useAssistantTransportRuntime } from "./useAssistantTransportRuntime";
7
- import type { AssistantRuntime } from "../../runtime/AssistantRuntime";
8
- import { AssistantRuntimeProvider } from "../../../context";
9
- import type {
10
- AssistantTransportCommand,
11
- AssistantTransportStateConverter,
12
- } from "./types";
13
-
14
- const emptySuccessfulResponse = () =>
15
- new Response(
16
- new ReadableStream<Uint8Array>({
17
- start(controller) {
18
- controller.close();
19
- },
20
- }),
21
- { status: 200 },
22
- );
23
-
24
- const setupRuntime = () => {
25
- const fetchMock = vi.fn(async () => emptySuccessfulResponse());
26
- vi.stubGlobal("fetch", fetchMock);
27
-
28
- const pendingRef: { current: AssistantTransportCommand[] } = { current: [] };
29
- const converter: AssistantTransportStateConverter<Record<string, never>> = (
30
- _state,
31
- { pendingCommands, isSending },
32
- ) => {
33
- pendingRef.current = pendingCommands;
34
- return { messages: [], isRunning: isSending };
35
- };
36
-
37
- const runtimeRef: { current: AssistantRuntime | null } = { current: null };
38
- const App: FC = () => {
39
- const runtime = useAssistantTransportRuntime({
40
- initialState: {},
41
- api: "http://localhost/api",
42
- converter,
43
- headers: {},
44
- });
45
- runtimeRef.current = runtime;
46
- return (
47
- <AssistantRuntimeProvider runtime={runtime}>
48
- {null}
49
- </AssistantRuntimeProvider>
50
- );
51
- };
52
-
53
- return { App, fetchMock, pendingRef, runtimeRef };
54
- };
55
-
56
- describe("assistant transport delivery contracts", () => {
57
- afterEach(() => {
58
- vi.unstubAllGlobals();
59
- });
60
-
61
- it("clears in-transit commands when a run succeeds without state chunks", async () => {
62
- const { App, fetchMock, pendingRef, runtimeRef } = setupRuntime();
63
-
64
- await act(async () => {
65
- render(<App />);
66
- });
67
- await waitFor(() => expect(runtimeRef.current).not.toBeNull());
68
-
69
- await act(async () => {
70
- runtimeRef.current!.thread.append("m1");
71
- });
72
-
73
- await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
74
- await waitFor(() =>
75
- expect(runtimeRef.current!.thread.getState().isRunning).toBe(false),
76
- );
77
- await waitFor(() => expect(pendingRef.current).toHaveLength(0));
78
- });
79
- });
@@ -1,97 +0,0 @@
1
- // @vitest-environment jsdom
2
-
3
- import { act, render, waitFor } from "@testing-library/react";
4
- import { afterEach, describe, expect, it, vi } from "vitest";
5
- import type { FC } from "react";
6
- import { useAssistantTransportRuntime } from "./useAssistantTransportRuntime";
7
- import type { AssistantRuntime } from "../../runtime/AssistantRuntime";
8
- import { AssistantRuntimeProvider } from "../../../context";
9
- import type {
10
- AssistantTransportCommand,
11
- AssistantTransportStateConverter,
12
- } from "./types";
13
-
14
- const emptySuccessfulResponse = () =>
15
- new Response(
16
- new ReadableStream<Uint8Array>({
17
- start(controller) {
18
- controller.close();
19
- },
20
- }),
21
- { status: 200 },
22
- );
23
-
24
- const setupRuntime = () => {
25
- const requestBodies: Record<string, unknown>[] = [];
26
- const fetchMock = vi.fn(
27
- async (_url: RequestInfo | URL, init?: RequestInit) => {
28
- requestBodies.push(JSON.parse(init!.body as string));
29
- return emptySuccessfulResponse();
30
- },
31
- );
32
- vi.stubGlobal("fetch", fetchMock);
33
-
34
- const converter: AssistantTransportStateConverter<Record<string, never>> = (
35
- _state,
36
- { isSending },
37
- ) => ({ messages: [], isRunning: isSending });
38
-
39
- const runtimeRef: { current: AssistantRuntime | null } = { current: null };
40
- const App: FC = () => {
41
- const runtime = useAssistantTransportRuntime({
42
- initialState: {},
43
- api: "http://localhost/api",
44
- converter,
45
- headers: {},
46
- });
47
- runtimeRef.current = runtime;
48
- return (
49
- <AssistantRuntimeProvider runtime={runtime}>
50
- {null}
51
- </AssistantRuntimeProvider>
52
- );
53
- };
54
-
55
- return { App, fetchMock, requestBodies, runtimeRef };
56
- };
57
-
58
- describe("assistant transport parentId lifetime", () => {
59
- afterEach(() => {
60
- vi.unstubAllGlobals();
61
- });
62
-
63
- it("consumes parentId per run: the append's run carries it, a later sendCommand run omits it", async () => {
64
- const { App, fetchMock, requestBodies, runtimeRef } = setupRuntime();
65
-
66
- await act(async () => {
67
- render(<App />);
68
- });
69
- await waitFor(() => expect(runtimeRef.current).not.toBeNull());
70
-
71
- await act(async () => {
72
- runtimeRef.current!.thread.append("m1");
73
- });
74
-
75
- await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
76
- await waitFor(() =>
77
- expect(runtimeRef.current!.thread.getState().isRunning).toBe(false),
78
- );
79
- expect(Object.hasOwn(requestBodies[0]!, "parentId")).toBe(true);
80
-
81
- const extras = runtimeRef.current!.thread.getState().extras as {
82
- sendCommand: (command: AssistantTransportCommand) => void;
83
- };
84
- await act(async () => {
85
- extras.sendCommand({
86
- type: "add-tool-result",
87
- toolCallId: "t1",
88
- toolName: "tool",
89
- result: {},
90
- isError: false,
91
- });
92
- });
93
-
94
- await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2));
95
- expect(Object.hasOwn(requestBodies[1]!, "parentId")).toBe(false);
96
- });
97
- });
@@ -1,307 +0,0 @@
1
- // @vitest-environment jsdom
2
-
3
- import { act, renderHook, waitFor } from "@testing-library/react";
4
- import { describe, expect, it, vi } from "vitest";
5
- import { createElement, StrictMode, useRef } from "react";
6
- import { useCommandQueue } from "./commandQueue";
7
- import { useRunManager } from "./runManager";
8
- import type { AssistantTransportCommand } from "./types";
9
-
10
- const createMessageCommand = (id: string): AssistantTransportCommand => ({
11
- type: "add-message",
12
- message: {
13
- role: "user",
14
- parts: [{ type: "text", text: id }],
15
- },
16
- parentId: null,
17
- sourceId: null,
18
- });
19
-
20
- const createDeferred = () => {
21
- let resolve!: () => void;
22
- const promise = new Promise<void>((res) => {
23
- resolve = res;
24
- });
25
- return { promise, resolve };
26
- };
27
-
28
- const useTransportSchedulingHarness = (
29
- opts: {
30
- onRun?: (signal: AbortSignal) => Promise<void> | void;
31
- onCancel?: (commands: AssistantTransportCommand[]) => void;
32
- onError?: (commands: AssistantTransportCommand[]) => void;
33
- onFinish?: () => void;
34
- } = {},
35
- ) => {
36
- const commandQueueRef = useRef<ReturnType<typeof useCommandQueue> | null>(
37
- null,
38
- );
39
- const runBatchesRef = useRef<AssistantTransportCommand[][]>([]);
40
-
41
- const runManager = useRunManager({
42
- onRun: async (signal) => {
43
- const batch = commandQueueRef.current!.flush();
44
- runBatchesRef.current.push(batch);
45
- await opts.onRun?.(signal);
46
- },
47
- onCancel: () => {
48
- const queue = commandQueueRef.current!;
49
- const commands = [...queue.state.inTransit, ...queue.state.queued];
50
- queue.reset();
51
- opts.onCancel?.(commands);
52
- },
53
- onError: async () => {
54
- const queue = commandQueueRef.current!;
55
- opts.onError?.([...queue.state.inTransit]);
56
- },
57
- onFinish: () => opts.onFinish?.(),
58
- });
59
-
60
- const commandQueue = useCommandQueue({
61
- onQueue: () => runManager.schedule(),
62
- });
63
- commandQueueRef.current = commandQueue;
64
-
65
- return {
66
- commandQueue,
67
- runManager,
68
- runBatchesRef,
69
- };
70
- };
71
-
72
- describe("assistant transport scheduling contracts", () => {
73
- it("runs in single-flight mode and schedules exactly one follow-up run", async () => {
74
- const gate = createDeferred();
75
- const { result } = renderHook(() =>
76
- useTransportSchedulingHarness({
77
- onRun: () => gate.promise,
78
- }),
79
- );
80
-
81
- act(() => {
82
- result.current.commandQueue.enqueue(createMessageCommand("m1"));
83
- result.current.commandQueue.enqueue(createMessageCommand("m2"));
84
- });
85
-
86
- await waitFor(() => {
87
- expect(result.current.runBatchesRef.current).toHaveLength(1);
88
- });
89
- expect(result.current.runBatchesRef.current[0]).toHaveLength(2);
90
-
91
- act(() => {
92
- result.current.commandQueue.enqueue(createMessageCommand("m3"));
93
- });
94
-
95
- await Promise.resolve();
96
- expect(result.current.runBatchesRef.current).toHaveLength(1);
97
-
98
- gate.resolve();
99
-
100
- await waitFor(() => {
101
- expect(result.current.runBatchesRef.current).toHaveLength(2);
102
- });
103
- expect(result.current.runBatchesRef.current[1]).toHaveLength(1);
104
- });
105
-
106
- it("can enqueue without scheduling until a run is started", async () => {
107
- const { result } = renderHook(() => useTransportSchedulingHarness());
108
-
109
- act(() => {
110
- result.current.commandQueue.enqueue(createMessageCommand("staged"), {
111
- schedule: false,
112
- });
113
- });
114
-
115
- await Promise.resolve();
116
- expect(result.current.runBatchesRef.current).toHaveLength(0);
117
-
118
- act(() => {
119
- result.current.runManager.schedule();
120
- });
121
-
122
- await waitFor(() => {
123
- expect(result.current.runBatchesRef.current).toHaveLength(1);
124
- });
125
- expect(result.current.runBatchesRef.current[0]).toHaveLength(1);
126
- });
127
-
128
- it("onError receives the live in-transit commands at error time", async () => {
129
- const seen: AssistantTransportCommand[][] = [];
130
- const { result } = renderHook(() =>
131
- useTransportSchedulingHarness({
132
- onRun: () => {
133
- throw new Error("network error");
134
- },
135
- onError: (commands) => seen.push(commands),
136
- }),
137
- );
138
-
139
- act(() => {
140
- result.current.commandQueue.enqueue(createMessageCommand("m1"));
141
- });
142
-
143
- // The flush that moved m1 into transit has not re-rendered yet when the
144
- // error fires; the queue state must be read live, not from a render snapshot.
145
- await waitFor(() => expect(seen).toHaveLength(1));
146
- expect(seen[0]).toEqual([createMessageCommand("m1")]);
147
- });
148
-
149
- it("settles the run when onFinish throws", async () => {
150
- const error = new Error("telemetry failed");
151
- const onFinish = vi.fn<() => void>().mockImplementationOnce(() => {
152
- throw error;
153
- });
154
- const consoleError = vi
155
- .spyOn(console, "error")
156
- .mockImplementation(() => {});
157
-
158
- try {
159
- const { result } = renderHook(() =>
160
- useTransportSchedulingHarness({ onFinish }),
161
- );
162
-
163
- act(() => {
164
- result.current.commandQueue.enqueue(createMessageCommand("m1"));
165
- });
166
-
167
- await waitFor(() => {
168
- expect(result.current.runManager.isRunning).toBe(false);
169
- });
170
-
171
- act(() => {
172
- result.current.commandQueue.enqueue(createMessageCommand("m2"));
173
- });
174
-
175
- await waitFor(() => {
176
- expect(result.current.runBatchesRef.current).toHaveLength(2);
177
- });
178
- expect(consoleError).toHaveBeenCalledWith(
179
- "[assistant-ui] Assistant transport onFinish callback threw an error",
180
- error,
181
- );
182
- } finally {
183
- consoleError.mockRestore();
184
- }
185
- });
186
-
187
- it("cancel returns combined in-flight and queued commands", async () => {
188
- const onCancel = vi.fn();
189
- const { result } = renderHook(() =>
190
- useTransportSchedulingHarness({
191
- onRun: (signal) =>
192
- new Promise<void>((_resolve, reject) => {
193
- signal.addEventListener(
194
- "abort",
195
- () => reject(new Error("aborted")),
196
- { once: true },
197
- );
198
- }),
199
- onCancel,
200
- }),
201
- );
202
-
203
- act(() => {
204
- result.current.commandQueue.enqueue(createMessageCommand("in-flight"));
205
- });
206
-
207
- await waitFor(() => {
208
- expect(result.current.runBatchesRef.current).toHaveLength(1);
209
- });
210
-
211
- act(() => {
212
- result.current.commandQueue.enqueue(createMessageCommand("queued"));
213
- result.current.runManager.cancel();
214
- });
215
-
216
- await waitFor(() => {
217
- expect(onCancel).toHaveBeenCalledTimes(1);
218
- });
219
- expect(onCancel.mock.calls[0]?.[0]).toHaveLength(2);
220
- });
221
-
222
- it("unmount aborts the in-flight run without invoking callbacks", async () => {
223
- let aborted = false;
224
- const onCancel = vi.fn();
225
- const onError = vi.fn();
226
- const onFinish = vi.fn();
227
- const { result, unmount } = renderHook(() =>
228
- useTransportSchedulingHarness({
229
- onRun: (signal) =>
230
- new Promise<void>((_resolve, reject) => {
231
- signal.addEventListener(
232
- "abort",
233
- () => {
234
- aborted = true;
235
- reject(new Error("aborted"));
236
- },
237
- { once: true },
238
- );
239
- }),
240
- onCancel,
241
- onError,
242
- onFinish,
243
- }),
244
- );
245
-
246
- act(() => {
247
- result.current.commandQueue.enqueue(createMessageCommand("in-flight"));
248
- });
249
- await waitFor(() => {
250
- expect(result.current.runBatchesRef.current).toHaveLength(1);
251
- });
252
-
253
- unmount();
254
-
255
- expect(aborted).toBe(true);
256
- await act(async () => {});
257
- expect(onCancel).not.toHaveBeenCalled();
258
- expect(onError).not.toHaveBeenCalled();
259
- expect(onFinish).not.toHaveBeenCalled();
260
- });
261
-
262
- it("ignores schedules after unmount", async () => {
263
- const { result, unmount } = renderHook(() =>
264
- useTransportSchedulingHarness(),
265
- );
266
-
267
- unmount();
268
- act(() => {
269
- result.current.commandQueue.enqueue(createMessageCommand("late"));
270
- });
271
-
272
- await act(async () => {});
273
- expect(result.current.runBatchesRef.current).toHaveLength(0);
274
- });
275
-
276
- it("survives StrictMode double-mounting", async () => {
277
- const gate = createDeferred();
278
- const onFinish = vi.fn();
279
- const { result } = renderHook(
280
- () =>
281
- useTransportSchedulingHarness({ onRun: () => gate.promise, onFinish }),
282
- { wrapper: ({ children }) => createElement(StrictMode, null, children) },
283
- );
284
-
285
- act(() => {
286
- result.current.commandQueue.enqueue(createMessageCommand("m1"));
287
- });
288
- await waitFor(() => {
289
- expect(result.current.runBatchesRef.current).toHaveLength(1);
290
- });
291
-
292
- // enqueued while the first run is in flight — must restart as a follow-up
293
- act(() => {
294
- result.current.commandQueue.enqueue(createMessageCommand("m2"));
295
- });
296
-
297
- gate.resolve();
298
- await waitFor(() => {
299
- expect(result.current.runBatchesRef.current).toHaveLength(2);
300
- });
301
- expect(result.current.runBatchesRef.current[1]).toHaveLength(1);
302
- expect(onFinish).toHaveBeenCalled();
303
- await waitFor(() => {
304
- expect(result.current.runManager.isRunning).toBe(false);
305
- });
306
- });
307
- });
@@ -1,170 +0,0 @@
1
- import type { ToolModelContentPart } from "assistant-stream";
2
- import type { ThreadMessage } from "@assistant-ui/core";
3
- import type { ReadonlyJSONValue } from "assistant-stream/utils";
4
- import type {
5
- AttachmentAdapter,
6
- ThreadHistoryAdapter,
7
- LanguageModelV1CallSettings,
8
- LanguageModelConfig,
9
- } from "@assistant-ui/core";
10
- import type { UserCommands } from "../../../augmentations";
11
- import type { ToolExecutionStatus } from "@assistant-ui/core";
12
-
13
- // Message part types
14
- export type TextPart = {
15
- readonly type: "text";
16
- readonly text: string;
17
- };
18
-
19
- export type ImagePart = {
20
- readonly type: "image";
21
- readonly image: string;
22
- };
23
-
24
- export type UserMessagePart = TextPart | ImagePart;
25
-
26
- export type UserMessage = {
27
- readonly role: "user";
28
- readonly parts: readonly UserMessagePart[];
29
- };
30
-
31
- export type AssistantMessage = {
32
- readonly role: "assistant";
33
- readonly parts: readonly TextPart[];
34
- };
35
-
36
- // Command types
37
- export type AddMessageCommand = {
38
- readonly type: "add-message";
39
- readonly message: UserMessage | AssistantMessage;
40
- readonly parentId: string | null;
41
- readonly sourceId: string | null;
42
- };
43
-
44
- export type AddToolResultCommand = {
45
- readonly type: "add-tool-result";
46
- readonly toolCallId: string;
47
- readonly toolName: string;
48
- readonly result: ReadonlyJSONValue;
49
- readonly isError: boolean;
50
- readonly artifact?: ReadonlyJSONValue;
51
- readonly modelContent?: readonly ToolModelContentPart[];
52
- };
53
-
54
- export type AssistantTransportCommand =
55
- | AddMessageCommand
56
- | AddToolResultCommand
57
- | UserCommands;
58
-
59
- // State types
60
- export type AssistantTransportState = {
61
- readonly messages: readonly ThreadMessage[];
62
- readonly state?: ReadonlyJSONValue;
63
- readonly isRunning: boolean;
64
- };
65
-
66
- export type AssistantTransportConnectionMetadata = {
67
- pendingCommands: AssistantTransportCommand[];
68
- isSending: boolean;
69
- toolStatuses: Record<string, ToolExecutionStatus>;
70
- };
71
-
72
- export type AssistantTransportStateConverter<T> = (
73
- state: T,
74
- connectionMetadata: AssistantTransportConnectionMetadata,
75
- ) => AssistantTransportState;
76
-
77
- // Queue types
78
- export type CommandQueueState = {
79
- queued: AssistantTransportCommand[];
80
- inTransit: AssistantTransportCommand[];
81
- };
82
-
83
- // For now, queued items are plain commands (runConfig not supported)
84
- export type QueuedCommand = AssistantTransportCommand;
85
-
86
- // Task types
87
-
88
- // Options types
89
- export type HeadersValue = Record<string, string> | Headers;
90
-
91
- export type AssistantTransportProtocol = "data-stream" | "assistant-transport";
92
-
93
- export type SendCommandsRequestBody = {
94
- commands: QueuedCommand[];
95
- /** Absent on a resume with `resumeStateApi`; the server replays from its retained snapshot. */
96
- state?: unknown;
97
- runId?: string;
98
- system: string | undefined;
99
- tools: Record<string, unknown> | undefined;
100
- callSettings: LanguageModelV1CallSettings | undefined;
101
- config: LanguageModelConfig | undefined;
102
- threadId: string | null;
103
- parentId?: string | null;
104
- // `callSettings` and `config` fields are also spread at the top level for
105
- // backward compatibility (e.g. `body.modelName`). Use the nested objects
106
- // instead. The top-level fields will be removed in a future version.
107
- [key: string]: unknown;
108
- };
109
-
110
- export type AssistantTransportOptions<T> = {
111
- initialState: T;
112
- api: string;
113
- resumeApi?: string;
114
- /** Endpoint that returns the retained initial state and run ID for a resume stream. A 204 response means no run is active and the resume is skipped. */
115
- resumeStateApi?: string;
116
- protocol?: AssistantTransportProtocol;
117
- /**
118
- * When `false`, stream decoding and state reconciliation tolerate malformed
119
- * input (invalid chunks are dropped with a console log) instead of throwing.
120
- * Resume runs always decode leniently. Defaults to `true`.
121
- */
122
- strict?: boolean;
123
- converter: AssistantTransportStateConverter<T>;
124
- headers: HeadersValue | (() => Promise<HeadersValue>);
125
- body?: object | (() => Promise<object | undefined>);
126
- /**
127
- * Transform the request body before it is sent to the API.
128
- * Receives the fully assembled body and returns the (potentially transformed) body.
129
- *
130
- * @example
131
- * ```ts
132
- * prepareSendCommandsRequest: (body) => ({
133
- * ...body,
134
- * trackingId: crypto.randomUUID(),
135
- * })
136
- * ```
137
- */
138
- prepareSendCommandsRequest?: (
139
- body: SendCommandsRequestBody,
140
- ) => Record<string, unknown> | Promise<Record<string, unknown>>;
141
- onResponse?: (response: Response) => void;
142
- onFinish?: () => void;
143
- onError?: (
144
- error: Error,
145
- params: {
146
- commands: AssistantTransportCommand[];
147
- updateState: (updater: (state: T) => T) => void;
148
- },
149
- ) => void | Promise<void>;
150
- /**
151
- * Called when commands are cancelled.
152
- *
153
- * When an error occurs, queued commands are automatically cancelled after `onError` settles.
154
- * In this case, the `error` parameter contains the error that caused the cancellation.
155
- */
156
- onCancel?: (params: {
157
- commands: AssistantTransportCommand[];
158
- updateState: (updater: (state: T) => T) => void;
159
- error?: Error;
160
- }) => void;
161
- capabilities?: {
162
- edit?: boolean;
163
- };
164
- adapters?: {
165
- attachments?: AttachmentAdapter | undefined;
166
- history?: ThreadHistoryAdapter | undefined;
167
- };
168
- };
169
-
170
- // (no task or stream-specific types needed in this module)