@copilotkit/vue 1.70.0 → 1.70.2
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/dist/hooks/use-copilot-action.d.ts +7 -0
- package/dist/hooks/use-copilot-action.d.ts.map +1 -1
- package/dist/hooks/use-frontend-tool.d.ts +7 -0
- package/dist/hooks/use-frontend-tool.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +35 -23
- package/dist/index.mjs.map +1 -1
- package/dist/{use-render-activity-message-h2NHCkBt.cjs → use-render-activity-message-CyThomMH.cjs} +2 -2
- package/dist/{use-render-activity-message-h2NHCkBt.cjs.map → use-render-activity-message-CyThomMH.cjs.map} +1 -1
- package/dist/{use-render-activity-message-ytoSQi6V.js → use-render-activity-message-WeN5iF5H.js} +4 -1
- package/dist/{use-render-activity-message-ytoSQi6V.js.map → use-render-activity-message-WeN5iF5H.js.map} +1 -1
- package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/package.json +5 -5
- package/src/hooks/__tests__/use-frontend-tool-webmcp.test.ts +113 -0
- package/src/hooks/use-copilot-action.ts +13 -0
- package/src/hooks/use-frontend-tool.ts +13 -0
- package/src/v2/hooks/__tests__/use-frontend-tool-webmcp.test.ts +282 -0
- package/src/v2/hooks/use-frontend-tool.ts +3 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-frontend-tool.d.ts","sourceRoot":"","sources":["../../../src/v2/hooks/use-frontend-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAKhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,EACxB,IAAI,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"use-frontend-tool.d.ts","sourceRoot":"","sources":["../../../src/v2/hooks/use-frontend-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAKhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,EACxB,IAAI,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,QA6C9B"}
|
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-CyThomMH.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-WeN5iF5H.js";
|
|
4
4
|
import { createLicenseContextValue as ye } from "@copilotkit/shared";
|
|
5
5
|
export {
|
|
6
6
|
o as A2UIActivityContentSchema,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/vue",
|
|
3
|
-
"version": "1.70.
|
|
3
|
+
"version": "1.70.2",
|
|
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.70.
|
|
74
|
-
"@copilotkit/
|
|
75
|
-
"@copilotkit/web-
|
|
76
|
-
"@copilotkit/
|
|
73
|
+
"@copilotkit/core": "1.70.2",
|
|
74
|
+
"@copilotkit/shared": "1.70.2",
|
|
75
|
+
"@copilotkit/web-components": "1.70.2",
|
|
76
|
+
"@copilotkit/web-inspector": "1.70.2"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@eslint/js": "^9.39.2",
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { defineComponent, ref, watch } from "vue";
|
|
2
|
+
import { screen, fireEvent, waitFor } from "@testing-library/vue";
|
|
3
|
+
import { describe, it, expect } from "vitest";
|
|
4
|
+
import { useFrontendTool } from "../use-frontend-tool";
|
|
5
|
+
import { useCopilotKit } from "../../v2/providers/useCopilotKit";
|
|
6
|
+
import type { CopilotKitCoreVue } from "../../v2/lib/vue-core";
|
|
7
|
+
import { renderWithCopilotKit } from "../../v2/__tests__/utils/test-helpers";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Component that captures the copilotkit core ref for test assertions.
|
|
11
|
+
*/
|
|
12
|
+
const CoreCapture = defineComponent({
|
|
13
|
+
props: {
|
|
14
|
+
onCore: {
|
|
15
|
+
type: Function as () => (core: CopilotKitCoreVue) => void,
|
|
16
|
+
required: true,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
setup(props) {
|
|
20
|
+
const { copilotkit } = useCopilotKit();
|
|
21
|
+
watch(
|
|
22
|
+
copilotkit,
|
|
23
|
+
(core) => {
|
|
24
|
+
if (core) {
|
|
25
|
+
props.onCore(core);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{ immediate: true },
|
|
29
|
+
);
|
|
30
|
+
return {};
|
|
31
|
+
},
|
|
32
|
+
template: `<div />`,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("useFrontendTool v1 webmcp pass-through", () => {
|
|
36
|
+
it("re-registers tool when a reactive webmcp getter changes", async () => {
|
|
37
|
+
let coreRef: CopilotKitCoreVue | null = null;
|
|
38
|
+
|
|
39
|
+
const ToolComponent = defineComponent({
|
|
40
|
+
setup() {
|
|
41
|
+
const readOnly = ref(false);
|
|
42
|
+
|
|
43
|
+
useFrontendTool(
|
|
44
|
+
{
|
|
45
|
+
name: "v1WebmcpTool",
|
|
46
|
+
description: "A v1 WebMCP-exposed tool",
|
|
47
|
+
handler: async () => ({ ok: true }),
|
|
48
|
+
// Expose webmcp as a getter over reactive state: the registration
|
|
49
|
+
// must re-read it when it changes, not capture the initial value.
|
|
50
|
+
get webmcp() {
|
|
51
|
+
return { annotations: { readOnlyHint: readOnly.value } };
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
[readOnly],
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
readOnly,
|
|
59
|
+
toggle: () => {
|
|
60
|
+
readOnly.value = !readOnly.value;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
template: `
|
|
65
|
+
<button data-testid="toggle-btn" @click="toggle">
|
|
66
|
+
{{ readOnly ? "Read-write" : "Read-only" }}
|
|
67
|
+
</button>
|
|
68
|
+
`,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const Host = defineComponent({
|
|
72
|
+
components: { ToolComponent, CoreCapture },
|
|
73
|
+
setup() {
|
|
74
|
+
return {
|
|
75
|
+
setCore: (core: CopilotKitCoreVue) => {
|
|
76
|
+
coreRef = core;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
template: `
|
|
81
|
+
<div>
|
|
82
|
+
<ToolComponent />
|
|
83
|
+
<CoreCapture :on-core="setCore" />
|
|
84
|
+
</div>
|
|
85
|
+
`,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const ui = renderWithCopilotKit({
|
|
89
|
+
children: Host,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await waitFor(() => {
|
|
93
|
+
expect(coreRef).not.toBeNull();
|
|
94
|
+
const tool = coreRef!.tools.find(
|
|
95
|
+
(entry) => entry.name === "v1WebmcpTool",
|
|
96
|
+
);
|
|
97
|
+
expect(tool).toBeDefined();
|
|
98
|
+
expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: false } });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await fireEvent.click(screen.getByTestId("toggle-btn"));
|
|
102
|
+
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
const tool = coreRef!.tools.find(
|
|
105
|
+
(entry) => entry.name === "v1WebmcpTool",
|
|
106
|
+
);
|
|
107
|
+
expect(tool).toBeDefined();
|
|
108
|
+
expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: true } });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
ui.unmount();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* v2 composable (useFrontendTool, useHumanInTheLoop, or useRenderTool).
|
|
6
6
|
*/
|
|
7
7
|
import type { WatchSource } from "vue";
|
|
8
|
+
import type { WebMCPToolConfig } from "@copilotkit/core";
|
|
8
9
|
import type { Parameter, MappedParameterTypes } from "@copilotkit/shared";
|
|
9
10
|
import { getZodParameters, parseJson } from "@copilotkit/shared";
|
|
10
11
|
import { useFrontendTool as useFrontendToolV2 } from "../v2/hooks/use-frontend-tool";
|
|
@@ -38,6 +39,12 @@ export interface FrontendAction<T extends Parameter[] | [] = []> {
|
|
|
38
39
|
renderAndWaitForResponse?: VueFrontendTool<MappedParameterTypes<T>>["render"];
|
|
39
40
|
renderAndWait?: VueFrontendTool<MappedParameterTypes<T>>["render"];
|
|
40
41
|
agentId?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Also expose this action to browser agents through the WebMCP API
|
|
44
|
+
* (`document.modelContext`). `true` uses default annotations;
|
|
45
|
+
* `{ annotations }` provides WebMCP annotations.
|
|
46
|
+
*/
|
|
47
|
+
webmcp?: boolean | WebMCPToolConfig;
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
export interface CatchAllFrontendAction {
|
|
@@ -162,6 +169,12 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
|
|
|
162
169
|
render: wrapRenderWithJsonResult(typedAction.render),
|
|
163
170
|
available: normalizedAvailable,
|
|
164
171
|
agentId: typedAction.agentId,
|
|
172
|
+
// Read webmcp at watch time: the action object may expose it as a getter
|
|
173
|
+
// over reactive state, and capturing the initial value here would
|
|
174
|
+
// re-register a stale configuration.
|
|
175
|
+
get webmcp() {
|
|
176
|
+
return typedAction.webmcp;
|
|
177
|
+
},
|
|
165
178
|
},
|
|
166
179
|
deps,
|
|
167
180
|
);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* then delegates to the v2 composable.
|
|
6
6
|
*/
|
|
7
7
|
import type { WatchSource } from "vue";
|
|
8
|
+
import type { WebMCPToolConfig } from "@copilotkit/core";
|
|
8
9
|
import {
|
|
9
10
|
type Parameter,
|
|
10
11
|
type MappedParameterTypes,
|
|
@@ -23,6 +24,12 @@ export interface UseFrontendToolArgs<T extends Parameter[] | [] = []> {
|
|
|
23
24
|
available?: "disabled" | "enabled";
|
|
24
25
|
render?: VueFrontendTool<MappedParameterTypes<T>>["render"];
|
|
25
26
|
agentId?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Also expose this tool to browser agents through the WebMCP API
|
|
29
|
+
* (`document.modelContext`). `true` uses default annotations;
|
|
30
|
+
* `{ annotations }` provides WebMCP annotations.
|
|
31
|
+
*/
|
|
32
|
+
webmcp?: boolean | WebMCPToolConfig;
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
export function useFrontendTool<const T extends Parameter[] = []>(
|
|
@@ -70,6 +77,12 @@ export function useFrontendTool<const T extends Parameter[] = []>(
|
|
|
70
77
|
render: normalizedRender,
|
|
71
78
|
available: available === undefined ? undefined : available !== "disabled",
|
|
72
79
|
agentId,
|
|
80
|
+
// Read webmcp at watch time: the v1 args may expose it as a getter over
|
|
81
|
+
// reactive state, and capturing the initial value here would re-register
|
|
82
|
+
// a stale configuration.
|
|
83
|
+
get webmcp() {
|
|
84
|
+
return tool.webmcp;
|
|
85
|
+
},
|
|
73
86
|
},
|
|
74
87
|
deps,
|
|
75
88
|
);
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { defineComponent, ref, watch } from "vue";
|
|
2
|
+
import { screen, fireEvent, waitFor } from "@testing-library/vue";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
import { useFrontendTool } from "../use-frontend-tool";
|
|
6
|
+
import { useCopilotKit } from "../../providers/useCopilotKit";
|
|
7
|
+
import type { VueFrontendTool } from "../../types";
|
|
8
|
+
import type { CopilotKitCoreVue } from "../../lib/vue-core";
|
|
9
|
+
import { renderWithCopilotKit } from "../../__tests__/utils/test-helpers";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Component that captures the copilotkit core ref for test assertions.
|
|
13
|
+
*/
|
|
14
|
+
const CoreCapture = defineComponent({
|
|
15
|
+
props: {
|
|
16
|
+
onCore: {
|
|
17
|
+
type: Function as () => (core: CopilotKitCoreVue) => void,
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
setup(props) {
|
|
22
|
+
const { copilotkit } = useCopilotKit();
|
|
23
|
+
watch(
|
|
24
|
+
copilotkit,
|
|
25
|
+
(core) => {
|
|
26
|
+
if (core) {
|
|
27
|
+
props.onCore(core);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{ immediate: true },
|
|
31
|
+
);
|
|
32
|
+
return {};
|
|
33
|
+
},
|
|
34
|
+
template: `<div />`,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("useFrontendTool webmcp flag", () => {
|
|
38
|
+
it("registers tool with webmcp: true on the core", async () => {
|
|
39
|
+
let coreRef: CopilotKitCoreVue | null = null;
|
|
40
|
+
|
|
41
|
+
const ToolComponent = defineComponent({
|
|
42
|
+
setup() {
|
|
43
|
+
const tool: VueFrontendTool<{ msg: string }> = {
|
|
44
|
+
name: "webmcpTool",
|
|
45
|
+
description: "A WebMCP-exposed tool",
|
|
46
|
+
webmcp: true,
|
|
47
|
+
parameters: z.object({ msg: z.string() }),
|
|
48
|
+
handler: async () => ({ result: "ok" }),
|
|
49
|
+
};
|
|
50
|
+
useFrontendTool(tool);
|
|
51
|
+
return {};
|
|
52
|
+
},
|
|
53
|
+
template: `<div />`,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const Host = defineComponent({
|
|
57
|
+
components: { ToolComponent, CoreCapture },
|
|
58
|
+
setup() {
|
|
59
|
+
return {
|
|
60
|
+
setCore: (core: CopilotKitCoreVue) => {
|
|
61
|
+
coreRef = core;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
template: `
|
|
66
|
+
<div>
|
|
67
|
+
<ToolComponent />
|
|
68
|
+
<CoreCapture :on-core="setCore" />
|
|
69
|
+
</div>
|
|
70
|
+
`,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const ui = renderWithCopilotKit({
|
|
74
|
+
children: Host,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
await waitFor(() => {
|
|
78
|
+
expect(coreRef).not.toBeNull();
|
|
79
|
+
const tool = coreRef!.tools.find((entry) => entry.name === "webmcpTool");
|
|
80
|
+
expect(tool).toBeDefined();
|
|
81
|
+
expect(tool!.webmcp).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
ui.unmount();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("registers tool with webmcp annotations on the core", async () => {
|
|
88
|
+
let coreRef: CopilotKitCoreVue | null = null;
|
|
89
|
+
|
|
90
|
+
const ToolComponent = defineComponent({
|
|
91
|
+
setup() {
|
|
92
|
+
const tool: VueFrontendTool<{ msg: string }> = {
|
|
93
|
+
name: "annotatedTool",
|
|
94
|
+
description: "A WebMCP-exposed tool with annotations",
|
|
95
|
+
webmcp: { annotations: { readOnlyHint: true } },
|
|
96
|
+
parameters: z.object({ msg: z.string() }),
|
|
97
|
+
handler: async () => ({ result: "ok" }),
|
|
98
|
+
};
|
|
99
|
+
useFrontendTool(tool);
|
|
100
|
+
return {};
|
|
101
|
+
},
|
|
102
|
+
template: `<div />`,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const Host = defineComponent({
|
|
106
|
+
components: { ToolComponent, CoreCapture },
|
|
107
|
+
setup() {
|
|
108
|
+
return {
|
|
109
|
+
setCore: (core: CopilotKitCoreVue) => {
|
|
110
|
+
coreRef = core;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
template: `
|
|
115
|
+
<div>
|
|
116
|
+
<ToolComponent />
|
|
117
|
+
<CoreCapture :on-core="setCore" />
|
|
118
|
+
</div>
|
|
119
|
+
`,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const ui = renderWithCopilotKit({
|
|
123
|
+
children: Host,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await waitFor(() => {
|
|
127
|
+
expect(coreRef).not.toBeNull();
|
|
128
|
+
const tool = coreRef!.tools.find(
|
|
129
|
+
(entry) => entry.name === "annotatedTool",
|
|
130
|
+
);
|
|
131
|
+
expect(tool).toBeDefined();
|
|
132
|
+
expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: true } });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
ui.unmount();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("re-registers tool when the webmcp config changes", async () => {
|
|
139
|
+
let coreRef: CopilotKitCoreVue | null = null;
|
|
140
|
+
|
|
141
|
+
const ToolWithToggle = defineComponent({
|
|
142
|
+
setup() {
|
|
143
|
+
const readOnly = ref(false);
|
|
144
|
+
|
|
145
|
+
const tool: VueFrontendTool<{ data: string }> = {
|
|
146
|
+
name: "toggleWebmcpTool",
|
|
147
|
+
description: "A tool with a toggleable WebMCP config",
|
|
148
|
+
get webmcp() {
|
|
149
|
+
return { annotations: { readOnlyHint: readOnly.value } };
|
|
150
|
+
},
|
|
151
|
+
parameters: z.object({ data: z.string() }),
|
|
152
|
+
handler: async () => ({ ok: true }),
|
|
153
|
+
};
|
|
154
|
+
useFrontendTool(tool, [readOnly]);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
readOnly,
|
|
158
|
+
toggle: () => {
|
|
159
|
+
readOnly.value = !readOnly.value;
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
template: `
|
|
164
|
+
<button data-testid="toggle-btn" @click="toggle">
|
|
165
|
+
{{ readOnly ? "Read-write" : "Read-only" }}
|
|
166
|
+
</button>
|
|
167
|
+
`,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const Host = defineComponent({
|
|
171
|
+
components: { ToolWithToggle, CoreCapture },
|
|
172
|
+
setup() {
|
|
173
|
+
return {
|
|
174
|
+
setCore: (core: CopilotKitCoreVue) => {
|
|
175
|
+
coreRef = core;
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
template: `
|
|
180
|
+
<div>
|
|
181
|
+
<ToolWithToggle />
|
|
182
|
+
<CoreCapture :on-core="setCore" />
|
|
183
|
+
</div>
|
|
184
|
+
`,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const ui = renderWithCopilotKit({
|
|
188
|
+
children: Host,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await waitFor(() => {
|
|
192
|
+
expect(coreRef).not.toBeNull();
|
|
193
|
+
const tool = coreRef!.tools.find(
|
|
194
|
+
(entry) => entry.name === "toggleWebmcpTool",
|
|
195
|
+
);
|
|
196
|
+
expect(tool).toBeDefined();
|
|
197
|
+
expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: false } });
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await fireEvent.click(screen.getByTestId("toggle-btn"));
|
|
201
|
+
|
|
202
|
+
await waitFor(() => {
|
|
203
|
+
const tool = coreRef!.tools.find(
|
|
204
|
+
(entry) => entry.name === "toggleWebmcpTool",
|
|
205
|
+
);
|
|
206
|
+
expect(tool).toBeDefined();
|
|
207
|
+
expect(tool!.webmcp).toEqual({ annotations: { readOnlyHint: true } });
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
ui.unmount();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("registers and cleans up agent-scoped webmcp tools on the core", async () => {
|
|
214
|
+
let coreRef: CopilotKitCoreVue | null = null;
|
|
215
|
+
|
|
216
|
+
const ToolComponent = defineComponent({
|
|
217
|
+
setup() {
|
|
218
|
+
useFrontendTool({
|
|
219
|
+
name: "scopedTool",
|
|
220
|
+
description: "A WebMCP tool scoped to alpha",
|
|
221
|
+
agentId: "alpha",
|
|
222
|
+
webmcp: { annotations: { readOnlyHint: true } },
|
|
223
|
+
handler: async () => ({ ok: true }),
|
|
224
|
+
});
|
|
225
|
+
useFrontendTool({
|
|
226
|
+
name: "scopedTool",
|
|
227
|
+
description: "A WebMCP tool scoped to beta",
|
|
228
|
+
agentId: "beta",
|
|
229
|
+
webmcp: true,
|
|
230
|
+
handler: async () => ({ ok: true }),
|
|
231
|
+
});
|
|
232
|
+
return {};
|
|
233
|
+
},
|
|
234
|
+
template: `<div />`,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const Host = defineComponent({
|
|
238
|
+
components: { ToolComponent, CoreCapture },
|
|
239
|
+
setup() {
|
|
240
|
+
return {
|
|
241
|
+
setCore: (core: CopilotKitCoreVue) => {
|
|
242
|
+
coreRef = core;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
},
|
|
246
|
+
template: `
|
|
247
|
+
<div>
|
|
248
|
+
<ToolComponent />
|
|
249
|
+
<CoreCapture :on-core="setCore" />
|
|
250
|
+
</div>
|
|
251
|
+
`,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const ui = renderWithCopilotKit({
|
|
255
|
+
children: Host,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
await waitFor(() => {
|
|
259
|
+
expect(coreRef).not.toBeNull();
|
|
260
|
+
const alphaTool = coreRef!.tools.find(
|
|
261
|
+
(entry) => entry.name === "scopedTool" && entry.agentId === "alpha",
|
|
262
|
+
);
|
|
263
|
+
expect(alphaTool).toBeDefined();
|
|
264
|
+
expect(alphaTool!.webmcp).toEqual({
|
|
265
|
+
annotations: { readOnlyHint: true },
|
|
266
|
+
});
|
|
267
|
+
const betaTool = coreRef!.tools.find(
|
|
268
|
+
(entry) => entry.name === "scopedTool" && entry.agentId === "beta",
|
|
269
|
+
);
|
|
270
|
+
expect(betaTool).toBeDefined();
|
|
271
|
+
expect(betaTool!.webmcp).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
ui.unmount();
|
|
275
|
+
|
|
276
|
+
await waitFor(() => {
|
|
277
|
+
expect(
|
|
278
|
+
coreRef!.tools.filter((entry) => entry.name === "scopedTool").length,
|
|
279
|
+
).toBe(0);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -32,6 +32,9 @@ export function useFrontendTool<T extends Record<string, unknown>>(
|
|
|
32
32
|
[
|
|
33
33
|
() => tool.name,
|
|
34
34
|
() => tool.available,
|
|
35
|
+
// webmcp is commonly an object literal, so compare it by value to avoid
|
|
36
|
+
// re-registering on identity churn while still reacting to real changes.
|
|
37
|
+
() => JSON.stringify(tool.webmcp ?? null),
|
|
35
38
|
() => extraDeps.length,
|
|
36
39
|
...extraDeps,
|
|
37
40
|
],
|