@copilotkit/react-core 1.59.3 → 1.59.5-canary.1781104893

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,6 +1,6 @@
1
1
  import * as React$1 from "react";
2
2
  import React, { createContext, forwardRef, memo, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
3
- import { CopilotKitCore, CopilotKitCoreRuntimeConnectionStatus, ProxiedCopilotRuntimeAgent, ToolCallStatus, ɵcreateThreadStore, ɵselectHasNextPage, ɵselectIsFetchingNextPage, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading } from "@copilotkit/core";
3
+ import { CopilotKitCore, CopilotKitCoreRuntimeConnectionStatus, ProxiedCopilotRuntimeAgent, ToolCallStatus, isRunCompletionAware, ɵcreateThreadStore, ɵselectHasNextPage, ɵselectIsFetchingNextPage, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading } from "@copilotkit/core";
4
4
  import { HttpAgent } from "@ag-ui/client";
5
5
  import { extendTailwindMerge, twMerge } from "tailwind-merge";
6
6
  import { ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronRightIcon, Copy, Edit, Loader2, MessageCircle, Mic, Play, Plus, RefreshCw, Square, ThumbsDown, ThumbsUp, Upload, Volume2, X } from "lucide-react";
@@ -1995,7 +1995,7 @@ function LicenseWarningBanner({ type, featureName, expiryDate, graceRemaining, o
1995
1995
  severity: "warning",
1996
1996
  message: `Your CopilotKit license expires in ${graceRemaining} day${graceRemaining !== 1 ? "s" : ""}. Please renew.`,
1997
1997
  actionLabel: "Renew",
1998
- actionUrl: "https://cloud.copilotkit.ai",
1998
+ actionUrl: "https://dashboard.operations.copilotkit.ai",
1999
1999
  onDismiss
2000
2000
  });
2001
2001
  case "expired": return /* @__PURE__ */ jsx(BannerShell, {
@@ -2924,210 +2924,110 @@ const OpenGenerativeUIToolRenderer = function OpenGenerativeUIToolRenderer(props
2924
2924
  };
2925
2925
 
2926
2926
  //#endregion
2927
- //#region src/v2/a2ui/A2UIMessageRenderer.tsx
2927
+ //#region src/v2/a2ui/A2UIRecoveryStates.tsx
2928
2928
  /**
2929
- * The container key used to wrap A2UI operations for explicit detection.
2930
- * Must match A2UI_OPERATIONS_KEY in @ag-ui/a2ui-middleware and copilotkit.a2ui (Python).
2929
+ * The pre-paint lifecycle fields the middleware stamps onto the `a2ui-surface`
2930
+ * activity content (alongside `a2ui_operations` on paint). `.passthrough()` keeps
2931
+ * `a2ui_operations` and any future fields intact.
2931
2932
  */
2932
- const A2UI_OPERATIONS_KEY = "a2ui_operations";
2933
- let initialized = false;
2934
- function ensureInitialized() {
2935
- if (!initialized) {
2936
- initializeDefaultCatalog();
2937
- injectStyles();
2938
- initialized = true;
2939
- }
2940
- }
2941
- function createA2UIMessageRenderer(options) {
2942
- const { theme, catalog, loadingComponent } = options;
2943
- return {
2944
- activityType: "a2ui-surface",
2945
- content: z.any(),
2946
- render: ({ content, agent }) => {
2947
- ensureInitialized();
2948
- const [operations, setOperations] = useState([]);
2949
- const { copilotkit } = useCopilotKit();
2950
- const lastContentRef = useRef(null);
2951
- useEffect(() => {
2952
- if (content === lastContentRef.current) return;
2953
- lastContentRef.current = content;
2954
- const incoming = content?.[A2UI_OPERATIONS_KEY];
2955
- if (!content || !Array.isArray(incoming)) {
2956
- setOperations([]);
2957
- return;
2958
- }
2959
- setOperations(incoming);
2960
- }, [content]);
2961
- const groupedOperations = useMemo(() => {
2962
- const groups = /* @__PURE__ */ new Map();
2963
- for (const operation of operations) {
2964
- const surfaceId = getOperationSurfaceId(operation) ?? DEFAULT_SURFACE_ID;
2965
- if (!groups.has(surfaceId)) groups.set(surfaceId, []);
2966
- groups.get(surfaceId).push(operation);
2967
- }
2968
- return groups;
2969
- }, [operations]);
2970
- if (!groupedOperations.size) return /* @__PURE__ */ jsx(loadingComponent ?? DefaultA2UILoading, {});
2971
- return /* @__PURE__ */ jsx("div", {
2972
- className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
2973
- children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ jsx(ReactSurfaceHost, {
2974
- surfaceId,
2975
- operations: ops,
2976
- theme,
2977
- agent,
2978
- copilotkit,
2979
- catalog
2980
- }, surfaceId))
2981
- });
2982
- }
2983
- };
2984
- }
2985
- /**
2986
- * Renders a single A2UI surface using the React renderer.
2987
- * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
2988
- */
2989
- function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog }) {
2990
- return /* @__PURE__ */ jsx("div", {
2991
- className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
2992
- children: /* @__PURE__ */ jsxs(A2UIProvider, {
2993
- onAction: useCallback(async (message) => {
2994
- if (!agent) return;
2995
- message.userAction;
2996
- try {
2997
- copilotkit.setProperties({
2998
- ...copilotkit.properties,
2999
- a2uiAction: message
3000
- });
3001
- await copilotkit.runAgent({ agent });
3002
- } finally {
3003
- if (copilotkit.properties) {
3004
- const { a2uiAction, ...rest } = copilotkit.properties;
3005
- copilotkit.setProperties(rest);
3006
- }
3007
- }
3008
- }, [agent, copilotkit]),
3009
- theme,
3010
- catalog,
3011
- children: [/* @__PURE__ */ jsx(SurfaceMessageProcessor, {
3012
- surfaceId,
3013
- operations
3014
- }), /* @__PURE__ */ jsx(A2UISurfaceOrError, { surfaceId })]
3015
- })
3016
- });
2933
+ const A2UILifecycleFields = {
2934
+ status: z.enum([
2935
+ "building",
2936
+ "retrying",
2937
+ "failed"
2938
+ ]).optional(),
2939
+ attempt: z.number().optional(),
2940
+ maxAttempts: z.number().optional(),
2941
+ progressTokens: z.number().optional(),
2942
+ error: z.string().optional(),
2943
+ errors: z.array(z.any()).optional(),
2944
+ attempts: z.array(z.any()).optional(),
2945
+ debugExposure: z.enum([
2946
+ "hidden",
2947
+ "collapsed",
2948
+ "verbose"
2949
+ ]).optional()
2950
+ };
2951
+ /** Server-stamped debugExposure wins; else the client option; else "collapsed". */
2952
+ function resolveDebugExposure(content, optionDebugExposure) {
2953
+ return content?.debugExposure ?? optionDebugExposure;
3017
2954
  }
3018
- /**
3019
- * Renders the A2UI surface, or an error message if processing failed.
3020
- * Must be a child of A2UIProvider to access the error state.
3021
- */
3022
- function A2UISurfaceOrError({ surfaceId }) {
3023
- const error = useA2UIError();
3024
- if (error) return /* @__PURE__ */ jsxs("div", {
3025
- className: "cpk:rounded-lg cpk:border cpk:border-red-200 cpk:bg-red-50 cpk:p-3 cpk:text-sm cpk:text-red-700",
3026
- children: ["A2UI render error: ", error]
3027
- });
3028
- return /* @__PURE__ */ jsx(A2UIRenderer, {
3029
- surfaceId,
3030
- className: "cpk:flex cpk:flex-1"
2955
+ /** building: the generic skeleton + optional live token count. */
2956
+ function A2UIBuildingState({ content }) {
2957
+ return /* @__PURE__ */ jsx(A2UIGeneratingSkeleton, {
2958
+ label: "Building interface",
2959
+ tokens: typeof content?.progressTokens === "number" ? content.progressTokens : void 0
3031
2960
  });
3032
2961
  }
3033
2962
  /**
3034
- * Processes A2UI operations into the provider's message processor.
3035
- * Must be a child of A2UIProvider to access the actions context.
2963
+ * retrying: stays the generic skeleton through fast/transient retries; only once
2964
+ * the retry is perceptible (after `showAfterMs`, or once `attempt` crosses
2965
+ * `showAfterAttempts`) does the sub-label reveal "Retrying generation… (N/M)".
3036
2966
  */
3037
- function SurfaceMessageProcessor({ surfaceId, operations }) {
3038
- const { processMessages, getSurface } = useA2UIActions();
3039
- const lastHashRef = useRef("");
2967
+ function A2UIRetryingState({ content, showAfterMs, showAfterAttempts, debugExposure }) {
2968
+ const attempt = typeof content?.attempt === "number" ? content.attempt : void 0;
2969
+ const maxAttempts = typeof content?.maxAttempts === "number" ? content.maxAttempts : void 0;
2970
+ const immediate = attempt !== void 0 && attempt >= showAfterAttempts;
2971
+ const [revealed, setRevealed] = useState(immediate);
3040
2972
  useEffect(() => {
3041
- const hash = JSON.stringify(operations);
3042
- if (hash === lastHashRef.current) return;
3043
- lastHashRef.current = hash;
3044
- processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
3045
- }, [
3046
- processMessages,
3047
- getSurface,
3048
- surfaceId,
3049
- operations
3050
- ]);
3051
- return null;
2973
+ if (immediate) {
2974
+ setRevealed(true);
2975
+ return;
2976
+ }
2977
+ const timer = setTimeout(() => setRevealed(true), showAfterMs);
2978
+ return () => clearTimeout(timer);
2979
+ }, [immediate, showAfterMs]);
2980
+ const tokens = typeof content?.progressTokens === "number" ? content.progressTokens : void 0;
2981
+ if (!revealed) return /* @__PURE__ */ jsx(A2UIGeneratingSkeleton, {
2982
+ label: "Building interface",
2983
+ tokens
2984
+ });
2985
+ const label = attempt !== void 0 && maxAttempts !== void 0 ? `Retrying generation… (${attempt}/${maxAttempts} attempts)` : "Retrying generation…";
2986
+ const errors = Array.isArray(content?.errors) ? content.errors : [];
2987
+ return /* @__PURE__ */ jsx(A2UIGeneratingSkeleton, {
2988
+ label,
2989
+ tokens,
2990
+ children: debugExposure !== "hidden" && errors.length > 0 && /* @__PURE__ */ jsx(A2UIDebugDetails, {
2991
+ label: "validation issues",
2992
+ open: debugExposure === "verbose",
2993
+ payload: {
2994
+ attempt: content?.attempt,
2995
+ errors
2996
+ }
2997
+ })
2998
+ });
3052
2999
  }
3053
- /**
3054
- * Default loading component shown while an A2UI surface is generating.
3055
- * Displays an animated shimmer skeleton.
3056
- */
3057
- function DefaultA2UILoading() {
3000
+ /** failed: a clean hard-failure card that replaces the skeleton in place. */
3001
+ function A2UIRecoveryFailure({ content, debugExposure }) {
3058
3002
  return /* @__PURE__ */ jsxs("div", {
3059
- className: "cpk:flex cpk:flex-col cpk:gap-3 cpk:rounded-xl cpk:border cpk:border-gray-100 cpk:bg-gray-50/50 cpk:p-5",
3060
- style: { minHeight: 120 },
3003
+ className: "cpk:rounded-lg cpk:border cpk:border-amber-200 cpk:bg-amber-50 cpk:p-3 cpk:text-sm cpk:text-amber-800",
3061
3004
  children: [
3062
- /* @__PURE__ */ jsxs("div", {
3063
- className: "cpk:flex cpk:items-center cpk:gap-2",
3064
- children: [/* @__PURE__ */ jsx("div", {
3065
- className: "cpk:h-3 cpk:w-3 cpk:rounded-full cpk:bg-gray-200",
3066
- style: { animation: "cpk-a2ui-pulse 1.5s ease-in-out infinite" }
3067
- }), /* @__PURE__ */ jsx("span", {
3068
- className: "cpk:text-xs cpk:font-medium cpk:text-gray-400",
3069
- children: "Generating UI..."
3070
- })]
3005
+ /* @__PURE__ */ jsx("div", {
3006
+ className: "cpk:font-medium",
3007
+ children: "Couldn't generate the UI"
3071
3008
  }),
3072
3009
  /* @__PURE__ */ jsx("div", {
3073
- className: "cpk:flex cpk:flex-col cpk:gap-2",
3074
- children: [
3075
- .8,
3076
- .6,
3077
- .4
3078
- ].map((width, i) => /* @__PURE__ */ jsx("div", {
3079
- className: "cpk:h-3 cpk:rounded cpk:bg-gray-200/70",
3080
- style: {
3081
- width: `${width * 100}%`,
3082
- animation: `cpk-a2ui-pulse 1.5s ease-in-out ${i * .15}s infinite`
3083
- }
3084
- }, i))
3010
+ className: "cpk:mt-1 cpk:text-xs cpk:text-amber-700",
3011
+ children: "Something went wrong rendering this. You can keep chatting and try again."
3085
3012
  }),
3086
- /* @__PURE__ */ jsx("style", { children: `
3087
- @keyframes cpk-a2ui-pulse {
3088
- 0%, 100% { opacity: 0.4; }
3089
- 50% { opacity: 1; }
3090
- }
3091
- ` })
3013
+ debugExposure !== "hidden" && /* @__PURE__ */ jsx(A2UIDebugDetails, {
3014
+ label: "developer details",
3015
+ open: debugExposure === "verbose",
3016
+ payload: {
3017
+ error: content?.error,
3018
+ attempts: content?.attempts
3019
+ }
3020
+ })
3092
3021
  ]
3093
3022
  });
3094
3023
  }
3095
- function getOperationSurfaceId(operation) {
3096
- if (!operation || typeof operation !== "object") return null;
3097
- if (typeof operation.surfaceId === "string") return operation.surfaceId;
3098
- return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
3099
- }
3100
-
3101
- //#endregion
3102
- //#region src/v2/a2ui/A2UIToolCallRenderer.tsx
3103
3024
  /**
3104
- * Tool name used by the dynamic A2UI generation secondary LLM.
3105
- * This renderer is auto-registered when A2UI is enabled.
3106
- */
3107
- const RENDER_A2UI_TOOL_NAME = "render_a2ui";
3108
- /**
3109
- * Built-in progress indicator for dynamic A2UI generation.
3110
- * Shows a skeleton wireframe that progressively reveals as tokens stream in.
3111
- *
3112
- * Registered automatically when A2UI is enabled. Users can override by
3113
- * providing their own `useRenderTool({ name: "render_a2ui", ... })`.
3025
+ * Animated wireframe skeleton with a label, an optional live token count, and an
3026
+ * optional debug-detail slot below it. Pure CSS animation (no data dependency).
3027
+ * The `tokens` count drives a progressive reveal of skeleton rows.
3114
3028
  */
3115
- function A2UIProgressIndicator({ parameters }) {
3116
- const lastRef = useRef({
3117
- time: 0,
3118
- tokens: 0
3119
- });
3120
- const now = Date.now();
3121
- let { tokens } = lastRef.current;
3122
- if (now - lastRef.current.time > 200) {
3123
- const chars = JSON.stringify(parameters ?? {}).length;
3124
- tokens = Math.round(chars / 4);
3125
- lastRef.current = {
3126
- time: now,
3127
- tokens
3128
- };
3129
- }
3130
- const phase = tokens < 50 ? 0 : tokens < 200 ? 1 : tokens < 400 ? 2 : 3;
3029
+ function A2UIGeneratingSkeleton({ label, tokens, children }) {
3030
+ const phase = tokens == null ? 3 : tokens < 50 ? 0 : tokens < 200 ? 1 : tokens < 400 ? 2 : 3;
3131
3031
  return /* @__PURE__ */ jsxs("div", {
3132
3032
  style: {
3133
3033
  margin: "12px 0",
@@ -3335,8 +3235,8 @@ function A2UIProgressIndicator({ parameters }) {
3335
3235
  color: "#a1a1aa",
3336
3236
  letterSpacing: "0.025em"
3337
3237
  },
3338
- children: "Building interface"
3339
- }), tokens > 0 && /* @__PURE__ */ jsxs("span", {
3238
+ children: label
3239
+ }), typeof tokens === "number" && tokens > 0 && /* @__PURE__ */ jsxs("span", {
3340
3240
  style: {
3341
3241
  fontSize: 11,
3342
3242
  color: "#d4d4d8",
@@ -3349,6 +3249,7 @@ function A2UIProgressIndicator({ parameters }) {
3349
3249
  ]
3350
3250
  })]
3351
3251
  }),
3252
+ children,
3352
3253
  /* @__PURE__ */ jsx("style", { children: `
3353
3254
  @keyframes cpk-a2ui-fade {
3354
3255
  0%, 100% { opacity: 1; }
@@ -3362,6 +3263,20 @@ function A2UIProgressIndicator({ parameters }) {
3362
3263
  ]
3363
3264
  });
3364
3265
  }
3266
+ function A2UIDebugDetails({ label, open, payload }) {
3267
+ return /* @__PURE__ */ jsxs("details", {
3268
+ open,
3269
+ className: "cpk:mt-2 cpk:text-xs",
3270
+ children: [/* @__PURE__ */ jsx("summary", {
3271
+ className: "cpk:cursor-pointer cpk:text-gray-500",
3272
+ children: label
3273
+ }), /* @__PURE__ */ jsx("pre", {
3274
+ className: "cpk:mt-1 cpk:overflow-auto cpk:rounded cpk:bg-gray-100 cpk:p-2 cpk:text-gray-700",
3275
+ style: { fontSize: 11 },
3276
+ children: JSON.stringify(payload, null, 2)
3277
+ })]
3278
+ });
3279
+ }
3365
3280
  function Dot() {
3366
3281
  return /* @__PURE__ */ jsx("div", { style: {
3367
3282
  width: 7,
@@ -3397,13 +3312,242 @@ function Row({ children, show, delay = 0 }) {
3397
3312
  children
3398
3313
  });
3399
3314
  }
3315
+
3316
+ //#endregion
3317
+ //#region src/v2/a2ui/A2UIMessageRenderer.tsx
3318
+ /**
3319
+ * The container key used to wrap A2UI operations for explicit detection.
3320
+ * Must match A2UI_OPERATIONS_KEY in @ag-ui/a2ui-middleware and copilotkit.a2ui (Python).
3321
+ */
3322
+ const A2UI_OPERATIONS_KEY = "a2ui_operations";
3323
+ let initialized = false;
3324
+ function ensureInitialized() {
3325
+ if (!initialized) {
3326
+ initializeDefaultCatalog();
3327
+ injectStyles();
3328
+ initialized = true;
3329
+ }
3330
+ }
3331
+ /**
3332
+ * The `a2ui-surface` activity carries the WHOLE generative-UI lifecycle on one
3333
+ * stable messageId (OSS-162): pre-paint `status` ("building" | "retrying" |
3334
+ * "failed") with recovery detail, then `a2ui_operations` on paint. The states
3335
+ * swap in place, so the painted surface replaces the skeleton with no extra
3336
+ * coordination. `.passthrough()` preserves operations + any future fields.
3337
+ */
3338
+ const A2UISurfaceContentSchema = z.object({
3339
+ a2ui_operations: z.array(z.any()).optional(),
3340
+ ...A2UILifecycleFields
3341
+ }).passthrough();
3342
+ function createA2UIMessageRenderer(options) {
3343
+ const { theme, catalog, loadingComponent, recovery } = options;
3344
+ const showAfterMs = recovery?.showAfterMs ?? 2e3;
3345
+ const showAfterAttempts = recovery?.showAfterAttempts ?? 2;
3346
+ const optionDebugExposure = recovery?.debugExposure ?? "collapsed";
3347
+ return {
3348
+ activityType: "a2ui-surface",
3349
+ content: A2UISurfaceContentSchema,
3350
+ render: ({ content, agent }) => {
3351
+ ensureInitialized();
3352
+ const [operations, setOperations] = useState([]);
3353
+ const { copilotkit } = useCopilotKit();
3354
+ const lastContentRef = useRef(null);
3355
+ useEffect(() => {
3356
+ if (content === lastContentRef.current) return;
3357
+ lastContentRef.current = content;
3358
+ const incoming = content?.[A2UI_OPERATIONS_KEY];
3359
+ if (!content || !Array.isArray(incoming)) {
3360
+ setOperations([]);
3361
+ return;
3362
+ }
3363
+ setOperations(incoming);
3364
+ }, [content]);
3365
+ const groupedOperations = useMemo(() => {
3366
+ const groups = /* @__PURE__ */ new Map();
3367
+ for (const operation of operations) {
3368
+ const surfaceId = getOperationSurfaceId(operation) ?? DEFAULT_SURFACE_ID;
3369
+ if (!groups.has(surfaceId)) groups.set(surfaceId, []);
3370
+ groups.get(surfaceId).push(operation);
3371
+ }
3372
+ return groups;
3373
+ }, [operations]);
3374
+ const hasOps = groupedOperations.size > 0;
3375
+ const renderLifecycle = (c) => {
3376
+ const status = c?.status;
3377
+ const debugExposure = resolveDebugExposure(c, optionDebugExposure);
3378
+ if (status === "failed") return /* @__PURE__ */ jsx(A2UIRecoveryFailure, {
3379
+ content: c,
3380
+ debugExposure
3381
+ });
3382
+ if (status === "retrying") return /* @__PURE__ */ jsx(A2UIRetryingState, {
3383
+ content: c,
3384
+ showAfterMs,
3385
+ showAfterAttempts,
3386
+ debugExposure
3387
+ });
3388
+ if (loadingComponent) return /* @__PURE__ */ jsx(loadingComponent, {});
3389
+ return /* @__PURE__ */ jsx(A2UIBuildingState, { content: c });
3390
+ };
3391
+ const lastLoaderContentRef = useRef(null);
3392
+ if (!(Array.isArray(content?.[A2UI_OPERATIONS_KEY]) && content[A2UI_OPERATIONS_KEY].length > 0)) lastLoaderContentRef.current = content;
3393
+ const [surfaceReady, setSurfaceReady] = useState(false);
3394
+ const readyRef = useRef(false);
3395
+ const markSurfaceReady = useCallback(() => {
3396
+ if (readyRef.current) return;
3397
+ readyRef.current = true;
3398
+ requestAnimationFrame(() => setSurfaceReady(true));
3399
+ }, []);
3400
+ useEffect(() => {
3401
+ if (!hasOps) {
3402
+ setSurfaceReady(false);
3403
+ readyRef.current = false;
3404
+ return;
3405
+ }
3406
+ const t = setTimeout(() => setSurfaceReady(true), 8e3);
3407
+ return () => clearTimeout(t);
3408
+ }, [hasOps]);
3409
+ if (!hasOps) return renderLifecycle(content);
3410
+ const surfaces = /* @__PURE__ */ jsx("div", {
3411
+ className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
3412
+ children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ jsx(ReactSurfaceHost, {
3413
+ surfaceId,
3414
+ operations: ops,
3415
+ theme,
3416
+ agent,
3417
+ copilotkit,
3418
+ catalog,
3419
+ onReady: markSurfaceReady
3420
+ }, surfaceId))
3421
+ });
3422
+ return /* @__PURE__ */ jsxs("div", {
3423
+ style: { position: "relative" },
3424
+ children: [/* @__PURE__ */ jsx("div", {
3425
+ "aria-hidden": !surfaceReady,
3426
+ style: surfaceReady ? void 0 : {
3427
+ position: "absolute",
3428
+ inset: 0,
3429
+ opacity: 0,
3430
+ pointerEvents: "none"
3431
+ },
3432
+ children: surfaces
3433
+ }), !surfaceReady && renderLifecycle(lastLoaderContentRef.current ?? content)]
3434
+ });
3435
+ }
3436
+ };
3437
+ }
3400
3438
  /**
3401
- * Registers the built-in `render_a2ui` tool call renderer via the props-based
3402
- * `setRenderToolCalls` mechanism (not `useRenderTool`).
3439
+ * Renders a single A2UI surface using the React renderer.
3440
+ * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
3441
+ */
3442
+ function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog, onReady }) {
3443
+ return /* @__PURE__ */ jsx("div", {
3444
+ className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
3445
+ children: /* @__PURE__ */ jsxs(A2UIProvider, {
3446
+ onAction: useCallback(async (message) => {
3447
+ if (!agent) return;
3448
+ message.userAction;
3449
+ try {
3450
+ copilotkit.setProperties({
3451
+ ...copilotkit.properties,
3452
+ a2uiAction: message
3453
+ });
3454
+ await copilotkit.runAgent({ agent });
3455
+ } finally {
3456
+ if (copilotkit.properties) {
3457
+ const { a2uiAction, ...rest } = copilotkit.properties;
3458
+ copilotkit.setProperties(rest);
3459
+ }
3460
+ }
3461
+ }, [agent, copilotkit]),
3462
+ theme,
3463
+ catalog,
3464
+ children: [/* @__PURE__ */ jsx(SurfaceMessageProcessor, {
3465
+ surfaceId,
3466
+ operations,
3467
+ onReady
3468
+ }), /* @__PURE__ */ jsx(A2UISurfaceOrError, { surfaceId })]
3469
+ })
3470
+ });
3471
+ }
3472
+ /**
3473
+ * Renders the A2UI surface, or an error message if processing failed.
3474
+ * Must be a child of A2UIProvider to access the error state.
3475
+ */
3476
+ function A2UISurfaceOrError({ surfaceId }) {
3477
+ const error = useA2UIError();
3478
+ if (error) return /* @__PURE__ */ jsxs("div", {
3479
+ className: "cpk:rounded-lg cpk:border cpk:border-red-200 cpk:bg-red-50 cpk:p-3 cpk:text-sm cpk:text-red-700",
3480
+ children: ["A2UI render error: ", error]
3481
+ });
3482
+ return /* @__PURE__ */ jsx(A2UIRenderer, {
3483
+ surfaceId,
3484
+ className: "cpk:flex cpk:flex-1"
3485
+ });
3486
+ }
3487
+ /**
3488
+ * Processes A2UI operations into the provider's message processor.
3489
+ * Must be a child of A2UIProvider to access the actions context.
3490
+ */
3491
+ function SurfaceMessageProcessor({ surfaceId, operations, onReady }) {
3492
+ const { processMessages, getSurface } = useA2UIActions();
3493
+ const lastHashRef = useRef("");
3494
+ useEffect(() => {
3495
+ const hash = JSON.stringify(operations);
3496
+ if (hash === lastHashRef.current) return;
3497
+ lastHashRef.current = hash;
3498
+ processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
3499
+ if (onReady && surfaceHasRenderableContent(operations)) onReady();
3500
+ }, [
3501
+ processMessages,
3502
+ getSurface,
3503
+ surfaceId,
3504
+ operations,
3505
+ onReady
3506
+ ]);
3507
+ return null;
3508
+ }
3509
+ /**
3510
+ * Whether the surface's operations are enough to paint a visible card yet.
3511
+ * A data-bound surface references its data via `path` and renders nothing until
3512
+ * the data model has ≥1 value; a static surface (no path refs) paints from its
3513
+ * components alone. Used to time the loader→surface cross-over to actual content
3514
+ * arrival rather than a fixed delay. (OSS-162)
3515
+ */
3516
+ function surfaceHasRenderableContent(operations) {
3517
+ const componentOps = operations.filter((o) => o?.updateComponents);
3518
+ if (!componentOps.length) return false;
3519
+ if (!JSON.stringify(componentOps).includes("\"path\"")) return true;
3520
+ return operations.some((o) => {
3521
+ const v = o?.updateDataModel?.value;
3522
+ if (!v || typeof v !== "object") return false;
3523
+ return Object.values(v).some((x) => Array.isArray(x) ? x.length > 0 : x !== null && x !== void 0 && x !== "");
3524
+ });
3525
+ }
3526
+ function getOperationSurfaceId(operation) {
3527
+ if (!operation || typeof operation !== "object") return null;
3528
+ if (typeof operation.surfaceId === "string") return operation.surfaceId;
3529
+ return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
3530
+ }
3531
+
3532
+ //#endregion
3533
+ //#region src/v2/a2ui/A2UIToolCallRenderer.tsx
3534
+ /**
3535
+ * Tool name used by the dynamic A2UI generation secondary LLM.
3536
+ */
3537
+ const RENDER_A2UI_TOOL_NAME = "render_a2ui";
3538
+ /**
3539
+ * Registers a no-op renderer for the `render_a2ui` tool call so its raw streamed
3540
+ * args are never surfaced in the transcript.
3541
+ *
3542
+ * The generation skeleton / retry / failure UX is NO LONGER owned here (OSS-162):
3543
+ * the A2UI middleware drives the whole lifecycle on the `a2ui-surface` activity
3544
+ * (one stable messageId, building → retrying → failed → painted), rendered in
3545
+ * place by `createA2UIMessageRenderer`. Owning a skeleton per tool call caused a
3546
+ * duplicate skeleton on retries / multi-call generations and a skeleton that
3547
+ * lingered after the surface painted — both fixed by retiring it here.
3403
3548
  *
3404
- * This ensures user-registered `useRenderTool({ name: "render_a2ui", ... })`
3405
- * hooks automatically override the built-in, since the merge logic in
3406
- * react-core.ts gives hook-based entries priority over prop-based entries.
3549
+ * Users can still override with their own `useRenderTool({ name: "render_a2ui" })`
3550
+ * (hook-based entries take priority over this prop-based registration).
3407
3551
  */
3408
3552
  function A2UIBuiltInToolCallRenderer() {
3409
3553
  const { copilotkit } = useCopilotKit();
@@ -3411,15 +3555,7 @@ function A2UIBuiltInToolCallRenderer() {
3411
3555
  const renderer = defineToolCallRenderer({
3412
3556
  name: RENDER_A2UI_TOOL_NAME,
3413
3557
  args: z.any(),
3414
- render: ({ status, args: parameters }) => {
3415
- if (status === "complete") return /* @__PURE__ */ jsx(Fragment$1, {});
3416
- const params = parameters;
3417
- const items = params?.items;
3418
- if (Array.isArray(items) && items.length > 0) return /* @__PURE__ */ jsx(Fragment$1, {});
3419
- const components = params?.components;
3420
- if (Array.isArray(components) && components.length > 2) return /* @__PURE__ */ jsx(Fragment$1, {});
3421
- return /* @__PURE__ */ jsx(A2UIProgressIndicator, { parameters });
3422
- }
3558
+ render: () => /* @__PURE__ */ jsx(Fragment$1, {})
3423
3559
  });
3424
3560
  const existing = copilotkit._renderToolCalls ?? [];
3425
3561
  copilotkit.setRenderToolCalls([...existing.filter((rc) => rc.name !== RENDER_A2UI_TOOL_NAME), renderer]);
@@ -3556,7 +3692,8 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY
3556
3692
  if (runtimeA2UIEnabled) renderers.unshift(createA2UIMessageRenderer({
3557
3693
  theme: a2ui?.theme ?? viewerTheme,
3558
3694
  catalog: a2ui?.catalog,
3559
- loadingComponent: a2ui?.loadingComponent
3695
+ loadingComponent: a2ui?.loadingComponent,
3696
+ recovery: a2ui?.recovery
3560
3697
  }));
3561
3698
  return renderers;
3562
3699
  }, [
@@ -4781,6 +4918,175 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4781
4918
  };
4782
4919
  }
4783
4920
 
4921
+ //#endregion
4922
+ //#region src/v2/lib/record-annotation.ts
4923
+ /**
4924
+ * Low-level function that posts an arbitrary annotation to the CopilotKit
4925
+ * runtime's general annotation endpoint (`POST /annotate`).
4926
+ *
4927
+ * This is the single transport entry point for all annotation types. Higher-
4928
+ * level hooks (e.g. `useLearnFromUserAction`) build the `type`/`payload` pair
4929
+ * for their specific annotation shape and delegate the HTTP call here.
4930
+ *
4931
+ * The function uses the same transport as `useLearnFromUserAction`:
4932
+ * - `runtimeUrl` from `copilotkit.runtimeUrl` (BFF proxies to the platform)
4933
+ * - `headers` from `copilotkit.headers` (customer auth forwarded to BFF)
4934
+ * - `clientEventId` auto-generated via `randomUUID()` when omitted
4935
+ * - `userId` is resolved server-side by the runtime; the client never sends it
4936
+ * - Errors propagate to the caller (fire-and-propagate, not fire-and-forget)
4937
+ *
4938
+ * @param args - Transport dependencies plus annotation fields.
4939
+ * @returns The platform result containing the annotation row `id` and a
4940
+ * `duplicate` flag.
4941
+ * @throws When the network request fails or the runtime returns a non-2xx
4942
+ * status. Callers that want fire-and-forget behavior should `.catch`
4943
+ * at the call site.
4944
+ */
4945
+ async function recordAnnotation(args) {
4946
+ const { runtimeUrl, headers, type, payload, threadId, occurredAt } = args;
4947
+ const body = {
4948
+ type,
4949
+ threadId,
4950
+ clientEventId: args.clientEventId ?? randomUUID(),
4951
+ ...payload !== void 0 ? { payload } : {},
4952
+ ...occurredAt !== void 0 ? { occurredAt } : {}
4953
+ };
4954
+ const response = await fetch(`${runtimeUrl}/annotate`, {
4955
+ method: "POST",
4956
+ headers: {
4957
+ "Content-Type": "application/json",
4958
+ ...headers
4959
+ },
4960
+ body: JSON.stringify(body)
4961
+ });
4962
+ if (!response.ok) {
4963
+ const text = await response.text().catch(() => "");
4964
+ throw new Error(`recordAnnotation: request failed (${response.status})${text ? `: ${text}` : ""}`);
4965
+ }
4966
+ const text = await response.text();
4967
+ if (!text) throw new Error(`recordAnnotation: runtime ${runtimeUrl}/annotate returned ${response.status} with an empty body`);
4968
+ try {
4969
+ return JSON.parse(text);
4970
+ } catch {
4971
+ throw new Error(`recordAnnotation: runtime ${runtimeUrl}/annotate returned a non-JSON body (status ${response.status})`);
4972
+ }
4973
+ }
4974
+
4975
+ //#endregion
4976
+ //#region src/v2/hooks/use-learn-from-user-action.tsx
4977
+ /**
4978
+ * Record a user UI interaction in the Intelligence platform's user-actions
4979
+ * stream. The platform's auto-curated knowledge base agent reads these
4980
+ * (alongside finished agent runs) and writes free-form Obsidian-flavored
4981
+ * markdown to `/project`, where any agent in the same project can later
4982
+ * read it via the `copilotkit_knowledge_base_shell` MCP tool.
4983
+ *
4984
+ * The hook returns a stable function. Calling it issues a request to the
4985
+ * customer's CopilotKit runtime (`POST ${runtimeUrl}/annotate`), which
4986
+ * resolves the Intel user from the BFF's auth and forwards to the
4987
+ * platform — the Intel API key never reaches the browser.
4988
+ *
4989
+ * If `clientEventId` is omitted `recordAnnotation` generates a UUID per call,
4990
+ * so a naive double-call (e.g. React 18 strict-mode double-mount, or a retry
4991
+ * after a network blip on a fresh Promise) is naturally safe. Supply your
4992
+ * own key when a single semantic event must remain idempotent across
4993
+ * multiple `learnFromUserAction(...)` calls.
4994
+ *
4995
+ * @example
4996
+ * ```tsx
4997
+ * import { useLearnFromUserAction } from "@copilotkit/react-core";
4998
+ *
4999
+ * function SettingsPage({ threadId }) {
5000
+ * const learnFromUserAction = useLearnFromUserAction();
5001
+ *
5002
+ * const onRename = (oldName: string, newName: string) => {
5003
+ * void learnFromUserAction({
5004
+ * threadId,
5005
+ * title: "Renamed project",
5006
+ * data: { previous: { name: oldName }, next: { name: newName } },
5007
+ * });
5008
+ * };
5009
+ * }
5010
+ * ```
5011
+ */
5012
+ function useLearnFromUserAction() {
5013
+ const { copilotkit } = useCopilotKit();
5014
+ return useCallback(async (input) => {
5015
+ const runtimeUrl = copilotkit.runtimeUrl;
5016
+ if (!runtimeUrl) throw new Error("useLearnFromUserAction: runtimeUrl is not configured. Set it on <CopilotKitProvider runtimeUrl=...>.");
5017
+ const payload = {
5018
+ ...input.title !== void 0 ? { title: input.title } : {},
5019
+ ...input.description !== void 0 ? { description: input.description } : {},
5020
+ ...input.data !== void 0 ? { data: input.data } : {}
5021
+ };
5022
+ return recordAnnotation({
5023
+ runtimeUrl,
5024
+ headers: copilotkit.headers ?? {},
5025
+ type: "user_action",
5026
+ payload: Object.keys(payload).length > 0 ? payload : void 0,
5027
+ threadId: input.threadId,
5028
+ clientEventId: input.clientEventId,
5029
+ occurredAt: input.occurredAt
5030
+ });
5031
+ }, [copilotkit]);
5032
+ }
5033
+
5034
+ //#endregion
5035
+ //#region src/v2/hooks/use-learn-from-user-action-in-current-thread.tsx
5036
+ /**
5037
+ * Record a user UI interaction against the **current chat's** thread. The
5038
+ * `threadId` is sourced from the surrounding
5039
+ * `<CopilotChatConfigurationProvider>` (the same provider `<CopilotChat>`,
5040
+ * `<CopilotSidebar>`, and friends set up), so callers in a chat-aware
5041
+ * subtree don't need to thread an id through manually.
5042
+ *
5043
+ * Throws on **call** (not on mount) when there is no chat-config provider
5044
+ * in scope — matches the "throw on call when runtimeUrl is missing"
5045
+ * behavior of {@link useLearnFromUserAction}. Mounting the hook in a branch
5046
+ * that never fires is harmless.
5047
+ *
5048
+ * The recorder does NOT accept a `threadId` override. If you need to
5049
+ * record against an explicit thread, use {@link useLearnFromUserAction}
5050
+ * directly — two hooks, two crisp contracts, no mode confusion.
5051
+ *
5052
+ * This hook always uses `config.threadId`, regardless of whether the
5053
+ * surrounding chat config minted it internally or received one from
5054
+ * the caller. Auto-minted threads simply mean the action lands under
5055
+ * a thread the platform never saw — the writer agent still distills
5056
+ * user-action-only threads (it does not require the thread to exist
5057
+ * in `cpki.threads`), so the loop keeps learning.
5058
+ *
5059
+ * @example
5060
+ * ```tsx
5061
+ * import { useLearnFromUserActionInCurrentThread } from "@copilotkit/react-core";
5062
+ *
5063
+ * function SettingsPanel() {
5064
+ * const learnFromUserAction = useLearnFromUserActionInCurrentThread();
5065
+ *
5066
+ * const onRename = (oldName: string, newName: string) => {
5067
+ * void learnFromUserAction({
5068
+ * title: "Renamed project",
5069
+ * data: { previous: { name: oldName }, next: { name: newName } },
5070
+ * });
5071
+ * };
5072
+ *
5073
+ * // ...
5074
+ * }
5075
+ * ```
5076
+ */
5077
+ function useLearnFromUserActionInCurrentThread() {
5078
+ const config = useCopilotChatConfiguration();
5079
+ const learnFromUserAction = useLearnFromUserAction();
5080
+ return useCallback(async (input) => {
5081
+ const threadId = config?.threadId;
5082
+ if (!threadId) throw new Error("useLearnFromUserActionInCurrentThread: no CopilotChatConfigurationProvider in scope. Wrap the call site in <CopilotChat>, <CopilotSidebar>, or <CopilotChatConfigurationProvider>, or use `useLearnFromUserAction()` and pass `threadId` explicitly.");
5083
+ return learnFromUserAction({
5084
+ ...input,
5085
+ threadId
5086
+ });
5087
+ }, [config?.threadId, learnFromUserAction]);
5088
+ }
5089
+
4784
5090
  //#endregion
4785
5091
  //#region src/v2/hooks/use-attachments.tsx
4786
5092
  /**
@@ -4939,6 +5245,153 @@ function useAttachments({ config }) {
4939
5245
  };
4940
5246
  }
4941
5247
 
5248
+ //#endregion
5249
+ //#region src/v2/hooks/use-learning-containers.tsx
5250
+ /** The default learning containers value. Matches the backend default. */
5251
+ const DEFAULT_CONTAINERS = ["project"];
5252
+ /**
5253
+ * Declaratively keeps a thread's learning containers in sync by emitting
5254
+ * `set_learning_containers` annotations via the CopilotKit runtime annotate
5255
+ * endpoint (`POST ${runtimeUrl}/annotate`).
5256
+ *
5257
+ * **Emit rules:**
5258
+ * - On mount with `["project"]` (the backend default) → does NOT emit.
5259
+ * Absence of an annotation equals the default, so the round-trip is skipped.
5260
+ * - On mount with any other value → emits immediately.
5261
+ * - On any subsequent content change (including a switch back to
5262
+ * `["project"]`) → emits (a deliberate switch is always recorded).
5263
+ * - On unmount or threadId change → emits a reset to `["project"]`
5264
+ * so the backend is left in a clean state for the next consumer.
5265
+ * Changing `learningContainers` within the same thread does NOT reset the
5266
+ * thread; only the new value is emitted.
5267
+ *
5268
+ * Content-equality is evaluated via `JSON.stringify` so a fresh array literal
5269
+ * with the same items does NOT trigger a redundant emit.
5270
+ *
5271
+ * If `runtimeUrl` is absent, all emits are silently skipped.
5272
+ *
5273
+ * @example
5274
+ * ```tsx
5275
+ * function ThreadPane({ threadId, userScope }: Props) {
5276
+ * useLearningContainers({
5277
+ * threadId,
5278
+ * learningContainers: [userScope],
5279
+ * });
5280
+ * // ...
5281
+ * }
5282
+ * ```
5283
+ */
5284
+ function useLearningContainers({ threadId, learningContainers }) {
5285
+ const { copilotkit } = useCopilotKit();
5286
+ /**
5287
+ * Tracks the last-synced container list so content-identical rerenders
5288
+ * (fresh array, same values) do not fire a redundant emit.
5289
+ * `null` = nothing synced yet (initial state or after a threadId reset).
5290
+ */
5291
+ const lastSyncedRef = useRef(null);
5292
+ /** Guards the missing-runtimeUrl warning so it fires at most once per hook instance. */
5293
+ const warnedMissingUrlRef = useRef(false);
5294
+ const runtimeUrlRef = useRef(copilotkit.runtimeUrl);
5295
+ const headersRef = useRef(copilotkit.headers ?? {});
5296
+ runtimeUrlRef.current = copilotkit.runtimeUrl;
5297
+ headersRef.current = copilotkit.headers ?? {};
5298
+ const key = JSON.stringify(learningContainers);
5299
+ const defaultKey = JSON.stringify(DEFAULT_CONTAINERS);
5300
+ useEffect(() => {
5301
+ const runtimeUrl = copilotkit.runtimeUrl;
5302
+ const headers = copilotkit.headers ?? {};
5303
+ /**
5304
+ * Fire-and-forget emit; errors must not surface in render.
5305
+ * Failures are logged as warnings so they are diagnosable without
5306
+ * propagating into the React render cycle.
5307
+ */
5308
+ const emit = (containers) => {
5309
+ if (!runtimeUrl) {
5310
+ if (!warnedMissingUrlRef.current) {
5311
+ warnedMissingUrlRef.current = true;
5312
+ console.warn("useLearningContainers: runtimeUrl not configured; learning-container sync disabled");
5313
+ }
5314
+ return;
5315
+ }
5316
+ recordAnnotation({
5317
+ runtimeUrl,
5318
+ headers,
5319
+ type: "set_learning_containers",
5320
+ payload: { containers },
5321
+ threadId
5322
+ }).catch((err) => {
5323
+ console.warn("useLearningContainers: failed to record set_learning_containers", err);
5324
+ });
5325
+ };
5326
+ if (lastSyncedRef.current === null) {
5327
+ if (key === defaultKey) {
5328
+ lastSyncedRef.current = learningContainers;
5329
+ return;
5330
+ }
5331
+ emit(learningContainers);
5332
+ lastSyncedRef.current = learningContainers;
5333
+ } else if (key !== JSON.stringify(lastSyncedRef.current)) {
5334
+ emit(learningContainers);
5335
+ lastSyncedRef.current = learningContainers;
5336
+ }
5337
+ }, [threadId, key]);
5338
+ useEffect(() => {
5339
+ const capturedThreadId = threadId;
5340
+ return () => {
5341
+ const capturedRuntimeUrl = runtimeUrlRef.current;
5342
+ const capturedHeaders = headersRef.current;
5343
+ if (capturedRuntimeUrl) recordAnnotation({
5344
+ runtimeUrl: capturedRuntimeUrl,
5345
+ headers: capturedHeaders,
5346
+ type: "set_learning_containers",
5347
+ payload: { containers: DEFAULT_CONTAINERS },
5348
+ threadId: capturedThreadId
5349
+ }).catch((err) => {
5350
+ console.warn("useLearningContainers: failed to record set_learning_containers", err);
5351
+ });
5352
+ lastSyncedRef.current = null;
5353
+ };
5354
+ }, [threadId]);
5355
+ }
5356
+
5357
+ //#endregion
5358
+ //#region src/v2/hooks/use-learning-containers-in-current-thread.tsx
5359
+ /**
5360
+ * Declaratively keeps the **current chat thread's** learning containers in
5361
+ * sync. The `threadId` is sourced from the surrounding
5362
+ * `<CopilotChatConfigurationProvider>` (the same provider `<CopilotChat>`,
5363
+ * `<CopilotSidebar>`, and friends set up), so callers in a chat-aware
5364
+ * subtree don't need to thread an id through manually.
5365
+ *
5366
+ * **Throws on render** when there is no chat-config provider in scope or
5367
+ * when the provider does not yet have an active `threadId`. Mount the hook
5368
+ * inside a subtree that is guaranteed to have a thread context.
5369
+ *
5370
+ * If you need to manage an explicit thread, use {@link useLearningContainers}
5371
+ * directly — two hooks, two crisp contracts, no mode confusion.
5372
+ *
5373
+ * @throws When no `CopilotChatConfigurationProvider` is in scope or when the
5374
+ * active `threadId` is absent/empty.
5375
+ *
5376
+ * @example
5377
+ * ```tsx
5378
+ * function ThreadPanel({ scope }: Props) {
5379
+ * useLearningContainersInCurrentThread({
5380
+ * learningContainers: [scope],
5381
+ * });
5382
+ * // ...
5383
+ * }
5384
+ * ```
5385
+ */
5386
+ function useLearningContainersInCurrentThread({ learningContainers }) {
5387
+ const threadId = useCopilotChatConfiguration()?.threadId;
5388
+ if (!threadId) throw new Error("useLearningContainersInCurrentThread must be used within a thread context (no active threadId). Wrap the component in <CopilotChat>, <CopilotSidebar>, or <CopilotChatConfigurationProvider>, or use `useLearningContainers()` and pass `threadId` explicitly.");
5389
+ useLearningContainers({
5390
+ threadId,
5391
+ learningContainers
5392
+ });
5393
+ }
5394
+
4942
5395
  //#endregion
4943
5396
  //#region src/v2/components/chat/CopilotChatToolCallsView.tsx
4944
5397
  function CopilotChatToolCallsView({ message, messages = [] }) {
@@ -5711,33 +6164,170 @@ CopilotChatSuggestionView.displayName = "CopilotChatSuggestionView";
5711
6164
  */
5712
6165
  const ScrollElementContext = React.createContext(null);
5713
6166
 
6167
+ //#endregion
6168
+ //#region src/v2/components/intelligence-indicator/IntelligenceIndicatorView.tsx
6169
+ /**
6170
+ * The presentational "CopilotKit Intelligence" face — the default
6171
+ * rendered by the {@link IntelligenceIndicator} brain and the default
6172
+ * value for the `intelligenceIndicator` slot.
6173
+ *
6174
+ * Layout: a glassmorphism pill (the `__chrome` layer) wrapping an icon
6175
+ * and a label. The icon is two overlaid SVG paths — a spinner arc and a
6176
+ * checkmark — whose geometry lives in each path's `d` ATTRIBUTE so it
6177
+ * renders in every browser (the CSS `d:` property is Chrome-only).
6178
+ *
6179
+ * Two states, driven by the `data-status` attribute (see globals.css
6180
+ * for the exact timing):
6181
+ * 1. **In-progress.** The arc spins (CSS rotation) inside the pill and
6182
+ * the checkmark is hidden. Label + icon are a saturated purple.
6183
+ * 2. **Finished.** The arc fades out mid-spin while the checkmark draws
6184
+ * itself in upright (animated `stroke-dashoffset`); the pill chrome
6185
+ * fades away; and the label + icon settle from purple to a neutral
6186
+ * gray, with the label slanting slightly (a `transform: skewX`
6187
+ * faux-italic, so it interpolates with the color instead of snapping
6188
+ * and never reflows). The result reads as quiet "history metadata"
6189
+ * rather than an active spinner. The label text itself never changes
6190
+ * — the static check plus the color/slant shift carry the "done"
6191
+ * meaning, so no wording change is needed.
6192
+ *
6193
+ * All motion is gated behind `prefers-reduced-motion` (globals.css):
6194
+ * when reduced motion is requested the arc does not spin and the two
6195
+ * states swap instantly, without transitions.
6196
+ *
6197
+ * Customize via the `intelligenceIndicator` slot on `CopilotChat`:
6198
+ * a className string restyles the wrapper, a props object tweaks
6199
+ * the default (`{ label }`), and a component replaces it entirely
6200
+ * with full control over visuals and timing.
6201
+ */
6202
+ function IntelligenceIndicatorView({ message, status, label, className, ...rest }) {
6203
+ return /* @__PURE__ */ jsxs("span", {
6204
+ className: twMerge("cpk-intelligence-indicator", className),
6205
+ role: "status",
6206
+ "aria-live": "polite",
6207
+ "data-testid": `cpk-intelligence-indicator-${message.id}`,
6208
+ "data-status": status,
6209
+ title: label,
6210
+ ...rest,
6211
+ children: [/* @__PURE__ */ jsx("span", {
6212
+ className: "cpk-intelligence-indicator__chrome",
6213
+ "aria-hidden": "true"
6214
+ }), /* @__PURE__ */ jsxs("span", {
6215
+ className: "cpk-intelligence-indicator__content",
6216
+ children: [/* @__PURE__ */ jsxs("svg", {
6217
+ className: "cpk-intelligence-indicator__icon",
6218
+ viewBox: "0 0 24 24",
6219
+ width: "14",
6220
+ height: "14",
6221
+ "aria-hidden": "true",
6222
+ children: [/* @__PURE__ */ jsx("path", {
6223
+ className: "cpk-intelligence-indicator__icon-arc",
6224
+ pathLength: 1,
6225
+ d: "M 12 3 C 17 3 21 7 21 12 C 21 17 17 21 12 21 C 7 21 3 17 3 12 C 3 7 7 3 12 3"
6226
+ }), /* @__PURE__ */ jsx("path", {
6227
+ className: "cpk-intelligence-indicator__icon-check",
6228
+ pathLength: 1,
6229
+ d: "M 5 12.5 L 9 16.5 L 19 6.5"
6230
+ })]
6231
+ }), /* @__PURE__ */ jsx("span", {
6232
+ className: "cpk-intelligence-indicator__label",
6233
+ children: label
6234
+ })]
6235
+ })]
6236
+ });
6237
+ }
6238
+
5714
6239
  //#endregion
5715
6240
  //#region src/v2/components/intelligence-indicator/IntelligenceIndicator.tsx
5716
6241
  /**
5717
6242
  * Grace window before showing the spinner. A matching tool call must
5718
6243
  * remain unresolved (no `tool`-role result message in `agent.messages`)
5719
- * for at least this long before the pill appears. This filters out
5720
- * history-replay flashes — during `connectAgent` replay, tool calls and
5721
- * their results arrive back-to-back in sub-millisecond bursts, so the
5722
- * timer is cancelled before it fires. Live runs cross the threshold
5723
- * easily because the tool actually has to execute.
6244
+ * for at least this long before the indicator transitions out of
6245
+ * `hidden`. This filters out history-replay flashes — during
6246
+ * `connectAgent` replay, tool calls and their results arrive
6247
+ * back-to-back in sub-millisecond bursts, so the timer is cancelled
6248
+ * before it fires. Live runs cross the threshold easily because the
6249
+ * tool actually has to execute.
5724
6250
  */
5725
6251
  const PENDING_THRESHOLD_MS = 100;
5726
- /** Hold the checkmark briefly before fading out. */
5727
- const CHECK_HOLD_MS = 800;
5728
6252
  /**
5729
- * Duration of the fade-out animation. Must match
5730
- * `cpk-intelligence-pill-fade-out` keyframes in `v2/styles/globals.css`.
6253
+ * Tool-name regex patterns that trigger the indicator. Matches any tool
6254
+ * name *containing* the Intelligence MCP server's canonical tool name, so
6255
+ * both the bare `copilotkit_knowledge_base_shell` and the namespaced
6256
+ * `mcp__<server>__copilotkit_knowledge_base_shell` form (emitted by
6257
+ * `@ag-ui/mcp-middleware`) light up the pill. If we add per-instance
6258
+ * customization later (e.g. a `CopilotKitProvider` prop or a runtime-info
6259
+ * field), this constant becomes the fallback.
5731
6260
  */
5732
- const FADE_OUT_ANIMATION_MS = 480;
6261
+ const DEFAULT_TOOL_PATTERNS = [/copilotkit_knowledge_base_shell/];
5733
6262
  /**
5734
- * Tool-name regex patterns that trigger the indicator. Currently
5735
- * hardcoded to the Intelligence MCP server's canonical tool name. If
5736
- * we add per-instance customization later (e.g. a `CopilotKitProvider`
5737
- * prop or a runtime-info field), this constant becomes the fallback.
6263
+ * Phase to start in when an indicator first mounts. A turn that is already
6264
+ * complete at mount jumps straight to `finished` no `hidden` flash, no
6265
+ * spinner blip — which is what makes scrolled-back / replayed history render
6266
+ * its indicators directly in the finished state.
6267
+ *
6268
+ * Pure and timing-free on purpose: the grace window ({@link
6269
+ * PENDING_THRESHOLD_MS}) only controls *when* the live transition is applied;
6270
+ * these functions decide *what* it resolves to, so the decision can be unit
6271
+ * tested deterministically without any timers.
5738
6272
  */
5739
- const DEFAULT_TOOL_PATTERNS = [/^copilotkit_knowledge_base_shell$/];
6273
+ function initialIndicatorPhase(turnComplete) {
6274
+ return turnComplete ? "finished" : "hidden";
6275
+ }
6276
+ /**
6277
+ * Phase the grace window resolves to once it elapses:
6278
+ * - completed turn → `finished` (replay-flash suppression: a tool whose
6279
+ * result lands within the window skips the spinner entirely),
6280
+ * - a still-pending matching tool call → `spinner`,
6281
+ * - otherwise stay `hidden` (the matching tool call hasn't landed yet).
6282
+ */
6283
+ function resolveGracePhase(turnComplete, hasPending) {
6284
+ if (turnComplete) return "finished";
6285
+ if (hasPending) return "spinner";
6286
+ return "hidden";
6287
+ }
5740
6288
  const isMatchingToolCallName = (name) => typeof name === "string" && DEFAULT_TOOL_PATTERNS.some((p) => p.test(name));
6289
+ const messageHasMatchingToolCall = (m) => {
6290
+ if (m.role !== "assistant") return false;
6291
+ return (Array.isArray(m.toolCalls) ? m.toolCalls : []).some((tc) => isMatchingToolCallName(tc?.function?.name));
6292
+ };
6293
+ /**
6294
+ * Stable turn id for the messages that precede the first user message (a turn
6295
+ * with no opening user message of its own). Used as the React key so the
6296
+ * indicator for that turn never collides with a real user-message id.
6297
+ */
6298
+ const INTELLIGENCE_TURN_HEAD = "__cpk_turn_head__";
6299
+ /**
6300
+ * Map each Intelligence-using turn to its anchor message — the FIRST bash-using
6301
+ * assistant message of the turn — and a stable turn id (the id of the user
6302
+ * message that opened the turn, or {@link INTELLIGENCE_TURN_HEAD} for the
6303
+ * pre-first-user turn). Returns `Map<anchorMessageId, turnId>`.
6304
+ *
6305
+ * Anchoring to the FIRST (not last) bash-using message keeps the indicator
6306
+ * fixed in place for the whole turn: later bash steps don't reposition it, so
6307
+ * the spinner never abruptly jumps mid-turn (bug 1). `CopilotChatMessageView`
6308
+ * emits exactly one `IntelligenceIndicator` per entry, keyed by the turn id and
6309
+ * positioned at the anchor; the per-turn key also lets every past turn keep its
6310
+ * own indicator in scroll-back.
6311
+ */
6312
+ function getIntelligenceTurnAnchors(messages) {
6313
+ const anchors = /* @__PURE__ */ new Map();
6314
+ let turnId = INTELLIGENCE_TURN_HEAD;
6315
+ let anchorId = null;
6316
+ const commit = () => {
6317
+ if (anchorId !== null) anchors.set(anchorId, turnId);
6318
+ anchorId = null;
6319
+ };
6320
+ for (const m of messages) {
6321
+ if (m.role === "user") {
6322
+ commit();
6323
+ turnId = m.id;
6324
+ continue;
6325
+ }
6326
+ if (anchorId === null && messageHasMatchingToolCall(m)) anchorId = m.id;
6327
+ }
6328
+ commit();
6329
+ return anchors;
6330
+ }
5741
6331
  /**
5742
6332
  * "Tool-call-like" messages do NOT count as a real follow-up: tool
5743
6333
  * result messages, assistant messages that carry tool calls, and
@@ -5756,45 +6346,51 @@ const isToolCallLikeMessage = (m) => {
5756
6346
  return false;
5757
6347
  };
5758
6348
  /**
5759
- * The "Using CopilotKit Intelligence" pill. Auto-mounted by
5760
- * `CopilotChatMessageView` for every message slot when
5761
- * `copilotkit.intelligence` is configured callers do not register
5762
- * this themselves. Self-gates so only the canonical message renders a
5763
- * pill.
6349
+ * The "Using CopilotKit Intelligence" indicator brain. Auto-mounted by
6350
+ * `CopilotChatMessageView` once per Intelligence-using turn, at that
6351
+ * turn's anchor message and keyed by the turn id (see
6352
+ * {@link getIntelligenceTurnAnchors}). Callers do not register this
6353
+ * themselves. It owns the run subscription and the phase machine and
6354
+ * renders its swappable face via the `intelligenceIndicator` slot.
6355
+ *
6356
+ * Placement (which message anchors the turn) is decided by the view, so
6357
+ * this component does not self-gate its own placement; it only derives
6358
+ * in-progress/finished for the turn it was mounted on.
5764
6359
  *
5765
6360
  * Render gates (all must hold):
5766
6361
  * 1. `copilotkit.intelligence !== undefined`
5767
- * 2. The message is an assistant message with at least one tool call
5768
- * whose name matches {@link DEFAULT_TOOL_PATTERNS}
5769
- * 3. The message is the *latest* such matching-assistant message in
5770
- * `agent.messages` — tool-result messages and prose-only assistant
5771
- * messages don't invalidate the slot, so the pill stays
5772
- * continuously through a multi-step tool chain.
5773
- * 4. The phase machine is past `idle` (the pending-grace timer fired)
5774
- * and not yet `hidden`.
6362
+ * 2. The (anchor) message is an assistant message with at least one
6363
+ * tool call whose name matches {@link DEFAULT_TOOL_PATTERNS}.
6364
+ * 3. The phase machine is past `hidden`.
6365
+ *
6366
+ * Because the view keys each indicator by its turn id, the instance moves
6367
+ * with the anchor across a hand-off (no remount, no spinner restart), and
6368
+ * every prior Intelligence-using turn keeps its own persistent indicator
6369
+ * in chat history.
5775
6370
  *
5776
6371
  * Phase machine (per-instance, all timers local):
5777
- * - Starts in `idle` nothing rendered.
5778
- * - `idle spinner` once a matching tool call has been pending
6372
+ * - Starts in `hidden`, unless the message mounts onto an
6373
+ * already-completed turn (no pending work, agent stopped or a
6374
+ * real follow-up already present), in which case the lazy
6375
+ * `useState` initializer starts directly in `finished`. This is
6376
+ * what avoids a "hidden flash" on history replay.
6377
+ * - `hidden → spinner` once a matching tool call has been pending
5779
6378
  * (no `tool`-role result with a matching `toolCallId`) for
5780
6379
  * {@link PENDING_THRESHOLD_MS}. Replay flashes (tool call + result
5781
6380
  * in the same tick) never cross this threshold.
5782
- * - `spinnercheck` as soon as EITHER `agent.isRunning` flips
6381
+ * - `hiddenfinished` if after the grace window the turn is
6382
+ * already complete (no pending work AND
6383
+ * `sawRealFollowup || !agent.isRunning`). Handles very fast tools
6384
+ * whose result lands within the grace window.
6385
+ * - `spinner → finished` as soon as EITHER `agent.isRunning` flips
5783
6386
  * false OR a non-tool-call-like message appears later in
5784
- * `agent.messages` (i.e. the agent has produced a "real"
5785
- * follow-up — prose answer or a new user turn).
5786
- * - `check fading` after {@link CHECK_HOLD_MS}.
5787
- * - `fading hidden` after {@link FADE_OUT_ANIMATION_MS}.
5788
- *
5789
- * Once `hidden`, the phase is sticky — a finished pill never re-spawns
5790
- * on the same message. New runs mount fresh indicator instances on
5791
- * their own assistant messages.
5792
- *
5793
- * The "exactly one pill at a time" guarantee is structural: only one
5794
- * message satisfies the latest-matching-assistant gate at any moment.
6387
+ * `agent.messages` (i.e. the agent produced a "real" follow-up —
6388
+ * prose answer or a new user turn).
6389
+ * - `finished` is terminal: the indicator settles into its
6390
+ * persistent tag form and stays mounted.
5795
6391
  */
5796
6392
  function IntelligenceIndicator(props) {
5797
- const { message, agentId, label = "Using CopilotKit Intelligence" } = props;
6393
+ const { message, agentId, label = "CopilotKit Intelligence", intelligenceIndicator } = props;
5798
6394
  const { copilotkit } = useCopilotKit();
5799
6395
  const config = useCopilotChatConfiguration();
5800
6396
  const { agent } = useAgent({
@@ -5814,90 +6410,95 @@ function IntelligenceIndicator(props) {
5814
6410
  for (const m of agent.messages) if (m.role === "tool" && m.toolCallId) resolved.add(m.toolCallId);
5815
6411
  return matchingToolCallIds.some((id) => !resolved.has(id));
5816
6412
  }, [matchingToolCallIds, agent.messages]);
5817
- const sawRealFollowup = useMemo(() => {
6413
+ const turnComplete = useMemo(() => {
5818
6414
  const idx = agent.messages.findIndex((m) => m.id === message.id);
5819
6415
  if (idx < 0) return false;
5820
6416
  for (let i = idx + 1; i < agent.messages.length; i += 1) if (!isToolCallLikeMessage(agent.messages[i])) return true;
5821
6417
  return false;
5822
- }, [agent.messages, message.id]);
5823
- const [phase, setPhase] = useState("idle");
6418
+ }, [agent.messages, message.id]) || !agent.isRunning;
6419
+ const [phase, setPhase] = useState(() => initialIndicatorPhase(turnComplete));
5824
6420
  useEffect(() => {
5825
- if (phase !== "idle") return void 0;
5826
- if (!hasPending) return void 0;
5827
- const t = setTimeout(() => setPhase("spinner"), PENDING_THRESHOLD_MS);
6421
+ if (phase !== "hidden") return void 0;
6422
+ const t = setTimeout(() => {
6423
+ setPhase(resolveGracePhase(turnComplete, hasPending));
6424
+ }, PENDING_THRESHOLD_MS);
5828
6425
  return () => clearTimeout(t);
5829
- }, [phase, hasPending]);
5830
- useEffect(() => {
5831
- if (phase !== "spinner") return void 0;
5832
- if (!agent.isRunning || sawRealFollowup) setPhase("check");
5833
6426
  }, [
5834
6427
  phase,
5835
- agent.isRunning,
5836
- sawRealFollowup
6428
+ hasPending,
6429
+ turnComplete
5837
6430
  ]);
5838
6431
  useEffect(() => {
5839
- if (phase !== "check") return void 0;
5840
- const t = setTimeout(() => setPhase("fading"), CHECK_HOLD_MS);
5841
- return () => clearTimeout(t);
5842
- }, [phase]);
5843
- useEffect(() => {
5844
- if (phase !== "fading") return void 0;
5845
- const t = setTimeout(() => setPhase("hidden"), FADE_OUT_ANIMATION_MS);
5846
- return () => clearTimeout(t);
5847
- }, [phase]);
6432
+ if (phase !== "spinner") return void 0;
6433
+ if (turnComplete) setPhase("finished");
6434
+ }, [phase, turnComplete]);
5848
6435
  if (copilotkit.intelligence === void 0) return null;
5849
6436
  if (!config) return null;
5850
- if (phase === "idle" || phase === "hidden") return null;
6437
+ if (phase === "hidden") return null;
5851
6438
  if (message.role !== "assistant") return null;
5852
- if (!(Array.isArray(message.toolCalls) ? message.toolCalls : []).some((tc) => isMatchingToolCallName(tc?.function?.name))) return null;
5853
- let latestMatchingAssistantId;
5854
- for (let i = agent.messages.length - 1; i >= 0; i -= 1) {
5855
- const m = agent.messages[i];
5856
- if (m.role !== "assistant") continue;
5857
- if ((Array.isArray(m.toolCalls) ? m.toolCalls : []).some((tc) => isMatchingToolCallName(tc?.function?.name))) {
5858
- latestMatchingAssistantId = m.id;
5859
- break;
5860
- }
5861
- }
5862
- if (latestMatchingAssistantId !== message.id) return null;
5863
- const showSpinner = phase === "spinner";
5864
- const isFading = phase === "fading";
5865
- return /* @__PURE__ */ jsxs("span", {
5866
- className: "cpk-intelligence-pill" + (isFading ? " cpk-intelligence-pill--fading" : ""),
5867
- role: "status",
5868
- "aria-live": "polite",
5869
- "aria-hidden": isFading || void 0,
5870
- "data-testid": `cpk-intelligence-pill-${message.id}`,
5871
- title: label,
5872
- children: [/* @__PURE__ */ jsxs("svg", {
5873
- className: "cpk-intelligence-pill__icon",
5874
- viewBox: "0 0 24 24",
5875
- width: "14",
5876
- height: "14",
5877
- "aria-hidden": "true",
5878
- children: [/* @__PURE__ */ jsx("circle", {
5879
- cx: "12",
5880
- cy: "12",
5881
- r: "9",
5882
- fill: "none",
5883
- strokeWidth: "2.5",
5884
- strokeLinecap: "round",
5885
- className: "cpk-intelligence-pill__ring" + (showSpinner ? "" : " cpk-intelligence-pill__ring--done")
5886
- }), /* @__PURE__ */ jsx("path", {
5887
- d: "M8 12.5l3 3 5-6",
5888
- fill: "none",
5889
- strokeWidth: "2.5",
5890
- strokeLinecap: "round",
5891
- strokeLinejoin: "round",
5892
- className: "cpk-intelligence-pill__check" + (showSpinner ? "" : " cpk-intelligence-pill__check--shown")
5893
- })]
5894
- }), /* @__PURE__ */ jsx("span", { children: label })]
6439
+ if (!messageHasMatchingToolCall(message)) return null;
6440
+ return renderSlot(intelligenceIndicator, IntelligenceIndicatorView, {
6441
+ message,
6442
+ status: phase === "finished" ? "finished" : "in-progress",
6443
+ label
5895
6444
  });
5896
6445
  }
5897
6446
 
5898
6447
  //#endregion
5899
6448
  //#region src/v2/components/chat/CopilotChatMessageView.tsx
5900
6449
  /**
6450
+ * Builds a map of message.id → stable per-row React key for an entire message
6451
+ * list. A message's canonical `id` is not stable within a turn: some backends
6452
+ * re-key a message mid-stream (e.g. LangChain replaces its transient
6453
+ * `lc_run--…` streaming id with the provider's final `resp_…` id in the
6454
+ * MESSAGES_SNAPSHOT). Keying rows by `id` made React unmount/remount the row
6455
+ * on that swap — the visible HITL chat flash. Tool-call ids survive the
6456
+ * rename, so an assistant message anchored by a tool call is keyed by its
6457
+ * first tool-call id (`tc:<anchorToolCallId>`); everything else falls back to
6458
+ * `id`.
6459
+ *
6460
+ * Load-bearing assumption: this only helps when the first tool-call id itself
6461
+ * is stable across the rename; backends that re-key tool-call ids mid-stream
6462
+ * still remount.
6463
+ *
6464
+ * Collision rule: two distinct assistant messages can occasionally share a
6465
+ * first-tool-call id (e.g. due to upstream bugs or replayed state). First
6466
+ * occurrence (in list order) claims `tc:<id>`; later collisions fall back to
6467
+ * `message.id`. Every assigned key is checked against the claimed set, so
6468
+ * keys are unique even for pathological ids (e.g. a raw message id beginning
6469
+ * with "tc:"); collisions disambiguate with a deterministic numeric suffix.
6470
+ *
6471
+ * Precondition: callers must pass a deduplicated list (see
6472
+ * `deduplicateMessages`); duplicate message ids would silently overwrite map
6473
+ * entries.
6474
+ *
6475
+ * Order caveat: the collision rule is order-sensitive — if two messages
6476
+ * sharing a first tool-call id swap list positions across renders, the
6477
+ * claimant changes and both rows remount. Acceptable: that situation already
6478
+ * indicates an upstream id bug, and keys remain unique.
6479
+ */
6480
+ function buildRowRenderKeys(messages) {
6481
+ const keys = /* @__PURE__ */ new Map();
6482
+ const claimed = /* @__PURE__ */ new Set();
6483
+ for (const message of messages) {
6484
+ let candidate;
6485
+ if (message.role === "assistant") {
6486
+ const anchorToolCallId = message.toolCalls?.[0]?.id;
6487
+ if (anchorToolCallId) candidate = `tc:${anchorToolCallId}`;
6488
+ }
6489
+ let assigned = message.id;
6490
+ if (candidate && !claimed.has(candidate)) assigned = candidate;
6491
+ else if (claimed.has(assigned)) {
6492
+ let n = 2;
6493
+ while (claimed.has(`${assigned}:${n}`)) n += 1;
6494
+ assigned = `${assigned}:${n}`;
6495
+ }
6496
+ keys.set(message.id, assigned);
6497
+ claimed.add(assigned);
6498
+ }
6499
+ return keys;
6500
+ }
6501
+ /**
5901
6502
  * Resolves a slot value into a { Component, slotProps } pair, handling the three
5902
6503
  * slot forms: a component type, a className string, or a partial-props object.
5903
6504
  */
@@ -6044,7 +6645,7 @@ function deduplicateMessages(messages) {
6044
6645
  return [...acc.values()];
6045
6646
  }
6046
6647
  const VIRTUALIZE_THRESHOLD = 50;
6047
- function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, isRunning = false, children, className, ...props }) {
6648
+ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, intelligenceIndicator, isRunning = false, children, className, ...props }) {
6048
6649
  const renderCustomMessage = useRenderCustomMessages();
6049
6650
  const { renderActivityMessage } = useRenderActivityMessage();
6050
6651
  const { copilotkit } = useCopilotKit();
@@ -6076,6 +6677,7 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
6076
6677
  return copilotkit.getStateByRun(config.agentId, config.threadId, resolvedRunId);
6077
6678
  };
6078
6679
  const deduplicatedMessages = useMemo(() => deduplicateMessages(messages), [messages]);
6680
+ const rowRenderKeys = useMemo(() => buildRowRenderKeys(deduplicatedMessages), [deduplicatedMessages]);
6079
6681
  if (process.env.NODE_ENV === "development" && deduplicatedMessages.length < messages.length) console.warn(`CopilotChatMessageView: Merged ${messages.length - deduplicatedMessages.length} message(s) with duplicate IDs.`);
6080
6682
  const { Component: AssistantComponent, slotProps: assistantSlotProps } = useMemo(() => resolveSlotComponent(assistantMessage, CopilotChatAssistantMessage_default), [assistantMessage]);
6081
6683
  const { Component: UserComponent, slotProps: userSlotProps } = useMemo(() => resolveSlotComponent(userMessage, CopilotChatUserMessage_default), [userMessage]);
@@ -6102,48 +6704,52 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
6102
6704
  if (!shouldVirtualize || !deduplicatedMessages.length) return;
6103
6705
  virtualizer.scrollToIndex(deduplicatedMessages.length - 1, { align: "end" });
6104
6706
  }, [shouldVirtualize, firstMessageId]);
6707
+ const intelligenceTurnAnchors = useMemo(() => getIntelligenceTurnAnchors(deduplicatedMessages), [deduplicatedMessages]);
6105
6708
  const renderMessageBlock = (message) => {
6106
6709
  const elements = [];
6107
6710
  const stateSnapshot = getStateSnapshotForMessage(message.id);
6711
+ const rowKey = rowRenderKeys.get(message.id) ?? message.id;
6108
6712
  if (renderCustomMessage) elements.push(/* @__PURE__ */ jsx(MemoizedCustomMessage, {
6109
6713
  message,
6110
6714
  position: "before",
6111
6715
  renderCustomMessage,
6112
6716
  stateSnapshot
6113
- }, `${message.id}-custom-before`));
6717
+ }, `${rowKey}-custom-before`));
6114
6718
  if (message.role === "assistant") elements.push(/* @__PURE__ */ jsx(MemoizedAssistantMessage, {
6115
6719
  message,
6116
6720
  messages,
6117
6721
  isRunning,
6118
6722
  AssistantMessageComponent: AssistantComponent,
6119
6723
  slotProps: assistantSlotProps
6120
- }, message.id));
6724
+ }, rowKey));
6121
6725
  else if (message.role === "user") elements.push(/* @__PURE__ */ jsx(MemoizedUserMessage, {
6122
6726
  message,
6123
6727
  UserMessageComponent: UserComponent,
6124
6728
  slotProps: userSlotProps
6125
- }, message.id));
6729
+ }, rowKey));
6126
6730
  else if (message.role === "activity") elements.push(/* @__PURE__ */ jsx(MemoizedActivityMessage, {
6127
6731
  message,
6128
6732
  renderActivityMessage
6129
- }, message.id));
6733
+ }, rowKey));
6130
6734
  else if (message.role === "reasoning") elements.push(/* @__PURE__ */ jsx(MemoizedReasoningMessage, {
6131
6735
  message,
6132
6736
  messages,
6133
6737
  isRunning,
6134
6738
  ReasoningMessageComponent: ReasoningComponent,
6135
6739
  slotProps: reasoningSlotProps
6136
- }, message.id));
6740
+ }, rowKey));
6137
6741
  if (renderCustomMessage) elements.push(/* @__PURE__ */ jsx(MemoizedCustomMessage, {
6138
6742
  message,
6139
6743
  position: "after",
6140
6744
  renderCustomMessage,
6141
6745
  stateSnapshot
6142
- }, `${message.id}-custom-after`));
6143
- if (copilotkit.intelligence !== void 0 && message.role === "assistant") elements.push(/* @__PURE__ */ jsx(IntelligenceIndicator, {
6746
+ }, `${rowKey}-custom-after`));
6747
+ const intelligenceTurnId = intelligenceTurnAnchors.get(message.id);
6748
+ if (intelligenceTurnId !== void 0) elements.push(/* @__PURE__ */ jsx(IntelligenceIndicator, {
6144
6749
  message,
6145
- agentId: config?.agentId ?? DEFAULT_AGENT_ID
6146
- }, `${message.id}-intelligence`));
6750
+ agentId: config?.agentId ?? DEFAULT_AGENT_ID,
6751
+ intelligenceIndicator
6752
+ }, `intelligence-${intelligenceTurnId}`));
6147
6753
  return elements.filter(Boolean);
6148
6754
  };
6149
6755
  const messageElements = shouldVirtualize ? [] : deduplicatedMessages.flatMap(renderMessageBlock);
@@ -6183,7 +6789,7 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
6183
6789
  transform: `translateY(${virtualItem.start}px)`
6184
6790
  },
6185
6791
  children: renderMessageBlock(message)
6186
- }, message.id);
6792
+ }, rowRenderKeys.get(message.id) ?? message.id);
6187
6793
  })
6188
6794
  }) : messageElements,
6189
6795
  interruptElement,
@@ -6580,7 +7186,7 @@ function DropOverlay() {
6580
7186
  })
6581
7187
  });
6582
7188
  }
6583
- function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, attachments, onRemoveAttachment, onAddFile, dragOver, onDragOver, onDragLeave, onDrop, isConnecting = false, hasExplicitThreadId = false, disclaimer, children, className, ...props }) {
7189
+ function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, attachments, onRemoveAttachment, onAddFile, dragOver, onDragOver, onDragLeave, onDrop, isConnecting = false, hasExplicitThreadId = false, disclaimer, intelligenceIndicator, children, className, ...props }) {
6584
7190
  const [inputContainerEl, setInputContainerEl] = useState(null);
6585
7191
  const [inputContainerHeight, setInputContainerHeight] = useState(0);
6586
7192
  const [isResizing, setIsResizing] = useState(false);
@@ -6617,7 +7223,8 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6617
7223
  }, [inputContainerEl]);
6618
7224
  const BoundMessageView = renderSlot(messageView, CopilotChatMessageView, {
6619
7225
  messages,
6620
- isRunning
7226
+ isRunning,
7227
+ intelligenceIndicator
6621
7228
  });
6622
7229
  const BoundInput = renderSlot(input, CopilotChatInput_default, {
6623
7230
  onSubmitMessage,
@@ -7162,6 +7769,13 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
7162
7769
  resolvedAgentId,
7163
7770
  hasExplicitThreadId
7164
7771
  ]);
7772
+ const waitForActiveRunToSettle = useCallback(async () => {
7773
+ if (agent.isRunning && isRunCompletionAware(agent) && agent.activeRunCompletionPromise) try {
7774
+ await agent.activeRunCompletionPromise;
7775
+ } catch (error) {
7776
+ console.error("CopilotChat: in-flight run rejected while queuing send", error);
7777
+ }
7778
+ }, [agent]);
7165
7779
  const onSubmitInput = useCallback(async (value) => {
7166
7780
  if (selectedAttachmentsRef.current.some((a) => a.status === "uploading")) {
7167
7781
  console.error("[CopilotKit] Cannot send while attachments are uploading (pre-await guard)");
@@ -7169,11 +7783,7 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
7169
7783
  return;
7170
7784
  }
7171
7785
  setInputValue("");
7172
- if (agent.isRunning && "activeRunCompletionPromise" in agent && agent.activeRunCompletionPromise) try {
7173
- await agent.activeRunCompletionPromise;
7174
- } catch (error) {
7175
- console.error("CopilotChat: in-flight run rejected while queuing send", error);
7176
- }
7786
+ await waitForActiveRunToSettle();
7177
7787
  if (selectedAttachmentsRef.current.some((a) => a.status === "uploading")) {
7178
7788
  console.error("[CopilotKit] Cannot send while attachments are uploading (post-await re-check)");
7179
7789
  setTranscriptionError("Cannot send while attachments are uploading.");
@@ -7210,8 +7820,13 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
7210
7820
  } catch (error) {
7211
7821
  console.error("CopilotChat: runAgent failed", error);
7212
7822
  }
7213
- }, [agent, consumeAttachments]);
7823
+ }, [
7824
+ agent,
7825
+ consumeAttachments,
7826
+ waitForActiveRunToSettle
7827
+ ]);
7214
7828
  const handleSelectSuggestion = useCallback(async (suggestion) => {
7829
+ await waitForActiveRunToSettle();
7215
7830
  agent.addMessage({
7216
7831
  id: randomUUID(),
7217
7832
  role: "user",
@@ -7222,7 +7837,7 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
7222
7837
  } catch (error) {
7223
7838
  console.error("CopilotChat: runAgent failed after selecting suggestion", error);
7224
7839
  }
7225
- }, [agent]);
7840
+ }, [agent, waitForActiveRunToSettle]);
7226
7841
  const stopCurrentRun = useCallback(() => {
7227
7842
  try {
7228
7843
  copilotkit.stopAgent({ agent });
@@ -8891,7 +9506,7 @@ const getErrorActions = (error) => {
8891
9506
  } };
8892
9507
  case CopilotKitErrorCode.UPGRADE_REQUIRED_ERROR: return { primary: {
8893
9508
  label: "Upgrade",
8894
- onClick: () => window.open("https://cloud.copilotkit.ai", "_blank", "noopener,noreferrer")
9509
+ onClick: () => window.open("https://dashboard.operations.copilotkit.ai", "_blank", "noopener,noreferrer")
8895
9510
  } };
8896
9511
  default: return;
8897
9512
  }
@@ -10098,5 +10713,5 @@ function validateProps(props) {
10098
10713
  }
10099
10714
 
10100
10715
  //#endregion
10101
- export { createA2UIMessageRenderer as $, IntelligenceIndicator as A, useInterrupt as B, CopilotChatToggleButton as C, CopilotChatView_default as D, CopilotChat as E, CopilotChatAttachmentRenderer as F, useAgent as G, useSuggestions as H, CopilotChatAssistantMessage_default as I, useFrontendTool as J, useHumanInTheLoop as K, CopilotChatToolCallsView as L, CopilotChatSuggestionPill as M, CopilotChatReasoningMessage_default as N, CopilotChatAttachmentQueue as O, CopilotChatUserMessage_default as P, useAgentContext as Q, useAttachments as R, CopilotModalHeader as S, DefaultOpenIcon as T, useCapabilities as U, useConfigureSuggestions as V, UseAgentUpdate as W, useRenderCustomMessages as X, useRenderActivityMessage as Y, CopilotKitProvider as Z, WildcardToolCallRender as _, ThreadsProvider as a, CopilotKitInspector as at, CopilotPopupView as b, CoAgentStateRendersProvider as c, useRenderTool as ct, shouldShowDevConsole as d, CopilotKitCoreReact as dt, SandboxFunctionsContext as et, useToast as f, CopilotChatInput_default as ft, useCopilotContext as g, useCopilotChatConfiguration as gt, CopilotContext as h, CopilotChatConfigurationProvider as ht, ThreadsContext as i, MCPAppsActivityType as it, CopilotChatSuggestionView as j, CopilotChatMessageView as k, useCoAgentStateRenders as l, defineToolCallRenderer as lt, useCopilotMessagesContext as m, CopilotChatAudioRecorder as mt, defaultCopilotContextCategories as n, MCPAppsActivityContentSchema as nt, useThreads as o, useRenderToolCall as ot, CopilotMessagesContext as p, AudioRecorderError as pt, useComponent as q, CoAgentStateRenderBridge as r, MCPAppsActivityRenderer as rt, CoAgentStateRendersContext as s, useDefaultRenderTool as st, CopilotKit as t, useSandboxFunctions as tt, useAsyncCallback as u, useCopilotKit as ut, CopilotPopup as v, DefaultCloseIcon as w, CopilotSidebarView as x, CopilotSidebar as y, useThreads$1 as z };
10102
- //# sourceMappingURL=copilotkit-BRNy5UvX.mjs.map
10716
+ export { useHumanInTheLoop as $, INTELLIGENCE_TURN_HEAD as A, CopilotChatToolCallsView as B, CopilotChatToggleButton as C, useCopilotChatConfiguration as Ct, CopilotChatView_default as D, CopilotChat as E, CopilotChatSuggestionPill as F, useLearnFromUserAction as G, useLearningContainers as H, CopilotChatReasoningMessage_default as I, useConfigureSuggestions as J, useThreads$1 as K, CopilotChatUserMessage_default as L, getIntelligenceTurnAnchors as M, IntelligenceIndicatorView as N, CopilotChatAttachmentQueue as O, CopilotChatSuggestionView as P, useAgent as Q, CopilotChatAttachmentRenderer as R, CopilotModalHeader as S, CopilotChatConfigurationProvider as St, DefaultOpenIcon as T, useAttachments as U, useLearningContainersInCurrentThread as V, useLearnFromUserActionInCurrentThread as W, useCapabilities as X, useSuggestions as Y, UseAgentUpdate as Z, WildcardToolCallRender as _, useCopilotKit as _t, ThreadsProvider as a, useAgentContext as at, CopilotPopupView as b, AudioRecorderError as bt, CoAgentStateRendersProvider as c, useSandboxFunctions as ct, shouldShowDevConsole as d, MCPAppsActivityType as dt, useComponent as et, useToast as f, CopilotKitInspector as ft, useCopilotContext as g, defineToolCallRenderer as gt, CopilotContext as h, useRenderTool as ht, ThreadsContext as i, CopilotKitProvider as it, IntelligenceIndicator as j, CopilotChatMessageView as k, useCoAgentStateRenders as l, MCPAppsActivityContentSchema as lt, useCopilotMessagesContext as m, useDefaultRenderTool as mt, defaultCopilotContextCategories as n, useRenderActivityMessage as nt, useThreads as o, createA2UIMessageRenderer as ot, CopilotMessagesContext as p, useRenderToolCall as pt, useInterrupt as q, CoAgentStateRenderBridge as r, useRenderCustomMessages as rt, CoAgentStateRendersContext as s, SandboxFunctionsContext as st, CopilotKit as t, useFrontendTool as tt, useAsyncCallback as u, MCPAppsActivityRenderer as ut, CopilotPopup as v, CopilotKitCoreReact as vt, DefaultCloseIcon as w, CopilotSidebarView as x, CopilotChatAudioRecorder as xt, CopilotSidebar as y, CopilotChatInput_default as yt, CopilotChatAssistantMessage_default as z };
10717
+ //# sourceMappingURL=copilotkit-DBOofUYQ.mjs.map