@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.
@@ -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;AAGjD,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;AA8FF,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,CAiBN"}
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-D7Ve-ASg.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]})});
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-CE0GKV5B.js";
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.1",
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.1",
74
- "@copilotkit/shared": "1.59.1",
75
- "@copilotkit/web-inspector": "1.59.1"
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-chat-cursor"
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-chat-cursor");
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-chat-cursor");
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-chat-cursor")).toBeNull();
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"] as const;
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 as unknown as any[],
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
- { name: string; render: typeof customRender },
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
- expect(config.render).toBe(customRender);
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
- it("forwards custom render component", () => {
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
- { name: string; render: typeof customRender },
171
+ {
172
+ name: string;
173
+ render: (props: unknown) => unknown;
174
+ },
139
175
  ];
140
176
 
141
177
  expect(config.name).toBe("*");
142
- expect(config.render).toBe(customRender);
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
  });