@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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const SAVE_SNIPPET_ICON_SLOT_PX = 36;
|
|
2
|
+
|
|
3
|
+
export const SAVE_SNIPPET_BESIDE_WRAP_CLASS =
|
|
4
|
+
"cpk:relative cpk:w-full cpk:overflow-visible";
|
|
5
|
+
|
|
6
|
+
export const SAVE_SNIPPET_BESIDE_BODY_CLASS = "cpk:w-full";
|
|
7
|
+
|
|
8
|
+
export const SAVE_SNIPPET_BESIDE_SAVE_CLASS = "cpk:absolute cpk:top-0 cpk:z-10";
|
|
9
|
+
|
|
10
|
+
export function saveSnippetBesideStyle(side: "left" | "right") {
|
|
11
|
+
if (side === "left") {
|
|
12
|
+
return {
|
|
13
|
+
left: "auto",
|
|
14
|
+
right: "100%",
|
|
15
|
+
marginLeft: "0px",
|
|
16
|
+
marginRight: "4px",
|
|
17
|
+
} as const;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
left: "100%",
|
|
21
|
+
right: "auto",
|
|
22
|
+
marginLeft: "4px",
|
|
23
|
+
marginRight: "0px",
|
|
24
|
+
} as const;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function pickSaveSnippetSide(
|
|
28
|
+
roomRight: number,
|
|
29
|
+
roomLeft: number,
|
|
30
|
+
slotPx = SAVE_SNIPPET_ICON_SLOT_PX,
|
|
31
|
+
): "left" | "right" {
|
|
32
|
+
if (roomRight >= slotPx) {
|
|
33
|
+
return "right";
|
|
34
|
+
}
|
|
35
|
+
if (roomLeft >= slotPx) {
|
|
36
|
+
return "left";
|
|
37
|
+
}
|
|
38
|
+
return "right";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function findOverflowAncestor(el: HTMLElement): HTMLElement {
|
|
42
|
+
let node: HTMLElement | null = el.parentElement;
|
|
43
|
+
while (node) {
|
|
44
|
+
const style = getComputedStyle(node);
|
|
45
|
+
const overflowX = style.overflowX;
|
|
46
|
+
const overflow = style.overflow;
|
|
47
|
+
if (
|
|
48
|
+
overflowX === "hidden" ||
|
|
49
|
+
overflowX === "auto" ||
|
|
50
|
+
overflowX === "scroll" ||
|
|
51
|
+
overflow === "hidden" ||
|
|
52
|
+
overflow === "auto" ||
|
|
53
|
+
overflow === "scroll"
|
|
54
|
+
) {
|
|
55
|
+
return node;
|
|
56
|
+
}
|
|
57
|
+
node = node.parentElement;
|
|
58
|
+
}
|
|
59
|
+
return document.documentElement;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function measurableBox(el: HTMLElement): HTMLElement {
|
|
63
|
+
let node: HTMLElement | null = el;
|
|
64
|
+
while (node && getComputedStyle(node).display === "contents") {
|
|
65
|
+
node = node.firstElementChild as HTMLElement | null;
|
|
66
|
+
}
|
|
67
|
+
return node ?? el;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function measureSaveSnippetSide(
|
|
71
|
+
wrap: HTMLElement,
|
|
72
|
+
body: HTMLElement,
|
|
73
|
+
): "left" | "right" {
|
|
74
|
+
const clip = findOverflowAncestor(wrap);
|
|
75
|
+
const clipRect = clip.getBoundingClientRect();
|
|
76
|
+
const box = measurableBox(
|
|
77
|
+
(body.firstElementChild as HTMLElement | null) ?? body,
|
|
78
|
+
);
|
|
79
|
+
const bodyRect = box.getBoundingClientRect();
|
|
80
|
+
return pickSaveSnippetSide(
|
|
81
|
+
clipRect.right - bodyRect.right,
|
|
82
|
+
bodyRect.left - clipRect.left,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { mount } from "@vue/test-utils";
|
|
3
3
|
import {
|
|
4
4
|
IconArrowUp,
|
|
5
|
+
IconBookmark,
|
|
5
6
|
IconCheck,
|
|
6
7
|
IconCheckCircle,
|
|
7
8
|
IconChevronDown,
|
|
@@ -31,6 +32,7 @@ describe("icons adapter", () => {
|
|
|
31
32
|
IconPlus,
|
|
32
33
|
IconMic,
|
|
33
34
|
IconArrowUp,
|
|
35
|
+
IconBookmark,
|
|
34
36
|
IconCheck,
|
|
35
37
|
IconSquare,
|
|
36
38
|
IconLoader2,
|
|
@@ -80,7 +80,7 @@ function bindThreadStoreSelector<T>(
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Vue composable for listing and managing Intelligence
|
|
83
|
+
* Vue composable for listing and managing CopilotKit Intelligence threads.
|
|
84
84
|
*
|
|
85
85
|
* The hook fetches threads for the runtime-authenticated user and the given
|
|
86
86
|
* `agentId`, then keeps the result in sync via the core thread store's realtime
|
|
@@ -20,7 +20,7 @@ export interface CopilotKitProviderProps {
|
|
|
20
20
|
publicApiKey?: string;
|
|
21
21
|
publicLicenseKey?: string;
|
|
22
22
|
/**
|
|
23
|
-
* Signed license token for offline verification of
|
|
23
|
+
* Signed license token for offline verification of CopilotKit Intelligence features.
|
|
24
24
|
* Obtain from https://dashboard.operations.copilotkit.ai.
|
|
25
25
|
*/
|
|
26
26
|
licenseToken?: string;
|
|
@@ -34,7 +34,8 @@ import {
|
|
|
34
34
|
MCPAppsActivityRenderer,
|
|
35
35
|
MCPAppsActivityType,
|
|
36
36
|
} from "../components/MCPAppsActivityRenderer";
|
|
37
|
-
import { CopilotKitKey, SandboxFunctionsKey } from "./keys";
|
|
37
|
+
import { CopilotKitKey, InspectorKey, SandboxFunctionsKey } from "./keys";
|
|
38
|
+
import type { VueInspectorOpenRequest, VueInspectorSaveRequest } from "./keys";
|
|
38
39
|
import {
|
|
39
40
|
LicenseContextKey,
|
|
40
41
|
createLicenseContextValue,
|
|
@@ -109,28 +110,86 @@ const props = withDefaults(defineProps<CopilotKitProviderProps>(), {
|
|
|
109
110
|
});
|
|
110
111
|
|
|
111
112
|
const shouldRenderInspector = ref(false);
|
|
113
|
+
// Mounting the overlay follows showDevConsole. The in-chat affordances are
|
|
114
|
+
// narrower: a dev build on localhost only, so `showDevConsole: true` on a
|
|
115
|
+
// staging URL never puts a bookmark into a production chat. Matches React.
|
|
116
|
+
const canShowLocalInspectorAction = ref(false);
|
|
112
117
|
|
|
113
118
|
const updateInspectorVisibility = () => {
|
|
119
|
+
const isLocalhost =
|
|
120
|
+
typeof window !== "undefined" &&
|
|
121
|
+
new Set(["localhost", "127.0.0.1"]).has(window.location?.hostname ?? "");
|
|
122
|
+
canShowLocalInspectorAction.value =
|
|
123
|
+
process.env.NODE_ENV === "development" && isLocalhost;
|
|
124
|
+
|
|
114
125
|
if (props.showDevConsole === true) {
|
|
115
126
|
shouldRenderInspector.value = true;
|
|
116
127
|
return;
|
|
117
128
|
}
|
|
118
129
|
if (props.showDevConsole === "auto") {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
130
|
+
shouldRenderInspector.value = isLocalhost;
|
|
131
|
+
if (!isLocalhost) {
|
|
132
|
+
canShowLocalInspectorAction.value = false;
|
|
122
133
|
}
|
|
123
|
-
const localhostHosts = new Set(["localhost", "127.0.0.1"]);
|
|
124
|
-
shouldRenderInspector.value = localhostHosts.has(window.location.hostname);
|
|
125
134
|
return;
|
|
126
135
|
}
|
|
127
136
|
shouldRenderInspector.value = false;
|
|
137
|
+
canShowLocalInspectorAction.value = false;
|
|
128
138
|
};
|
|
129
139
|
|
|
130
140
|
watch(() => props.showDevConsole, updateInspectorVisibility, {
|
|
131
141
|
immediate: true,
|
|
132
142
|
});
|
|
133
143
|
|
|
144
|
+
const inspectorOpenRequest = ref<VueInspectorOpenRequest | null>(null);
|
|
145
|
+
const isLocalInspectorEnabled = computed(
|
|
146
|
+
() => shouldRenderInspector.value && canShowLocalInspectorAction.value,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
function openInspector(request: VueInspectorOpenRequest) {
|
|
150
|
+
inspectorOpenRequest.value = { ...request };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function saveEventSnippet(request: VueInspectorSaveRequest) {
|
|
154
|
+
try {
|
|
155
|
+
const mod = await import("@copilotkit/web-inspector");
|
|
156
|
+
const threadId = request.threadId ?? "inspector-snippet";
|
|
157
|
+
const runId = `inspector-snippet-${Date.now()}`;
|
|
158
|
+
const compiled = mod.compileChatSnippet({
|
|
159
|
+
...request,
|
|
160
|
+
threadId,
|
|
161
|
+
runId,
|
|
162
|
+
});
|
|
163
|
+
const now = new Date().toISOString();
|
|
164
|
+
const snippet = {
|
|
165
|
+
id: crypto.randomUUID(),
|
|
166
|
+
name: compiled.name,
|
|
167
|
+
recipe: compiled.recipe,
|
|
168
|
+
events: compiled.events,
|
|
169
|
+
createdAt: now,
|
|
170
|
+
updatedAt: now,
|
|
171
|
+
};
|
|
172
|
+
mod.upsertEventSnippet(snippet);
|
|
173
|
+
openInspector({
|
|
174
|
+
messageId: request.messageId,
|
|
175
|
+
threadId: request.threadId,
|
|
176
|
+
agentId: request.agentId,
|
|
177
|
+
menu: "event-snippets",
|
|
178
|
+
snippetId: snippet.id,
|
|
179
|
+
});
|
|
180
|
+
} catch (error) {
|
|
181
|
+
// Compile can throw on bad args, and storage can throw QuotaExceededError.
|
|
182
|
+
// Callers fire this as `void saveEventSnippet(...)`, so report it here.
|
|
183
|
+
console.error("[CopilotKit] Could not save the event snippet.", error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
provide(InspectorKey, {
|
|
188
|
+
isLocalInspectorEnabled,
|
|
189
|
+
openInspector,
|
|
190
|
+
saveEventSnippet,
|
|
191
|
+
});
|
|
192
|
+
|
|
134
193
|
const initialFrontendTools = props.frontendTools;
|
|
135
194
|
const initialHumanInTheLoop = props.humanInTheLoop;
|
|
136
195
|
const initialRenderCustomMessages = props.renderCustomMessages;
|
|
@@ -657,6 +716,7 @@ const showExpiringBanner = computed(
|
|
|
657
716
|
v-if="shouldRenderInspector"
|
|
658
717
|
:core="copilotkit"
|
|
659
718
|
:default-anchor="props.inspectorDefaultAnchor"
|
|
719
|
+
:open-request="inspectorOpenRequest"
|
|
660
720
|
/>
|
|
661
721
|
<!-- License warnings — driven by server-reported status -->
|
|
662
722
|
<LicenseWarningBanner v-if="showNoLicenseBanner" type="no_license" />
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cleanup,
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
screen,
|
|
6
|
+
waitFor,
|
|
7
|
+
} from "@testing-library/vue";
|
|
8
|
+
import { defineComponent } from "vue";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
10
|
+
import {
|
|
11
|
+
emitInspectorStopViewing,
|
|
12
|
+
emitInspectorViewThread,
|
|
13
|
+
onInspectorActiveThread,
|
|
14
|
+
onInspectorViewThreadResult,
|
|
15
|
+
} from "@copilotkit/core";
|
|
16
|
+
import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
|
|
17
|
+
import { MockStepwiseAgent } from "../../__tests__/utils/test-helpers";
|
|
18
|
+
import CopilotChat from "../../components/chat/CopilotChat.vue";
|
|
19
|
+
import CopilotKitProvider from "../CopilotKitProvider.vue";
|
|
20
|
+
import CopilotChatConfigurationProvider from "../CopilotChatConfigurationProvider.vue";
|
|
21
|
+
import { useCopilotChatConfiguration } from "../useCopilotChatConfiguration";
|
|
22
|
+
|
|
23
|
+
class TrackingAgent extends MockStepwiseAgent {
|
|
24
|
+
connectCalls: string[] = [];
|
|
25
|
+
failThreadId: string | null = null;
|
|
26
|
+
|
|
27
|
+
async connectAgent(
|
|
28
|
+
_params: unknown,
|
|
29
|
+
_subscriber: unknown,
|
|
30
|
+
): Promise<{ result: unknown; newMessages: [] }> {
|
|
31
|
+
this.connectCalls.push(this.threadId ?? "");
|
|
32
|
+
if (this.threadId === this.failThreadId) {
|
|
33
|
+
throw new Error("connect failed");
|
|
34
|
+
}
|
|
35
|
+
return { result: undefined, newMessages: [] };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ThreadControls = defineComponent({
|
|
40
|
+
setup() {
|
|
41
|
+
const configuration = useCopilotChatConfiguration();
|
|
42
|
+
return { configuration };
|
|
43
|
+
},
|
|
44
|
+
template: `
|
|
45
|
+
<button @click="configuration?.setActiveThreadId('app-picked')">select</button>
|
|
46
|
+
<button @click="configuration?.startNewThread()">new</button>
|
|
47
|
+
`,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function renderChat(
|
|
51
|
+
agent: TrackingAgent,
|
|
52
|
+
options: { threadId?: string; withControls?: boolean } = {},
|
|
53
|
+
) {
|
|
54
|
+
agent.agentId = DEFAULT_AGENT_ID;
|
|
55
|
+
const Root = defineComponent({
|
|
56
|
+
components: {
|
|
57
|
+
CopilotChat,
|
|
58
|
+
CopilotChatConfigurationProvider,
|
|
59
|
+
CopilotKitProvider,
|
|
60
|
+
ThreadControls,
|
|
61
|
+
},
|
|
62
|
+
setup() {
|
|
63
|
+
return {
|
|
64
|
+
agents: { [DEFAULT_AGENT_ID]: agent },
|
|
65
|
+
threadId: options.threadId,
|
|
66
|
+
withControls: options.withControls ?? false,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
template: `
|
|
70
|
+
<CopilotKitProvider :agents__unsafe_dev_only="agents">
|
|
71
|
+
<CopilotChatConfigurationProvider v-if="withControls" :agent-id="'default'">
|
|
72
|
+
<CopilotChat :thread-id="threadId" />
|
|
73
|
+
<ThreadControls />
|
|
74
|
+
</CopilotChatConfigurationProvider>
|
|
75
|
+
<CopilotChat v-else :thread-id="threadId" />
|
|
76
|
+
</CopilotKitProvider>
|
|
77
|
+
`,
|
|
78
|
+
});
|
|
79
|
+
return render(Root);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function viewRequest(requestId: string, threadId = "saved-thread") {
|
|
83
|
+
return emitInspectorViewThread({
|
|
84
|
+
requestId,
|
|
85
|
+
threadId,
|
|
86
|
+
agentId: DEFAULT_AGENT_ID,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe("inspector thread override", () => {
|
|
91
|
+
const cleanups: Array<() => void> = [];
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
cleanups.length = 0;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterEach(() => {
|
|
98
|
+
for (const unsubscribe of cleanups.splice(0)) unsubscribe();
|
|
99
|
+
cleanup();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("switches a pinned threadId when view-thread matches the agent", async () => {
|
|
103
|
+
const agent = new TrackingAgent();
|
|
104
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
105
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
106
|
+
|
|
107
|
+
expect(viewRequest("request-1")).toBe(true);
|
|
108
|
+
|
|
109
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
110
|
+
expect(agent.connectCalls).toContain("saved-thread");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("ignores view-thread for a different agent", async () => {
|
|
114
|
+
const agent = new TrackingAgent();
|
|
115
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
116
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
117
|
+
|
|
118
|
+
expect(
|
|
119
|
+
emitInspectorViewThread({
|
|
120
|
+
requestId: "request-2",
|
|
121
|
+
threadId: "other-thread",
|
|
122
|
+
agentId: "other-agent",
|
|
123
|
+
}),
|
|
124
|
+
).toBe(false);
|
|
125
|
+
|
|
126
|
+
expect(agent.threadId).toBe("pinned-thread");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("restores the previous thread on stop-viewing", async () => {
|
|
130
|
+
const agent = new TrackingAgent();
|
|
131
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
132
|
+
viewRequest("request-3");
|
|
133
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
134
|
+
|
|
135
|
+
emitInspectorStopViewing({
|
|
136
|
+
requestId: "request-3",
|
|
137
|
+
agentId: DEFAULT_AGENT_ID,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("keeps the original previous thread after a second view-thread", async () => {
|
|
144
|
+
const agent = new TrackingAgent();
|
|
145
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
146
|
+
viewRequest("request-4a", "first-saved");
|
|
147
|
+
await waitFor(() => expect(agent.threadId).toBe("first-saved"));
|
|
148
|
+
viewRequest("request-4b", "second-saved");
|
|
149
|
+
await waitFor(() => expect(agent.threadId).toBe("second-saved"));
|
|
150
|
+
|
|
151
|
+
emitInspectorStopViewing({
|
|
152
|
+
requestId: "request-4b",
|
|
153
|
+
agentId: DEFAULT_AGENT_ID,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("ends the override when the app picks a thread", async () => {
|
|
160
|
+
const agent = new TrackingAgent();
|
|
161
|
+
renderChat(agent, { withControls: true });
|
|
162
|
+
viewRequest("request-5");
|
|
163
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
164
|
+
|
|
165
|
+
await fireEvent.click(screen.getByText("select"));
|
|
166
|
+
|
|
167
|
+
await waitFor(() => expect(agent.threadId).toBe("app-picked"));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("ends the override when the app starts a new thread", async () => {
|
|
171
|
+
const agent = new TrackingAgent();
|
|
172
|
+
renderChat(agent, { withControls: true });
|
|
173
|
+
viewRequest("request-6");
|
|
174
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
175
|
+
|
|
176
|
+
await fireEvent.click(screen.getByText("new"));
|
|
177
|
+
|
|
178
|
+
await waitFor(() => expect(agent.threadId).not.toBe("saved-thread"));
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("rolls back when connect fails", async () => {
|
|
182
|
+
const agent = new TrackingAgent();
|
|
183
|
+
agent.failThreadId = "saved-thread";
|
|
184
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
185
|
+
const results: boolean[] = [];
|
|
186
|
+
cleanups.push(
|
|
187
|
+
onInspectorViewThreadResult((payload) => {
|
|
188
|
+
if (payload.requestId === "request-7") results.push(payload.ok);
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
viewRequest("request-7");
|
|
193
|
+
|
|
194
|
+
await waitFor(() => expect(results).toContain(false));
|
|
195
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("emits applied result and active-thread on view", async () => {
|
|
199
|
+
const agent = new TrackingAgent();
|
|
200
|
+
const results: boolean[] = [];
|
|
201
|
+
const activeSources: string[] = [];
|
|
202
|
+
cleanups.push(
|
|
203
|
+
onInspectorViewThreadResult((payload) => results.push(payload.ok)),
|
|
204
|
+
onInspectorActiveThread((payload) => activeSources.push(payload.source)),
|
|
205
|
+
);
|
|
206
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
207
|
+
|
|
208
|
+
expect(viewRequest("request-8")).toBe(true);
|
|
209
|
+
|
|
210
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
211
|
+
expect(results).toContain(true);
|
|
212
|
+
expect(activeSources).toContain("override");
|
|
213
|
+
});
|
|
214
|
+
});
|
package/src/v2/providers/keys.ts
CHANGED
|
@@ -23,3 +23,50 @@ export const CopilotChatConfigurationKey: InjectionKey<
|
|
|
23
23
|
export const SandboxFunctionsKey: InjectionKey<
|
|
24
24
|
Ref<readonly SandboxFunction[]>
|
|
25
25
|
> = Symbol("SandboxFunctions");
|
|
26
|
+
|
|
27
|
+
export type VueInspectorOpenRequest = {
|
|
28
|
+
messageId: string;
|
|
29
|
+
threadId?: string;
|
|
30
|
+
agentId?: string;
|
|
31
|
+
menu?: "event-snippets";
|
|
32
|
+
snippetId?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type VueInspectorSaveRequest = {
|
|
36
|
+
threadId?: string;
|
|
37
|
+
agentId?: string;
|
|
38
|
+
} & (
|
|
39
|
+
| {
|
|
40
|
+
kind: "text";
|
|
41
|
+
messageId: string;
|
|
42
|
+
content: string;
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
kind: "reasoning";
|
|
46
|
+
messageId: string;
|
|
47
|
+
content: string;
|
|
48
|
+
}
|
|
49
|
+
| {
|
|
50
|
+
kind: "tool-call";
|
|
51
|
+
messageId: string;
|
|
52
|
+
toolCallId: string;
|
|
53
|
+
toolName: string;
|
|
54
|
+
argsJson: string | Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
kind: "activity";
|
|
58
|
+
messageId: string;
|
|
59
|
+
activityType: string;
|
|
60
|
+
content: unknown;
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
export type VueInspectorContextValue = {
|
|
65
|
+
isLocalInspectorEnabled: ComputedRef<boolean>;
|
|
66
|
+
openInspector: (request: VueInspectorOpenRequest) => void;
|
|
67
|
+
saveEventSnippet: (request: VueInspectorSaveRequest) => Promise<void>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const InspectorKey: InjectionKey<VueInspectorContextValue> = Symbol(
|
|
71
|
+
"CopilotKitInspector",
|
|
72
|
+
);
|
|
@@ -8,6 +8,9 @@ export const CopilotChatDefaultLabels = {
|
|
|
8
8
|
assistantMessageToolbarCopyCodeLabel: "Copy",
|
|
9
9
|
assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
|
|
10
10
|
assistantMessageToolbarCopyMessageLabel: "Copy",
|
|
11
|
+
assistantMessageToolbarInspectorLabel: "View in Inspector",
|
|
12
|
+
assistantMessageToolbarInspectorLocalOnlyLabel: "Local Only",
|
|
13
|
+
assistantMessageToolbarSaveSnippetLabel: "Save as snippet",
|
|
11
14
|
assistantMessageToolbarThumbsUpLabel: "Good response",
|
|
12
15
|
assistantMessageToolbarThumbsDownLabel: "Bad response",
|
|
13
16
|
assistantMessageToolbarReadAloudLabel: "Read aloud",
|