@copilotkit/vue 1.70.2 → 1.71.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.
- package/PARITY.md +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{use-render-activity-message-WeN5iF5H.js → use-render-activity-message-Ciee03Rx.js} +810 -791
- package/dist/{use-render-activity-message-WeN5iF5H.js.map → use-render-activity-message-Ciee03Rx.js.map} +1 -1
- package/dist/use-render-activity-message-DXJDZGkW.cjs +85 -0
- package/dist/{use-render-activity-message-CyThomMH.cjs.map → use-render-activity-message-DXJDZGkW.cjs.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/types/human-in-the-loop.d.ts +6 -0
- package/dist/v2/types/human-in-the-loop.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +252 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.test.ts +247 -0
- package/src/v2/hooks/use-human-in-the-loop.ts +39 -6
- package/src/v2/types/human-in-the-loop.ts +6 -0
- package/CHANGELOG.md +0 -13
- package/dist/use-render-activity-message-CyThomMH.cjs +0 -85
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-human-in-the-loop.d.ts","sourceRoot":"","sources":["../../../src/v2/hooks/use-human-in-the-loop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"use-human-in-the-loop.d.ts","sourceRoot":"","sources":["../../../src/v2/hooks/use-human-in-the-loop.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAKvC,OAAO,KAAK,EAEV,iBAAiB,EAGlB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC1B,IAAI,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,QA4F9B"}
|
package/dist/v2/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@copilotkit/core"),i=require("@ag-ui/client"),e=require("../use-render-activity-message-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@copilotkit/core"),i=require("@ag-ui/client"),e=require("../use-render-activity-message-DXJDZGkW.cjs"),n=require("@copilotkit/shared");exports.A2UIActivityContentSchema=e.A2UIActivityContentSchema;exports.A2UISurfaceActivityRenderer=e._sfc_main;exports.A2UISurfaceActivityType=e.A2UISurfaceActivityType;exports.CopilotChat=e.CopilotChat;exports.CopilotChatAssistantMessage=e._sfc_main$1;exports.CopilotChatAttachmentQueue=e._sfc_main$2;exports.CopilotChatAttachmentRenderer=e._sfc_main$3;exports.CopilotChatAudioRecorder=e._sfc_main$4;exports.CopilotChatConfigurationProvider=e._sfc_main$5;exports.CopilotChatDefaultLabels=e.CopilotChatDefaultLabels;exports.CopilotChatInput=e._sfc_main$6;exports.CopilotChatMessageView=e._sfc_main$7;exports.CopilotChatReasoningMessage=e._sfc_main$8;exports.CopilotChatSuggestionPill=e._sfc_main$9;exports.CopilotChatSuggestionView=e._sfc_main$10;exports.CopilotChatToggleButton=e.CopilotChatToggleButton;exports.CopilotChatToggleButtonCloseIcon=e.CopilotChatToggleButtonCloseIcon;exports.CopilotChatToggleButtonOpenIcon=e.CopilotChatToggleButtonOpenIcon;exports.CopilotChatToolCallsView=e._sfc_main$11;exports.CopilotChatUserMessage=e._sfc_main$12;exports.CopilotChatView=e._sfc_main$13;exports.CopilotKitCoreVue=e.CopilotKitCoreVue;exports.CopilotKitInspector=e._sfc_main$14;exports.CopilotKitProvider=e._sfc_main$15;exports.CopilotModalHeader=e.CopilotModalHeader;exports.CopilotPopup=e._sfc_main$16;exports.CopilotPopupView=e.CopilotPopupView;exports.CopilotSidebar=e._sfc_main$17;exports.CopilotSidebarView=e.CopilotSidebarView;exports.CopilotThreadsDrawer=e._sfc_main$18;exports.GenerateSandboxedUiArgsSchema=e.GenerateSandboxedUiArgsSchema;exports.LicenseContextKey=e.LicenseContextKey;exports.MCPAppsActivityContentSchema=e.MCPAppsActivityContentSchema;exports.MCPAppsActivityRenderer=e.MCPAppsActivityRenderer;exports.MCPAppsActivityType=e.MCPAppsActivityType;exports.OpenGenerativeUIActivityRenderer=e.OpenGenerativeUIActivityRenderer;exports.OpenGenerativeUIActivityType=e.OpenGenerativeUIActivityType;exports.OpenGenerativeUIContentSchema=e.OpenGenerativeUIContentSchema;exports.OpenGenerativeUIRenderer=e.OpenGenerativeUIRenderer;exports.OpenGenerativeUIToolRenderer=e.OpenGenerativeUIToolRenderer;exports.UseAgentUpdate=e.UseAgentUpdate;exports.createA2UIMessageRenderer=e.createA2UIMessageRenderer;exports.createDefaultLicenseRef=e.createDefaultLicenseRef;exports.defineToolCallRenderer=e.defineToolCallRenderer;exports.extractCompleteStyles=e.extractCompleteStyles;exports.getOperationSurfaceId=e.getOperationSurfaceId;exports.processPartialHtml=e.processPartialHtml;exports.useAgent=e.useAgent;exports.useAgentContext=e.useAgentContext;exports.useAttachments=e.useAttachments;exports.useCapabilities=e.useCapabilities;exports.useComponent=e.useComponent;exports.useConfigureSuggestions=e.useConfigureSuggestions;exports.useCopilotChatConfiguration=e.useCopilotChatConfiguration;exports.useCopilotKit=e.useCopilotKit;exports.useDefaultRenderTool=e.useDefaultRenderTool;exports.useFrontendTool=e.useFrontendTool;exports.useHumanInTheLoop=e.useHumanInTheLoop;exports.useInterrupt=e.useInterrupt;exports.useKatexStyles=e.useKatexStyles;exports.useKeyboardHeight=e.useKeyboardHeight;exports.useLicenseContext=e.useLicenseContext;exports.useRenderActivityMessage=e.useRenderActivityMessage;exports.useRenderCustomMessages=e.useRenderCustomMessages;exports.useRenderTool=e.useRenderTool;exports.useSandboxFunctions=e.useSandboxFunctions;exports.useSuggestions=e.useSuggestions;exports.useThreads=e.useThreads;exports.vueBasicCatalog=e.vueBasicCatalog;Object.defineProperty(exports,"createLicenseContextValue",{enumerable:!0,get:()=>n.createLicenseContextValue});Object.keys(o).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>o[t]})});Object.keys(i).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>i[t]})});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/v2/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "@copilotkit/core";
|
|
2
2
|
export * from "@ag-ui/client";
|
|
3
|
-
import { A as o, _ as i, a as n, C, b as r, c as p, d as l, e as u, f as c, g, h as d, i as h, j as A, k as v, l as S, m, n as y, o as I, p as f, q as R, r as T, s as U, t as x, u as M, v as P, w as b, x as V, y as w, z as K, B as O, G, L, M as B, D, E as H, O as F, F as Q, H as j, I as k, J as q, U as z, K as E, N as J, P as N, Q as W, R as X, S as Y, T as Z, V as _, W as $, X as ee, Y as ae, Z as te, $ as se, a0 as oe, a1 as ie, a2 as ne, a3 as Ce, a4 as re, a5 as pe, a6 as le, a7 as ue, a8 as ce, a9 as ge, aa as de, ab as he, ac as Ae, ad as ve, ae as Se } from "../use-render-activity-message-
|
|
3
|
+
import { A as o, _ as i, a as n, C, b as r, c as p, d as l, e as u, f as c, g, h as d, i as h, j as A, k as v, l as S, m, n as y, o as I, p as f, q as R, r as T, s as U, t as x, u as M, v as P, w as b, x as V, y as w, z as K, B as O, G, L, M as B, D, E as H, O as F, F as Q, H as j, I as k, J as q, U as z, K as E, N as J, P as N, Q as W, R as X, S as Y, T as Z, V as _, W as $, X as ee, Y as ae, Z as te, $ as se, a0 as oe, a1 as ie, a2 as ne, a3 as Ce, a4 as re, a5 as pe, a6 as le, a7 as ue, a8 as ce, a9 as ge, aa as de, ab as he, ac as Ae, ad as ve, ae as Se } from "../use-render-activity-message-Ciee03Rx.js";
|
|
4
4
|
import { createLicenseContextValue as ye } from "@copilotkit/shared";
|
|
5
5
|
export {
|
|
6
6
|
o as A2UIActivityContentSchema,
|
|
@@ -3,6 +3,8 @@ import type { Component, VNodeChild } from "vue";
|
|
|
3
3
|
export type VueHumanInTheLoopRenderProps<T> = {
|
|
4
4
|
name: string;
|
|
5
5
|
description: string;
|
|
6
|
+
toolCallId: string;
|
|
7
|
+
agentId?: string;
|
|
6
8
|
args: Partial<T>;
|
|
7
9
|
status: ToolCallStatus.InProgress;
|
|
8
10
|
result: undefined;
|
|
@@ -10,6 +12,8 @@ export type VueHumanInTheLoopRenderProps<T> = {
|
|
|
10
12
|
} | {
|
|
11
13
|
name: string;
|
|
12
14
|
description: string;
|
|
15
|
+
toolCallId: string;
|
|
16
|
+
agentId?: string;
|
|
13
17
|
args: T;
|
|
14
18
|
status: ToolCallStatus.Executing;
|
|
15
19
|
result: undefined;
|
|
@@ -17,6 +21,8 @@ export type VueHumanInTheLoopRenderProps<T> = {
|
|
|
17
21
|
} | {
|
|
18
22
|
name: string;
|
|
19
23
|
description: string;
|
|
24
|
+
toolCallId: string;
|
|
25
|
+
agentId?: string;
|
|
20
26
|
args: T;
|
|
21
27
|
status: ToolCallStatus.Complete;
|
|
22
28
|
result: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"human-in-the-loop.d.ts","sourceRoot":"","sources":["../../../src/v2/types/human-in-the-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAEjD,MAAM,MAAM,4BAA4B,CAAC,CAAC,IACtC;IACE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC;IAClC,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,SAAS,CAAC;CACpB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC;IACjC,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,SAAS,CAAC;CACpB,CAAC;AAEN,MAAM,MAAM,yBAAyB,CAAC,CAAC,IACnC,CAAC,CAAC,KAAK,EAAE,4BAA4B,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,GACxD,SAAS,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/C,MAAM,MAAM,iBAAiB,CAC3B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACzD,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG;IACrC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC;CACtC,CAAC"}
|
|
1
|
+
{"version":3,"file":"human-in-the-loop.d.ts","sourceRoot":"","sources":["../../../src/v2/types/human-in-the-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAEjD,MAAM,MAAM,4BAA4B,CAAC,CAAC,IACtC;IACE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACjB,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC;IAClC,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,SAAS,CAAC;CACpB,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC;IACjC,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C,GACD;IACE,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;IACR,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,SAAS,CAAC;CACpB,CAAC;AAEN,MAAM,MAAM,yBAAyB,CAAC,CAAC,IACnC,CAAC,CAAC,KAAK,EAAE,4BAA4B,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,GACxD,SAAS,CAAC,4BAA4B,CAAC,CAAC,CAAC,CAAC,CAAC;AAE/C,MAAM,MAAM,iBAAiB,CAC3B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACzD,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG;IACrC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC,CAAC;CACtC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/vue",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.71.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Vue 3 components and composables for CopilotKit",
|
|
6
6
|
"keywords": [
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"streamdown-vue": "^1.0.29",
|
|
71
71
|
"zod": "^3.25.75",
|
|
72
72
|
"zod-to-json-schema": "^3.24.5",
|
|
73
|
-
"@copilotkit/core": "1.
|
|
74
|
-
"@copilotkit/
|
|
75
|
-
"@copilotkit/
|
|
76
|
-
"@copilotkit/web-inspector": "1.
|
|
73
|
+
"@copilotkit/core": "1.71.0",
|
|
74
|
+
"@copilotkit/web-components": "1.71.0",
|
|
75
|
+
"@copilotkit/shared": "1.71.0",
|
|
76
|
+
"@copilotkit/web-inspector": "1.71.0"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@eslint/js": "^9.39.2",
|
|
@@ -8,6 +8,7 @@ import { ToolCallStatus } from "@copilotkit/core";
|
|
|
8
8
|
import CopilotChat from "../../components/chat/CopilotChat.vue";
|
|
9
9
|
import CopilotChatToolCallsView from "../../components/chat/CopilotChatToolCallsView.vue";
|
|
10
10
|
import { useHumanInTheLoop } from "../use-human-in-the-loop";
|
|
11
|
+
import { useCopilotKit } from "../../providers/useCopilotKit";
|
|
11
12
|
import {
|
|
12
13
|
MockStepwiseAgent,
|
|
13
14
|
MockReconnectableAgent,
|
|
@@ -843,6 +844,257 @@ describe("useHumanInTheLoop E2E - HITL Tool Rendering", () => {
|
|
|
843
844
|
});
|
|
844
845
|
});
|
|
845
846
|
});
|
|
847
|
+
|
|
848
|
+
describe("HITL attribution (toolCallId + agentId)", () => {
|
|
849
|
+
it("exposes toolCallId and the registered agentId to the render props", async () => {
|
|
850
|
+
const agent = new MockStepwiseAgent();
|
|
851
|
+
let renderedToolCallId: string | undefined;
|
|
852
|
+
let renderedAgentId: string | undefined;
|
|
853
|
+
|
|
854
|
+
const AttributionRenderer = defineComponent({
|
|
855
|
+
props: {
|
|
856
|
+
toolCallId: { type: String, required: true },
|
|
857
|
+
agentId: { type: String, required: false },
|
|
858
|
+
status: { type: String as PropType<ToolCallStatus>, required: true },
|
|
859
|
+
args: {
|
|
860
|
+
type: Object as PropType<{ action?: string }>,
|
|
861
|
+
required: true,
|
|
862
|
+
},
|
|
863
|
+
},
|
|
864
|
+
setup(props) {
|
|
865
|
+
watch(
|
|
866
|
+
() => [props.toolCallId, props.agentId],
|
|
867
|
+
() => {
|
|
868
|
+
renderedToolCallId = props.toolCallId;
|
|
869
|
+
renderedAgentId = props.agentId;
|
|
870
|
+
},
|
|
871
|
+
{ immediate: true },
|
|
872
|
+
);
|
|
873
|
+
return {};
|
|
874
|
+
},
|
|
875
|
+
template: `
|
|
876
|
+
<div data-testid="attr-hitl">
|
|
877
|
+
<div data-testid="attr-tool-call-id">{{ toolCallId }}</div>
|
|
878
|
+
<div data-testid="attr-agent-id">{{ agentId ?? "none" }}</div>
|
|
879
|
+
<div data-testid="attr-status">{{ status }}</div>
|
|
880
|
+
<div data-testid="attr-action">{{ args.action ?? "" }}</div>
|
|
881
|
+
</div>
|
|
882
|
+
`,
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
const AttributionHITLComponent = defineComponent({
|
|
886
|
+
setup() {
|
|
887
|
+
useHumanInTheLoop({
|
|
888
|
+
name: "scopedApprovalTool",
|
|
889
|
+
description: "Scoped approval",
|
|
890
|
+
agentId: "research-agent",
|
|
891
|
+
parameters: z.object({ action: z.string() }),
|
|
892
|
+
render: AttributionRenderer,
|
|
893
|
+
});
|
|
894
|
+
return {};
|
|
895
|
+
},
|
|
896
|
+
template: `<div />`,
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
renderWithCopilotKit({
|
|
900
|
+
agent,
|
|
901
|
+
agentId: "research-agent",
|
|
902
|
+
children: createChatHost(AttributionHITLComponent),
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
await submitMessage("Need approval");
|
|
906
|
+
const messageId = testId("msg");
|
|
907
|
+
const toolCallId = testId("tc");
|
|
908
|
+
await agent.emit(runStartedEvent());
|
|
909
|
+
await agent.emit(
|
|
910
|
+
toolCallChunkEvent({
|
|
911
|
+
toolCallId,
|
|
912
|
+
toolCallName: "scopedApprovalTool",
|
|
913
|
+
parentMessageId: messageId,
|
|
914
|
+
delta: JSON.stringify({ action: "delete" }),
|
|
915
|
+
}),
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
await waitFor(() => {
|
|
919
|
+
expect(screen.getByTestId("attr-tool-call-id").textContent).toBe(
|
|
920
|
+
toolCallId,
|
|
921
|
+
);
|
|
922
|
+
expect(screen.getByTestId("attr-agent-id").textContent).toBe(
|
|
923
|
+
"research-agent",
|
|
924
|
+
);
|
|
925
|
+
});
|
|
926
|
+
expect(renderedToolCallId).toBe(toolCallId);
|
|
927
|
+
expect(renderedAgentId).toBe("research-agent");
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
it("leaves agentId undefined for an unscoped HITL tool", async () => {
|
|
931
|
+
const agent = new MockStepwiseAgent();
|
|
932
|
+
const UnscopedRenderer = defineComponent({
|
|
933
|
+
props: {
|
|
934
|
+
toolCallId: { type: String, required: true },
|
|
935
|
+
agentId: { type: String, required: false },
|
|
936
|
+
status: { type: String as PropType<ToolCallStatus>, required: true },
|
|
937
|
+
args: {
|
|
938
|
+
type: Object as PropType<{ action?: string }>,
|
|
939
|
+
required: true,
|
|
940
|
+
},
|
|
941
|
+
},
|
|
942
|
+
template: `
|
|
943
|
+
<div data-testid="unscoped-hitl">
|
|
944
|
+
<div data-testid="unscoped-tool-call-id">{{ toolCallId }}</div>
|
|
945
|
+
<div data-testid="unscoped-agent-id">{{ agentId ?? "none" }}</div>
|
|
946
|
+
<div data-testid="unscoped-status">{{ status }}</div>
|
|
947
|
+
<div data-testid="unscoped-action">{{ args.action ?? "" }}</div>
|
|
948
|
+
</div>
|
|
949
|
+
`,
|
|
950
|
+
});
|
|
951
|
+
const UnscopedHITLComponent = defineComponent({
|
|
952
|
+
setup() {
|
|
953
|
+
useHumanInTheLoop({
|
|
954
|
+
name: "unscopedApprovalTool",
|
|
955
|
+
description: "Unscoped approval",
|
|
956
|
+
parameters: z.object({ action: z.string() }),
|
|
957
|
+
render: UnscopedRenderer,
|
|
958
|
+
});
|
|
959
|
+
return {};
|
|
960
|
+
},
|
|
961
|
+
template: `<div />`,
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
renderWithCopilotKit({
|
|
965
|
+
agent,
|
|
966
|
+
children: createChatHost(UnscopedHITLComponent),
|
|
967
|
+
});
|
|
968
|
+
await submitMessage("Need approval");
|
|
969
|
+
const messageId = testId("msg");
|
|
970
|
+
const toolCallId = testId("tc");
|
|
971
|
+
await agent.emit(runStartedEvent());
|
|
972
|
+
await agent.emit(
|
|
973
|
+
toolCallChunkEvent({
|
|
974
|
+
toolCallId,
|
|
975
|
+
toolCallName: "unscopedApprovalTool",
|
|
976
|
+
parentMessageId: messageId,
|
|
977
|
+
delta: JSON.stringify({ action: "archive" }),
|
|
978
|
+
}),
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
await waitFor(() => {
|
|
982
|
+
expect(screen.getByTestId("unscoped-status").textContent).toBe(
|
|
983
|
+
ToolCallStatus.InProgress,
|
|
984
|
+
);
|
|
985
|
+
expect(screen.getByTestId("unscoped-tool-call-id").textContent).toBe(
|
|
986
|
+
toolCallId,
|
|
987
|
+
);
|
|
988
|
+
expect(screen.getByTestId("unscoped-agent-id").textContent).toBe(
|
|
989
|
+
"none",
|
|
990
|
+
);
|
|
991
|
+
});
|
|
992
|
+
await agent.emit(runFinishedEvent());
|
|
993
|
+
await agent.complete();
|
|
994
|
+
await waitFor(() => {
|
|
995
|
+
expect(screen.getByTestId("unscoped-status").textContent).toBe(
|
|
996
|
+
ToolCallStatus.Executing,
|
|
997
|
+
);
|
|
998
|
+
expect(screen.getByTestId("unscoped-tool-call-id").textContent).toBe(
|
|
999
|
+
toolCallId,
|
|
1000
|
+
);
|
|
1001
|
+
expect(screen.getByTestId("unscoped-agent-id").textContent).toBe(
|
|
1002
|
+
"none",
|
|
1003
|
+
);
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
});
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
describe("HITL Run Abort", () => {
|
|
1010
|
+
it("rejects the pending HITL promise with an error tool result when the run is aborted", async () => {
|
|
1011
|
+
const agent = new MockStepwiseAgent();
|
|
1012
|
+
const toolExecutionEnds: Array<{ result: string; error?: string }> = [];
|
|
1013
|
+
let stopRun: (() => void) | undefined;
|
|
1014
|
+
|
|
1015
|
+
const AbortHITLComponent = defineComponent({
|
|
1016
|
+
setup() {
|
|
1017
|
+
const { copilotkit } = useCopilotKit();
|
|
1018
|
+
stopRun = () =>
|
|
1019
|
+
copilotkit.value.stopAgent({
|
|
1020
|
+
agent: copilotkit.value.getAgent("default")!,
|
|
1021
|
+
});
|
|
1022
|
+
copilotkit.value.subscribe({
|
|
1023
|
+
onToolExecutionEnd: ({ result, error }) => {
|
|
1024
|
+
toolExecutionEnds.push({ result, error });
|
|
1025
|
+
},
|
|
1026
|
+
});
|
|
1027
|
+
useHumanInTheLoop({
|
|
1028
|
+
name: "abortApprovalTool",
|
|
1029
|
+
description: "Requires human approval",
|
|
1030
|
+
parameters: z.object({ action: z.string() }),
|
|
1031
|
+
render: defineComponent({
|
|
1032
|
+
props: {
|
|
1033
|
+
status: {
|
|
1034
|
+
type: String as PropType<ToolCallStatus>,
|
|
1035
|
+
required: true,
|
|
1036
|
+
},
|
|
1037
|
+
args: {
|
|
1038
|
+
type: Object as PropType<{ action?: string }>,
|
|
1039
|
+
required: true,
|
|
1040
|
+
},
|
|
1041
|
+
respond: {
|
|
1042
|
+
type: Function as PropType<
|
|
1043
|
+
((result: unknown) => Promise<void>) | undefined
|
|
1044
|
+
>,
|
|
1045
|
+
required: false,
|
|
1046
|
+
},
|
|
1047
|
+
},
|
|
1048
|
+
template: `
|
|
1049
|
+
<div data-testid="abort-hitl">
|
|
1050
|
+
<div data-testid="abort-status">{{ status }}</div>
|
|
1051
|
+
<div data-testid="abort-action">{{ args.action ?? "" }}</div>
|
|
1052
|
+
</div>
|
|
1053
|
+
`,
|
|
1054
|
+
}),
|
|
1055
|
+
});
|
|
1056
|
+
return {};
|
|
1057
|
+
},
|
|
1058
|
+
template: `<div />`,
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
renderWithCopilotKit({
|
|
1062
|
+
agent,
|
|
1063
|
+
children: createChatHost(AbortHITLComponent),
|
|
1064
|
+
});
|
|
1065
|
+
await submitMessage("Request approval");
|
|
1066
|
+
const messageId = testId("msg");
|
|
1067
|
+
const toolCallId = testId("tc");
|
|
1068
|
+
await agent.emit(runStartedEvent());
|
|
1069
|
+
await agent.emit(
|
|
1070
|
+
toolCallChunkEvent({
|
|
1071
|
+
toolCallId,
|
|
1072
|
+
toolCallName: "abortApprovalTool",
|
|
1073
|
+
parentMessageId: messageId,
|
|
1074
|
+
delta: JSON.stringify({ action: "delete" }),
|
|
1075
|
+
}),
|
|
1076
|
+
);
|
|
1077
|
+
await waitFor(() => {
|
|
1078
|
+
expect(screen.getByTestId("abort-status").textContent).toBe(
|
|
1079
|
+
ToolCallStatus.InProgress,
|
|
1080
|
+
);
|
|
1081
|
+
});
|
|
1082
|
+
await agent.emit(runFinishedEvent());
|
|
1083
|
+
await agent.complete();
|
|
1084
|
+
await waitFor(() => {
|
|
1085
|
+
expect(screen.getByTestId("abort-status").textContent).toBe(
|
|
1086
|
+
ToolCallStatus.Executing,
|
|
1087
|
+
);
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
stopRun?.();
|
|
1091
|
+
await waitFor(() => {
|
|
1092
|
+
expect(toolExecutionEnds.length).toBeGreaterThan(0);
|
|
1093
|
+
const last = toolExecutionEnds[toolExecutionEnds.length - 1];
|
|
1094
|
+
expect(last.error).toBeDefined();
|
|
1095
|
+
expect(last.error).not.toBe("");
|
|
1096
|
+
});
|
|
1097
|
+
});
|
|
846
1098
|
});
|
|
847
1099
|
|
|
848
1100
|
describe("HITL Thread Reconnection Bug", () => {
|
|
@@ -58,6 +58,7 @@ describe("useHumanInTheLoop", () => {
|
|
|
58
58
|
|
|
59
59
|
const executingVNode = renderer!.render({
|
|
60
60
|
name: "approveTool",
|
|
61
|
+
toolCallId: "tc-approve",
|
|
61
62
|
args: { question: "Proceed?" },
|
|
62
63
|
status: "executing",
|
|
63
64
|
result: undefined,
|
|
@@ -112,6 +113,7 @@ describe("useHumanInTheLoop", () => {
|
|
|
112
113
|
const respondA = (
|
|
113
114
|
renderA!.render({
|
|
114
115
|
name: "toolA",
|
|
116
|
+
toolCallId: "tc-a",
|
|
115
117
|
args: {},
|
|
116
118
|
status: "executing",
|
|
117
119
|
result: undefined,
|
|
@@ -122,6 +124,7 @@ describe("useHumanInTheLoop", () => {
|
|
|
122
124
|
const respondB = (
|
|
123
125
|
renderB!.render({
|
|
124
126
|
name: "toolB",
|
|
127
|
+
toolCallId: "tc-b",
|
|
125
128
|
args: {},
|
|
126
129
|
status: "executing",
|
|
127
130
|
result: undefined,
|
|
@@ -163,6 +166,7 @@ describe("useHumanInTheLoop", () => {
|
|
|
163
166
|
|
|
164
167
|
const inProgress = renderer!.render({
|
|
165
168
|
name: "approveTool",
|
|
169
|
+
toolCallId: "tc-status",
|
|
166
170
|
args: {},
|
|
167
171
|
status: "inProgress",
|
|
168
172
|
result: undefined,
|
|
@@ -171,6 +175,7 @@ describe("useHumanInTheLoop", () => {
|
|
|
171
175
|
};
|
|
172
176
|
const executing = renderer!.render({
|
|
173
177
|
name: "approveTool",
|
|
178
|
+
toolCallId: "tc-status",
|
|
174
179
|
args: {},
|
|
175
180
|
status: "executing",
|
|
176
181
|
result: undefined,
|
|
@@ -179,6 +184,7 @@ describe("useHumanInTheLoop", () => {
|
|
|
179
184
|
};
|
|
180
185
|
const complete = renderer!.render({
|
|
181
186
|
name: "approveTool",
|
|
187
|
+
toolCallId: "tc-status",
|
|
182
188
|
args: {},
|
|
183
189
|
status: "complete",
|
|
184
190
|
result: "ok",
|
|
@@ -279,4 +285,245 @@ describe("useHumanInTheLoop", () => {
|
|
|
279
285
|
expect(getCore().getTool({ toolName: "depHitl" })).toBeDefined();
|
|
280
286
|
addToolSpy.mockRestore();
|
|
281
287
|
});
|
|
288
|
+
|
|
289
|
+
describe("Vue-specific HITL lifecycle semantics", () => {
|
|
290
|
+
function mountHitlTool(agentId?: string) {
|
|
291
|
+
const RenderComp = defineComponent({
|
|
292
|
+
setup() {
|
|
293
|
+
return () => h("div");
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
const Child = defineComponent({
|
|
297
|
+
setup() {
|
|
298
|
+
useHumanInTheLoop({
|
|
299
|
+
name: "lifecycleTool",
|
|
300
|
+
description: "Lifecycle description",
|
|
301
|
+
agentId,
|
|
302
|
+
parameters: z.object({ value: z.string() }),
|
|
303
|
+
render: RenderComp,
|
|
304
|
+
});
|
|
305
|
+
return () => null;
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
const mounted = mountWithProvider(() => h(Child));
|
|
309
|
+
const core = mounted.getCore();
|
|
310
|
+
return {
|
|
311
|
+
...mounted,
|
|
312
|
+
handler: core.getTool({ toolName: "lifecycleTool", agentId })!.handler!,
|
|
313
|
+
renderer: core.renderToolCalls.find(
|
|
314
|
+
(renderTool) =>
|
|
315
|
+
renderTool.name === "lifecycleTool" &&
|
|
316
|
+
renderTool.agentId === agentId,
|
|
317
|
+
)!,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
it("rejects an already-aborted signal with the exact HITL error", async () => {
|
|
322
|
+
const { handler } = mountHitlTool();
|
|
323
|
+
const controller = new AbortController();
|
|
324
|
+
controller.abort();
|
|
325
|
+
|
|
326
|
+
await expect(handler!({}, { signal: controller.signal })).rejects.toEqual(
|
|
327
|
+
new Error("Human-in-the-loop interaction aborted"),
|
|
328
|
+
);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("rejects once on abort, clears pending refs, and installs a one-shot listener", async () => {
|
|
332
|
+
const { handler, renderer } = mountHitlTool();
|
|
333
|
+
const listeners = new Set<() => void>();
|
|
334
|
+
const signal = {
|
|
335
|
+
aborted: false,
|
|
336
|
+
addEventListener: vi.fn(
|
|
337
|
+
(_type: string, listener: () => void, options) => {
|
|
338
|
+
expect(options).toEqual({ once: true });
|
|
339
|
+
listeners.add(listener);
|
|
340
|
+
},
|
|
341
|
+
),
|
|
342
|
+
removeEventListener: vi.fn((_type: string, listener: () => void) => {
|
|
343
|
+
listeners.delete(listener);
|
|
344
|
+
}),
|
|
345
|
+
} as unknown as AbortSignal;
|
|
346
|
+
|
|
347
|
+
const pending = handler!({}, { signal });
|
|
348
|
+
expect(listeners.size).toBe(1);
|
|
349
|
+
const onAbort = [...listeners][0];
|
|
350
|
+
onAbort();
|
|
351
|
+
onAbort();
|
|
352
|
+
|
|
353
|
+
await expect(pending).rejects.toEqual(
|
|
354
|
+
new Error("Human-in-the-loop interaction aborted"),
|
|
355
|
+
);
|
|
356
|
+
const respond = (
|
|
357
|
+
renderer.render({
|
|
358
|
+
name: "lifecycleTool",
|
|
359
|
+
toolCallId: "tc-aborted",
|
|
360
|
+
args: {},
|
|
361
|
+
status: "executing",
|
|
362
|
+
result: undefined,
|
|
363
|
+
}) as { props?: { respond?: (result: unknown) => Promise<void> } }
|
|
364
|
+
).props?.respond;
|
|
365
|
+
await respond?.("late response");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("removes the abort listener before respond resolves and ignores a late abort", async () => {
|
|
369
|
+
const { handler, renderer } = mountHitlTool();
|
|
370
|
+
const events: string[] = [];
|
|
371
|
+
let onAbort: (() => void) | undefined;
|
|
372
|
+
const signal = {
|
|
373
|
+
aborted: false,
|
|
374
|
+
addEventListener: vi.fn((_type: string, listener: () => void) => {
|
|
375
|
+
onAbort = listener;
|
|
376
|
+
}),
|
|
377
|
+
removeEventListener: vi.fn(() => {
|
|
378
|
+
events.push("removed");
|
|
379
|
+
}),
|
|
380
|
+
} as unknown as AbortSignal;
|
|
381
|
+
|
|
382
|
+
const pending = handler!({}, { signal }).then((result) => {
|
|
383
|
+
events.push(`resolved:${String(result)}`);
|
|
384
|
+
return result;
|
|
385
|
+
});
|
|
386
|
+
const respond = (
|
|
387
|
+
renderer.render({
|
|
388
|
+
name: "lifecycleTool",
|
|
389
|
+
toolCallId: "tc-respond",
|
|
390
|
+
args: {},
|
|
391
|
+
status: "executing",
|
|
392
|
+
result: undefined,
|
|
393
|
+
}) as { props?: { respond?: (result: unknown) => Promise<void> } }
|
|
394
|
+
).props?.respond;
|
|
395
|
+
|
|
396
|
+
await respond!("approved");
|
|
397
|
+
onAbort?.();
|
|
398
|
+
await expect(pending).resolves.toBe("approved");
|
|
399
|
+
expect(events).toEqual(["removed", "resolved:approved"]);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it("passes toolCallId and registration agentId through every HITL status", () => {
|
|
403
|
+
const { renderer } = mountHitlTool("research-agent");
|
|
404
|
+
const shared = { name: "lifecycleTool", toolCallId: "tc-props" };
|
|
405
|
+
const inProgress = renderer.render({
|
|
406
|
+
...shared,
|
|
407
|
+
args: { value: "partial" },
|
|
408
|
+
status: "inProgress",
|
|
409
|
+
result: undefined,
|
|
410
|
+
}) as { props?: Record<string, unknown> };
|
|
411
|
+
const executing = renderer.render({
|
|
412
|
+
...shared,
|
|
413
|
+
args: { value: "complete-args" },
|
|
414
|
+
status: "executing",
|
|
415
|
+
result: undefined,
|
|
416
|
+
}) as { props?: Record<string, unknown> };
|
|
417
|
+
const complete = renderer.render({
|
|
418
|
+
...shared,
|
|
419
|
+
args: { value: "complete-args" },
|
|
420
|
+
status: "complete",
|
|
421
|
+
result: "done",
|
|
422
|
+
}) as { props?: Record<string, unknown> };
|
|
423
|
+
|
|
424
|
+
for (const vnode of [inProgress, executing, complete]) {
|
|
425
|
+
expect(vnode.props?.name).toBe("lifecycleTool");
|
|
426
|
+
expect(vnode.props?.description).toBe("Lifecycle description");
|
|
427
|
+
expect(vnode.props?.toolCallId).toBe("tc-props");
|
|
428
|
+
expect(vnode.props?.agentId).toBe("research-agent");
|
|
429
|
+
}
|
|
430
|
+
expect(inProgress.props?.args).toEqual({ value: "partial" });
|
|
431
|
+
expect(inProgress.props?.result).toBeUndefined();
|
|
432
|
+
expect(executing.props?.args).toEqual({ value: "complete-args" });
|
|
433
|
+
expect(executing.props?.result).toBeUndefined();
|
|
434
|
+
expect(complete.props?.args).toEqual({ value: "complete-args" });
|
|
435
|
+
expect(complete.props?.result).toBe("done");
|
|
436
|
+
expect(inProgress.props?.respond).toBeUndefined();
|
|
437
|
+
expect(typeof executing.props?.respond).toBe("function");
|
|
438
|
+
expect(complete.props?.respond).toBeUndefined();
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("removes only the agent-scoped renderer on scope disposal", async () => {
|
|
442
|
+
const RenderComp = defineComponent({
|
|
443
|
+
setup() {
|
|
444
|
+
return () => h("div");
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
const createScopedChild = (agentId: string) =>
|
|
448
|
+
defineComponent({
|
|
449
|
+
setup() {
|
|
450
|
+
useHumanInTheLoop({
|
|
451
|
+
name: "sharedLifecycleTool",
|
|
452
|
+
agentId,
|
|
453
|
+
parameters: z.object({}),
|
|
454
|
+
render: RenderComp,
|
|
455
|
+
});
|
|
456
|
+
return () => null;
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
const ScopedA = createScopedChild("agent-a");
|
|
460
|
+
const ScopedB = createScopedChild("agent-b");
|
|
461
|
+
const Parent = defineComponent({
|
|
462
|
+
setup() {
|
|
463
|
+
const showA = ref(true);
|
|
464
|
+
return () =>
|
|
465
|
+
h("div", [
|
|
466
|
+
h("button", {
|
|
467
|
+
"data-testid": "dispose-agent-a",
|
|
468
|
+
onClick: () => (showA.value = false),
|
|
469
|
+
}),
|
|
470
|
+
showA.value ? h(ScopedA) : null,
|
|
471
|
+
h(ScopedB),
|
|
472
|
+
]);
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
const { wrapper, getCore } = mountWithProvider(() => h(Parent));
|
|
477
|
+
await nextTick();
|
|
478
|
+
const renderers = () =>
|
|
479
|
+
getCore().renderToolCalls.filter(
|
|
480
|
+
(renderer) => renderer.name === "sharedLifecycleTool",
|
|
481
|
+
);
|
|
482
|
+
expect(renderers().map((renderer) => renderer.agentId)).toEqual([
|
|
483
|
+
"agent-a",
|
|
484
|
+
"agent-b",
|
|
485
|
+
]);
|
|
486
|
+
|
|
487
|
+
await wrapper.find("[data-testid=dispose-agent-a]").trigger("click");
|
|
488
|
+
await nextTick();
|
|
489
|
+
|
|
490
|
+
expect(renderers().map((renderer) => renderer.agentId)).toEqual([
|
|
491
|
+
"agent-b",
|
|
492
|
+
]);
|
|
493
|
+
expect(
|
|
494
|
+
getCore().getTool({
|
|
495
|
+
toolName: "sharedLifecycleTool",
|
|
496
|
+
agentId: "agent-b",
|
|
497
|
+
}),
|
|
498
|
+
).toBeDefined();
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it("leaves agentId undefined for an unscoped HITL tool", () => {
|
|
502
|
+
const { renderer } = mountHitlTool();
|
|
503
|
+
const vnode = renderer.render({
|
|
504
|
+
name: "lifecycleTool",
|
|
505
|
+
toolCallId: "tc-unscoped",
|
|
506
|
+
args: {},
|
|
507
|
+
status: "executing",
|
|
508
|
+
result: undefined,
|
|
509
|
+
}) as { props?: Record<string, unknown> };
|
|
510
|
+
|
|
511
|
+
expect(vnode.props?.agentId).toBeUndefined();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it("does not settle a pending HITL promise when its renderer unmounts", async () => {
|
|
515
|
+
const mounted = mountHitlTool();
|
|
516
|
+
const pending = mounted.handler({}, {});
|
|
517
|
+
mounted.wrapper.unmount();
|
|
518
|
+
|
|
519
|
+
const outcome = await Promise.race([
|
|
520
|
+
pending.then(
|
|
521
|
+
() => "settled",
|
|
522
|
+
() => "settled",
|
|
523
|
+
),
|
|
524
|
+
new Promise((resolve) => setTimeout(() => resolve("pending"), 0)),
|
|
525
|
+
]);
|
|
526
|
+
expect(outcome).toBe("pending");
|
|
527
|
+
});
|
|
528
|
+
});
|
|
282
529
|
});
|