@copilotkit/vue 1.83.0 → 2.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/AGENTS.md +1 -1
  2. package/dist/index.cjs +76 -1
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.mjs +14290 -287
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/styles.css +1 -1
  7. package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -1
  8. package/dist/v2/components/a2ui/catalog.d.ts +1 -1
  9. package/dist/v2/components/a2ui/catalog.d.ts.map +1 -1
  10. package/dist/v2/components/chat/BasicMarkdown.vue.d.ts +9 -0
  11. package/dist/v2/components/chat/BasicMarkdown.vue.d.ts.map +1 -0
  12. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
  13. package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
  14. package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts +10 -0
  15. package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts.map +1 -0
  16. package/dist/v2/components/chat/index.d.ts +2 -0
  17. package/dist/v2/components/chat/index.d.ts.map +1 -1
  18. package/dist/v2/hooks/index.d.ts +0 -1
  19. package/dist/v2/hooks/index.d.ts.map +1 -1
  20. package/dist/v2/hooks/use-pin-to-send.d.ts +1 -1
  21. package/dist/v2/index.cjs +1 -1
  22. package/dist/v2/index.mjs +195 -188
  23. package/dist/v2/providers/CopilotKitProvider.types.d.ts +10 -0
  24. package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
  25. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
  26. package/dist/v2/providers/index.d.ts +1 -0
  27. package/dist/v2/providers/index.d.ts.map +1 -1
  28. package/dist/v2/providers/markdown-renderer.d.ts +23 -0
  29. package/dist/v2/providers/markdown-renderer.d.ts.map +1 -0
  30. package/package.json +8 -9
  31. package/src/styles/globals.css +0 -97
  32. package/src/v2/components/A2UIMessageRenderer.ts +3 -2
  33. package/src/v2/components/a2ui/catalog.ts +4 -2
  34. package/src/v2/components/chat/BasicMarkdown.vue +158 -0
  35. package/src/v2/components/chat/CopilotChatAssistantMessage.vue +27 -536
  36. package/src/v2/components/chat/CopilotChatReasoningMessage.vue +7 -2
  37. package/src/v2/components/chat/StreamingMarkdownDefault.vue +57 -0
  38. package/src/v2/components/chat/__tests__/BasicMarkdown.test.ts +69 -0
  39. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.ts +53 -16
  40. package/src/v2/components/chat/__tests__/CopilotChat.licenseWarning.test.ts +5 -7
  41. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.markdown.test.ts +54 -0
  42. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.ts +0 -41
  43. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.ts +18 -4
  44. package/src/v2/components/chat/__tests__/markdown-renderer-config.e2e.test.ts +59 -0
  45. package/src/v2/components/chat/index.ts +2 -0
  46. package/src/v2/hooks/__tests__/use-agent.e2e.test.ts +7 -3
  47. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +7 -3
  48. package/src/v2/hooks/index.ts +0 -1
  49. package/src/v2/hooks/use-pin-to-send.ts +1 -1
  50. package/src/v2/providers/CopilotKitProvider.types.ts +10 -0
  51. package/src/v2/providers/CopilotKitProvider.vue +2 -0
  52. package/src/v2/providers/__tests__/CopilotKitProvider.headersAtSendTime.test.ts +1 -1
  53. package/src/v2/providers/__tests__/markdown-renderer.test.ts +40 -0
  54. package/src/v2/providers/index.ts +1 -0
  55. package/src/v2/providers/markdown-renderer.ts +39 -0
  56. package/vite.config.ts +1 -3
  57. package/dist/use-render-activity-message-B7sDGUl4.cjs +0 -70
  58. package/dist/use-render-activity-message-B7sDGUl4.cjs.map +0 -1
  59. package/dist/use-render-activity-message-mQvByEYF.js +0 -12544
  60. package/dist/use-render-activity-message-mQvByEYF.js.map +0 -1
  61. package/dist/v2/hooks/use-katex-styles.d.ts +0 -22
  62. package/dist/v2/hooks/use-katex-styles.d.ts.map +0 -1
  63. package/src/v2/hooks/__tests__/use-katex-styles.test.ts +0 -69
  64. package/src/v2/hooks/use-katex-styles.ts +0 -44
@@ -1,25 +1,22 @@
1
1
  <script setup lang="ts">
2
- import {
3
- computed,
4
- defineComponent,
5
- h,
6
- onBeforeUnmount,
7
- onMounted,
8
- ref,
9
- } from "vue";
2
+ import { computed, onBeforeUnmount, ref } from "vue";
3
+ import type { Component } from "vue";
10
4
  import type { AssistantMessage, Message } from "@ag-ui/core";
11
- import { StreamMarkdown } from "streamdown-vue";
12
5
  import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
13
6
  import { CopilotChatDefaultLabels } from "../../providers/types";
14
7
  import {
15
8
  IconCheck,
16
9
  IconCopy,
17
- IconDownload,
18
10
  IconRefreshCw,
19
11
  IconThumbsDown,
20
12
  IconThumbsUp,
21
13
  IconVolume2,
22
14
  } from "../icons";
15
+ import StreamingMarkdownDefault from "./StreamingMarkdownDefault.vue";
16
+ import {
17
+ useMarkdownRenderer,
18
+ isVueComponentRenderer,
19
+ } from "../../providers/markdown-renderer";
23
20
  import CopilotChatToolCallsView from "./CopilotChatToolCallsView.vue";
24
21
  import type {
25
22
  CopilotChatAssistantMessageCopyButtonSlotProps,
@@ -32,10 +29,6 @@ import type {
32
29
  CopilotChatAssistantMessageToolCallsViewSlotProps,
33
30
  CopilotChatAssistantMessageToolbarSlotProps,
34
31
  } from "./types";
35
- import { useKatexStyles } from "../../hooks/use-katex-styles";
36
-
37
- useKatexStyles();
38
-
39
32
  const props = withDefaults(
40
33
  defineProps<{
41
34
  message: AssistantMessage;
@@ -101,512 +94,6 @@ const toolbarButtonClass = [
101
94
  "cpk:dark:hover:text-[rgb(243,243,243)] cpk:disabled:pointer-events-none cpk:disabled:opacity-50",
102
95
  ].join(" ");
103
96
 
104
- function extractFileNameFromUrl(url: string, fallback: string) {
105
- try {
106
- const parsed = new URL(url, "https://copilotkit.local");
107
- const pathname = parsed.pathname.split("/").filter(Boolean).pop();
108
- if (pathname) {
109
- return decodeURIComponent(pathname);
110
- }
111
- } catch {
112
- return fallback;
113
- }
114
- return fallback;
115
- }
116
-
117
- function triggerDownload(href: string, fileName: string) {
118
- if (typeof document === "undefined") {
119
- return;
120
- }
121
- const anchor = document.createElement("a");
122
- anchor.href = href;
123
- anchor.download = fileName;
124
- anchor.rel = "noopener noreferrer";
125
- document.body.appendChild(anchor);
126
- anchor.click();
127
- document.body.removeChild(anchor);
128
- }
129
-
130
- type MarkdownTableData = {
131
- headers: string[];
132
- rows: string[][];
133
- };
134
-
135
- function extractTableData(table: HTMLTableElement): MarkdownTableData {
136
- const headers = Array.from(table.querySelectorAll("thead th")).map((cell) =>
137
- (cell.textContent ?? "").trim(),
138
- );
139
- const rows = Array.from(table.querySelectorAll("tbody tr")).map((row) =>
140
- Array.from(row.querySelectorAll("td")).map((cell) =>
141
- (cell.textContent ?? "").trim(),
142
- ),
143
- );
144
- return { headers, rows };
145
- }
146
-
147
- function toDelimitedTable(
148
- data: MarkdownTableData,
149
- delimiter: "," | "\t",
150
- ): string {
151
- const escapeCell = (value: string): string => {
152
- const needsQuotes =
153
- value.includes(delimiter) || value.includes('"') || value.includes("\n");
154
- const escaped = value.replace(/"/g, '""');
155
- return needsQuotes ? `"${escaped}"` : escaped;
156
- };
157
-
158
- const lines: string[] = [];
159
- if (data.headers.length > 0) {
160
- lines.push(data.headers.map(escapeCell).join(delimiter));
161
- }
162
- for (const row of data.rows) {
163
- lines.push(row.map(escapeCell).join(delimiter));
164
- }
165
- return lines.join("\n");
166
- }
167
-
168
- function toMarkdownTable(data: MarkdownTableData): string {
169
- if (data.headers.length === 0) {
170
- return data.rows.map((row) => `| ${row.join(" | ")} |`).join("\n");
171
- }
172
-
173
- const separator = `| ${data.headers.map(() => "---").join(" | ")} |`;
174
- const body = data.rows.map((row) => `| ${row.join(" | ")} |`);
175
- return [`| ${data.headers.join(" | ")} |`, separator, ...body].join("\n");
176
- }
177
-
178
- function triggerBlobDownload(
179
- content: string,
180
- fileName: string,
181
- mimeType: string,
182
- ) {
183
- if (typeof document === "undefined") {
184
- return;
185
- }
186
- const blob = new Blob([content], { type: mimeType });
187
- const url = URL.createObjectURL(blob);
188
- triggerDownload(url, fileName);
189
- URL.revokeObjectURL(url);
190
- }
191
-
192
- const MarkdownImage = defineComponent({
193
- name: "CopilotMarkdownImage",
194
- inheritAttrs: false,
195
- props: {
196
- src: {
197
- type: String,
198
- default: "",
199
- },
200
- alt: {
201
- type: String,
202
- default: "",
203
- },
204
- },
205
- setup(imageProps, { attrs }) {
206
- async function handleDownload() {
207
- if (!imageProps.src) {
208
- return;
209
- }
210
-
211
- const fileName = extractFileNameFromUrl(imageProps.src, "image");
212
- try {
213
- const response = await fetch(imageProps.src);
214
- if (!response.ok) {
215
- throw new Error("Failed to fetch image");
216
- }
217
- const blob = await response.blob();
218
- const objectUrl = URL.createObjectURL(blob);
219
- triggerDownload(objectUrl, fileName);
220
- URL.revokeObjectURL(objectUrl);
221
- } catch {
222
- triggerDownload(imageProps.src, fileName);
223
- }
224
- }
225
-
226
- return () => {
227
- const imageAttrs = {
228
- ...attrs,
229
- src: imageProps.src,
230
- alt: imageProps.alt,
231
- class: ["cpk:max-w-full cpk:rounded-lg", attrs.class]
232
- .filter(Boolean)
233
- .join(" "),
234
- "data-streamdown": "image",
235
- } as Record<string, unknown>;
236
-
237
- delete imageAttrs.className;
238
-
239
- return h(
240
- "div",
241
- {
242
- class: "cpk:group cpk:relative cpk:my-4 cpk:inline-block",
243
- "data-streamdown": "image-wrapper",
244
- },
245
- [
246
- h("img", imageAttrs),
247
- h("div", {
248
- class:
249
- "cpk:pointer-events-none cpk:absolute cpk:inset-0 cpk:hidden cpk:rounded-lg cpk:bg-black/10 cpk:group-hover:block",
250
- }),
251
- h(
252
- "button",
253
- {
254
- type: "button",
255
- class:
256
- "cpk:absolute cpk:right-2 cpk:bottom-2 cpk:flex cpk:h-8 cpk:w-8 cpk:cursor-pointer cpk:items-center cpk:justify-center cpk:rounded-md cpk:border cpk:border-border cpk:bg-background/90 cpk:shadow-sm cpk:backdrop-blur-sm cpk:transition-all cpk:duration-200 cpk:hover:bg-background cpk:opacity-0 cpk:group-hover:opacity-100",
257
- title: "Download image",
258
- onClick: handleDownload,
259
- },
260
- [h(IconDownload, { class: "cpk:size-[14px]" })],
261
- ),
262
- ],
263
- );
264
- };
265
- },
266
- });
267
-
268
- const tableIconButtonClass =
269
- "cpk:cursor-pointer cpk:p-1 cpk:text-muted-foreground cpk:transition-all cpk:hover:text-foreground cpk:disabled:cursor-not-allowed cpk:disabled:opacity-50";
270
- const tableMenuClass =
271
- "cpk:absolute cpk:top-full cpk:right-0 cpk:z-10 cpk:mt-1 cpk:min-w-[120px] cpk:overflow-hidden cpk:rounded-md cpk:border cpk:border-border cpk:bg-background cpk:shadow-lg";
272
- const tableMenuItemClass =
273
- "cpk:w-full cpk:px-3 cpk:py-2 cpk:text-left cpk:text-sm cpk:transition-colors cpk:hover:bg-muted/40";
274
-
275
- const MarkdownTable = defineComponent({
276
- name: "CopilotMarkdownTable",
277
- inheritAttrs: false,
278
- setup(_, { attrs, slots }) {
279
- const wrapperRef = ref<HTMLElement | null>(null);
280
- const showCopyMenu = ref(false);
281
- const showDownloadMenu = ref(false);
282
- const tableCopied = ref(false);
283
- let tableCopiedResetTimeout: ReturnType<typeof setTimeout> | null = null;
284
-
285
- const closeMenus = () => {
286
- showCopyMenu.value = false;
287
- showDownloadMenu.value = false;
288
- };
289
-
290
- const setTableCopiedStateWithDelay = () => {
291
- if (tableCopiedResetTimeout) {
292
- clearTimeout(tableCopiedResetTimeout);
293
- }
294
- tableCopied.value = true;
295
- tableCopiedResetTimeout = setTimeout(() => {
296
- tableCopied.value = false;
297
- tableCopiedResetTimeout = null;
298
- }, 2000);
299
- };
300
-
301
- const findTable = (): HTMLTableElement | null => {
302
- if (!wrapperRef.value) return null;
303
- return wrapperRef.value.querySelector("table");
304
- };
305
-
306
- const getTableData = (): MarkdownTableData | null => {
307
- const table = findTable();
308
- if (!table) return null;
309
- return extractTableData(table);
310
- };
311
-
312
- const copyTableAs = async (format: "csv" | "tsv") => {
313
- const data = getTableData();
314
- if (!data) return;
315
-
316
- const delimiter = format === "csv" ? "," : "\t";
317
- const text = toDelimitedTable(data, delimiter);
318
-
319
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
320
- try {
321
- await navigator.clipboard.writeText(text);
322
- } catch {
323
- return;
324
- }
325
- }
326
-
327
- closeMenus();
328
- setTableCopiedStateWithDelay();
329
- };
330
-
331
- const downloadTableAs = (format: "csv" | "markdown") => {
332
- const data = getTableData();
333
- if (!data) return;
334
-
335
- if (format === "csv") {
336
- triggerBlobDownload(
337
- toDelimitedTable(data, ","),
338
- "table.csv",
339
- "text/csv",
340
- );
341
- } else {
342
- triggerBlobDownload(toMarkdownTable(data), "table.md", "text/markdown");
343
- }
344
-
345
- closeMenus();
346
- };
347
-
348
- const handleClickOutside = (event: MouseEvent) => {
349
- if (!wrapperRef.value) return;
350
- const target = event.target as Node | null;
351
- if (target && !wrapperRef.value.contains(target)) {
352
- closeMenus();
353
- }
354
- };
355
-
356
- onMounted(() => {
357
- if (typeof document !== "undefined") {
358
- document.addEventListener("mousedown", handleClickOutside);
359
- }
360
- });
361
-
362
- onBeforeUnmount(() => {
363
- if (tableCopiedResetTimeout) {
364
- clearTimeout(tableCopiedResetTimeout);
365
- }
366
- if (typeof document !== "undefined") {
367
- document.removeEventListener("mousedown", handleClickOutside);
368
- }
369
- });
370
-
371
- return () => {
372
- const tableAttrs = {
373
- ...attrs,
374
- class: [
375
- "cpk:w-full cpk:border-collapse cpk:border cpk:border-border",
376
- attrs.class,
377
- ]
378
- .filter(Boolean)
379
- .join(" "),
380
- "data-streamdown": "table",
381
- } as Record<string, unknown>;
382
-
383
- delete tableAttrs.className;
384
-
385
- return h(
386
- "div",
387
- {
388
- ref: wrapperRef,
389
- class: "cpk:my-4 cpk:flex cpk:flex-col cpk:space-y-2",
390
- "data-streamdown": "table-wrapper",
391
- },
392
- [
393
- h(
394
- "div",
395
- { class: "cpk:flex cpk:items-center cpk:justify-end cpk:gap-1" },
396
- [
397
- h("div", { class: "cpk:relative" }, [
398
- h(
399
- "button",
400
- {
401
- type: "button",
402
- class: tableIconButtonClass,
403
- title: "Copy table",
404
- onClick: () => {
405
- showCopyMenu.value = !showCopyMenu.value;
406
- showDownloadMenu.value = false;
407
- },
408
- },
409
- [
410
- tableCopied.value
411
- ? h(IconCheck, { class: "cpk:size-[14px]" })
412
- : h(IconCopy, { class: "cpk:size-[14px]" }),
413
- ],
414
- ),
415
- showCopyMenu.value
416
- ? h("div", { class: tableMenuClass }, [
417
- h(
418
- "button",
419
- {
420
- type: "button",
421
- class: tableMenuItemClass,
422
- title: "Copy table as CSV",
423
- onClick: () => copyTableAs("csv"),
424
- },
425
- "CSV",
426
- ),
427
- h(
428
- "button",
429
- {
430
- type: "button",
431
- class: tableMenuItemClass,
432
- title: "Copy table as TSV",
433
- onClick: () => copyTableAs("tsv"),
434
- },
435
- "TSV",
436
- ),
437
- ])
438
- : null,
439
- ]),
440
- h("div", { class: "cpk:relative" }, [
441
- h(
442
- "button",
443
- {
444
- type: "button",
445
- class: tableIconButtonClass,
446
- title: "Download table",
447
- onClick: () => {
448
- showDownloadMenu.value = !showDownloadMenu.value;
449
- showCopyMenu.value = false;
450
- },
451
- },
452
- [h(IconDownload, { class: "cpk:size-[14px]" })],
453
- ),
454
- showDownloadMenu.value
455
- ? h("div", { class: tableMenuClass }, [
456
- h(
457
- "button",
458
- {
459
- type: "button",
460
- class: tableMenuItemClass,
461
- title: "Download table as CSV",
462
- onClick: () => downloadTableAs("csv"),
463
- },
464
- "CSV",
465
- ),
466
- h(
467
- "button",
468
- {
469
- type: "button",
470
- class: tableMenuItemClass,
471
- title: "Download table as Markdown",
472
- onClick: () => downloadTableAs("markdown"),
473
- },
474
- "Markdown",
475
- ),
476
- ])
477
- : null,
478
- ]),
479
- ],
480
- ),
481
- h("div", { class: "cpk:overflow-x-auto" }, [
482
- h("table", tableAttrs, slots.default ? slots.default() : []),
483
- ]),
484
- ],
485
- );
486
- };
487
- },
488
- });
489
-
490
- const codeActionButtonClass =
491
- "cpk:cursor-pointer cpk:p-1 cpk:text-muted-foreground cpk:transition-all cpk:hover:text-foreground cpk:disabled:cursor-not-allowed cpk:disabled:opacity-50";
492
-
493
- const codeLanguageExtensionMap: Record<string, string> = {
494
- javascript: "js",
495
- js: "js",
496
- typescript: "ts",
497
- ts: "ts",
498
- json: "json",
499
- vue: "vue",
500
- html: "html",
501
- css: "css",
502
- md: "md",
503
- markdown: "md",
504
- sh: "sh",
505
- bash: "sh",
506
- py: "py",
507
- python: "py",
508
- go: "go",
509
- rust: "rs",
510
- rs: "rs",
511
- };
512
-
513
- const CodeBlockCopyAction = defineComponent({
514
- name: "CopilotCodeBlockCopyAction",
515
- props: {
516
- code: {
517
- type: String,
518
- default: "",
519
- },
520
- },
521
- setup(actionProps) {
522
- const isCopied = ref(false);
523
- let resetTimeout: ReturnType<typeof setTimeout> | null = null;
524
-
525
- const handleClick = async () => {
526
- if (!actionProps.code) return;
527
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
528
- try {
529
- await navigator.clipboard.writeText(actionProps.code);
530
- } catch {
531
- return;
532
- }
533
- }
534
-
535
- if (resetTimeout) {
536
- clearTimeout(resetTimeout);
537
- }
538
- isCopied.value = true;
539
- resetTimeout = setTimeout(() => {
540
- isCopied.value = false;
541
- resetTimeout = null;
542
- }, 2000);
543
- };
544
-
545
- onBeforeUnmount(() => {
546
- if (resetTimeout) {
547
- clearTimeout(resetTimeout);
548
- }
549
- });
550
-
551
- return () =>
552
- h(
553
- "button",
554
- {
555
- type: "button",
556
- class: codeActionButtonClass,
557
- title: "Copy Code",
558
- "data-streamdown": "code-block-copy-button",
559
- onClick: handleClick,
560
- },
561
- [
562
- isCopied.value
563
- ? h(IconCheck, { class: "cpk:size-[14px]" })
564
- : h(IconCopy, { class: "cpk:size-[14px]" }),
565
- ],
566
- );
567
- },
568
- });
569
-
570
- const CodeBlockDownloadAction = defineComponent({
571
- name: "CopilotCodeBlockDownloadAction",
572
- props: {
573
- code: {
574
- type: String,
575
- default: "",
576
- },
577
- language: {
578
- type: String,
579
- default: "",
580
- },
581
- },
582
- setup(actionProps) {
583
- const handleClick = () => {
584
- if (!actionProps.code) return;
585
- const extension =
586
- codeLanguageExtensionMap[actionProps.language.toLowerCase()] ?? "txt";
587
- triggerBlobDownload(actionProps.code, `file.${extension}`, "text/plain");
588
- };
589
-
590
- return () =>
591
- h(
592
- "button",
593
- {
594
- type: "button",
595
- class: codeActionButtonClass,
596
- title: "Download file",
597
- "data-streamdown": "code-block-download-button",
598
- onClick: handleClick,
599
- },
600
- [h(IconDownload, { class: "cpk:size-[14px]" })],
601
- );
602
- },
603
- });
604
-
605
- const markdownComponents = {
606
- img: MarkdownImage,
607
- table: MarkdownTable,
608
- };
609
-
610
97
  function normalizeContent(content: unknown): string {
611
98
  if (!content) {
612
99
  return "";
@@ -642,6 +129,22 @@ const normalizedContent = computed(() =>
642
129
  normalizeContent(props.message.content),
643
130
  );
644
131
  const hasContent = computed(() => normalizedContent.value.trim().length > 0);
132
+
133
+ const providerMarkdownRenderer = useMarkdownRenderer();
134
+ const providerIsComponent = computed(() =>
135
+ isVueComponentRenderer(providerMarkdownRenderer),
136
+ );
137
+ const ActiveMarkdownRenderer = computed(() =>
138
+ providerIsComponent.value
139
+ ? (providerMarkdownRenderer as Component)
140
+ : StreamingMarkdownDefault,
141
+ );
142
+ const activeMarkdownConfig = computed(() =>
143
+ providerIsComponent.value || !providerMarkdownRenderer
144
+ ? {}
145
+ : (providerMarkdownRenderer as Record<string, unknown>),
146
+ );
147
+
645
148
  const hasThumbsUp = computed(() => typeof props.onThumbsUp === "function");
646
149
  const hasThumbsDown = computed(() => typeof props.onThumbsDown === "function");
647
150
  const hasReadAloud = computed(() => typeof props.onReadAloud === "function");
@@ -743,24 +246,12 @@ onBeforeUnmount(() => {
743
246
  :message="message"
744
247
  :content="normalizedContent"
745
248
  >
746
- <StreamMarkdown
249
+ <component
250
+ :is="ActiveMarkdownRenderer"
747
251
  v-if="hasContent"
748
- class="copilot-chat-assistant-markdown"
252
+ v-bind="activeMarkdownConfig"
749
253
  :content="normalizedContent"
750
- :components="markdownComponents"
751
- :code-block-actions="[CodeBlockDownloadAction, CodeBlockCopyAction]"
752
- :code-block-show-line-numbers="false"
753
- :code-block-hide-copy="true"
754
- :code-block-hide-download="true"
755
- :allowed-link-prefixes="[
756
- 'https://',
757
- 'http://',
758
- '#',
759
- '/',
760
- './',
761
- '../',
762
- ]"
763
- :shiki-theme="{ light: 'github-light', dark: 'github-dark' }"
254
+ :is-streaming="isRunning && isLatestAssistantMessage"
764
255
  />
765
256
  </slot>
766
257
 
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, onBeforeUnmount, ref, watch } from "vue";
3
3
  import type { Message, ReasoningMessage } from "@ag-ui/core";
4
- import { StreamMarkdown } from "streamdown-vue";
5
4
  import { IconChevronRight } from "../icons";
5
+ import StreamingMarkdownDefault from "./StreamingMarkdownDefault.vue";
6
6
  import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
7
7
  import { CopilotChatDefaultLabels } from "../../providers/types";
8
8
 
@@ -237,7 +237,12 @@ function toggleOpen() {
237
237
  >
238
238
  <div v-if="hasContent || isStreaming" class="cpk:pb-2 cpk:pt-1">
239
239
  <div class="cpk:text-sm cpk:text-muted-foreground">
240
- <StreamMarkdown :content="normalizedContent" />
240
+ <!-- Reasoning content uses the streaming renderer (same as assistant);
241
+ the pluggable provider renderer applies to assistant messages only. -->
242
+ <StreamingMarkdownDefault
243
+ :content="normalizedContent"
244
+ :is-streaming="isStreaming"
245
+ />
241
246
  <span
242
247
  v-if="isStreaming && hasContent"
243
248
  class="cpk:inline-flex cpk:items-center cpk:ml-1 cpk:align-middle"
@@ -0,0 +1,57 @@
1
+ <script setup lang="ts">
2
+ import { computed, h, watchEffect } from "vue";
3
+ import {
4
+ StreamingMarkdownRenderer,
5
+ type VueStreamingMarkdownNodeRenderer,
6
+ } from "@copilotkit/markdown-renderer/vue";
7
+ import { warnUnsupportedRichSyntaxOnce } from "@copilotkit/markdown-renderer";
8
+ import type { StreamingMarkdownParserOptions } from "@copilotkit/markdown-renderer";
9
+ import type { DefaultMarkdownRendererProps } from "../../providers/markdown-renderer";
10
+
11
+ const props = withDefaults(
12
+ defineProps<
13
+ { content: string; isStreaming?: boolean } & DefaultMarkdownRendererProps
14
+ >(),
15
+ { isStreaming: false },
16
+ );
17
+
18
+ const resolvedOptions = computed(
19
+ () => props.options as StreamingMarkdownParserOptions | undefined,
20
+ );
21
+
22
+ const defaultCodeBlockRenderer: VueStreamingMarkdownNodeRenderer = (
23
+ node,
24
+ defaultVNode,
25
+ ) => {
26
+ if (node.type !== "code-block") return defaultVNode;
27
+ return h(
28
+ "pre",
29
+ {
30
+ "data-streaming-markdown-node": "code-block",
31
+ "data-node-open": String(!node.closed),
32
+ class: "cpk:overflow-x-auto cpk:rounded-lg cpk:p-3",
33
+ },
34
+ [h("code", { "data-code-info": node.info ?? undefined }, node.text)],
35
+ );
36
+ };
37
+
38
+ const mergedNodeRenderers = computed(() => ({
39
+ codeBlock: defaultCodeBlockRenderer,
40
+ ...props.nodeRenderers,
41
+ }));
42
+
43
+ // Dev-only: nudge upgraders from the bundled Streamdown default when their
44
+ // content needs math/syntax highlighting the built-in renderer doesn't do.
45
+ watchEffect(() => warnUnsupportedRichSyntaxOnce(props.content));
46
+ </script>
47
+
48
+ <template>
49
+ <StreamingMarkdownRenderer
50
+ :content="props.content"
51
+ :is-complete="!props.isStreaming"
52
+ :node-renderers="mergedNodeRenderers"
53
+ :caret="props.caret ?? props.isStreaming"
54
+ :options="resolvedOptions"
55
+ :class="props.class"
56
+ />
57
+ </template>