@copilotkit/vue 1.59.1 → 1.59.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/index.cjs +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{use-render-activity-message-CE0GKV5B.js → use-render-activity-message-C6mweDb2.js} +1356 -1232
- package/dist/use-render-activity-message-C6mweDb2.js.map +1 -0
- package/dist/{use-render-activity-message-D7Ve-ASg.cjs → use-render-activity-message-CiFAWxYV.cjs} +18 -18
- package/dist/use-render-activity-message-CiFAWxYV.cjs.map +1 -0
- package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/package.json +4 -4
- package/src/v2/components/chat/CopilotChatMessageView.vue +1 -1
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.ts +2 -2
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.ts +1 -1
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.testid.spec.ts +17 -0
- package/src/v2/hooks/__tests__/use-default-render-tool.test.ts +374 -8
- package/src/v2/hooks/use-default-render-tool.ts +247 -59
- package/dist/use-render-activity-message-CE0GKV5B.js.map +0 -1
- package/dist/use-render-activity-message-D7Ve-ASg.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-default-render-tool.d.ts","sourceRoot":"","sources":["../../../src/v2/hooks/use-default-render-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"use-default-render-tool.d.ts","sourceRoot":"","sources":["../../../src/v2/hooks/use-default-render-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAIjD,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,YAAY,GAAG,WAAW,GAAG,UAAU,CAAC;IAChD,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,CAAC;AAoPF,wBAAgB,oBAAoB,CAClC,MAAM,CAAC,EAAE;IACP,MAAM,CAAC,EACH,CAAC,CAAC,KAAK,EAAE,kBAAkB,KAAK,UAAU,CAAC,GAC3C,SAAS,CAAC,kBAAkB,CAAC,CAAC;CACnC,EACD,IAAI,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,GAC5B,IAAI,CAsDN"}
|
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-CiFAWxYV.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.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;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 S, l as m, m as v, 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 K, z as O, G as w, L as G, M as L, B as H, D as B, O as D, E as F, F as Q, H as j, I as k, U as q, J 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 } 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 S, l as m, m as v, 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 K, z as O, G as w, L as G, M as L, B as H, D as B, O as D, E as F, F as Q, H as j, I as k, U as q, J 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 } from "../use-render-activity-message-C6mweDb2.js";
|
|
4
4
|
import { createLicenseContextValue as me } 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.59.
|
|
3
|
+
"version": "1.59.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Vue 3 components and composables for CopilotKit",
|
|
6
6
|
"keywords": [
|
|
@@ -70,9 +70,9 @@
|
|
|
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.59.
|
|
74
|
-
"@copilotkit/
|
|
75
|
-
"@copilotkit/
|
|
73
|
+
"@copilotkit/core": "1.59.2",
|
|
74
|
+
"@copilotkit/web-inspector": "1.59.2",
|
|
75
|
+
"@copilotkit/shared": "1.59.2"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@eslint/js": "^9.39.2",
|
|
@@ -469,7 +469,7 @@ function resolveToolMessage(
|
|
|
469
469
|
<slot v-if="showCursor" name="cursor">
|
|
470
470
|
<div
|
|
471
471
|
class="cpk:w-[11px] cpk:h-[11px] cpk:rounded-full cpk:bg-foreground cpk:animate-pulse cpk:ml-1"
|
|
472
|
-
data-testid="copilot-
|
|
472
|
+
data-testid="copilot-loading-cursor"
|
|
473
473
|
/>
|
|
474
474
|
</slot>
|
|
475
475
|
</div>
|
|
@@ -1069,7 +1069,7 @@ describe("CopilotChat E2E - Chat Basics and Streaming Patterns", () => {
|
|
|
1069
1069
|
);
|
|
1070
1070
|
|
|
1071
1071
|
await waitFor(() => {
|
|
1072
|
-
const chatLevelCursor = screen.queryByTestId("copilot-
|
|
1072
|
+
const chatLevelCursor = screen.queryByTestId("copilot-loading-cursor");
|
|
1073
1073
|
expect(chatLevelCursor).toBeNull();
|
|
1074
1074
|
});
|
|
1075
1075
|
|
|
@@ -1098,7 +1098,7 @@ describe("CopilotChat E2E - Chat Basics and Streaming Patterns", () => {
|
|
|
1098
1098
|
});
|
|
1099
1099
|
|
|
1100
1100
|
await waitFor(() => {
|
|
1101
|
-
const chatLevelCursor = screen.queryByTestId("copilot-
|
|
1101
|
+
const chatLevelCursor = screen.queryByTestId("copilot-loading-cursor");
|
|
1102
1102
|
expect(chatLevelCursor).not.toBeNull();
|
|
1103
1103
|
});
|
|
1104
1104
|
|
|
@@ -179,7 +179,7 @@ function jsonResponse(body: unknown, status = 200): Response {
|
|
|
179
179
|
|
|
180
180
|
async function submitMessageAndWaitForUserMessage(value: string) {
|
|
181
181
|
await waitFor(() => {
|
|
182
|
-
expect(screen.queryByTestId("copilot-
|
|
182
|
+
expect(screen.queryByTestId("copilot-loading-cursor")).toBeNull();
|
|
183
183
|
});
|
|
184
184
|
|
|
185
185
|
const input = await screen.findByRole("textbox");
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Verifies the vue MessageView cursor renders the stable
|
|
6
|
+
* `copilot-loading-cursor` testid so e2e tests can deterministically detect
|
|
7
|
+
* the "still loading" state. Aligns with the v2 react-core convention.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const viewPath = resolve(__dirname, "../CopilotChatMessageView.vue");
|
|
11
|
+
const viewSrc = readFileSync(viewPath, "utf-8");
|
|
12
|
+
|
|
13
|
+
describe("vue stable testids", () => {
|
|
14
|
+
it("CopilotChatMessageView cursor renders the copilot-loading-cursor testid", () => {
|
|
15
|
+
expect(viewSrc).toMatch(/data-testid="copilot-loading-cursor"/);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -46,7 +46,7 @@ describe("useDefaultRenderTool", () => {
|
|
|
46
46
|
|
|
47
47
|
it("forwards custom render function and deps", () => {
|
|
48
48
|
const customRender = vi.fn(() => "custom");
|
|
49
|
-
const deps = ["compact"]
|
|
49
|
+
const deps = [() => "compact"];
|
|
50
50
|
|
|
51
51
|
const Harness = defineComponent({
|
|
52
52
|
setup() {
|
|
@@ -54,7 +54,7 @@ describe("useDefaultRenderTool", () => {
|
|
|
54
54
|
{
|
|
55
55
|
render: customRender,
|
|
56
56
|
},
|
|
57
|
-
deps
|
|
57
|
+
deps,
|
|
58
58
|
);
|
|
59
59
|
return {};
|
|
60
60
|
},
|
|
@@ -65,12 +65,33 @@ describe("useDefaultRenderTool", () => {
|
|
|
65
65
|
|
|
66
66
|
expect(mockUseRenderTool).toHaveBeenCalledTimes(1);
|
|
67
67
|
const [config, forwardedDeps] = mockUseRenderTool.mock.calls[0] as [
|
|
68
|
-
{
|
|
68
|
+
{
|
|
69
|
+
name: string;
|
|
70
|
+
render: (props: {
|
|
71
|
+
name: string;
|
|
72
|
+
toolCallId: string;
|
|
73
|
+
args: unknown;
|
|
74
|
+
status: string;
|
|
75
|
+
result: string | undefined;
|
|
76
|
+
}) => unknown;
|
|
77
|
+
},
|
|
69
78
|
unknown[],
|
|
70
79
|
];
|
|
71
80
|
|
|
72
81
|
expect(config.name).toBe("*");
|
|
73
|
-
|
|
82
|
+
// The registered render is a wrapper that adapts RawRendererProps →
|
|
83
|
+
// DefaultRenderProps before invoking the user's render, so the user
|
|
84
|
+
// function is not the registered render by reference. Verify the
|
|
85
|
+
// wrapper forwards correctly instead.
|
|
86
|
+
expect(typeof config.render).toBe("function");
|
|
87
|
+
config.render({
|
|
88
|
+
name: "x",
|
|
89
|
+
toolCallId: "tc-1",
|
|
90
|
+
args: { a: 1 },
|
|
91
|
+
status: "complete",
|
|
92
|
+
result: "ok",
|
|
93
|
+
});
|
|
94
|
+
expect(customRender).toHaveBeenCalledTimes(1);
|
|
74
95
|
expect(forwardedDeps).toBe(deps);
|
|
75
96
|
});
|
|
76
97
|
|
|
@@ -114,12 +135,24 @@ describe("useDefaultRenderTool", () => {
|
|
|
114
135
|
});
|
|
115
136
|
});
|
|
116
137
|
|
|
117
|
-
|
|
138
|
+
// F14: component-typed render must receive adapted DefaultRenderProps
|
|
139
|
+
// (parameters + string-union status), not the raw call-site shape (args).
|
|
140
|
+
// The registered render is a WRAPPER that runs adaptRendererProps and
|
|
141
|
+
// forwards to the user's component — not the component itself by reference.
|
|
142
|
+
it("forwards custom render component with adapted DefaultRenderProps", async () => {
|
|
143
|
+
const receivedProps = vi.fn();
|
|
118
144
|
const customRender = defineComponent({
|
|
119
145
|
props: {
|
|
120
146
|
name: { type: String, required: true },
|
|
147
|
+
toolCallId: { type: String, required: true },
|
|
148
|
+
parameters: { type: null, required: false, default: undefined },
|
|
149
|
+
status: { type: String, required: true },
|
|
150
|
+
result: { type: null, required: false, default: undefined },
|
|
151
|
+
},
|
|
152
|
+
setup(props) {
|
|
153
|
+
receivedProps({ ...props });
|
|
154
|
+
return () => null;
|
|
121
155
|
},
|
|
122
|
-
template: `<div>{{ name }}</div>`,
|
|
123
156
|
});
|
|
124
157
|
|
|
125
158
|
const Harness = defineComponent({
|
|
@@ -135,11 +168,131 @@ describe("useDefaultRenderTool", () => {
|
|
|
135
168
|
render(Harness);
|
|
136
169
|
|
|
137
170
|
const [config] = mockUseRenderTool.mock.calls[0] as [
|
|
138
|
-
{
|
|
171
|
+
{
|
|
172
|
+
name: string;
|
|
173
|
+
render: (props: unknown) => unknown;
|
|
174
|
+
},
|
|
139
175
|
];
|
|
140
176
|
|
|
141
177
|
expect(config.name).toBe("*");
|
|
142
|
-
|
|
178
|
+
// Wrapper, not reference equality.
|
|
179
|
+
expect(typeof config.render).toBe("function");
|
|
180
|
+
|
|
181
|
+
// Render the wrapper with the RAW call-site shape (args + enum-string status).
|
|
182
|
+
const Wrapper = defineComponent({
|
|
183
|
+
setup() {
|
|
184
|
+
return () =>
|
|
185
|
+
(config.render as (p: unknown) => unknown)({
|
|
186
|
+
name: "searchDocs",
|
|
187
|
+
toolCallId: "tc-component-adapt",
|
|
188
|
+
args: { query: "copilot" },
|
|
189
|
+
status: "complete",
|
|
190
|
+
result: "ok",
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
render(Wrapper);
|
|
196
|
+
|
|
197
|
+
expect(receivedProps).toHaveBeenCalled();
|
|
198
|
+
const adapted = receivedProps.mock.calls[0][0] as Record<string, unknown>;
|
|
199
|
+
expect(adapted.parameters).toEqual({ query: "copilot" });
|
|
200
|
+
expect(adapted.status).toBe("complete");
|
|
201
|
+
expect(adapted.toolCallId).toBe("tc-component-adapt");
|
|
202
|
+
expect(adapted.result).toBe("ok");
|
|
203
|
+
expect(adapted.name).toBe("searchDocs");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// F11: result prop is typeless (type: null) so a non-string result is rendered
|
|
207
|
+
// safely via safeStringifyForPre and serialized into data-result without
|
|
208
|
+
// Vue dev-mode type warnings (no "Invalid prop: type check failed for prop
|
|
209
|
+
// 'result'" noise).
|
|
210
|
+
it("default renderer handles non-string result via safe stringify with no Vue type warning", async () => {
|
|
211
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
212
|
+
const Harness = defineComponent({
|
|
213
|
+
setup() {
|
|
214
|
+
useDefaultRenderTool();
|
|
215
|
+
return {};
|
|
216
|
+
},
|
|
217
|
+
template: `<div />`,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
render(Harness);
|
|
221
|
+
|
|
222
|
+
const [config] = mockUseRenderTool.mock.calls[0] as [{ render: unknown }];
|
|
223
|
+
const DefaultRenderer = config.render;
|
|
224
|
+
const structuredResult = { ok: true, count: 3 };
|
|
225
|
+
|
|
226
|
+
render(DefaultRenderer as any, {
|
|
227
|
+
props: {
|
|
228
|
+
name: "searchDocs",
|
|
229
|
+
toolCallId: "tc-nonstring-result",
|
|
230
|
+
parameters: { query: "copilot" },
|
|
231
|
+
status: "complete",
|
|
232
|
+
result: structuredResult,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const wrapper = screen.getByTestId("copilot-tool-render");
|
|
237
|
+
expect(wrapper.getAttribute("data-result")).toBe(
|
|
238
|
+
JSON.stringify(structuredResult),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
await fireEvent.click(screen.getByText("searchDocs"));
|
|
242
|
+
expect(screen.getByText("Result")).toBeDefined();
|
|
243
|
+
// The stringified payload appears in the <pre>.
|
|
244
|
+
expect(screen.getByText(/"count": 3/)).toBeDefined();
|
|
245
|
+
|
|
246
|
+
// The "result" prop must be typeless so non-string values do not trip
|
|
247
|
+
// Vue's runtime type validator (dev-mode warn).
|
|
248
|
+
const offending = warnSpy.mock.calls.filter((call) =>
|
|
249
|
+
String(call[0] ?? "").includes(
|
|
250
|
+
'Invalid prop: type check failed for prop "result"',
|
|
251
|
+
),
|
|
252
|
+
);
|
|
253
|
+
expect(offending.length).toBe(0);
|
|
254
|
+
warnSpy.mockRestore();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// F9: warn-on-unknown-status is deduplicated per distinct value.
|
|
258
|
+
it("warns at most once for the same unknown status across renders", () => {
|
|
259
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
260
|
+
|
|
261
|
+
const customRender = vi.fn(() => "custom");
|
|
262
|
+
|
|
263
|
+
const Harness = defineComponent({
|
|
264
|
+
setup() {
|
|
265
|
+
useDefaultRenderTool({ render: customRender });
|
|
266
|
+
return {};
|
|
267
|
+
},
|
|
268
|
+
template: `<div />`,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
render(Harness);
|
|
272
|
+
|
|
273
|
+
const [config] = mockUseRenderTool.mock.calls[0] as [
|
|
274
|
+
{ render: (props: unknown) => unknown },
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
const unknownStatus = "vue-unknown-status-xyz";
|
|
278
|
+
|
|
279
|
+
// Invoke 3 times with the same unknown status; should warn ONCE total
|
|
280
|
+
// for this value.
|
|
281
|
+
for (let i = 0; i < 3; i++) {
|
|
282
|
+
config.render({
|
|
283
|
+
name: "searchDocs",
|
|
284
|
+
toolCallId: `tc-unknown-${i}`,
|
|
285
|
+
args: {},
|
|
286
|
+
status: unknownStatus,
|
|
287
|
+
result: undefined,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const matching = warnSpy.mock.calls.filter((call) =>
|
|
292
|
+
String(call[0] ?? "").includes(unknownStatus),
|
|
293
|
+
);
|
|
294
|
+
expect(matching.length).toBe(1);
|
|
295
|
+
warnSpy.mockRestore();
|
|
143
296
|
});
|
|
144
297
|
|
|
145
298
|
it("default renderer shows status and expands to show parameters/result", async () => {
|
|
@@ -211,4 +364,217 @@ describe("useDefaultRenderTool", () => {
|
|
|
211
364
|
expect(screen.getByText("Result")).toBeDefined();
|
|
212
365
|
expect(screen.getByText("done")).toBeDefined();
|
|
213
366
|
});
|
|
367
|
+
|
|
368
|
+
it("default renderer emits stable copilot-tool-render testid and metadata attrs", () => {
|
|
369
|
+
const Harness = defineComponent({
|
|
370
|
+
setup() {
|
|
371
|
+
useDefaultRenderTool();
|
|
372
|
+
return {};
|
|
373
|
+
},
|
|
374
|
+
template: `<div />`,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
render(Harness);
|
|
378
|
+
|
|
379
|
+
const [config] = mockUseRenderTool.mock.calls[0] as [
|
|
380
|
+
{
|
|
381
|
+
render: unknown;
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
const DefaultRenderer = config.render;
|
|
386
|
+
render(DefaultRenderer as any, {
|
|
387
|
+
props: {
|
|
388
|
+
name: "searchDocs",
|
|
389
|
+
toolCallId: "tc-testid-1",
|
|
390
|
+
parameters: { query: "copilot" },
|
|
391
|
+
status: "complete",
|
|
392
|
+
result: "ok",
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const wrapper = screen.getByTestId("copilot-tool-render");
|
|
397
|
+
expect(wrapper).toBeDefined();
|
|
398
|
+
expect(wrapper.getAttribute("data-tool-name")).toBe("searchDocs");
|
|
399
|
+
expect(wrapper.getAttribute("data-status")).toBe("complete");
|
|
400
|
+
expect(wrapper.getAttribute("data-args")).toBe(
|
|
401
|
+
JSON.stringify({ query: "copilot" }),
|
|
402
|
+
);
|
|
403
|
+
expect(wrapper.getAttribute("data-result")).toBe("ok");
|
|
404
|
+
expect(screen.getByTestId("copilot-tool-render-name").textContent).toBe(
|
|
405
|
+
"searchDocs",
|
|
406
|
+
);
|
|
407
|
+
expect(screen.getByTestId("copilot-tool-render-status").textContent).toBe(
|
|
408
|
+
"Done",
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Fix #1: a11y — vue version already uses <button>; assert aria-expanded toggles.
|
|
413
|
+
it("default renderer header is a button with aria-expanded that toggles", async () => {
|
|
414
|
+
const Harness = defineComponent({
|
|
415
|
+
setup() {
|
|
416
|
+
useDefaultRenderTool();
|
|
417
|
+
return {};
|
|
418
|
+
},
|
|
419
|
+
template: `<div />`,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
render(Harness);
|
|
423
|
+
|
|
424
|
+
const [config] = mockUseRenderTool.mock.calls[0] as [
|
|
425
|
+
{
|
|
426
|
+
render: unknown;
|
|
427
|
+
},
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
const DefaultRenderer = config.render;
|
|
431
|
+
render(DefaultRenderer as any, {
|
|
432
|
+
props: {
|
|
433
|
+
name: "searchDocs",
|
|
434
|
+
toolCallId: "tc-a11y",
|
|
435
|
+
parameters: { query: "copilot" },
|
|
436
|
+
status: "executing",
|
|
437
|
+
result: undefined,
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const nameNode = screen.getByTestId("copilot-tool-render-name");
|
|
442
|
+
const headerButton = nameNode.closest("button");
|
|
443
|
+
expect(headerButton).not.toBeNull();
|
|
444
|
+
expect(headerButton!.getAttribute("type")).toBe("button");
|
|
445
|
+
expect(headerButton!.getAttribute("aria-expanded")).toBe("false");
|
|
446
|
+
|
|
447
|
+
await fireEvent.click(headerButton!);
|
|
448
|
+
expect(headerButton!.getAttribute("aria-expanded")).toBe("true");
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Fix #3: data-tool-call-id is emitted on the vue wrapper too.
|
|
452
|
+
it("default renderer emits data-tool-call-id on the wrapper element", () => {
|
|
453
|
+
const Harness = defineComponent({
|
|
454
|
+
setup() {
|
|
455
|
+
useDefaultRenderTool();
|
|
456
|
+
return {};
|
|
457
|
+
},
|
|
458
|
+
template: `<div />`,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
render(Harness);
|
|
462
|
+
|
|
463
|
+
const [config] = mockUseRenderTool.mock.calls[0] as [
|
|
464
|
+
{
|
|
465
|
+
render: unknown;
|
|
466
|
+
},
|
|
467
|
+
];
|
|
468
|
+
|
|
469
|
+
const DefaultRenderer = config.render;
|
|
470
|
+
render(DefaultRenderer as any, {
|
|
471
|
+
props: {
|
|
472
|
+
name: "searchDocs",
|
|
473
|
+
toolCallId: "tc-id-emit",
|
|
474
|
+
parameters: { query: "copilot" },
|
|
475
|
+
status: "complete",
|
|
476
|
+
result: "ok",
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const wrapper = screen.getByTestId("copilot-tool-render");
|
|
481
|
+
expect(wrapper.getAttribute("data-tool-call-id")).toBe("tc-id-emit");
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Fix #4: opt-in config.render receives adapted DefaultRenderProps shape
|
|
485
|
+
// (parameters, string-union status) — not the raw renderer signature.
|
|
486
|
+
it("opt-in config.render receives parameters (not args) and string-union status", () => {
|
|
487
|
+
const customRender = vi.fn(() => "custom");
|
|
488
|
+
|
|
489
|
+
const Harness = defineComponent({
|
|
490
|
+
setup() {
|
|
491
|
+
useDefaultRenderTool({ render: customRender });
|
|
492
|
+
return {};
|
|
493
|
+
},
|
|
494
|
+
template: `<div />`,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
render(Harness);
|
|
498
|
+
|
|
499
|
+
const [config] = mockUseRenderTool.mock.calls[0] as [
|
|
500
|
+
{
|
|
501
|
+
name: string;
|
|
502
|
+
render: (props: {
|
|
503
|
+
name: string;
|
|
504
|
+
toolCallId: string;
|
|
505
|
+
args: unknown;
|
|
506
|
+
status: string;
|
|
507
|
+
result: string | undefined;
|
|
508
|
+
}) => unknown;
|
|
509
|
+
},
|
|
510
|
+
];
|
|
511
|
+
|
|
512
|
+
// Simulate what CopilotChatToolCallsView actually passes:
|
|
513
|
+
// { name, toolCallId, args, status: ToolCallStatus, result }
|
|
514
|
+
config.render({
|
|
515
|
+
name: "searchDocs",
|
|
516
|
+
toolCallId: "tc-adapt-1",
|
|
517
|
+
args: { query: "copilot" },
|
|
518
|
+
status: "complete",
|
|
519
|
+
result: "ok",
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
expect(customRender).toHaveBeenCalledTimes(1);
|
|
523
|
+
const forwarded = customRender.mock.calls[0]?.[0] as Record<
|
|
524
|
+
string,
|
|
525
|
+
unknown
|
|
526
|
+
>;
|
|
527
|
+
expect(forwarded.parameters).toEqual({ query: "copilot" });
|
|
528
|
+
expect(forwarded.status).toBe("complete");
|
|
529
|
+
expect(forwarded.toolCallId).toBe("tc-adapt-1");
|
|
530
|
+
expect(forwarded.result).toBe("ok");
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Fix #5: circular-ref parameters must not crash the vue render; log.
|
|
534
|
+
it("default renderer survives circular-ref parameters and logs", async () => {
|
|
535
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
536
|
+
|
|
537
|
+
const Harness = defineComponent({
|
|
538
|
+
setup() {
|
|
539
|
+
useDefaultRenderTool();
|
|
540
|
+
return {};
|
|
541
|
+
},
|
|
542
|
+
template: `<div />`,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
render(Harness);
|
|
546
|
+
|
|
547
|
+
const [config] = mockUseRenderTool.mock.calls[0] as [
|
|
548
|
+
{
|
|
549
|
+
render: unknown;
|
|
550
|
+
},
|
|
551
|
+
];
|
|
552
|
+
|
|
553
|
+
const DefaultRenderer = config.render;
|
|
554
|
+
|
|
555
|
+
const circular: Record<string, unknown> = { a: 1 };
|
|
556
|
+
circular.self = circular;
|
|
557
|
+
|
|
558
|
+
expect(() =>
|
|
559
|
+
render(DefaultRenderer as any, {
|
|
560
|
+
props: {
|
|
561
|
+
name: "circ",
|
|
562
|
+
toolCallId: "tc-circular",
|
|
563
|
+
parameters: circular,
|
|
564
|
+
status: "executing",
|
|
565
|
+
result: undefined,
|
|
566
|
+
},
|
|
567
|
+
}),
|
|
568
|
+
).not.toThrow();
|
|
569
|
+
|
|
570
|
+
const headerButton = screen
|
|
571
|
+
.getByTestId("copilot-tool-render-name")
|
|
572
|
+
.closest("button");
|
|
573
|
+
if (headerButton) {
|
|
574
|
+
await expect(fireEvent.click(headerButton)).resolves.not.toThrow();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
578
|
+
warnSpy.mockRestore();
|
|
579
|
+
});
|
|
214
580
|
});
|