@assistant-ui/react 0.14.13 → 0.14.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.
- package/README.md +5 -1
- package/dist/client/ExternalThread.d.ts +2 -12
- package/dist/client/ExternalThread.d.ts.map +1 -1
- package/dist/client/ExternalThread.js +30 -29
- package/dist/client/ExternalThread.js.map +1 -1
- package/dist/client/InMemoryThreadList.d.ts.map +1 -1
- package/dist/client/InMemoryThreadList.js +24 -13
- package/dist/client/InMemoryThreadList.js.map +1 -1
- package/dist/client/SingleThreadList.d.ts.map +1 -1
- package/dist/client/SingleThreadList.js +12 -8
- package/dist/client/SingleThreadList.js.map +1 -1
- package/dist/context/providers/ThreadViewportProvider.js +1 -1
- package/dist/context/providers/ThreadViewportProvider.js.map +1 -1
- package/dist/context/react/ThreadViewportContext.js +1 -1
- package/dist/context/react/utils/createContextHook.js +1 -1
- package/dist/context/react/utils/ensureBinding.js.map +1 -1
- package/dist/context/react/utils/useRuntimeState.js +1 -1
- package/dist/context/stores/ThreadViewport.js.map +1 -1
- package/dist/devtools/DevToolsHooks.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
- package/dist/legacy-runtime/cloud/auiV0.js +1 -1
- package/dist/legacy-runtime/hooks/AssistantContext.js.map +1 -1
- package/dist/legacy-runtime/hooks/AttachmentContext.js.map +1 -1
- package/dist/legacy-runtime/hooks/ComposerContext.js.map +1 -1
- package/dist/legacy-runtime/hooks/MessageContext.js.map +1 -1
- package/dist/legacy-runtime/hooks/MessagePartContext.js.map +1 -1
- package/dist/legacy-runtime/hooks/ThreadContext.js +1 -1
- package/dist/legacy-runtime/hooks/ThreadContext.js.map +1 -1
- package/dist/legacy-runtime/hooks/ThreadListItemContext.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts +14 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts.map +1 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +101 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +1 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +13 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +1 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +1 -1
- package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +7 -7
- package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +5 -4
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
- package/dist/mcp-apps/app-frame.d.ts +1 -1
- package/dist/mcp-apps/app-frame.d.ts.map +1 -1
- package/dist/mcp-apps/app-frame.js +82 -104
- package/dist/mcp-apps/app-frame.js.map +1 -1
- package/dist/mcp-apps/bridge.d.ts +3 -3
- package/dist/mcp-apps/bridge.d.ts.map +1 -1
- package/dist/mcp-apps/bridge.js +35 -10
- package/dist/mcp-apps/bridge.js.map +1 -1
- package/dist/mcp-apps/types.d.ts +2 -12
- package/dist/mcp-apps/types.d.ts.map +1 -1
- package/dist/mcp-apps/types.js.map +1 -1
- package/dist/model-context/frame/useAssistantFrameHost.js +1 -1
- package/dist/model-context/makeAssistantVisible.js +1 -1
- package/dist/model-context/makeAssistantVisible.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js.map +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackNegative.js +1 -1
- package/dist/primitives/actionBar/ActionBarFeedbackPositive.js +1 -1
- package/dist/primitives/actionBar/ActionBarInteractionContext.js +1 -1
- package/dist/primitives/actionBar/ActionBarRoot.js +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreContent.js +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreItem.js +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreRoot.js +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreSeparator.js +1 -1
- package/dist/primitives/actionBarMore/ActionBarMoreTrigger.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalAnchor.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalContent.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalTrigger.js +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js.map +1 -1
- package/dist/primitives/attachment/AttachmentRoot.js +1 -1
- package/dist/primitives/attachment/AttachmentThumb.js +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.js +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtRoot.js +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js.map +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.js +1 -1
- package/dist/primitives/composer/ComposerInput.js +1 -1
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerInputPluginContext.js +1 -1
- package/dist/primitives/composer/ComposerQuote.js +1 -1
- package/dist/primitives/composer/ComposerQuote.js.map +1 -1
- package/dist/primitives/composer/ComposerRoot.js +1 -1
- package/dist/primitives/composer/ComposerSend.js +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +2 -2
- package/dist/primitives/composer/trigger/TriggerPopover.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverAction.js +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverBack.js +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverCategories.js +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverItems.js +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js +8 -7
- package/dist/primitives/composer/trigger/TriggerPopoverResource.js.map +1 -1
- package/dist/primitives/composer/trigger/TriggerPopoverRootContext.js +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerDetectionResource.js +5 -4
- package/dist/primitives/composer/trigger/triggerDetectionResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js +8 -7
- package/dist/primitives/composer/trigger/triggerKeyboardResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerNavigationResource.js +13 -12
- package/dist/primitives/composer/trigger/triggerNavigationResource.js.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +7 -6
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/error/ErrorMessage.js +1 -1
- package/dist/primitives/error/ErrorRoot.js +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js.map +1 -1
- package/dist/primitives/message/MessageRoot.js +1 -1
- package/dist/primitives/message/MessageRoot.js.map +1 -1
- package/dist/primitives/messagePart/MessagePartImage.js +1 -1
- package/dist/primitives/messagePart/MessagePartText.js +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js.map +1 -1
- package/dist/primitives/queueItem/QueueItemText.js +1 -1
- package/dist/primitives/reasoning/useScrollLock.js +1 -1
- package/dist/primitives/reasoning/useScrollLock.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js.map +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarRoot.js.map +1 -1
- package/dist/primitives/suggestion/SuggestionDescription.js +1 -1
- package/dist/primitives/suggestion/SuggestionTitle.js +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js +1 -1
- package/dist/primitives/suggestion/SuggestionTrigger.js.map +1 -1
- package/dist/primitives/thread/ThreadRoot.js +1 -1
- package/dist/primitives/thread/ThreadScrollToBottom.js +1 -1
- package/dist/primitives/thread/ThreadScrollToBottom.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +1 -1
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.js +1 -1
- package/dist/primitives/thread/ThreadViewportFooter.js.map +1 -1
- package/dist/primitives/thread/topAnchor/topAnchorTurn.js.map +1 -1
- package/dist/primitives/thread/topAnchor/topAnchorUtils.js.map +1 -1
- package/dist/primitives/thread/topAnchor/useTopAnchorReserve.js +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +1 -1
- package/dist/primitives/threadList/ThreadListRoot.js +1 -1
- package/dist/primitives/threadListItem/ThreadListItemRoot.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreContent.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreItem.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreSeparator.js +1 -1
- package/dist/primitives/threadListItemMore/ThreadListItemMoreTrigger.js +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts +50 -0
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -0
- package/dist/sandbox-host/SandboxHost.js +85 -0
- package/dist/sandbox-host/SandboxHost.js.map +1 -0
- package/dist/unstable/useMentionAdapter.d.ts +2 -2
- package/dist/unstable/useMentionAdapter.js +2 -2
- package/dist/unstable/useMentionAdapter.js.map +1 -1
- package/dist/unstable/useSlashCommandAdapter.js +1 -1
- package/dist/unstable/useSlashCommandAdapter.js.map +1 -1
- package/dist/utils/Primitive.js +1 -1
- package/dist/utils/createActionButton.js +1 -1
- package/dist/utils/createActionButton.js.map +1 -1
- package/dist/utils/hooks/useManagedRef.js +1 -1
- package/dist/utils/hooks/useMediaQuery.js +1 -1
- package/dist/utils/hooks/useMediaQuery.js.map +1 -1
- package/dist/utils/hooks/useOnResizeContent.js +1 -1
- package/dist/utils/hooks/useOnScrollToBottom.js +1 -1
- package/dist/utils/hooks/useSizeHandle.js +1 -1
- package/dist/utils/json/is-json.js.map +1 -1
- package/dist/utils/smooth/SmoothContext.js +1 -1
- package/dist/utils/smooth/SmoothContext.js.map +1 -1
- package/dist/utils/smooth/useSmooth.js +1 -1
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/package.json +21 -20
- package/src/client/ExternalThread.ts +484 -515
- package/src/client/InMemoryThreadList.ts +154 -142
- package/src/client/SingleThreadList.ts +88 -81
- package/src/context/providers/ThreadViewportProvider.tsx +2 -2
- package/src/index.ts +18 -3
- package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +426 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +146 -0
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +16 -1
- package/src/mcp-apps/McpAppRenderer.tsx +28 -35
- package/src/mcp-apps/McpAppsRemoteHost.ts +25 -24
- package/src/mcp-apps/app-frame.tsx +100 -141
- package/src/mcp-apps/bridge.test.ts +100 -60
- package/src/mcp-apps/bridge.ts +43 -21
- package/src/mcp-apps/types.ts +2 -12
- package/src/primitives/composer/trigger/TriggerPopover.tsx +1 -1
- package/src/primitives/composer/trigger/TriggerPopoverResource.ts +75 -76
- package/src/primitives/composer/trigger/triggerDetectionResource.ts +6 -5
- package/src/primitives/composer/trigger/triggerKeyboardResource.ts +9 -13
- package/src/primitives/composer/trigger/triggerNavigationResource.ts +14 -19
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +8 -7
- package/src/sandbox-host/SandboxHost.test.tsx +231 -0
- package/src/sandbox-host/SandboxHost.tsx +185 -0
- package/src/tests/local-runtime-queue.test.tsx +305 -0
- package/src/unstable/useMentionAdapter.ts +2 -2
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AssistantMessageAccumulator,
|
|
5
|
+
DataStreamDecoder,
|
|
6
|
+
} from "assistant-stream";
|
|
7
|
+
import { act, renderHook } from "@testing-library/react";
|
|
8
|
+
import { describe, expect, it, vi } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
createReplayBoundaryStream,
|
|
11
|
+
REPLAY_CONTENT_LENGTH_HEADER,
|
|
12
|
+
useReplayRenderWait,
|
|
13
|
+
} from "./replayBoundaryStream";
|
|
14
|
+
|
|
15
|
+
const encoder = new TextEncoder();
|
|
16
|
+
const decoder = new TextDecoder();
|
|
17
|
+
|
|
18
|
+
const createBody = (chunks: readonly string[]) =>
|
|
19
|
+
new ReadableStream<Uint8Array>({
|
|
20
|
+
start(controller) {
|
|
21
|
+
for (const chunk of chunks) {
|
|
22
|
+
controller.enqueue(encoder.encode(chunk));
|
|
23
|
+
}
|
|
24
|
+
controller.close();
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const createResponse = (
|
|
29
|
+
chunks: readonly string[],
|
|
30
|
+
replayContentLength?: number | string,
|
|
31
|
+
) =>
|
|
32
|
+
new Response(createBody(chunks), {
|
|
33
|
+
headers:
|
|
34
|
+
replayContentLength === undefined
|
|
35
|
+
? undefined
|
|
36
|
+
: { [REPLAY_CONTENT_LENGTH_HEADER]: String(replayContentLength) },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const createRenderWait = () => {
|
|
40
|
+
const pending: Array<() => void> = [];
|
|
41
|
+
const waitForRender = vi.fn(
|
|
42
|
+
() =>
|
|
43
|
+
new Promise<void>((resolve) => {
|
|
44
|
+
pending.push(resolve);
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const releaseNext = async () => {
|
|
49
|
+
for (let i = 0; pending.length === 0 && i < 10; i++) {
|
|
50
|
+
await Promise.resolve();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const resolve = pending.shift();
|
|
54
|
+
expect(resolve).toBeDefined();
|
|
55
|
+
resolve!();
|
|
56
|
+
await Promise.resolve();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return { waitForRender, releaseNext };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const readText = async (stream: ReadableStream<Uint8Array>) => {
|
|
63
|
+
const reader = stream.getReader();
|
|
64
|
+
const chunks: string[] = [];
|
|
65
|
+
|
|
66
|
+
while (true) {
|
|
67
|
+
const { done, value } = await reader.read();
|
|
68
|
+
if (done) break;
|
|
69
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
70
|
+
}
|
|
71
|
+
chunks.push(decoder.decode());
|
|
72
|
+
|
|
73
|
+
return chunks.join("");
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
describe("useReplayRenderWait", () => {
|
|
77
|
+
it("resolves after its own render ticket commits", async () => {
|
|
78
|
+
vi.useFakeTimers();
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const { result } = renderHook(() => useReplayRenderWait());
|
|
82
|
+
|
|
83
|
+
let resolved = false;
|
|
84
|
+
const wait = result.current().then(() => {
|
|
85
|
+
resolved = true;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await Promise.resolve();
|
|
89
|
+
expect(resolved).toBe(false);
|
|
90
|
+
|
|
91
|
+
await act(async () => {
|
|
92
|
+
vi.runOnlyPendingTimers();
|
|
93
|
+
});
|
|
94
|
+
await wait;
|
|
95
|
+
|
|
96
|
+
expect(resolved).toBe(true);
|
|
97
|
+
} finally {
|
|
98
|
+
vi.useRealTimers();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("createReplayBoundaryStream", () => {
|
|
104
|
+
it("short-circuits responses without a valid replay content length", async () => {
|
|
105
|
+
const setReplaying = vi.fn();
|
|
106
|
+
const waitForRender = vi.fn();
|
|
107
|
+
|
|
108
|
+
for (const replayContentLength of [undefined, "abc", "3.5", "-1"]) {
|
|
109
|
+
setReplaying.mockClear();
|
|
110
|
+
waitForRender.mockClear();
|
|
111
|
+
|
|
112
|
+
const body = await createReplayBoundaryStream(
|
|
113
|
+
createResponse(["live"], replayContentLength),
|
|
114
|
+
{
|
|
115
|
+
setReplaying,
|
|
116
|
+
waitForRender,
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
expect(await readText(body)).toBe("live");
|
|
121
|
+
expect(setReplaying).not.toHaveBeenCalled();
|
|
122
|
+
expect(waitForRender).not.toHaveBeenCalled();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("pauses at the replay boundary before releasing live bytes", async () => {
|
|
127
|
+
const { waitForRender, releaseNext } = createRenderWait();
|
|
128
|
+
const setReplaying = vi.fn();
|
|
129
|
+
const replayPrefix = '0:"hi"\nb:{"toolCallId":"call-1","toolName":"te';
|
|
130
|
+
const liveSuffix = 'st"}\n';
|
|
131
|
+
const replayContentLength = encoder.encode(replayPrefix).byteLength;
|
|
132
|
+
|
|
133
|
+
const streamPromise = createReplayBoundaryStream(
|
|
134
|
+
createResponse([replayPrefix + liveSuffix], replayContentLength),
|
|
135
|
+
{ setReplaying, waitForRender },
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
expect(setReplaying).toHaveBeenCalledWith(true);
|
|
139
|
+
expect(waitForRender).toHaveBeenCalledTimes(1);
|
|
140
|
+
|
|
141
|
+
await releaseNext();
|
|
142
|
+
const stream = await streamPromise;
|
|
143
|
+
const reader = stream.getReader();
|
|
144
|
+
|
|
145
|
+
await expect(reader.read()).resolves.toMatchObject({
|
|
146
|
+
done: false,
|
|
147
|
+
value: encoder.encode(replayPrefix),
|
|
148
|
+
});
|
|
149
|
+
expect(waitForRender).toHaveBeenCalledTimes(2);
|
|
150
|
+
expect(setReplaying).toHaveBeenCalledTimes(1);
|
|
151
|
+
|
|
152
|
+
let liveReadResolved = false;
|
|
153
|
+
const liveRead = reader.read().then((read) => {
|
|
154
|
+
liveReadResolved = true;
|
|
155
|
+
return read;
|
|
156
|
+
});
|
|
157
|
+
await Promise.resolve();
|
|
158
|
+
expect(liveReadResolved).toBe(false);
|
|
159
|
+
|
|
160
|
+
await releaseNext();
|
|
161
|
+
expect(setReplaying).toHaveBeenLastCalledWith(false);
|
|
162
|
+
expect(waitForRender).toHaveBeenCalledTimes(3);
|
|
163
|
+
await Promise.resolve();
|
|
164
|
+
expect(liveReadResolved).toBe(false);
|
|
165
|
+
|
|
166
|
+
await releaseNext();
|
|
167
|
+
await expect(liveRead).resolves.toMatchObject({
|
|
168
|
+
done: false,
|
|
169
|
+
value: encoder.encode(liveSuffix),
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("pauses when a chunk ends exactly at the replay boundary", async () => {
|
|
174
|
+
const { waitForRender, releaseNext } = createRenderWait();
|
|
175
|
+
const setReplaying = vi.fn();
|
|
176
|
+
const replayPrefix = "replay";
|
|
177
|
+
const liveSuffix = "live";
|
|
178
|
+
const replayContentLength = encoder.encode(replayPrefix).byteLength;
|
|
179
|
+
|
|
180
|
+
const streamPromise = createReplayBoundaryStream(
|
|
181
|
+
createResponse([replayPrefix, liveSuffix], replayContentLength),
|
|
182
|
+
{ setReplaying, waitForRender },
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
await releaseNext();
|
|
186
|
+
const stream = await streamPromise;
|
|
187
|
+
const reader = stream.getReader();
|
|
188
|
+
|
|
189
|
+
await expect(reader.read()).resolves.toMatchObject({
|
|
190
|
+
done: false,
|
|
191
|
+
value: encoder.encode(replayPrefix),
|
|
192
|
+
});
|
|
193
|
+
expect(setReplaying).toHaveBeenCalledTimes(1);
|
|
194
|
+
|
|
195
|
+
let liveReadResolved = false;
|
|
196
|
+
const liveRead = reader.read().then((read) => {
|
|
197
|
+
liveReadResolved = true;
|
|
198
|
+
return read;
|
|
199
|
+
});
|
|
200
|
+
await Promise.resolve();
|
|
201
|
+
expect(liveReadResolved).toBe(false);
|
|
202
|
+
|
|
203
|
+
await releaseNext();
|
|
204
|
+
expect(setReplaying).toHaveBeenLastCalledWith(false);
|
|
205
|
+
await releaseNext();
|
|
206
|
+
|
|
207
|
+
await expect(liveRead).resolves.toMatchObject({
|
|
208
|
+
done: false,
|
|
209
|
+
value: encoder.encode(liveSuffix),
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("accumulates replay bytes across chunks before splitting live bytes", async () => {
|
|
214
|
+
const { waitForRender, releaseNext } = createRenderWait();
|
|
215
|
+
const setReplaying = vi.fn();
|
|
216
|
+
const firstReplayChunk = "part1";
|
|
217
|
+
const secondReplayChunk = "part2";
|
|
218
|
+
const firstLiveChunk = "live1";
|
|
219
|
+
const secondLiveChunk = "live2";
|
|
220
|
+
const replayContentLength = encoder.encode(
|
|
221
|
+
firstReplayChunk + secondReplayChunk,
|
|
222
|
+
).byteLength;
|
|
223
|
+
|
|
224
|
+
const streamPromise = createReplayBoundaryStream(
|
|
225
|
+
createResponse(
|
|
226
|
+
[firstReplayChunk, secondReplayChunk + firstLiveChunk, secondLiveChunk],
|
|
227
|
+
replayContentLength,
|
|
228
|
+
),
|
|
229
|
+
{ setReplaying, waitForRender },
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
await releaseNext();
|
|
233
|
+
const stream = await streamPromise;
|
|
234
|
+
const reader = stream.getReader();
|
|
235
|
+
|
|
236
|
+
await expect(reader.read()).resolves.toMatchObject({
|
|
237
|
+
done: false,
|
|
238
|
+
value: encoder.encode(firstReplayChunk),
|
|
239
|
+
});
|
|
240
|
+
await expect(reader.read()).resolves.toMatchObject({
|
|
241
|
+
done: false,
|
|
242
|
+
value: encoder.encode(secondReplayChunk),
|
|
243
|
+
});
|
|
244
|
+
expect(setReplaying).toHaveBeenCalledTimes(1);
|
|
245
|
+
|
|
246
|
+
let liveReadResolved = false;
|
|
247
|
+
const liveRead = reader.read().then((read) => {
|
|
248
|
+
liveReadResolved = true;
|
|
249
|
+
return read;
|
|
250
|
+
});
|
|
251
|
+
await Promise.resolve();
|
|
252
|
+
expect(liveReadResolved).toBe(false);
|
|
253
|
+
|
|
254
|
+
await releaseNext();
|
|
255
|
+
expect(setReplaying).toHaveBeenLastCalledWith(false);
|
|
256
|
+
await releaseNext();
|
|
257
|
+
|
|
258
|
+
await expect(liveRead).resolves.toMatchObject({
|
|
259
|
+
done: false,
|
|
260
|
+
value: encoder.encode(firstLiveChunk),
|
|
261
|
+
});
|
|
262
|
+
await expect(reader.read()).resolves.toMatchObject({
|
|
263
|
+
done: false,
|
|
264
|
+
value: encoder.encode(secondLiveChunk),
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("clears replaying when the stream ends before the boundary", async () => {
|
|
269
|
+
const { waitForRender, releaseNext } = createRenderWait();
|
|
270
|
+
const setReplaying = vi.fn();
|
|
271
|
+
const streamPromise = createReplayBoundaryStream(
|
|
272
|
+
createResponse(["hi"], 10),
|
|
273
|
+
{
|
|
274
|
+
setReplaying,
|
|
275
|
+
waitForRender,
|
|
276
|
+
},
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
await releaseNext();
|
|
280
|
+
const stream = await streamPromise;
|
|
281
|
+
const text = readText(stream);
|
|
282
|
+
await releaseNext();
|
|
283
|
+
await releaseNext();
|
|
284
|
+
|
|
285
|
+
await expect(text).resolves.toBe("hi");
|
|
286
|
+
expect(setReplaying).toHaveBeenLastCalledWith(false);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("clears replaying when the gated stream is cancelled", async () => {
|
|
290
|
+
const { waitForRender, releaseNext } = createRenderWait();
|
|
291
|
+
const setReplaying = vi.fn();
|
|
292
|
+
let cancelled = false;
|
|
293
|
+
const body = new ReadableStream<Uint8Array>({
|
|
294
|
+
start(controller) {
|
|
295
|
+
controller.enqueue(encoder.encode("hi"));
|
|
296
|
+
},
|
|
297
|
+
cancel() {
|
|
298
|
+
cancelled = true;
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
const streamPromise = createReplayBoundaryStream(
|
|
302
|
+
new Response(body, { headers: { [REPLAY_CONTENT_LENGTH_HEADER]: "10" } }),
|
|
303
|
+
{ setReplaying, waitForRender },
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
await releaseNext();
|
|
307
|
+
const stream = await streamPromise;
|
|
308
|
+
await stream.cancel("done");
|
|
309
|
+
|
|
310
|
+
expect(setReplaying).toHaveBeenLastCalledWith(false);
|
|
311
|
+
expect(cancelled).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("does not wait for replay completion after cancellation unblocks a read", async () => {
|
|
315
|
+
const { waitForRender, releaseNext } = createRenderWait();
|
|
316
|
+
const setReplaying = vi.fn();
|
|
317
|
+
let cancelled = false;
|
|
318
|
+
const body = new ReadableStream<Uint8Array>({
|
|
319
|
+
cancel() {
|
|
320
|
+
cancelled = true;
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
const streamPromise = createReplayBoundaryStream(
|
|
324
|
+
new Response(body, { headers: { [REPLAY_CONTENT_LENGTH_HEADER]: "10" } }),
|
|
325
|
+
{ setReplaying, waitForRender },
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
await releaseNext();
|
|
329
|
+
const stream = await streamPromise;
|
|
330
|
+
const reader = stream.getReader();
|
|
331
|
+
const read = reader.read();
|
|
332
|
+
|
|
333
|
+
await Promise.resolve();
|
|
334
|
+
expect(waitForRender).toHaveBeenCalledTimes(1);
|
|
335
|
+
|
|
336
|
+
await reader.cancel("done");
|
|
337
|
+
await expect(read).resolves.toMatchObject({ done: true });
|
|
338
|
+
|
|
339
|
+
expect(waitForRender).toHaveBeenCalledTimes(1);
|
|
340
|
+
expect(setReplaying).toHaveBeenLastCalledWith(false);
|
|
341
|
+
expect(cancelled).toBe(true);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("does not clear replaying twice when cancelled during replay completion", async () => {
|
|
345
|
+
const { waitForRender, releaseNext } = createRenderWait();
|
|
346
|
+
const setReplaying = vi.fn();
|
|
347
|
+
const replayStr = "replay";
|
|
348
|
+
const replayContentLength = encoder.encode(replayStr).byteLength;
|
|
349
|
+
|
|
350
|
+
const streamPromise = createReplayBoundaryStream(
|
|
351
|
+
createResponse([replayStr], replayContentLength),
|
|
352
|
+
{ setReplaying, waitForRender },
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
await releaseNext();
|
|
356
|
+
const stream = await streamPromise;
|
|
357
|
+
const reader = stream.getReader();
|
|
358
|
+
|
|
359
|
+
await expect(reader.read()).resolves.toMatchObject({
|
|
360
|
+
done: false,
|
|
361
|
+
value: encoder.encode(replayStr),
|
|
362
|
+
});
|
|
363
|
+
expect(waitForRender).toHaveBeenCalledTimes(2);
|
|
364
|
+
|
|
365
|
+
const cancel = reader.cancel("done");
|
|
366
|
+
await Promise.resolve();
|
|
367
|
+
expect(setReplaying).toHaveBeenCalledTimes(1);
|
|
368
|
+
|
|
369
|
+
await releaseNext();
|
|
370
|
+
expect(setReplaying).toHaveBeenCalledTimes(2);
|
|
371
|
+
expect(setReplaying).toHaveBeenLastCalledWith(false);
|
|
372
|
+
|
|
373
|
+
await releaseNext();
|
|
374
|
+
await cancel;
|
|
375
|
+
expect(setReplaying).toHaveBeenCalledTimes(2);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("lets data-stream parsing commit replayed text before live tool calls", async () => {
|
|
379
|
+
const { waitForRender, releaseNext } = createRenderWait();
|
|
380
|
+
const setReplaying = vi.fn();
|
|
381
|
+
const replayPrefix = '0:"hi"\nb:{"toolCallId":"call-1","toolName":"te';
|
|
382
|
+
const liveSuffix = 'st"}\n';
|
|
383
|
+
const replayContentLength = encoder.encode(replayPrefix).byteLength;
|
|
384
|
+
|
|
385
|
+
const streamPromise = createReplayBoundaryStream(
|
|
386
|
+
createResponse([replayPrefix + liveSuffix], replayContentLength),
|
|
387
|
+
{ setReplaying, waitForRender },
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
await releaseNext();
|
|
391
|
+
const messages = (await streamPromise)
|
|
392
|
+
.pipeThrough(new DataStreamDecoder())
|
|
393
|
+
.pipeThrough(new AssistantMessageAccumulator({ throttle: true }));
|
|
394
|
+
const reader = messages.getReader();
|
|
395
|
+
|
|
396
|
+
let sawReplayedText = false;
|
|
397
|
+
while (!sawReplayedText) {
|
|
398
|
+
const replayedMessage = await reader.read();
|
|
399
|
+
expect(replayedMessage.done).toBe(false);
|
|
400
|
+
expect(
|
|
401
|
+
replayedMessage.value?.parts.some((part) => part.type === "tool-call"),
|
|
402
|
+
).toBe(false);
|
|
403
|
+
sawReplayedText =
|
|
404
|
+
replayedMessage.value?.parts.some(
|
|
405
|
+
(part) => part.type === "text" && part.text === "hi",
|
|
406
|
+
) ?? false;
|
|
407
|
+
}
|
|
408
|
+
expect(setReplaying).toHaveBeenCalledTimes(1);
|
|
409
|
+
|
|
410
|
+
const liveMessagePromise = reader.read();
|
|
411
|
+
await releaseNext();
|
|
412
|
+
expect(setReplaying).toHaveBeenLastCalledWith(false);
|
|
413
|
+
await releaseNext();
|
|
414
|
+
|
|
415
|
+
let liveMessage = await liveMessagePromise;
|
|
416
|
+
while (
|
|
417
|
+
!liveMessage.done &&
|
|
418
|
+
!liveMessage.value?.parts.some(
|
|
419
|
+
(part) => part.type === "tool-call" && part.toolName === "test",
|
|
420
|
+
)
|
|
421
|
+
) {
|
|
422
|
+
liveMessage = await reader.read();
|
|
423
|
+
}
|
|
424
|
+
expect(liveMessage.done).toBe(false);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export const REPLAY_CONTENT_LENGTH_HEADER = "Aui-Replay-Content-Length";
|
|
6
|
+
|
|
7
|
+
type ReplayBoundaryStreamOptions = {
|
|
8
|
+
setReplaying: (value: boolean) => void;
|
|
9
|
+
waitForRender: () => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useReplayRenderWait = () => {
|
|
13
|
+
const [renderTicket, setRenderTicket] = useState(0);
|
|
14
|
+
const mountedRef = useRef(true);
|
|
15
|
+
const nextTicketRef = useRef(0);
|
|
16
|
+
const waitersRef = useRef<Array<{ ticket: number; resolve: () => void }>>([]);
|
|
17
|
+
|
|
18
|
+
const resolveWaiters = useCallback((committedTicket?: number) => {
|
|
19
|
+
const pendingWaiters = [];
|
|
20
|
+
|
|
21
|
+
for (const waiter of waitersRef.current) {
|
|
22
|
+
if (committedTicket === undefined || waiter.ticket <= committedTicket) {
|
|
23
|
+
waiter.resolve();
|
|
24
|
+
} else {
|
|
25
|
+
pendingWaiters.push(waiter);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
waitersRef.current = pendingWaiters;
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
mountedRef.current = true;
|
|
34
|
+
resolveWaiters(renderTicket);
|
|
35
|
+
}, [renderTicket, resolveWaiters]);
|
|
36
|
+
|
|
37
|
+
useEffect(
|
|
38
|
+
() => () => {
|
|
39
|
+
mountedRef.current = false;
|
|
40
|
+
resolveWaiters();
|
|
41
|
+
},
|
|
42
|
+
[resolveWaiters],
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return useCallback(
|
|
46
|
+
() =>
|
|
47
|
+
new Promise<void>((resolve) => {
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
if (!mountedRef.current) {
|
|
50
|
+
resolve();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ticket = nextTicketRef.current + 1;
|
|
55
|
+
nextTicketRef.current = ticket;
|
|
56
|
+
waitersRef.current.push({ ticket, resolve });
|
|
57
|
+
setRenderTicket(ticket);
|
|
58
|
+
}, 0);
|
|
59
|
+
}),
|
|
60
|
+
[],
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const parseReplayContentLength = (headers: Headers): number => {
|
|
65
|
+
const raw = headers.get(REPLAY_CONTENT_LENGTH_HEADER);
|
|
66
|
+
if (raw == null) return 0;
|
|
67
|
+
|
|
68
|
+
const boundary = Number(raw);
|
|
69
|
+
return Number.isSafeInteger(boundary) && boundary > 0 ? boundary : 0;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Gates replay bytes until isReplaying:true renders, then releases live bytes after isReplaying:false renders.
|
|
73
|
+
export const createReplayBoundaryStream = async (
|
|
74
|
+
response: Response,
|
|
75
|
+
{
|
|
76
|
+
setReplaying,
|
|
77
|
+
waitForRender: waitForReplayRender,
|
|
78
|
+
}: ReplayBoundaryStreamOptions,
|
|
79
|
+
): Promise<ReadableStream<Uint8Array>> => {
|
|
80
|
+
const body = response.body as ReadableStream<Uint8Array>;
|
|
81
|
+
const replayContentLength = parseReplayContentLength(response.headers);
|
|
82
|
+
|
|
83
|
+
if (replayContentLength <= 0) {
|
|
84
|
+
return body;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setReplaying(true);
|
|
88
|
+
await waitForReplayRender();
|
|
89
|
+
|
|
90
|
+
const reader = body.getReader();
|
|
91
|
+
let bytesForwarded = 0;
|
|
92
|
+
let replayFinished = false;
|
|
93
|
+
|
|
94
|
+
const finishReplay = async () => {
|
|
95
|
+
if (replayFinished) return;
|
|
96
|
+
replayFinished = true;
|
|
97
|
+
|
|
98
|
+
// Let replay bytes drain before rendering live mode, then render live mode before releasing live bytes.
|
|
99
|
+
await waitForReplayRender();
|
|
100
|
+
setReplaying(false);
|
|
101
|
+
await waitForReplayRender();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return new ReadableStream<Uint8Array>({
|
|
105
|
+
async pull(controller) {
|
|
106
|
+
const { done, value } = await reader.read();
|
|
107
|
+
|
|
108
|
+
if (done) {
|
|
109
|
+
await finishReplay();
|
|
110
|
+
controller.close();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (replayFinished) {
|
|
115
|
+
controller.enqueue(value);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const nextBytesForwarded = bytesForwarded + value.byteLength;
|
|
120
|
+
|
|
121
|
+
if (nextBytesForwarded < replayContentLength) {
|
|
122
|
+
bytesForwarded = nextBytesForwarded;
|
|
123
|
+
controller.enqueue(value);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (nextBytesForwarded === replayContentLength) {
|
|
128
|
+
controller.enqueue(value);
|
|
129
|
+
await finishReplay();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const replayBytesInChunk = replayContentLength - bytesForwarded;
|
|
134
|
+
|
|
135
|
+
controller.enqueue(value.subarray(0, replayBytesInChunk));
|
|
136
|
+
await finishReplay();
|
|
137
|
+
controller.enqueue(value.subarray(replayBytesInChunk));
|
|
138
|
+
},
|
|
139
|
+
async cancel(reason) {
|
|
140
|
+
const wasFinished = replayFinished;
|
|
141
|
+
replayFinished = true;
|
|
142
|
+
if (!wasFinished) setReplaying(false);
|
|
143
|
+
await reader.cancel(reason);
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
};
|
package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts
CHANGED
|
@@ -27,6 +27,10 @@ import type {
|
|
|
27
27
|
SendCommandsRequestBody,
|
|
28
28
|
} from "./types";
|
|
29
29
|
import { useCommandQueue } from "./commandQueue";
|
|
30
|
+
import {
|
|
31
|
+
createReplayBoundaryStream,
|
|
32
|
+
useReplayRenderWait,
|
|
33
|
+
} from "./replayBoundaryStream";
|
|
30
34
|
import { useRunManager } from "./runManager";
|
|
31
35
|
import { useConvertedState } from "./useConvertedState";
|
|
32
36
|
import type { ToolExecutionStatus } from "@assistant-ui/core";
|
|
@@ -116,6 +120,8 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
116
120
|
const agentStateRef = useRef(options.initialState);
|
|
117
121
|
const [, rerender] = useState(0);
|
|
118
122
|
const resumeFlagRef = useRef(false);
|
|
123
|
+
const [isReplaying, setIsReplaying] = useState(false);
|
|
124
|
+
const waitForReplayRender = useReplayRenderWait();
|
|
119
125
|
const parentIdRef = useRef<string | null | undefined>(undefined);
|
|
120
126
|
const commandQueue = useCommandQueue({
|
|
121
127
|
onQueue: () => runManager.schedule(),
|
|
@@ -127,6 +133,7 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
127
133
|
onRun: async (signal: AbortSignal) => {
|
|
128
134
|
const isResume = resumeFlagRef.current;
|
|
129
135
|
resumeFlagRef.current = false;
|
|
136
|
+
setIsReplaying(false);
|
|
130
137
|
const commands: QueuedCommand[] = isResume ? [] : commandQueue.flush();
|
|
131
138
|
if (commands.length === 0 && !isResume)
|
|
132
139
|
throw new Error("No commands to send");
|
|
@@ -182,6 +189,11 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
182
189
|
throw new Error("Response body is null");
|
|
183
190
|
}
|
|
184
191
|
|
|
192
|
+
const body = await createReplayBoundaryStream(response, {
|
|
193
|
+
setReplaying: setIsReplaying,
|
|
194
|
+
waitForRender: waitForReplayRender,
|
|
195
|
+
});
|
|
196
|
+
|
|
185
197
|
// Select decoder based on protocol option
|
|
186
198
|
const protocol = options.protocol ?? "data-stream";
|
|
187
199
|
const decoder =
|
|
@@ -190,7 +202,7 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
190
202
|
: new DataStreamDecoder();
|
|
191
203
|
|
|
192
204
|
let err: string | undefined;
|
|
193
|
-
const stream =
|
|
205
|
+
const stream = body.pipeThrough(decoder).pipeThrough(
|
|
194
206
|
new AssistantMessageAccumulator({
|
|
195
207
|
initialMessage: createInitialMessage({
|
|
196
208
|
unstable_state:
|
|
@@ -223,6 +235,7 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
223
235
|
},
|
|
224
236
|
onFinish: options.onFinish,
|
|
225
237
|
onCancel: () => {
|
|
238
|
+
setIsReplaying(false);
|
|
226
239
|
const cmds = [
|
|
227
240
|
...commandQueue.state.inTransit,
|
|
228
241
|
...commandQueue.state.queued,
|
|
@@ -239,6 +252,7 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
239
252
|
});
|
|
240
253
|
},
|
|
241
254
|
onError: async (error) => {
|
|
255
|
+
setIsReplaying(false);
|
|
242
256
|
const inTransitCmds = [...commandQueue.state.inTransit];
|
|
243
257
|
const queuedCmds = [...commandQueue.state.queued];
|
|
244
258
|
|
|
@@ -288,6 +302,7 @@ const useAssistantTransportThreadRuntime = <T>(
|
|
|
288
302
|
messages: converted.messages,
|
|
289
303
|
state: converted.state,
|
|
290
304
|
isRunning: converted.isRunning,
|
|
305
|
+
isLoading: isReplaying,
|
|
291
306
|
adapters: options.adapters,
|
|
292
307
|
unstable_enableToolInvocations: true,
|
|
293
308
|
setToolStatuses,
|