@copilotkit/vue 1.72.1-canary.sp-longthread-perf → 1.73.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 +15 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +42 -41
- package/dist/index.mjs.map +1 -1
- package/dist/use-render-activity-message-6LwUTCNm.cjs +85 -0
- package/dist/use-render-activity-message-6LwUTCNm.cjs.map +1 -0
- package/dist/{use-render-activity-message-Ciee03Rx.js → use-render-activity-message-Di_L8HCv.js} +4501 -4400
- package/dist/use-render-activity-message-Di_L8HCv.js.map +1 -0
- package/dist/v2/components/LicenseWarningBanner.vue.d.ts +3 -5
- package/dist/v2/components/LicenseWarningBanner.vue.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 +4 -0
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +7 -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/CopilotChatUserMessage.vue.d.ts +2 -0
- package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts +8 -2
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopup.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts +7 -4
- package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebar.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts +8 -5
- package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/index.d.ts +50 -26
- package/dist/v2/components/chat/index.d.ts.map +1 -1
- package/dist/v2/components/chat/types.d.ts +44 -17
- package/dist/v2/components/chat/types.d.ts.map +1 -1
- package/dist/v2/hooks/use-human-in-the-loop.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/providers/CopilotKitProvider.types.d.ts +25 -1
- package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/v2/components/LicenseWarningBanner.vue +5 -17
- package/src/v2/components/__tests__/LicenseWarningBanner.test.ts +23 -0
- package/src/v2/components/chat/CopilotChat.vue +23 -22
- package/src/v2/components/chat/CopilotChatAssistantMessage.vue +12 -21
- package/src/v2/components/chat/CopilotChatInput.vue +32 -26
- package/src/v2/components/chat/CopilotChatMessageView.vue +29 -3
- package/src/v2/components/chat/CopilotChatUserMessage.vue +19 -19
- package/src/v2/components/chat/CopilotChatView.vue +31 -39
- package/src/v2/components/chat/CopilotPopup.vue +8 -10
- package/src/v2/components/chat/CopilotPopupView.vue +8 -18
- package/src/v2/components/chat/CopilotPopupViewInternal.vue +5 -19
- package/src/v2/components/chat/CopilotPopupWelcomeScreen.vue +36 -6
- package/src/v2/components/chat/CopilotSidebar.vue +8 -10
- package/src/v2/components/chat/CopilotSidebarView.vue +8 -18
- package/src/v2/components/chat/CopilotSidebarViewInternal.vue +5 -19
- package/src/v2/components/chat/CopilotSidebarWelcomeScreen.vue +36 -6
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.ts +156 -5
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +34 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.rowKeys.test.ts +140 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.test.ts +46 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.test.ts +118 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.test.ts +79 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.test.ts +93 -0
- package/src/v2/components/chat/types.ts +73 -18
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +32 -6
- package/src/v2/hooks/use-human-in-the-loop.ts +1 -0
- package/src/v2/providers/CopilotKitProvider.types.ts +28 -1
- package/src/v2/providers/CopilotKitProvider.vue +86 -6
- package/src/v2/providers/__tests__/CopilotKitProvider.hitl.e2e.test.ts +192 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.messageFilter.test.ts +159 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +275 -21
- package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.ts +19 -1
- package/dist/use-render-activity-message-Ciee03Rx.js.map +0 -1
- package/dist/use-render-activity-message-DXJDZGkW.cjs +0 -85
- package/dist/use-render-activity-message-DXJDZGkW.cjs.map +0 -1
|
@@ -157,6 +157,43 @@ describe("CopilotSidebarView", () => {
|
|
|
157
157
|
expect(onSelectSuggestion).toHaveBeenCalledTimes(1);
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
+
it("only exposes stop capability when an onStop callback is available", async () => {
|
|
161
|
+
const withoutStop = mountSidebarView(
|
|
162
|
+
{},
|
|
163
|
+
{
|
|
164
|
+
"welcome-screen": ({ canStop }: { canStop: boolean }) =>
|
|
165
|
+
h("div", { "data-testid": "sidebar-can-stop" }, String(canStop)),
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(withoutStop.get("[data-testid='sidebar-can-stop']").text()).toBe(
|
|
170
|
+
"false",
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const onStop = vi.fn();
|
|
174
|
+
const withStop = mountSidebarView(
|
|
175
|
+
{ onStop },
|
|
176
|
+
{
|
|
177
|
+
"welcome-screen": ({
|
|
178
|
+
canStop,
|
|
179
|
+
onStop: stop,
|
|
180
|
+
}: {
|
|
181
|
+
canStop: boolean;
|
|
182
|
+
onStop: () => void;
|
|
183
|
+
}) =>
|
|
184
|
+
h(
|
|
185
|
+
"button",
|
|
186
|
+
{ "data-testid": "sidebar-stop", onClick: stop },
|
|
187
|
+
String(canStop),
|
|
188
|
+
),
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(withStop.get("[data-testid='sidebar-stop']").text()).toBe("true");
|
|
193
|
+
await withStop.get("[data-testid='sidebar-stop']").trigger("click");
|
|
194
|
+
expect(onStop).toHaveBeenCalledTimes(1);
|
|
195
|
+
});
|
|
196
|
+
|
|
160
197
|
it("uses the default chat welcome screen and still forwards welcome sub-slots", () => {
|
|
161
198
|
const wrapper = mountSidebarView(
|
|
162
199
|
{
|
|
@@ -211,4 +248,60 @@ describe("CopilotSidebarView", () => {
|
|
|
211
248
|
"Hello",
|
|
212
249
|
);
|
|
213
250
|
});
|
|
251
|
+
|
|
252
|
+
it("exposes reactive capability flags from the namespaced WelcomeScreen input slot", async () => {
|
|
253
|
+
const wrapper = mount(CopilotSidebarView.WelcomeScreen, {
|
|
254
|
+
props: {
|
|
255
|
+
suggestions: [],
|
|
256
|
+
loadingIndexes: [],
|
|
257
|
+
modelValue: "",
|
|
258
|
+
isRunning: false,
|
|
259
|
+
inputMode: "input",
|
|
260
|
+
inputToolsMenu: [],
|
|
261
|
+
onUpdateModelValue: vi.fn(),
|
|
262
|
+
onSubmitMessage: vi.fn(),
|
|
263
|
+
onSelectSuggestion: vi.fn(),
|
|
264
|
+
},
|
|
265
|
+
slots: {
|
|
266
|
+
input: ({
|
|
267
|
+
canStop,
|
|
268
|
+
canAddFile,
|
|
269
|
+
canTranscribe,
|
|
270
|
+
}: {
|
|
271
|
+
canStop: boolean;
|
|
272
|
+
canAddFile: boolean;
|
|
273
|
+
canTranscribe: boolean;
|
|
274
|
+
}) =>
|
|
275
|
+
h(
|
|
276
|
+
"div",
|
|
277
|
+
{ "data-testid": "sidebar-welcome-capabilities" },
|
|
278
|
+
`${canStop}:${canAddFile}:${canTranscribe}`,
|
|
279
|
+
),
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
expect(
|
|
284
|
+
wrapper.get("[data-testid='sidebar-welcome-capabilities']").text(),
|
|
285
|
+
).toBe("false:false:false");
|
|
286
|
+
|
|
287
|
+
await wrapper.setProps({
|
|
288
|
+
onStop: vi.fn(),
|
|
289
|
+
onAddFile: vi.fn(),
|
|
290
|
+
onStartTranscribe: vi.fn(),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
expect(
|
|
294
|
+
wrapper.get("[data-testid='sidebar-welcome-capabilities']").text(),
|
|
295
|
+
).toBe("true:true:true");
|
|
296
|
+
|
|
297
|
+
await wrapper.setProps({
|
|
298
|
+
canStop: false,
|
|
299
|
+
canAddFile: false,
|
|
300
|
+
canTranscribe: false,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
expect(
|
|
304
|
+
wrapper.get("[data-testid='sidebar-welcome-capabilities']").text(),
|
|
305
|
+
).toBe("false:false:false");
|
|
306
|
+
});
|
|
214
307
|
});
|
|
@@ -57,6 +57,22 @@ export interface CopilotChatViewProps {
|
|
|
57
57
|
inputValue?: string;
|
|
58
58
|
inputMode?: CopilotChatInputMode;
|
|
59
59
|
inputToolsMenu?: (ToolsMenuItem | "-")[];
|
|
60
|
+
/** Optional event callbacks used to drive the default input controls. */
|
|
61
|
+
onSubmitMessage?: (value: string) => void | Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Presence drives reactive gating of the default stop control.
|
|
64
|
+
* Prefer `@stop` / `emit("stop")` for invocation; do not call this prop
|
|
65
|
+
* directly or the handler can fire twice.
|
|
66
|
+
*/
|
|
67
|
+
onStop?: () => void;
|
|
68
|
+
onInputChange?: (value: string) => void;
|
|
69
|
+
onSelectSuggestion?: (
|
|
70
|
+
suggestion: Suggestion,
|
|
71
|
+
index: number,
|
|
72
|
+
) => void | Promise<void>;
|
|
73
|
+
onStartTranscribe?: () => void;
|
|
74
|
+
onCancelTranscribe?: () => void;
|
|
75
|
+
onFinishTranscribe?: () => void;
|
|
60
76
|
/**
|
|
61
77
|
* When `true`, suppresses the welcome screen while a thread's initial
|
|
62
78
|
* connect is in flight. Prevents the "How can I help you today?" flash
|
|
@@ -93,6 +109,13 @@ export interface CopilotChatProps extends Omit<
|
|
|
93
109
|
| "onDragOver"
|
|
94
110
|
| "onDragLeave"
|
|
95
111
|
| "onDrop"
|
|
112
|
+
| "onSubmitMessage"
|
|
113
|
+
| "onStop"
|
|
114
|
+
| "onInputChange"
|
|
115
|
+
| "onSelectSuggestion"
|
|
116
|
+
| "onStartTranscribe"
|
|
117
|
+
| "onCancelTranscribe"
|
|
118
|
+
| "onFinishTranscribe"
|
|
96
119
|
> {
|
|
97
120
|
agentId?: string;
|
|
98
121
|
threadId?: string;
|
|
@@ -107,18 +130,21 @@ export interface CopilotChatProps extends Omit<
|
|
|
107
130
|
}
|
|
108
131
|
|
|
109
132
|
export interface CopilotChatViewOverrideSlotProps extends CopilotChatViewProps {
|
|
133
|
+
canStop: boolean;
|
|
134
|
+
canAddFile: boolean;
|
|
135
|
+
canTranscribe: boolean;
|
|
110
136
|
onSubmitMessage: (value: string) => void | Promise<void>;
|
|
111
|
-
onStop
|
|
137
|
+
onStop: () => void;
|
|
112
138
|
onInputChange: (value: string) => void;
|
|
113
139
|
onSelectSuggestion: (
|
|
114
140
|
suggestion: Suggestion,
|
|
115
141
|
index: number,
|
|
116
142
|
) => void | Promise<void>;
|
|
117
|
-
onAddFile
|
|
118
|
-
onStartTranscribe
|
|
119
|
-
onCancelTranscribe
|
|
120
|
-
onFinishTranscribe
|
|
121
|
-
onFinishTranscribeWithAudio
|
|
143
|
+
onAddFile: () => void;
|
|
144
|
+
onStartTranscribe: () => void;
|
|
145
|
+
onCancelTranscribe: () => void;
|
|
146
|
+
onFinishTranscribe: () => void;
|
|
147
|
+
onFinishTranscribeWithAudio: (audioBlob: Blob) => void | Promise<void>;
|
|
122
148
|
}
|
|
123
149
|
|
|
124
150
|
export interface CopilotChatMessageViewSlotProps {
|
|
@@ -154,9 +180,12 @@ export interface CopilotChatInputSlotProps {
|
|
|
154
180
|
isRunning: boolean;
|
|
155
181
|
inputMode: CopilotChatInputMode;
|
|
156
182
|
inputToolsMenu: (ToolsMenuItem | "-")[];
|
|
183
|
+
canStop: boolean;
|
|
184
|
+
canAddFile: boolean;
|
|
185
|
+
canTranscribe: boolean;
|
|
157
186
|
onUpdateModelValue: (value: string) => void;
|
|
158
187
|
onSubmitMessage: (value: string) => void;
|
|
159
|
-
onStop
|
|
188
|
+
onStop: () => void;
|
|
160
189
|
onAddFile: () => void;
|
|
161
190
|
onStartTranscribe: () => void;
|
|
162
191
|
onCancelTranscribe: () => void;
|
|
@@ -189,9 +218,12 @@ export interface CopilotChatWelcomeScreenSlotProps extends CopilotChatSuggestion
|
|
|
189
218
|
isRunning: boolean;
|
|
190
219
|
inputMode: CopilotChatInputMode;
|
|
191
220
|
inputToolsMenu: (ToolsMenuItem | "-")[];
|
|
221
|
+
canStop: boolean;
|
|
222
|
+
canAddFile: boolean;
|
|
223
|
+
canTranscribe: boolean;
|
|
192
224
|
onUpdateModelValue: (value: string) => void;
|
|
193
225
|
onSubmitMessage: (value: string) => void;
|
|
194
|
-
onStop
|
|
226
|
+
onStop: () => void;
|
|
195
227
|
onAddFile: () => void;
|
|
196
228
|
onStartTranscribe: () => void;
|
|
197
229
|
onCancelTranscribe: () => void;
|
|
@@ -402,14 +434,17 @@ export interface CopilotSidebarWelcomeScreenInputSlotProps {
|
|
|
402
434
|
isRunning: boolean;
|
|
403
435
|
inputMode: CopilotChatInputMode;
|
|
404
436
|
inputToolsMenu: (ToolsMenuItem | "-")[];
|
|
437
|
+
canStop: boolean;
|
|
438
|
+
canAddFile: boolean;
|
|
439
|
+
canTranscribe: boolean;
|
|
405
440
|
onUpdateModelValue: (value: string) => void;
|
|
406
441
|
onSubmitMessage: (value: string) => void;
|
|
407
|
-
onStop
|
|
408
|
-
onAddFile
|
|
409
|
-
onStartTranscribe
|
|
410
|
-
onCancelTranscribe
|
|
411
|
-
onFinishTranscribe
|
|
412
|
-
onFinishTranscribeWithAudio
|
|
442
|
+
onStop: () => void;
|
|
443
|
+
onAddFile: () => void;
|
|
444
|
+
onStartTranscribe: () => void;
|
|
445
|
+
onCancelTranscribe: () => void;
|
|
446
|
+
onFinishTranscribe: () => void;
|
|
447
|
+
onFinishTranscribeWithAudio: (audioBlob: Blob) => void | Promise<void>;
|
|
413
448
|
}
|
|
414
449
|
|
|
415
450
|
export interface CopilotSidebarWelcomeScreenSuggestionViewSlotProps {
|
|
@@ -423,8 +458,29 @@ export interface CopilotSidebarWelcomeScreenLayoutSlotProps
|
|
|
423
458
|
CopilotSidebarWelcomeScreenInputSlotProps,
|
|
424
459
|
CopilotSidebarWelcomeScreenSuggestionViewSlotProps {}
|
|
425
460
|
|
|
426
|
-
|
|
427
|
-
|
|
461
|
+
type WelcomeScreenCommandProps =
|
|
462
|
+
| "onStop"
|
|
463
|
+
| "onAddFile"
|
|
464
|
+
| "onStartTranscribe"
|
|
465
|
+
| "onCancelTranscribe"
|
|
466
|
+
| "onFinishTranscribe"
|
|
467
|
+
| "onFinishTranscribeWithAudio";
|
|
468
|
+
|
|
469
|
+
type WelcomeScreenDerivedCapabilityProps =
|
|
470
|
+
| "canStop"
|
|
471
|
+
| "canAddFile"
|
|
472
|
+
| "canTranscribe";
|
|
473
|
+
|
|
474
|
+
export type CopilotSidebarWelcomeScreenProps = Omit<
|
|
475
|
+
CopilotSidebarWelcomeScreenLayoutSlotProps,
|
|
476
|
+
WelcomeScreenCommandProps | WelcomeScreenDerivedCapabilityProps
|
|
477
|
+
> &
|
|
478
|
+
Partial<
|
|
479
|
+
Pick<
|
|
480
|
+
CopilotSidebarWelcomeScreenLayoutSlotProps,
|
|
481
|
+
WelcomeScreenCommandProps | WelcomeScreenDerivedCapabilityProps
|
|
482
|
+
>
|
|
483
|
+
>;
|
|
428
484
|
|
|
429
485
|
export interface CopilotSidebarViewHeaderSlotProps {
|
|
430
486
|
title: string;
|
|
@@ -458,8 +514,7 @@ export type CopilotPopupWelcomeScreenSuggestionViewSlotProps =
|
|
|
458
514
|
export type CopilotPopupWelcomeScreenLayoutSlotProps =
|
|
459
515
|
CopilotSidebarWelcomeScreenLayoutSlotProps;
|
|
460
516
|
|
|
461
|
-
export type CopilotPopupWelcomeScreenProps =
|
|
462
|
-
CopilotPopupWelcomeScreenLayoutSlotProps;
|
|
517
|
+
export type CopilotPopupWelcomeScreenProps = CopilotSidebarWelcomeScreenProps;
|
|
463
518
|
|
|
464
519
|
export interface CopilotPopupViewProps extends CopilotChatViewProps {
|
|
465
520
|
width?: number | string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineComponent, ref, watch } from "vue";
|
|
2
2
|
import type { PropType } from "vue";
|
|
3
3
|
import { screen, fireEvent, waitFor, cleanup } from "@testing-library/vue";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import type { AssistantMessage, Message } from "@ag-ui/core";
|
|
7
7
|
import { ToolCallStatus } from "@copilotkit/core";
|
|
@@ -1100,6 +1100,7 @@ describe("HITL Run Abort", () => {
|
|
|
1100
1100
|
describe("HITL Thread Reconnection Bug", () => {
|
|
1101
1101
|
it("should show executing status when reconnecting to thread with pending HITL", async () => {
|
|
1102
1102
|
const agent = new MockReconnectableAgent();
|
|
1103
|
+
const handlerStarted = vi.fn();
|
|
1103
1104
|
|
|
1104
1105
|
const HITLRenderer = defineComponent({
|
|
1105
1106
|
props: {
|
|
@@ -1116,7 +1117,7 @@ describe("HITL Thread Reconnection Bug", () => {
|
|
|
1116
1117
|
<div data-testid="hitl-tool">
|
|
1117
1118
|
<div data-testid="hitl-status">{{ status }}</div>
|
|
1118
1119
|
<div data-testid="hitl-action">{{ args.action ?? "no-action" }}</div>
|
|
1119
|
-
<button v-if="respond" data-testid="hitl-respond">Respond</button>
|
|
1120
|
+
<button v-if="respond" data-testid="hitl-respond" @click="respond('approved')">Respond</button>
|
|
1120
1121
|
</div>
|
|
1121
1122
|
`,
|
|
1122
1123
|
});
|
|
@@ -1130,6 +1131,19 @@ describe("HITL Thread Reconnection Bug", () => {
|
|
|
1130
1131
|
render: HITLRenderer,
|
|
1131
1132
|
};
|
|
1132
1133
|
useHumanInTheLoop(hitlTool);
|
|
1134
|
+
const { copilotkit } = useCopilotKit();
|
|
1135
|
+
const registered = copilotkit.value.getTool({
|
|
1136
|
+
toolName: "approvalTool",
|
|
1137
|
+
});
|
|
1138
|
+
if (!registered?.handler) {
|
|
1139
|
+
throw new Error("HITL handler was not registered");
|
|
1140
|
+
}
|
|
1141
|
+
const originalHandler = registered.handler;
|
|
1142
|
+
registered.handler = (...args) => {
|
|
1143
|
+
const pending = originalHandler(...args);
|
|
1144
|
+
handlerStarted();
|
|
1145
|
+
return pending;
|
|
1146
|
+
};
|
|
1133
1147
|
return {};
|
|
1134
1148
|
},
|
|
1135
1149
|
template: `<div />`,
|
|
@@ -1196,11 +1210,23 @@ describe("HITL Thread Reconnection Bug", () => {
|
|
|
1196
1210
|
expect(screen.getByTestId("hitl-action").textContent).toBe("delete");
|
|
1197
1211
|
});
|
|
1198
1212
|
|
|
1199
|
-
// Core-level coverage asserts that passive replay does not re-invoke local
|
|
1200
|
-
// frontend handlers for replayed assistant tool calls.
|
|
1201
1213
|
await waitFor(() => {
|
|
1202
|
-
expect(screen.getByTestId("hitl-status").textContent).
|
|
1203
|
-
|
|
1214
|
+
expect(screen.getByTestId("hitl-status").textContent).toBe(
|
|
1215
|
+
ToolCallStatus.Executing,
|
|
1216
|
+
);
|
|
1217
|
+
});
|
|
1218
|
+
// Vue can paint Executing during the awaited execution-start notification,
|
|
1219
|
+
// before core invokes the handler. Wait for the remounted hook to install
|
|
1220
|
+
// its response resolver before dispatching a synchronous test click.
|
|
1221
|
+
await waitFor(() => expect(handlerStarted).toHaveBeenCalledTimes(2));
|
|
1222
|
+
await fireEvent.click(screen.getByTestId("hitl-respond"));
|
|
1223
|
+
await waitFor(() => {
|
|
1224
|
+
expect(agent.messages).toContainEqual(
|
|
1225
|
+
expect.objectContaining({
|
|
1226
|
+
role: "tool",
|
|
1227
|
+
toolCallId,
|
|
1228
|
+
content: "approved",
|
|
1229
|
+
}),
|
|
1204
1230
|
);
|
|
1205
1231
|
});
|
|
1206
1232
|
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { AbstractAgent } from "@ag-ui/client";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
CopilotKitCoreErrorCode,
|
|
4
|
+
CopilotKitMessageFilter,
|
|
5
|
+
} from "@copilotkit/core";
|
|
3
6
|
import type { DebugConfig } from "@copilotkit/shared";
|
|
4
7
|
import type {
|
|
5
8
|
A2UITheme,
|
|
@@ -16,6 +19,30 @@ export interface CopilotKitProviderProps {
|
|
|
16
19
|
runtimeUrl?: string;
|
|
17
20
|
headers?: Record<string, string> | (() => Record<string, string>);
|
|
18
21
|
credentials?: RequestCredentials;
|
|
22
|
+
/**
|
|
23
|
+
* Rewrites the message list sent to runtime agents on every run.
|
|
24
|
+
*
|
|
25
|
+
* CopilotKit sends the whole thread each time. When your agent already
|
|
26
|
+
* stores the conversation, most of that payload is waste, and an agent that
|
|
27
|
+
* merges the inbound list with its own store can show the model every turn
|
|
28
|
+
* twice. Return the messages to send:
|
|
29
|
+
*
|
|
30
|
+
* ```vue
|
|
31
|
+
* <CopilotKitProvider
|
|
32
|
+
* runtime-url="/api/copilotkit"
|
|
33
|
+
* :message-filter="(messages) => messages.slice(-1)"
|
|
34
|
+
* />
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* The filter changes the request body only. The transcript the UI renders is
|
|
38
|
+
* untouched. Broken tool-call pairs are repaired before the request is sent,
|
|
39
|
+
* so a filter this blunt cannot strand a tool result mid-HITL.
|
|
40
|
+
*
|
|
41
|
+
* Agents reached through your CopilotRuntime honor this. An agent your app
|
|
42
|
+
* passes in directly does not, and neither Intelligence runs nor suggestion
|
|
43
|
+
* runs are ever filtered.
|
|
44
|
+
*/
|
|
45
|
+
messageFilter?: CopilotKitMessageFilter;
|
|
19
46
|
defaultThrottleMs?: number;
|
|
20
47
|
publicApiKey?: string;
|
|
21
48
|
publicLicenseKey?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {
|
|
3
3
|
computed,
|
|
4
|
+
h,
|
|
4
5
|
onMounted,
|
|
5
6
|
provide,
|
|
6
7
|
ref,
|
|
@@ -10,6 +11,7 @@ import {
|
|
|
10
11
|
} from "vue";
|
|
11
12
|
import { z } from "zod";
|
|
12
13
|
import type { AbstractAgent } from "@ag-ui/client";
|
|
14
|
+
import { ToolCallStatus } from "@copilotkit/core";
|
|
13
15
|
import type {
|
|
14
16
|
CopilotKitCoreErrorCode,
|
|
15
17
|
CopilotKitCoreSubscriber,
|
|
@@ -52,6 +54,7 @@ import type {
|
|
|
52
54
|
VueActivityMessageRenderer,
|
|
53
55
|
VueFrontendTool,
|
|
54
56
|
VueToolCallRenderer,
|
|
57
|
+
VueToolCallRendererRenderProps,
|
|
55
58
|
} from "../types";
|
|
56
59
|
|
|
57
60
|
const HEADER_NAME = "X-CopilotCloud-Public-Api-Key";
|
|
@@ -70,6 +73,10 @@ const RENDER_ACTIVITY_MESSAGES_STABLE_WARNING =
|
|
|
70
73
|
"renderActivityMessages must be a stable array.";
|
|
71
74
|
const SANDBOX_FUNCTIONS_STABLE_WARNING =
|
|
72
75
|
"openGenerativeUI.sandboxFunctions must be a stable array.";
|
|
76
|
+
// Matches the message React's `useHumanInTheLoop` rejects with, so an aborted
|
|
77
|
+
// interrupt reads the same across frameworks (see #5554).
|
|
78
|
+
const HUMAN_IN_THE_LOOP_ABORTED_MESSAGE =
|
|
79
|
+
"Human-in-the-loop interaction aborted";
|
|
73
80
|
const DEFAULT_DESIGN_SKILL = `When generating UI with generateSandboxedUi, follow these design principles inspired by shadcn/ui:
|
|
74
81
|
|
|
75
82
|
- Use a minimal, flat aesthetic. Avoid drop shadows and gradients — rely on subtle borders (1px solid, light gray like #e5e7eb) to define surfaces.
|
|
@@ -228,29 +235,99 @@ watch(
|
|
|
228
235
|
{ immediate: true },
|
|
229
236
|
);
|
|
230
237
|
|
|
238
|
+
/**
|
|
239
|
+
* A human-in-the-loop tool call from the `humanInTheLoop` prop that is waiting
|
|
240
|
+
* on the user. Keyed by tool call id so parallel interrupts on the same tool
|
|
241
|
+
* stay independent.
|
|
242
|
+
*/
|
|
243
|
+
type PendingHumanInTheLoop = {
|
|
244
|
+
resolve: (result: unknown) => void;
|
|
245
|
+
detachAbort?: () => void;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const pendingHumanInTheLoop = new Map<string, PendingHumanInTheLoop>();
|
|
249
|
+
|
|
250
|
+
/** Removes a pending interaction and detaches its abort listener. */
|
|
251
|
+
const takePendingHumanInTheLoop = (key: string) => {
|
|
252
|
+
const pending = pendingHumanInTheLoop.get(key);
|
|
253
|
+
if (!pending) return undefined;
|
|
254
|
+
pending.detachAbort?.();
|
|
255
|
+
pendingHumanInTheLoop.delete(key);
|
|
256
|
+
return pending;
|
|
257
|
+
};
|
|
258
|
+
|
|
231
259
|
const processedHumanInTheLoop = computed(() => {
|
|
232
260
|
const tools: FrontendTool[] = [];
|
|
233
261
|
const renderToolCalls: VueToolCallRenderer<unknown>[] = [];
|
|
234
262
|
|
|
235
263
|
for (const tool of props.humanInTheLoop) {
|
|
236
264
|
tools.push({
|
|
265
|
+
type: "human-in-the-loop",
|
|
237
266
|
name: tool.name,
|
|
238
267
|
description: tool.description,
|
|
239
268
|
parameters: tool.parameters,
|
|
240
269
|
followUp: tool.followUp,
|
|
241
270
|
...(tool.agentId && { agentId: tool.agentId }),
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
271
|
+
// Keep the tool call pending until the render calls `respond`, matching
|
|
272
|
+
// the `useHumanInTheLoop` composable. Resolving immediately made the HITL
|
|
273
|
+
// UI flash and disappear without ever waiting for the user.
|
|
274
|
+
handler: async (_args, context) => {
|
|
275
|
+
const signal = context?.signal;
|
|
276
|
+
const key = context?.toolCall?.id ?? tool.name;
|
|
277
|
+
|
|
278
|
+
return new Promise((resolve, reject) => {
|
|
279
|
+
// Already aborted before the handler ran — reject so core records an
|
|
280
|
+
// explicit error tool result rather than silently resolving empty.
|
|
281
|
+
if (signal?.aborted) {
|
|
282
|
+
reject(new Error(HUMAN_IN_THE_LOOP_ABORTED_MESSAGE));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const pending: PendingHumanInTheLoop = { resolve };
|
|
287
|
+
pendingHumanInTheLoop.set(key, pending);
|
|
288
|
+
|
|
289
|
+
if (signal) {
|
|
290
|
+
const onAbort = () => {
|
|
291
|
+
pendingHumanInTheLoop.delete(key);
|
|
292
|
+
reject(new Error(HUMAN_IN_THE_LOOP_ABORTED_MESSAGE));
|
|
293
|
+
};
|
|
294
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
295
|
+
pending.detachAbort = () => {
|
|
296
|
+
signal.removeEventListener("abort", onAbort);
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
});
|
|
247
300
|
},
|
|
248
301
|
});
|
|
249
302
|
if (tool.render) {
|
|
303
|
+
const ToolComponent = tool.render;
|
|
304
|
+
const render: VueToolCallRenderer<unknown>["render"] = (
|
|
305
|
+
renderProps: VueToolCallRendererRenderProps<unknown>,
|
|
306
|
+
) => {
|
|
307
|
+
const key = renderProps.toolCallId ?? tool.name;
|
|
308
|
+
return h(ToolComponent as Parameters<typeof h>[0], {
|
|
309
|
+
...renderProps,
|
|
310
|
+
// `renderProps.name` is the tool that was actually invoked. It equals
|
|
311
|
+
// `tool.name` for a named registration; for a wildcard (`"*"`) it is
|
|
312
|
+
// the only place the real name exists.
|
|
313
|
+
name: tool.name === "*" ? renderProps.name : tool.name,
|
|
314
|
+
description: tool.description || "",
|
|
315
|
+
agentId: tool.agentId,
|
|
316
|
+
// `respond` is live only while the tool is executing — the one phase
|
|
317
|
+
// with a promise waiting on the user.
|
|
318
|
+
respond:
|
|
319
|
+
renderProps.status === ToolCallStatus.Executing
|
|
320
|
+
? async (result: unknown) => {
|
|
321
|
+
takePendingHumanInTheLoop(key)?.resolve(result);
|
|
322
|
+
}
|
|
323
|
+
: undefined,
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
|
|
250
327
|
renderToolCalls.push({
|
|
251
328
|
name: tool.name,
|
|
252
329
|
args: tool.parameters ?? z.any(),
|
|
253
|
-
render
|
|
330
|
+
render,
|
|
254
331
|
...(tool.agentId && { agentId: tool.agentId }),
|
|
255
332
|
} as VueToolCallRenderer<unknown>);
|
|
256
333
|
}
|
|
@@ -407,6 +484,7 @@ const createCopilotKit = () => {
|
|
|
407
484
|
: "auto",
|
|
408
485
|
headers: mergedHeaders.value,
|
|
409
486
|
credentials: props.credentials,
|
|
487
|
+
messageFilter: props.messageFilter,
|
|
410
488
|
properties: resolvedProperties.value,
|
|
411
489
|
agents__unsafe_dev_only: mergedAgents.value,
|
|
412
490
|
tools: allTools.value,
|
|
@@ -525,6 +603,7 @@ function syncRuntimeConfig() {
|
|
|
525
603
|
);
|
|
526
604
|
copilotkit.value.setHeaders(mergedHeaders.value);
|
|
527
605
|
copilotkit.value.setCredentials(props.credentials);
|
|
606
|
+
copilotkit.value.setMessageFilter(props.messageFilter);
|
|
528
607
|
copilotkit.value.setProperties(resolvedProperties.value);
|
|
529
608
|
copilotkit.value.setAgents__unsafe_dev_only(mergedAgents.value);
|
|
530
609
|
copilotkit.value.setDebug(props.debug);
|
|
@@ -536,6 +615,7 @@ watch(
|
|
|
536
615
|
() => chatApiEndpoint.value,
|
|
537
616
|
() => mergedHeaders.value,
|
|
538
617
|
() => props.credentials,
|
|
618
|
+
() => props.messageFilter,
|
|
539
619
|
() => resolvedProperties.value,
|
|
540
620
|
() => mergedAgents.value,
|
|
541
621
|
() => props.useSingleEndpoint,
|