@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.
- package/dist/assistant-transport.d.ts +32 -0
- package/dist/assistant-transport.d.ts.map +1 -0
- package/dist/assistant-transport.js +27 -0
- package/dist/assistant-transport.js.map +1 -0
- package/dist/context/providers/MessageProvider.js +1 -1
- package/dist/devtools/DevToolsHooks.d.ts +0 -2
- package/dist/devtools/DevToolsHooks.d.ts.map +1 -1
- package/dist/devtools/DevToolsHooks.js +10 -12
- package/dist/devtools/DevToolsHooks.js.map +1 -1
- package/dist/hooks/useMessageQuote.js +1 -1
- package/dist/hooks/useToolCallElapsed.js +1 -1
- package/dist/index.d.ts +4 -7
- package/dist/index.js +6 -8
- package/dist/legacy-runtime/AssistantRuntimeProvider.js +1 -1
- package/dist/mcp-apps/McpAppRenderer.js +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.d.ts.map +1 -1
- package/dist/mcp-apps/McpAppsRemoteHost.js +18 -2
- package/dist/mcp-apps/McpAppsRemoteHost.js.map +1 -1
- package/dist/mcp-apps/bridge.d.ts.map +1 -1
- package/dist/mcp-apps/bridge.js +33 -24
- package/dist/mcp-apps/bridge.js.map +1 -1
- package/dist/model-context/makeAssistantVisible.js +1 -1
- package/dist/primitives/actionBar/ActionBarCopy.js +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarExportMarkdown.js +5 -2
- 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/ActionBarStopSpeaking.d.ts.map +1 -1
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js +24 -37
- package/dist/primitives/actionBar/ActionBarStopSpeaking.js.map +1 -1
- package/dist/primitives/actionBar/useActionBarFloatStatus.js +1 -1
- package/dist/primitives/assistantModal/AssistantModalRoot.js +1 -1
- package/dist/primitives/attachment/AttachmentName.js +1 -1
- package/dist/primitives/attachment/AttachmentRemove.js +1 -1
- package/dist/primitives/attachment/AttachmentThumb.js +1 -1
- package/dist/primitives/branchPicker/BranchPickerCount.js +1 -1
- package/dist/primitives/branchPicker/BranchPickerNumber.js +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.d.ts.map +1 -1
- package/dist/primitives/branchPicker/BranchPickerRoot.js +20 -15
- package/dist/primitives/branchPicker/BranchPickerRoot.js.map +1 -1
- package/dist/primitives/chainOfThought/ChainOfThoughtAccordionTrigger.js +1 -1
- package/dist/primitives/composer/ComposerAddAttachment.js +1 -1
- package/dist/primitives/composer/ComposerAttachmentDropzone.js +1 -1
- package/dist/primitives/composer/ComposerDictationTranscript.d.ts +2 -2
- package/dist/primitives/composer/ComposerDictationTranscript.js +3 -3
- package/dist/primitives/composer/ComposerDictationTranscript.js.map +1 -1
- package/dist/primitives/composer/ComposerInput.js +2 -2
- package/dist/primitives/composer/ComposerInput.js.map +1 -1
- package/dist/primitives/composer/ComposerQuote.js +1 -1
- package/dist/primitives/composer/ComposerRoot.js +1 -1
- package/dist/primitives/composer/ComposerStopDictation.js +1 -1
- package/dist/primitives/composer/trigger/TriggerPopover.js +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
- package/dist/primitives/composer/trigger/triggerSelectionResource.js +24 -20
- package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
- package/dist/primitives/composer/useComposerInputState.js +1 -1
- package/dist/primitives/message/MessageIf.js +1 -1
- package/dist/primitives/message/MessagePartsGrouped.js +1 -1
- package/dist/primitives/message/MessageRoot.js +1 -1
- package/dist/primitives/message.js +1 -1
- package/dist/primitives/queueItem/QueueItemRemove.js +1 -1
- package/dist/primitives/queueItem/QueueItemSteer.js +1 -1
- package/dist/primitives/queueItem/QueueItemText.js +1 -1
- package/dist/primitives/selectionToolbar/SelectionToolbarQuote.js +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/thread/ThreadIf.js +1 -1
- package/dist/primitives/thread/ThreadRoot.d.ts +3 -0
- package/dist/primitives/thread/ThreadRoot.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadRoot.js +44 -9
- package/dist/primitives/thread/ThreadRoot.js.map +1 -1
- package/dist/primitives/thread/ThreadViewport.d.ts.map +1 -1
- package/dist/primitives/thread/ThreadViewport.js +2 -13
- package/dist/primitives/thread/ThreadViewport.js.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
- package/dist/primitives/thread/useThreadViewportAutoScroll.js +23 -8
- package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
- package/dist/primitives/threadList/ThreadListNew.js +1 -1
- package/dist/sandbox-host/SandboxHost.d.ts.map +1 -1
- package/dist/sandbox-host/SandboxHost.js +6 -1
- package/dist/sandbox-host/SandboxHost.js.map +1 -1
- package/dist/unstable/useComposerInput.d.ts +2 -2
- package/dist/unstable/useComposerInput.js +1 -1
- package/dist/unstable/useComposerInput.js.map +1 -1
- package/dist/unstable/useComposerInputHistory.js +2 -2
- package/dist/unstable/useComposerInputHistory.js.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.d.ts +5 -0
- package/dist/unstable/useLiveCompletionAdapter.d.ts.map +1 -1
- package/dist/unstable/useLiveCompletionAdapter.js +16 -3
- package/dist/unstable/useLiveCompletionAdapter.js.map +1 -1
- package/dist/unstable/useMentionAdapter.d.ts +1 -1
- package/dist/utils/invokeCallbackSafely.d.ts +5 -0
- package/dist/utils/invokeCallbackSafely.d.ts.map +1 -0
- package/dist/utils/invokeCallbackSafely.js +15 -0
- package/dist/utils/invokeCallbackSafely.js.map +1 -0
- package/dist/utils/smooth/SmoothContext.d.ts +6 -6
- package/dist/utils/smooth/SmoothContext.js +1 -1
- package/dist/utils/smooth/useSmooth.d.ts +3 -0
- package/dist/utils/smooth/useSmooth.d.ts.map +1 -1
- package/dist/utils/smooth/useSmooth.js +16 -1
- package/dist/utils/smooth/useSmooth.js.map +1 -1
- package/dist/utils/useToolArgsFieldStatus.js +1 -1
- package/package.json +10 -10
- package/src/assistant-transport.ts +94 -0
- package/src/devtools/DevToolsHooks.test.ts +40 -0
- package/src/devtools/DevToolsHooks.ts +10 -15
- package/src/index.ts +8 -4
- package/src/mcp-apps/McpAppsRemoteHost.test.ts +29 -3
- package/src/mcp-apps/McpAppsRemoteHost.ts +42 -7
- package/src/mcp-apps/bridge.test.ts +153 -0
- package/src/mcp-apps/bridge.ts +42 -30
- package/src/model-context/frame/SPEC_AssistantFrame.md +1 -1
- package/src/primitives/actionBar/ActionBarExportMarkdown.test.tsx +67 -0
- package/src/primitives/actionBar/ActionBarExportMarkdown.tsx +3 -1
- package/src/primitives/actionBar/ActionBarStopSpeaking.tsx +0 -9
- package/src/primitives/branchPicker/BranchPickerRoot.tsx +5 -3
- package/src/primitives/composer/ComposerDictationTranscript.tsx +2 -2
- package/src/primitives/composer/ComposerInput.tsx +1 -1
- package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +135 -0
- package/src/primitives/composer/trigger/triggerSelectionResource.ts +2 -0
- package/src/primitives/thread/ThreadRoot.test.tsx +251 -0
- package/src/primitives/thread/ThreadRoot.tsx +33 -0
- package/src/primitives/thread/ThreadViewport.tsx +1 -8
- package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +152 -0
- package/src/primitives/thread/useThreadViewportAutoScroll.ts +26 -19
- package/src/sandbox-host/SandboxHost.test.tsx +90 -0
- package/src/sandbox-host/SandboxHost.tsx +8 -2
- package/src/tests/RemoteThreadListRuntime.adapterProvider.test.tsx +24 -3
- package/src/tests/RemoteThreadListRuntime.reloadMainThread.test.tsx +1 -2
- package/src/tests/augmentations.test.ts +49 -0
- package/src/tests/in-memory-thread-list.test.tsx +1 -1
- package/src/tests/threadMessageById.test.tsx +61 -1
- package/src/unstable/useComposerInput.ts +2 -2
- package/src/unstable/useComposerInputHistory.test.tsx +4 -4
- package/src/unstable/useComposerInputHistory.ts +1 -1
- package/src/unstable/useLiveCompletionAdapter.test.tsx +157 -0
- package/src/unstable/useLiveCompletionAdapter.ts +41 -17
- package/src/utils/invokeCallbackSafely.ts +11 -0
- package/src/utils/smooth/useSmooth.test.tsx +110 -0
- package/src/utils/smooth/useSmooth.ts +20 -6
- package/dist/client/InMemoryThreadList.d.ts +0 -12
- package/dist/client/InMemoryThreadList.d.ts.map +0 -1
- package/dist/client/InMemoryThreadList.js +0 -374
- package/dist/client/InMemoryThreadList.js.map +0 -1
- package/dist/context/react/utils/ensureBinding.d.ts +0 -5
- package/dist/context/react/utils/ensureBinding.d.ts.map +0 -1
- package/dist/context/react/utils/ensureBinding.js +0 -24
- package/dist/context/react/utils/ensureBinding.js.map +0 -1
- package/dist/context/react/utils/useRuntimeState.d.ts +0 -13
- package/dist/context/react/utils/useRuntimeState.d.ts.map +0 -1
- package/dist/context/react/utils/useRuntimeState.js +0 -33
- package/dist/context/react/utils/useRuntimeState.js.map +0 -1
- package/dist/legacy-runtime/cloud/auiV0.d.ts +0 -119
- package/dist/legacy-runtime/cloud/auiV0.d.ts.map +0 -1
- package/dist/legacy-runtime/cloud/auiV0.js +0 -137
- package/dist/legacy-runtime/cloud/auiV0.js.map +0 -1
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts +0 -16
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.d.ts.map +0 -1
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js +0 -36
- package/dist/legacy-runtime/cloud/useCloudThreadListRuntime.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts +0 -17
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js +0 -55
- package/dist/legacy-runtime/runtime-cores/assistant-transport/commandQueue.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts +0 -11
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js +0 -165
- package/dist/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts +0 -15
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js +0 -81
- package/dist/legacy-runtime/runtime-cores/assistant-transport/runManager.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts +0 -126
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/types.js +0 -0
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts +0 -14
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js +0 -290
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts +0 -7
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js +0 -25
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts +0 -7
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.d.ts.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js +0 -27
- package/dist/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.js.map +0 -1
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.d.ts +0 -2
- package/dist/legacy-runtime/runtime-cores/assistant-transport/utils.js +0 -2
- package/src/client/InMemoryThreadList.ts +0 -247
- package/src/context/react/utils/ensureBinding.ts +0 -46
- package/src/context/react/utils/useRuntimeState.ts +0 -46
- package/src/legacy-runtime/cloud/auiV0.ts +0 -331
- package/src/legacy-runtime/cloud/useCloudThreadListRuntime.ts +0 -31
- package/src/legacy-runtime/runtime-cores/assistant-transport/commandQueue.ts +0 -68
- package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.test.ts +0 -455
- package/src/legacy-runtime/runtime-cores/assistant-transport/replayBoundaryStream.ts +0 -172
- package/src/legacy-runtime/runtime-cores/assistant-transport/runManager.ts +0 -113
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-delivery.test.tsx +0 -79
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-parentid.test.tsx +0 -97
- package/src/legacy-runtime/runtime-cores/assistant-transport/transport-scheduling.test.ts +0 -307
- package/src/legacy-runtime/runtime-cores/assistant-transport/types.ts +0 -170
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransport.spec.md +0 -141
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.test.tsx +0 -585
- package/src/legacy-runtime/runtime-cores/assistant-transport/useAssistantTransportRuntime.ts +0 -476
- package/src/legacy-runtime/runtime-cores/assistant-transport/useConvertedState.ts +0 -20
- package/src/legacy-runtime/runtime-cores/assistant-transport/useLatestRef.ts +0 -9
- package/src/legacy-runtime/runtime-cores/assistant-transport/utils.ts +0 -1
|
@@ -124,6 +124,53 @@ describe("createMcpAppBridge", () => {
|
|
|
124
124
|
bridge.dispose();
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
+
it.each([
|
|
128
|
+
[
|
|
129
|
+
"throws",
|
|
130
|
+
() => {
|
|
131
|
+
throw new Error("error callback failed");
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
["rejects", () => Promise.reject(new Error("error callback failed"))],
|
|
135
|
+
])(
|
|
136
|
+
"returns an error response when onError %s",
|
|
137
|
+
async (_behavior, onErrorCallback) => {
|
|
138
|
+
const { frame, captured } = makeFrame();
|
|
139
|
+
const callTool = vi.fn().mockRejectedValue(new Error("tool failed"));
|
|
140
|
+
const consoleError = vi
|
|
141
|
+
.spyOn(console, "error")
|
|
142
|
+
.mockImplementation(() => {});
|
|
143
|
+
const onError = vi.fn(onErrorCallback);
|
|
144
|
+
const bridge = createMcpAppBridge({
|
|
145
|
+
frame,
|
|
146
|
+
handlers: { callTool, onError },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
deliver(bridge, {
|
|
150
|
+
jsonrpc: "2.0",
|
|
151
|
+
id: 8,
|
|
152
|
+
method: "tools/call",
|
|
153
|
+
params: { name: "search" },
|
|
154
|
+
});
|
|
155
|
+
await flush();
|
|
156
|
+
|
|
157
|
+
expect(onError).toHaveBeenCalledWith(new Error("tool failed"));
|
|
158
|
+
expect(captured).toEqual([
|
|
159
|
+
{
|
|
160
|
+
jsonrpc: "2.0",
|
|
161
|
+
id: 8,
|
|
162
|
+
error: { code: -32603, message: "tool failed" },
|
|
163
|
+
},
|
|
164
|
+
]);
|
|
165
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
166
|
+
"[assistant-ui] MCP App onError callback threw an error",
|
|
167
|
+
new Error("error callback failed"),
|
|
168
|
+
);
|
|
169
|
+
consoleError.mockRestore();
|
|
170
|
+
bridge.dispose();
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
127
174
|
it("rejects tools/call for disallowed tool with -32602", async () => {
|
|
128
175
|
const { frame, captured } = makeFrame();
|
|
129
176
|
const callTool = vi.fn();
|
|
@@ -244,6 +291,60 @@ describe("createMcpAppBridge", () => {
|
|
|
244
291
|
bridge.dispose();
|
|
245
292
|
});
|
|
246
293
|
|
|
294
|
+
it("isolates throwing notification handlers and reports their errors", () => {
|
|
295
|
+
const { frame, captured } = makeFrame();
|
|
296
|
+
const initializedError = new Error("initialized callback failed");
|
|
297
|
+
const sizeChangeError = new Error("size change callback failed");
|
|
298
|
+
const teardownError = new Error("teardown callback failed");
|
|
299
|
+
const onError = vi.fn();
|
|
300
|
+
const bridge = createMcpAppBridge({
|
|
301
|
+
frame,
|
|
302
|
+
handlers: {
|
|
303
|
+
onInitialized: () => {
|
|
304
|
+
throw initializedError;
|
|
305
|
+
},
|
|
306
|
+
onSizeChange: () => {
|
|
307
|
+
throw sizeChangeError;
|
|
308
|
+
},
|
|
309
|
+
onLog: () => {
|
|
310
|
+
throw "log callback failed";
|
|
311
|
+
},
|
|
312
|
+
onRequestTeardown: () => {
|
|
313
|
+
throw teardownError;
|
|
314
|
+
},
|
|
315
|
+
onError,
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(() => {
|
|
320
|
+
deliver(bridge, {
|
|
321
|
+
jsonrpc: "2.0",
|
|
322
|
+
method: "notifications/initialized",
|
|
323
|
+
});
|
|
324
|
+
deliver(bridge, {
|
|
325
|
+
jsonrpc: "2.0",
|
|
326
|
+
method: "notifications/size_changed",
|
|
327
|
+
});
|
|
328
|
+
deliver(bridge, {
|
|
329
|
+
jsonrpc: "2.0",
|
|
330
|
+
method: "notifications/log",
|
|
331
|
+
});
|
|
332
|
+
deliver(bridge, {
|
|
333
|
+
jsonrpc: "2.0",
|
|
334
|
+
method: "notifications/request_teardown",
|
|
335
|
+
});
|
|
336
|
+
}).not.toThrow();
|
|
337
|
+
|
|
338
|
+
expect(onError.mock.calls.map(([error]) => error)).toEqual([
|
|
339
|
+
initializedError,
|
|
340
|
+
sizeChangeError,
|
|
341
|
+
new Error("log callback failed"),
|
|
342
|
+
teardownError,
|
|
343
|
+
]);
|
|
344
|
+
expect(captured).toEqual([]);
|
|
345
|
+
bridge.dispose();
|
|
346
|
+
});
|
|
347
|
+
|
|
247
348
|
it("notifyToolInput / notifyToolResult / notifyHostContextChanged post both legacy and 2026-01-26 notifications", () => {
|
|
248
349
|
const { frame, captured } = makeFrame();
|
|
249
350
|
const bridge = createMcpAppBridge({ frame });
|
|
@@ -428,4 +529,56 @@ describe("createMcpAppBridge", () => {
|
|
|
428
529
|
expect(onRequestTeardown).toHaveBeenCalledWith({ reason: "done" });
|
|
429
530
|
bridge.dispose();
|
|
430
531
|
});
|
|
532
|
+
|
|
533
|
+
it("isolates a throwing onError from widget error notifications", () => {
|
|
534
|
+
const { frame } = makeFrame();
|
|
535
|
+
const callbackError = new Error("error callback failed");
|
|
536
|
+
const consoleError = vi
|
|
537
|
+
.spyOn(console, "error")
|
|
538
|
+
.mockImplementation(() => {});
|
|
539
|
+
const onError = vi.fn(() => {
|
|
540
|
+
throw callbackError;
|
|
541
|
+
});
|
|
542
|
+
const bridge = createMcpAppBridge({
|
|
543
|
+
frame,
|
|
544
|
+
handlers: { callTool: vi.fn(), onError },
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
expect(() =>
|
|
548
|
+
deliver(bridge, {
|
|
549
|
+
jsonrpc: "2.0",
|
|
550
|
+
method: "notifications/error",
|
|
551
|
+
params: { message: "kaboom" },
|
|
552
|
+
}),
|
|
553
|
+
).not.toThrow();
|
|
554
|
+
|
|
555
|
+
expect(onError).toHaveBeenCalledWith(new Error("kaboom"));
|
|
556
|
+
expect(consoleError).toHaveBeenCalledWith(
|
|
557
|
+
"[assistant-ui] MCP App onError callback threw an error",
|
|
558
|
+
callbackError,
|
|
559
|
+
);
|
|
560
|
+
consoleError.mockRestore();
|
|
561
|
+
bridge.dispose();
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it("preserves the onError receiver", () => {
|
|
565
|
+
const { frame } = makeFrame();
|
|
566
|
+
const handlers = {
|
|
567
|
+
calls: 0,
|
|
568
|
+
onError() {
|
|
569
|
+
expect(this).toBe(handlers);
|
|
570
|
+
this.calls += 1;
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
const bridge = createMcpAppBridge({ frame, handlers });
|
|
574
|
+
|
|
575
|
+
deliver(bridge, {
|
|
576
|
+
jsonrpc: "2.0",
|
|
577
|
+
method: "notifications/error",
|
|
578
|
+
params: { message: "kaboom" },
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
expect(handlers.calls).toBe(1);
|
|
582
|
+
bridge.dispose();
|
|
583
|
+
});
|
|
431
584
|
});
|
package/src/mcp-apps/bridge.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SandboxHostFrame } from "../sandbox-host/SandboxHost";
|
|
2
|
+
import { invokeCallbackSafely } from "../utils/invokeCallbackSafely";
|
|
2
3
|
import {
|
|
3
4
|
MCP_APP_PROTOCOL_VERSION,
|
|
4
5
|
type McpAppBridgeHandlers,
|
|
@@ -123,6 +124,10 @@ export function createMcpAppBridge(
|
|
|
123
124
|
});
|
|
124
125
|
};
|
|
125
126
|
|
|
127
|
+
const reportError = (error: Error) => {
|
|
128
|
+
invokeCallbackSafely(() => handlers.onError?.(error), "MCP App onError");
|
|
129
|
+
};
|
|
130
|
+
|
|
126
131
|
const handleRequest = async (req: McpAppJsonRpcRequest) => {
|
|
127
132
|
try {
|
|
128
133
|
const params = req.params;
|
|
@@ -376,42 +381,49 @@ export function createMcpAppBridge(
|
|
|
376
381
|
}
|
|
377
382
|
} catch (err) {
|
|
378
383
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
379
|
-
|
|
384
|
+
reportError(error);
|
|
380
385
|
errorResponse(req.id, JSONRPC_ERROR.internalError, error.message);
|
|
381
386
|
}
|
|
382
387
|
};
|
|
383
388
|
|
|
384
389
|
const handleNotification = (note: McpAppJsonRpcNotification) => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
390
|
+
try {
|
|
391
|
+
switch (normalizeMethod(note.method)) {
|
|
392
|
+
case "notifications/initialized": {
|
|
393
|
+
handlers.onInitialized?.();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
case "notifications/size_changed": {
|
|
397
|
+
const p = (note.params ?? {}) as { width?: number; height?: number };
|
|
398
|
+
handlers.onSizeChange?.({
|
|
399
|
+
...(typeof p.width === "number" ? { width: p.width } : {}),
|
|
400
|
+
...(typeof p.height === "number" ? { height: p.height } : {}),
|
|
401
|
+
});
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
case "notifications/log": {
|
|
405
|
+
handlers.onLog?.(note.params);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
case "notifications/request_teardown": {
|
|
409
|
+
handlers.onRequestTeardown?.(note.params);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
case "notifications/error": {
|
|
413
|
+
const p = (note.params ?? {}) as { message?: string };
|
|
414
|
+
reportError(
|
|
415
|
+
new Error(
|
|
416
|
+
typeof p.message === "string" ? p.message : "Widget error",
|
|
417
|
+
),
|
|
418
|
+
);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
default:
|
|
422
|
+
return;
|
|
412
423
|
}
|
|
413
|
-
|
|
414
|
-
|
|
424
|
+
} catch (err) {
|
|
425
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
426
|
+
reportError(error);
|
|
415
427
|
}
|
|
416
428
|
};
|
|
417
429
|
|
|
@@ -88,7 +88,7 @@ All messages are wrapped with a channel identifier to avoid conflicts with other
|
|
|
88
88
|
|
|
89
89
|
#### Security Considerations
|
|
90
90
|
|
|
91
|
-
1. **Origin Validation**: Both sides can specify `targetOrigin` to restrict message sources
|
|
91
|
+
1. **Origin Validation**: Both sides can specify `targetOrigin` to restrict message sources; providers share a fail-closed origin policy
|
|
92
92
|
2. **Window Reference**: Host (parent) only accepts messages from the specific iframe window it's connected to
|
|
93
93
|
3. **Message Channel**: Using a unique channel identifier prevents cross-talk with other postMessage users
|
|
94
94
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import { act } from "react";
|
|
5
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
6
|
+
import type * as AssistantStore from "@assistant-ui/store";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import { ActionBarPrimitiveExportMarkdown } from "./ActionBarExportMarkdown";
|
|
9
|
+
|
|
10
|
+
const { getCopyText } = vi.hoisted(() => ({
|
|
11
|
+
getCopyText: vi.fn(() => "# Exported message"),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
(globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
|
|
15
|
+
|
|
16
|
+
vi.mock("@assistant-ui/store", async (importOriginal) => {
|
|
17
|
+
const actual = await importOriginal<typeof AssistantStore>();
|
|
18
|
+
return {
|
|
19
|
+
...actual,
|
|
20
|
+
useAui: () => ({ message: { getCopyText } }),
|
|
21
|
+
useAuiState: () => true,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("ActionBarPrimitiveExportMarkdown", () => {
|
|
26
|
+
let container: HTMLDivElement;
|
|
27
|
+
let root: Root;
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
getCopyText.mockClear();
|
|
31
|
+
container = document.createElement("div");
|
|
32
|
+
document.body.appendChild(container);
|
|
33
|
+
root = createRoot(container);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(async () => {
|
|
37
|
+
await act(async () => {
|
|
38
|
+
root.unmount();
|
|
39
|
+
});
|
|
40
|
+
container.remove();
|
|
41
|
+
vi.restoreAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("handles rejected asynchronous exports", async () => {
|
|
45
|
+
const error = new Error("remote save failed");
|
|
46
|
+
const onExport = vi.fn().mockRejectedValue(error);
|
|
47
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
48
|
+
|
|
49
|
+
await act(async () => {
|
|
50
|
+
root.render(
|
|
51
|
+
<ActionBarPrimitiveExportMarkdown onExport={onExport}>
|
|
52
|
+
Export
|
|
53
|
+
</ActionBarPrimitiveExportMarkdown>,
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await act(async () => {
|
|
58
|
+
container.querySelector("button")!.click();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(onExport).toHaveBeenCalledWith("# Exported message");
|
|
62
|
+
expect(errorSpy).toHaveBeenCalledWith(
|
|
63
|
+
"[assistant-ui] markdown export failed:",
|
|
64
|
+
error,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -61,7 +61,9 @@ export const ActionBarPrimitiveExportMarkdown = forwardRef<
|
|
|
61
61
|
ref={forwardedRef}
|
|
62
62
|
disabled={disabled || !callback}
|
|
63
63
|
onClick={composeEventHandlers(onClick, () => {
|
|
64
|
-
callback?.()
|
|
64
|
+
void callback?.().catch((error: unknown) => {
|
|
65
|
+
console.error("[assistant-ui] markdown export failed:", error);
|
|
66
|
+
});
|
|
65
67
|
})}
|
|
66
68
|
/>
|
|
67
69
|
);
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import type { ActionButtonProps } from "../../utils/createActionButton";
|
|
5
|
-
import { useEscapeKeydown } from "@radix-ui/react-use-escape-keydown";
|
|
6
5
|
import { Primitive } from "../../utils/Primitive";
|
|
7
6
|
import { composeEventHandlers } from "@radix-ui/primitive";
|
|
8
7
|
import { useActionBarStopSpeaking as useActionBarStopSpeakingBehavior } from "@assistant-ui/core/react";
|
|
@@ -24,14 +23,6 @@ export const ActionBarPrimitiveStopSpeaking = forwardRef<
|
|
|
24
23
|
>((props, ref) => {
|
|
25
24
|
const callback = useActionBarStopSpeaking();
|
|
26
25
|
|
|
27
|
-
// TODO this stops working if the user is not hovering over an older message
|
|
28
|
-
useEscapeKeydown((e) => {
|
|
29
|
-
if (callback) {
|
|
30
|
-
e.preventDefault();
|
|
31
|
-
callback();
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
26
|
return (
|
|
36
27
|
<Primitive.button
|
|
37
28
|
type="button"
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
forwardRef,
|
|
7
7
|
type ComponentPropsWithoutRef,
|
|
8
8
|
} from "react";
|
|
9
|
-
import {
|
|
9
|
+
import { AuiIf } from "@assistant-ui/store";
|
|
10
10
|
|
|
11
11
|
export namespace BranchPickerPrimitiveRoot {
|
|
12
12
|
export type Element = ComponentRef<typeof Primitive.div>;
|
|
@@ -42,9 +42,11 @@ export const BranchPickerPrimitiveRoot = forwardRef<
|
|
|
42
42
|
BranchPickerPrimitiveRoot.Props
|
|
43
43
|
>(({ hideWhenSingleBranch, ...rest }, ref) => {
|
|
44
44
|
return (
|
|
45
|
-
<
|
|
45
|
+
<AuiIf
|
|
46
|
+
condition={(s) => !hideWhenSingleBranch || s.message.branchCount >= 2}
|
|
47
|
+
>
|
|
46
48
|
<Primitive.div {...rest} ref={ref} />
|
|
47
|
-
</
|
|
49
|
+
</AuiIf>
|
|
48
50
|
);
|
|
49
51
|
});
|
|
50
52
|
|
|
@@ -21,11 +21,11 @@ export namespace ComposerPrimitiveDictationTranscript {
|
|
|
21
21
|
*
|
|
22
22
|
* @example
|
|
23
23
|
* ```tsx
|
|
24
|
-
* <
|
|
24
|
+
* <AuiIf condition={(s) => s.composer.dictation != null}>
|
|
25
25
|
* <div className="dictation-preview">
|
|
26
26
|
* <ComposerPrimitive.DictationTranscript />
|
|
27
27
|
* </div>
|
|
28
|
-
* </
|
|
28
|
+
* </AuiIf>
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
export const ComposerPrimitiveDictationTranscript = forwardRef<
|
|
@@ -346,7 +346,7 @@ export const ComposerPrimitiveInput = forwardRef<
|
|
|
346
346
|
)
|
|
347
347
|
return undefined;
|
|
348
348
|
|
|
349
|
-
return aui.on("
|
|
349
|
+
return aui.on("threads.selectionChanged", focus);
|
|
350
350
|
}, [unstable_focusOnThreadSwitched, focus, aui]);
|
|
351
351
|
|
|
352
352
|
useEffect(() => {
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { createTapRoot, flushTapSync, useResource } from "@assistant-ui/tap";
|
|
3
|
+
import type {
|
|
4
|
+
Unstable_DirectiveFormatter,
|
|
5
|
+
Unstable_TriggerItem,
|
|
6
|
+
} from "@assistant-ui/core";
|
|
7
|
+
import type { AssistantClient } from "@assistant-ui/store";
|
|
8
|
+
import { TriggerDetectionResource } from "./triggerDetectionResource";
|
|
9
|
+
import {
|
|
10
|
+
TriggerSelectionResource,
|
|
11
|
+
type TriggerBehavior,
|
|
12
|
+
} from "./triggerSelectionResource";
|
|
13
|
+
|
|
14
|
+
const item: Unstable_TriggerItem = {
|
|
15
|
+
id: "file",
|
|
16
|
+
type: "file",
|
|
17
|
+
label: "file.txt",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const formatter: Unstable_DirectiveFormatter = {
|
|
21
|
+
serialize: () => "@file.txt",
|
|
22
|
+
parse: () => [],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const makeAui = (textRef: { value: string }) => {
|
|
26
|
+
const setText = vi.fn((value: string) => {
|
|
27
|
+
textRef.value = value;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
aui: {
|
|
32
|
+
composer: {
|
|
33
|
+
getState: () => ({ text: textRef.value }),
|
|
34
|
+
setText,
|
|
35
|
+
},
|
|
36
|
+
} as unknown as AssistantClient,
|
|
37
|
+
setText,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const render = ({
|
|
42
|
+
behavior,
|
|
43
|
+
text,
|
|
44
|
+
trigger,
|
|
45
|
+
}: {
|
|
46
|
+
behavior: TriggerBehavior;
|
|
47
|
+
text: string;
|
|
48
|
+
trigger: { offset: number; query: string };
|
|
49
|
+
}) => {
|
|
50
|
+
const textRef = { value: text };
|
|
51
|
+
const { aui, setText } = makeAui(textRef);
|
|
52
|
+
const setCursorPosition = vi.fn();
|
|
53
|
+
const onSelected = vi.fn();
|
|
54
|
+
const root = createTapRoot(function Root() {
|
|
55
|
+
return useResource(
|
|
56
|
+
TriggerSelectionResource({
|
|
57
|
+
behavior,
|
|
58
|
+
trigger,
|
|
59
|
+
aui,
|
|
60
|
+
triggerChar: "@",
|
|
61
|
+
setCursorPosition,
|
|
62
|
+
onSelected,
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return { root, setText, setCursorPosition, onSelected };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
describe("TriggerSelectionResource", () => {
|
|
71
|
+
it("resynchronizes the cursor after inserting a directive", () => {
|
|
72
|
+
const { root, setText, setCursorPosition, onSelected } = render({
|
|
73
|
+
behavior: { kind: "directive", formatter },
|
|
74
|
+
text: "@file tail",
|
|
75
|
+
trigger: { offset: 0, query: "file" },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
root.getValue().selectItem(item);
|
|
79
|
+
|
|
80
|
+
expect(setText).toHaveBeenCalledWith("@file.txt tail");
|
|
81
|
+
expect(setCursorPosition).toHaveBeenCalledWith("@file.txt".length + 1);
|
|
82
|
+
expect(onSelected).toHaveBeenCalledOnce();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("resynchronizes the cursor after removing an action", () => {
|
|
86
|
+
const { root, setText, setCursorPosition } = render({
|
|
87
|
+
behavior: {
|
|
88
|
+
kind: "action",
|
|
89
|
+
formatter,
|
|
90
|
+
onExecute: vi.fn(),
|
|
91
|
+
removeOnExecute: true,
|
|
92
|
+
},
|
|
93
|
+
text: "before @file tail",
|
|
94
|
+
trigger: { offset: 7, query: "file" },
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
root.getValue().selectItem(item);
|
|
98
|
+
|
|
99
|
+
expect(setText).toHaveBeenCalledWith("before tail");
|
|
100
|
+
expect(setCursorPosition).toHaveBeenCalledWith(7);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("does not rewrite composer text on a second select after insertion", () => {
|
|
104
|
+
const textRef = { value: "@fil" };
|
|
105
|
+
const { aui, setText } = makeAui(textRef);
|
|
106
|
+
const onSelected = vi.fn();
|
|
107
|
+
const root = createTapRoot(function Root() {
|
|
108
|
+
const detection = useResource(
|
|
109
|
+
TriggerDetectionResource({
|
|
110
|
+
text: textRef.value,
|
|
111
|
+
triggerChar: "@",
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
return useResource(
|
|
115
|
+
TriggerSelectionResource({
|
|
116
|
+
behavior: { kind: "directive", formatter },
|
|
117
|
+
trigger: detection.trigger,
|
|
118
|
+
aui,
|
|
119
|
+
triggerChar: "@",
|
|
120
|
+
setCursorPosition: detection.setCursorPosition,
|
|
121
|
+
onSelected,
|
|
122
|
+
}),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
flushTapSync(() => {
|
|
127
|
+
root.getValue().selectItem(item);
|
|
128
|
+
});
|
|
129
|
+
root.getValue().selectItem(item);
|
|
130
|
+
|
|
131
|
+
expect(setText).toHaveBeenCalledOnce();
|
|
132
|
+
expect(setText).toHaveBeenCalledWith("@file.txt ");
|
|
133
|
+
expect(textRef.value).toBe("@file.txt ");
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -83,6 +83,7 @@ const useTriggerSelectionResource = ({
|
|
|
83
83
|
aui.composer.setText(
|
|
84
84
|
before + directive + (after.startsWith(" ") ? after : ` ${after}`),
|
|
85
85
|
);
|
|
86
|
+
setCursorPosition(before.length + directive.length + 1);
|
|
86
87
|
};
|
|
87
88
|
|
|
88
89
|
if (behavior.kind === "directive") {
|
|
@@ -93,6 +94,7 @@ const useTriggerSelectionResource = ({
|
|
|
93
94
|
aui.composer.setText(
|
|
94
95
|
before + (after.startsWith(" ") ? after.slice(1) : after),
|
|
95
96
|
);
|
|
97
|
+
setCursorPosition(before.length);
|
|
96
98
|
} else {
|
|
97
99
|
// Leave directive chip in the composer as an audit trail
|
|
98
100
|
insertDirective();
|