@assistant-ui/react 0.15.28 → 0.15.30

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 (71) hide show
  1. package/dist/adapters/OpenAIRealtimeVoiceAdapter.d.ts +22 -0
  2. package/dist/adapters/OpenAIRealtimeVoiceAdapter.d.ts.map +1 -0
  3. package/dist/adapters/OpenAIRealtimeVoiceAdapter.js +297 -0
  4. package/dist/adapters/OpenAIRealtimeVoiceAdapter.js.map +1 -0
  5. package/dist/context/stores/ThreadViewport.d.ts +8 -0
  6. package/dist/context/stores/ThreadViewport.d.ts.map +1 -1
  7. package/dist/context/stores/ThreadViewport.js +24 -8
  8. package/dist/context/stores/ThreadViewport.js.map +1 -1
  9. package/dist/index.d.ts +2 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -2
  12. package/dist/legacy-runtime/runtime-cores/utils/MessageRepository.js +1 -1
  13. package/dist/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  14. package/dist/mcp-apps/McpAppRenderer.js +5 -1
  15. package/dist/mcp-apps/McpAppRenderer.js.map +1 -1
  16. package/dist/model-context/frame/useAssistantFrameHost.d.ts.map +1 -1
  17. package/dist/model-context/frame/useAssistantFrameHost.js +67 -46
  18. package/dist/model-context/frame/useAssistantFrameHost.js.map +1 -1
  19. package/dist/model-context/makeAssistantVisible.js +1 -1
  20. package/dist/primitives/composer/ComposerAddAttachment.d.ts.map +1 -1
  21. package/dist/primitives/composer/ComposerAddAttachment.js +28 -10
  22. package/dist/primitives/composer/ComposerAddAttachment.js.map +1 -1
  23. package/dist/primitives/composer/ComposerInput.d.ts +4 -1
  24. package/dist/primitives/composer/ComposerInput.d.ts.map +1 -1
  25. package/dist/primitives/composer/ComposerInput.js +15 -2
  26. package/dist/primitives/composer/ComposerInput.js.map +1 -1
  27. package/dist/primitives/composer/ComposerResume.d.ts +23 -0
  28. package/dist/primitives/composer/ComposerResume.d.ts.map +1 -0
  29. package/dist/primitives/composer/ComposerResume.js +34 -0
  30. package/dist/primitives/composer/ComposerResume.js.map +1 -0
  31. package/dist/primitives/composer/trigger/TriggerPopoverAction.js +1 -1
  32. package/dist/primitives/composer/trigger/TriggerPopoverDirective.js +1 -1
  33. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts +6 -2
  34. package/dist/primitives/composer/trigger/triggerSelectionResource.d.ts.map +1 -1
  35. package/dist/primitives/composer/trigger/triggerSelectionResource.js +4 -1
  36. package/dist/primitives/composer/trigger/triggerSelectionResource.js.map +1 -1
  37. package/dist/primitives/composer.d.ts +1 -0
  38. package/dist/primitives/composer.d.ts.map +1 -1
  39. package/dist/primitives/composer.js +3 -1
  40. package/dist/primitives/composer.js.map +1 -1
  41. package/dist/primitives/thread/useThreadViewportAutoScroll.d.ts.map +1 -1
  42. package/dist/primitives/thread/useThreadViewportAutoScroll.js +32 -292
  43. package/dist/primitives/thread/useThreadViewportAutoScroll.js.map +1 -1
  44. package/dist/unstable/useMentionAdapter.js +1 -1
  45. package/dist/utils/Primitive.d.ts +14 -14
  46. package/dist/utils/hooks/useOnResizeContent.js +1 -1
  47. package/package.json +5 -5
  48. package/src/adapters/OpenAIRealtimeVoiceAdapter.test.ts +748 -0
  49. package/src/adapters/OpenAIRealtimeVoiceAdapter.ts +388 -0
  50. package/src/context/stores/ThreadViewport.ts +36 -8
  51. package/src/index.ts +6 -0
  52. package/src/mcp-apps/McpAppRenderer.test.tsx +65 -0
  53. package/src/mcp-apps/McpAppRenderer.tsx +13 -2
  54. package/src/model-context/frame/useAssistantFrameHost.detach.test.tsx +145 -0
  55. package/src/model-context/frame/useAssistantFrameHost.reload.test.tsx +106 -0
  56. package/src/model-context/frame/useAssistantFrameHost.test.tsx +3 -1
  57. package/src/model-context/frame/useAssistantFrameHost.ts +72 -24
  58. package/src/model-context/makeAssistantVisible.test.tsx +25 -37
  59. package/src/primitives/composer/ComposerAddAttachment.test.tsx +1 -0
  60. package/src/primitives/composer/ComposerAddAttachment.thread-scope.test.tsx +202 -0
  61. package/src/primitives/composer/ComposerAddAttachment.ts +11 -1
  62. package/src/primitives/composer/ComposerInput.focus-on-run-start.test.tsx +130 -0
  63. package/src/primitives/composer/ComposerInput.tsx +30 -4
  64. package/src/primitives/composer/ComposerResume.ts +36 -0
  65. package/src/primitives/composer/trigger/triggerSelectionResource.test.ts +24 -0
  66. package/src/primitives/composer/trigger/triggerSelectionResource.ts +14 -3
  67. package/src/primitives/composer/useAuiState.composer.test.tsx +84 -0
  68. package/src/primitives/composer.ts +1 -0
  69. package/src/primitives/thread/useThreadViewportAutoScroll.test.tsx +90 -0
  70. package/src/primitives/thread/useThreadViewportAutoScroll.ts +48 -486
  71. package/src/model-context/frame/AssistantFrame.test.ts +0 -400
@@ -1,400 +0,0 @@
1
- /**
2
- * @vitest-environment jsdom
3
- */
4
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
5
- import {
6
- AssistantFrameProvider,
7
- AssistantFrameHost,
8
- ModelContextRegistry,
9
- } from "@assistant-ui/core";
10
- import z from "zod";
11
-
12
- describe("AssistantFrame Integration", () => {
13
- let messageHandlers: Map<string, (event: MessageEvent) => void>;
14
- let iframeWindow: Window;
15
- let parentWindow: any;
16
-
17
- beforeEach(() => {
18
- messageHandlers = new Map();
19
-
20
- // Create a mock parent window that the iframe can post back to
21
- parentWindow = {
22
- postMessage: vi.fn((data: any) => {
23
- // When iframe posts to parent, deliver to parent handler
24
- const parentHandler = messageHandlers.get("parent");
25
- if (parentHandler) {
26
- Promise.resolve().then(() => {
27
- parentHandler({
28
- data,
29
- source: iframeWindow,
30
- origin: window.location.origin,
31
- } as MessageEvent);
32
- });
33
- }
34
- }),
35
- };
36
-
37
- // Create mock iframe window with proper message routing
38
- iframeWindow = {
39
- postMessage: vi.fn((data: any) => {
40
- // Route message to iframe handler (provider)
41
- const iframeHandler = messageHandlers.get("iframe");
42
- if (iframeHandler) {
43
- Promise.resolve().then(() => {
44
- iframeHandler({
45
- data,
46
- source: parentWindow, // parent window is the source for subscription
47
- origin: window.location.origin,
48
- } as MessageEvent);
49
- });
50
- }
51
- }),
52
- } as any;
53
-
54
- // Mock window.parent for iframe to broadcast to
55
- Object.defineProperty(window, "parent", {
56
- value: parentWindow,
57
- writable: true,
58
- configurable: true,
59
- });
60
-
61
- // Mock window methods for message passing
62
- vi.spyOn(window, "addEventListener").mockImplementation(
63
- (event: string, handler: any) => {
64
- if (event === "message") {
65
- // Store both handlers - we'll determine which is which based on usage
66
- if (!messageHandlers.has("iframe")) {
67
- messageHandlers.set("iframe", handler); // First registration is provider
68
- } else {
69
- messageHandlers.set("parent", handler); // Second is host
70
- }
71
- }
72
- },
73
- );
74
-
75
- vi.spyOn(window, "removeEventListener").mockImplementation(() => {});
76
-
77
- vi.spyOn(window, "postMessage").mockImplementation(() => {
78
- // This shouldn't be called in our test setup
79
- });
80
- });
81
-
82
- afterEach(() => {
83
- // Clean up
84
- vi.restoreAllMocks();
85
- AssistantFrameProvider.dispose();
86
- messageHandlers.clear();
87
- });
88
-
89
- it("should establish connection between host and provider", async () => {
90
- // Setup provider in iframe
91
- const registry = new ModelContextRegistry();
92
- const unsubscribe =
93
- AssistantFrameProvider.addModelContextProvider(registry);
94
-
95
- // Setup host in parent
96
- const host = new AssistantFrameHost(iframeWindow);
97
-
98
- // Wait for connection
99
- await vi.waitFor(() => {
100
- expect(iframeWindow.postMessage).toHaveBeenCalledWith(
101
- expect.objectContaining({
102
- channel: "assistant-ui-frame",
103
- message: expect.objectContaining({
104
- type: "model-context-request",
105
- }),
106
- }),
107
- window.location.origin,
108
- );
109
- });
110
-
111
- // Clean up
112
- host.dispose();
113
- unsubscribe();
114
- });
115
-
116
- it("should sync tools from provider to host", async () => {
117
- // Setup provider with tools
118
- const registry = new ModelContextRegistry();
119
-
120
- const toolExecute = vi.fn().mockResolvedValue({ result: "search results" });
121
- registry.addTool({
122
- toolName: "search",
123
- description: "Search the web",
124
- parameters: z.object({ query: z.string() }),
125
- execute: toolExecute,
126
- });
127
-
128
- const unsubscribe =
129
- AssistantFrameProvider.addModelContextProvider(registry);
130
-
131
- // Setup host
132
- const host = new AssistantFrameHost(iframeWindow);
133
-
134
- // Wait for connection and initial sync
135
- await vi.waitFor(() => {
136
- const context = host.getModelContext();
137
- expect(context.tools).toBeDefined();
138
- expect(context.tools?.search).toBeDefined();
139
- expect(context.tools?.search?.description).toBe("Search the web");
140
- });
141
-
142
- // Clean up
143
- host.dispose();
144
- unsubscribe();
145
- });
146
-
147
- it("should execute tools through the frame boundary", async () => {
148
- // Setup provider with executable tool
149
- const registry = new ModelContextRegistry();
150
-
151
- const toolExecute = vi
152
- .fn()
153
- .mockResolvedValue({ results: ["result1", "result2"] });
154
- registry.addTool({
155
- toolName: "search",
156
- description: "Search the web",
157
- parameters: z.object({ query: z.string() }),
158
- execute: toolExecute,
159
- });
160
-
161
- const unsubscribe =
162
- AssistantFrameProvider.addModelContextProvider(registry);
163
-
164
- // Setup host
165
- const host = new AssistantFrameHost(iframeWindow);
166
-
167
- // Wait for tools to be available
168
- await vi.waitFor(() => {
169
- const context = host.getModelContext();
170
- expect(context.tools?.search).toBeDefined();
171
- });
172
-
173
- // Execute tool through host
174
- const context = host.getModelContext();
175
- const searchTool = context.tools?.search;
176
-
177
- const resultPromise = searchTool!.execute!(
178
- { query: "test query" },
179
- {} as any,
180
- );
181
-
182
- // Wait for tool execution
183
- await vi.waitFor(() => {
184
- expect(toolExecute).toHaveBeenCalledWith(
185
- { query: "test query" },
186
- expect.objectContaining({
187
- toolCallId: expect.any(String),
188
- abortSignal: expect.any(AbortSignal),
189
- }),
190
- );
191
- });
192
-
193
- const result = await resultPromise;
194
- expect(result).toEqual({ results: ["result1", "result2"] });
195
-
196
- // Clean up
197
- host.dispose();
198
- unsubscribe();
199
- });
200
-
201
- it("should handle tool execution errors", async () => {
202
- // Setup provider with failing tool
203
- const registry = new ModelContextRegistry();
204
-
205
- const toolExecute = vi
206
- .fn()
207
- .mockRejectedValue(new Error("Tool execution failed"));
208
- registry.addTool({
209
- toolName: "failingTool",
210
- description: "A tool that fails",
211
- parameters: z.object({ input: z.string() }),
212
- execute: toolExecute,
213
- });
214
-
215
- const unsubscribe =
216
- AssistantFrameProvider.addModelContextProvider(registry);
217
-
218
- // Setup host
219
- const host = new AssistantFrameHost(iframeWindow);
220
-
221
- // Wait for tools to be available
222
- await vi.waitFor(() => {
223
- const context = host.getModelContext();
224
- expect(context.tools?.failingTool).toBeDefined();
225
- });
226
-
227
- // Execute tool and expect error
228
- const context = host.getModelContext();
229
- const failingTool = context.tools?.failingTool;
230
-
231
- await expect(
232
- failingTool!.execute!({ input: "test" }, {} as any),
233
- ).rejects.toThrow("Tool execution failed");
234
-
235
- // Clean up
236
- host.dispose();
237
- unsubscribe();
238
- });
239
-
240
- it("rejects a tool call the frame drops while it is disposed", async () => {
241
- vi.mocked(iframeWindow.postMessage).mockImplementation((data: any) => {
242
- Promise.resolve().then(() => {
243
- messageHandlers.get("iframe")?.({
244
- data,
245
- source: parentWindow,
246
- origin: window.location.origin,
247
- } as MessageEvent);
248
- });
249
- });
250
- vi.mocked(window.removeEventListener).mockImplementation(
251
- (event: string, handler: any) => {
252
- if (event === "message" && messageHandlers.get("iframe") === handler) {
253
- messageHandlers.delete("iframe");
254
- }
255
- },
256
- );
257
- const registry = new ModelContextRegistry();
258
- const toolExecute = vi.fn().mockResolvedValue("result");
259
- registry.addTool({
260
- toolName: "search",
261
- parameters: z.object({}),
262
- execute: toolExecute,
263
- });
264
- AssistantFrameProvider.addModelContextProvider(registry);
265
- const host = new AssistantFrameHost(iframeWindow);
266
- await vi.waitFor(() => {
267
- expect(host.getModelContext().tools?.search).toBeDefined();
268
- });
269
-
270
- const result = host.getModelContext().tools!.search!.execute!(
271
- {},
272
- {} as any,
273
- );
274
- AssistantFrameProvider.dispose();
275
-
276
- await expect(result).rejects.toThrow(
277
- "AssistantFrameProvider has been disposed",
278
- );
279
- expect(toolExecute).not.toHaveBeenCalled();
280
- expect(host.getModelContext().tools).toBeUndefined();
281
-
282
- host.dispose();
283
- });
284
-
285
- it("should handle multiple providers", async () => {
286
- // Setup multiple providers
287
- const registry1 = new ModelContextRegistry();
288
- const registry2 = new ModelContextRegistry();
289
-
290
- registry1.addTool({
291
- toolName: "tool1",
292
- description: "First tool",
293
- parameters: z.object({ input: z.string() }),
294
- execute: async () => ({ from: "tool1" }),
295
- });
296
-
297
- registry2.addTool({
298
- toolName: "tool2",
299
- description: "Second tool",
300
- parameters: z.object({ input: z.string() }),
301
- execute: async () => ({ from: "tool2" }),
302
- });
303
-
304
- const unsub1 = AssistantFrameProvider.addModelContextProvider(registry1);
305
- const unsub2 = AssistantFrameProvider.addModelContextProvider(registry2);
306
-
307
- // Setup host
308
- const host = new AssistantFrameHost(iframeWindow);
309
-
310
- // Wait for both tools to be available
311
- await vi.waitFor(() => {
312
- const context = host.getModelContext();
313
- expect(context.tools?.tool1).toBeDefined();
314
- expect(context.tools?.tool2).toBeDefined();
315
- });
316
-
317
- // Clean up
318
- host.dispose();
319
- unsub1();
320
- unsub2();
321
- });
322
-
323
- it("should merge system instructions from multiple providers", async () => {
324
- // Setup providers with system instructions
325
- const registry1 = new ModelContextRegistry();
326
- const registry2 = new ModelContextRegistry();
327
-
328
- registry1.addInstruction("You are a helpful assistant.");
329
- registry2.addInstruction("Always be concise.");
330
-
331
- const unsub1 = AssistantFrameProvider.addModelContextProvider(registry1);
332
- const unsub2 = AssistantFrameProvider.addModelContextProvider(registry2);
333
-
334
- // Setup host
335
- const host = new AssistantFrameHost(iframeWindow);
336
-
337
- // Wait for instructions to be synced
338
- await vi.waitFor(() => {
339
- const context = host.getModelContext();
340
- expect(context.system).toBeDefined();
341
- expect(context.system).toContain("You are a helpful assistant.");
342
- expect(context.system).toContain("Always be concise.");
343
- });
344
-
345
- // Clean up
346
- host.dispose();
347
- unsub1();
348
- unsub2();
349
- });
350
-
351
- it("should act as empty ModelContextProvider when iframe has no providers", async () => {
352
- // Don't register any providers in the iframe
353
- // This simulates an iframe that doesn't respond to model-context requests
354
-
355
- // Setup host
356
- const host = new AssistantFrameHost(iframeWindow);
357
-
358
- // Host should immediately return empty context
359
- const context = host.getModelContext();
360
- expect(context).toEqual({});
361
-
362
- // Wait a bit to ensure no errors occur
363
- await new Promise((resolve) => setTimeout(resolve, 100));
364
-
365
- // Context should still be empty
366
- expect(host.getModelContext()).toEqual({});
367
-
368
- // Clean up
369
- host.dispose();
370
- });
371
-
372
- it("should clean up properly on dispose", async () => {
373
- // Setup provider
374
- const registry = new ModelContextRegistry();
375
-
376
- const unsubscribe =
377
- AssistantFrameProvider.addModelContextProvider(registry);
378
-
379
- // Setup host
380
- const host = new AssistantFrameHost(iframeWindow);
381
-
382
- // Wait for connection
383
- await vi.waitFor(() => {
384
- expect(iframeWindow.postMessage).toHaveBeenCalled();
385
- });
386
-
387
- // Dispose host
388
- host.dispose();
389
-
390
- // Verify event listener was removed (no unsubscribe message in new design)
391
- expect(window.removeEventListener).toHaveBeenCalledWith(
392
- "message",
393
- expect.any(Function),
394
- );
395
-
396
- // Clean up provider
397
- unsubscribe();
398
- AssistantFrameProvider.dispose();
399
- });
400
- });