@copilotkit/react-core 1.54.1 → 1.55.0-next.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +117 -116
- package/dist/copilotkit-B3Mb1yVE.cjs +7975 -0
- package/dist/copilotkit-B3Mb1yVE.cjs.map +1 -0
- package/dist/copilotkit-DBzgOMby.d.cts +2182 -0
- package/dist/copilotkit-DBzgOMby.d.cts.map +1 -0
- package/dist/copilotkit-DNYSFuz5.mjs +7562 -0
- package/dist/copilotkit-DNYSFuz5.mjs.map +1 -0
- package/dist/copilotkit-Dy5w3qEV.d.mts +2182 -0
- package/dist/copilotkit-Dy5w3qEV.d.mts.map +1 -0
- package/dist/index.cjs +27 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -5
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1941 -35
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/index.cjs +77 -7
- package/dist/v2/index.css +1 -2
- package/dist/v2/index.d.cts +6 -4
- package/dist/v2/index.d.mts +6 -4
- package/dist/v2/index.mjs +7 -4
- package/dist/v2/index.umd.js +5725 -24
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +37 -9
- package/scripts/scope-preflight.mjs +101 -0
- package/src/components/CopilotListeners.tsx +2 -6
- package/src/components/copilot-provider/copilot-messages.tsx +1 -1
- package/src/components/copilot-provider/copilotkit-props.tsx +1 -1
- package/src/components/copilot-provider/copilotkit.tsx +4 -4
- package/src/context/copilot-messages-context.tsx +1 -1
- package/src/hooks/__tests__/use-coagent-config.test.ts +2 -2
- package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +2 -2
- package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +3 -7
- package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +1 -1
- package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +4 -4
- package/src/hooks/use-agent-nodename.ts +1 -1
- package/src/hooks/use-coagent-state-render-bridge.tsx +1 -4
- package/src/hooks/use-coagent.ts +1 -1
- package/src/hooks/use-configure-chat-suggestions.tsx +2 -2
- package/src/hooks/use-copilot-chat-suggestions.tsx +2 -2
- package/src/hooks/use-copilot-chat_internal.ts +2 -2
- package/src/hooks/use-copilot-readable.ts +1 -1
- package/src/hooks/use-frontend-tool.ts +2 -2
- package/src/hooks/use-human-in-the-loop.ts +2 -2
- package/src/hooks/use-langgraph-interrupt.ts +2 -5
- package/src/hooks/use-lazy-tool-renderer.tsx +1 -1
- package/src/hooks/use-render-tool-call.ts +1 -1
- package/src/lib/copilot-task.ts +1 -1
- package/src/setupTests.ts +18 -14
- package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +176 -0
- package/src/v2/__tests__/globalSetup.ts +14 -0
- package/src/v2/__tests__/setup.ts +93 -0
- package/src/v2/__tests__/utils/test-helpers.tsx +470 -0
- package/src/v2/a2ui/A2UIMessageRenderer.tsx +206 -0
- package/src/v2/components/CopilotKitInspector.tsx +50 -0
- package/src/v2/components/MCPAppsActivityRenderer.tsx +785 -0
- package/src/v2/components/WildcardToolCallRender.tsx +86 -0
- package/src/v2/components/__tests__/license-warning-banner.test.tsx +46 -0
- package/src/v2/components/chat/CopilotChat.tsx +431 -0
- package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +375 -0
- package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +350 -0
- package/src/v2/components/chat/CopilotChatInput.tsx +1302 -0
- package/src/v2/components/chat/CopilotChatMessageView.tsx +556 -0
- package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +252 -0
- package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +59 -0
- package/src/v2/components/chat/CopilotChatSuggestionView.tsx +133 -0
- package/src/v2/components/chat/CopilotChatToggleButton.tsx +171 -0
- package/src/v2/components/chat/CopilotChatToolCallsView.tsx +40 -0
- package/src/v2/components/chat/CopilotChatUserMessage.tsx +388 -0
- package/src/v2/components/chat/CopilotChatView.tsx +598 -0
- package/src/v2/components/chat/CopilotModalHeader.tsx +129 -0
- package/src/v2/components/chat/CopilotPopup.tsx +81 -0
- package/src/v2/components/chat/CopilotPopupView.tsx +317 -0
- package/src/v2/components/chat/CopilotSidebar.tsx +76 -0
- package/src/v2/components/chat/CopilotSidebarView.tsx +255 -0
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +1113 -0
- package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +73 -0
- package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +432 -0
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +150 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +624 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +702 -0
- package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +107 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +929 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +986 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +1004 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +169 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +530 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +782 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2413 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +621 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +853 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +1050 -0
- package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +484 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +612 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +502 -0
- package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1011 -0
- package/src/v2/components/chat/__tests__/setup.ts +1 -0
- package/src/v2/components/chat/index.ts +79 -0
- package/src/v2/components/index.ts +7 -0
- package/src/v2/components/license-warning-banner.tsx +198 -0
- package/src/v2/components/ui/button.tsx +123 -0
- package/src/v2/components/ui/dropdown-menu.tsx +258 -0
- package/src/v2/components/ui/tooltip.tsx +60 -0
- package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +152 -0
- package/src/v2/hooks/__tests__/standard-schema.test.tsx +282 -0
- package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +132 -0
- package/src/v2/hooks/__tests__/use-agent-context.test.tsx +401 -0
- package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +44 -0
- package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +205 -0
- package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +148 -0
- package/src/v2/hooks/__tests__/use-component.test.tsx +123 -0
- package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +696 -0
- package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +153 -0
- package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +167 -0
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +2129 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +1261 -0
- package/src/v2/hooks/__tests__/use-interrupt.test.tsx +397 -0
- package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +56 -0
- package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +192 -0
- package/src/v2/hooks/__tests__/use-render-tool.test.tsx +259 -0
- package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +524 -0
- package/src/v2/hooks/__tests__/use-threads.test.tsx +433 -0
- package/src/v2/hooks/__tests__/zod-regression.test.tsx +311 -0
- package/src/v2/hooks/index.ts +18 -0
- package/src/v2/hooks/use-agent-context.tsx +45 -0
- package/src/v2/hooks/use-agent.tsx +155 -0
- package/src/v2/hooks/use-component.tsx +89 -0
- package/src/v2/hooks/use-configure-suggestions.tsx +187 -0
- package/src/v2/hooks/use-default-render-tool.tsx +254 -0
- package/src/v2/hooks/use-frontend-tool.tsx +43 -0
- package/src/v2/hooks/use-human-in-the-loop.tsx +81 -0
- package/src/v2/hooks/use-interrupt.tsx +305 -0
- package/src/v2/hooks/use-keyboard-height.tsx +67 -0
- package/src/v2/hooks/use-render-activity-message.tsx +73 -0
- package/src/v2/hooks/use-render-custom-messages.tsx +93 -0
- package/src/v2/hooks/use-render-tool-call.tsx +175 -0
- package/src/v2/hooks/use-render-tool.tsx +181 -0
- package/src/v2/hooks/use-suggestions.tsx +91 -0
- package/src/v2/hooks/use-threads.tsx +256 -0
- package/src/v2/hooks/useKatexStyles.ts +27 -0
- package/src/v2/index.css +1 -1
- package/src/v2/index.ts +18 -2
- package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +495 -0
- package/src/v2/lib/__tests__/renderSlot.test.tsx +588 -0
- package/src/v2/lib/react-core.ts +156 -0
- package/src/v2/lib/slots.tsx +143 -0
- package/src/v2/lib/transcription-client.ts +184 -0
- package/src/v2/lib/utils.ts +8 -0
- package/src/v2/providers/CopilotChatConfigurationProvider.tsx +162 -0
- package/src/v2/providers/CopilotKitProvider.tsx +600 -0
- package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +546 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +101 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +69 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +881 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +740 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +642 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +294 -0
- package/src/v2/providers/index.ts +14 -0
- package/src/v2/styles/globals.css +230 -0
- package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +525 -0
- package/src/v2/types/defineToolCallRenderer.ts +65 -0
- package/src/v2/types/frontend-tool.ts +8 -0
- package/src/v2/types/human-in-the-loop.ts +33 -0
- package/src/v2/types/index.ts +7 -0
- package/src/v2/types/interrupt.ts +15 -0
- package/src/v2/types/react-activity-message-renderer.ts +27 -0
- package/src/v2/types/react-custom-message-renderer.ts +17 -0
- package/src/v2/types/react-tool-call-renderer.ts +32 -0
- package/tsdown.config.ts +34 -10
- package/vitest.config.mjs +4 -3
- package/LICENSE +0 -21
- package/dist/copilotkit-BRPQ2sqS.d.cts +0 -670
- package/dist/copilotkit-BRPQ2sqS.d.cts.map +0 -1
- package/dist/copilotkit-C94ayZbs.cjs +0 -2161
- package/dist/copilotkit-C94ayZbs.cjs.map +0 -1
- package/dist/copilotkit-CwZMFmSK.d.mts +0 -670
- package/dist/copilotkit-CwZMFmSK.d.mts.map +0 -1
- package/dist/copilotkit-Yh_Ld_FX.mjs +0 -2031
- package/dist/copilotkit-Yh_Ld_FX.mjs.map +0 -1
- 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";
|