@copilotkit/vue 2.0.0-rc.20 → 2.0.0-rc.22

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 (193) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.mjs +13 -13
  3. package/dist/use-render-activity-message-CyrhHFhA.js +16200 -0
  4. package/dist/use-render-activity-message-CyrhHFhA.js.map +1 -0
  5. package/dist/use-render-activity-message-EwHHrwQB.cjs +114 -0
  6. package/dist/use-render-activity-message-EwHHrwQB.cjs.map +1 -0
  7. package/dist/v2/components/A2UIMessageRenderer.d.ts +14 -0
  8. package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -1
  9. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts +10 -3
  10. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -1
  11. package/dist/v2/components/CopilotKitInspector.vue.d.ts +5 -1
  12. package/dist/v2/components/CopilotKitInspector.vue.d.ts.map +1 -1
  13. package/dist/v2/components/MCPAppsActivityRenderer.d.ts.map +1 -1
  14. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +7 -21
  15. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -1
  16. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +0 -5
  17. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -1
  18. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts +10 -15
  19. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts.map +1 -1
  20. package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts +14 -138
  21. package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts.map +1 -1
  22. package/dist/v2/components/a2ui/catalog.d.ts.map +1 -1
  23. package/dist/v2/components/a2ui/utils.d.ts +3 -15
  24. package/dist/v2/components/a2ui/utils.d.ts.map +1 -1
  25. package/dist/v2/components/a2ui.d.ts +6 -1
  26. package/dist/v2/components/a2ui.d.ts.map +1 -1
  27. package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
  28. package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -1
  29. package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts +4 -8
  30. package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts.map +1 -1
  31. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
  32. package/dist/v2/components/chat/CopilotChatLightbox.vue.d.ts.map +1 -1
  33. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
  34. package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts.map +1 -1
  35. package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts.map +1 -1
  36. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -1
  37. package/dist/v2/components/chat/CopilotChatView.vue.d.ts +1 -1
  38. package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
  39. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
  40. package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +1 -1
  41. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +1 -1
  42. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
  43. package/dist/v2/components/chat/CopilotThreadsDrawer.vue.d.ts.map +1 -1
  44. package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts +1 -0
  45. package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts.map +1 -1
  46. package/dist/v2/components/chat/audioRecorder.d.ts +3 -4
  47. package/dist/v2/components/chat/audioRecorder.d.ts.map +1 -1
  48. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts +3 -2
  49. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -1
  50. package/dist/v2/components/chat/index.d.ts +6 -6
  51. package/dist/v2/components/chat/tool-call-display-context.d.ts +3 -1
  52. package/dist/v2/components/chat/tool-call-display-context.d.ts.map +1 -1
  53. package/dist/v2/components/chat/tool-call-group-parts.d.ts.map +1 -1
  54. package/dist/v2/components/chat/types.d.ts +1 -1
  55. package/dist/v2/components/chat/types.d.ts.map +1 -1
  56. package/dist/v2/components/chat/use-lightbox.d.ts.map +1 -1
  57. package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts +1 -1
  58. package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts.map +1 -1
  59. package/dist/v2/hooks/use-agent-context.d.ts +2 -3
  60. package/dist/v2/hooks/use-agent-context.d.ts.map +1 -1
  61. package/dist/v2/hooks/use-agent.d.ts +4 -1
  62. package/dist/v2/hooks/use-agent.d.ts.map +1 -1
  63. package/dist/v2/hooks/use-attachments.d.ts +7 -0
  64. package/dist/v2/hooks/use-attachments.d.ts.map +1 -1
  65. package/dist/v2/hooks/use-capabilities.d.ts.map +1 -1
  66. package/dist/v2/hooks/use-component.d.ts +1 -0
  67. package/dist/v2/hooks/use-component.d.ts.map +1 -1
  68. package/dist/v2/hooks/use-configure-suggestions.d.ts +4 -9
  69. package/dist/v2/hooks/use-configure-suggestions.d.ts.map +1 -1
  70. package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -1
  71. package/dist/v2/hooks/use-human-in-the-loop.d.ts +2 -1
  72. package/dist/v2/hooks/use-human-in-the-loop.d.ts.map +1 -1
  73. package/dist/v2/hooks/use-interrupt.d.ts +0 -1
  74. package/dist/v2/hooks/use-interrupt.d.ts.map +1 -1
  75. package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -1
  76. package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -1
  77. package/dist/v2/hooks/use-suggestions.d.ts.map +1 -1
  78. package/dist/v2/hooks/use-threads.d.ts +0 -8
  79. package/dist/v2/hooks/use-threads.d.ts.map +1 -1
  80. package/dist/v2/index.cjs +1 -1
  81. package/dist/v2/index.mjs +1 -1
  82. package/dist/v2/lib/vue-core.d.ts +12 -11
  83. package/dist/v2/lib/vue-core.d.ts.map +1 -1
  84. package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts.map +1 -1
  85. package/dist/v2/providers/CopilotKitProvider.types.d.ts +9 -124
  86. package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
  87. package/dist/v2/providers/CopilotKitProvider.vue.d.ts +9 -10
  88. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
  89. package/dist/v2/providers/chat-configuration-store-key.d.ts +8 -0
  90. package/dist/v2/providers/chat-configuration-store-key.d.ts.map +1 -0
  91. package/dist/v2/providers/markdown-renderer.d.ts +3 -1
  92. package/dist/v2/providers/markdown-renderer.d.ts.map +1 -1
  93. package/dist/v2/providers/use-inspector-thread-override.d.ts +5 -0
  94. package/dist/v2/providers/use-inspector-thread-override.d.ts.map +1 -1
  95. package/package.json +8 -8
  96. package/src/v2/__tests__/exports.test.ts +10 -0
  97. package/src/v2/__tests__/mocks/web-inspector.ts +6 -0
  98. package/src/v2/__tests__/utils/test-helpers.ts +34 -4
  99. package/src/v2/components/A2UIMessageRenderer.ts +98 -68
  100. package/src/v2/components/A2UISurfaceActivityRenderer.vue +68 -103
  101. package/src/v2/components/CopilotKitInspector.vue +104 -51
  102. package/src/v2/components/MCPAppsActivityRenderer.ts +75 -201
  103. package/src/v2/components/OpenGenerativeUIRenderer.ts +78 -80
  104. package/src/v2/components/__tests__/A2UIMessageRenderer.test.ts +171 -8
  105. package/src/v2/components/__tests__/CopilotKitInspector.test.ts +42 -0
  106. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.ts +9 -2
  107. package/src/v2/components/__tests__/OpenGenerativeUIToolRenderer.test.ts +38 -0
  108. package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +1 -6
  109. package/src/v2/components/a2ui/A2UICatalogContext.ts +27 -93
  110. package/src/v2/components/a2ui/A2UIRecoveryStates.ts +127 -327
  111. package/src/v2/components/a2ui/catalog.ts +2 -3
  112. package/src/v2/components/a2ui/utils.ts +13 -67
  113. package/src/v2/components/a2ui.ts +14 -21
  114. package/src/v2/components/chat/CopilotChat.vue +136 -324
  115. package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +22 -45
  116. package/src/v2/components/chat/CopilotChatAudioRecorder.vue +19 -246
  117. package/src/v2/components/chat/CopilotChatInput.vue +69 -407
  118. package/src/v2/components/chat/CopilotChatLightbox.vue +10 -31
  119. package/src/v2/components/chat/CopilotChatMessageView.vue +52 -112
  120. package/src/v2/components/chat/CopilotChatToolCallGroup.vue +14 -45
  121. package/src/v2/components/chat/CopilotChatToolCallItem.vue +6 -4
  122. package/src/v2/components/chat/CopilotChatToolCallsView.vue +16 -19
  123. package/src/v2/components/chat/CopilotChatView.vue +28 -23
  124. package/src/v2/components/chat/CopilotPopupViewInternal.vue +21 -97
  125. package/src/v2/components/chat/CopilotSidebarViewInternal.vue +24 -25
  126. package/src/v2/components/chat/CopilotThreadsDrawer.vue +37 -102
  127. package/src/v2/components/chat/StreamingMarkdownDefault.vue +13 -26
  128. package/src/v2/components/chat/__tests__/CopilotChat.test.ts +12 -10
  129. package/src/v2/components/chat/__tests__/CopilotChatAttachmentQueue.test.ts +28 -0
  130. package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +56 -0
  131. package/src/v2/components/chat/__tests__/CopilotPopupView.test.ts +19 -0
  132. package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.ts +32 -8
  133. package/src/v2/components/chat/__tests__/CopilotSidebarView.test.ts +2 -1
  134. package/src/v2/components/chat/__tests__/StreamingMarkdownDefault.test.ts +54 -0
  135. package/src/v2/components/chat/audioRecorder.ts +4 -7
  136. package/src/v2/components/chat/copilotChatToolCallItemRender.ts +22 -71
  137. package/src/v2/components/chat/tool-call-display-context.ts +6 -1
  138. package/src/v2/components/chat/tool-call-group-parts.ts +13 -33
  139. package/src/v2/components/chat/types.ts +1 -1
  140. package/src/v2/components/chat/use-lightbox.ts +15 -33
  141. package/src/v2/hooks/__tests__/use-agent-error-state.test.ts +5 -3
  142. package/src/v2/hooks/__tests__/use-agent-stability.test.ts +25 -4
  143. package/src/v2/hooks/__tests__/use-agent-throttle.test.ts +5 -1
  144. package/src/v2/hooks/__tests__/use-agent.test.ts +6 -3
  145. package/src/v2/hooks/__tests__/use-component.test.ts +21 -0
  146. package/src/v2/hooks/__tests__/use-configure-suggestions.test.ts +30 -1
  147. package/src/v2/hooks/__tests__/use-default-render-tool.test.ts +10 -10
  148. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +4 -0
  149. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +6 -1
  150. package/src/v2/hooks/__tests__/use-human-in-the-loop.test.ts +187 -12
  151. package/src/v2/hooks/__tests__/use-interrupt.test.ts +97 -0
  152. package/src/v2/hooks/__tests__/use-render-activity-message.test.ts +2 -1
  153. package/src/v2/hooks/__tests__/use-threads.test.ts +48 -6
  154. package/src/v2/hooks/use-agent-context.ts +4 -10
  155. package/src/v2/hooks/use-agent.ts +86 -290
  156. package/src/v2/hooks/use-attachments.ts +42 -166
  157. package/src/v2/hooks/use-capabilities.ts +2 -7
  158. package/src/v2/hooks/use-component.ts +7 -4
  159. package/src/v2/hooks/use-configure-suggestions.ts +28 -218
  160. package/src/v2/hooks/use-default-render-tool.ts +34 -94
  161. package/src/v2/hooks/use-human-in-the-loop.ts +11 -80
  162. package/src/v2/hooks/use-interrupt.ts +29 -308
  163. package/src/v2/hooks/use-render-activity-message.ts +17 -20
  164. package/src/v2/hooks/use-render-custom-messages.ts +20 -63
  165. package/src/v2/hooks/use-suggestions.ts +9 -33
  166. package/src/v2/hooks/use-threads.ts +43 -273
  167. package/src/v2/lib/__tests__/vue-core-renderers.test.ts +42 -0
  168. package/src/v2/lib/vue-core.ts +67 -72
  169. package/src/v2/providers/CopilotChatConfigurationProvider.vue +48 -186
  170. package/src/v2/providers/CopilotKitProvider.types.ts +12 -127
  171. package/src/v2/providers/CopilotKitProvider.vue +188 -402
  172. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.ts +22 -2
  173. package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +19 -1
  174. package/src/v2/providers/__tests__/CopilotKitProvider.config.test.ts +124 -0
  175. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.ts +4 -1
  176. package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +45 -2
  177. package/src/v2/providers/chat-configuration-store-key.ts +9 -0
  178. package/src/v2/providers/markdown-renderer.ts +7 -1
  179. package/src/v2/providers/use-inspector-thread-override.ts +22 -80
  180. package/vite.config.ts +5 -1
  181. package/vitest.config.ts +12 -7
  182. package/dist/use-render-activity-message-CsWYyd6r.cjs +0 -91
  183. package/dist/use-render-activity-message-CsWYyd6r.cjs.map +0 -1
  184. package/dist/use-render-activity-message-DntKKNdd.js +0 -17418
  185. package/dist/use-render-activity-message-DntKKNdd.js.map +0 -1
  186. package/dist/v2/components/chat/normalize-auto-scroll.d.ts +0 -3
  187. package/dist/v2/components/chat/normalize-auto-scroll.d.ts.map +0 -1
  188. package/dist/v2/lib/is-mobile-viewport.d.ts +0 -2
  189. package/dist/v2/lib/is-mobile-viewport.d.ts.map +0 -1
  190. package/src/v2/components/__tests__/A2UICatalogContext.test.ts +0 -63
  191. package/src/v2/components/a2ui/__tests__/A2UIRecoveryStates.test.ts +0 -175
  192. package/src/v2/components/chat/normalize-auto-scroll.ts +0 -3
  193. package/src/v2/lib/is-mobile-viewport.ts +0 -5
@@ -1,17 +1,25 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
2
  import { defineComponent, h, ref } from "vue";
3
- import { render, waitFor } from "@testing-library/vue";
3
+ import { fireEvent, render, waitFor } from "@testing-library/vue";
4
+ import type { ɵA2UIActionInterceptor } from "@copilotkit/core";
4
5
  import { createA2UIMessageRenderer } from "../A2UIMessageRenderer";
5
6
  import { CopilotKitKey } from "../../providers/keys";
6
7
 
7
- function copilotKitProvide() {
8
+ function copilotKitProvide(
9
+ onRunAgent: (properties: Record<string, unknown>) => void = () => undefined,
10
+ ) {
11
+ const copilotkit = {
12
+ properties: {} as Record<string, unknown>,
13
+ setProperties(properties: Record<string, unknown>) {
14
+ copilotkit.properties = properties;
15
+ },
16
+ runAgent: async () => {
17
+ onRunAgent(copilotkit.properties);
18
+ },
19
+ };
8
20
  return {
9
21
  [CopilotKitKey as symbol]: {
10
- copilotkit: ref({
11
- properties: {},
12
- setProperties: () => undefined,
13
- runAgent: async () => undefined,
14
- }),
22
+ copilotkit: ref(copilotkit),
15
23
  executingToolCallIds: ref(new Set()),
16
24
  a2uiTheme: ref({}),
17
25
  a2uiCatalog: ref(undefined),
@@ -22,6 +30,10 @@ function copilotKitProvide() {
22
30
  }
23
31
 
24
32
  describe("createA2UIMessageRenderer", () => {
33
+ afterEach(() => {
34
+ vi.useRealTimers();
35
+ });
36
+
25
37
  const renderLifecycle = (
26
38
  content: Record<string, unknown>,
27
39
  options: Partial<Parameters<typeof createA2UIMessageRenderer>[0]> = {},
@@ -46,6 +58,43 @@ describe("createA2UIMessageRenderer", () => {
46
58
  expect(container.textContent).toContain("~97 tokens");
47
59
  });
48
60
 
61
+ it("keeps an early retry looking like building until showAfterMs passes", async () => {
62
+ vi.useFakeTimers();
63
+ const { container } = renderLifecycle(
64
+ { status: "retrying", attempt: 1, maxAttempts: 3 },
65
+ { recovery: { showAfterMs: 2000 } },
66
+ );
67
+ expect(container.textContent).toContain("Building interface");
68
+ await vi.advanceTimersByTimeAsync(2000);
69
+ expect(container.textContent).toContain(
70
+ "Retrying generation… (1/3 attempts)",
71
+ );
72
+ });
73
+
74
+ it("shows retry validation issues unless debugExposure is hidden", () => {
75
+ const retry = {
76
+ status: "retrying",
77
+ attempt: 2,
78
+ errors: [{ code: "missing_required_prop" }],
79
+ };
80
+ const shown = renderLifecycle(retry);
81
+ expect(shown.container.querySelector("details summary")?.textContent).toBe(
82
+ "validation issues",
83
+ );
84
+ expect(shown.container.textContent).toContain("missing_required_prop");
85
+ const hidden = renderLifecycle(retry, {
86
+ recovery: { debugExposure: "hidden" },
87
+ });
88
+ expect(hidden.container.querySelector("details")).toBeNull();
89
+ });
90
+
91
+ it("shows collapsed developer details on failure by default", () => {
92
+ const { container } = renderLifecycle({ status: "failed", error: "boom" });
93
+ const details = container.querySelector("details");
94
+ expect(details?.hasAttribute("open")).toBe(false);
95
+ expect(details?.textContent).toContain("boom");
96
+ });
97
+
49
98
  it("shows the retry state for status 'retrying'", () => {
50
99
  const { container } = renderLifecycle({
51
100
  status: "retrying",
@@ -295,4 +344,118 @@ describe("createA2UIMessageRenderer", () => {
295
344
  expect(container.querySelector("[data-surface-id='s2']")).not.toBeNull();
296
345
  });
297
346
  });
347
+
348
+ const buttonContent = {
349
+ a2ui_operations: [
350
+ {
351
+ version: "v0.9",
352
+ createSurface: {
353
+ surfaceId: "form",
354
+ catalogId: "https://a2ui.org/specification/v0_9/basic_catalog.json",
355
+ },
356
+ },
357
+ {
358
+ version: "v0.9",
359
+ updateComponents: {
360
+ surfaceId: "form",
361
+ components: [
362
+ {
363
+ id: "root",
364
+ component: "Button",
365
+ child: "label",
366
+ action: { event: { name: "confirm", context: { n: 1 } } },
367
+ },
368
+ { id: "label", component: "Text", text: "Confirm" },
369
+ ],
370
+ },
371
+ },
372
+ ],
373
+ };
374
+
375
+ /** Renders the button surface, clicks it, and returns what reached the agent. */
376
+ async function clickButton(onAction?: ɵA2UIActionInterceptor) {
377
+ const forwarded: Record<string, unknown>[] = [];
378
+ const renderer = createA2UIMessageRenderer({ theme: {}, onAction });
379
+ const { findByRole } = render(renderer.render, {
380
+ props: {
381
+ activityType: "a2ui-surface",
382
+ content: buttonContent,
383
+ message: {},
384
+ agent: { agentId: "a" },
385
+ },
386
+ global: {
387
+ provide: copilotKitProvide((properties) =>
388
+ forwarded.push({ ...properties }),
389
+ ),
390
+ },
391
+ });
392
+ await fireEvent.click(await findByRole("button", { hidden: true }));
393
+ return forwarded;
394
+ }
395
+
396
+ it("forwards a button click to the agent wrapped as an A2UI userAction envelope", async () => {
397
+ const forwarded = await clickButton();
398
+ await waitFor(() => expect(forwarded).toHaveLength(1));
399
+ expect(forwarded[0]).toEqual({
400
+ a2uiAction: {
401
+ userAction: {
402
+ name: "confirm",
403
+ surfaceId: "form",
404
+ sourceComponentId: "root",
405
+ context: { n: 1 },
406
+ timestamp: expect.any(String),
407
+ },
408
+ },
409
+ });
410
+ });
411
+
412
+ it("does not run the agent when onAction handles the action", async () => {
413
+ const onAction = vi.fn<ɵA2UIActionInterceptor>(() => null);
414
+ const forwarded = await clickButton(onAction);
415
+ await waitFor(() => expect(onAction).toHaveBeenCalledOnce());
416
+ expect(forwarded).toEqual([]);
417
+ });
418
+
419
+ it("keeps the loader over the hidden surface until the surface can paint", async () => {
420
+ const renderer = createA2UIMessageRenderer({ theme: {} });
421
+ const createOnly = {
422
+ a2ui_operations: [buttonContent.a2ui_operations[0]],
423
+ };
424
+ const mounted = render(renderer.render, {
425
+ props: {
426
+ activityType: "a2ui-surface",
427
+ content: { status: "building", progressTokens: 120 },
428
+ message: {},
429
+ agent: {},
430
+ },
431
+ global: { provide: copilotKitProvide() },
432
+ });
433
+
434
+ await mounted.rerender({
435
+ activityType: "a2ui-surface",
436
+ content: createOnly,
437
+ message: {},
438
+ agent: {},
439
+ });
440
+ // A surface without components cannot paint: the loader stays on top.
441
+ expect(mounted.container.textContent).toContain("~120 tokens");
442
+ expect(
443
+ mounted.container.querySelector(
444
+ "[aria-hidden='true'] [data-testid='a2ui-activity-renderer']",
445
+ ),
446
+ ).not.toBeNull();
447
+
448
+ await mounted.rerender({
449
+ activityType: "a2ui-surface",
450
+ content: buttonContent,
451
+ message: {},
452
+ agent: {},
453
+ });
454
+ await waitFor(() => {
455
+ expect(mounted.container.textContent).not.toContain("Building interface");
456
+ expect(
457
+ mounted.container.querySelector("[aria-hidden='true']"),
458
+ ).toBeNull();
459
+ });
460
+ });
298
461
  });
@@ -62,6 +62,48 @@ describe("CopilotKitInspector", () => {
62
62
  );
63
63
  });
64
64
 
65
+ it("forwards event listeners to the web inspector element", async () => {
66
+ const onVisibility = vi.fn();
67
+ const wrapper = mount(CopilotKitInspector, {
68
+ attrs: { onCpkInspectorVisibilityChange: onVisibility },
69
+ });
70
+ await settleInspectorLoad();
71
+
72
+ const event = new CustomEvent("cpk-inspector-visibility-change", {
73
+ detail: { visible: false },
74
+ });
75
+ wrapper.get("cpk-web-inspector").element.dispatchEvent(event);
76
+
77
+ expect(onVisibility).toHaveBeenCalledWith(event);
78
+ });
79
+
80
+ it("opens a request made before the web inspector module resolves", async () => {
81
+ const wrapper = mount(CopilotKitInspector);
82
+ const openRequest = { messageId: "message-early" };
83
+ await wrapper.setProps({ openRequest });
84
+
85
+ await settleInspectorLoad();
86
+
87
+ const inspector = wrapper.get("cpk-web-inspector")
88
+ .element as HTMLElement & {
89
+ openInspector: ReturnType<typeof vi.fn>;
90
+ };
91
+ expect(inspector.openInspector).toHaveBeenCalledExactlyOnceWith(
92
+ "message_toolbar",
93
+ openRequest,
94
+ );
95
+ });
96
+
97
+ it("emits Inspector visibility and removes the element on unmount", async () => {
98
+ const wrapper = mount(CopilotKitInspector, { attachTo: document.body });
99
+ await settleInspectorLoad();
100
+ expect(wrapper.emitted("visibility-change")).toEqual([[true]]);
101
+
102
+ wrapper.unmount();
103
+
104
+ expect(document.querySelector("cpk-web-inspector")).toBeNull();
105
+ });
106
+
65
107
  it("forwards Inspector open requests to the mounted element", async () => {
66
108
  const openRequest = { messageId: "message-1" };
67
109
  const wrapper = mount(CopilotKitInspector, {
@@ -100,16 +100,23 @@ describe("OpenGenerativeUIRenderer", () => {
100
100
  vi.restoreAllMocks();
101
101
  });
102
102
 
103
- it("renders placeholder when no html", async () => {
103
+ it("shows the progress overlay with a spinner while generating", async () => {
104
104
  const { getByTestId, queryByTestId } = renderRenderer({
105
105
  initialHeight: 300,
106
106
  });
107
107
  await flushImport();
108
- expect(getByTestId("open-generative-ui-placeholder")).not.toBeNull();
108
+ expect(getByTestId("open-generative-ui-spinner")).not.toBeNull();
109
109
  expect(queryByTestId("open-generative-ui-final-sandbox")).toBeNull();
110
110
  expect(mockCreate).not.toHaveBeenCalled();
111
111
  });
112
112
 
113
+ it("shows no spinner or overlay once generation is done", async () => {
114
+ const { queryByTestId } = renderRenderer({ generating: false });
115
+ await flushImport();
116
+ expect(queryByTestId("open-generative-ui-progress-overlay")).toBeNull();
117
+ expect(queryByTestId("open-generative-ui-spinner")).toBeNull();
118
+ });
119
+
113
120
  it("creates final sandbox when html is complete", async () => {
114
121
  renderRenderer({
115
122
  html: ["<head></head><body><p>Hello</p></body>"],
@@ -0,0 +1,38 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { render } from "@testing-library/vue";
3
+ import { ToolCallStatus } from "@copilotkit/core";
4
+ import { OpenGenerativeUIToolRenderer } from "../OpenGenerativeUIRenderer";
5
+
6
+ function toolProps(messages: string[], status = ToolCallStatus.InProgress) {
7
+ return {
8
+ name: "generateSandboxedUi",
9
+ args: { placeholderMessages: [...messages] },
10
+ status,
11
+ };
12
+ }
13
+
14
+ describe("OpenGenerativeUIToolRenderer", () => {
15
+ beforeEach(() => vi.useFakeTimers());
16
+ afterEach(() => vi.useRealTimers());
17
+
18
+ it("keeps rotating while streamed snapshots repeat the same messages", async () => {
19
+ const view = render(OpenGenerativeUIToolRenderer, {
20
+ props: toolProps(["Sketching", "Styling"]),
21
+ });
22
+ expect(view.container.textContent).toBe("Styling");
23
+
24
+ await vi.advanceTimersByTimeAsync(4000);
25
+ await view.rerender(toolProps(["Sketching", "Styling"]));
26
+ await vi.advanceTimersByTimeAsync(1000);
27
+
28
+ expect(view.container.textContent).toBe("Sketching");
29
+ });
30
+
31
+ it("renders nothing once the tool call is complete", async () => {
32
+ const view = render(OpenGenerativeUIToolRenderer, {
33
+ props: toolProps(["Sketching"]),
34
+ });
35
+ await view.rerender(toolProps(["Sketching"], ToolCallStatus.Complete));
36
+ expect(view.container.textContent).toBe("");
37
+ });
38
+ });
@@ -1,15 +1,10 @@
1
1
  import { watch } from "vue";
2
2
  import type { ShallowRef } from "vue";
3
3
  import * as z from "zod";
4
+ import { ɵRENDER_A2UI_TOOL_NAME as RENDER_A2UI_TOOL_NAME } from "@copilotkit/core";
4
5
  import type { CopilotKitCoreVue } from "../../lib/vue-core";
5
6
  import { defineToolCallRenderer } from "../../types/defineToolCallRenderer";
6
7
 
7
- /**
8
- * Tool name used by the dynamic A2UI generation secondary LLM.
9
- * This renderer is auto-registered when A2UI is enabled.
10
- */
11
- export const RENDER_A2UI_TOOL_NAME = "render_a2ui";
12
-
13
8
  /**
14
9
  * Registers a no-op renderer for the `render_a2ui` tool call so its raw
15
10
  * streamed args are never surfaced in the transcript, as React does.
@@ -1,116 +1,50 @@
1
- import { computed, watch } from "vue";
1
+ import { watch } from "vue";
2
2
  import type { ShallowRef } from "vue";
3
3
  import type { Catalog, ComponentApi } from "@a2ui/web_core/v0_9";
4
- import {
5
- A2UI_SCHEMA_CONTEXT_DESCRIPTION,
6
- buildCatalogContextValue as buildContextValue,
7
- extractCatalogComponentSchemas,
8
- } from "@copilotkit/a2ui-renderer/catalog-schema";
9
- import {
10
- A2UI_DEFAULT_GENERATION_GUIDELINES,
11
- A2UI_DEFAULT_DESIGN_GUIDELINES,
12
- } from "@copilotkit/shared";
4
+ import { A2UICatalogController } from "@copilotkit/a2ui-renderer/catalog-schema";
13
5
  import type { CopilotKitCoreVue } from "../../lib/vue-core";
14
6
  import { vueBasicCatalog } from "./catalog";
15
7
 
16
8
  /**
17
- * Build a context string describing the available A2UI catalog.
18
- * Defaults to the Vue basic catalog.
9
+ * Keeps the provider's A2UI catalog in sync with core through the shared
10
+ * `A2UICatalogController`: registers its components (so the Inspector can
11
+ * toggle them), reports the catalog with disabled components removed, and
12
+ * registers the agent context entries scoped to `core.a2uiAgents` while A2UI
13
+ * is on. Call from the provider's setup function.
19
14
  *
20
- * When `options.includeSchema` is `false`, custom components are listed by name
21
- * only and their JSON schemas are omitted.
15
+ * @param copilotkit - The provider's core.
16
+ * @param options.onFilteredCatalog - Receives the filtered catalog each time
17
+ * it changes; the provider renders surfaces with it.
22
18
  */
23
- export function buildCatalogContextValue(
24
- catalog?: Catalog<ComponentApi>,
25
- options?: { includeSchema?: boolean },
26
- ): string {
27
- return buildContextValue(catalog ?? vueBasicCatalog, options);
28
- }
29
-
30
- /**
31
- * Registers agent context describing the available A2UI catalog, component
32
- * schemas, and generation/design guidelines.
33
- *
34
- * Vue-native equivalent of React's `<A2UICatalogContext>` component.
35
- * Call from the provider's setup function.
36
- */
37
- export function registerA2UICatalogContext(
19
+ export function registerA2UICatalog(
38
20
  copilotkit: ShallowRef<CopilotKitCoreVue>,
39
21
  options: {
40
22
  enabled: () => boolean;
41
23
  catalog: () => Catalog<ComponentApi> | undefined;
42
24
  includeSchema: () => boolean;
25
+ onFilteredCatalog: (catalog: Catalog<ComponentApi> | undefined) => void;
43
26
  },
44
27
  ): void {
45
- const contextValue = computed(() =>
46
- buildCatalogContextValue(options.catalog(), {
47
- includeSchema: options.includeSchema(),
48
- }),
49
- );
50
-
51
- // Register catalog capabilities context
52
- watch(
53
- [() => copilotkit.value, options.enabled, contextValue],
54
- ([core, isEnabled, value], _prev, onCleanup) => {
55
- if (!isEnabled) return;
56
- // Scope to the agents the runtime applies A2UI to (#5369), so other
57
- // agents on the endpoint don't receive the catalog payload.
58
- const agentIds = core.a2uiAgents;
59
- const id = core.addContext({
60
- description:
61
- "A2UI catalog capabilities: available catalog IDs and " +
62
- "custom component definitions the client can render.",
63
- value,
64
- ...(agentIds ? { agentIds } : {}),
65
- });
66
- onCleanup(() => core.removeContext(id));
67
- },
68
- { immediate: true },
69
- );
70
-
71
- // Register schema + generation/design guidelines
72
- const schemaValue = computed(() => {
73
- if (!options.includeSchema()) return null;
74
- return JSON.stringify(
75
- extractCatalogComponentSchemas(options.catalog() ?? vueBasicCatalog),
76
- );
77
- });
78
-
79
28
  watch(
80
- [() => copilotkit.value, options.enabled, schemaValue],
81
- ([core, isEnabled, schema], _prev, onCleanup) => {
82
- if (!isEnabled || !schema) return;
83
- const agentIds = core.a2uiAgents;
84
- const scope = agentIds ? { agentIds } : {};
85
- const ids: string[] = [];
86
- ids.push(
87
- core.addContext({
88
- description: A2UI_SCHEMA_CONTEXT_DESCRIPTION,
89
- value: schema,
90
- ...scope,
91
- }),
29
+ () => copilotkit.value,
30
+ (core, _previous, onCleanup) => {
31
+ const controller = new A2UICatalogController<Catalog<ComponentApi>>(
32
+ core,
33
+ vueBasicCatalog,
92
34
  );
93
- ids.push(
94
- core.addContext({
95
- description:
96
- "A2UI generation guidelines — protocol rules, tool arguments, " +
97
- "path rules, data model format, and " +
98
- "form/two-way-binding instructions.",
99
- value: A2UI_DEFAULT_GENERATION_GUIDELINES,
100
- ...scope,
101
- }),
35
+ const unsubscribe = controller.subscribe(() =>
36
+ options.onFilteredCatalog(controller.getSnapshot()),
102
37
  );
103
- ids.push(
104
- core.addContext({
105
- description:
106
- "A2UI design guidelines — visual design rules, component " +
107
- "hierarchy tips, and action handler patterns.",
108
- value: A2UI_DEFAULT_DESIGN_GUIDELINES,
109
- ...scope,
110
- }),
38
+ const stop = watch(
39
+ [options.enabled, options.catalog, options.includeSchema],
40
+ ([enabled, catalog, includeSchema]) =>
41
+ controller.update({ enabled, catalog, includeSchema }),
42
+ { immediate: true },
111
43
  );
112
44
  onCleanup(() => {
113
- for (const id of ids) core.removeContext(id);
45
+ stop();
46
+ unsubscribe();
47
+ controller.dispose();
114
48
  });
115
49
  },
116
50
  { immediate: true },