@copilotkit/vue 1.68.3-canary.1787220377 → 1.69.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 (24) hide show
  1. package/PARITY.md +1 -1
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.mjs +2 -2
  4. package/dist/{use-render-activity-message-DljRBrXN.js → use-render-activity-message-BQADVWev.js} +2242 -2193
  5. package/dist/use-render-activity-message-BQADVWev.js.map +1 -0
  6. package/dist/{use-render-activity-message-bkrPa-92.cjs → use-render-activity-message-DYb_lsQs.cjs} +22 -22
  7. package/dist/use-render-activity-message-DYb_lsQs.cjs.map +1 -0
  8. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts +16 -40
  9. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
  10. package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts +21 -0
  11. package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts.map +1 -0
  12. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -1
  13. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts +10 -0
  14. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -0
  15. package/dist/v2/index.cjs +1 -1
  16. package/dist/v2/index.mjs +1 -1
  17. package/package.json +6 -6
  18. package/src/v2/components/chat/CopilotChatToolCallItem.vue +86 -0
  19. package/src/v2/components/chat/CopilotChatToolCallsView.vue +24 -136
  20. package/src/v2/components/chat/__tests__/CopilotChatToolCallsView.test.ts +302 -1
  21. package/src/v2/components/chat/__tests__/CopilotThreadsDrawer.ssr.test.ts +1 -1
  22. package/src/v2/components/chat/copilotChatToolCallItemRender.ts +126 -0
  23. package/dist/use-render-activity-message-DljRBrXN.js.map +0 -1
  24. package/dist/use-render-activity-message-bkrPa-92.cjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { defineComponent, h } from "vue";
2
+ import { defineComponent, h, nextTick, ref } from "vue";
3
3
  import { mount } from "@vue/test-utils";
4
4
  import type { AssistantMessage, Message, ToolMessage } from "@ag-ui/core";
5
5
  import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue";
@@ -252,6 +252,307 @@ describe("CopilotChatToolCallsView", () => {
252
252
  );
253
253
  });
254
254
 
255
+ it("updates generic consumer-provided tool-call slots when parent state changes", async () => {
256
+ const message = baseAssistantMessage();
257
+ const label = ref("first");
258
+ const Harness = defineComponent({
259
+ setup() {
260
+ return () => {
261
+ const currentLabel = label.value;
262
+ return h(
263
+ CopilotChatToolCallsView,
264
+ { message, messages: [message] },
265
+ {
266
+ "tool-call": () =>
267
+ h("div", { "data-testid": "slot-label" }, currentLabel),
268
+ },
269
+ );
270
+ };
271
+ },
272
+ });
273
+
274
+ const wrapper = mount(CopilotKitProvider, {
275
+ props: { runtimeUrl: "/api/copilotkit" },
276
+ slots: {
277
+ default: () =>
278
+ h(
279
+ CopilotChatConfigurationProvider,
280
+ { threadId: "thread-1", agentId: "default" },
281
+ { default: () => h(Harness) },
282
+ ),
283
+ },
284
+ });
285
+
286
+ expect(wrapper.get("[data-testid='slot-label']").text()).toBe("first");
287
+ label.value = "second";
288
+ await nextTick();
289
+ expect(wrapper.get("[data-testid='slot-label']").text()).toBe("second");
290
+ });
291
+
292
+ it("updates exact consumer-provided tool-call slots when parent state changes", async () => {
293
+ const message = baseAssistantMessage();
294
+ const label = ref("first");
295
+ const Harness = defineComponent({
296
+ setup() {
297
+ return () => {
298
+ const currentLabel = label.value;
299
+ return h(
300
+ CopilotChatToolCallsView,
301
+ { message, messages: [message] },
302
+ {
303
+ "tool-call-search_docs": () =>
304
+ h("div", { "data-testid": "slot-label" }, currentLabel),
305
+ },
306
+ );
307
+ };
308
+ },
309
+ });
310
+
311
+ const wrapper = mount(CopilotKitProvider, {
312
+ props: { runtimeUrl: "/api/copilotkit" },
313
+ slots: {
314
+ default: () =>
315
+ h(
316
+ CopilotChatConfigurationProvider,
317
+ { threadId: "thread-1", agentId: "default" },
318
+ { default: () => h(Harness) },
319
+ ),
320
+ },
321
+ });
322
+
323
+ expect(wrapper.get("[data-testid='slot-label']").text()).toBe("first");
324
+ label.value = "second";
325
+ await nextTick();
326
+ expect(wrapper.get("[data-testid='slot-label']").text()).toBe("second");
327
+ });
328
+
329
+ it("forwards a consumer-provided tool-call slot when slot membership changes", async () => {
330
+ const message = baseAssistantMessage();
331
+ const showCustom = ref(false);
332
+ const Harness = defineComponent({
333
+ components: { CopilotChatToolCallsView },
334
+ setup() {
335
+ return { message, showCustom };
336
+ },
337
+ template: `
338
+ <CopilotChatToolCallsView :message="message" :messages="[message]">
339
+ <template v-if="showCustom" #tool-call-search_docs>
340
+ <div data-testid="custom-tool-slot">CUSTOM</div>
341
+ </template>
342
+ </CopilotChatToolCallsView>
343
+ `,
344
+ });
345
+
346
+ const wrapper = mount(CopilotKitProvider, {
347
+ props: { runtimeUrl: "/api/copilotkit" },
348
+ slots: {
349
+ default: () =>
350
+ h(
351
+ CopilotChatConfigurationProvider,
352
+ { threadId: "thread-1", agentId: "default" },
353
+ { default: () => h(Harness) },
354
+ ),
355
+ },
356
+ });
357
+
358
+ expect(wrapper.find("[data-testid='custom-tool-slot']").exists()).toBe(
359
+ false,
360
+ );
361
+
362
+ showCustom.value = true;
363
+ await nextTick();
364
+ expect(wrapper.find("[data-testid='custom-tool-slot']").text()).toBe(
365
+ "CUSTOM",
366
+ );
367
+
368
+ showCustom.value = false;
369
+ await nextTick();
370
+ expect(wrapper.find("[data-testid='custom-tool-slot']").exists()).toBe(
371
+ false,
372
+ );
373
+ });
374
+
375
+ it("skips unchanged fallback renderers when an unrelated parent value changes", async () => {
376
+ const message = baseAssistantMessage();
377
+ const label = ref("first");
378
+ let renderCount = 0;
379
+ const ToolRenderer = defineComponent({
380
+ setup() {
381
+ return () => {
382
+ renderCount += 1;
383
+ return h("div", { "data-testid": "core-render-tool" }, "rendered");
384
+ };
385
+ },
386
+ });
387
+ const RegisterRenderer = defineComponent({
388
+ setup() {
389
+ const { copilotkit } = useCopilotKit();
390
+ copilotkit.value.setRenderToolCalls([
391
+ defineToolCallRenderer({
392
+ name: "search_docs",
393
+ args: undefined,
394
+ render: ToolRenderer,
395
+ }),
396
+ ]);
397
+ return () => null;
398
+ },
399
+ });
400
+ const Harness = defineComponent({
401
+ setup() {
402
+ return () =>
403
+ h("div", [
404
+ h("span", { "data-testid": "unrelated-value" }, label.value),
405
+ h(RegisterRenderer),
406
+ h(CopilotChatToolCallsView, { message, messages: [message] }),
407
+ ]);
408
+ },
409
+ });
410
+
411
+ const wrapper = mount(CopilotKitProvider, {
412
+ props: { runtimeUrl: "/api/copilotkit" },
413
+ slots: {
414
+ default: () =>
415
+ h(
416
+ CopilotChatConfigurationProvider,
417
+ { threadId: "thread-1", agentId: "default" },
418
+ { default: () => h(Harness) },
419
+ ),
420
+ },
421
+ });
422
+
423
+ expect(renderCount).toBe(1);
424
+ label.value = "second";
425
+ await nextTick();
426
+ expect(wrapper.get("[data-testid='unrelated-value']").text()).toBe(
427
+ "second",
428
+ );
429
+ expect(renderCount).toBe(1);
430
+ });
431
+
432
+ it("updates a fallback renderer when the tool name changes", async () => {
433
+ const message = baseAssistantMessage();
434
+ const toolCall = message.toolCalls![0];
435
+ const currentMessage = ref(message);
436
+ const ToolRenderer = defineComponent({
437
+ props: {
438
+ name: { type: String, required: true },
439
+ },
440
+ template: `<div data-testid="fallback-tool-name">{{ name }}</div>`,
441
+ });
442
+ const RegisterRenderer = defineComponent({
443
+ setup() {
444
+ const { copilotkit } = useCopilotKit();
445
+ copilotkit.value.setRenderToolCalls([
446
+ defineToolCallRenderer({ name: "*", render: ToolRenderer }),
447
+ ]);
448
+ return () => null;
449
+ },
450
+ });
451
+ const Harness = defineComponent({
452
+ setup() {
453
+ return () =>
454
+ h("div", [
455
+ h(RegisterRenderer),
456
+ h(CopilotChatToolCallsView, {
457
+ message: currentMessage.value,
458
+ messages: [currentMessage.value],
459
+ }),
460
+ ]);
461
+ },
462
+ });
463
+
464
+ const wrapper = mount(CopilotKitProvider, {
465
+ props: { runtimeUrl: "/api/copilotkit" },
466
+ slots: {
467
+ default: () =>
468
+ h(
469
+ CopilotChatConfigurationProvider,
470
+ { threadId: "thread-1", agentId: "default" },
471
+ { default: () => h(Harness) },
472
+ ),
473
+ },
474
+ });
475
+
476
+ expect(wrapper.get("[data-testid='fallback-tool-name']").text()).toBe(
477
+ "search_docs",
478
+ );
479
+ currentMessage.value = {
480
+ ...message,
481
+ toolCalls: [
482
+ {
483
+ ...toolCall,
484
+ function: { ...toolCall.function, name: "other_tool" },
485
+ },
486
+ ],
487
+ };
488
+ await nextTick();
489
+ expect(wrapper.get("[data-testid='fallback-tool-name']").text()).toBe(
490
+ "other_tool",
491
+ );
492
+ });
493
+
494
+ it("updates a fallback renderer when agent-specific selection changes", async () => {
495
+ const message = baseAssistantMessage();
496
+ const agentId = ref("agent-a");
497
+ const AgentARenderer = defineComponent({
498
+ template: `<div data-testid="agent-renderer">agent-a</div>`,
499
+ });
500
+ const AgentBRenderer = defineComponent({
501
+ template: `<div data-testid="agent-renderer">agent-b</div>`,
502
+ });
503
+ const RegisterRenderer = defineComponent({
504
+ setup() {
505
+ const { copilotkit } = useCopilotKit();
506
+ copilotkit.value.setRenderToolCalls([
507
+ defineToolCallRenderer({
508
+ name: "search_docs",
509
+ agentId: "agent-a",
510
+ render: AgentARenderer,
511
+ }),
512
+ defineToolCallRenderer({
513
+ name: "search_docs",
514
+ agentId: "agent-b",
515
+ render: AgentBRenderer,
516
+ }),
517
+ ]);
518
+ return () => null;
519
+ },
520
+ });
521
+ const Harness = defineComponent({
522
+ setup() {
523
+ return () =>
524
+ h(
525
+ CopilotChatConfigurationProvider,
526
+ { threadId: "thread-1", agentId: agentId.value },
527
+ {
528
+ default: () =>
529
+ h("div", [
530
+ h(RegisterRenderer),
531
+ h(CopilotChatToolCallsView, {
532
+ message,
533
+ messages: [message],
534
+ }),
535
+ ]),
536
+ },
537
+ );
538
+ },
539
+ });
540
+
541
+ const wrapper = mount(CopilotKitProvider, {
542
+ props: { runtimeUrl: "/api/copilotkit" },
543
+ slots: { default: () => h(Harness) },
544
+ });
545
+
546
+ expect(wrapper.get("[data-testid='agent-renderer']").text()).toBe(
547
+ "agent-a",
548
+ );
549
+ agentId.value = "agent-b";
550
+ await nextTick();
551
+ expect(wrapper.get("[data-testid='agent-renderer']").text()).toBe(
552
+ "agent-b",
553
+ );
554
+ });
555
+
255
556
  it("prefers current-agent renderers over unscoped and wildcard renderers", () => {
256
557
  const message = baseAssistantMessage("search_docs");
257
558
  const RegisterRenderer = defineComponent({
@@ -21,5 +21,5 @@ describe("@copilotkit/vue SSR import safety", () => {
21
21
  const mod = await import("../../../../index");
22
22
  expect(mod.CopilotThreadsDrawer).toBeDefined();
23
23
  expect(evaluated.current).toBe(false);
24
- });
24
+ }, 20_000);
25
25
  });
@@ -0,0 +1,126 @@
1
+ import type { Message, ToolCall, ToolMessage } from "@ag-ui/core";
2
+ import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
3
+ import { ToolCallStatus } from "@copilotkit/core";
4
+ import { partialJSONParse } from "@copilotkit/shared";
5
+ import type { CopilotChatToolCallRenderSlotProps } from "./types";
6
+ import type {
7
+ VueToolCallRenderer,
8
+ VueToolCallRendererRenderProps,
9
+ } from "../../types";
10
+
11
+ export function findToolMessage(
12
+ messages: Message[],
13
+ toolCallId: string,
14
+ ): ToolMessage | undefined {
15
+ return messages.find(
16
+ (message) =>
17
+ message.role === "tool" &&
18
+ (message as ToolMessage).toolCallId === toolCallId,
19
+ ) as ToolMessage | undefined;
20
+ }
21
+
22
+ export function getToolCallSlotName(toolName: string): `tool-call-${string}` {
23
+ return `tool-call-${toolName}`;
24
+ }
25
+
26
+ export function getCoreRenderConfig(
27
+ toolCall: ToolCall,
28
+ renderToolCalls: readonly VueToolCallRenderer<unknown>[],
29
+ agentId: string,
30
+ ): VueToolCallRenderer<unknown> | undefined {
31
+ const exactMatches = renderToolCalls.filter(
32
+ (renderConfig) => renderConfig.name === toolCall.function.name,
33
+ );
34
+
35
+ return (
36
+ exactMatches.find((renderConfig) => renderConfig.agentId === agentId) ??
37
+ exactMatches.find((renderConfig) => !renderConfig.agentId) ??
38
+ exactMatches[0] ??
39
+ renderToolCalls.find((renderConfig) => renderConfig.name === "*")
40
+ );
41
+ }
42
+
43
+ export function getToolCallRenderProps(
44
+ toolCall: ToolCall,
45
+ messages: Message[],
46
+ executingToolCallIds: ReadonlySet<string>,
47
+ ): CopilotChatToolCallRenderSlotProps {
48
+ const toolMessage = findToolMessage(messages, toolCall.id);
49
+ const parsedArgs = partialJSONParse(toolCall.function.arguments);
50
+
51
+ if (toolMessage) {
52
+ return {
53
+ name: toolCall.function.name,
54
+ args: parsedArgs,
55
+ status: ToolCallStatus.Complete,
56
+ result: toolMessage.content,
57
+ toolCall,
58
+ toolMessage,
59
+ };
60
+ }
61
+
62
+ const isExecuting = executingToolCallIds.has(toolCall.id);
63
+ return {
64
+ name: toolCall.function.name,
65
+ args: parsedArgs,
66
+ status: isExecuting ? ToolCallStatus.Executing : ToolCallStatus.InProgress,
67
+ result: undefined,
68
+ toolCall,
69
+ toolMessage: undefined,
70
+ };
71
+ }
72
+
73
+ export function getCoreRenderProps(
74
+ toolCall: ToolCall,
75
+ messages: Message[],
76
+ executingToolCallIds: ReadonlySet<string>,
77
+ ): VueToolCallRendererRenderProps<unknown> {
78
+ const toolMessage = findToolMessage(messages, toolCall.id);
79
+ const parsedArgs = partialJSONParse(toolCall.function.arguments);
80
+
81
+ if (toolMessage) {
82
+ return {
83
+ name: toolCall.function.name,
84
+ toolCallId: toolCall.id,
85
+ args: parsedArgs,
86
+ status: ToolCallStatus.Complete,
87
+ result: toolMessage.content,
88
+ };
89
+ }
90
+
91
+ const isExecuting = executingToolCallIds.has(toolCall.id);
92
+ if (isExecuting) {
93
+ return {
94
+ name: toolCall.function.name,
95
+ toolCallId: toolCall.id,
96
+ args: parsedArgs,
97
+ status: ToolCallStatus.Executing,
98
+ result: undefined,
99
+ };
100
+ }
101
+
102
+ return {
103
+ name: toolCall.function.name,
104
+ toolCallId: toolCall.id,
105
+ args: parsedArgs && typeof parsedArgs === "object" ? parsedArgs : {},
106
+ status: ToolCallStatus.InProgress,
107
+ result: undefined,
108
+ };
109
+ }
110
+
111
+ export function getToolCallMemoDeps(
112
+ toolCall: ToolCall,
113
+ messages: Message[],
114
+ executingToolCallIds: ReadonlySet<string>,
115
+ renderToolCalls: readonly VueToolCallRenderer<unknown>[],
116
+ agentId: string = DEFAULT_AGENT_ID,
117
+ ): unknown[] {
118
+ return [
119
+ toolCall.id,
120
+ toolCall.function.name,
121
+ toolCall.function.arguments,
122
+ findToolMessage(messages, toolCall.id)?.content,
123
+ executingToolCallIds.has(toolCall.id),
124
+ getCoreRenderConfig(toolCall, renderToolCalls, agentId)?.render,
125
+ ];
126
+ }