@copilotkit/vue 2.0.0-rc.20 → 2.0.0-rc.22

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 (193) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.mjs +13 -13
  3. package/dist/use-render-activity-message-CyrhHFhA.js +16200 -0
  4. package/dist/use-render-activity-message-CyrhHFhA.js.map +1 -0
  5. package/dist/use-render-activity-message-EwHHrwQB.cjs +114 -0
  6. package/dist/use-render-activity-message-EwHHrwQB.cjs.map +1 -0
  7. package/dist/v2/components/A2UIMessageRenderer.d.ts +14 -0
  8. package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -1
  9. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts +10 -3
  10. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -1
  11. package/dist/v2/components/CopilotKitInspector.vue.d.ts +5 -1
  12. package/dist/v2/components/CopilotKitInspector.vue.d.ts.map +1 -1
  13. package/dist/v2/components/MCPAppsActivityRenderer.d.ts.map +1 -1
  14. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +7 -21
  15. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -1
  16. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +0 -5
  17. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -1
  18. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts +10 -15
  19. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts.map +1 -1
  20. package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts +14 -138
  21. package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts.map +1 -1
  22. package/dist/v2/components/a2ui/catalog.d.ts.map +1 -1
  23. package/dist/v2/components/a2ui/utils.d.ts +3 -15
  24. package/dist/v2/components/a2ui/utils.d.ts.map +1 -1
  25. package/dist/v2/components/a2ui.d.ts +6 -1
  26. package/dist/v2/components/a2ui.d.ts.map +1 -1
  27. package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
  28. package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -1
  29. package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts +4 -8
  30. package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts.map +1 -1
  31. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
  32. package/dist/v2/components/chat/CopilotChatLightbox.vue.d.ts.map +1 -1
  33. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
  34. package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts.map +1 -1
  35. package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts.map +1 -1
  36. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -1
  37. package/dist/v2/components/chat/CopilotChatView.vue.d.ts +1 -1
  38. package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
  39. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
  40. package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +1 -1
  41. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +1 -1
  42. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
  43. package/dist/v2/components/chat/CopilotThreadsDrawer.vue.d.ts.map +1 -1
  44. package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts +1 -0
  45. package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts.map +1 -1
  46. package/dist/v2/components/chat/audioRecorder.d.ts +3 -4
  47. package/dist/v2/components/chat/audioRecorder.d.ts.map +1 -1
  48. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts +3 -2
  49. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -1
  50. package/dist/v2/components/chat/index.d.ts +6 -6
  51. package/dist/v2/components/chat/tool-call-display-context.d.ts +3 -1
  52. package/dist/v2/components/chat/tool-call-display-context.d.ts.map +1 -1
  53. package/dist/v2/components/chat/tool-call-group-parts.d.ts.map +1 -1
  54. package/dist/v2/components/chat/types.d.ts +1 -1
  55. package/dist/v2/components/chat/types.d.ts.map +1 -1
  56. package/dist/v2/components/chat/use-lightbox.d.ts.map +1 -1
  57. package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts +1 -1
  58. package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts.map +1 -1
  59. package/dist/v2/hooks/use-agent-context.d.ts +2 -3
  60. package/dist/v2/hooks/use-agent-context.d.ts.map +1 -1
  61. package/dist/v2/hooks/use-agent.d.ts +4 -1
  62. package/dist/v2/hooks/use-agent.d.ts.map +1 -1
  63. package/dist/v2/hooks/use-attachments.d.ts +7 -0
  64. package/dist/v2/hooks/use-attachments.d.ts.map +1 -1
  65. package/dist/v2/hooks/use-capabilities.d.ts.map +1 -1
  66. package/dist/v2/hooks/use-component.d.ts +1 -0
  67. package/dist/v2/hooks/use-component.d.ts.map +1 -1
  68. package/dist/v2/hooks/use-configure-suggestions.d.ts +4 -9
  69. package/dist/v2/hooks/use-configure-suggestions.d.ts.map +1 -1
  70. package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -1
  71. package/dist/v2/hooks/use-human-in-the-loop.d.ts +2 -1
  72. package/dist/v2/hooks/use-human-in-the-loop.d.ts.map +1 -1
  73. package/dist/v2/hooks/use-interrupt.d.ts +0 -1
  74. package/dist/v2/hooks/use-interrupt.d.ts.map +1 -1
  75. package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -1
  76. package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -1
  77. package/dist/v2/hooks/use-suggestions.d.ts.map +1 -1
  78. package/dist/v2/hooks/use-threads.d.ts +0 -8
  79. package/dist/v2/hooks/use-threads.d.ts.map +1 -1
  80. package/dist/v2/index.cjs +1 -1
  81. package/dist/v2/index.mjs +1 -1
  82. package/dist/v2/lib/vue-core.d.ts +12 -11
  83. package/dist/v2/lib/vue-core.d.ts.map +1 -1
  84. package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts.map +1 -1
  85. package/dist/v2/providers/CopilotKitProvider.types.d.ts +9 -124
  86. package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
  87. package/dist/v2/providers/CopilotKitProvider.vue.d.ts +9 -10
  88. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
  89. package/dist/v2/providers/chat-configuration-store-key.d.ts +8 -0
  90. package/dist/v2/providers/chat-configuration-store-key.d.ts.map +1 -0
  91. package/dist/v2/providers/markdown-renderer.d.ts +3 -1
  92. package/dist/v2/providers/markdown-renderer.d.ts.map +1 -1
  93. package/dist/v2/providers/use-inspector-thread-override.d.ts +5 -0
  94. package/dist/v2/providers/use-inspector-thread-override.d.ts.map +1 -1
  95. package/package.json +8 -8
  96. package/src/v2/__tests__/exports.test.ts +10 -0
  97. package/src/v2/__tests__/mocks/web-inspector.ts +6 -0
  98. package/src/v2/__tests__/utils/test-helpers.ts +34 -4
  99. package/src/v2/components/A2UIMessageRenderer.ts +98 -68
  100. package/src/v2/components/A2UISurfaceActivityRenderer.vue +68 -103
  101. package/src/v2/components/CopilotKitInspector.vue +104 -51
  102. package/src/v2/components/MCPAppsActivityRenderer.ts +75 -201
  103. package/src/v2/components/OpenGenerativeUIRenderer.ts +78 -80
  104. package/src/v2/components/__tests__/A2UIMessageRenderer.test.ts +171 -8
  105. package/src/v2/components/__tests__/CopilotKitInspector.test.ts +42 -0
  106. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.ts +9 -2
  107. package/src/v2/components/__tests__/OpenGenerativeUIToolRenderer.test.ts +38 -0
  108. package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +1 -6
  109. package/src/v2/components/a2ui/A2UICatalogContext.ts +27 -93
  110. package/src/v2/components/a2ui/A2UIRecoveryStates.ts +127 -327
  111. package/src/v2/components/a2ui/catalog.ts +2 -3
  112. package/src/v2/components/a2ui/utils.ts +13 -67
  113. package/src/v2/components/a2ui.ts +14 -21
  114. package/src/v2/components/chat/CopilotChat.vue +136 -324
  115. package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +22 -45
  116. package/src/v2/components/chat/CopilotChatAudioRecorder.vue +19 -246
  117. package/src/v2/components/chat/CopilotChatInput.vue +69 -407
  118. package/src/v2/components/chat/CopilotChatLightbox.vue +10 -31
  119. package/src/v2/components/chat/CopilotChatMessageView.vue +52 -112
  120. package/src/v2/components/chat/CopilotChatToolCallGroup.vue +14 -45
  121. package/src/v2/components/chat/CopilotChatToolCallItem.vue +6 -4
  122. package/src/v2/components/chat/CopilotChatToolCallsView.vue +16 -19
  123. package/src/v2/components/chat/CopilotChatView.vue +28 -23
  124. package/src/v2/components/chat/CopilotPopupViewInternal.vue +21 -97
  125. package/src/v2/components/chat/CopilotSidebarViewInternal.vue +24 -25
  126. package/src/v2/components/chat/CopilotThreadsDrawer.vue +37 -102
  127. package/src/v2/components/chat/StreamingMarkdownDefault.vue +13 -26
  128. package/src/v2/components/chat/__tests__/CopilotChat.test.ts +12 -10
  129. package/src/v2/components/chat/__tests__/CopilotChatAttachmentQueue.test.ts +28 -0
  130. package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +56 -0
  131. package/src/v2/components/chat/__tests__/CopilotPopupView.test.ts +19 -0
  132. package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.ts +32 -8
  133. package/src/v2/components/chat/__tests__/CopilotSidebarView.test.ts +2 -1
  134. package/src/v2/components/chat/__tests__/StreamingMarkdownDefault.test.ts +54 -0
  135. package/src/v2/components/chat/audioRecorder.ts +4 -7
  136. package/src/v2/components/chat/copilotChatToolCallItemRender.ts +22 -71
  137. package/src/v2/components/chat/tool-call-display-context.ts +6 -1
  138. package/src/v2/components/chat/tool-call-group-parts.ts +13 -33
  139. package/src/v2/components/chat/types.ts +1 -1
  140. package/src/v2/components/chat/use-lightbox.ts +15 -33
  141. package/src/v2/hooks/__tests__/use-agent-error-state.test.ts +5 -3
  142. package/src/v2/hooks/__tests__/use-agent-stability.test.ts +25 -4
  143. package/src/v2/hooks/__tests__/use-agent-throttle.test.ts +5 -1
  144. package/src/v2/hooks/__tests__/use-agent.test.ts +6 -3
  145. package/src/v2/hooks/__tests__/use-component.test.ts +21 -0
  146. package/src/v2/hooks/__tests__/use-configure-suggestions.test.ts +30 -1
  147. package/src/v2/hooks/__tests__/use-default-render-tool.test.ts +10 -10
  148. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +4 -0
  149. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +6 -1
  150. package/src/v2/hooks/__tests__/use-human-in-the-loop.test.ts +187 -12
  151. package/src/v2/hooks/__tests__/use-interrupt.test.ts +97 -0
  152. package/src/v2/hooks/__tests__/use-render-activity-message.test.ts +2 -1
  153. package/src/v2/hooks/__tests__/use-threads.test.ts +48 -6
  154. package/src/v2/hooks/use-agent-context.ts +4 -10
  155. package/src/v2/hooks/use-agent.ts +86 -290
  156. package/src/v2/hooks/use-attachments.ts +42 -166
  157. package/src/v2/hooks/use-capabilities.ts +2 -7
  158. package/src/v2/hooks/use-component.ts +7 -4
  159. package/src/v2/hooks/use-configure-suggestions.ts +28 -218
  160. package/src/v2/hooks/use-default-render-tool.ts +34 -94
  161. package/src/v2/hooks/use-human-in-the-loop.ts +11 -80
  162. package/src/v2/hooks/use-interrupt.ts +29 -308
  163. package/src/v2/hooks/use-render-activity-message.ts +17 -20
  164. package/src/v2/hooks/use-render-custom-messages.ts +20 -63
  165. package/src/v2/hooks/use-suggestions.ts +9 -33
  166. package/src/v2/hooks/use-threads.ts +43 -273
  167. package/src/v2/lib/__tests__/vue-core-renderers.test.ts +42 -0
  168. package/src/v2/lib/vue-core.ts +67 -72
  169. package/src/v2/providers/CopilotChatConfigurationProvider.vue +48 -186
  170. package/src/v2/providers/CopilotKitProvider.types.ts +12 -127
  171. package/src/v2/providers/CopilotKitProvider.vue +188 -402
  172. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.ts +22 -2
  173. package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +19 -1
  174. package/src/v2/providers/__tests__/CopilotKitProvider.config.test.ts +124 -0
  175. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.ts +4 -1
  176. package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +45 -2
  177. package/src/v2/providers/chat-configuration-store-key.ts +9 -0
  178. package/src/v2/providers/markdown-renderer.ts +7 -1
  179. package/src/v2/providers/use-inspector-thread-override.ts +22 -80
  180. package/vite.config.ts +5 -1
  181. package/vitest.config.ts +12 -7
  182. package/dist/use-render-activity-message-CsWYyd6r.cjs +0 -91
  183. package/dist/use-render-activity-message-CsWYyd6r.cjs.map +0 -1
  184. package/dist/use-render-activity-message-DntKKNdd.js +0 -17418
  185. package/dist/use-render-activity-message-DntKKNdd.js.map +0 -1
  186. package/dist/v2/components/chat/normalize-auto-scroll.d.ts +0 -3
  187. package/dist/v2/components/chat/normalize-auto-scroll.d.ts.map +0 -1
  188. package/dist/v2/lib/is-mobile-viewport.d.ts +0 -2
  189. package/dist/v2/lib/is-mobile-viewport.d.ts.map +0 -1
  190. package/src/v2/components/__tests__/A2UICatalogContext.test.ts +0 -63
  191. package/src/v2/components/a2ui/__tests__/A2UIRecoveryStates.test.ts +0 -175
  192. package/src/v2/components/chat/normalize-auto-scroll.ts +0 -3
  193. package/src/v2/lib/is-mobile-viewport.ts +0 -5
@@ -1,5 +1,15 @@
1
1
  <script setup lang="ts">
2
- import { computed, h, onMounted, provide, ref, useSlots, watch } from "vue";
2
+ import {
3
+ computed,
4
+ h,
5
+ onBeforeUnmount,
6
+ onMounted,
7
+ provide,
8
+ ref,
9
+ shallowRef,
10
+ useSlots,
11
+ watch,
12
+ } from "vue";
3
13
  import type { Component, FunctionalComponent, VNode, VNodeChild } from "vue";
4
14
  import type {
5
15
  ActivityMessage,
@@ -17,24 +27,16 @@ import {
17
27
  } from "@copilotkit/shared";
18
28
  import {
19
29
  ɵdeduplicateMessages,
20
- ɵgetToolCallLayouts,
30
+ ɵcreateMessageViewModel,
31
+ ɵduplicateMessagesWarning,
21
32
  ɵgetToolCallRendererNames,
22
- ɵgetTurnWork,
23
- ɵgetWorkOpenState,
24
- ɵisTurnWorkOpen,
25
- ɵisTurnWorking,
26
- ɵrecordWorkTime,
27
33
  ɵresolveActivityRenderer,
28
34
  ɵresolveCustomMessageRenderers,
29
- ɵgetLatestTurnKey,
30
35
  } from "@copilotkit/core";
31
36
  import type {
32
37
  CopilotChatToolCallDisplay,
33
38
  ToolCallEntry,
34
39
  ɵToolCallDisplayContextValue,
35
- ɵToolCallLayout,
36
- ɵTurnWork,
37
- ɵWorkTime,
38
40
  } from "@copilotkit/core";
39
41
  import type {
40
42
  InterruptRenderProps,
@@ -219,17 +221,30 @@ const deduplicatedMessages = computed(() =>
219
221
  // Groups of calls can span messages (an agent often sends each call as its
220
222
  // own message), so they're laid out here for the whole list and handed to
221
223
  // each message's tool calls view through provide/inject.
222
- const toolCallLayouts = computed<ReadonlyMap<string, ɵToolCallLayout>>(() =>
223
- props.toolCallDisplay === "separate"
224
- ? new Map()
225
- : ɵgetToolCallLayouts(
226
- deduplicatedMessages.value,
227
- props.messages,
228
- ɵgetToolCallRendererNames(copilotkit.value.renderToolCalls, {
229
- slotNames: forwardedSlotNames.value,
230
- }),
231
- ),
224
+ const rendererNames = computed(() =>
225
+ ɵgetToolCallRendererNames(copilotkit.value.renderToolCalls, {
226
+ slotNames: forwardedSlotNames.value,
227
+ }),
232
228
  );
229
+ // The list's tool-call layout and turn state live in core. A user's open or
230
+ // close of a reply's work is the only change it reports by itself.
231
+ const viewModel = ɵcreateMessageViewModel();
232
+ const toggledTurns = shallowRef(viewModel.getSnapshot());
233
+ const unsubscribeViewModel = viewModel.subscribe(() => {
234
+ toggledTurns.value = viewModel.getSnapshot();
235
+ });
236
+ onBeforeUnmount(unsubscribeViewModel);
237
+ const viewState = computed(() => {
238
+ void toggledTurns.value;
239
+ return viewModel.update({
240
+ display: props.toolCallDisplay,
241
+ messages: props.messages,
242
+ rendered: deduplicatedMessages.value,
243
+ isRunning: props.isRunning,
244
+ rendererNames: rendererNames.value,
245
+ now: Date.now(),
246
+ });
247
+ });
233
248
  const learningFeedbackEnabled = useLearningFeedbackEnabled(copilotkit);
234
249
  provide(
235
250
  MessageFeedbackScopeKey,
@@ -247,74 +262,18 @@ provide(
247
262
  ),
248
263
  );
249
264
 
250
- const turnWork = computed(() =>
251
- props.toolCallDisplay === "turn"
252
- ? ɵgetTurnWork(deduplicatedMessages.value, toolCallLayouts.value)
253
- : undefined,
254
- );
255
- const turnByFirstMessage = computed(() => {
256
- const byFirst = new Map<string, ɵTurnWork>();
257
- for (const turn of turnWork.value?.values() ?? []) {
258
- byFirst.set(turn.firstMessageId, turn);
259
- }
260
- return byFirst;
261
- });
262
-
263
- const lastRenderedMessage = computed(
264
- () => deduplicatedMessages.value[deduplicatedMessages.value.length - 1],
265
- );
265
+ const turnByFirstMessage = computed(() => viewState.value.turnByFirstMessage);
266
266
  // The latest reply is keyed by the user message it answers (see getTurnWork).
267
- const latestTurnKey = computed(() =>
268
- ɵgetLatestTurnKey(deduplicatedMessages.value),
269
- );
270
-
271
- // Work shows while the agent is on it and folds once the reply is done,
272
- // unless the user has opened or closed it themselves.
273
- const toggledTurns = ref<ReadonlyMap<string, boolean>>(new Map());
274
- const turnOpenState = computed(() => ({
275
- toggledTurns: toggledTurns.value,
276
- isRunning: props.isRunning,
277
- latestTurnKey: latestTurnKey.value,
278
- }));
279
-
280
- // How long the agent worked, for "Worked for 9s". Only known for replies this
281
- // client watched start from the user's message; a restored thread shows the
282
- // tool count instead.
283
- const workTimes = new Map<string, ɵWorkTime>();
284
- const workTimesVersion = ref(0);
285
- watch(
286
- [
287
- () => props.toolCallDisplay,
288
- () => props.isRunning,
289
- latestTurnKey,
290
- () => lastRenderedMessage.value?.role,
291
- ],
292
- ([display, isRunning, turnKey, lastRole]) => {
293
- if (display !== "turn") return;
294
- ɵrecordWorkTime(workTimes, {
295
- isRunning,
296
- latestTurnKey: turnKey,
297
- lastMessageRole: lastRole,
298
- now: Date.now(),
299
- });
300
- workTimesVersion.value++;
301
- },
302
- { immediate: true },
303
- );
304
-
305
- // Whether each working-phase message's work, and its text, shows.
306
- const workOpenState = computed(() =>
307
- ɵgetWorkOpenState(turnWork.value?.values() ?? [], turnOpenState.value),
308
- );
267
+ const latestTurnKey = computed(() => viewState.value.latestTurnKey);
309
268
 
310
269
  provide(
311
270
  ToolCallDisplayContextKey,
312
271
  computed<ɵToolCallDisplayContextValue>(() => ({
313
272
  display: props.toolCallDisplay,
314
273
  isRunning: props.isRunning,
315
- layouts: toolCallLayouts.value,
316
- workOpen: workOpenState.value.workOpen,
317
- workTextOpen: workOpenState.value.workTextOpen,
274
+ layouts: viewState.value.layouts,
275
+ workOpen: viewState.value.workOpen,
276
+ workTextOpen: viewState.value.workTextOpen,
318
277
  })),
319
278
  );
320
279
 
@@ -323,10 +282,10 @@ const WorkSummaryRow: FunctionalComponent<{
323
282
  turnKey: string;
324
283
  calls: ToolCallEntry[];
325
284
  }> = ({ turnKey, calls }) => {
326
- void workTimesVersion.value;
327
- const isWorking = ɵisTurnWorking(turnKey, turnOpenState.value);
328
- const isOpen = ɵisTurnWorkOpen(turnKey, turnOpenState.value);
329
- const times = workTimes.get(turnKey);
285
+ void viewState.value;
286
+ const isWorking = viewModel.isTurnWorking(turnKey);
287
+ const isOpen = viewModel.isTurnOpen(turnKey);
288
+ const times = viewModel.getWorkTime(turnKey);
330
289
  const summaryProps: CopilotChatWorkSummarySlotProps = {
331
290
  isWorking,
332
291
  isOpen,
@@ -334,7 +293,7 @@ const WorkSummaryRow: FunctionalComponent<{
334
293
  elapsedMs: times?.elapsedMs,
335
294
  workingSince: times?.workingSince,
336
295
  onClick: () => {
337
- toggledTurns.value = new Map(toggledTurns.value).set(turnKey, !isOpen);
296
+ viewModel.toggleTurn(turnKey);
338
297
  },
339
298
  };
340
299
  const slot = componentSlots["work-summary"];
@@ -371,34 +330,15 @@ watch(
371
330
  (messages) => commitRowKeyStore(rowKeyStore, messages),
372
331
  { flush: "post" },
373
332
  );
374
- const lastMessage = computed(() => props.messages[props.messages.length - 1]);
375
- // In the "turn" display the "Working" line at the top of the reply is the
376
- // progress indicator; before the reply's first message it sits where the
377
- // cursor would.
378
- const showCursor = computed(
379
- () =>
380
- props.isRunning &&
381
- lastMessage.value?.role !== "reasoning" &&
382
- props.toolCallDisplay !== "turn",
383
- );
384
- const pendingWorkSummary = computed(
385
- () =>
386
- props.toolCallDisplay === "turn" &&
387
- props.isRunning &&
388
- lastRenderedMessage.value?.role === "user",
389
- );
333
+ const showCursor = computed(() => viewState.value.showCursor);
334
+ const pendingWorkSummary = computed(() => viewState.value.pendingWorkSummary);
390
335
 
391
336
  watch(
392
337
  [() => props.messages.length, () => deduplicatedMessages.value.length],
393
338
  ([messageCount, deduplicatedCount]) => {
394
- if (
395
- process.env.NODE_ENV === "development" &&
396
- deduplicatedCount < messageCount
397
- ) {
398
- console.warn(
399
- `CopilotChatMessageView: Deduplicated ${messageCount - deduplicatedCount} message(s) with duplicate IDs.`,
400
- );
401
- }
339
+ if (process.env.NODE_ENV !== "development") return;
340
+ const warning = ɵduplicateMessagesWarning(messageCount, deduplicatedCount);
341
+ if (warning) console.warn(warning);
402
342
  },
403
343
  { immediate: true },
404
344
  );
@@ -607,7 +547,7 @@ function resolveActivityRenderer(
607
547
  v-else-if="message.role === 'reasoning'"
608
548
  :open="
609
549
  toolCallDisplay === 'turn'
610
- ? (workOpenState.workOpen.get(message.id) ?? true)
550
+ ? (viewState.workOpen.get(message.id) ?? true)
611
551
  : undefined
612
552
  "
613
553
  >
@@ -13,11 +13,10 @@ import { computed, provide, ref } from "vue";
13
13
  import {
14
14
  ɵformatLabel,
15
15
  ɵformatToolNames,
16
- ɵgetToolCallPreview,
17
- ɵgetToolCallRowStatus,
16
+ ɵgetToolCallGroupState,
18
17
  ɵformatCountLabel,
19
18
  } from "@copilotkit/core";
20
- import type { ToolCallEntry, ɵToolCallRowStatus } from "@copilotkit/core";
19
+ import type { ToolCallEntry } from "@copilotkit/core";
21
20
  import { useCopilotId } from "../ui/use-id";
22
21
  import { ToolCallGroupContextKey } from "./tool-call-display-context";
23
22
  import type { ToolCallGroupContextValue } from "./tool-call-display-context";
@@ -59,40 +58,15 @@ const toggle = () => {
59
58
  open.value = !open.value;
60
59
  };
61
60
 
62
- const latest = computed(() => props.calls[props.calls.length - 1]);
63
- // Only an active group has a call in progress. Between calls nothing is
64
- // running, so the latest call stays the subject (or the label flickers).
65
- const current = computed(() =>
66
- props.isActive
67
- ? (props.calls.find((call) => !call.toolMessage) ?? latest.value)
68
- : undefined,
69
- );
70
- const preview = computed(() =>
71
- current.value ? ɵgetToolCallPreview(current.value) : undefined,
72
- );
73
- const done = computed(
74
- () => props.calls.filter((call) => call.toolMessage).length,
75
- );
76
- const statuses = computed(() =>
77
- props.calls.map((call) => ɵgetToolCallRowStatus(call, props.isActive)),
78
- );
79
- const failedIds = computed(
80
- () =>
81
- new Set(
82
- props.calls
83
- .filter((_, index) => statuses.value[index] === "failed")
84
- .map((call) => call.toolCall.id),
85
- ),
86
- );
87
- const status = computed<ɵToolCallRowStatus>(() =>
88
- current.value
89
- ? "running"
90
- : failedIds.value.size > 0
91
- ? "failed"
92
- : statuses.value.includes("stopped")
93
- ? "stopped"
94
- : "done",
61
+ const state = computed(() =>
62
+ ɵgetToolCallGroupState(props.calls, props.isActive, open.value),
95
63
  );
64
+ const latest = computed(() => state.value.latest);
65
+ const current = computed(() => state.value.current);
66
+ const preview = computed(() => state.value.preview);
67
+ const done = computed(() => state.value.done);
68
+ const failedIds = computed(() => state.value.failedIds);
69
+ const status = computed(() => state.value.status);
96
70
  const single = computed(
97
71
  () =>
98
72
  props.appearance === "line" && props.calls.length === 1 && !current.value,
@@ -103,20 +77,15 @@ provide(
103
77
  computed<ToolCallGroupContextValue>(() => ({
104
78
  appearance: props.appearance,
105
79
  failedIds: failedIds.value,
80
+ statusById: state.value.statusById,
106
81
  active: props.isActive,
107
82
  })),
108
83
  );
109
84
 
110
85
  // Stack: a lone call is the top card of a stack with nothing behind it yet.
111
- const behind = computed(() =>
112
- open.value ? 0 : Math.min(props.calls.length - 1, 2),
113
- );
114
- const latestStatus = computed(() =>
115
- latest.value ? ɵgetToolCallRowStatus(latest.value, props.isActive) : "done",
116
- );
117
- const latestPreview = computed(() =>
118
- latest.value ? ɵgetToolCallPreview(latest.value) : undefined,
119
- );
86
+ const behind = computed(() => state.value.behind);
87
+ const latestStatus = computed(() => state.value.latestStatus);
88
+ const latestPreview = computed(() => state.value.latestPreview);
120
89
  const runningLabel = computed(() =>
121
90
  current.value
122
91
  ? ɵformatLabel(labels.value.toolCallRunningLabel, {
@@ -1,7 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import type { Message, ToolCall } from "@ag-ui/core";
3
- import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
4
- import { ɵresolveToolCallRenderer } from "@copilotkit/core";
3
+ import {
4
+ ɵresolveToolCallAgentId,
5
+ ɵresolveToolCallRenderer,
6
+ } from "@copilotkit/core";
5
7
  import { useCopilotKit } from "../../providers/useCopilotKit";
6
8
  import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
7
9
  import type { CopilotChatToolCallRenderSlotProps } from "./types";
@@ -54,7 +56,7 @@ function getCoreProps() {
54
56
  }
55
57
 
56
58
  function getRenderConfig() {
57
- const agentId = config.value?.agentId ?? DEFAULT_AGENT_ID;
59
+ const agentId = ɵresolveToolCallAgentId(config.value?.agentId, undefined);
58
60
  return ɵresolveToolCallRenderer(
59
61
  copilotkit.value.renderToolCalls,
60
62
  props.toolCall.function.name,
@@ -63,7 +65,7 @@ function getRenderConfig() {
63
65
  }
64
66
 
65
67
  function getMemoDeps() {
66
- const agentId = config.value?.agentId ?? DEFAULT_AGENT_ID;
68
+ const agentId = ɵresolveToolCallAgentId(config.value?.agentId, undefined);
67
69
  return getToolCallMemoDeps(
68
70
  props.toolCall,
69
71
  props.messages,
@@ -4,8 +4,10 @@ import type { PropType } from "vue";
4
4
  import type { Slots, VNode } from "vue";
5
5
  import type { AssistantMessage, Message } from "@ag-ui/core";
6
6
  import {
7
+ ɵgetToolCallGroupState,
7
8
  ɵgetToolCallLayouts,
8
9
  ɵgetToolCallRendererNames,
10
+ ɵtoolCallAppearance,
9
11
  } from "@copilotkit/core";
10
12
  import type {
11
13
  CopilotChatToolCallDisplay,
@@ -13,7 +15,6 @@ import type {
13
15
  } from "@copilotkit/core";
14
16
  import { useCopilotKit } from "../../providers/useCopilotKit";
15
17
  import type {
16
- CopilotChatToolCallGroupAppearance,
17
18
  CopilotChatToolCallRenderSlotProps,
18
19
  CopilotChatToolCallsGroupSlotProps,
19
20
  } from "./types";
@@ -54,14 +55,6 @@ defineSlots<{
54
55
 
55
56
  type ToolCallSlotName = "tool-call" | `tool-call-${string}`;
56
57
 
57
- const appearances: Partial<
58
- Record<CopilotChatToolCallDisplay, CopilotChatToolCallGroupAppearance>
59
- > = {
60
- grouped: "card",
61
- line: "line",
62
- stacked: "stack",
63
- };
64
-
65
58
  const componentSlots = useSlots() as Slots;
66
59
  const { copilotkit } = useCopilotKit();
67
60
  const context = inject(ToolCallDisplayContextKey, null);
@@ -101,7 +94,7 @@ const layout = computed(() => {
101
94
  const groupActive = (trailing: boolean) =>
102
95
  trailing && (context ? context.value.isRunning : true);
103
96
 
104
- const appearance = computed(() => appearances[display.value] ?? "card");
97
+ const appearance = computed(() => ɵtoolCallAppearance(display.value) ?? "card");
105
98
 
106
99
  const workOpen = computed(() => context?.value.workOpen.get(props.message.id));
107
100
 
@@ -117,15 +110,19 @@ const TurnRows = defineComponent({
117
110
  setup(rowProps, { slots }) {
118
111
  provide(
119
112
  ToolCallGroupContextKey,
120
- computed<ToolCallGroupContextValue>(() => ({
121
- appearance: "line",
122
- failedIds: new Set(
123
- rowProps.calls
124
- .filter((call) => call.toolMessage?.error)
125
- .map((call) => call.toolCall.id),
126
- ),
127
- active: rowProps.active,
128
- })),
113
+ computed<ToolCallGroupContextValue>(() => {
114
+ const { failedIds, statusById } = ɵgetToolCallGroupState(
115
+ rowProps.calls,
116
+ rowProps.active,
117
+ false,
118
+ );
119
+ return {
120
+ appearance: "line",
121
+ failedIds,
122
+ statusById,
123
+ active: rowProps.active,
124
+ };
125
+ }),
129
126
  );
130
127
  return () => slots.default?.();
131
128
  },
@@ -9,6 +9,11 @@ import {
9
9
  watch,
10
10
  } from "vue";
11
11
  import type { Message } from "@ag-ui/core";
12
+ import {
13
+ ɵgetChatViewState,
14
+ ɵisScrolledToBottom,
15
+ ɵnormalizeAutoScroll,
16
+ } from "@copilotkit/core";
12
17
  import type { Suggestion } from "@copilotkit/core";
13
18
  import type { Attachment } from "@copilotkit/shared";
14
19
  import { useChatLabels } from "./use-chat-labels";
@@ -19,7 +24,6 @@ import CopilotChatInput from "./CopilotChatInput.vue";
19
24
  import CopilotChatAttachmentQueue from "./CopilotChatAttachmentQueue.vue";
20
25
  import CopilotChatMessageView from "./CopilotChatMessageView.vue";
21
26
  import CopilotChatSuggestionView from "./CopilotChatSuggestionView.vue";
22
- import { normalizeAutoScroll } from "./normalize-auto-scroll";
23
27
  import type {
24
28
  CopilotChatInputMode,
25
29
  CopilotChatInterruptSlotProps,
@@ -30,7 +34,6 @@ import type {
30
34
  // Vertical gap between the scroll-to-bottom button and the input container.
31
35
  // Mirrors React `CopilotChatView` `SCROLL_BUTTON_OFFSET`.
32
36
  const SCROLL_BUTTON_OFFSET = 16;
33
- const SCROLL_BOTTOM_THRESHOLD = 12;
34
37
 
35
38
  const props = withDefaults(defineProps<CopilotChatViewProps>(), {
36
39
  messageFeedback: true,
@@ -140,8 +143,8 @@ const localInputValue = ref(props.inputValue ?? "");
140
143
 
141
144
  // Normalize the `autoScroll` prop into the canonical mode triplet
142
145
  // (`pin-to-bottom` | `pin-to-send` | `none`). Boolean back-compat is
143
- // preserved by `normalizeAutoScroll` (parity with React).
144
- const autoScrollMode = computed(() => normalizeAutoScroll(props.autoScroll));
146
+ // preserved by `ɵnormalizeAutoScroll` (parity with React).
147
+ const autoScrollMode = computed(() => ɵnormalizeAutoScroll(props.autoScroll));
145
148
  const isPinToBottomMode = computed(
146
149
  () => autoScrollMode.value === "pin-to-bottom",
147
150
  );
@@ -167,13 +170,23 @@ const effectiveKeyboardHeight = computed(() =>
167
170
  const resolvedInputValue = computed(() =>
168
171
  isControlledInput.value ? (props.inputValue ?? "") : localInputValue.value,
169
172
  );
170
- const hasSuggestions = computed(
171
- () =>
172
- !props.isConnecting &&
173
- !props.isRunning &&
174
- Array.isArray(props.suggestions) &&
175
- props.suggestions.length > 0,
173
+ // Suggestions, the welcome screen and the bottom padding follow core's rules:
174
+ // no suggestions while connecting or running, no generic welcome for a
175
+ // connecting or caller-picked thread.
176
+ const viewState = computed(() =>
177
+ ɵgetChatViewState({
178
+ messageCount: props.messages.length,
179
+ suggestionCount: Array.isArray(props.suggestions)
180
+ ? props.suggestions.length
181
+ : 0,
182
+ isRunning: props.isRunning,
183
+ isConnecting: props.isConnecting,
184
+ hasExplicitThreadId: props.hasExplicitThreadId,
185
+ welcomeScreenEnabled: props.welcomeScreen !== false,
186
+ inputContainerHeight: inputContainerHeight.value,
187
+ }),
176
188
  );
189
+ const hasSuggestions = computed(() => viewState.value.hasSuggestions);
177
190
  const hasAttachments = computed(
178
191
  () => Array.isArray(props.attachments) && props.attachments.length > 0,
179
192
  );
@@ -191,18 +204,12 @@ const forwardedMessageViewSlotNames = computed(() =>
191
204
  ),
192
205
  );
193
206
  const shouldShowWelcomeScreen = computed(
194
- () =>
195
- props.messages.length === 0 &&
196
- props.welcomeScreen !== false &&
197
- !props.isConnecting &&
198
- !props.hasExplicitThreadId,
207
+ () => viewState.value.showWelcomeScreen,
199
208
  );
200
- // Mirrors React: `paddingBottom = inputContainerHeight + (hasSuggestions ? 4 : 32)`.
201
- // React intentionally does NOT add bonus padding for attachments — the
202
- // attachment queue lives inside the input overlay, so its height is already
203
- // captured by the overlay's measured `inputContainerHeight`.
209
+ // No bonus padding for attachments: the attachment queue lives inside the
210
+ // input overlay, so its height is already in `inputContainerHeight`.
204
211
  const messagePaddingBottom = computed(
205
- () => `${inputContainerHeight.value + (hasSuggestions.value ? 4 : 32)}px`,
212
+ () => `${viewState.value.paddingBottom}px`,
206
213
  );
207
214
  const showScrollToBottomButton = computed(
208
215
  () => !shouldShowWelcomeScreen.value && !isAtBottom.value,
@@ -300,9 +307,7 @@ function updateIsAtBottom() {
300
307
  return;
301
308
  }
302
309
 
303
- const distance =
304
- element.scrollHeight - element.scrollTop - element.clientHeight;
305
- isAtBottom.value = distance <= SCROLL_BOTTOM_THRESHOLD;
310
+ isAtBottom.value = ɵisScrolledToBottom(element);
306
311
  }
307
312
 
308
313
  function syncInputContainerHeight() {