@copilotkit/vue 1.68.3 → 1.69.1
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 -1
- package/README.md +1 -1
- package/dist/components/copilot-provider/types.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +2 -2
- package/dist/use-render-activity-message-BtaJ2iQc.cjs +97 -0
- package/dist/use-render-activity-message-BtaJ2iQc.cjs.map +1 -0
- package/dist/use-render-activity-message-CADUB--8.js +9943 -0
- package/dist/use-render-activity-message-CADUB--8.js.map +1 -0
- package/dist/v2/components/CopilotKitInspector.vue.d.ts +2 -0
- package/dist/v2/components/CopilotKitInspector.vue.d.ts.map +1 -1
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +1 -1
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +18 -0
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts +21 -0
- package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/SaveSnippetBeside.vue.d.ts +20 -0
- package/dist/v2/components/chat/SaveSnippetBeside.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts +10 -0
- package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -0
- package/dist/v2/components/chat/save-snippet-beside.d.ts +20 -0
- package/dist/v2/components/chat/save-snippet-beside.d.ts.map +1 -0
- package/dist/v2/components/icons/index.d.ts +1 -1
- package/dist/v2/components/icons/index.d.ts.map +1 -1
- package/dist/v2/hooks/use-threads.d.ts +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/providers/CopilotKitProvider.types.d.ts +1 -1
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
- package/dist/v2/providers/keys.d.ts +36 -0
- package/dist/v2/providers/keys.d.ts.map +1 -1
- package/dist/v2/providers/types.d.ts +3 -0
- package/dist/v2/providers/types.d.ts.map +1 -1
- package/dist/v2/providers/use-inspector-thread-override.d.ts +10 -0
- package/dist/v2/providers/use-inspector-thread-override.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/components/copilot-provider/types.ts +1 -1
- package/src/v2/components/CopilotKitInspector.vue +19 -1
- package/src/v2/components/OpenGenerativeUIRenderer.ts +44 -10
- package/src/v2/components/chat/CopilotChat.vue +64 -4
- package/src/v2/components/chat/CopilotChatAssistantMessage.vue +52 -1
- package/src/v2/components/chat/CopilotChatMessageView.vue +57 -23
- package/src/v2/components/chat/CopilotChatReasoningMessage.vue +68 -32
- package/src/v2/components/chat/CopilotChatToolCallItem.vue +86 -0
- package/src/v2/components/chat/CopilotChatToolCallsView.vue +72 -130
- package/src/v2/components/chat/SaveSnippetBeside.vue +82 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolCallsView.test.ts +302 -1
- package/src/v2/components/chat/__tests__/CopilotThreadsDrawer.ssr.test.ts +1 -1
- package/src/v2/components/chat/copilotChatToolCallItemRender.ts +126 -0
- package/src/v2/components/chat/save-snippet-beside.ts +84 -0
- package/src/v2/components/icons/__tests__/icons.test.ts +2 -0
- package/src/v2/components/icons/index.ts +1 -0
- package/src/v2/hooks/use-threads.ts +1 -1
- package/src/v2/providers/CopilotKitProvider.types.ts +1 -1
- package/src/v2/providers/CopilotKitProvider.vue +66 -6
- package/src/v2/providers/__tests__/inspector-thread-override.test.ts +214 -0
- package/src/v2/providers/keys.ts +47 -0
- package/src/v2/providers/types.ts +3 -0
- package/src/v2/providers/use-inspector-thread-override.ts +99 -0
- package/dist/use-render-activity-message-DljRBrXN.js +0 -9513
- package/dist/use-render-activity-message-DljRBrXN.js.map +0 -1
- package/dist/use-render-activity-message-bkrPa-92.cjs +0 -85
- package/dist/use-render-activity-message-bkrPa-92.cjs.map +0 -1
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
Message,
|
|
5
|
-
ToolCall,
|
|
6
|
-
ToolMessage,
|
|
7
|
-
} from "@ag-ui/core";
|
|
8
|
-
import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
|
|
9
|
-
import { ToolCallStatus } from "@copilotkit/core";
|
|
10
|
-
import { partialJSONParse } from "@copilotkit/shared";
|
|
11
|
-
import { useCopilotKit } from "../../providers/useCopilotKit";
|
|
12
|
-
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
2
|
+
import { inject, useSlots } from "vue";
|
|
3
|
+
import type { AssistantMessage, Message } from "@ag-ui/core";
|
|
13
4
|
import type { CopilotChatToolCallRenderSlotProps } from "./types";
|
|
14
|
-
import
|
|
15
|
-
import
|
|
5
|
+
import CopilotChatToolCallItem from "./CopilotChatToolCallItem.vue";
|
|
6
|
+
import SaveSnippetBeside from "./SaveSnippetBeside.vue";
|
|
7
|
+
import { InspectorKey } from "../../providers/keys";
|
|
8
|
+
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
9
|
+
import { CopilotChatDefaultLabels } from "../../providers/types";
|
|
10
|
+
import { IconBookmark } from "../icons";
|
|
16
11
|
|
|
17
12
|
const props = withDefaults(
|
|
18
13
|
defineProps<{
|
|
@@ -31,131 +26,78 @@ defineSlots<{
|
|
|
31
26
|
) => unknown;
|
|
32
27
|
}>();
|
|
33
28
|
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
(message as ToolMessage).toolCallId === toolCallId,
|
|
42
|
-
) as ToolMessage | undefined;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getSlotName(toolName: string): `tool-call-${string}` {
|
|
46
|
-
return `tool-call-${toolName}`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function isExecutingToolCall(toolCallId: string): boolean {
|
|
50
|
-
return executingToolCallIds.value.has(toolCallId);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function getToolResultContent(toolCallId: string): string | undefined {
|
|
54
|
-
return findToolMessage(toolCallId)?.content;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function getRenderProps(
|
|
58
|
-
toolCall: ToolCall,
|
|
59
|
-
): CopilotChatToolCallRenderSlotProps {
|
|
60
|
-
const toolMessage = findToolMessage(toolCall.id);
|
|
61
|
-
const parsedArgs = partialJSONParse(toolCall.function.arguments);
|
|
62
|
-
|
|
63
|
-
if (toolMessage) {
|
|
64
|
-
return {
|
|
65
|
-
name: toolCall.function.name,
|
|
66
|
-
args: parsedArgs,
|
|
67
|
-
status: ToolCallStatus.Complete,
|
|
68
|
-
result: toolMessage.content,
|
|
69
|
-
toolCall,
|
|
70
|
-
toolMessage,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const isExecuting = isExecutingToolCall(toolCall.id);
|
|
75
|
-
return {
|
|
76
|
-
name: toolCall.function.name,
|
|
77
|
-
args: parsedArgs,
|
|
78
|
-
status: isExecuting ? ToolCallStatus.Executing : ToolCallStatus.InProgress,
|
|
79
|
-
result: undefined,
|
|
80
|
-
toolCall,
|
|
81
|
-
toolMessage: undefined,
|
|
82
|
-
};
|
|
29
|
+
type ToolCallSlotName = "tool-call" | `tool-call-${string}`;
|
|
30
|
+
const componentSlots = useSlots() as Record<
|
|
31
|
+
ToolCallSlotName,
|
|
32
|
+
(props?: unknown) => unknown
|
|
33
|
+
>;
|
|
34
|
+
function getForwardedSlotNames(): ToolCallSlotName[] {
|
|
35
|
+
return Object.keys(componentSlots) as ToolCallSlotName[];
|
|
83
36
|
}
|
|
84
37
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
):
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (toolMessage) {
|
|
92
|
-
return {
|
|
93
|
-
name: toolCall.function.name,
|
|
94
|
-
toolCallId: toolCall.id,
|
|
95
|
-
args: parsedArgs,
|
|
96
|
-
status: ToolCallStatus.Complete,
|
|
97
|
-
result: toolMessage.content,
|
|
98
|
-
};
|
|
38
|
+
// A streaming tool call has truncated arguments. Do not offer to capture it
|
|
39
|
+
// until the JSON is complete, or the snippet holds a broken partial payload.
|
|
40
|
+
function hasCompleteArgs(args: string | undefined): boolean {
|
|
41
|
+
const trimmed = (args ?? "").trim();
|
|
42
|
+
if (!trimmed) {
|
|
43
|
+
return true;
|
|
99
44
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
toolCallId: toolCall.id,
|
|
106
|
-
args: parsedArgs,
|
|
107
|
-
status: ToolCallStatus.Executing,
|
|
108
|
-
result: undefined,
|
|
109
|
-
};
|
|
45
|
+
try {
|
|
46
|
+
JSON.parse(trimmed);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
110
50
|
}
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
name: toolCall.function.name,
|
|
114
|
-
toolCallId: toolCall.id,
|
|
115
|
-
args: parsedArgs && typeof parsedArgs === "object" ? parsedArgs : {},
|
|
116
|
-
status: ToolCallStatus.InProgress,
|
|
117
|
-
result: undefined,
|
|
118
|
-
};
|
|
119
51
|
}
|
|
120
52
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
53
|
+
const inspector = inject(InspectorKey, null);
|
|
54
|
+
const chatConfiguration = useCopilotChatConfiguration();
|
|
55
|
+
const canSave = (toolCall: { function: { arguments: string } }) =>
|
|
56
|
+
inspector?.isLocalInspectorEnabled.value === true &&
|
|
57
|
+
hasCompleteArgs(toolCall.function.arguments);
|
|
58
|
+
const saveLabel = () =>
|
|
59
|
+
chatConfiguration.value?.labels.assistantMessageToolbarSaveSnippetLabel ??
|
|
60
|
+
CopilotChatDefaultLabels.assistantMessageToolbarSaveSnippetLabel;
|
|
61
|
+
|
|
62
|
+
function saveToolCall(toolCall: {
|
|
63
|
+
id: string;
|
|
64
|
+
function: { name: string; arguments: string };
|
|
65
|
+
}) {
|
|
66
|
+
void inspector?.saveEventSnippet({
|
|
67
|
+
kind: "tool-call",
|
|
68
|
+
messageId: props.message.id,
|
|
69
|
+
toolCallId: toolCall.id,
|
|
70
|
+
toolName: toolCall.function.name,
|
|
71
|
+
argsJson: toolCall.function.arguments || "{}",
|
|
72
|
+
threadId: chatConfiguration.value?.threadId,
|
|
73
|
+
agentId: chatConfiguration.value?.agentId,
|
|
74
|
+
});
|
|
136
75
|
}
|
|
137
76
|
</script>
|
|
138
77
|
|
|
139
78
|
<template>
|
|
140
|
-
<
|
|
141
|
-
<
|
|
142
|
-
:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
v-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
79
|
+
<div v-for="toolCall in message.toolCalls ?? []" :key="toolCall.id">
|
|
80
|
+
<SaveSnippetBeside :enabled="canSave(toolCall)">
|
|
81
|
+
<CopilotChatToolCallItem :tool-call="toolCall" :messages="messages">
|
|
82
|
+
<template
|
|
83
|
+
v-for="slotName in getForwardedSlotNames()"
|
|
84
|
+
:key="slotName"
|
|
85
|
+
#[slotName]="slotProps"
|
|
86
|
+
>
|
|
87
|
+
<slot :name="slotName" v-bind="slotProps ?? {}" />
|
|
88
|
+
</template>
|
|
89
|
+
</CopilotChatToolCallItem>
|
|
90
|
+
<template #save>
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
class="cpk:inline-flex cpk:h-8 cpk:w-8 cpk:items-center cpk:justify-center cpk:rounded-md cpk:p-0 cpk:text-[rgb(93,93,93)] cpk:hover:bg-[#E8E8E8] cpk:dark:text-[rgb(243,243,243)] cpk:dark:hover:bg-[#303030]"
|
|
94
|
+
data-testid="copilot-tool-save-snippet-button"
|
|
95
|
+
:aria-label="`${saveLabel()} (Local Only)`"
|
|
96
|
+
@click="saveToolCall(toolCall)"
|
|
97
|
+
>
|
|
98
|
+
<IconBookmark class="cpk:size-[18px]" />
|
|
99
|
+
</button>
|
|
100
|
+
</template>
|
|
101
|
+
</SaveSnippetBeside>
|
|
102
|
+
</div>
|
|
161
103
|
</template>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
SAVE_SNIPPET_BESIDE_BODY_CLASS,
|
|
5
|
+
SAVE_SNIPPET_BESIDE_SAVE_CLASS,
|
|
6
|
+
SAVE_SNIPPET_BESIDE_WRAP_CLASS,
|
|
7
|
+
findOverflowAncestor,
|
|
8
|
+
measureSaveSnippetSide,
|
|
9
|
+
saveSnippetBesideStyle,
|
|
10
|
+
} from "./save-snippet-beside";
|
|
11
|
+
|
|
12
|
+
const props = defineProps<{
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
const wrapRef = ref<HTMLElement | null>(null);
|
|
17
|
+
const bodyRef = ref<HTMLElement | null>(null);
|
|
18
|
+
const side = ref<"left" | "right">("right");
|
|
19
|
+
let observer: ResizeObserver | undefined;
|
|
20
|
+
|
|
21
|
+
function measure() {
|
|
22
|
+
const wrap = wrapRef.value;
|
|
23
|
+
const body = bodyRef.value;
|
|
24
|
+
if (!props.enabled || !wrap || !body) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
side.value = measureSaveSnippetSide(wrap, body);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function bindObserver() {
|
|
31
|
+
observer?.disconnect();
|
|
32
|
+
const wrap = wrapRef.value;
|
|
33
|
+
const body = bodyRef.value;
|
|
34
|
+
if (!props.enabled || !wrap || !body) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
measure();
|
|
38
|
+
if (typeof ResizeObserver === "undefined") {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
observer = new ResizeObserver(measure);
|
|
42
|
+
observer.observe(wrap);
|
|
43
|
+
observer.observe(body);
|
|
44
|
+
const clip = findOverflowAncestor(wrap);
|
|
45
|
+
if (clip !== wrap) {
|
|
46
|
+
observer.observe(clip);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onMounted(() => {
|
|
51
|
+
watch([wrapRef, bodyRef, () => props.enabled], () => bindObserver(), {
|
|
52
|
+
flush: "post",
|
|
53
|
+
immediate: true,
|
|
54
|
+
});
|
|
55
|
+
window.addEventListener("resize", measure);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
onBeforeUnmount(() => {
|
|
59
|
+
observer?.disconnect();
|
|
60
|
+
window.removeEventListener("resize", measure);
|
|
61
|
+
});
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<slot v-if="!enabled" />
|
|
66
|
+
<div
|
|
67
|
+
v-else
|
|
68
|
+
ref="wrapRef"
|
|
69
|
+
:class="SAVE_SNIPPET_BESIDE_WRAP_CLASS"
|
|
70
|
+
:data-save-snippet-side="side"
|
|
71
|
+
>
|
|
72
|
+
<div ref="bodyRef" :class="SAVE_SNIPPET_BESIDE_BODY_CLASS">
|
|
73
|
+
<slot />
|
|
74
|
+
</div>
|
|
75
|
+
<div
|
|
76
|
+
:class="SAVE_SNIPPET_BESIDE_SAVE_CLASS"
|
|
77
|
+
:style="saveSnippetBesideStyle(side)"
|
|
78
|
+
>
|
|
79
|
+
<slot name="save" />
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</template>
|
|
@@ -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({
|