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

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-BAhbpSDh.js +16200 -0
  4. package/dist/use-render-activity-message-BAhbpSDh.js.map +1 -0
  5. package/dist/use-render-activity-message-DJ6q1qHV.cjs +114 -0
  6. package/dist/use-render-activity-message-DJ6q1qHV.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,4 +1,12 @@
1
- import { computed, defineComponent, h, ref, shallowRef, watch } from "vue";
1
+ import {
2
+ computed,
3
+ defineComponent,
4
+ h,
5
+ onBeforeUnmount,
6
+ ref,
7
+ shallowRef,
8
+ watch,
9
+ } from "vue";
2
10
  import type { PropType } from "vue";
3
11
  import type { AbstractAgent } from "@ag-ui/client";
4
12
  import type { VueActivityMessageRendererProps } from "../types";
@@ -27,12 +35,8 @@ export type {
27
35
  } from "@copilotkit/mcp-apps-renderer/activity";
28
36
 
29
37
  import type { MCPAppsActivityContent } from "@copilotkit/mcp-apps-renderer/activity";
30
- // Type-only imports: erased at build, so they never pull the ext-apps bridge
31
- // into the bundle. Only the dynamic import() below does, and only lazily.
32
- import type {
33
- McpAppSession,
34
- FetchedResource,
35
- } from "@copilotkit/mcp-apps-renderer";
38
+ import { ɵMcpAppHost } from "@copilotkit/mcp-apps-renderer/activity";
39
+ import type { ɵMcpAppHostState } from "@copilotkit/mcp-apps-renderer/activity";
36
40
 
37
41
  /**
38
42
  * MCP Apps Extension Activity Renderer
@@ -77,213 +81,82 @@ export const MCPAppsActivityRenderer = defineComponent({
77
81
  setup(props) {
78
82
  const { copilotkit } = useCopilotKit();
79
83
  const containerRef = ref<HTMLDivElement | null>(null);
80
- // UI-only state: plain refs are fine (no agent-bound payload crosses here).
81
- const error = ref<Error | null>(null);
82
- // Recoverable: an activity update the content schema rejected, from the
83
- // store or from props. Cleared as soon as valid content resumes, unlike
84
- // `error`, which is a fatal setup failure.
85
- const contentError = ref<Error | null>(null);
86
- const isLoading = ref(true);
87
- const iframeSize = ref<{ width?: number; height?: number }>({});
88
- // shallowRef for externally-owned objects: the session, the iframe element
89
- // and the fetched resource must never be wrapped in a reactive proxy (the
90
- // resource crosses into the widget, and proxies are not clone-safe).
91
- const iframeRef = shallowRef<HTMLIFrameElement | null>(null);
92
- const sessionRef = shallowRef<McpAppSession | null>(null);
93
- const fetchedResource = shallowRef<FetchedResource | null>(null);
94
-
95
- // The activity message id. Passed to the session so it self-subscribes to
96
- // the agent's activity stream and pushes tool input/result itself (this
97
- // adapter does not forward store-backed activities).
98
- const messageId = computed(() => props.message?.id);
84
+ // The shared host owns the iframe, its size and its state, and binds the
85
+ // protocol session (loaded lazily); this component only draws the state.
86
+ const app = shallowRef<ɵMcpAppHost | null>(null);
87
+ // Set by the immediate watcher below, before the first render.
88
+ const state = shallowRef<ɵMcpAppHostState>({
89
+ loading: true,
90
+ error: null,
91
+ contentError: null,
92
+ size: {},
93
+ prefersBorder: false,
94
+ });
95
+ let unsubscribe: () => void = () => undefined;
99
96
 
100
- // Create the sandbox iframe and bind the MCP session. Re-binds only when the
101
- // widget identity (resourceUri/serverHash/serverId) or the agent/host/message
102
- // changes - NOT when tool input/result stream in (those are pushed without
103
- // recreating the iframe). `flush: "post"` so the container element is mounted
104
- // when the callback runs.
97
+ // A new host or sandbox setting needs a new host object.
105
98
  watch(
106
- [
107
- // The container element is a watch source, not just a read: `immediate`
108
- // runs the callback synchronously at setup, before the template ref is
109
- // populated. Watching it re-runs the bind once the element mounts.
110
- containerRef,
111
- () => props.agent,
112
- () => copilotkit.value,
113
- messageId,
114
- () => props.content.resourceUri,
115
- () => props.content.serverHash,
116
- () => props.content.serverId,
117
- () => props.allowSameOriginFallback,
118
- ],
119
- ([container, agent], _old, onCleanup) => {
120
- if (!agent) {
121
- error.value = new Error("No agent available to fetch resource");
122
- isLoading.value = false;
123
- return;
124
- }
125
- if (!container) {
126
- return;
127
- }
128
-
129
- let mounted = true;
130
- isLoading.value = true;
131
- error.value = null;
132
- contentError.value = null;
133
- iframeSize.value = {};
134
- fetchedResource.value = null;
135
-
136
- // The host owns the iframe: create + mount it here (bindMcpApp only
137
- // configures the sandbox contract + talks to it through the bridge).
138
- const iframe = document.createElement("iframe");
139
- iframe.style.width = "100%";
140
- iframe.style.height = "100px";
141
- iframe.style.border = "none";
142
- iframe.style.backgroundColor = "transparent";
143
- iframe.style.display = "block";
144
- container.appendChild(iframe);
145
- iframeRef.value = iframe;
146
-
147
- // Register cleanup BEFORE the async import: if this watcher is
148
- // invalidated (props change / unmount) while the import is in flight,
149
- // `mounted` is already false when it resolves, so we never bind.
150
- onCleanup(() => {
151
- mounted = false;
152
- sessionRef.value?.teardown();
153
- sessionRef.value = null;
154
- iframe.remove();
155
- if (iframeRef.value === iframe) {
156
- iframeRef.value = null;
157
- }
99
+ [() => copilotkit.value, () => props.allowSameOriginFallback],
100
+ ([core, allowSameOriginFallback]) => {
101
+ unsubscribe();
102
+ app.value?.destroy();
103
+ const created = new ɵMcpAppHost({
104
+ host: core,
105
+ sessionOptions: { allowSameOriginFallback },
106
+ importRenderer: () => import("@copilotkit/mcp-apps-renderer"),
158
107
  });
159
-
160
- const setup = async () => {
161
- try {
162
- // Load the bridge package lazily. The bridge is heavy (it pulls the
163
- // MCP SDK Protocol + zod schemas, ~40-50 kB gzipped); keeping it
164
- // behind a dynamic import() means a non-MCP app never pays for it.
165
- const mod = await import("@copilotkit/mcp-apps-renderer").catch(
166
- (importErr) => {
167
- throw new Error(
168
- "MCP Apps require '@copilotkit/mcp-apps-renderer' and its " +
169
- "'@modelcontextprotocol/ext-apps' dependency. Reinstall your " +
170
- "dependencies if this package is missing.",
171
- { cause: importErr },
172
- );
173
- },
174
- );
175
- if (!mounted) return;
176
-
177
- const session = mod.bindMcpApp({
178
- iframe,
179
- getContent: () => props.content,
180
- getAgent: () => props.agent,
181
- host: copilotkit.value,
182
- // Self-driving: the session subscribes to the agent's activity
183
- // stream (filtered by messageId) and pushes tool input/result to
184
- // the widget itself, so this adapter does not forward STORE-backed
185
- // activities. It still calls syncContent for activities rendered
186
- // from an external messages list (see the seed below).
187
- messageId: messageId.value,
188
- options: {
189
- allowSameOriginFallback: props.allowSameOriginFallback,
190
- },
191
- hooks: {
192
- onResource: (resource) => {
193
- if (!mounted) return;
194
- fetchedResource.value = resource;
195
- isLoading.value = false;
196
- },
197
- onSizeChanged: (size) => {
198
- if (mounted) iframeSize.value = size;
199
- },
200
- onContentError: (err) => {
201
- if (mounted) contentError.value = err;
202
- },
203
- onError: (err) => {
204
- if (!mounted) return;
205
- error.value = err;
206
- isLoading.value = false;
207
- },
208
- },
209
- });
210
- sessionRef.value = session;
211
- // Seed the initial content now: the content watcher below does not
212
- // re-run for unchanged props, so an activity rendered from an
213
- // EXTERNAL messages list (absent from agent.messages) would never
214
- // receive its initial tool input/result. Deduped + store precedence
215
- // make this a no-op for agent-backed activities.
216
- session.syncContent(props.content);
217
- } catch (err) {
218
- console.error("[MCPAppsRenderer] Setup error:", err);
219
- if (mounted) {
220
- error.value = err instanceof Error ? err : new Error(String(err));
221
- isLoading.value = false;
222
- }
223
- }
224
- };
225
-
226
- void setup();
108
+ unsubscribe = created.subscribe(() => {
109
+ state.value = created.getState();
110
+ });
111
+ state.value = created.getState();
112
+ app.value = created;
227
113
  },
228
- { immediate: true, flush: "post" },
114
+ { immediate: true },
229
115
  );
230
-
231
- // Size the iframe when the widget reports a new content size.
116
+ // `flush: "post"` so the container element is mounted when this runs.
232
117
  watch(
233
- iframeSize,
234
- (size) => {
235
- const iframe = iframeRef.value;
236
- if (!iframe) return;
237
- if (size.width !== undefined) {
238
- // Use minWidth with min() to allow expansion but cap at 100%
239
- iframe.style.minWidth = `min(${size.width}px, 100%)`;
240
- iframe.style.width = "100%";
241
- }
242
- if (size.height !== undefined) {
243
- iframe.style.height = `${size.height}px`;
244
- }
118
+ [
119
+ app,
120
+ () => props.agent,
121
+ () => props.message?.id,
122
+ () => props.content,
123
+ containerRef,
124
+ ],
125
+ ([host, agent, messageId, content, container]) => {
126
+ if (!host) return;
127
+ host.setAgent(agent);
128
+ host.setMessageId(messageId);
129
+ host.setContent(content);
130
+ host.setContainer(container);
245
131
  },
246
- { deep: true },
132
+ { immediate: true, flush: "post" },
247
133
  );
134
+ onBeforeUnmount(() => {
135
+ unsubscribe();
136
+ app.value?.destroy();
137
+ });
248
138
 
249
- // Forward tool input/result from the content prop. The session is
250
- // self-driving for activities that live in the agent's message store, but a
251
- // host can render an activity from an EXTERNAL messages list that is absent
252
- // from `agent.messages`; this keeps such widgets fed. `syncContent` is
253
- // deduped and shares the subscription's dedup, so the agent-driven path
254
- // never double-sends.
255
- watch(
256
- [() => props.content.toolInput, () => props.content.result],
257
- () => {
258
- sessionRef.value?.syncContent(props.content);
259
- },
260
- { deep: true },
139
+ // prefersBorder: true = border and background; otherwise none.
140
+ const borderStyle = computed(() =>
141
+ state.value.prefersBorder
142
+ ? {
143
+ borderRadius: "8px",
144
+ backgroundColor: "#f9f9f9",
145
+ border: "1px solid #e0e0e0",
146
+ }
147
+ : {},
261
148
  );
262
149
 
263
- // Determine border styling based on prefersBorder metadata from fetched
264
- // resource: true = show border/background, false = none, undefined = host
265
- // decides (we default to none).
266
- const borderStyle = computed(() => {
267
- const prefersBorder = fetchedResource.value?._meta?.ui?.prefersBorder;
268
- if (prefersBorder !== true) return {};
269
- return {
270
- borderRadius: "8px",
271
- backgroundColor: "#f9f9f9",
272
- border: "1px solid #e0e0e0",
273
- };
274
- });
275
-
276
- return () =>
277
- h(
150
+ return () => {
151
+ const { loading, error, contentError, size } = state.value;
152
+ const shownError = error ?? contentError;
153
+ return h(
278
154
  "div",
279
155
  {
280
156
  ref: containerRef,
281
157
  style: {
282
158
  width: "100%",
283
- height:
284
- iframeSize.value.height !== undefined
285
- ? `${iframeSize.value.height}px`
286
- : "auto",
159
+ height: size.height !== undefined ? `${size.height}px` : "auto",
287
160
  minHeight: "100px",
288
161
  overflow: "hidden",
289
162
  position: "relative",
@@ -291,22 +164,23 @@ export const MCPAppsActivityRenderer = defineComponent({
291
164
  },
292
165
  },
293
166
  [
294
- isLoading.value
167
+ loading
295
168
  ? h(
296
169
  "div",
297
170
  { style: { padding: "1rem", color: "#666" } },
298
171
  "Loading...",
299
172
  )
300
173
  : null,
301
- error.value || contentError.value
174
+ shownError
302
175
  ? h(
303
176
  "div",
304
177
  { style: { color: "red", padding: "1rem" } },
305
- `Error: ${(error.value ?? contentError.value)!.message}`,
178
+ `Error: ${shownError}`,
306
179
  )
307
180
  : null,
308
181
  ],
309
182
  );
183
+ };
310
184
  },
311
185
  });
312
186
 
@@ -8,11 +8,13 @@ import {
8
8
  watch,
9
9
  } from "vue";
10
10
  import type { PropType } from "vue";
11
- import * as z from "zod";
12
11
  import {
13
12
  OPEN_GENERATIVE_UI_ACTIVITY_TYPE,
14
13
  ToolCallStatus,
14
+ ɵGENERATE_SANDBOXED_UI_ARGS_SCHEMA,
15
+ ɵOPEN_GENERATIVE_UI_CONTENT_SCHEMA,
15
16
  ɵOpenGenerativeUIController,
17
+ ɵPlaceholderCycler,
16
18
  } from "@copilotkit/core";
17
19
  import type {
18
20
  GenerateSandboxedUiArgs,
@@ -24,27 +26,14 @@ export type { GenerateSandboxedUiArgs, OpenGenerativeUIContent };
24
26
 
25
27
  export const OpenGenerativeUIActivityType = OPEN_GENERATIVE_UI_ACTIVITY_TYPE;
26
28
 
27
- export const OpenGenerativeUIContentSchema = z.object({
28
- initialHeight: z.number().optional(),
29
- generating: z.boolean().optional(),
30
- css: z.string().optional(),
31
- cssComplete: z.boolean().optional(),
32
- html: z.array(z.string()).optional(),
33
- htmlComplete: z.boolean().optional(),
34
- jsFunctions: z.string().optional(),
35
- jsFunctionsComplete: z.boolean().optional(),
36
- jsExpressions: z.array(z.string()).optional(),
37
- jsExpressionsComplete: z.boolean().optional(),
38
- }) satisfies z.ZodType<OpenGenerativeUIContent>;
29
+ /** Validates Open Generative UI activity content (shared with React and Angular). */
30
+ export const OpenGenerativeUIContentSchema = ɵOPEN_GENERATIVE_UI_CONTENT_SCHEMA;
39
31
 
40
- export const GenerateSandboxedUiArgsSchema = z.object({
41
- initialHeight: z.number().optional(),
42
- placeholderMessages: z.array(z.string()).optional(),
43
- css: z.string().optional(),
44
- html: z.string().optional(),
45
- jsFunctions: z.string().optional(),
46
- jsExpressions: z.array(z.string()).optional(),
47
- }) satisfies z.ZodType<GenerateSandboxedUiArgs>;
32
+ /**
33
+ * Schema for the generateSandboxedUi tool call arguments (shared with React
34
+ * and Angular). It also describes the arguments to the model as JSON Schema.
35
+ */
36
+ export const GenerateSandboxedUiArgsSchema = ɵGENERATE_SANDBOXED_UI_ARGS_SCHEMA;
48
37
 
49
38
  /**
50
39
  * Renders Open Generative UI content. The core controller throttles the
@@ -84,7 +73,7 @@ export const OpenGenerativeUIRenderer = defineComponent({
84
73
  });
85
74
 
86
75
  return () => {
87
- const { phase, generating, height } = state.value;
76
+ const { phase, height, showSpinner, showOverlay } = state.value;
88
77
  const hasVisibleSandbox = phase !== "placeholder";
89
78
  return h(
90
79
  "div",
@@ -105,48 +94,57 @@ export const OpenGenerativeUIRenderer = defineComponent({
105
94
  },
106
95
  },
107
96
  [
108
- !hasVisibleSandbox
109
- ? h("div", { "data-testid": "open-generative-ui-placeholder" }, [
110
- h(
111
- "svg",
112
- {
113
- width: "48",
114
- height: "48",
115
- viewBox: "0 0 24 24",
116
- fill: "none",
117
- style: { animation: "ck-spin 1s linear infinite" },
97
+ showOverlay
98
+ ? h(
99
+ "div",
100
+ {
101
+ "data-testid": "open-generative-ui-progress-overlay",
102
+ style: {
103
+ position: "absolute",
104
+ inset: 0,
105
+ zIndex: 10,
106
+ pointerEvents: "all",
107
+ backgroundColor: "rgba(255, 255, 255, 0.5)",
108
+ display: "flex",
109
+ alignItems: "center",
110
+ justifyContent: "center",
118
111
  },
119
- [
120
- h("circle", {
121
- cx: "12",
122
- cy: "12",
123
- r: "10",
124
- stroke: "#e0e0e0",
125
- "stroke-width": "3",
126
- }),
127
- h("path", {
128
- d: "M12 2a10 10 0 0 1 10 10",
129
- stroke: "#999",
130
- "stroke-width": "3",
131
- "stroke-linecap": "round",
132
- }),
133
- ],
134
- ),
135
- h(
136
- "style",
137
- "@keyframes ck-spin { to { transform: rotate(360deg) } }",
138
- ),
139
- ])
140
- : null,
141
- generating
142
- ? h("div", {
143
- "data-testid": "open-generative-ui-progress-overlay",
144
- style: {
145
- position: "absolute",
146
- inset: 0,
147
- backgroundColor: "rgba(255,255,255,0.45)",
148
112
  },
149
- })
113
+ showSpinner
114
+ ? [
115
+ h(
116
+ "svg",
117
+ {
118
+ "data-testid": "open-generative-ui-spinner",
119
+ width: "48",
120
+ height: "48",
121
+ viewBox: "0 0 24 24",
122
+ fill: "none",
123
+ style: { animation: "ck-spin 1s linear infinite" },
124
+ },
125
+ [
126
+ h("circle", {
127
+ cx: "12",
128
+ cy: "12",
129
+ r: "10",
130
+ stroke: "#e0e0e0",
131
+ "stroke-width": "3",
132
+ }),
133
+ h("path", {
134
+ d: "M12 2a10 10 0 0 1 10 10",
135
+ stroke: "#999",
136
+ "stroke-width": "3",
137
+ "stroke-linecap": "round",
138
+ }),
139
+ ],
140
+ ),
141
+ h(
142
+ "style",
143
+ "@keyframes ck-spin { to { transform: rotate(360deg) } }",
144
+ ),
145
+ ]
146
+ : [],
147
+ )
150
148
  : null,
151
149
  ],
152
150
  );
@@ -186,35 +184,35 @@ export const OpenGenerativeUIToolRenderer = defineComponent({
186
184
  result: { type: String as PropType<string | undefined>, required: false },
187
185
  },
188
186
  setup(props) {
189
- const visibleMessageIndex = ref(0);
190
-
187
+ const cycler = new ɵPlaceholderCycler();
188
+ const message = shallowRef(cycler.getSnapshot().message);
189
+ const unsubscribe = cycler.subscribe(() => {
190
+ message.value = cycler.getSnapshot().message;
191
+ });
191
192
  watch(
192
- () => props.args.placeholderMessages,
193
- (messages, _, onCleanup) => {
194
- if (!messages?.length || props.status === ToolCallStatus.Complete)
195
- return;
196
- visibleMessageIndex.value = Math.max(messages.length - 1, 0);
197
- const timer = window.setInterval(() => {
198
- visibleMessageIndex.value =
199
- (visibleMessageIndex.value + 1) % Math.max(messages.length, 1);
200
- }, 5000);
201
- onCleanup(() => window.clearInterval(timer));
202
- },
193
+ () =>
194
+ [
195
+ props.args.placeholderMessages,
196
+ props.status === ToolCallStatus.Complete,
197
+ ] as const,
198
+ ([messages, complete]) => cycler.update(messages, complete),
203
199
  { immediate: true },
204
200
  );
201
+ onBeforeUnmount(() => {
202
+ unsubscribe();
203
+ cycler.dispose();
204
+ });
205
205
 
206
206
  return () => {
207
- if (props.status === ToolCallStatus.Complete) return null;
208
- const messages = props.args.placeholderMessages;
209
- if (!messages?.length) return null;
210
- const currentMessage = messages[visibleMessageIndex.value] ?? messages[0];
207
+ // Nothing once complete — the activity renderer handles the UI.
208
+ if (message.value === undefined) return null;
211
209
  return h(
212
210
  "div",
213
211
  {
214
212
  style: { padding: "8px 12px", color: "#999", fontSize: "14px" },
215
213
  "data-testid": "open-generative-ui-tool-placeholder",
216
214
  },
217
- currentMessage,
215
+ message.value,
218
216
  );
219
217
  };
220
218
  },