@copilotkit/vue 1.67.1 → 1.68.0
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/PARITY.md +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{use-render-activity-message-CaArNmtw.js → use-render-activity-message-DljRBrXN.js} +3401 -3370
- package/dist/use-render-activity-message-DljRBrXN.js.map +1 -0
- package/dist/use-render-activity-message-bkrPa-92.cjs +85 -0
- package/dist/use-render-activity-message-bkrPa-92.cjs.map +1 -0
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
- package/dist/v2/hooks/use-agent.d.ts +66 -5
- package/dist/v2/hooks/use-agent.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/package.json +5 -5
- package/src/v2/components/chat/CopilotChat.vue +14 -1
- package/src/v2/components/chat/CopilotChatMessageView.vue +5 -7
- package/src/v2/components/chat/__tests__/CopilotChat.clearOnFresh.test.ts +14 -25
- package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.ts +1 -2
- package/src/v2/components/chat/__tests__/CopilotChat.test.ts +2 -3
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.ts +6 -6
- package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.ts +14 -0
- package/src/v2/hooks/__tests__/use-agent-thread-pinning.test.ts +179 -0
- package/src/v2/hooks/use-agent.ts +205 -78
- package/dist/use-render-activity-message-CNbp9Fob.cjs +0 -85
- package/dist/use-render-activity-message-CNbp9Fob.cjs.map +0 -1
- package/dist/use-render-activity-message-CaArNmtw.js.map +0 -1
- package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.ts +0 -433
|
@@ -1,433 +0,0 @@
|
|
|
1
|
-
import { defineComponent, nextTick, ref, toRaw, watchEffect } from "vue";
|
|
2
|
-
import { render, cleanup } from "@testing-library/vue";
|
|
3
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
4
|
-
import { AbstractAgent, type BaseEvent } from "@ag-ui/client";
|
|
5
|
-
import { CopilotKitCoreRuntimeConnectionStatus } from "@copilotkit/core";
|
|
6
|
-
import { Observable } from "rxjs";
|
|
7
|
-
import { useCopilotKit } from "../../providers/useCopilotKit";
|
|
8
|
-
import { useAgent } from "../use-agent";
|
|
9
|
-
|
|
10
|
-
vi.mock("../../providers/useCopilotKit", () => ({
|
|
11
|
-
useCopilotKit: vi.fn(),
|
|
12
|
-
}));
|
|
13
|
-
|
|
14
|
-
const mockUseCopilotKit = useCopilotKit as ReturnType<typeof vi.fn>;
|
|
15
|
-
|
|
16
|
-
class CloneableAgent extends AbstractAgent {
|
|
17
|
-
clone(): CloneableAgent {
|
|
18
|
-
const cloned = new CloneableAgent();
|
|
19
|
-
cloned.agentId = this.agentId;
|
|
20
|
-
cloned.setMessages([...this.messages]);
|
|
21
|
-
return cloned;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
run(): Observable<BaseEvent> {
|
|
25
|
-
return new Observable();
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe("useAgent thread isolation", () => {
|
|
30
|
-
let mockCopilotkit: {
|
|
31
|
-
getAgent: ReturnType<typeof vi.fn>;
|
|
32
|
-
runtimeUrl: string | undefined;
|
|
33
|
-
runtimeConnectionStatus: CopilotKitCoreRuntimeConnectionStatus;
|
|
34
|
-
runtimeTransport: string;
|
|
35
|
-
headers: Record<string, string>;
|
|
36
|
-
agents: Record<string, AbstractAgent>;
|
|
37
|
-
// Added after the hook moved to consume the shared core API. Mocks only
|
|
38
|
-
// need a no-op subscription object here; notification behavior is covered
|
|
39
|
-
// in `use-agent-throttle.test.ts`.
|
|
40
|
-
subscribeToAgentWithOptions: ReturnType<typeof vi.fn>;
|
|
41
|
-
};
|
|
42
|
-
let copilotkitRef: ReturnType<typeof ref<typeof mockCopilotkit>>;
|
|
43
|
-
let registeredAgent: CloneableAgent;
|
|
44
|
-
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
registeredAgent = new CloneableAgent();
|
|
47
|
-
registeredAgent.agentId = "my-agent";
|
|
48
|
-
mockCopilotkit = {
|
|
49
|
-
getAgent: vi.fn((id: string) =>
|
|
50
|
-
id === "my-agent" ? registeredAgent : undefined,
|
|
51
|
-
),
|
|
52
|
-
runtimeUrl: "http://localhost:3000/api/copilotkit",
|
|
53
|
-
runtimeConnectionStatus: CopilotKitCoreRuntimeConnectionStatus.Connected,
|
|
54
|
-
runtimeTransport: "rest",
|
|
55
|
-
headers: {},
|
|
56
|
-
agents: { "my-agent": registeredAgent },
|
|
57
|
-
subscribeToAgentWithOptions: vi.fn(() => ({ unsubscribe: vi.fn() })),
|
|
58
|
-
};
|
|
59
|
-
copilotkitRef = ref(mockCopilotkit);
|
|
60
|
-
mockUseCopilotKit.mockReturnValue({
|
|
61
|
-
copilotkit: copilotkitRef,
|
|
62
|
-
executingToolCallIds: ref(new Set()),
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
afterEach(() => {
|
|
67
|
-
cleanup();
|
|
68
|
-
vi.restoreAllMocks();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("returns different agent instances for different threadIds with the same agentId", async () => {
|
|
72
|
-
const agents: Record<string, AbstractAgent> = {};
|
|
73
|
-
|
|
74
|
-
const TrackerA = defineComponent({
|
|
75
|
-
setup(props) {
|
|
76
|
-
const { agent } = useAgent({
|
|
77
|
-
agentId: "my-agent",
|
|
78
|
-
threadId: "thread-a",
|
|
79
|
-
});
|
|
80
|
-
watchEffect(() => {
|
|
81
|
-
agents.a = agent.value;
|
|
82
|
-
});
|
|
83
|
-
return {};
|
|
84
|
-
},
|
|
85
|
-
template: "<div />",
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const TrackerB = defineComponent({
|
|
89
|
-
setup(props) {
|
|
90
|
-
const { agent } = useAgent({
|
|
91
|
-
agentId: "my-agent",
|
|
92
|
-
threadId: "thread-b",
|
|
93
|
-
});
|
|
94
|
-
watchEffect(() => {
|
|
95
|
-
agents.b = agent.value;
|
|
96
|
-
});
|
|
97
|
-
return {};
|
|
98
|
-
},
|
|
99
|
-
template: "<div />",
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const Host = defineComponent({
|
|
103
|
-
components: { TrackerA, TrackerB },
|
|
104
|
-
template: "<div><TrackerA /><TrackerB /></div>",
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
render(Host);
|
|
108
|
-
await nextTick();
|
|
109
|
-
|
|
110
|
-
expect(agents.a).toBeDefined();
|
|
111
|
-
expect(agents.b).toBeDefined();
|
|
112
|
-
expect(agents.a).not.toBe(agents.b);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("returns the same cached instance for the same (agentId, threadId) across re-renders", async () => {
|
|
116
|
-
const instances: AbstractAgent[] = [];
|
|
117
|
-
|
|
118
|
-
const Tracker = defineComponent({
|
|
119
|
-
props: {
|
|
120
|
-
tick: { type: Number, required: true },
|
|
121
|
-
},
|
|
122
|
-
setup(props) {
|
|
123
|
-
const { agent } = useAgent({
|
|
124
|
-
agentId: "my-agent",
|
|
125
|
-
threadId: "thread-x",
|
|
126
|
-
});
|
|
127
|
-
watchEffect(() => {
|
|
128
|
-
const currentTick = props.tick;
|
|
129
|
-
void currentTick;
|
|
130
|
-
instances.push(agent.value);
|
|
131
|
-
});
|
|
132
|
-
return {};
|
|
133
|
-
},
|
|
134
|
-
template: "<div>{{ tick }}</div>",
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const { rerender } = render(Tracker, {
|
|
138
|
-
props: { tick: 0 },
|
|
139
|
-
});
|
|
140
|
-
await nextTick();
|
|
141
|
-
await rerender({ tick: 1 });
|
|
142
|
-
await nextTick();
|
|
143
|
-
|
|
144
|
-
expect(instances.length).toBe(2);
|
|
145
|
-
expect(toRaw(instances[0]!)).toBe(toRaw(instances[1]!));
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("returns the shared registry agent when no threadId is provided (backward compat)", async () => {
|
|
149
|
-
const captured = ref<AbstractAgent | undefined>();
|
|
150
|
-
const Tracker = defineComponent({
|
|
151
|
-
setup() {
|
|
152
|
-
const { agent } = useAgent({ agentId: "my-agent" });
|
|
153
|
-
watchEffect(() => {
|
|
154
|
-
captured.value = agent.value;
|
|
155
|
-
});
|
|
156
|
-
return {};
|
|
157
|
-
},
|
|
158
|
-
template: "<div />",
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
render(Tracker);
|
|
162
|
-
await nextTick();
|
|
163
|
-
expect(toRaw(captured.value!)).toBe(registeredAgent);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("isolates messages between thread-specific agents", async () => {
|
|
167
|
-
registeredAgent.addMessage({
|
|
168
|
-
id: "source-msg",
|
|
169
|
-
role: "user",
|
|
170
|
-
content: "pre-existing on source",
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const agents: Record<string, AbstractAgent> = {};
|
|
174
|
-
const TrackerA = defineComponent({
|
|
175
|
-
setup() {
|
|
176
|
-
const { agent } = useAgent({
|
|
177
|
-
agentId: "my-agent",
|
|
178
|
-
threadId: "thread-a",
|
|
179
|
-
});
|
|
180
|
-
watchEffect(() => {
|
|
181
|
-
agents.a = agent.value;
|
|
182
|
-
});
|
|
183
|
-
return {};
|
|
184
|
-
},
|
|
185
|
-
template: "<div />",
|
|
186
|
-
});
|
|
187
|
-
const TrackerB = defineComponent({
|
|
188
|
-
setup() {
|
|
189
|
-
const { agent } = useAgent({
|
|
190
|
-
agentId: "my-agent",
|
|
191
|
-
threadId: "thread-b",
|
|
192
|
-
});
|
|
193
|
-
watchEffect(() => {
|
|
194
|
-
agents.b = agent.value;
|
|
195
|
-
});
|
|
196
|
-
return {};
|
|
197
|
-
},
|
|
198
|
-
template: "<div />",
|
|
199
|
-
});
|
|
200
|
-
const Host = defineComponent({
|
|
201
|
-
components: { TrackerA, TrackerB },
|
|
202
|
-
template: "<div><TrackerA /><TrackerB /></div>",
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
render(Host);
|
|
206
|
-
await nextTick();
|
|
207
|
-
|
|
208
|
-
expect(agents.a.messages).toHaveLength(0);
|
|
209
|
-
expect(agents.b.messages).toHaveLength(0);
|
|
210
|
-
|
|
211
|
-
agents.a.addMessage({
|
|
212
|
-
id: "msg-1",
|
|
213
|
-
role: "user",
|
|
214
|
-
content: "hello from thread A",
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
expect(agents.a.messages).toHaveLength(1);
|
|
218
|
-
expect(agents.b.messages).toHaveLength(0);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it("sets threadId on cloned agents", async () => {
|
|
222
|
-
const agents: Record<string, AbstractAgent> = {};
|
|
223
|
-
const TrackerA = defineComponent({
|
|
224
|
-
setup() {
|
|
225
|
-
const { agent } = useAgent({
|
|
226
|
-
agentId: "my-agent",
|
|
227
|
-
threadId: "thread-a",
|
|
228
|
-
});
|
|
229
|
-
watchEffect(() => {
|
|
230
|
-
agents.a = agent.value;
|
|
231
|
-
});
|
|
232
|
-
return {};
|
|
233
|
-
},
|
|
234
|
-
template: "<div />",
|
|
235
|
-
});
|
|
236
|
-
const TrackerB = defineComponent({
|
|
237
|
-
setup() {
|
|
238
|
-
const { agent } = useAgent({
|
|
239
|
-
agentId: "my-agent",
|
|
240
|
-
threadId: "thread-b",
|
|
241
|
-
});
|
|
242
|
-
watchEffect(() => {
|
|
243
|
-
agents.b = agent.value;
|
|
244
|
-
});
|
|
245
|
-
return {};
|
|
246
|
-
},
|
|
247
|
-
template: "<div />",
|
|
248
|
-
});
|
|
249
|
-
const Host = defineComponent({
|
|
250
|
-
components: { TrackerA, TrackerB },
|
|
251
|
-
template: "<div><TrackerA /><TrackerB /></div>",
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
render(Host);
|
|
255
|
-
await nextTick();
|
|
256
|
-
|
|
257
|
-
expect(agents.a.threadId).toBe("thread-a");
|
|
258
|
-
expect(agents.b.threadId).toBe("thread-b");
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it("invalidates stale clone when the registry agent is replaced", async () => {
|
|
262
|
-
const capturedAgent = ref<AbstractAgent | undefined>();
|
|
263
|
-
|
|
264
|
-
const Tracker = defineComponent({
|
|
265
|
-
props: {
|
|
266
|
-
tid: { type: String, required: true },
|
|
267
|
-
tick: { type: Number, required: true },
|
|
268
|
-
},
|
|
269
|
-
setup(props) {
|
|
270
|
-
const { agent } = useAgent({
|
|
271
|
-
agentId: "my-agent",
|
|
272
|
-
threadId: () => props.tid,
|
|
273
|
-
});
|
|
274
|
-
watchEffect(() => {
|
|
275
|
-
capturedAgent.value = agent.value;
|
|
276
|
-
});
|
|
277
|
-
return {};
|
|
278
|
-
},
|
|
279
|
-
template: "<div>{{ tick }}</div>",
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
const { rerender } = render(Tracker, {
|
|
283
|
-
props: { tid: "thread-a", tick: 0 },
|
|
284
|
-
});
|
|
285
|
-
await nextTick();
|
|
286
|
-
const firstClone = capturedAgent.value;
|
|
287
|
-
expect(firstClone).not.toBe(registeredAgent);
|
|
288
|
-
|
|
289
|
-
const replacementAgent = new CloneableAgent();
|
|
290
|
-
replacementAgent.agentId = "my-agent";
|
|
291
|
-
|
|
292
|
-
copilotkitRef.value.agents = { "my-agent": replacementAgent };
|
|
293
|
-
copilotkitRef.value.getAgent = vi.fn((id: string) =>
|
|
294
|
-
id === "my-agent" ? replacementAgent : undefined,
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
await rerender({ tid: "thread-a", tick: 1 });
|
|
298
|
-
await nextTick();
|
|
299
|
-
|
|
300
|
-
const secondClone = capturedAgent.value;
|
|
301
|
-
expect(secondClone).not.toBe(firstClone);
|
|
302
|
-
expect(secondClone).not.toBe(replacementAgent);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it("switching threadId returns a fresh clone; switching back returns the cached one", async () => {
|
|
306
|
-
const capturedAgent = ref<AbstractAgent | undefined>();
|
|
307
|
-
|
|
308
|
-
const Tracker = defineComponent({
|
|
309
|
-
props: {
|
|
310
|
-
tid: { type: String, required: true },
|
|
311
|
-
},
|
|
312
|
-
setup(props) {
|
|
313
|
-
const { agent } = useAgent({
|
|
314
|
-
agentId: "my-agent",
|
|
315
|
-
threadId: () => props.tid,
|
|
316
|
-
});
|
|
317
|
-
watchEffect(() => {
|
|
318
|
-
capturedAgent.value = agent.value;
|
|
319
|
-
});
|
|
320
|
-
return {};
|
|
321
|
-
},
|
|
322
|
-
template: "<div />",
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
const { rerender } = render(Tracker, {
|
|
326
|
-
props: { tid: "thread-a" },
|
|
327
|
-
});
|
|
328
|
-
await nextTick();
|
|
329
|
-
const cloneA = capturedAgent.value;
|
|
330
|
-
|
|
331
|
-
await rerender({ tid: "thread-b" });
|
|
332
|
-
await nextTick();
|
|
333
|
-
const cloneB = capturedAgent.value;
|
|
334
|
-
expect(cloneB).not.toBe(cloneA);
|
|
335
|
-
|
|
336
|
-
await rerender({ tid: "thread-a" });
|
|
337
|
-
await nextTick();
|
|
338
|
-
expect(capturedAgent.value).toBe(cloneA);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
it("uses a fresh clone with correct threadId when provisional transitions to real agent", async () => {
|
|
342
|
-
copilotkitRef.value.runtimeConnectionStatus =
|
|
343
|
-
CopilotKitCoreRuntimeConnectionStatus.Disconnected;
|
|
344
|
-
copilotkitRef.value.getAgent = vi.fn(() => undefined);
|
|
345
|
-
copilotkitRef.value.agents = {};
|
|
346
|
-
|
|
347
|
-
const capturedAgent = ref<AbstractAgent | undefined>();
|
|
348
|
-
const Tracker = defineComponent({
|
|
349
|
-
props: {
|
|
350
|
-
tick: { type: Number, required: true },
|
|
351
|
-
},
|
|
352
|
-
setup() {
|
|
353
|
-
const { agent } = useAgent({
|
|
354
|
-
agentId: "my-agent",
|
|
355
|
-
threadId: "thread-a",
|
|
356
|
-
});
|
|
357
|
-
watchEffect(() => {
|
|
358
|
-
capturedAgent.value = agent.value;
|
|
359
|
-
});
|
|
360
|
-
return {};
|
|
361
|
-
},
|
|
362
|
-
template: "<div>{{ tick }}</div>",
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
const { rerender } = render(Tracker, {
|
|
366
|
-
props: { tick: 0 },
|
|
367
|
-
});
|
|
368
|
-
await nextTick();
|
|
369
|
-
const provisional = capturedAgent.value!;
|
|
370
|
-
expect(provisional.threadId).toBe("thread-a");
|
|
371
|
-
|
|
372
|
-
copilotkitRef.value.runtimeConnectionStatus =
|
|
373
|
-
CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
374
|
-
copilotkitRef.value.getAgent = vi.fn((id: string) =>
|
|
375
|
-
id === "my-agent" ? registeredAgent : undefined,
|
|
376
|
-
);
|
|
377
|
-
copilotkitRef.value.agents = { "my-agent": registeredAgent };
|
|
378
|
-
|
|
379
|
-
await rerender({ tick: 1 });
|
|
380
|
-
await nextTick();
|
|
381
|
-
|
|
382
|
-
const realClone = capturedAgent.value!;
|
|
383
|
-
expect(realClone).not.toBe(provisional);
|
|
384
|
-
expect(realClone).not.toBe(registeredAgent);
|
|
385
|
-
expect(realClone.threadId).toBe("thread-a");
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it("uses composite key for provisional agents when threadId is provided", async () => {
|
|
389
|
-
copilotkitRef.value.runtimeConnectionStatus =
|
|
390
|
-
CopilotKitCoreRuntimeConnectionStatus.Disconnected;
|
|
391
|
-
copilotkitRef.value.getAgent = vi.fn(() => undefined);
|
|
392
|
-
copilotkitRef.value.agents = {};
|
|
393
|
-
|
|
394
|
-
const agents: Record<string, AbstractAgent> = {};
|
|
395
|
-
const TrackerA = defineComponent({
|
|
396
|
-
setup() {
|
|
397
|
-
const { agent } = useAgent({
|
|
398
|
-
agentId: "my-agent",
|
|
399
|
-
threadId: "thread-a",
|
|
400
|
-
});
|
|
401
|
-
watchEffect(() => {
|
|
402
|
-
agents.a = agent.value;
|
|
403
|
-
});
|
|
404
|
-
return {};
|
|
405
|
-
},
|
|
406
|
-
template: "<div />",
|
|
407
|
-
});
|
|
408
|
-
const TrackerB = defineComponent({
|
|
409
|
-
setup() {
|
|
410
|
-
const { agent } = useAgent({
|
|
411
|
-
agentId: "my-agent",
|
|
412
|
-
threadId: "thread-b",
|
|
413
|
-
});
|
|
414
|
-
watchEffect(() => {
|
|
415
|
-
agents.b = agent.value;
|
|
416
|
-
});
|
|
417
|
-
return {};
|
|
418
|
-
},
|
|
419
|
-
template: "<div />",
|
|
420
|
-
});
|
|
421
|
-
const Host = defineComponent({
|
|
422
|
-
components: { TrackerA, TrackerB },
|
|
423
|
-
template: "<div><TrackerA /><TrackerB /></div>",
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
render(Host);
|
|
427
|
-
await nextTick();
|
|
428
|
-
|
|
429
|
-
expect(agents.a).not.toBe(agents.b);
|
|
430
|
-
expect(agents.a.threadId).toBe("thread-a");
|
|
431
|
-
expect(agents.b.threadId).toBe("thread-b");
|
|
432
|
-
});
|
|
433
|
-
});
|