@copilotkit/react-core 1.54.1 → 1.55.0-next.8

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 (183) hide show
  1. package/CHANGELOG.md +127 -116
  2. package/dist/copilotkit-B3Mb1yVE.cjs +7975 -0
  3. package/dist/copilotkit-B3Mb1yVE.cjs.map +1 -0
  4. package/dist/copilotkit-DBzgOMby.d.cts +2182 -0
  5. package/dist/copilotkit-DBzgOMby.d.cts.map +1 -0
  6. package/dist/copilotkit-DNYSFuz5.mjs +7562 -0
  7. package/dist/copilotkit-DNYSFuz5.mjs.map +1 -0
  8. package/dist/copilotkit-Dy5w3qEV.d.mts +2182 -0
  9. package/dist/copilotkit-Dy5w3qEV.d.mts.map +1 -0
  10. package/dist/index.cjs +27 -28
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +3 -3
  13. package/dist/index.d.cts.map +1 -1
  14. package/dist/index.d.mts +3 -3
  15. package/dist/index.d.mts.map +1 -1
  16. package/dist/index.mjs +4 -5
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/index.umd.js +1941 -35
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/v2/index.cjs +77 -7
  21. package/dist/v2/index.css +1 -2
  22. package/dist/v2/index.d.cts +6 -4
  23. package/dist/v2/index.d.mts +6 -4
  24. package/dist/v2/index.mjs +7 -4
  25. package/dist/v2/index.umd.js +5725 -24
  26. package/dist/v2/index.umd.js.map +1 -1
  27. package/package.json +37 -9
  28. package/scripts/scope-preflight.mjs +101 -0
  29. package/src/components/CopilotListeners.tsx +2 -6
  30. package/src/components/copilot-provider/copilot-messages.tsx +1 -1
  31. package/src/components/copilot-provider/copilotkit-props.tsx +1 -1
  32. package/src/components/copilot-provider/copilotkit.tsx +4 -4
  33. package/src/context/copilot-messages-context.tsx +1 -1
  34. package/src/hooks/__tests__/use-coagent-config.test.ts +2 -2
  35. package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +2 -2
  36. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +3 -7
  37. package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +1 -1
  38. package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +4 -4
  39. package/src/hooks/use-agent-nodename.ts +1 -1
  40. package/src/hooks/use-coagent-state-render-bridge.tsx +1 -4
  41. package/src/hooks/use-coagent.ts +1 -1
  42. package/src/hooks/use-configure-chat-suggestions.tsx +2 -2
  43. package/src/hooks/use-copilot-chat-suggestions.tsx +2 -2
  44. package/src/hooks/use-copilot-chat_internal.ts +2 -2
  45. package/src/hooks/use-copilot-readable.ts +1 -1
  46. package/src/hooks/use-frontend-tool.ts +2 -2
  47. package/src/hooks/use-human-in-the-loop.ts +2 -2
  48. package/src/hooks/use-langgraph-interrupt.ts +2 -5
  49. package/src/hooks/use-lazy-tool-renderer.tsx +1 -1
  50. package/src/hooks/use-render-tool-call.ts +1 -1
  51. package/src/lib/copilot-task.ts +1 -1
  52. package/src/setupTests.ts +18 -14
  53. package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +176 -0
  54. package/src/v2/__tests__/globalSetup.ts +14 -0
  55. package/src/v2/__tests__/setup.ts +93 -0
  56. package/src/v2/__tests__/utils/test-helpers.tsx +470 -0
  57. package/src/v2/a2ui/A2UIMessageRenderer.tsx +206 -0
  58. package/src/v2/components/CopilotKitInspector.tsx +50 -0
  59. package/src/v2/components/MCPAppsActivityRenderer.tsx +785 -0
  60. package/src/v2/components/WildcardToolCallRender.tsx +86 -0
  61. package/src/v2/components/__tests__/license-warning-banner.test.tsx +46 -0
  62. package/src/v2/components/chat/CopilotChat.tsx +431 -0
  63. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +375 -0
  64. package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +350 -0
  65. package/src/v2/components/chat/CopilotChatInput.tsx +1302 -0
  66. package/src/v2/components/chat/CopilotChatMessageView.tsx +556 -0
  67. package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +252 -0
  68. package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +59 -0
  69. package/src/v2/components/chat/CopilotChatSuggestionView.tsx +133 -0
  70. package/src/v2/components/chat/CopilotChatToggleButton.tsx +171 -0
  71. package/src/v2/components/chat/CopilotChatToolCallsView.tsx +40 -0
  72. package/src/v2/components/chat/CopilotChatUserMessage.tsx +388 -0
  73. package/src/v2/components/chat/CopilotChatView.tsx +598 -0
  74. package/src/v2/components/chat/CopilotModalHeader.tsx +129 -0
  75. package/src/v2/components/chat/CopilotPopup.tsx +81 -0
  76. package/src/v2/components/chat/CopilotPopupView.tsx +317 -0
  77. package/src/v2/components/chat/CopilotSidebar.tsx +76 -0
  78. package/src/v2/components/chat/CopilotSidebarView.tsx +255 -0
  79. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +1113 -0
  80. package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +73 -0
  81. package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +432 -0
  82. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +150 -0
  83. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +624 -0
  84. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +702 -0
  85. package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +107 -0
  86. package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +929 -0
  87. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +986 -0
  88. package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +1004 -0
  89. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +169 -0
  90. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +530 -0
  91. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +782 -0
  92. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2413 -0
  93. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +621 -0
  94. package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +853 -0
  95. package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +1050 -0
  96. package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +484 -0
  97. package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +612 -0
  98. package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +502 -0
  99. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1011 -0
  100. package/src/v2/components/chat/__tests__/setup.ts +1 -0
  101. package/src/v2/components/chat/index.ts +79 -0
  102. package/src/v2/components/index.ts +7 -0
  103. package/src/v2/components/license-warning-banner.tsx +198 -0
  104. package/src/v2/components/ui/button.tsx +123 -0
  105. package/src/v2/components/ui/dropdown-menu.tsx +258 -0
  106. package/src/v2/components/ui/tooltip.tsx +60 -0
  107. package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +152 -0
  108. package/src/v2/hooks/__tests__/standard-schema.test.tsx +282 -0
  109. package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +132 -0
  110. package/src/v2/hooks/__tests__/use-agent-context.test.tsx +401 -0
  111. package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +44 -0
  112. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +205 -0
  113. package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +148 -0
  114. package/src/v2/hooks/__tests__/use-component.test.tsx +123 -0
  115. package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +696 -0
  116. package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +153 -0
  117. package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +167 -0
  118. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +2129 -0
  119. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +1261 -0
  120. package/src/v2/hooks/__tests__/use-interrupt.test.tsx +397 -0
  121. package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +56 -0
  122. package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +192 -0
  123. package/src/v2/hooks/__tests__/use-render-tool.test.tsx +259 -0
  124. package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +524 -0
  125. package/src/v2/hooks/__tests__/use-threads.test.tsx +433 -0
  126. package/src/v2/hooks/__tests__/zod-regression.test.tsx +311 -0
  127. package/src/v2/hooks/index.ts +18 -0
  128. package/src/v2/hooks/use-agent-context.tsx +45 -0
  129. package/src/v2/hooks/use-agent.tsx +155 -0
  130. package/src/v2/hooks/use-component.tsx +89 -0
  131. package/src/v2/hooks/use-configure-suggestions.tsx +187 -0
  132. package/src/v2/hooks/use-default-render-tool.tsx +254 -0
  133. package/src/v2/hooks/use-frontend-tool.tsx +43 -0
  134. package/src/v2/hooks/use-human-in-the-loop.tsx +81 -0
  135. package/src/v2/hooks/use-interrupt.tsx +305 -0
  136. package/src/v2/hooks/use-keyboard-height.tsx +67 -0
  137. package/src/v2/hooks/use-render-activity-message.tsx +73 -0
  138. package/src/v2/hooks/use-render-custom-messages.tsx +93 -0
  139. package/src/v2/hooks/use-render-tool-call.tsx +175 -0
  140. package/src/v2/hooks/use-render-tool.tsx +181 -0
  141. package/src/v2/hooks/use-suggestions.tsx +91 -0
  142. package/src/v2/hooks/use-threads.tsx +256 -0
  143. package/src/v2/hooks/useKatexStyles.ts +27 -0
  144. package/src/v2/index.css +1 -1
  145. package/src/v2/index.ts +18 -2
  146. package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +495 -0
  147. package/src/v2/lib/__tests__/renderSlot.test.tsx +588 -0
  148. package/src/v2/lib/react-core.ts +156 -0
  149. package/src/v2/lib/slots.tsx +143 -0
  150. package/src/v2/lib/transcription-client.ts +184 -0
  151. package/src/v2/lib/utils.ts +8 -0
  152. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +162 -0
  153. package/src/v2/providers/CopilotKitProvider.tsx +600 -0
  154. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +546 -0
  155. package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +101 -0
  156. package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +69 -0
  157. package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +881 -0
  158. package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +740 -0
  159. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +642 -0
  160. package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +294 -0
  161. package/src/v2/providers/index.ts +14 -0
  162. package/src/v2/styles/globals.css +230 -0
  163. package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +525 -0
  164. package/src/v2/types/defineToolCallRenderer.ts +65 -0
  165. package/src/v2/types/frontend-tool.ts +8 -0
  166. package/src/v2/types/human-in-the-loop.ts +33 -0
  167. package/src/v2/types/index.ts +7 -0
  168. package/src/v2/types/interrupt.ts +15 -0
  169. package/src/v2/types/react-activity-message-renderer.ts +27 -0
  170. package/src/v2/types/react-custom-message-renderer.ts +17 -0
  171. package/src/v2/types/react-tool-call-renderer.ts +32 -0
  172. package/tsdown.config.ts +34 -10
  173. package/vitest.config.mjs +4 -3
  174. package/LICENSE +0 -21
  175. package/dist/copilotkit-BRPQ2sqS.d.cts +0 -670
  176. package/dist/copilotkit-BRPQ2sqS.d.cts.map +0 -1
  177. package/dist/copilotkit-C94ayZbs.cjs +0 -2161
  178. package/dist/copilotkit-C94ayZbs.cjs.map +0 -1
  179. package/dist/copilotkit-CwZMFmSK.d.mts +0 -670
  180. package/dist/copilotkit-CwZMFmSK.d.mts.map +0 -1
  181. package/dist/copilotkit-Yh_Ld_FX.mjs +0 -2031
  182. package/dist/copilotkit-Yh_Ld_FX.mjs.map +0 -1
  183. package/dist/v2/index.css.map +0 -1
@@ -0,0 +1,470 @@
1
+ import React from "react";
2
+ import { render, act } from "@testing-library/react";
3
+ import { CopilotKitProvider } from "../../providers/CopilotKitProvider";
4
+ import { CopilotChat } from "../../components/chat/CopilotChat";
5
+ import { CopilotChatConfigurationProvider } from "../../providers/CopilotChatConfigurationProvider";
6
+ import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
7
+ import {
8
+ AbstractAgent,
9
+ EventType,
10
+ type BaseEvent,
11
+ type RunAgentInput,
12
+ } from "@ag-ui/client";
13
+ import { Observable, Subject, from, delay } from "rxjs";
14
+ import {
15
+ ReactActivityMessageRenderer,
16
+ ReactToolCallRenderer,
17
+ } from "../../types";
18
+ import { ReactCustomMessageRenderer } from "../../types/react-custom-message-renderer";
19
+
20
+ /**
21
+ * A controllable mock agent for deterministic E2E testing.
22
+ * Exposes emit() and complete() methods to drive agent events step-by-step.
23
+ */
24
+ export class MockStepwiseAgent extends AbstractAgent {
25
+ private subject = new Subject<BaseEvent>();
26
+
27
+ /**
28
+ * Emit a single agent event
29
+ */
30
+ emit(event: BaseEvent) {
31
+ if (event.type === EventType.RUN_STARTED) {
32
+ this.isRunning = true;
33
+ } else if (
34
+ event.type === EventType.RUN_FINISHED ||
35
+ event.type === EventType.RUN_ERROR
36
+ ) {
37
+ this.isRunning = false;
38
+ }
39
+ act(() => {
40
+ this.subject.next(event);
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Complete the agent stream
46
+ */
47
+ complete() {
48
+ this.isRunning = false;
49
+ act(() => {
50
+ this.subject.complete();
51
+ });
52
+ }
53
+
54
+ clone(): MockStepwiseAgent {
55
+ // For tests, return same instance so we can keep controlling it
56
+ return this;
57
+ }
58
+
59
+ // No-op: prevent the base class from tearing down the Subject
60
+ // before tests have finished emitting events.
61
+ async detachActiveRun(): Promise<void> {}
62
+
63
+ run(_input: RunAgentInput): Observable<BaseEvent> {
64
+ return this.subject.asObservable();
65
+ }
66
+ }
67
+
68
+ /**
69
+ * A mock agent that supports both run() and connect() for testing reconnection scenarios.
70
+ * On run(), emits events and stores them.
71
+ * On connect(), replays stored events (simulating thread history replay).
72
+ */
73
+ export class MockReconnectableAgent extends AbstractAgent {
74
+ private subject = new Subject<BaseEvent>();
75
+ private storedEvents: BaseEvent[] = [];
76
+
77
+ /**
78
+ * Emit a single agent event during run
79
+ */
80
+ emit(event: BaseEvent) {
81
+ if (event.type === EventType.RUN_STARTED) {
82
+ this.isRunning = true;
83
+ } else if (
84
+ event.type === EventType.RUN_FINISHED ||
85
+ event.type === EventType.RUN_ERROR
86
+ ) {
87
+ this.isRunning = false;
88
+ }
89
+ this.storedEvents.push(event);
90
+ act(() => {
91
+ this.subject.next(event);
92
+ });
93
+ }
94
+
95
+ /**
96
+ * Complete the agent stream
97
+ */
98
+ complete() {
99
+ this.isRunning = false;
100
+ act(() => {
101
+ this.subject.complete();
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Reset for reconnection test - creates new subject for connect
107
+ */
108
+ reset() {
109
+ this.subject = new Subject<BaseEvent>();
110
+ }
111
+
112
+ clone(): MockReconnectableAgent {
113
+ return this;
114
+ }
115
+
116
+ // No-op: prevent the base class from tearing down the Subject
117
+ // before tests have finished emitting events.
118
+ async detachActiveRun(): Promise<void> {}
119
+
120
+ run(_input: RunAgentInput): Observable<BaseEvent> {
121
+ return this.subject.asObservable();
122
+ }
123
+
124
+ connect(_input: RunAgentInput): Observable<BaseEvent> {
125
+ // Replay stored events with async delay to simulate HTTP transport
126
+ // This is critical for reproducing timing bugs that occur in real scenarios
127
+ return from(this.storedEvents).pipe(delay(10));
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Helper to render components with CopilotKitProvider for E2E tests
133
+ */
134
+ export function renderWithCopilotKit({
135
+ agent,
136
+ agents,
137
+ renderToolCalls,
138
+ renderCustomMessages,
139
+ renderActivityMessages,
140
+ frontendTools,
141
+ humanInTheLoop,
142
+ agentId,
143
+ threadId,
144
+ children,
145
+ }: {
146
+ agent?: AbstractAgent;
147
+ agents?: Record<string, AbstractAgent>;
148
+ renderToolCalls?: ReactToolCallRenderer<any>[];
149
+ renderCustomMessages?: ReactCustomMessageRenderer[];
150
+ renderActivityMessages?: ReactActivityMessageRenderer<any>[];
151
+ frontendTools?: any[];
152
+ humanInTheLoop?: any[];
153
+ agentId?: string;
154
+ threadId?: string;
155
+ children?: React.ReactNode;
156
+ }): ReturnType<typeof render> {
157
+ const resolvedAgents = agents || (agent ? { default: agent } : undefined);
158
+ const resolvedAgentId = agentId ?? DEFAULT_AGENT_ID;
159
+ const resolvedThreadId = threadId ?? "test-thread";
160
+
161
+ return render(
162
+ <CopilotKitProvider
163
+ agents__unsafe_dev_only={resolvedAgents}
164
+ renderToolCalls={renderToolCalls}
165
+ renderCustomMessages={renderCustomMessages}
166
+ renderActivityMessages={renderActivityMessages}
167
+ frontendTools={frontendTools}
168
+ humanInTheLoop={humanInTheLoop}
169
+ >
170
+ <CopilotChatConfigurationProvider
171
+ agentId={resolvedAgentId}
172
+ threadId={resolvedThreadId}
173
+ >
174
+ {children || (
175
+ <div style={{ height: 400 }}>
176
+ <CopilotChat welcomeScreen={false} />
177
+ </div>
178
+ )}
179
+ </CopilotChatConfigurationProvider>
180
+ </CopilotKitProvider>,
181
+ );
182
+ }
183
+
184
+ /**
185
+ * Helper to create a RUN_STARTED event
186
+ */
187
+ export function runStartedEvent(): BaseEvent {
188
+ return { type: EventType.RUN_STARTED } as BaseEvent;
189
+ }
190
+
191
+ /**
192
+ * Helper to create a RUN_FINISHED event
193
+ */
194
+ export function runFinishedEvent(): BaseEvent {
195
+ return { type: EventType.RUN_FINISHED } as BaseEvent;
196
+ }
197
+
198
+ /**
199
+ * Helper to create a STATE_SNAPSHOT event
200
+ */
201
+ export function stateSnapshotEvent(snapshot: unknown): BaseEvent {
202
+ return {
203
+ type: EventType.STATE_SNAPSHOT,
204
+ snapshot,
205
+ } as BaseEvent;
206
+ }
207
+
208
+ /**
209
+ * Helper to create an ACTIVITY_SNAPSHOT event
210
+ */
211
+ export function activitySnapshotEvent({
212
+ messageId,
213
+ activityType,
214
+ content,
215
+ }: {
216
+ messageId: string;
217
+ activityType: string;
218
+ content: Record<string, unknown>;
219
+ }): BaseEvent {
220
+ return {
221
+ type: EventType.ACTIVITY_SNAPSHOT,
222
+ messageId,
223
+ activityType,
224
+ content,
225
+ } as BaseEvent;
226
+ }
227
+
228
+ /**
229
+ * Helper to start an assistant text message
230
+ */
231
+ export function textMessageStartEvent(
232
+ messageId: string,
233
+ role: "assistant" | "developer" | "system" | "user" = "assistant",
234
+ ): BaseEvent {
235
+ return {
236
+ type: EventType.TEXT_MESSAGE_START,
237
+ messageId,
238
+ role,
239
+ } as BaseEvent;
240
+ }
241
+
242
+ /**
243
+ * Helper to stream text message content
244
+ */
245
+ export function textMessageContentEvent(
246
+ messageId: string,
247
+ delta: string,
248
+ ): BaseEvent {
249
+ return {
250
+ type: EventType.TEXT_MESSAGE_CONTENT,
251
+ messageId,
252
+ delta,
253
+ } as BaseEvent;
254
+ }
255
+
256
+ /**
257
+ * Helper to end a text message
258
+ */
259
+ export function textMessageEndEvent(messageId: string): BaseEvent {
260
+ return {
261
+ type: EventType.TEXT_MESSAGE_END,
262
+ messageId,
263
+ } as BaseEvent;
264
+ }
265
+
266
+ /**
267
+ * Helper to create a TEXT_MESSAGE_CHUNK event
268
+ */
269
+ export function textChunkEvent(messageId: string, delta: string): BaseEvent {
270
+ return {
271
+ type: EventType.TEXT_MESSAGE_CHUNK,
272
+ messageId,
273
+ delta,
274
+ } as BaseEvent;
275
+ }
276
+
277
+ /**
278
+ * Helper to create a TOOL_CALL_CHUNK event
279
+ */
280
+ export function toolCallChunkEvent({
281
+ toolCallId,
282
+ toolCallName,
283
+ parentMessageId,
284
+ delta,
285
+ }: {
286
+ toolCallId: string;
287
+ toolCallName?: string;
288
+ parentMessageId: string;
289
+ delta: string;
290
+ }): BaseEvent {
291
+ return {
292
+ type: EventType.TOOL_CALL_CHUNK,
293
+ toolCallId,
294
+ toolCallName,
295
+ parentMessageId,
296
+ delta,
297
+ } as BaseEvent;
298
+ }
299
+
300
+ /**
301
+ * Helper to create a TOOL_CALL_RESULT event
302
+ */
303
+ export function toolCallResultEvent({
304
+ toolCallId,
305
+ messageId,
306
+ content,
307
+ }: {
308
+ toolCallId: string;
309
+ messageId: string;
310
+ content: string;
311
+ }): BaseEvent {
312
+ return {
313
+ type: EventType.TOOL_CALL_RESULT,
314
+ toolCallId,
315
+ messageId,
316
+ content,
317
+ } as BaseEvent;
318
+ }
319
+
320
+ /**
321
+ * Helper to create a REASONING_START event
322
+ */
323
+ export function reasoningStartEvent(messageId: string): BaseEvent {
324
+ return {
325
+ type: EventType.REASONING_START,
326
+ messageId,
327
+ } as BaseEvent;
328
+ }
329
+
330
+ /**
331
+ * Helper to create a REASONING_MESSAGE_START event
332
+ */
333
+ export function reasoningMessageStartEvent(messageId: string): BaseEvent {
334
+ return {
335
+ type: EventType.REASONING_MESSAGE_START,
336
+ messageId,
337
+ role: "reasoning",
338
+ } as BaseEvent;
339
+ }
340
+
341
+ /**
342
+ * Helper to create a REASONING_MESSAGE_CONTENT event
343
+ */
344
+ export function reasoningMessageContentEvent(
345
+ messageId: string,
346
+ delta: string,
347
+ ): BaseEvent {
348
+ return {
349
+ type: EventType.REASONING_MESSAGE_CONTENT,
350
+ messageId,
351
+ delta,
352
+ } as BaseEvent;
353
+ }
354
+
355
+ /**
356
+ * Helper to create a REASONING_MESSAGE_END event
357
+ */
358
+ export function reasoningMessageEndEvent(messageId: string): BaseEvent {
359
+ return {
360
+ type: EventType.REASONING_MESSAGE_END,
361
+ messageId,
362
+ } as BaseEvent;
363
+ }
364
+
365
+ /**
366
+ * Helper to create a REASONING_END event
367
+ */
368
+ export function reasoningEndEvent(messageId: string): BaseEvent {
369
+ return {
370
+ type: EventType.REASONING_END,
371
+ messageId,
372
+ } as BaseEvent;
373
+ }
374
+
375
+ /**
376
+ * Helper to emit a complete reasoning sequence (all 5 events)
377
+ */
378
+ export function emitReasoningSequence(
379
+ agent: MockStepwiseAgent,
380
+ messageId: string,
381
+ content: string,
382
+ ) {
383
+ agent.emit(reasoningStartEvent(messageId));
384
+ agent.emit(reasoningMessageStartEvent(messageId));
385
+ agent.emit(reasoningMessageContentEvent(messageId, content));
386
+ agent.emit(reasoningMessageEndEvent(messageId));
387
+ agent.emit(reasoningEndEvent(messageId));
388
+ }
389
+
390
+ /**
391
+ * Helper to generate unique IDs for tests
392
+ */
393
+ export function testId(prefix: string): string {
394
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
395
+ }
396
+
397
+ /**
398
+ * Helper to emit a complete suggestion tool call with streaming chunks
399
+ */
400
+ export function emitSuggestionToolCall(
401
+ agent: MockStepwiseAgent,
402
+ {
403
+ toolCallId,
404
+ parentMessageId,
405
+ suggestions,
406
+ }: {
407
+ toolCallId: string;
408
+ parentMessageId: string;
409
+ suggestions: Array<{ title: string; message: string }>;
410
+ },
411
+ ) {
412
+ // Convert suggestions to JSON string
413
+ const suggestionsJson = JSON.stringify({ suggestions });
414
+
415
+ // Emit the tool call name first
416
+ agent.emit(
417
+ toolCallChunkEvent({
418
+ toolCallId,
419
+ toolCallName: "copilotkitSuggest",
420
+ parentMessageId,
421
+ delta: "",
422
+ }),
423
+ );
424
+
425
+ // Stream the JSON in chunks to simulate streaming
426
+ const chunkSize = 10; // Characters per chunk
427
+ for (let i = 0; i < suggestionsJson.length; i += chunkSize) {
428
+ const chunk = suggestionsJson.substring(i, i + chunkSize);
429
+ agent.emit(
430
+ toolCallChunkEvent({
431
+ toolCallId,
432
+ parentMessageId,
433
+ delta: chunk,
434
+ }),
435
+ );
436
+ }
437
+ }
438
+
439
+ /**
440
+ * A MockStepwiseAgent that emits suggestion events when run() is called
441
+ */
442
+ export class SuggestionsProviderAgent extends MockStepwiseAgent {
443
+ private _suggestions: Array<{ title: string; message: string }> = [];
444
+
445
+ setSuggestions(suggestions: Array<{ title: string; message: string }>) {
446
+ this._suggestions = suggestions;
447
+ }
448
+
449
+ run(_input: RunAgentInput): Observable<BaseEvent> {
450
+ // Call the parent's run() to get the Subject that's already set up
451
+ const parentObservable = super.run(_input);
452
+
453
+ // Use setTimeout to emit events asynchronously through the existing subject
454
+ setTimeout(() => {
455
+ const messageId = testId("suggest-msg");
456
+ this.emit({ type: EventType.RUN_STARTED } as BaseEvent);
457
+
458
+ emitSuggestionToolCall(this, {
459
+ toolCallId: testId("tc"),
460
+ parentMessageId: messageId,
461
+ suggestions: this._suggestions,
462
+ });
463
+
464
+ this.emit({ type: EventType.RUN_FINISHED } as BaseEvent);
465
+ this.complete();
466
+ }, 0);
467
+
468
+ return parentObservable;
469
+ }
470
+ }
@@ -0,0 +1,206 @@
1
+ import { useCopilotKit } from "../providers";
2
+ import type { ReactActivityMessageRenderer } from "../types/react-activity-message-renderer";
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { z } from "zod";
5
+ import {
6
+ A2UIProvider,
7
+ useA2UIActions,
8
+ A2UIRenderer,
9
+ initializeDefaultCatalog,
10
+ injectStyles,
11
+ DEFAULT_SURFACE_ID,
12
+ } from "@copilotkit/a2ui-renderer";
13
+ import type { Theme, A2UIClientEventMessage } from "@copilotkit/a2ui-renderer";
14
+
15
+ // Initialize the React renderer's component catalog and styles once
16
+ let initialized = false;
17
+ function ensureInitialized() {
18
+ if (!initialized) {
19
+ initializeDefaultCatalog();
20
+ injectStyles();
21
+ initialized = true;
22
+ }
23
+ }
24
+
25
+ export type A2UIMessageRendererOptions = {
26
+ theme: Theme;
27
+ };
28
+
29
+ export function createA2UIMessageRenderer(
30
+ options: A2UIMessageRendererOptions,
31
+ ): ReactActivityMessageRenderer<any> {
32
+ const { theme } = options;
33
+
34
+ return {
35
+ activityType: "a2ui-surface",
36
+ content: z.any(),
37
+ render: ({ content, agent }) => {
38
+ ensureInitialized();
39
+
40
+ const [operations, setOperations] = useState<any[]>([]);
41
+ const lastSignatureRef = useRef<string | null>(null);
42
+ const { copilotkit } = useCopilotKit();
43
+
44
+ useEffect(() => {
45
+ if (!content || !Array.isArray(content.operations)) {
46
+ lastSignatureRef.current = null;
47
+ setOperations([]);
48
+ return;
49
+ }
50
+
51
+ const incoming = content.operations as any[];
52
+ const signature = stringifyOperations(incoming);
53
+
54
+ if (signature && signature === lastSignatureRef.current) {
55
+ return;
56
+ }
57
+
58
+ lastSignatureRef.current = signature;
59
+ setOperations(incoming);
60
+ }, [content]);
61
+
62
+ // Group operations by surface ID
63
+ const groupedOperations = useMemo(() => {
64
+ const groups = new Map<string, any[]>();
65
+
66
+ for (const operation of operations) {
67
+ const surfaceId =
68
+ getOperationSurfaceId(operation) ?? DEFAULT_SURFACE_ID;
69
+
70
+ if (!groups.has(surfaceId)) {
71
+ groups.set(surfaceId, []);
72
+ }
73
+ groups.get(surfaceId)!.push(operation);
74
+ }
75
+
76
+ return groups;
77
+ }, [operations]);
78
+
79
+ if (!groupedOperations.size) {
80
+ return null;
81
+ }
82
+
83
+ return (
84
+ <div className="cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6">
85
+ {Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => (
86
+ <ReactSurfaceHost
87
+ key={surfaceId}
88
+ surfaceId={surfaceId}
89
+ operations={ops}
90
+ theme={theme}
91
+ agent={agent}
92
+ copilotkit={copilotkit}
93
+ />
94
+ ))}
95
+ </div>
96
+ );
97
+ },
98
+ };
99
+ }
100
+
101
+ type ReactSurfaceHostProps = {
102
+ surfaceId: string;
103
+ operations: any[];
104
+ theme: Theme;
105
+ agent: any;
106
+ copilotkit: any;
107
+ };
108
+
109
+ /**
110
+ * Renders a single A2UI surface using the React renderer.
111
+ * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
112
+ */
113
+ function ReactSurfaceHost({
114
+ surfaceId,
115
+ operations,
116
+ theme,
117
+ agent,
118
+ copilotkit,
119
+ }: ReactSurfaceHostProps) {
120
+ // Bridge: when the React renderer dispatches an action, send it to CopilotKit
121
+ const handleAction = useCallback(
122
+ async (message: A2UIClientEventMessage) => {
123
+ if (!agent) return;
124
+
125
+ try {
126
+ console.info("[A2UI] Action dispatched", message.userAction);
127
+
128
+ copilotkit.setProperties({
129
+ ...(copilotkit.properties ?? {}),
130
+ a2uiAction: message,
131
+ });
132
+
133
+ await copilotkit.runAgent({ agent });
134
+ } finally {
135
+ if (copilotkit.properties) {
136
+ const { a2uiAction, ...rest } = copilotkit.properties;
137
+ copilotkit.setProperties(rest);
138
+ }
139
+ }
140
+ },
141
+ [agent, copilotkit],
142
+ );
143
+
144
+ return (
145
+ <div className="cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4">
146
+ <A2UIProvider onAction={handleAction} theme={theme}>
147
+ <SurfaceMessageProcessor
148
+ surfaceId={surfaceId}
149
+ operations={operations}
150
+ />
151
+ <A2UIRenderer surfaceId={surfaceId} className="cpk:flex cpk:flex-1" />
152
+ </A2UIProvider>
153
+ </div>
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Processes A2UI operations into the provider's message processor.
159
+ * Must be a child of A2UIProvider to access the actions context.
160
+ */
161
+ function SurfaceMessageProcessor({
162
+ surfaceId,
163
+ operations,
164
+ }: {
165
+ surfaceId: string;
166
+ operations: any[];
167
+ }) {
168
+ const { processMessages } = useA2UIActions();
169
+ const lastProcessedRef = useRef<string>("");
170
+
171
+ useEffect(() => {
172
+ const key = `${surfaceId}-${JSON.stringify(operations)}`;
173
+ if (key === lastProcessedRef.current) return;
174
+ lastProcessedRef.current = key;
175
+
176
+ processMessages(operations);
177
+ }, [processMessages, surfaceId, operations]);
178
+
179
+ return null;
180
+ }
181
+
182
+ function getOperationSurfaceId(operation: any): string | null {
183
+ if (!operation || typeof operation !== "object") {
184
+ return null;
185
+ }
186
+
187
+ if (typeof operation.surfaceId === "string") {
188
+ return operation.surfaceId;
189
+ }
190
+
191
+ return (
192
+ operation?.beginRendering?.surfaceId ??
193
+ operation?.surfaceUpdate?.surfaceId ??
194
+ operation?.dataModelUpdate?.surfaceId ??
195
+ operation?.deleteSurface?.surfaceId ??
196
+ null
197
+ );
198
+ }
199
+
200
+ function stringifyOperations(ops: any[]): string | null {
201
+ try {
202
+ return JSON.stringify(ops);
203
+ } catch (error) {
204
+ return null;
205
+ }
206
+ }
@@ -0,0 +1,50 @@
1
+ import * as React from "react";
2
+ import { createComponent } from "@lit-labs/react";
3
+ import type { CopilotKitCore } from "@copilotkit/core";
4
+
5
+ type CopilotKitInspectorBaseProps = {
6
+ core?: CopilotKitCore | null;
7
+ [key: string]: unknown;
8
+ };
9
+
10
+ type InspectorComponent = React.ComponentType<CopilotKitInspectorBaseProps>;
11
+
12
+ export interface CopilotKitInspectorProps extends CopilotKitInspectorBaseProps {}
13
+
14
+ export const CopilotKitInspector: React.FC<CopilotKitInspectorProps> = ({
15
+ core,
16
+ ...rest
17
+ }) => {
18
+ const [InspectorComponent, setInspectorComponent] =
19
+ React.useState<InspectorComponent | null>(null);
20
+
21
+ React.useEffect(() => {
22
+ let mounted = true;
23
+
24
+ // Load the web component only on the client to keep SSR output stable.
25
+ import("@copilotkit/web-inspector").then((mod) => {
26
+ mod.defineWebInspector?.();
27
+
28
+ const Component = createComponent({
29
+ tagName: mod.WEB_INSPECTOR_TAG,
30
+ elementClass: mod.WebInspectorElement,
31
+ react: React,
32
+ }) as InspectorComponent;
33
+
34
+ if (mounted) {
35
+ setInspectorComponent(() => Component);
36
+ }
37
+ });
38
+
39
+ return () => {
40
+ mounted = false;
41
+ };
42
+ }, []);
43
+
44
+ // During SSR (and until the client finishes loading), render nothing to keep markup consistent.
45
+ if (!InspectorComponent) return null;
46
+
47
+ return <InspectorComponent {...rest} core={core ?? null} />;
48
+ };
49
+
50
+ CopilotKitInspector.displayName = "CopilotKitInspector";