@copilotkit/vue 1.79.2-canary.1792172552 → 1.80.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 (40) hide show
  1. package/PARITY.md +1 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.mjs +3 -2
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/{use-render-activity-message-DlRZfpDh.js → use-render-activity-message-BhgLK8XD.js} +3068 -3042
  7. package/dist/use-render-activity-message-BhgLK8XD.js.map +1 -0
  8. package/dist/use-render-activity-message-DfvDG9VF.cjs +70 -0
  9. package/dist/use-render-activity-message-DfvDG9VF.cjs.map +1 -0
  10. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -1
  11. package/dist/v2/components/MCPAppsActivityRenderer.d.ts +21 -0
  12. package/dist/v2/components/MCPAppsActivityRenderer.d.ts.map +1 -1
  13. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +30 -0
  14. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
  15. package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
  16. package/dist/v2/index.cjs +1 -1
  17. package/dist/v2/index.mjs +1 -1
  18. package/dist/v2/providers/CopilotKitProvider.types.d.ts +12 -0
  19. package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
  20. package/dist/v2/providers/CopilotKitProvider.vue.d.ts +5 -1
  21. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
  22. package/dist/v2/providers/types.d.ts +5 -0
  23. package/dist/v2/providers/types.d.ts.map +1 -1
  24. package/package.json +9 -9
  25. package/src/v2/components/A2UISurfaceActivityRenderer.vue +17 -12
  26. package/src/v2/components/MCPAppsActivityRenderer.ts +14 -0
  27. package/src/v2/components/__tests__/MCPAppsActivityRenderer.e2e.test.ts +74 -2
  28. package/src/v2/components/chat/CopilotChatInput.vue +5 -1
  29. package/src/v2/components/chat/CopilotChatUserMessage.vue +4 -4
  30. package/src/v2/components/chat/CopilotChatView.vue +1 -0
  31. package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +39 -0
  32. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.test.ts +42 -1
  33. package/src/v2/components/chat/__tests__/CopilotChatView.test.ts +56 -0
  34. package/src/v2/providers/CopilotKitProvider.types.ts +12 -0
  35. package/src/v2/providers/CopilotKitProvider.vue +14 -2
  36. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxReplay.test.ts +157 -0
  37. package/src/v2/providers/types.ts +5 -0
  38. package/dist/use-render-activity-message-CGgJhO9J.cjs +0 -70
  39. package/dist/use-render-activity-message-CGgJhO9J.cjs.map +0 -1
  40. package/dist/use-render-activity-message-DlRZfpDh.js.map +0 -1
@@ -290,8 +290,8 @@ onBeforeUnmount(() => {
290
290
  :class="toolbarButtonClass"
291
291
  class="cpk:h-6 cpk:w-6 cpk:p-0"
292
292
  :disabled="!canGoPrev"
293
- aria-label="Previous branch"
294
- title="Previous branch"
293
+ :aria-label="labels.userMessageToolbarPreviousBranchLabel"
294
+ :title="labels.userMessageToolbarPreviousBranchLabel"
295
295
  @click="goPrev"
296
296
  >
297
297
  <IconChevronLeft class="cpk:size-[20px]" />
@@ -306,8 +306,8 @@ onBeforeUnmount(() => {
306
306
  :class="toolbarButtonClass"
307
307
  class="cpk:h-6 cpk:w-6 cpk:p-0"
308
308
  :disabled="!canGoNext"
309
- aria-label="Next branch"
310
- title="Next branch"
309
+ :aria-label="labels.userMessageToolbarNextBranchLabel"
310
+ :title="labels.userMessageToolbarNextBranchLabel"
311
311
  @click="goNext"
312
312
  >
313
313
  <IconChevronRight class="cpk:size-[20px]" />
@@ -660,6 +660,7 @@ onBeforeUnmount(() => {
660
660
  <button
661
661
  type="button"
662
662
  data-testid="copilot-chat-view-scroll-to-bottom"
663
+ :aria-label="labels.chatScrollToBottomLabel"
663
664
  class="cpk:rounded-full cpk:w-10 cpk:h-10 cpk:p-0 cpk:pointer-events-auto cpk:bg-white cpk:dark:bg-gray-900 cpk:shadow-lg cpk:border cpk:border-gray-200 cpk:dark:border-gray-700 cpk:hover:bg-gray-50 cpk:dark:hover:bg-gray-800 cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer"
664
665
  @click="scrollToBottom()"
665
666
  >
@@ -535,6 +535,45 @@ describe("CopilotChatInput", () => {
535
535
  });
536
536
  });
537
537
 
538
+ // The send button shows only an icon, so its accessible name comes from the
539
+ // labels config; that is also what lets an app translate it.
540
+ it("names the send button with the default label", () => {
541
+ renderWithProvider({
542
+ props: { modelValue: "" },
543
+ listeners: { onSubmitMessage: mockOnSubmitMessage },
544
+ });
545
+
546
+ expect(screen.getByRole("button", { name: "Send message" })).toBe(
547
+ getSendButton(),
548
+ );
549
+ });
550
+
551
+ it("names the send button Stop while it stops the run", () => {
552
+ renderWithProvider({
553
+ props: { modelValue: "", isRunning: true },
554
+ listeners: { onSubmitMessage: mockOnSubmitMessage, onStop: vi.fn() },
555
+ });
556
+
557
+ expect(screen.getByRole("button", { name: "Stop" })).toBe(getSendButton());
558
+ });
559
+
560
+ it("names the send button with the configured labels", () => {
561
+ renderWithProvider({
562
+ template: `
563
+ <CopilotChatConfigurationProvider
564
+ :thread-id="TEST_THREAD_ID"
565
+ :labels="{ chatInputToolbarSendButtonLabel: 'Nachricht senden' }"
566
+ >
567
+ <CopilotChatInput model-value="" />
568
+ </CopilotChatConfigurationProvider>
569
+ `,
570
+ });
571
+
572
+ expect(screen.getByRole("button", { name: "Nachricht senden" })).toBe(
573
+ getSendButton(),
574
+ );
575
+ });
576
+
538
577
  it("shows cancel and finish buttons in transcribe mode", () => {
539
578
  renderWithProvider({
540
579
  props: {
@@ -1,9 +1,11 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
- import { h, nextTick } from "vue";
2
+ import { defineComponent, h, nextTick } from "vue";
3
+ import { render, screen } from "@testing-library/vue";
3
4
  import { mount } from "@vue/test-utils";
4
5
  import type { UserMessage } from "@ag-ui/core";
5
6
  import { CopilotChatDefaultLabels } from "../../../providers/types";
6
7
  import CopilotChatUserMessage from "../CopilotChatUserMessage.vue";
8
+ import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue";
7
9
 
8
10
  describe("CopilotChatUserMessage", () => {
9
11
  let originalClipboard: Clipboard | undefined;
@@ -101,6 +103,45 @@ describe("CopilotChatUserMessage", () => {
101
103
  ).toBe(false);
102
104
  });
103
105
 
106
+ it("names the branch buttons with the configured labels", () => {
107
+ const Host = defineComponent({
108
+ components: { CopilotChatConfigurationProvider, CopilotChatUserMessage },
109
+ setup() {
110
+ return {
111
+ message: {
112
+ id: "user-5",
113
+ role: "user",
114
+ timestamp: new Date(),
115
+ content: "Branch message",
116
+ } as UserMessage,
117
+ labels: {
118
+ userMessageToolbarPreviousBranchLabel: "Vorherige Antwort",
119
+ userMessageToolbarNextBranchLabel: "Nächste Antwort",
120
+ },
121
+ onSwitchToBranch: vi.fn(),
122
+ };
123
+ },
124
+ template: `
125
+ <CopilotChatConfigurationProvider thread-id="thread-1" :labels="labels">
126
+ <CopilotChatUserMessage
127
+ :message="message"
128
+ :branch-index="1"
129
+ :number-of-branches="3"
130
+ :on-switch-to-branch="onSwitchToBranch"
131
+ />
132
+ </CopilotChatConfigurationProvider>
133
+ `,
134
+ });
135
+ render(Host);
136
+
137
+ expect(
138
+ screen.getByRole("button", { name: "Vorherige Antwort" }),
139
+ ).toBeDefined();
140
+ expect(
141
+ screen.getByRole("button", { name: "Nächste Antwort" }),
142
+ ).toBeDefined();
143
+ });
144
+
104
145
  it("renders branch navigation and emits switch payload exactly once", async () => {
105
146
  const onSwitchToBranch = vi.fn();
106
147
  const message: UserMessage = {
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import { defineComponent, h, nextTick, ref } from "vue";
3
3
  import { mount } from "@vue/test-utils";
4
+ import { render, screen } from "@testing-library/vue";
4
5
  import type { AssistantMessage, Message, UserMessage } from "@ag-ui/core";
5
6
  import type { Suggestion } from "@copilotkit/core";
6
7
  import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue";
@@ -404,6 +405,61 @@ describe("CopilotChatView", () => {
404
405
  .trigger("click");
405
406
  });
406
407
 
408
+ it("names the scroll-to-bottom button with the configured label", async () => {
409
+ const Host = defineComponent({
410
+ components: {
411
+ CopilotKitProvider,
412
+ CopilotChatConfigurationProvider,
413
+ CopilotChatView,
414
+ },
415
+ setup() {
416
+ return {
417
+ messages: createChatMessages(12),
418
+ labels: { chatScrollToBottomLabel: "Nach unten scrollen" },
419
+ };
420
+ },
421
+ template: `
422
+ <CopilotKitProvider runtime-url="/api/copilotkit">
423
+ <CopilotChatConfigurationProvider
424
+ thread-id="thread-1"
425
+ agent-id="default"
426
+ :labels="labels"
427
+ >
428
+ <CopilotChatView :messages="messages" />
429
+ </CopilotChatConfigurationProvider>
430
+ </CopilotKitProvider>
431
+ `,
432
+ });
433
+ render(Host);
434
+
435
+ // Let the initial auto-scroll settle, then scroll away from the bottom so
436
+ // the button appears.
437
+ const scrollElement = await screen.findByTestId("copilot-chat-view-scroll");
438
+ await new Promise((resolve) => setTimeout(resolve, 0));
439
+ let scrollTop = 0;
440
+ Object.defineProperty(scrollElement, "scrollHeight", {
441
+ configurable: true,
442
+ get: () => 1000,
443
+ });
444
+ Object.defineProperty(scrollElement, "clientHeight", {
445
+ configurable: true,
446
+ get: () => 300,
447
+ });
448
+ Object.defineProperty(scrollElement, "scrollTop", {
449
+ configurable: true,
450
+ get: () => scrollTop,
451
+ set: (value: number) => {
452
+ scrollTop = value;
453
+ },
454
+ });
455
+ scrollTop = 120;
456
+ scrollElement.dispatchEvent(new Event("scroll"));
457
+
458
+ expect(
459
+ await screen.findByRole("button", { name: "Nach unten scrollen" }),
460
+ ).toBeDefined();
461
+ });
462
+
407
463
  it("preserves scroll position when new messages arrive and the user is scrolled up", async () => {
408
464
  const messages = ref<Message[]>(createChatMessages(12));
409
465
 
@@ -92,6 +92,18 @@ export interface CopilotKitProviderProps {
92
92
  loadingComponent?: Component;
93
93
  includeSchema?: boolean;
94
94
  };
95
+ /** Configuration for the built-in MCP Apps renderer. */
96
+ mcpApps?: {
97
+ /**
98
+ * Restore `allow-same-origin` on the MCP App sandbox. Default `false`.
99
+ *
100
+ * **Unsafe.** The widget then runs with your page's origin: it can read
101
+ * your cookies, storage and DOM, and call your APIs as the signed-in user.
102
+ * Only set it for widgets you wrote and trust that need `localStorage`,
103
+ * `IndexedDB` or cookies. By default widgets get an opaque origin.
104
+ */
105
+ allowSameOriginFallback?: boolean;
106
+ };
95
107
  /**
96
108
  * Enable debug logging for the client-side event pipeline.
97
109
  *
@@ -10,6 +10,7 @@ import {
10
10
  triggerRef,
11
11
  watch,
12
12
  } from "vue";
13
+ import type { Component, FunctionalComponent } from "vue";
13
14
  import { z } from "zod";
14
15
  import type { AbstractAgent } from "@ag-ui/client";
15
16
  import { ToolCallStatus } from "@copilotkit/core";
@@ -91,6 +92,13 @@ const DEFAULT_DESIGN_SKILL = `When generating UI with generateSandboxedUi, follo
91
92
  - Minimal transitions (150ms) for hover/focus states only. No decorative animations.
92
93
  - Keep the UI focused and dense — avoid excessive padding. Use compact spacing (8–12px gaps, 10–14px padding in controls).`;
93
94
 
95
+ /** The MCP Apps renderer with `mcpApps.allowSameOriginFallback` applied. */
96
+ const SameOriginMCPAppsActivityRenderer: FunctionalComponent = (_, { attrs }) =>
97
+ h(MCPAppsActivityRenderer as Component, {
98
+ ...attrs,
99
+ allowSameOriginFallback: true,
100
+ });
101
+
94
102
  const props = withDefaults(defineProps<CopilotKitProviderProps>(), {
95
103
  headers: () => ({}),
96
104
  properties: () => ({}),
@@ -103,6 +111,7 @@ const props = withDefaults(defineProps<CopilotKitProviderProps>(), {
103
111
  openGenerativeUI: undefined,
104
112
  useSingleEndpoint: undefined,
105
113
  a2ui: undefined,
114
+ mcpApps: undefined,
106
115
  enableInspector: undefined,
107
116
  });
108
117
 
@@ -424,6 +433,8 @@ const builtInFrontendTools = computed<VueFrontendTool[]>(() => {
424
433
  description: GENERATE_SANDBOXED_UI_DESCRIPTION,
425
434
  parameters: GenerateSandboxedUiArgsSchema,
426
435
  handler: async () => "UI generated",
436
+ // This acknowledgement has no side effects and is safe to reconstruct.
437
+ reconnectBehavior: "resume-pending",
427
438
  followUp: true,
428
439
  render:
429
440
  OpenGenerativeUIToolRenderer as unknown as VueFrontendTool["render"],
@@ -438,8 +449,9 @@ const builtInActivityRenderers = computed<
438
449
  {
439
450
  activityType: MCPAppsActivityType,
440
451
  content: MCPAppsActivityContentSchema as unknown as z.ZodSchema<unknown>,
441
- render:
442
- MCPAppsActivityRenderer as unknown as VueActivityMessageRenderer<unknown>["render"],
452
+ render: (props.mcpApps?.allowSameOriginFallback
453
+ ? SameOriginMCPAppsActivityRenderer
454
+ : MCPAppsActivityRenderer) as unknown as VueActivityMessageRenderer<unknown>["render"],
443
455
  },
444
456
  ];
445
457
 
@@ -0,0 +1,157 @@
1
+ import { cleanup, render, waitFor } from "@testing-library/vue";
2
+ import { defineComponent } from "vue";
3
+ import CopilotKitProvider from "../CopilotKitProvider.vue";
4
+ import { useCopilotKit } from "../useCopilotKit";
5
+ import { HttpAgent } from "@ag-ui/client";
6
+ import type { Message } from "@ag-ui/client";
7
+ import type { CopilotKitCore } from "@copilotkit/core";
8
+ import { afterEach, describe, expect, it, vi } from "vitest";
9
+
10
+ /** Capture the real provider core after its built-in sandbox tool is registered. */
11
+ async function mountProvider() {
12
+ let core: CopilotKitCore | undefined;
13
+ const Capture = defineComponent({
14
+ /** Expose the injected core without retaining Vue's reactive wrapper. */
15
+ setup() {
16
+ core = useCopilotKit().copilotkit.value;
17
+ return () => null;
18
+ },
19
+ });
20
+ render(
21
+ defineComponent({
22
+ components: { CopilotKitProvider, Capture },
23
+ template:
24
+ '<CopilotKitProvider :open-generative-u-i="{}"><Capture /></CopilotKitProvider>',
25
+ }),
26
+ );
27
+ await waitFor(() =>
28
+ expect(core?.getTool({ toolName: "generateSandboxedUi" })).toBeDefined(),
29
+ );
30
+ if (!core) throw new Error("Missing core");
31
+ return core;
32
+ }
33
+
34
+ /** Hydrate imported tool calls with controlled approval and side-effect handlers. */
35
+ async function setupReplay(completed = false) {
36
+ const core = await mountProvider();
37
+ const agent = new HttpAgent({
38
+ url: "https://runtime.invalid",
39
+ agentId: "default",
40
+ threadId: "imported-thread",
41
+ });
42
+ const saved: Message[] = [
43
+ {
44
+ id: "imported-message",
45
+ role: "assistant",
46
+ toolCalls: [
47
+ {
48
+ id: "sandbox-id",
49
+ type: "function",
50
+ function: {
51
+ name: "generateSandboxedUi",
52
+ arguments: JSON.stringify({ html: "<button>Calculator</button>" }),
53
+ },
54
+ },
55
+ {
56
+ id: "approval-id",
57
+ type: "function",
58
+ function: { name: "meeting", arguments: "{}" },
59
+ },
60
+ {
61
+ id: "side-effect-id",
62
+ type: "function",
63
+ function: { name: "sendEmail", arguments: "{}" },
64
+ },
65
+ ],
66
+ },
67
+ ];
68
+ if (completed)
69
+ saved.push(
70
+ {
71
+ id: "saved-sandbox",
72
+ role: "tool",
73
+ toolCallId: "sandbox-id",
74
+ content: "UI generated",
75
+ },
76
+ {
77
+ id: "saved-approval",
78
+ role: "tool",
79
+ toolCallId: "approval-id",
80
+ content: "approved",
81
+ },
82
+ );
83
+ vi.spyOn(agent, "connectAgent").mockImplementation(async () => {
84
+ agent.setMessages(structuredClone(saved));
85
+ return { result: undefined, newMessages: structuredClone(saved) };
86
+ });
87
+ const submissions: Message[][] = [];
88
+ const run = vi.spyOn(agent, "runAgent").mockImplementation(async () => {
89
+ submissions.push(structuredClone(agent.messages));
90
+ return { result: undefined, newMessages: [] };
91
+ });
92
+ let answer!: (value: string) => void;
93
+ const pending = new Promise<string>((resolve) => {
94
+ answer = resolve;
95
+ });
96
+ const approval = vi.fn(() => pending);
97
+ const sideEffect = vi.fn(async () => "sent");
98
+ core.addTool({
99
+ name: "meeting",
100
+ type: "human-in-the-loop",
101
+ handler: approval,
102
+ });
103
+ core.addTool({ name: "sendEmail", handler: sideEffect });
104
+ return { core, agent, saved, run, submissions, answer, approval, sideEffect };
105
+ }
106
+
107
+ describe("CopilotKitProvider — sandboxed UI replay", () => {
108
+ afterEach(() => {
109
+ cleanup();
110
+ vi.restoreAllMocks();
111
+ });
112
+
113
+ it("restores the original sandbox result before the pending approval continues", async () => {
114
+ const { core, agent, run, submissions, answer, approval, sideEffect } =
115
+ await setupReplay();
116
+ await core.connectAgent({ agent });
117
+ await waitFor(() => expect(approval).toHaveBeenCalledTimes(1));
118
+ expect(agent.messages).toContainEqual(
119
+ expect.objectContaining({
120
+ role: "tool",
121
+ toolCallId: "sandbox-id",
122
+ content: "UI generated",
123
+ }),
124
+ );
125
+ expect(run).not.toHaveBeenCalled();
126
+ answer("approved");
127
+ await waitFor(() => expect(run).toHaveBeenCalledTimes(1));
128
+ expect(
129
+ submissions[0]?.filter((message) => message.role === "tool"),
130
+ ).toEqual([
131
+ expect.objectContaining({
132
+ toolCallId: "sandbox-id",
133
+ content: "UI generated",
134
+ }),
135
+ expect.objectContaining({
136
+ toolCallId: "approval-id",
137
+ content: "approved",
138
+ }),
139
+ ]);
140
+ expect(sideEffect).not.toHaveBeenCalled();
141
+ });
142
+
143
+ it("leaves saved sandbox and approval results completed across repeated reconnects", async () => {
144
+ const { core, agent, saved, run, approval, sideEffect } =
145
+ await setupReplay(true);
146
+ const tool = core.getTool({ toolName: "generateSandboxedUi" });
147
+ if (!tool?.handler) throw new Error("Missing sandbox handler");
148
+ const handler = vi.spyOn(tool, "handler");
149
+ await core.connectAgent({ agent });
150
+ await core.connectAgent({ agent });
151
+ expect(agent.messages).toEqual(saved);
152
+ expect(handler).not.toHaveBeenCalled();
153
+ expect(approval).not.toHaveBeenCalled();
154
+ expect(sideEffect).not.toHaveBeenCalled();
155
+ expect(run).not.toHaveBeenCalled();
156
+ });
157
+ });
@@ -5,6 +5,8 @@ export const CopilotChatDefaultLabels = {
5
5
  chatInputToolbarFinishTranscribeButtonLabel: "Finish",
6
6
  chatInputToolbarAddButtonLabel: "Add photos or files",
7
7
  chatInputToolbarToolsButtonLabel: "Tools",
8
+ chatInputToolbarSendButtonLabel: "Send message",
9
+ chatInputToolbarStopButtonLabel: "Stop",
8
10
  assistantMessageToolbarCopyCodeLabel: "Copy",
9
11
  assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
10
12
  assistantMessageToolbarCopyMessageLabel: "Copy",
@@ -14,10 +16,13 @@ export const CopilotChatDefaultLabels = {
14
16
  assistantMessageToolbarRegenerateLabel: "Regenerate",
15
17
  userMessageToolbarCopyMessageLabel: "Copy",
16
18
  userMessageToolbarEditMessageLabel: "Edit",
19
+ userMessageToolbarPreviousBranchLabel: "Previous branch",
20
+ userMessageToolbarNextBranchLabel: "Next branch",
17
21
  chatDisclaimerText:
18
22
  "AI can make mistakes. Please verify important information.",
19
23
  chatToggleOpenLabel: "Open chat",
20
24
  chatToggleCloseLabel: "Close chat",
25
+ chatScrollToBottomLabel: "Scroll to bottom",
21
26
  modalHeaderTitle: "CopilotKit Chat",
22
27
  welcomeMessageText: "How can I help you today?",
23
28
  } as const;