@assistant-ui/react 0.15.14 → 0.15.16

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 (210) 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/devtools/DevToolsHooks.d.ts +0 -2
  7. package/dist/devtools/DevToolsHooks.d.ts.map +1 -1
  8. package/dist/devtools/DevToolsHooks.js +10 -12
  9. package/dist/devtools/DevToolsHooks.js.map +1 -1
  10. package/dist/hooks/useMessageQuote.js +1 -1
  11. package/dist/hooks/useToolCallElapsed.js +1 -1
  12. package/dist/index.d.ts +4 -7
  13. package/dist/index.js +6 -8
  14. package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
  15. package/dist/mcp-apps/McpAppRenderer.js +1 -1
  16. package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
  17. package/dist/mcp-apps/McpAppsRemoteHost.js +18 -2
  18. package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
  19. package/dist/mcp-apps/bridge.d.ts.map +1 -1
  20. package/dist/mcp-apps/bridge.js +33 -24
  21. package/dist/mcp-apps/bridge.js.map +1 -1
  22. package/dist/model-context/makeAssistantVisible.js +1 -1
  23. package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
  24. package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts.map +1 -1
  25. package/dist/primitives/actionBar/ActionBarExportMarkdown.js +5 -2
  26. package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
  27. package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
  28. package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
  29. package/dist/primitives/actionBar/ActionBarStopSpeaking.d.ts.map +1 -1
  30. package/dist/primitives/actionBar/ActionBarStopSpeaking.js +24 -37
  31. package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
  32. package/dist/primitives/actionBar/useActionBarFloatStatus.js +1 -1
  33. package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
  34. package/dist/primitives/attachment/AttachmentName.js +1 -1
  35. package/dist/primitives/attachment/AttachmentRemove.js +1 -1
  36. package/dist/primitives/attachment/AttachmentThumb.js +1 -1
  37. package/dist/primitives/branchPicker/BranchPickerCount.js +1 -1
  38. package/dist/primitives/branchPicker/BranchPickerNumber.js +1 -1
  39. package/dist/primitives/branchPicker/BranchPickerRoot.d.ts.map +1 -1
  40. package/dist/primitives/branchPicker/BranchPickerRoot.js +20 -15
  41. package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
  42. package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
  43. package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
  44. package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
  45. package/dist/primitives/composer/ComposerDictationTranscript.d.ts +2 -2
  46. package/dist/primitives/composer/ComposerDictationTranscript.js +3 -3
  47. package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
  48. package/dist/primitives/composer/ComposerInput.js +2 -2
  49. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  50. package/dist/primitives/composer/ComposerQuote.js +1 -1
  51. package/dist/primitives/composer/ComposerRoot.js +1 -1
  52. package/dist/primitives/composer/ComposerStopDictation.js +1 -1
  53. package/dist/primitives/composer/trigger/TriggerPopover.js +1 -1
  54. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  55. package/dist/primitives/composer/trigger/triggerSelectionResource.js +24 -20
  56. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  57. package/dist/primitives/composer/useComposerInputState.js +1 -1
  58. package/dist/primitives/message/MessageIf.js +1 -1
  59. package/dist/primitives/message/MessagePartsGrouped.js +1 -1
  60. package/dist/primitives/message/MessageRoot.js +1 -1
  61. package/dist/primitives/message.js +1 -1
  62. package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
  63. package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
  64. package/dist/primitives/queueItem/QueueItemText.js +1 -1
  65. package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
  66. package/dist/primitives/suggestion/SuggestionDescription.js +1 -1
  67. package/dist/primitives/suggestion/SuggestionTitle.js +1 -1
  68. package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
  69. package/dist/primitives/thread/ThreadIf.js +1 -1
  70. package/dist/primitives/thread/ThreadRoot.d.ts +3 -0
  71. package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
  72. package/dist/primitives/thread/ThreadRoot.js +44 -9
  73. package/dist/primitives/thread/ThreadRoot.js.map +1 -1
  74. package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
  75. package/dist/primitives/thread/ThreadViewport.js +2 -13
  76. package/dist/primitives/thread/ThreadViewport.js.map +1 -1
  77. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  78. package/dist/primitives/thread/useThreadViewportAutoScroll.js +23 -8
  79. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  80. package/dist/primitives/threadList/ThreadListNew.js +1 -1
  81. package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
  82. package/dist/sandbox-host/SandboxHost.js +6 -1
  83. package/dist/sandbox-host/SandboxHost.js.map +1 -1
  84. package/dist/unstable/useComposerInput.d.ts +2 -2
  85. package/dist/unstable/useComposerInput.js +1 -1
  86. package/dist/unstable/useComposerInput.js.map +1 -1
  87. package/dist/unstable/useComposerInputHistory.js +2 -2
  88. package/dist/unstable/useComposerInputHistory.js.map +1 -1
  89. package/dist/unstable/useLiveCompletionAdapter.d.ts +5 -0
  90. package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
  91. package/dist/unstable/useLiveCompletionAdapter.js +16 -3
  92. package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
  93. package/dist/unstable/useMentionAdapter.d.ts +1 -1
  94. package/dist/utils/invokeCallbackSafely.d.ts +5 -0
  95. package/dist/utils/invokeCallbackSafely.d.ts.map +1 -0
  96. package/dist/utils/invokeCallbackSafely.js +15 -0
  97. package/dist/utils/invokeCallbackSafely.js.map +1 -0
  98. package/dist/utils/smooth/SmoothContext.d.ts +6 -6
  99. package/dist/utils/smooth/SmoothContext.js +1 -1
  100. package/dist/utils/smooth/useSmooth.d.ts +3 -0
  101. package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
  102. package/dist/utils/smooth/useSmooth.js +16 -1
  103. package/dist/utils/smooth/useSmooth.js.map +1 -1
  104. package/dist/utils/useToolArgsFieldStatus.js +1 -1
  105. package/package.json +10 -10
  106. package/src/assistant-transport.ts +94 -0
  107. package/src/devtools/DevToolsHooks.test.ts +40 -0
  108. package/src/devtools/DevToolsHooks.ts +10 -15
  109. package/src/index.ts +8 -4
  110. package/src/mcp-apps/McpAppsRemoteHost.test.ts +29 -3
  111. package/src/mcp-apps/McpAppsRemoteHost.ts +42 -7
  112. package/src/mcp-apps/bridge.test.ts +153 -0
  113. package/src/mcp-apps/bridge.ts +42 -30
  114. package/src/model-context/frame/SPEC_AssistantFrame.md +1 -1
  115. package/src/primitives/actionBar/ActionBarExportMarkdown.test.tsx +67 -0
  116. package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +3 -1
  117. package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +0 -9
  118. package/src/primitives/branchPicker/BranchPickerRoot.tsx +5 -3
  119. package/src/primitives/composer/ComposerDictationTranscript.tsx +2 -2
  120. package/src/primitives/composer/ComposerInput.tsx +1 -1
  121. package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +135 -0
  122. package/src/primitives/composer/trigger/triggerSelectionResource.ts +2 -0
  123. package/src/primitives/thread/ThreadRoot.test.tsx +251 -0
  124. package/src/primitives/thread/ThreadRoot.tsx +33 -0
  125. package/src/primitives/thread/ThreadViewport.tsx +1 -8
  126. package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +152 -0
  127. package/src/primitives/thread/useThreadViewportAutoScroll.ts +26 -19
  128. package/src/sandbox-host/SandboxHost.test.tsx +90 -0
  129. package/src/sandbox-host/SandboxHost.tsx +8 -2
  130. package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +24 -3
  131. package/src/tests/RemoteThreadListRuntime.reloadMainThread.test.tsx +1 -2
  132. package/src/tests/augmentations.test.ts +49 -0
  133. package/src/tests/in-memory-thread-list.test.tsx +1 -1
  134. package/src/tests/threadMessageById.test.tsx +61 -1
  135. package/src/unstable/useComposerInput.ts +2 -2
  136. package/src/unstable/useComposerInputHistory.test.tsx +4 -4
  137. package/src/unstable/useComposerInputHistory.ts +1 -1
  138. package/src/unstable/useLiveCompletionAdapter.test.tsx +157 -0
  139. package/src/unstable/useLiveCompletionAdapter.ts +41 -17
  140. package/src/utils/invokeCallbackSafely.ts +11 -0
  141. package/src/utils/smooth/useSmooth.test.tsx +110 -0
  142. package/src/utils/smooth/useSmooth.ts +20 -6
  143. package/dist/client/InMemoryThreadList.d.ts +0 -12
  144. package/dist/client/InMemoryThreadList.d.ts.map +0 -1
  145. package/dist/client/InMemoryThreadList.js +0 -374
  146. package/dist/client/InMemoryThreadList.js.map +0 -1
  147. package/dist/context/react/utils/ensureBinding.d.ts +0 -5
  148. package/dist/context/react/utils/ensureBinding.d.ts.map +0 -1
  149. package/dist/context/react/utils/ensureBinding.js +0 -24
  150. package/dist/context/react/utils/ensureBinding.js.map +0 -1
  151. package/dist/context/react/utils/useRuntimeState.d.ts +0 -13
  152. package/dist/context/react/utils/useRuntimeState.d.ts.map +0 -1
  153. package/dist/context/react/utils/useRuntimeState.js +0 -33
  154. package/dist/context/react/utils/useRuntimeState.js.map +0 -1
  155. package/dist/legacy-runtime/cloud/auiV0.d.ts +0 -119
  156. package/dist/legacy-runtime/cloud/auiV0.d.ts.map +0 -1
  157. package/dist/legacy-runtime/cloud/auiV0.js +0 -137
  158. package/dist/legacy-runtime/cloud/auiV0.js.map +0 -1
  159. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts +0 -16
  160. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts.map +0 -1
  161. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js +0 -36
  162. package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js.map +0 -1
  163. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +0 -17
  164. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +0 -1
  165. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +0 -55
  166. package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +0 -1
  167. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts +0 -11
  168. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts.map +0 -1
  169. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +0 -165
  170. package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +0 -1
  171. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts +0 -15
  172. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +0 -1
  173. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +0 -81
  174. package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +0 -1
  175. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +0 -126
  176. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +0 -1
  177. package/dist/legacy-runtime/runtime-cores/assistant-transport/types.js +0 -0
  178. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts +0 -14
  179. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +0 -1
  180. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +0 -290
  181. package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +0 -1
  182. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +0 -7
  183. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +0 -1
  184. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +0 -25
  185. package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +0 -1
  186. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts +0 -7
  187. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts.map +0 -1
  188. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +0 -27
  189. package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js.map +0 -1
  190. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts +0 -2
  191. package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js +0 -2
  192. package/src/client/InMemoryThreadList.ts +0 -247
  193. package/src/context/react/utils/ensureBinding.ts +0 -46
  194. package/src/context/react/utils/useRuntimeState.ts +0 -46
  195. package/src/legacy-runtime/cloud/auiV0.ts +0 -331
  196. package/src/legacy-runtime/cloud/useCloudThreadListRuntime.ts +0 -31
  197. package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +0 -68
  198. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +0 -455
  199. package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +0 -172
  200. package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +0 -113
  201. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +0 -79
  202. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +0 -97
  203. package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +0 -307
  204. package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +0 -170
  205. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +0 -141
  206. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +0 -585
  207. package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +0 -476
  208. package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +0 -20
  209. package/src/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.ts +0 -9
  210. package/src/legacy-runtime/runtime-cores/assistant-transport/utils.ts +0 -1
@@ -1,113 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import { useLatestRef } from "./useLatestRef";
3
-
4
- export type RunManager = Readonly<{
5
- isRunning: boolean;
6
- schedule: () => void;
7
- cancel: () => void;
8
- }>;
9
-
10
- const disposeReason = Symbol("assistant-transport-dispose");
11
-
12
- const reportFinishError = (error: unknown) => {
13
- console.error(
14
- "[assistant-ui] Assistant transport onFinish callback threw an error",
15
- error,
16
- );
17
- };
18
-
19
- export function useRunManager(config: {
20
- onRun: (signal: AbortSignal) => Promise<void>;
21
- onFinish?: (() => void) | undefined;
22
- onCancel?: (() => void) | undefined;
23
- onError?: ((error: Error) => void | Promise<void>) | undefined;
24
- }): RunManager {
25
- const [isRunning, setIsRunning] = useState(false);
26
- const stateRef = useRef({
27
- pending: false,
28
- disposed: false,
29
- abortController: null as AbortController | null,
30
- });
31
- const onRunRef = useLatestRef(config.onRun);
32
- const onFinishRef = useLatestRef(config.onFinish);
33
- const onCancelRef = useLatestRef(config.onCancel);
34
- const onErrorRef = useLatestRef(config.onError);
35
-
36
- const startRun = useCallback(() => {
37
- setIsRunning(true);
38
- stateRef.current.pending = false;
39
- const ac = new AbortController();
40
- stateRef.current.abortController = ac;
41
-
42
- // The dispose marker rides on the abort reason so a settling run detects
43
- // its own disposal even after an effect cycle re-arms the manager.
44
- const disposeAborted = () => ac.signal.reason === disposeReason;
45
-
46
- queueMicrotask(async () => {
47
- try {
48
- if (!disposeAborted()) {
49
- await onRunRef.current(ac.signal);
50
- }
51
- } catch (error) {
52
- if (!disposeAborted() && !stateRef.current.disposed) {
53
- stateRef.current.pending = false;
54
- if (ac.signal.aborted) {
55
- onCancelRef.current?.();
56
- } else {
57
- await onErrorRef.current?.(error as Error);
58
- }
59
- }
60
- } finally {
61
- if (!disposeAborted() && !stateRef.current.disposed) {
62
- try {
63
- void Promise.resolve(onFinishRef.current?.()).catch(
64
- reportFinishError,
65
- );
66
- } catch (error) {
67
- reportFinishError(error);
68
- }
69
- }
70
- if (!stateRef.current.disposed && stateRef.current.pending) {
71
- startRun();
72
- } else {
73
- setIsRunning(false);
74
- if (stateRef.current.abortController === ac) {
75
- stateRef.current.abortController = null;
76
- }
77
- }
78
- }
79
- });
80
- }, [onRunRef, onFinishRef, onErrorRef, onCancelRef]);
81
-
82
- const schedule = useCallback(() => {
83
- if (stateRef.current.disposed) return;
84
- if (stateRef.current.abortController) {
85
- // Coalesce multiple schedules while running into a single follow-up run.
86
- stateRef.current.pending = true;
87
- return;
88
- }
89
- startRun();
90
- }, [startRun]);
91
-
92
- // Strict-mode effects run setup / cleanup / setup on the same instance;
93
- // arming on setup undoes the cleanup's dispose.
94
- useEffect(() => {
95
- stateRef.current.disposed = false;
96
- return () => {
97
- stateRef.current.disposed = true;
98
- stateRef.current.pending = false;
99
- stateRef.current.abortController?.abort(disposeReason);
100
- };
101
- }, []);
102
-
103
- const cancel = useCallback(() => {
104
- stateRef.current.pending = false;
105
- stateRef.current.abortController?.abort();
106
- }, []);
107
-
108
- return {
109
- isRunning,
110
- schedule,
111
- cancel,
112
- };
113
- }
@@ -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
- });