@copilotkit/vue 2.0.0-rc.7 → 2.0.0-rc.8

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 (124) hide show
  1. package/dist/index.cjs +1 -73
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.mjs +292 -16947
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/use-render-activity-message-C5LCG9a9.js +16451 -0
  6. package/dist/use-render-activity-message-C5LCG9a9.js.map +1 -0
  7. package/dist/use-render-activity-message-CPzguixR.cjs +74 -0
  8. package/dist/use-render-activity-message-CPzguixR.cjs.map +1 -0
  9. package/dist/v2/components/LicenseWarningBanner.vue.d.ts.map +1 -1
  10. package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
  11. package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -1
  12. package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts.map +1 -1
  13. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +6 -270
  14. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
  15. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts +1 -1
  16. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
  17. package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
  18. package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts +1 -1
  19. package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts.map +1 -1
  20. package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts.map +1 -1
  21. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts +1 -1
  22. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -1
  23. package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
  24. package/dist/v2/components/chat/CopilotChatWorkSummary.vue.d.ts.map +1 -1
  25. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts +0 -1
  26. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -1
  27. package/dist/v2/components/chat/index.d.ts +0 -1
  28. package/dist/v2/components/chat/index.d.ts.map +1 -1
  29. package/dist/v2/components/chat/tool-call-display-context.d.ts +2 -19
  30. package/dist/v2/components/chat/tool-call-display-context.d.ts.map +1 -1
  31. package/dist/v2/components/chat/tool-call-group-parts.d.ts +7 -20
  32. package/dist/v2/components/chat/tool-call-group-parts.d.ts.map +1 -1
  33. package/dist/v2/components/chat/turn-work.d.ts +0 -2
  34. package/dist/v2/components/chat/turn-work.d.ts.map +1 -1
  35. package/dist/v2/components/chat/types.d.ts +1 -2
  36. package/dist/v2/components/chat/types.d.ts.map +1 -1
  37. package/dist/v2/components/chat/use-chat-labels.d.ts +8 -0
  38. package/dist/v2/components/chat/use-chat-labels.d.ts.map +1 -0
  39. package/dist/v2/components/icons/index.d.ts +968 -1
  40. package/dist/v2/components/icons/index.d.ts.map +1 -1
  41. package/dist/v2/hooks/use-frontend-tool.d.ts +5 -0
  42. package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -1
  43. package/dist/v2/hooks/use-render-activity-message.d.ts +13 -9
  44. package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -1
  45. package/dist/v2/hooks/use-render-custom-messages.d.ts +5 -7
  46. package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -1
  47. package/dist/v2/index.cjs +1 -1
  48. package/dist/v2/index.mjs +196 -199
  49. package/dist/v2/index.mjs.map +1 -1
  50. package/dist/v2/lib/vue-core.d.ts +0 -1
  51. package/dist/v2/lib/vue-core.d.ts.map +1 -1
  52. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
  53. package/dist/v2/providers/types.d.ts +3 -46
  54. package/dist/v2/providers/types.d.ts.map +1 -1
  55. package/dist/v2/types/human-in-the-loop.d.ts +8 -29
  56. package/dist/v2/types/human-in-the-loop.d.ts.map +1 -1
  57. package/dist/v2/types/interrupt.d.ts +1 -22
  58. package/dist/v2/types/interrupt.d.ts.map +1 -1
  59. package/dist/v2/types/vue-activity-message-renderer.d.ts +16 -15
  60. package/dist/v2/types/vue-activity-message-renderer.d.ts.map +1 -1
  61. package/dist/v2/types/vue-custom-message-renderer.d.ts +7 -16
  62. package/dist/v2/types/vue-custom-message-renderer.d.ts.map +1 -1
  63. package/dist/v2/types/vue-tool-call-renderer.d.ts +16 -35
  64. package/dist/v2/types/vue-tool-call-renderer.d.ts.map +1 -1
  65. package/package.json +7 -8
  66. package/src/__tests__/build-entries.test.ts +27 -0
  67. package/src/v2/components/LicenseWarningBanner.vue +31 -15
  68. package/src/v2/components/__tests__/LicenseWarningBanner.test.ts +20 -0
  69. package/src/v2/components/chat/CopilotChat.vue +19 -40
  70. package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +14 -10
  71. package/src/v2/components/chat/CopilotChatAttachmentRenderer.vue +14 -6
  72. package/src/v2/components/chat/CopilotChatInput.vue +40 -30
  73. package/src/v2/components/chat/CopilotChatMessageView.vue +72 -140
  74. package/src/v2/components/chat/CopilotChatReasoningMessage.vue +31 -13
  75. package/src/v2/components/chat/CopilotChatToolCallGroup.vue +60 -37
  76. package/src/v2/components/chat/CopilotChatToolCallItem.vue +3 -3
  77. package/src/v2/components/chat/CopilotChatToolCallsView.vue +15 -13
  78. package/src/v2/components/chat/CopilotChatView.vue +5 -5
  79. package/src/v2/components/chat/CopilotChatWorkSummary.vue +44 -18
  80. package/src/v2/components/chat/__tests__/CopilotChat.labels.test.ts +19 -4
  81. package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +33 -1
  82. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.ts +247 -403
  83. package/src/v2/components/chat/__tests__/CopilotChatMessageView.toolCallDisplay.test.ts +1 -99
  84. package/src/v2/components/chat/copilotChatToolCallItemRender.ts +3 -19
  85. package/src/v2/components/chat/index.ts +0 -1
  86. package/src/v2/components/chat/tool-call-display-context.ts +2 -23
  87. package/src/v2/components/chat/tool-call-group-parts.ts +44 -85
  88. package/src/v2/components/chat/turn-work.ts +0 -9
  89. package/src/v2/components/chat/types.ts +4 -4
  90. package/src/v2/components/chat/use-chat-labels.ts +16 -0
  91. package/src/v2/components/icons/__tests__/icons.test.ts +72 -68
  92. package/src/v2/components/icons/index.ts +109 -27
  93. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +62 -1
  94. package/src/v2/hooks/__tests__/use-render-activity-message.test.ts +105 -0
  95. package/src/v2/hooks/__tests__/use-render-tool.test.ts +3 -6
  96. package/src/v2/hooks/__tests__/zod-regression.test.ts +4 -3
  97. package/src/v2/hooks/use-frontend-tool.ts +10 -10
  98. package/src/v2/hooks/use-render-activity-message.ts +27 -48
  99. package/src/v2/hooks/use-render-custom-messages.ts +14 -35
  100. package/src/v2/lib/vue-core.ts +3 -5
  101. package/src/v2/providers/CopilotKitProvider.vue +29 -80
  102. package/src/v2/providers/__tests__/CopilotKitProvider.headersAtSendTime.test.ts +3 -3
  103. package/src/v2/providers/__tests__/CopilotKitProvider.learning.test.ts +3 -1
  104. package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +17 -0
  105. package/src/v2/providers/types.ts +3 -49
  106. package/src/v2/types/__tests__/defineToolCallRenderer.test.ts +28 -33
  107. package/src/v2/types/defineToolCallRenderer.ts +5 -4
  108. package/src/v2/types/human-in-the-loop.ts +8 -32
  109. package/src/v2/types/interrupt.ts +7 -31
  110. package/src/v2/types/vue-activity-message-renderer.ts +24 -15
  111. package/src/v2/types/vue-custom-message-renderer.ts +12 -16
  112. package/src/v2/types/vue-tool-call-renderer.ts +23 -38
  113. package/vite.config.ts +1 -2
  114. package/dist/v2/components/chat/BasicMarkdown.vue.d.ts +0 -9
  115. package/dist/v2/components/chat/BasicMarkdown.vue.d.ts.map +0 -1
  116. package/dist/v2/components/chat/tool-call-layout.d.ts +0 -115
  117. package/dist/v2/components/chat/tool-call-layout.d.ts.map +0 -1
  118. package/dist/v2/lib/transcription-client.d.ts +0 -19
  119. package/dist/v2/lib/transcription-client.d.ts.map +0 -1
  120. package/src/v2/components/chat/BasicMarkdown.vue +0 -160
  121. package/src/v2/components/chat/__tests__/BasicMarkdown.test.ts +0 -69
  122. package/src/v2/components/chat/tool-call-layout.ts +0 -304
  123. package/src/v2/lib/__tests__/transcription-client.test.ts +0 -67
  124. package/src/v2/lib/transcription-client.ts +0 -151
@@ -1,188 +1,172 @@
1
- import { defineComponent } from "vue";
1
+ import { defineComponent, h } from "vue";
2
2
  import { render, screen } from "@testing-library/vue";
3
3
  import { describe, expect, it } from "vitest";
4
4
  import { z } from "zod";
5
- import type {
6
- ActivityMessage,
7
- AssistantMessage,
8
- Message,
9
- UserMessage,
10
- } from "@ag-ui/core";
5
+ import type { Message, ToolCall } from "@ag-ui/core";
11
6
  import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue";
12
7
  import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue";
13
8
  import CopilotChatMessageView from "../CopilotChatMessageView.vue";
9
+ import type { VueActivityMessageRenderer } from "../../../types";
10
+
11
+ const agentId = "default";
12
+ const threadId = "thread-test";
13
+
14
+ /** Slot templates that print what the view hands each message slot. */
15
+ const assistantContentSlot = `
16
+ <template #assistant-message="{ message }">
17
+ <div data-testid="assistant-content">{{ message.content }}</div>
18
+ </template>`;
19
+ const assistantToolCallsSlot = `
20
+ <template #assistant-message="{ message }">
21
+ <div data-testid="assistant-tool-calls-count">{{ message.toolCalls?.length ?? 0 }}</div>
22
+ <div data-testid="assistant-tool-call-args">{{ (message.toolCalls ?? []).map((call) => call.function.arguments).join("|") }}</div>
23
+ <div data-testid="assistant-content">{{ message.content }}</div>
24
+ </template>`;
25
+ const userContentSlot = `
26
+ <template #user-message="{ message }">
27
+ <div data-testid="user-content">{{ message.content }}</div>
28
+ </template>`;
29
+
30
+ /**
31
+ * Renders a message view inside the providers.
32
+ *
33
+ * @param options.slots - Slot templates placed inside the message view.
34
+ * @param options.renderActivityMessages - Activity renderers to register.
35
+ */
36
+ function renderMessageView(options: {
37
+ messages: Message[];
38
+ slots?: string;
39
+ renderActivityMessages?: VueActivityMessageRenderer<unknown>[];
40
+ }) {
41
+ const { messages, slots = "", renderActivityMessages = [] } = options;
42
+ const Host = defineComponent({
43
+ components: {
44
+ CopilotKitProvider,
45
+ CopilotChatConfigurationProvider,
46
+ CopilotChatMessageView,
47
+ },
48
+ setup() {
49
+ return { messages, agentId, threadId, renderActivityMessages };
50
+ },
51
+ template: `
52
+ <CopilotKitProvider
53
+ runtime-url="/api/copilotkit"
54
+ :render-activity-messages="renderActivityMessages"
55
+ >
56
+ <CopilotChatConfigurationProvider :agent-id="agentId" :thread-id="threadId">
57
+ <CopilotChatMessageView :messages="messages">${slots}</CopilotChatMessageView>
58
+ </CopilotChatConfigurationProvider>
59
+ </CopilotKitProvider>
60
+ `,
61
+ });
14
62
 
15
- describe("CopilotChatMessageView activity rendering", () => {
16
- const agentId = "default";
17
- const threadId = "thread-test";
18
-
19
- function renderMessageView({
20
- messages,
21
- hostTemplate = `<CopilotChatMessageView :messages="messages" />`,
22
- }: {
23
- messages: Message[];
24
- hostTemplate?: string;
25
- }) {
26
- const ActivityRenderer = defineComponent({
27
- name: "ActivityRenderer",
28
- props: {
29
- content: { type: Object, required: true },
30
- },
31
- setup(props) {
32
- return {
33
- percent: (props.content as { percent: number }).percent,
34
- };
35
- },
36
- template: `
37
- <div data-testid="activity-renderer">
38
- Progress: {{ percent }}%
39
- </div>
40
- `,
41
- });
42
-
43
- const Host = defineComponent({
44
- components: {
45
- CopilotKitProvider,
46
- CopilotChatConfigurationProvider,
47
- CopilotChatMessageView,
48
- ActivityRenderer,
49
- },
50
- setup() {
51
- return { messages, agentId, threadId };
52
- },
53
- template: `
54
- <CopilotKitProvider runtime-url="/api/copilotkit">
55
- <CopilotChatConfigurationProvider :agent-id="agentId" :thread-id="threadId">
56
- ${hostTemplate}
57
- </CopilotChatConfigurationProvider>
58
- </CopilotKitProvider>
59
- `,
60
- });
63
+ return render(Host);
64
+ }
61
65
 
62
- return render(Host);
63
- }
64
-
65
- it("renders activity messages via matching custom renderer", () => {
66
- const messages: Message[] = [
67
- {
68
- id: "act-1",
69
- role: "activity",
70
- activityType: "search-progress",
71
- content: { percent: 42 },
72
- } as ActivityMessage,
73
- ];
74
-
75
- const renderers = [
76
- {
77
- activityType: "search-progress",
78
- content: z.object({ percent: z.number() }),
79
- },
80
- ];
81
- expect(renderers).toHaveLength(1);
66
+ function toolCall(id: string, name: string, args = "{}"): ToolCall {
67
+ return { id, type: "function", function: { name, arguments: args } };
68
+ }
82
69
 
70
+ describe("CopilotChatMessageView activity rendering", () => {
71
+ const progress: Message = {
72
+ id: "act-1",
73
+ role: "activity",
74
+ activityType: "search-progress",
75
+ content: { percent: 42 },
76
+ };
77
+
78
+ /** A `"*"` renderer that prints the content it was given. */
79
+ const wildcardRenderer: VueActivityMessageRenderer<unknown> = {
80
+ activityType: "*",
81
+ content: z.object({ percent: z.number() }),
82
+ render: (props) =>
83
+ h(
84
+ "div",
85
+ { "data-testid": "wildcard-activity" },
86
+ JSON.stringify(props.content),
87
+ ),
88
+ };
89
+
90
+ it("renders activity messages through their activity-<type> slot", () => {
83
91
  renderMessageView({
84
- messages,
85
- hostTemplate: `
86
- <CopilotChatMessageView :messages="messages">
87
- <template #activity-search-progress="{ content }">
88
- <ActivityRenderer :content="content" />
89
- </template>
90
- </CopilotChatMessageView>
91
- `,
92
+ messages: [progress],
93
+ slots: `
94
+ <template #activity-search-progress="{ content }">
95
+ <div data-testid="activity-renderer">Progress: {{ content.percent }}%</div>
96
+ </template>`,
92
97
  });
93
98
 
94
99
  expect(screen.getByTestId("activity-renderer").textContent).toContain("42");
95
100
  });
96
101
 
97
102
  it("skips rendering when no activity renderer matches", () => {
98
- const messages: Message[] = [
99
- {
100
- id: "act-2",
101
- role: "activity",
102
- activityType: "unknown-type",
103
- content: { message: "should not render" },
104
- } as ActivityMessage,
105
- ];
103
+ renderMessageView({
104
+ messages: [
105
+ {
106
+ id: "act-2",
107
+ role: "activity",
108
+ activityType: "unknown-type",
109
+ content: { message: "should not render" },
110
+ },
111
+ ],
112
+ });
113
+
114
+ expect(screen.queryByText("should not render")).toBeNull();
115
+ });
106
116
 
117
+ it("falls back to the * renderer for a type with no renderer of its own", () => {
107
118
  renderMessageView({
108
- messages,
109
- hostTemplate: `<CopilotChatMessageView :messages="messages" />`,
119
+ messages: [progress],
120
+ renderActivityMessages: [wildcardRenderer],
110
121
  });
111
122
 
112
- expect(screen.queryByTestId("activity-renderer")).toBeNull();
123
+ expect(screen.getByTestId("wildcard-activity").textContent).toBe(
124
+ '{"percent":42}',
125
+ );
113
126
  });
114
- });
115
127
 
116
- describe("CopilotChatMessageView duplicate message deduplication", () => {
117
- const agentId = "default";
118
- const threadId = "thread-test";
119
-
120
- function renderMessageView({ messages }: { messages: Message[] }) {
121
- const Host = defineComponent({
122
- components: {
123
- CopilotKitProvider,
124
- CopilotChatConfigurationProvider,
125
- CopilotChatMessageView,
126
- },
127
- setup() {
128
- return { messages, agentId, threadId };
129
- },
130
- template: `
131
- <CopilotKitProvider runtime-url="/api/copilotkit">
132
- <CopilotChatConfigurationProvider :agent-id="agentId" :thread-id="threadId">
133
- <CopilotChatMessageView :messages="messages" />
134
- </CopilotChatConfigurationProvider>
135
- </CopilotKitProvider>
136
- `,
128
+ it("uses an activity-<type> slot before the * renderer", () => {
129
+ renderMessageView({
130
+ messages: [progress],
131
+ renderActivityMessages: [wildcardRenderer],
132
+ slots: `
133
+ <template #activity-search-progress>
134
+ <div data-testid="activity-slot">slot</div>
135
+ </template>`,
137
136
  });
138
137
 
139
- return render(Host);
140
- }
138
+ expect(screen.getByTestId("activity-slot")).toBeTruthy();
139
+ expect(screen.queryByTestId("wildcard-activity")).toBeNull();
140
+ });
141
+ });
141
142
 
143
+ describe("CopilotChatMessageView duplicate message deduplication", () => {
142
144
  it("preserves assistant text content when later duplicate has empty content (multi-tool-call scenario)", () => {
143
- const messages: Message[] = [
144
- {
145
- id: "user-1",
146
- role: "user",
147
- content: "Record a headache",
148
- } as UserMessage,
149
- {
150
- id: "assistant-1",
151
- role: "assistant",
152
- content: "Let me record that...",
153
- } as AssistantMessage,
154
- {
155
- id: "assistant-1",
156
- role: "assistant",
157
- content: "",
158
- toolCalls: [
159
- {
160
- id: "tc-1",
161
- type: "function",
162
- function: { name: "captureData", arguments: "{}" },
163
- },
164
- ],
165
- } as AssistantMessage,
166
- {
167
- id: "assistant-1",
168
- role: "assistant",
169
- content: "",
170
- toolCalls: [
171
- {
172
- id: "tc-1",
173
- type: "function",
174
- function: { name: "captureData", arguments: "{}" },
175
- },
176
- {
177
- id: "tc-2",
178
- type: "function",
179
- function: { name: "updateMemory", arguments: "{}" },
180
- },
181
- ],
182
- } as AssistantMessage,
183
- ];
184
-
185
- renderMessageView({ messages });
145
+ renderMessageView({
146
+ messages: [
147
+ { id: "user-1", role: "user", content: "Record a headache" },
148
+ {
149
+ id: "assistant-1",
150
+ role: "assistant",
151
+ content: "Let me record that...",
152
+ },
153
+ {
154
+ id: "assistant-1",
155
+ role: "assistant",
156
+ content: "",
157
+ toolCalls: [toolCall("tc-1", "captureData")],
158
+ },
159
+ {
160
+ id: "assistant-1",
161
+ role: "assistant",
162
+ content: "",
163
+ toolCalls: [
164
+ toolCall("tc-1", "captureData"),
165
+ toolCall("tc-2", "updateMemory"),
166
+ ],
167
+ },
168
+ ],
169
+ });
186
170
 
187
171
  const assistantMessages = screen.getAllByTestId(
188
172
  "copilot-assistant-message",
@@ -194,25 +178,21 @@ describe("CopilotChatMessageView duplicate message deduplication", () => {
194
178
  });
195
179
 
196
180
  it("uses latest content when all assistant duplicates have non-empty content", () => {
197
- const messages: Message[] = [
198
- {
199
- id: "user-1",
200
- role: "user",
201
- content: "Hello",
202
- } as UserMessage,
203
- {
204
- id: "assistant-1",
205
- role: "assistant",
206
- content: "Partial response...",
207
- } as AssistantMessage,
208
- {
209
- id: "assistant-1",
210
- role: "assistant",
211
- content: "Full response from the assistant.",
212
- } as AssistantMessage,
213
- ];
214
-
215
- renderMessageView({ messages });
181
+ renderMessageView({
182
+ messages: [
183
+ { id: "user-1", role: "user", content: "Hello" },
184
+ {
185
+ id: "assistant-1",
186
+ role: "assistant",
187
+ content: "Partial response...",
188
+ },
189
+ {
190
+ id: "assistant-1",
191
+ role: "assistant",
192
+ content: "Full response from the assistant.",
193
+ },
194
+ ],
195
+ });
216
196
 
217
197
  const assistantMessages = screen.getAllByTestId(
218
198
  "copilot-assistant-message",
@@ -221,100 +201,45 @@ describe("CopilotChatMessageView duplicate message deduplication", () => {
221
201
  expect(assistantMessages[0]?.textContent).toContain(
222
202
  "Full response from the assistant.",
223
203
  );
224
-
225
- const userMessages = screen.getAllByTestId("copilot-user-message");
226
- expect(userMessages).toHaveLength(1);
204
+ expect(screen.getAllByTestId("copilot-user-message")).toHaveLength(1);
227
205
  });
228
206
 
229
207
  it("uses later content when earlier content is empty", () => {
230
- const messages: Message[] = [
231
- {
232
- id: "assistant-1",
233
- role: "assistant",
234
- content: "",
235
- } as AssistantMessage,
236
- {
237
- id: "assistant-1",
238
- role: "assistant",
239
- content: "Here is the result.",
240
- } as AssistantMessage,
241
- ];
242
-
243
- const Host = defineComponent({
244
- components: {
245
- CopilotKitProvider,
246
- CopilotChatConfigurationProvider,
247
- CopilotChatMessageView,
248
- },
249
- setup() {
250
- return { messages, agentId, threadId };
251
- },
252
- template: `
253
- <CopilotKitProvider runtime-url="/api/copilotkit">
254
- <CopilotChatConfigurationProvider :agent-id="agentId" :thread-id="threadId">
255
- <CopilotChatMessageView :messages="messages">
256
- <template #assistant-message="{ message }">
257
- <div data-testid="assistant-content">{{ message.content }}</div>
258
- </template>
259
- </CopilotChatMessageView>
260
- </CopilotChatConfigurationProvider>
261
- </CopilotKitProvider>
262
- `,
208
+ renderMessageView({
209
+ messages: [
210
+ { id: "assistant-1", role: "assistant", content: "" },
211
+ {
212
+ id: "assistant-1",
213
+ role: "assistant",
214
+ content: "Here is the result.",
215
+ },
216
+ ],
217
+ slots: assistantContentSlot,
263
218
  });
264
219
 
265
- render(Host);
266
-
267
220
  expect(screen.getByTestId("assistant-content").textContent).toContain(
268
221
  "Here is the result.",
269
222
  );
270
223
  });
271
224
 
272
225
  it("recovers toolCalls when later duplicate has content but undefined toolCalls", () => {
273
- const messages: Message[] = [
274
- {
275
- id: "assistant-1",
276
- role: "assistant",
277
- content: "",
278
- toolCalls: [
279
- {
280
- id: "tc-1",
281
- type: "function",
282
- function: { name: "captureData", arguments: "{}" },
283
- },
284
- ],
285
- } as AssistantMessage,
286
- {
287
- id: "assistant-1",
288
- role: "assistant",
289
- content: "Here is the result.",
290
- } as AssistantMessage,
291
- ];
292
-
293
- const Host = defineComponent({
294
- components: {
295
- CopilotKitProvider,
296
- CopilotChatConfigurationProvider,
297
- CopilotChatMessageView,
298
- },
299
- setup() {
300
- return { messages, agentId, threadId };
301
- },
302
- template: `
303
- <CopilotKitProvider runtime-url="/api/copilotkit">
304
- <CopilotChatConfigurationProvider :agent-id="agentId" :thread-id="threadId">
305
- <CopilotChatMessageView :messages="messages">
306
- <template #assistant-message="{ message }">
307
- <div data-testid="assistant-tool-calls-count">{{ message.toolCalls?.length ?? 0 }}</div>
308
- <div data-testid="assistant-content">{{ message.content }}</div>
309
- </template>
310
- </CopilotChatMessageView>
311
- </CopilotChatConfigurationProvider>
312
- </CopilotKitProvider>
313
- `,
226
+ renderMessageView({
227
+ messages: [
228
+ {
229
+ id: "assistant-1",
230
+ role: "assistant",
231
+ content: "",
232
+ toolCalls: [toolCall("tc-1", "captureData")],
233
+ },
234
+ {
235
+ id: "assistant-1",
236
+ role: "assistant",
237
+ content: "Here is the result.",
238
+ },
239
+ ],
240
+ slots: assistantToolCallsSlot,
314
241
  });
315
242
 
316
- render(Host);
317
-
318
243
  expect(screen.getByTestId("assistant-content").textContent).toContain(
319
244
  "Here is the result.",
320
245
  );
@@ -324,171 +249,90 @@ describe("CopilotChatMessageView duplicate message deduplication", () => {
324
249
  });
325
250
 
326
251
  it("keeps empty toolCalls array from later chunk", () => {
327
- const messages: Message[] = [
328
- {
329
- id: "assistant-1",
330
- role: "assistant",
331
- content: "",
332
- toolCalls: [
333
- {
334
- id: "tc-1",
335
- type: "function",
336
- function: { name: "captureData", arguments: "{}" },
337
- },
338
- ],
339
- } as AssistantMessage,
340
- {
341
- id: "assistant-1",
342
- role: "assistant",
343
- content: "Done.",
344
- toolCalls: [],
345
- } as AssistantMessage,
346
- ];
347
-
348
- const Host = defineComponent({
349
- components: {
350
- CopilotKitProvider,
351
- CopilotChatConfigurationProvider,
352
- CopilotChatMessageView,
353
- },
354
- setup() {
355
- return { messages, agentId, threadId };
356
- },
357
- template: `
358
- <CopilotKitProvider runtime-url="/api/copilotkit">
359
- <CopilotChatConfigurationProvider :agent-id="agentId" :thread-id="threadId">
360
- <CopilotChatMessageView :messages="messages">
361
- <template #assistant-message="{ message }">
362
- <div data-testid="assistant-tool-calls-count">{{ message.toolCalls?.length ?? 0 }}</div>
363
- </template>
364
- </CopilotChatMessageView>
365
- </CopilotChatConfigurationProvider>
366
- </CopilotKitProvider>
367
- `,
252
+ renderMessageView({
253
+ messages: [
254
+ {
255
+ id: "assistant-1",
256
+ role: "assistant",
257
+ content: "",
258
+ toolCalls: [toolCall("tc-1", "captureData")],
259
+ },
260
+ {
261
+ id: "assistant-1",
262
+ role: "assistant",
263
+ content: "Done.",
264
+ toolCalls: [],
265
+ },
266
+ ],
267
+ slots: assistantToolCallsSlot,
368
268
  });
369
269
 
370
- render(Host);
371
-
372
270
  expect(screen.getByTestId("assistant-tool-calls-count").textContent).toBe(
373
271
  "0",
374
272
  );
375
273
  });
376
274
 
377
- it("handles undefined content on both occurrences", () => {
378
- const messages: Message[] = [
379
- {
380
- id: "assistant-1",
381
- role: "assistant",
382
- content: undefined,
383
- } as AssistantMessage,
384
- {
385
- id: "assistant-1",
386
- role: "assistant",
387
- content: undefined,
388
- } as AssistantMessage,
389
- ];
390
-
391
- const Host = defineComponent({
392
- components: {
393
- CopilotKitProvider,
394
- CopilotChatConfigurationProvider,
395
- CopilotChatMessageView,
396
- },
397
- setup() {
398
- return { messages, agentId, threadId };
399
- },
400
- template: `
401
- <CopilotKitProvider runtime-url="/api/copilotkit">
402
- <CopilotChatConfigurationProvider :agent-id="agentId" :thread-id="threadId">
403
- <CopilotChatMessageView :messages="messages">
404
- <template #assistant-message="{ message }">
405
- <div data-testid="assistant-content">{{ message.content }}</div>
406
- </template>
407
- </CopilotChatMessageView>
408
- </CopilotChatConfigurationProvider>
409
- </CopilotKitProvider>
410
- `,
275
+ it("collapses a tool call that repeats in one message, keeping the copy with arguments", () => {
276
+ renderMessageView({
277
+ messages: [
278
+ {
279
+ id: "assistant-1",
280
+ role: "assistant",
281
+ content: "",
282
+ toolCalls: [
283
+ toolCall("tc-1", "getWeather", '{"city":"Paris"}'),
284
+ toolCall("tc-1", "getWeather", ""),
285
+ ],
286
+ },
287
+ ],
288
+ slots: assistantToolCallsSlot,
411
289
  });
412
290
 
413
- render(Host);
291
+ expect(screen.getByTestId("assistant-tool-calls-count").textContent).toBe(
292
+ "1",
293
+ );
294
+ expect(screen.getByTestId("assistant-tool-call-args").textContent).toBe(
295
+ '{"city":"Paris"}',
296
+ );
297
+ });
298
+
299
+ it("handles undefined content on both occurrences", () => {
300
+ renderMessageView({
301
+ messages: [
302
+ { id: "assistant-1", role: "assistant", content: undefined },
303
+ { id: "assistant-1", role: "assistant", content: undefined },
304
+ ],
305
+ slots: assistantContentSlot,
306
+ });
414
307
 
415
308
  expect(screen.getByTestId("assistant-content").textContent).toBe("");
416
309
  });
417
310
 
418
311
  it("keeps last entry for non-assistant roles", () => {
419
- const messages: Message[] = [
420
- {
421
- id: "user-1",
422
- role: "user",
423
- content: "Hello",
424
- } as UserMessage,
425
- {
426
- id: "user-1",
427
- role: "user",
428
- content: "Hello (updated)",
429
- } as UserMessage,
430
- ];
431
-
432
- const Host = defineComponent({
433
- components: {
434
- CopilotKitProvider,
435
- CopilotChatConfigurationProvider,
436
- CopilotChatMessageView,
437
- },
438
- setup() {
439
- return { messages, agentId, threadId };
440
- },
441
- template: `
442
- <CopilotKitProvider runtime-url="/api/copilotkit">
443
- <CopilotChatConfigurationProvider :agent-id="agentId" :thread-id="threadId">
444
- <CopilotChatMessageView :messages="messages">
445
- <template #user-message="{ message }">
446
- <div data-testid="user-content">{{ message.content }}</div>
447
- </template>
448
- </CopilotChatMessageView>
449
- </CopilotChatConfigurationProvider>
450
- </CopilotKitProvider>
451
- `,
312
+ renderMessageView({
313
+ messages: [
314
+ { id: "user-1", role: "user", content: "Hello" },
315
+ { id: "user-1", role: "user", content: "Hello (updated)" },
316
+ ],
317
+ slots: userContentSlot,
452
318
  });
453
319
 
454
- render(Host);
455
-
456
320
  expect(screen.getByTestId("user-content").textContent).toBe(
457
321
  "Hello (updated)",
458
322
  );
459
323
  });
460
324
 
461
325
  it("preserves order of unique messages", () => {
462
- const messages: Message[] = [
463
- {
464
- id: "user-1",
465
- role: "user",
466
- content: "First question",
467
- } as UserMessage,
468
- {
469
- id: "assistant-1",
470
- role: "assistant",
471
- content: "First answer",
472
- } as AssistantMessage,
473
- {
474
- id: "user-2",
475
- role: "user",
476
- content: "Second question",
477
- } as UserMessage,
478
- {
479
- id: "assistant-2",
480
- role: "assistant",
481
- content: "Second answer",
482
- } as AssistantMessage,
483
- ];
484
-
485
- renderMessageView({ messages });
486
-
487
- const userMessages = screen.getAllByTestId("copilot-user-message");
488
- const assistantMessages = screen.getAllByTestId(
489
- "copilot-assistant-message",
490
- );
491
- expect(userMessages).toHaveLength(2);
492
- expect(assistantMessages).toHaveLength(2);
326
+ renderMessageView({
327
+ messages: [
328
+ { id: "user-1", role: "user", content: "First question" },
329
+ { id: "assistant-1", role: "assistant", content: "First answer" },
330
+ { id: "user-2", role: "user", content: "Second question" },
331
+ { id: "assistant-2", role: "assistant", content: "Second answer" },
332
+ ],
333
+ });
334
+
335
+ expect(screen.getAllByTestId("copilot-user-message")).toHaveLength(2);
336
+ expect(screen.getAllByTestId("copilot-assistant-message")).toHaveLength(2);
493
337
  });
494
338
  });