@copilotkit/react-core 1.55.3-canary.1776243725 → 1.55.3-canary.1776979102
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/dist/{copilotkit-opur-20s.d.mts → copilotkit-3mXoM0Hd.d.mts} +9 -29
- package/dist/copilotkit-3mXoM0Hd.d.mts.map +1 -0
- package/dist/{copilotkit-EfopO2gn.d.cts → copilotkit-BDDjvB-p.d.cts} +9 -29
- package/dist/copilotkit-BDDjvB-p.d.cts.map +1 -0
- package/dist/{copilotkit-BoOnQHlE.cjs → copilotkit-BkcqmpWt.cjs} +162 -280
- package/dist/copilotkit-BkcqmpWt.cjs.map +1 -0
- package/dist/{copilotkit-Bm4ox8G0.mjs → copilotkit-C7n8Umv9.mjs} +164 -276
- package/dist/copilotkit-C7n8Umv9.mjs.map +1 -0
- package/dist/index.cjs +4 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +4 -9
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +143 -230
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/index.cjs +1 -2
- package/dist/v2/index.d.cts +2 -2
- package/dist/v2/index.d.mts +2 -2
- package/dist/v2/index.mjs +2 -2
- package/dist/v2/index.umd.js +165 -279
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +6 -6
- package/src/components/copilot-provider/copilot-messages.tsx +24 -39
- package/src/components/copilot-provider/copilotkit-props.tsx +5 -9
- package/src/components/copilot-provider/copilotkit.tsx +1 -4
- package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +16 -27
- package/src/hooks/use-copilot-chat_internal.ts +4 -15
- package/src/v2/__tests__/utils/test-helpers.tsx +7 -40
- package/src/v2/components/chat/CopilotChat.tsx +1 -1
- package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +15 -18
- package/src/v2/components/chat/CopilotChatMessageView.tsx +2 -7
- package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +4 -17
- package/src/v2/components/chat/CopilotChatUserMessage.tsx +10 -13
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +5 -131
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +0 -60
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +1 -1
- package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +2 -5
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2 -5
- package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1 -55
- package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +0 -8
- package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +10 -10
- package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +2 -13
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +4 -23
- package/src/v2/hooks/index.ts +0 -1
- package/src/v2/hooks/use-agent.tsx +10 -157
- package/src/v2/hooks/use-render-activity-message.tsx +3 -9
- package/src/v2/hooks/use-render-custom-messages.tsx +1 -6
- package/src/v2/providers/CopilotKitProvider.tsx +2 -6
- package/dist/copilotkit-Bm4ox8G0.mjs.map +0 -1
- package/dist/copilotkit-BoOnQHlE.cjs.map +0 -1
- package/dist/copilotkit-EfopO2gn.d.cts.map +0 -1
- package/dist/copilotkit-opur-20s.d.mts.map +0 -1
- package/src/components/copilot-provider/__tests__/error-visibility-prod.test.tsx +0 -70
- package/src/v2/components/chat/__tests__/CopilotChatCopyButton.clipboard.test.tsx +0 -241
- package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +0 -327
- package/src/v2/hooks/__tests__/use-capabilities.test.tsx +0 -76
- package/src/v2/hooks/use-capabilities.tsx +0 -25
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
|
-
import { screen, fireEvent, waitFor
|
|
2
|
+
import { screen, fireEvent, waitFor } from "@testing-library/react";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { defineToolCallRenderer, ReactToolCallRenderer } from "../../../types";
|
|
5
5
|
import {
|
|
@@ -1060,128 +1060,6 @@ describe("CopilotChat E2E - Chat Basics and Streaming Patterns", () => {
|
|
|
1060
1060
|
agent.complete();
|
|
1061
1061
|
});
|
|
1062
1062
|
|
|
1063
|
-
it("should not auto-collapse when user manually toggled during streaming", async () => {
|
|
1064
|
-
const agent = new MockStepwiseAgent();
|
|
1065
|
-
renderWithCopilotKit({ agent });
|
|
1066
|
-
|
|
1067
|
-
const input = await screen.findByRole("textbox");
|
|
1068
|
-
fireEvent.change(input, { target: { value: "User toggle test" } });
|
|
1069
|
-
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
|
1070
|
-
|
|
1071
|
-
await waitFor(() => {
|
|
1072
|
-
expect(screen.getByText("User toggle test")).toBeDefined();
|
|
1073
|
-
});
|
|
1074
|
-
|
|
1075
|
-
const reasoningId = testId("reasoning");
|
|
1076
|
-
const textId = testId("text");
|
|
1077
|
-
|
|
1078
|
-
// Start streaming reasoning — panel should auto-open
|
|
1079
|
-
agent.emit(runStartedEvent());
|
|
1080
|
-
agent.emit(reasoningStartEvent(reasoningId));
|
|
1081
|
-
agent.emit(reasoningMessageStartEvent(reasoningId));
|
|
1082
|
-
agent.emit(
|
|
1083
|
-
reasoningMessageContentEvent(reasoningId, "Deep analysis in progress"),
|
|
1084
|
-
);
|
|
1085
|
-
|
|
1086
|
-
await waitFor(() => {
|
|
1087
|
-
expect(screen.getByText("Thinking…")).toBeDefined();
|
|
1088
|
-
});
|
|
1089
|
-
|
|
1090
|
-
// Panel should be open (aria-expanded="true") while streaming
|
|
1091
|
-
await waitFor(() => {
|
|
1092
|
-
const header = screen.getByText("Thinking…");
|
|
1093
|
-
const button = header.closest("button");
|
|
1094
|
-
expect(button?.getAttribute("aria-expanded")).toBe("true");
|
|
1095
|
-
});
|
|
1096
|
-
|
|
1097
|
-
// User manually collapses during streaming — this sets userToggledRef
|
|
1098
|
-
const header = screen.getByText("Thinking…");
|
|
1099
|
-
const button = header.closest("button");
|
|
1100
|
-
act(() => {
|
|
1101
|
-
if (button) {
|
|
1102
|
-
fireEvent.click(button);
|
|
1103
|
-
}
|
|
1104
|
-
});
|
|
1105
|
-
|
|
1106
|
-
// Should now be collapsed by user action
|
|
1107
|
-
await waitFor(() => {
|
|
1108
|
-
const btn = screen.getByText("Thinking…").closest("button");
|
|
1109
|
-
expect(btn?.getAttribute("aria-expanded")).toBe("false");
|
|
1110
|
-
});
|
|
1111
|
-
|
|
1112
|
-
// Now streaming ends — because userToggledRef is true, the panel
|
|
1113
|
-
// should stay in whatever state the user set (collapsed).
|
|
1114
|
-
agent.emit(reasoningMessageEndEvent(reasoningId));
|
|
1115
|
-
agent.emit(reasoningEndEvent(reasoningId));
|
|
1116
|
-
agent.emit(textChunkEvent(textId, "Done."));
|
|
1117
|
-
agent.emit(runFinishedEvent());
|
|
1118
|
-
agent.complete();
|
|
1119
|
-
|
|
1120
|
-
// Panel should remain collapsed (not flash open then closed)
|
|
1121
|
-
await waitFor(() => {
|
|
1122
|
-
const btn = screen.getByText(/Thought for/).closest("button");
|
|
1123
|
-
expect(btn?.getAttribute("aria-expanded")).toBe("false");
|
|
1124
|
-
});
|
|
1125
|
-
});
|
|
1126
|
-
|
|
1127
|
-
it("should keep panel open when user re-expands during streaming", async () => {
|
|
1128
|
-
const agent = new MockStepwiseAgent();
|
|
1129
|
-
renderWithCopilotKit({ agent });
|
|
1130
|
-
|
|
1131
|
-
const input = await screen.findByRole("textbox");
|
|
1132
|
-
fireEvent.change(input, {
|
|
1133
|
-
target: { value: "Re-expand toggle test" },
|
|
1134
|
-
});
|
|
1135
|
-
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
|
1136
|
-
|
|
1137
|
-
await waitFor(() => {
|
|
1138
|
-
expect(screen.getByText("Re-expand toggle test")).toBeDefined();
|
|
1139
|
-
});
|
|
1140
|
-
|
|
1141
|
-
const reasoningId = testId("reasoning");
|
|
1142
|
-
const textId = testId("text");
|
|
1143
|
-
|
|
1144
|
-
// Start streaming reasoning — panel auto-opens
|
|
1145
|
-
agent.emit(runStartedEvent());
|
|
1146
|
-
agent.emit(reasoningStartEvent(reasoningId));
|
|
1147
|
-
agent.emit(reasoningMessageStartEvent(reasoningId));
|
|
1148
|
-
agent.emit(reasoningMessageContentEvent(reasoningId, "Thinking hard"));
|
|
1149
|
-
|
|
1150
|
-
await waitFor(() => {
|
|
1151
|
-
const btn = screen.getByText("Thinking…").closest("button");
|
|
1152
|
-
expect(btn?.getAttribute("aria-expanded")).toBe("true");
|
|
1153
|
-
});
|
|
1154
|
-
|
|
1155
|
-
// User collapses, then re-expands (both set userToggledRef = true)
|
|
1156
|
-
const headerEl = screen.getByText("Thinking…");
|
|
1157
|
-
const btn = headerEl.closest("button");
|
|
1158
|
-
act(() => {
|
|
1159
|
-
if (btn) {
|
|
1160
|
-
fireEvent.click(btn); // collapse
|
|
1161
|
-
fireEvent.click(btn); // re-expand
|
|
1162
|
-
}
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
await waitFor(() => {
|
|
1166
|
-
const b = screen.getByText("Thinking…").closest("button");
|
|
1167
|
-
expect(b?.getAttribute("aria-expanded")).toBe("true");
|
|
1168
|
-
});
|
|
1169
|
-
|
|
1170
|
-
// Streaming ends — because userToggledRef is true, panel should
|
|
1171
|
-
// stay in the user's chosen state (open).
|
|
1172
|
-
agent.emit(reasoningMessageEndEvent(reasoningId));
|
|
1173
|
-
agent.emit(reasoningEndEvent(reasoningId));
|
|
1174
|
-
agent.emit(textChunkEvent(textId, "All done."));
|
|
1175
|
-
agent.emit(runFinishedEvent());
|
|
1176
|
-
agent.complete();
|
|
1177
|
-
|
|
1178
|
-
// Panel should remain open (not auto-collapse)
|
|
1179
|
-
await waitFor(() => {
|
|
1180
|
-
const b = screen.getByText(/Thought for/).closest("button");
|
|
1181
|
-
expect(b?.getAttribute("aria-expanded")).toBe("true");
|
|
1182
|
-
});
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
1063
|
it("should expand and collapse reasoning content on click", async () => {
|
|
1186
1064
|
const agent = new MockStepwiseAgent();
|
|
1187
1065
|
renderWithCopilotKit({ agent });
|
|
@@ -1216,16 +1094,12 @@ describe("CopilotChat E2E - Chat Basics and Streaming Patterns", () => {
|
|
|
1216
1094
|
expect(button?.getAttribute("aria-expanded")).toBe("false");
|
|
1217
1095
|
});
|
|
1218
1096
|
|
|
1219
|
-
// Click to expand
|
|
1220
|
-
// update synchronously instead of deferring it through the scheduler,
|
|
1221
|
-
// which can race with waitFor polling on slow CI runners.
|
|
1097
|
+
// Click to expand
|
|
1222
1098
|
const header = screen.getByText(/Thought for/);
|
|
1223
1099
|
const button = header.closest("button");
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
}
|
|
1228
|
-
});
|
|
1100
|
+
if (button) {
|
|
1101
|
+
fireEvent.click(button);
|
|
1102
|
+
}
|
|
1229
1103
|
|
|
1230
1104
|
// Should now be expanded
|
|
1231
1105
|
await waitFor(() => {
|
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
} from "../../../__tests__/utils/test-helpers";
|
|
12
12
|
import { ReactActivityMessageRenderer } from "../../../types";
|
|
13
13
|
import { useCopilotKit } from "../../../providers";
|
|
14
|
-
import { AbstractAgent } from "@ag-ui/client";
|
|
15
|
-
import { getThreadClone } from "../../../hooks/use-agent";
|
|
16
14
|
|
|
17
15
|
describe("CopilotChat activity message rendering", () => {
|
|
18
16
|
it("renders custom components for activity snapshots", async () => {
|
|
@@ -150,62 +148,4 @@ describe("CopilotChat activity message rendering", () => {
|
|
|
150
148
|
expect(capturedCopilotkit).toBeDefined();
|
|
151
149
|
});
|
|
152
150
|
|
|
153
|
-
it("passes the per-thread clone (not the registry agent) to activity message renderers", async () => {
|
|
154
|
-
// Regression test for: A2UI button clicks firing runAgent on the registry
|
|
155
|
-
// agent instead of the per-thread clone that CopilotChat renders from.
|
|
156
|
-
// Caused by useRenderActivityMessage calling copilotkit.getAgent() directly
|
|
157
|
-
// instead of getThreadClone(registryAgent, threadId) ?? registryAgent.
|
|
158
|
-
const agent = new MockStepwiseAgent();
|
|
159
|
-
const agentId = "action-agent";
|
|
160
|
-
agent.agentId = agentId;
|
|
161
|
-
const threadId = "thread-for-action-test";
|
|
162
|
-
|
|
163
|
-
let capturedAgent: AbstractAgent | undefined;
|
|
164
|
-
|
|
165
|
-
const activityRenderer: ReactActivityMessageRenderer<{ label: string }> = {
|
|
166
|
-
activityType: "button-action",
|
|
167
|
-
content: z.object({ label: z.string() }),
|
|
168
|
-
render: ({ content, agent: renderedAgent }) => {
|
|
169
|
-
capturedAgent = renderedAgent;
|
|
170
|
-
return <button data-testid="action-button">{content.label}</button>;
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
renderWithCopilotKit({
|
|
175
|
-
agents: { [agentId]: agent },
|
|
176
|
-
agentId,
|
|
177
|
-
threadId,
|
|
178
|
-
renderActivityMessages: [activityRenderer],
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const input = await screen.findByRole("textbox");
|
|
182
|
-
fireEvent.change(input, { target: { value: "show me buttons" } });
|
|
183
|
-
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
|
184
|
-
|
|
185
|
-
await waitFor(() => {
|
|
186
|
-
expect(screen.getByText("show me buttons")).toBeDefined();
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
agent.emit(runStartedEvent());
|
|
190
|
-
agent.emit(
|
|
191
|
-
activitySnapshotEvent({
|
|
192
|
-
messageId: testId("activity-action"),
|
|
193
|
-
activityType: "button-action",
|
|
194
|
-
content: { label: "Click Me" },
|
|
195
|
-
}),
|
|
196
|
-
);
|
|
197
|
-
agent.emit(runFinishedEvent());
|
|
198
|
-
|
|
199
|
-
await waitFor(() => {
|
|
200
|
-
expect(screen.getByTestId("action-button")).toBeDefined();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// CopilotChat creates a per-thread clone via useAgent. The activity renderer
|
|
204
|
-
// must receive that clone so that handleAction → runAgent targets the same
|
|
205
|
-
// instance chat is rendering from.
|
|
206
|
-
const clone = getThreadClone(agent, threadId);
|
|
207
|
-
expect(clone).toBeDefined();
|
|
208
|
-
expect(capturedAgent).toBe(clone);
|
|
209
|
-
expect(capturedAgent).not.toBe(agent); // must NOT be the registry agent
|
|
210
|
-
});
|
|
211
151
|
});
|
|
@@ -250,11 +250,8 @@ class MockStepwiseAgent extends AbstractAgent {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
clone(): MockStepwiseAgent {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
(cloned as unknown as { subject: Subject<BaseEvent> }).subject =
|
|
256
|
-
this.subject;
|
|
257
|
-
return cloned;
|
|
253
|
+
// For tests, return same instance so we can keep controlling it.
|
|
254
|
+
return this;
|
|
258
255
|
}
|
|
259
256
|
|
|
260
257
|
async detachActiveRun(): Promise<void> {}
|
|
@@ -52,11 +52,8 @@ class MockStepwiseAgent extends AbstractAgent {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
clone(): MockStepwiseAgent {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
(cloned as unknown as { subject: Subject<BaseEvent> }).subject =
|
|
58
|
-
this.subject;
|
|
59
|
-
return cloned;
|
|
55
|
+
// For tests, return same instance so we can keep controlling it.
|
|
56
|
+
return this;
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
async detachActiveRun(): Promise<void> {}
|
|
@@ -79,61 +79,7 @@ class MockMCPProxyAgent extends AbstractAgent {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
clone(): MockMCPProxyAgent {
|
|
82
|
-
|
|
83
|
-
cloned.agentId = this.agentId;
|
|
84
|
-
type Internal = {
|
|
85
|
-
subject: Subject<BaseEvent>;
|
|
86
|
-
runAgentCalls: Array<{ input: Partial<RunAgentInput> }>;
|
|
87
|
-
runAgentResponses: Map<string, unknown>;
|
|
88
|
-
};
|
|
89
|
-
(cloned as unknown as Internal).subject = (
|
|
90
|
-
this as unknown as Internal
|
|
91
|
-
).subject;
|
|
92
|
-
(cloned as unknown as Internal).runAgentCalls = (
|
|
93
|
-
this as unknown as Internal
|
|
94
|
-
).runAgentCalls;
|
|
95
|
-
(cloned as unknown as Internal).runAgentResponses = (
|
|
96
|
-
this as unknown as Internal
|
|
97
|
-
).runAgentResponses;
|
|
98
|
-
// Share isRunning with the original so that emit(runFinishedEvent()) on the
|
|
99
|
-
// registry is visible to waitForAgentIdle() which now receives the clone.
|
|
100
|
-
//
|
|
101
|
-
// Also proxy runAgent dynamically so tests that monkey-patch agent.runAgent
|
|
102
|
-
// after renderWithCopilotKit (which creates the clone) still take effect.
|
|
103
|
-
// The clone is created and cached before tests can override runAgent, so a
|
|
104
|
-
// static copy would always see the pre-patch prototype method.
|
|
105
|
-
const registry = this;
|
|
106
|
-
Object.defineProperty(cloned, "isRunning", {
|
|
107
|
-
get() {
|
|
108
|
-
return registry.isRunning;
|
|
109
|
-
},
|
|
110
|
-
set(v: boolean) {
|
|
111
|
-
registry.isRunning = v;
|
|
112
|
-
},
|
|
113
|
-
configurable: true,
|
|
114
|
-
enumerable: true,
|
|
115
|
-
});
|
|
116
|
-
// Override runAgent so that:
|
|
117
|
-
// - Proxied MCP requests delegate to registry.runAgent (picking up any
|
|
118
|
-
// monkey-patches the test installed after renderWithCopilotKit).
|
|
119
|
-
// - User-message runs call the prototype method with `this = clone` so
|
|
120
|
-
// that clone.messages is updated (CopilotKit renders clone.messages).
|
|
121
|
-
const proto = MockMCPProxyAgent.prototype;
|
|
122
|
-
cloned.runAgent = async function (
|
|
123
|
-
input?: Partial<RunAgentInput>,
|
|
124
|
-
): Promise<RunAgentResult> {
|
|
125
|
-
const proxiedRequest = input?.forwardedProps?.__proxiedMCPRequest;
|
|
126
|
-
if (proxiedRequest) {
|
|
127
|
-
// Delegate to the registry so that monkey-patches applied by tests
|
|
128
|
-
// (e.g. "throws an error", "uses a controlled promise") take effect.
|
|
129
|
-
return registry.runAgent(input);
|
|
130
|
-
}
|
|
131
|
-
// For user-message runs: call the prototype method bound to the clone
|
|
132
|
-
// so AbstractAgent.runAgent processes events on clone and updates
|
|
133
|
-
// clone.messages (which is what CopilotKit reads to render messages).
|
|
134
|
-
return proto.runAgent.call(cloned, input);
|
|
135
|
-
};
|
|
136
|
-
return cloned;
|
|
82
|
+
return this;
|
|
137
83
|
}
|
|
138
84
|
|
|
139
85
|
async detachActiveRun(): Promise<void> {}
|
|
@@ -42,16 +42,8 @@ describe("useAgentContext timing - follow-up run sees updated context", () => {
|
|
|
42
42
|
* with no new messages — which is fine; we only need to capture context.
|
|
43
43
|
*/
|
|
44
44
|
class ContextCapturingAgent extends MockStepwiseAgent {
|
|
45
|
-
// Shared so the clone and original both see the captured contexts
|
|
46
45
|
public contextPerRun: Context[][] = [];
|
|
47
46
|
|
|
48
|
-
clone(): this {
|
|
49
|
-
const cloned = super.clone();
|
|
50
|
-
(cloned as unknown as ContextCapturingAgent).contextPerRun =
|
|
51
|
-
this.contextPerRun;
|
|
52
|
-
return cloned;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
47
|
async runAgent(
|
|
56
48
|
parameters?: RunAgentParameters,
|
|
57
49
|
subscriber?: AgentSubscriber,
|
|
@@ -313,7 +313,7 @@ describe("useAgent throttleMs", () => {
|
|
|
313
313
|
expect(screen.getByTestId("count").textContent).toBe("3");
|
|
314
314
|
});
|
|
315
315
|
|
|
316
|
-
it("with throttleMs, onStateChanged still fires immediately",
|
|
316
|
+
it("with throttleMs, onStateChanged still fires immediately", () => {
|
|
317
317
|
const TestComponent = createTestComponent({
|
|
318
318
|
updates: [
|
|
319
319
|
UseAgentUpdate.OnMessagesChanged,
|
|
@@ -330,8 +330,8 @@ describe("useAgent throttleMs", () => {
|
|
|
330
330
|
notifyMessagesChanged(mockAgent);
|
|
331
331
|
});
|
|
332
332
|
|
|
333
|
-
// Fire onStateChanged 10ms later —
|
|
334
|
-
|
|
333
|
+
// Fire onStateChanged 10ms later — should render immediately, not throttled
|
|
334
|
+
act(() => {
|
|
335
335
|
vi.advanceTimersByTime(10);
|
|
336
336
|
mockAgent.state = { count: 42 };
|
|
337
337
|
notifyStateChanged(mockAgent);
|
|
@@ -372,7 +372,7 @@ describe("useAgent throttleMs", () => {
|
|
|
372
372
|
expect(renderCount.current).toBe(countBeforeUnmount);
|
|
373
373
|
});
|
|
374
374
|
|
|
375
|
-
it("with throttleMs and updates excluding OnMessagesChanged, throttle is a no-op",
|
|
375
|
+
it("with throttleMs and updates excluding OnMessagesChanged, throttle is a no-op", () => {
|
|
376
376
|
const TestComponent = createTestComponent({
|
|
377
377
|
updates: [UseAgentUpdate.OnStateChanged],
|
|
378
378
|
throttleMs: 100,
|
|
@@ -380,8 +380,8 @@ describe("useAgent throttleMs", () => {
|
|
|
380
380
|
|
|
381
381
|
render(<TestComponent />);
|
|
382
382
|
|
|
383
|
-
// Only onStateChanged is subscribed —
|
|
384
|
-
|
|
383
|
+
// Only onStateChanged is subscribed — should fire immediately
|
|
384
|
+
act(() => {
|
|
385
385
|
mockAgent.state = { value: "test" };
|
|
386
386
|
notifyStateChanged(mockAgent);
|
|
387
387
|
});
|
|
@@ -649,7 +649,7 @@ describe("useAgent throttleMs", () => {
|
|
|
649
649
|
expect(renderCount.current).toBe(rendersAfterMount + 1);
|
|
650
650
|
});
|
|
651
651
|
|
|
652
|
-
it("with throttleMs, onRunInitialized still fires immediately during throttle window",
|
|
652
|
+
it("with throttleMs, onRunInitialized still fires immediately during throttle window", () => {
|
|
653
653
|
const renderCount = { current: 0 };
|
|
654
654
|
const TestComponent = createTestComponent({
|
|
655
655
|
updates: [
|
|
@@ -670,13 +670,13 @@ describe("useAgent throttleMs", () => {
|
|
|
670
670
|
});
|
|
671
671
|
expect(renderCount.current).toBe(rendersAfterMount + 1);
|
|
672
672
|
|
|
673
|
-
// Fire onRunInitialized 10ms later —
|
|
674
|
-
|
|
673
|
+
// Fire onRunInitialized 10ms later — should render immediately
|
|
674
|
+
act(() => {
|
|
675
675
|
vi.advanceTimersByTime(10);
|
|
676
676
|
notifyRunInitialized(mockAgent);
|
|
677
677
|
});
|
|
678
678
|
|
|
679
|
-
// Run status notification
|
|
679
|
+
// Run status notification is NOT throttled — renders immediately
|
|
680
680
|
expect(renderCount.current).toBe(rendersAfterMount + 2);
|
|
681
681
|
});
|
|
682
682
|
|
|
@@ -19,21 +19,10 @@ import { CopilotChat } from "../../components/chat/CopilotChat";
|
|
|
19
19
|
* Mock agent that captures RunAgentInput to verify state is passed correctly
|
|
20
20
|
*/
|
|
21
21
|
class StateCapturingMockAgent extends MockStepwiseAgent {
|
|
22
|
-
|
|
23
|
-
private _capture: { lastRunInput?: RunAgentInput } = {};
|
|
24
|
-
|
|
25
|
-
get lastRunInput(): RunAgentInput | undefined {
|
|
26
|
-
return this._capture.lastRunInput;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
clone(): this {
|
|
30
|
-
const cloned = super.clone();
|
|
31
|
-
(cloned as unknown as StateCapturingMockAgent)._capture = this._capture;
|
|
32
|
-
return cloned;
|
|
33
|
-
}
|
|
22
|
+
public lastRunInput?: RunAgentInput;
|
|
34
23
|
|
|
35
24
|
run(input: RunAgentInput): Observable<BaseEvent> {
|
|
36
|
-
this.
|
|
25
|
+
this.lastRunInput = input;
|
|
37
26
|
return super.run(input);
|
|
38
27
|
}
|
|
39
28
|
}
|
|
@@ -502,24 +502,13 @@ describe("useFrontendTool E2E - Dynamic Registration", () => {
|
|
|
502
502
|
describe("Agent input plumbing", () => {
|
|
503
503
|
it("forwards registered frontend tools to runAgent input", async () => {
|
|
504
504
|
class InstrumentedMockAgent extends MockStepwiseAgent {
|
|
505
|
-
|
|
506
|
-
private _capture: { lastRunParameters?: RunAgentParameters } = {};
|
|
507
|
-
|
|
508
|
-
get lastRunParameters(): RunAgentParameters | undefined {
|
|
509
|
-
return this._capture.lastRunParameters;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
clone(): this {
|
|
513
|
-
const cloned = super.clone();
|
|
514
|
-
(cloned as unknown as InstrumentedMockAgent)._capture = this._capture;
|
|
515
|
-
return cloned;
|
|
516
|
-
}
|
|
505
|
+
public lastRunParameters?: RunAgentParameters;
|
|
517
506
|
|
|
518
507
|
async runAgent(
|
|
519
508
|
parameters?: RunAgentParameters,
|
|
520
509
|
subscriber?: AgentSubscriber,
|
|
521
510
|
) {
|
|
522
|
-
this.
|
|
511
|
+
this.lastRunParameters = parameters;
|
|
523
512
|
return super.runAgent(parameters, subscriber);
|
|
524
513
|
}
|
|
525
514
|
}
|
|
@@ -579,16 +568,8 @@ describe("useFrontendTool E2E - Dynamic Registration", () => {
|
|
|
579
568
|
class OneShotToolCallAgent extends AbstractAgent {
|
|
580
569
|
private runCount = 0;
|
|
581
570
|
clone(): OneShotToolCallAgent {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
// Share runCount via reference so the second run emits different args
|
|
585
|
-
Object.defineProperty(cloned, "runCount", {
|
|
586
|
-
get: () => this.runCount,
|
|
587
|
-
set: (v: number) => {
|
|
588
|
-
this.runCount = v;
|
|
589
|
-
},
|
|
590
|
-
});
|
|
591
|
-
return cloned;
|
|
571
|
+
// Keep state across runs so the second run emits different args
|
|
572
|
+
return this;
|
|
592
573
|
}
|
|
593
574
|
async detachActiveRun(): Promise<void> {}
|
|
594
575
|
run(_input: RunAgentInput): Observable<BaseEvent> {
|
package/src/v2/hooks/index.ts
CHANGED
|
@@ -8,7 +8,6 @@ export { useRenderTool } from "./use-render-tool";
|
|
|
8
8
|
export { useDefaultRenderTool } from "./use-default-render-tool";
|
|
9
9
|
export { useHumanInTheLoop } from "./use-human-in-the-loop";
|
|
10
10
|
export { useAgent, UseAgentUpdate } from "./use-agent";
|
|
11
|
-
export { useCapabilities } from "./use-capabilities";
|
|
12
11
|
export { useAgentContext } from "./use-agent-context";
|
|
13
12
|
export type { AgentContextInput, JsonSerializable } from "./use-agent-context";
|
|
14
13
|
export { useSuggestions } from "./use-suggestions";
|