@copilotkit/vue 1.75.1 → 1.76.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.
Files changed (41) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.mjs +272 -157
  3. package/dist/index.mjs.map +1 -1
  4. package/dist/use-render-activity-message-BTKmd3f3.cjs +110 -0
  5. package/dist/use-render-activity-message-BTKmd3f3.cjs.map +1 -0
  6. package/dist/use-render-activity-message-Eu6R2vGT.js +12801 -0
  7. package/dist/use-render-activity-message-Eu6R2vGT.js.map +1 -0
  8. package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
  9. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts +8 -152
  10. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
  11. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
  12. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -1
  13. package/dist/v2/hooks/use-agent.d.ts.map +1 -1
  14. package/dist/v2/index.cjs +1 -1
  15. package/dist/v2/index.d.cts +2 -0
  16. package/dist/v2/index.d.mts +2 -0
  17. package/dist/v2/index.d.ts +2 -0
  18. package/dist/v2/index.d.ts.map +1 -1
  19. package/dist/v2/index.mjs +187 -72
  20. package/dist/v2/index.mjs.map +1 -1
  21. package/package.json +7 -7
  22. package/src/v2/__tests__/utils/test-helpers.ts +26 -6
  23. package/src/v2/components/chat/CopilotChat.vue +100 -20
  24. package/src/v2/components/chat/CopilotChatMessageView.vue +41 -30
  25. package/src/v2/components/chat/__tests__/CopilotChat.clearOnFresh.test.ts +130 -2
  26. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.ts +6 -1
  27. package/src/v2/components/chat/__tests__/CopilotChatMessageView.slotMetaCost.test.ts +187 -0
  28. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.ts +22 -40
  29. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.ts +12 -24
  30. package/src/v2/components/chat/copilotChatToolCallItemRender.ts +3 -2
  31. package/src/v2/hooks/__tests__/use-agent-headers.test.ts +83 -0
  32. package/src/v2/hooks/__tests__/use-agent-stability.test.ts +2 -0
  33. package/src/v2/hooks/__tests__/use-agent.e2e.test.ts +5 -3
  34. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +2 -2
  35. package/src/v2/hooks/use-agent.ts +3 -3
  36. package/src/v2/index.ts +5 -0
  37. package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.ts +6 -4
  38. package/dist/use-render-activity-message-BOPauH9X.cjs +0 -85
  39. package/dist/use-render-activity-message-BOPauH9X.cjs.map +0 -1
  40. package/dist/use-render-activity-message-xvpuaGyf.js +0 -9807
  41. package/dist/use-render-activity-message-xvpuaGyf.js.map +0 -1
@@ -7,6 +7,8 @@ import type {
7
7
  RunAgentInput,
8
8
  RunAgentParameters,
9
9
  RunAgentResult,
10
+ RunStartedEvent,
11
+ RunFinishedEvent,
10
12
  } from "@ag-ui/client";
11
13
  import { Observable, Subject, from, delay } from "rxjs";
12
14
  import { defineComponent, nextTick } from "vue";
@@ -485,12 +487,30 @@ export function renderWithCopilotKit({
485
487
  return render(Host);
486
488
  }
487
489
 
488
- export function runStartedEvent(): BaseEvent {
489
- return { type: EventType.RUN_STARTED } as BaseEvent;
490
+ export function runStartedEvent(
491
+ input: Pick<RunAgentInput, "threadId" | "runId"> = {
492
+ threadId: "test-thread",
493
+ runId: "test-run",
494
+ },
495
+ ): RunStartedEvent {
496
+ return {
497
+ type: EventType.RUN_STARTED,
498
+ threadId: input.threadId,
499
+ runId: input.runId,
500
+ };
490
501
  }
491
502
 
492
- export function runFinishedEvent(): BaseEvent {
493
- return { type: EventType.RUN_FINISHED } as BaseEvent;
503
+ export function runFinishedEvent(
504
+ input: Pick<RunAgentInput, "threadId" | "runId"> = {
505
+ threadId: "test-thread",
506
+ runId: "test-run",
507
+ },
508
+ ): RunFinishedEvent {
509
+ return {
510
+ type: EventType.RUN_FINISHED,
511
+ threadId: input.threadId,
512
+ runId: input.runId,
513
+ };
494
514
  }
495
515
 
496
516
  export function stateSnapshotEvent(snapshot: unknown): BaseEvent {
@@ -684,7 +704,7 @@ export class SuggestionsProviderAgent extends MockStepwiseAgent {
684
704
  setTimeout(() => {
685
705
  void (async () => {
686
706
  const messageId = testId("suggest-msg");
687
- await this.emit({ type: EventType.RUN_STARTED } as BaseEvent);
707
+ await this.emit(runStartedEvent(_input));
688
708
 
689
709
  await emitSuggestionToolCall(this, {
690
710
  toolCallId: testId("tc"),
@@ -692,7 +712,7 @@ export class SuggestionsProviderAgent extends MockStepwiseAgent {
692
712
  suggestions: this._suggestions,
693
713
  });
694
714
 
695
- await this.emit({ type: EventType.RUN_FINISHED } as BaseEvent);
715
+ await this.emit(runFinishedEvent(_input));
696
716
  await this.complete();
697
717
  })();
698
718
  }, 0);
@@ -1,9 +1,5 @@
1
1
  <script setup lang="ts">
2
- import {
3
- AGUIConnectNotImplementedError,
4
- AbstractAgent,
5
- HttpAgent,
6
- } from "@ag-ui/client";
2
+ import { AGUIConnectNotImplementedError, AbstractAgent } from "@ag-ui/client";
7
3
  import {
8
4
  createAttachmentContent,
9
5
  DEFAULT_AGENT_ID,
@@ -21,7 +17,7 @@ import {
21
17
  useSlots,
22
18
  watch,
23
19
  } from "vue";
24
- import { CopilotKitCoreErrorCode } from "@copilotkit/core";
20
+ import { CopilotKitCoreErrorCode, ɵisHttpAgent } from "@copilotkit/core";
25
21
  import type { Suggestion } from "@copilotkit/core";
26
22
  import CopilotChatConfigurationProvider from "../../providers/CopilotChatConfigurationProvider.vue";
27
23
  import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
@@ -111,9 +107,15 @@ type ActiveConnectCycle = {
111
107
  inspectorRequestId: string | null;
112
108
  abortController: AbortController;
113
109
  detached: boolean;
110
+ clearDiscardedBaseline?: () => void;
114
111
  };
115
112
 
116
113
  const activeConnectCycle = shallowRef<ActiveConnectCycle | null>(null);
114
+ let lastConnectCycle: ActiveConnectCycle | null = null;
115
+ const detachPromises = new WeakMap<AbstractAgent, Promise<void>>();
116
+ // Agent clones can share cursors. Finish every old reset before connecting again.
117
+ let pendingTeardown = Promise.resolve();
118
+ const baselineThreadIds = new WeakMap<AbstractAgent, string>();
117
119
 
118
120
  const resolvedAgentId = computed(
119
121
  () => props.agentId ?? existingConfig.value?.agentId ?? DEFAULT_AGENT_ID,
@@ -379,6 +381,7 @@ watch(
379
381
  // React does — makes the ordering unconditional instead of depending on
380
382
  // watcher scheduling. Non-explicit threads skip /connect below, but the first
381
383
  // runAgent still has to ship the thread the UI is rendering.
384
+ baselineThreadIds.set(currentAgent, currentAgent.threadId);
382
385
  currentAgent.threadId = threadId;
383
386
 
384
387
  // When the caller hasn't picked a specific thread, resolvedThreadId is
@@ -421,10 +424,6 @@ watch(
421
424
  cycle = existingCycle;
422
425
  } else {
423
426
  const connectAbortController = new AbortController();
424
- if (currentAgent instanceof HttpAgent) {
425
- currentAgent.abortController = connectAbortController;
426
- }
427
-
428
427
  cycle = {
429
428
  core: core as object,
430
429
  agent: currentAgent,
@@ -434,11 +433,18 @@ watch(
434
433
  detached: false,
435
434
  };
436
435
  activeConnectCycle.value = cycle;
437
-
438
- void core
439
- .connectAgent({ agent: currentAgent })
436
+ lastConnectCycle = cycle;
437
+
438
+ void pendingTeardown
439
+ .then(() => {
440
+ if (cycle.detached) return;
441
+ if (ɵisHttpAgent(currentAgent))
442
+ currentAgent.abortController = connectAbortController;
443
+ return core.connectAgent({ agent: currentAgent });
444
+ })
440
445
  .catch((error: unknown) => {
441
446
  if (cycle.detached) {
447
+ cycle.clearDiscardedBaseline?.();
442
448
  return;
443
449
  }
444
450
  if (error instanceof AGUIConnectNotImplementedError) {
@@ -460,6 +466,7 @@ watch(
460
466
  // can briefly render against an incompletely-laid-out message
461
467
  // tree and visibly snap once the last text chunk lands.
462
468
  if (cycle.detached) {
469
+ cycle.clearDiscardedBaseline?.();
463
470
  return;
464
471
  }
465
472
  const raf =
@@ -490,7 +497,17 @@ watch(
490
497
 
491
498
  activeCycle.detached = true;
492
499
  activeCycle.abortController.abort();
493
- void activeCycle.agent.detachActiveRun?.();
500
+ const detach = activeCycle.agent
501
+ .detachActiveRun()
502
+ .catch((error: unknown) => {
503
+ console.error("CopilotChat: detachActiveRun failed", error);
504
+ });
505
+ detachPromises.set(activeCycle.agent, detach);
506
+ pendingTeardown = Promise.all([pendingTeardown, detach]).then(() => {});
507
+ void detach.then(() => {
508
+ if (detachPromises.get(activeCycle.agent) === detach)
509
+ detachPromises.delete(activeCycle.agent);
510
+ });
494
511
  activeConnectCycle.value = null;
495
512
  });
496
513
  },
@@ -502,17 +519,80 @@ watch(
502
519
  // replay their history via the /connect cycle above, and the very first
503
520
  // resolution (mount) or an agent-store swap that keeps the same thread id
504
521
  // must never clear — only a real fresh-thread transition does.
505
- const lastFreshThreadId = ref<string | undefined>(undefined);
522
+ let lastFreshSelection:
523
+ | { threadId: string; agent: AbstractAgent | null }
524
+ | undefined;
506
525
  watch(
507
526
  [resolvedThreadId, hasExplicitThreadId, () => agent.value],
508
- ([threadId, isExplicit, currentAgent]) => {
509
- const previous = lastFreshThreadId.value;
510
- lastFreshThreadId.value = threadId;
527
+ ([threadId, isExplicit, currentAgent], _old, onCleanup) => {
528
+ const previous = lastFreshSelection;
529
+ const selection = { threadId, agent: currentAgent };
530
+ lastFreshSelection = selection;
511
531
  if (!currentAgent) return;
512
532
  if (isExplicit) return;
513
533
  if (previous === undefined) return;
514
- if (threadId === previous) return;
515
- currentAgent.setMessages([]);
534
+ if (threadId === previous.threadId) return;
535
+ let active = true;
536
+ let resetPending = true;
537
+ onCleanup(() => {
538
+ active = false;
539
+ });
540
+ const discardedThreadId =
541
+ previous.agent === currentAgent
542
+ ? previous.threadId
543
+ : baselineThreadIds.get(currentAgent);
544
+ const clearCursor = () => {
545
+ if (!discardedThreadId) return;
546
+ if (
547
+ "clearReplayCursor" in currentAgent &&
548
+ typeof currentAgent.clearReplayCursor === "function"
549
+ )
550
+ currentAgent.clearReplayCursor(discardedThreadId);
551
+ if (
552
+ "clearReconnectCursor" in currentAgent &&
553
+ typeof currentAgent.clearReconnectCursor === "function"
554
+ )
555
+ currentAgent.clearReconnectCursor(discardedThreadId);
556
+ };
557
+ const clearDiscardedBaseline = () => {
558
+ if (
559
+ !active ||
560
+ !resetPending ||
561
+ isUnmounting.value ||
562
+ lastFreshSelection !== selection ||
563
+ agent.value !== currentAgent
564
+ )
565
+ return;
566
+ currentAgent.setMessages([]);
567
+ currentAgent.setState({});
568
+ currentAgent.pendingInterrupts = [];
569
+ clearCursor();
570
+ };
571
+ if (
572
+ lastConnectCycle?.agent === currentAgent &&
573
+ lastConnectCycle.threadId === discardedThreadId
574
+ )
575
+ lastConnectCycle.clearDiscardedBaseline = clearDiscardedBaseline;
576
+ const detach =
577
+ detachPromises.get(currentAgent) ?? currentAgent.detachActiveRun();
578
+ detachPromises.set(currentAgent, detach);
579
+ const finishReset = () => {
580
+ if (!resetPending) return;
581
+ // The cursor belongs to the discarded view, even after a fast reopen.
582
+ clearCursor();
583
+ clearDiscardedBaseline();
584
+ resetPending = false;
585
+ };
586
+ clearDiscardedBaseline();
587
+ pendingTeardown = Promise.all([pendingTeardown, detach])
588
+ .then(finishReset)
589
+ .catch((error: unknown) => {
590
+ console.error("CopilotChat: detachActiveRun failed", error);
591
+ })
592
+ .finally(() => {
593
+ if (detachPromises.get(currentAgent) === detach)
594
+ detachPromises.delete(currentAgent);
595
+ });
516
596
  },
517
597
  { immediate: true },
518
598
  );
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, onMounted, ref, useSlots, watch } from "vue";
3
- import type { Component } from "vue";
3
+ import type { Component, FunctionalComponent, VNodeChild } from "vue";
4
4
  import type {
5
5
  ActivityMessage,
6
6
  AssistantMessage,
@@ -262,9 +262,11 @@ function getMeta(
262
262
  ? Math.max(messageIdsInRun.indexOf(message.id), 0)
263
263
  : 0;
264
264
  const numberOfMessagesInRun = resolvedRunId ? messageIdsInRun.length : 1;
265
- const stateSnapshot = resolvedRunId
266
- ? core.getStateByRun(agentId, threadId, resolvedRunId)
267
- : undefined;
265
+
266
+ // `getStateByRun` deep-clones the whole run state, so resolve it only when
267
+ // a consumer reads `stateSnapshot`, and at most once per meta object.
268
+ let stateSnapshot: unknown;
269
+ let stateSnapshotResolved = false;
268
270
 
269
271
  return {
270
272
  runId,
@@ -272,10 +274,37 @@ function getMeta(
272
274
  messageIndexInRun,
273
275
  numberOfMessagesInRun,
274
276
  agentId,
275
- stateSnapshot,
277
+ get stateSnapshot() {
278
+ if (!stateSnapshotResolved) {
279
+ stateSnapshotResolved = true;
280
+ stateSnapshot = resolvedRunId
281
+ ? core.getStateByRun(agentId, threadId, resolvedRunId)
282
+ : undefined;
283
+ }
284
+ return stateSnapshot;
285
+ },
276
286
  };
277
287
  }
278
288
 
289
+ // `Object.assign` onto the meta object keeps the lazy `stateSnapshot` getter.
290
+ // A spread would call it.
291
+ function getMessageSlotProps(
292
+ message: Message,
293
+ position: MessageMetaProps["position"],
294
+ ): MessageMetaProps {
295
+ return Object.assign(getMeta(message), { message, position });
296
+ }
297
+
298
+ // Calls a `#message-before` / `#message-after` slot with the meta object
299
+ // itself. A template `<slot v-bind>` inside `v-for` goes through
300
+ // `mergeProps`, which reads every key and so would clone the run state for
301
+ // slots that never read `stateSnapshot`.
302
+ const MessageMetaSlot: FunctionalComponent<{
303
+ render: (props: MessageMetaProps) => unknown;
304
+ slotProps: MessageMetaProps;
305
+ }> = ({ render, slotProps }) => render(slotProps) as VNodeChild;
306
+ MessageMetaSlot.props = ["render", "slotProps"];
307
+
279
308
  function getActivitySlotName(activityType: string): `activity-${string}` {
280
309
  return `activity-${activityType}`;
281
310
  }
@@ -310,11 +339,7 @@ function resolveCustomMessageRenderer(
310
339
 
311
340
  return {
312
341
  renderer: selected.render as ResolvedCustomMessageRenderer["renderer"],
313
- props: {
314
- ...getMeta(message),
315
- message,
316
- position,
317
- },
342
+ props: getMessageSlotProps(message, position),
318
343
  };
319
344
  }
320
345
 
@@ -370,17 +395,10 @@ function resolveToolMessage(
370
395
  v-for="message in deduplicatedMessages"
371
396
  :key="rowRenderKeys.get(message.id) ?? message.id"
372
397
  >
373
- <slot
398
+ <MessageMetaSlot
374
399
  v-if="componentSlots['message-before']"
375
- name="message-before"
376
- :message="message"
377
- position="before"
378
- :run-id="getMeta(message).runId"
379
- :message-index="getMeta(message).messageIndex"
380
- :message-index-in-run="getMeta(message).messageIndexInRun"
381
- :number-of-messages-in-run="getMeta(message).numberOfMessagesInRun"
382
- :agent-id="getMeta(message).agentId"
383
- :state-snapshot="getMeta(message).stateSnapshot"
400
+ :render="componentSlots['message-before']"
401
+ :slot-props="getMessageSlotProps(message, 'before')"
384
402
  />
385
403
  <component
386
404
  v-else-if="resolveCustomMessageRenderer(message, 'before')"
@@ -463,17 +481,10 @@ function resolveToolMessage(
463
481
  </slot>
464
482
  </slot>
465
483
 
466
- <slot
484
+ <MessageMetaSlot
467
485
  v-if="componentSlots['message-after']"
468
- name="message-after"
469
- :message="message"
470
- position="after"
471
- :run-id="getMeta(message).runId"
472
- :message-index="getMeta(message).messageIndex"
473
- :message-index-in-run="getMeta(message).messageIndexInRun"
474
- :number-of-messages-in-run="getMeta(message).numberOfMessagesInRun"
475
- :agent-id="getMeta(message).agentId"
476
- :state-snapshot="getMeta(message).stateSnapshot"
486
+ :render="componentSlots['message-after']"
487
+ :slot-props="getMessageSlotProps(message, 'after')"
477
488
  />
478
489
  <component
479
490
  v-else-if="resolveCustomMessageRenderer(message, 'after')"
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
- import { defineComponent, h, nextTick } from "vue";
2
+ import { defineComponent, h, nextTick, ref } from "vue";
3
3
  import { flushPromises, mount } from "@vue/test-utils";
4
4
  import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue";
5
5
  import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue";
@@ -14,6 +14,134 @@ import CopilotChat from "../CopilotChat.vue";
14
14
  // configuration transitions to a fresh, non-explicit thread via
15
15
  // startNewThread()
16
16
  describe("CopilotChat clear-on-fresh", () => {
17
+ it.each(["clearReplayCursor", "clearReconnectCursor"] as const)(
18
+ "clears state and the captured %s cursor on a fresh selection",
19
+ async (method) => {
20
+ const agent = new StateCapturingAgent();
21
+ const cursors = new Map<string, string>();
22
+ Object.defineProperty(agent, method, {
23
+ value: (threadId: string) => cursors.delete(threadId),
24
+ });
25
+ const threadId = ref("saved");
26
+ const explicit = ref(true);
27
+ const wrapper = mount(CopilotKitProvider, {
28
+ props: { agents__unsafe_dev_only: { default: agent } },
29
+ slots: {
30
+ default: () =>
31
+ h(
32
+ CopilotChatConfigurationProvider,
33
+ { threadId: threadId.value, hasExplicitThreadId: explicit.value },
34
+ { default: () => h(CopilotChat, { welcomeScreen: false }) },
35
+ ),
36
+ },
37
+ });
38
+ await flushPromises();
39
+ agent.setState({ saved: true });
40
+ agent.pendingInterrupts = [{ id: "approval-A", reason: "confirmation" }];
41
+ cursors.set("saved", "last-event");
42
+ cursors.set("other", "keep-other");
43
+ threadId.value = "fresh";
44
+ explicit.value = false;
45
+ await flushPromises();
46
+ expect(agent.state).toEqual({});
47
+ expect(agent.pendingInterrupts).toEqual([]);
48
+ expect(cursors.get("saved")).toBeUndefined();
49
+ expect(cursors.get("other")).toBe("keep-other");
50
+ wrapper.unmount();
51
+ },
52
+ );
53
+ it.each(["fresh", "saved", "other", "via-fresh", "swap"])(
54
+ "protects %s from an abandoned connect that completes later",
55
+ async (destination) => {
56
+ let releaseDetach = () => {};
57
+ let releaseConnect = () => {};
58
+ let pendingDetach: Promise<void> | undefined;
59
+ const oldConnect = new Promise<void>((resolve) => {
60
+ releaseConnect = resolve;
61
+ });
62
+ const cursors = new Map<string, string>();
63
+ const requests: (string | null)[] = [];
64
+ class ReplayAgent extends StateCapturingAgent {
65
+ clearReplayCursor(threadId: string) {
66
+ cursors.delete(threadId);
67
+ }
68
+ async detachActiveRun() {
69
+ await pendingDetach;
70
+ await super.detachActiveRun();
71
+ }
72
+ async connectAgent(
73
+ ...args: Parameters<StateCapturingAgent["connectAgent"]>
74
+ ) {
75
+ requests.push(cursors.get(this.threadId) ?? null);
76
+ const first = requests.length === 1;
77
+ const result = await super.connectAgent(...args);
78
+ if (first) await oldConnect;
79
+ return result;
80
+ }
81
+ }
82
+ const agent = new ReplayAgent();
83
+ let currentAgent = agent;
84
+ const threadId = ref("saved");
85
+ const explicit = ref(true);
86
+ const wrapper = mount(CopilotKitProvider, {
87
+ props: { agents__unsafe_dev_only: { default: agent } },
88
+ slots: {
89
+ default: () =>
90
+ h(
91
+ CopilotChatConfigurationProvider,
92
+ { threadId: threadId.value, hasExplicitThreadId: explicit.value },
93
+ { default: () => h(CopilotChat, { welcomeScreen: false }) },
94
+ ),
95
+ },
96
+ });
97
+ await flushPromises();
98
+ expect(requests).toEqual([null]);
99
+ pendingDetach = new Promise<void>((resolve) => {
100
+ releaseDetach = resolve;
101
+ });
102
+ cursors.set("saved", "old-event");
103
+ threadId.value = "fresh";
104
+ explicit.value = false;
105
+ await nextTick();
106
+ if (destination === "via-fresh") {
107
+ threadId.value = "fresh-again";
108
+ await nextTick();
109
+ }
110
+ if (destination === "swap") {
111
+ currentAgent = new ReplayAgent();
112
+ await wrapper.setProps({
113
+ agents__unsafe_dev_only: { default: currentAgent },
114
+ });
115
+ }
116
+ const target =
117
+ destination === "via-fresh" || destination === "swap"
118
+ ? "saved"
119
+ : destination;
120
+ if (target !== "fresh") {
121
+ threadId.value = target;
122
+ explicit.value = true;
123
+ await nextTick();
124
+ }
125
+ expect(requests).toEqual([null]);
126
+ cursors.set("saved", "late-event");
127
+ releaseDetach();
128
+ await flushPromises();
129
+ expect(requests).toEqual(target === "fresh" ? [null] : [null, null]);
130
+ currentAgent.setState({ current: true });
131
+ currentAgent.pendingInterrupts = [
132
+ { id: "approval-new", reason: "confirmation" },
133
+ ];
134
+ cursors.set(target, "new-event");
135
+ releaseConnect();
136
+ await flushPromises();
137
+ expect(currentAgent.state).toEqual({ current: true });
138
+ expect(currentAgent.pendingInterrupts).toEqual([
139
+ { id: "approval-new", reason: "confirmation" },
140
+ ]);
141
+ expect(cursors.get(target)).toBe("new-event");
142
+ wrapper.unmount();
143
+ },
144
+ );
17
145
  it("does not clear messages on initial mount", async () => {
18
146
  const agent = new StateCapturingAgent();
19
147
  // Spy before mount: attaching afterwards can never observe a mount-time
@@ -133,7 +261,7 @@ describe("CopilotChat clear-on-fresh", () => {
133
261
  await nextTick();
134
262
  expect(currentThreadId).toBe("elsewhere");
135
263
 
136
- newAgent!.setMessages([{ id: "m1", role: "user", content: "hi" } as never]);
264
+ newAgent!.setMessages([{ id: "m1", role: "user", content: "hi" }]);
137
265
  expect(newAgent!.messages.length).toBe(1);
138
266
 
139
267
  // Switch back non-explicitly. Any `setMessages([])` observed here must come
@@ -3,6 +3,7 @@ import { computed, defineComponent, onMounted } from "vue";
3
3
  import type { PropType } from "vue";
4
4
  import { afterEach, describe, expect, it, vi } from "vitest";
5
5
  import { EventType } from "@ag-ui/client";
6
+ import type { RunErrorEvent } from "@ag-ui/client";
6
7
  import { z } from "zod";
7
8
  import { useConfigureSuggestions } from "../../../hooks/use-configure-suggestions";
8
9
  import {
@@ -189,7 +190,11 @@ describe("CopilotChat E2E - Chat Basics and Streaming Patterns", () => {
189
190
  );
190
191
  });
191
192
 
192
- await agent.emit({ type: EventType.RUN_ERROR } as any);
193
+ const errorEvent: RunErrorEvent = {
194
+ type: EventType.RUN_ERROR,
195
+ message: "Test run failed",
196
+ };
197
+ await agent.emit(errorEvent);
193
198
  await agent.complete();
194
199
 
195
200
  await waitFor(() => {