@copilotkit/react-core 1.59.4 → 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.
@@ -2011,7 +2011,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
2011
2011
  severity: "warning",
2012
2012
  message: `Your CopilotKit license expires in ${graceRemaining} day${graceRemaining !== 1 ? "s" : ""}. Please renew.`,
2013
2013
  actionLabel: "Renew",
2014
- actionUrl: "https://cloud.copilotkit.ai",
2014
+ actionUrl: "https://dashboard.operations.copilotkit.ai",
2015
2015
  onDismiss
2016
2016
  });
2017
2017
  case "expired": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BannerShell, {
@@ -2940,210 +2940,110 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2940
2940
  };
2941
2941
 
2942
2942
  //#endregion
2943
- //#region src/v2/a2ui/A2UIMessageRenderer.tsx
2943
+ //#region src/v2/a2ui/A2UIRecoveryStates.tsx
2944
2944
  /**
2945
- * The container key used to wrap A2UI operations for explicit detection.
2946
- * Must match A2UI_OPERATIONS_KEY in @ag-ui/a2ui-middleware and copilotkit.a2ui (Python).
2945
+ * The pre-paint lifecycle fields the middleware stamps onto the `a2ui-surface`
2946
+ * activity content (alongside `a2ui_operations` on paint). `.passthrough()` keeps
2947
+ * `a2ui_operations` and any future fields intact.
2947
2948
  */
2948
- const A2UI_OPERATIONS_KEY = "a2ui_operations";
2949
- let initialized = false;
2950
- function ensureInitialized() {
2951
- if (!initialized) {
2952
- (0, _copilotkit_a2ui_renderer.initializeDefaultCatalog)();
2953
- (0, _copilotkit_a2ui_renderer.injectStyles)();
2954
- initialized = true;
2955
- }
2956
- }
2957
- function createA2UIMessageRenderer(options) {
2958
- const { theme, catalog, loadingComponent } = options;
2959
- return {
2960
- activityType: "a2ui-surface",
2961
- content: zod.z.any(),
2962
- render: ({ content, agent }) => {
2963
- ensureInitialized();
2964
- const [operations, setOperations] = (0, react.useState)([]);
2965
- const { copilotkit } = useCopilotKit();
2966
- const lastContentRef = (0, react.useRef)(null);
2967
- (0, react.useEffect)(() => {
2968
- if (content === lastContentRef.current) return;
2969
- lastContentRef.current = content;
2970
- const incoming = content?.[A2UI_OPERATIONS_KEY];
2971
- if (!content || !Array.isArray(incoming)) {
2972
- setOperations([]);
2973
- return;
2974
- }
2975
- setOperations(incoming);
2976
- }, [content]);
2977
- const groupedOperations = (0, react.useMemo)(() => {
2978
- const groups = /* @__PURE__ */ new Map();
2979
- for (const operation of operations) {
2980
- const surfaceId = getOperationSurfaceId(operation) ?? _copilotkit_a2ui_renderer.DEFAULT_SURFACE_ID;
2981
- if (!groups.has(surfaceId)) groups.set(surfaceId, []);
2982
- groups.get(surfaceId).push(operation);
2983
- }
2984
- return groups;
2985
- }, [operations]);
2986
- if (!groupedOperations.size) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(loadingComponent ?? DefaultA2UILoading, {});
2987
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2988
- className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
2989
- children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReactSurfaceHost, {
2990
- surfaceId,
2991
- operations: ops,
2992
- theme,
2993
- agent,
2994
- copilotkit,
2995
- catalog
2996
- }, surfaceId))
2997
- });
2998
- }
2999
- };
3000
- }
3001
- /**
3002
- * Renders a single A2UI surface using the React renderer.
3003
- * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
3004
- */
3005
- function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog }) {
3006
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3007
- className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
3008
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_copilotkit_a2ui_renderer.A2UIProvider, {
3009
- onAction: (0, react.useCallback)(async (message) => {
3010
- if (!agent) return;
3011
- message.userAction;
3012
- try {
3013
- copilotkit.setProperties({
3014
- ...copilotkit.properties,
3015
- a2uiAction: message
3016
- });
3017
- await copilotkit.runAgent({ agent });
3018
- } finally {
3019
- if (copilotkit.properties) {
3020
- const { a2uiAction, ...rest } = copilotkit.properties;
3021
- copilotkit.setProperties(rest);
3022
- }
3023
- }
3024
- }, [agent, copilotkit]),
3025
- theme,
3026
- catalog,
3027
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SurfaceMessageProcessor, {
3028
- surfaceId,
3029
- operations
3030
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UISurfaceOrError, { surfaceId })]
3031
- })
3032
- });
2949
+ const A2UILifecycleFields = {
2950
+ status: zod.z.enum([
2951
+ "building",
2952
+ "retrying",
2953
+ "failed"
2954
+ ]).optional(),
2955
+ attempt: zod.z.number().optional(),
2956
+ maxAttempts: zod.z.number().optional(),
2957
+ progressTokens: zod.z.number().optional(),
2958
+ error: zod.z.string().optional(),
2959
+ errors: zod.z.array(zod.z.any()).optional(),
2960
+ attempts: zod.z.array(zod.z.any()).optional(),
2961
+ debugExposure: zod.z.enum([
2962
+ "hidden",
2963
+ "collapsed",
2964
+ "verbose"
2965
+ ]).optional()
2966
+ };
2967
+ /** Server-stamped debugExposure wins; else the client option; else "collapsed". */
2968
+ function resolveDebugExposure(content, optionDebugExposure) {
2969
+ return content?.debugExposure ?? optionDebugExposure;
3033
2970
  }
3034
- /**
3035
- * Renders the A2UI surface, or an error message if processing failed.
3036
- * Must be a child of A2UIProvider to access the error state.
3037
- */
3038
- function A2UISurfaceOrError({ surfaceId }) {
3039
- const error = (0, _copilotkit_a2ui_renderer.useA2UIError)();
3040
- if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3041
- className: "cpk:rounded-lg cpk:border cpk:border-red-200 cpk:bg-red-50 cpk:p-3 cpk:text-sm cpk:text-red-700",
3042
- children: ["A2UI render error: ", error]
3043
- });
3044
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_copilotkit_a2ui_renderer.A2UIRenderer, {
3045
- surfaceId,
3046
- className: "cpk:flex cpk:flex-1"
2971
+ /** building: the generic skeleton + optional live token count. */
2972
+ function A2UIBuildingState({ content }) {
2973
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIGeneratingSkeleton, {
2974
+ label: "Building interface",
2975
+ tokens: typeof content?.progressTokens === "number" ? content.progressTokens : void 0
3047
2976
  });
3048
2977
  }
3049
2978
  /**
3050
- * Processes A2UI operations into the provider's message processor.
3051
- * Must be a child of A2UIProvider to access the actions context.
2979
+ * retrying: stays the generic skeleton through fast/transient retries; only once
2980
+ * the retry is perceptible (after `showAfterMs`, or once `attempt` crosses
2981
+ * `showAfterAttempts`) does the sub-label reveal "Retrying generation… (N/M)".
3052
2982
  */
3053
- function SurfaceMessageProcessor({ surfaceId, operations }) {
3054
- const { processMessages, getSurface } = (0, _copilotkit_a2ui_renderer.useA2UIActions)();
3055
- const lastHashRef = (0, react.useRef)("");
2983
+ function A2UIRetryingState({ content, showAfterMs, showAfterAttempts, debugExposure }) {
2984
+ const attempt = typeof content?.attempt === "number" ? content.attempt : void 0;
2985
+ const maxAttempts = typeof content?.maxAttempts === "number" ? content.maxAttempts : void 0;
2986
+ const immediate = attempt !== void 0 && attempt >= showAfterAttempts;
2987
+ const [revealed, setRevealed] = (0, react.useState)(immediate);
3056
2988
  (0, react.useEffect)(() => {
3057
- const hash = JSON.stringify(operations);
3058
- if (hash === lastHashRef.current) return;
3059
- lastHashRef.current = hash;
3060
- processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
3061
- }, [
3062
- processMessages,
3063
- getSurface,
3064
- surfaceId,
3065
- operations
3066
- ]);
3067
- return null;
2989
+ if (immediate) {
2990
+ setRevealed(true);
2991
+ return;
2992
+ }
2993
+ const timer = setTimeout(() => setRevealed(true), showAfterMs);
2994
+ return () => clearTimeout(timer);
2995
+ }, [immediate, showAfterMs]);
2996
+ const tokens = typeof content?.progressTokens === "number" ? content.progressTokens : void 0;
2997
+ if (!revealed) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIGeneratingSkeleton, {
2998
+ label: "Building interface",
2999
+ tokens
3000
+ });
3001
+ const label = attempt !== void 0 && maxAttempts !== void 0 ? `Retrying generation… (${attempt}/${maxAttempts} attempts)` : "Retrying generation…";
3002
+ const errors = Array.isArray(content?.errors) ? content.errors : [];
3003
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIGeneratingSkeleton, {
3004
+ label,
3005
+ tokens,
3006
+ children: debugExposure !== "hidden" && errors.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIDebugDetails, {
3007
+ label: "validation issues",
3008
+ open: debugExposure === "verbose",
3009
+ payload: {
3010
+ attempt: content?.attempt,
3011
+ errors
3012
+ }
3013
+ })
3014
+ });
3068
3015
  }
3069
- /**
3070
- * Default loading component shown while an A2UI surface is generating.
3071
- * Displays an animated shimmer skeleton.
3072
- */
3073
- function DefaultA2UILoading() {
3016
+ /** failed: a clean hard-failure card that replaces the skeleton in place. */
3017
+ function A2UIRecoveryFailure({ content, debugExposure }) {
3074
3018
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3075
- 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",
3076
- style: { minHeight: 120 },
3019
+ className: "cpk:rounded-lg cpk:border cpk:border-amber-200 cpk:bg-amber-50 cpk:p-3 cpk:text-sm cpk:text-amber-800",
3077
3020
  children: [
3078
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3079
- className: "cpk:flex cpk:items-center cpk:gap-2",
3080
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3081
- className: "cpk:h-3 cpk:w-3 cpk:rounded-full cpk:bg-gray-200",
3082
- style: { animation: "cpk-a2ui-pulse 1.5s ease-in-out infinite" }
3083
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
3084
- className: "cpk:text-xs cpk:font-medium cpk:text-gray-400",
3085
- children: "Generating UI..."
3086
- })]
3021
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3022
+ className: "cpk:font-medium",
3023
+ children: "Couldn't generate the UI"
3087
3024
  }),
3088
3025
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3089
- className: "cpk:flex cpk:flex-col cpk:gap-2",
3090
- children: [
3091
- .8,
3092
- .6,
3093
- .4
3094
- ].map((width, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3095
- className: "cpk:h-3 cpk:rounded cpk:bg-gray-200/70",
3096
- style: {
3097
- width: `${width * 100}%`,
3098
- animation: `cpk-a2ui-pulse 1.5s ease-in-out ${i * .15}s infinite`
3099
- }
3100
- }, i))
3026
+ className: "cpk:mt-1 cpk:text-xs cpk:text-amber-700",
3027
+ children: "Something went wrong rendering this. You can keep chatting and try again."
3101
3028
  }),
3102
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
3103
- @keyframes cpk-a2ui-pulse {
3104
- 0%, 100% { opacity: 0.4; }
3105
- 50% { opacity: 1; }
3106
- }
3107
- ` })
3029
+ debugExposure !== "hidden" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIDebugDetails, {
3030
+ label: "developer details",
3031
+ open: debugExposure === "verbose",
3032
+ payload: {
3033
+ error: content?.error,
3034
+ attempts: content?.attempts
3035
+ }
3036
+ })
3108
3037
  ]
3109
3038
  });
3110
3039
  }
3111
- function getOperationSurfaceId(operation) {
3112
- if (!operation || typeof operation !== "object") return null;
3113
- if (typeof operation.surfaceId === "string") return operation.surfaceId;
3114
- return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
3115
- }
3116
-
3117
- //#endregion
3118
- //#region src/v2/a2ui/A2UIToolCallRenderer.tsx
3119
- /**
3120
- * Tool name used by the dynamic A2UI generation secondary LLM.
3121
- * This renderer is auto-registered when A2UI is enabled.
3122
- */
3123
- const RENDER_A2UI_TOOL_NAME = "render_a2ui";
3124
3040
  /**
3125
- * Built-in progress indicator for dynamic A2UI generation.
3126
- * Shows a skeleton wireframe that progressively reveals as tokens stream in.
3127
- *
3128
- * Registered automatically when A2UI is enabled. Users can override by
3129
- * providing their own `useRenderTool({ name: "render_a2ui", ... })`.
3041
+ * Animated wireframe skeleton with a label, an optional live token count, and an
3042
+ * optional debug-detail slot below it. Pure CSS animation (no data dependency).
3043
+ * The `tokens` count drives a progressive reveal of skeleton rows.
3130
3044
  */
3131
- function A2UIProgressIndicator({ parameters }) {
3132
- const lastRef = (0, react.useRef)({
3133
- time: 0,
3134
- tokens: 0
3135
- });
3136
- const now = Date.now();
3137
- let { tokens } = lastRef.current;
3138
- if (now - lastRef.current.time > 200) {
3139
- const chars = JSON.stringify(parameters ?? {}).length;
3140
- tokens = Math.round(chars / 4);
3141
- lastRef.current = {
3142
- time: now,
3143
- tokens
3144
- };
3145
- }
3146
- const phase = tokens < 50 ? 0 : tokens < 200 ? 1 : tokens < 400 ? 2 : 3;
3045
+ function A2UIGeneratingSkeleton({ label, tokens, children }) {
3046
+ const phase = tokens == null ? 3 : tokens < 50 ? 0 : tokens < 200 ? 1 : tokens < 400 ? 2 : 3;
3147
3047
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3148
3048
  style: {
3149
3049
  margin: "12px 0",
@@ -3351,8 +3251,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3351
3251
  color: "#a1a1aa",
3352
3252
  letterSpacing: "0.025em"
3353
3253
  },
3354
- children: "Building interface"
3355
- }), tokens > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3254
+ children: label
3255
+ }), typeof tokens === "number" && tokens > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
3356
3256
  style: {
3357
3257
  fontSize: 11,
3358
3258
  color: "#d4d4d8",
@@ -3365,6 +3265,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3365
3265
  ]
3366
3266
  })]
3367
3267
  }),
3268
+ children,
3368
3269
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
3369
3270
  @keyframes cpk-a2ui-fade {
3370
3271
  0%, 100% { opacity: 1; }
@@ -3378,6 +3279,20 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3378
3279
  ]
3379
3280
  });
3380
3281
  }
3282
+ function A2UIDebugDetails({ label, open, payload }) {
3283
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
3284
+ open,
3285
+ className: "cpk:mt-2 cpk:text-xs",
3286
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
3287
+ className: "cpk:cursor-pointer cpk:text-gray-500",
3288
+ children: label
3289
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
3290
+ className: "cpk:mt-1 cpk:overflow-auto cpk:rounded cpk:bg-gray-100 cpk:p-2 cpk:text-gray-700",
3291
+ style: { fontSize: 11 },
3292
+ children: JSON.stringify(payload, null, 2)
3293
+ })]
3294
+ });
3295
+ }
3381
3296
  function Dot() {
3382
3297
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
3383
3298
  width: 7,
@@ -3413,13 +3328,242 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3413
3328
  children
3414
3329
  });
3415
3330
  }
3331
+
3332
+ //#endregion
3333
+ //#region src/v2/a2ui/A2UIMessageRenderer.tsx
3334
+ /**
3335
+ * The container key used to wrap A2UI operations for explicit detection.
3336
+ * Must match A2UI_OPERATIONS_KEY in @ag-ui/a2ui-middleware and copilotkit.a2ui (Python).
3337
+ */
3338
+ const A2UI_OPERATIONS_KEY = "a2ui_operations";
3339
+ let initialized = false;
3340
+ function ensureInitialized() {
3341
+ if (!initialized) {
3342
+ (0, _copilotkit_a2ui_renderer.initializeDefaultCatalog)();
3343
+ (0, _copilotkit_a2ui_renderer.injectStyles)();
3344
+ initialized = true;
3345
+ }
3346
+ }
3347
+ /**
3348
+ * The `a2ui-surface` activity carries the WHOLE generative-UI lifecycle on one
3349
+ * stable messageId (OSS-162): pre-paint `status` ("building" | "retrying" |
3350
+ * "failed") with recovery detail, then `a2ui_operations` on paint. The states
3351
+ * swap in place, so the painted surface replaces the skeleton with no extra
3352
+ * coordination. `.passthrough()` preserves operations + any future fields.
3353
+ */
3354
+ const A2UISurfaceContentSchema = zod.z.object({
3355
+ a2ui_operations: zod.z.array(zod.z.any()).optional(),
3356
+ ...A2UILifecycleFields
3357
+ }).passthrough();
3358
+ function createA2UIMessageRenderer(options) {
3359
+ const { theme, catalog, loadingComponent, recovery } = options;
3360
+ const showAfterMs = recovery?.showAfterMs ?? 2e3;
3361
+ const showAfterAttempts = recovery?.showAfterAttempts ?? 2;
3362
+ const optionDebugExposure = recovery?.debugExposure ?? "collapsed";
3363
+ return {
3364
+ activityType: "a2ui-surface",
3365
+ content: A2UISurfaceContentSchema,
3366
+ render: ({ content, agent }) => {
3367
+ ensureInitialized();
3368
+ const [operations, setOperations] = (0, react.useState)([]);
3369
+ const { copilotkit } = useCopilotKit();
3370
+ const lastContentRef = (0, react.useRef)(null);
3371
+ (0, react.useEffect)(() => {
3372
+ if (content === lastContentRef.current) return;
3373
+ lastContentRef.current = content;
3374
+ const incoming = content?.[A2UI_OPERATIONS_KEY];
3375
+ if (!content || !Array.isArray(incoming)) {
3376
+ setOperations([]);
3377
+ return;
3378
+ }
3379
+ setOperations(incoming);
3380
+ }, [content]);
3381
+ const groupedOperations = (0, react.useMemo)(() => {
3382
+ const groups = /* @__PURE__ */ new Map();
3383
+ for (const operation of operations) {
3384
+ const surfaceId = getOperationSurfaceId(operation) ?? _copilotkit_a2ui_renderer.DEFAULT_SURFACE_ID;
3385
+ if (!groups.has(surfaceId)) groups.set(surfaceId, []);
3386
+ groups.get(surfaceId).push(operation);
3387
+ }
3388
+ return groups;
3389
+ }, [operations]);
3390
+ const hasOps = groupedOperations.size > 0;
3391
+ const renderLifecycle = (c) => {
3392
+ const status = c?.status;
3393
+ const debugExposure = resolveDebugExposure(c, optionDebugExposure);
3394
+ if (status === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIRecoveryFailure, {
3395
+ content: c,
3396
+ debugExposure
3397
+ });
3398
+ if (status === "retrying") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIRetryingState, {
3399
+ content: c,
3400
+ showAfterMs,
3401
+ showAfterAttempts,
3402
+ debugExposure
3403
+ });
3404
+ if (loadingComponent) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(loadingComponent, {});
3405
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIBuildingState, { content: c });
3406
+ };
3407
+ const lastLoaderContentRef = (0, react.useRef)(null);
3408
+ if (!(Array.isArray(content?.[A2UI_OPERATIONS_KEY]) && content[A2UI_OPERATIONS_KEY].length > 0)) lastLoaderContentRef.current = content;
3409
+ const [surfaceReady, setSurfaceReady] = (0, react.useState)(false);
3410
+ const readyRef = (0, react.useRef)(false);
3411
+ const markSurfaceReady = (0, react.useCallback)(() => {
3412
+ if (readyRef.current) return;
3413
+ readyRef.current = true;
3414
+ requestAnimationFrame(() => setSurfaceReady(true));
3415
+ }, []);
3416
+ (0, react.useEffect)(() => {
3417
+ if (!hasOps) {
3418
+ setSurfaceReady(false);
3419
+ readyRef.current = false;
3420
+ return;
3421
+ }
3422
+ const t = setTimeout(() => setSurfaceReady(true), 8e3);
3423
+ return () => clearTimeout(t);
3424
+ }, [hasOps]);
3425
+ if (!hasOps) return renderLifecycle(content);
3426
+ const surfaces = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3427
+ className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
3428
+ children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReactSurfaceHost, {
3429
+ surfaceId,
3430
+ operations: ops,
3431
+ theme,
3432
+ agent,
3433
+ copilotkit,
3434
+ catalog,
3435
+ onReady: markSurfaceReady
3436
+ }, surfaceId))
3437
+ });
3438
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3439
+ style: { position: "relative" },
3440
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3441
+ "aria-hidden": !surfaceReady,
3442
+ style: surfaceReady ? void 0 : {
3443
+ position: "absolute",
3444
+ inset: 0,
3445
+ opacity: 0,
3446
+ pointerEvents: "none"
3447
+ },
3448
+ children: surfaces
3449
+ }), !surfaceReady && renderLifecycle(lastLoaderContentRef.current ?? content)]
3450
+ });
3451
+ }
3452
+ };
3453
+ }
3454
+ /**
3455
+ * Renders a single A2UI surface using the React renderer.
3456
+ * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
3457
+ */
3458
+ function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog, onReady }) {
3459
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3460
+ className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
3461
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_copilotkit_a2ui_renderer.A2UIProvider, {
3462
+ onAction: (0, react.useCallback)(async (message) => {
3463
+ if (!agent) return;
3464
+ message.userAction;
3465
+ try {
3466
+ copilotkit.setProperties({
3467
+ ...copilotkit.properties,
3468
+ a2uiAction: message
3469
+ });
3470
+ await copilotkit.runAgent({ agent });
3471
+ } finally {
3472
+ if (copilotkit.properties) {
3473
+ const { a2uiAction, ...rest } = copilotkit.properties;
3474
+ copilotkit.setProperties(rest);
3475
+ }
3476
+ }
3477
+ }, [agent, copilotkit]),
3478
+ theme,
3479
+ catalog,
3480
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SurfaceMessageProcessor, {
3481
+ surfaceId,
3482
+ operations,
3483
+ onReady
3484
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UISurfaceOrError, { surfaceId })]
3485
+ })
3486
+ });
3487
+ }
3488
+ /**
3489
+ * Renders the A2UI surface, or an error message if processing failed.
3490
+ * Must be a child of A2UIProvider to access the error state.
3491
+ */
3492
+ function A2UISurfaceOrError({ surfaceId }) {
3493
+ const error = (0, _copilotkit_a2ui_renderer.useA2UIError)();
3494
+ if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3495
+ className: "cpk:rounded-lg cpk:border cpk:border-red-200 cpk:bg-red-50 cpk:p-3 cpk:text-sm cpk:text-red-700",
3496
+ children: ["A2UI render error: ", error]
3497
+ });
3498
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_copilotkit_a2ui_renderer.A2UIRenderer, {
3499
+ surfaceId,
3500
+ className: "cpk:flex cpk:flex-1"
3501
+ });
3502
+ }
3416
3503
  /**
3417
- * Registers the built-in `render_a2ui` tool call renderer via the props-based
3418
- * `setRenderToolCalls` mechanism (not `useRenderTool`).
3504
+ * Processes A2UI operations into the provider's message processor.
3505
+ * Must be a child of A2UIProvider to access the actions context.
3506
+ */
3507
+ function SurfaceMessageProcessor({ surfaceId, operations, onReady }) {
3508
+ const { processMessages, getSurface } = (0, _copilotkit_a2ui_renderer.useA2UIActions)();
3509
+ const lastHashRef = (0, react.useRef)("");
3510
+ (0, react.useEffect)(() => {
3511
+ const hash = JSON.stringify(operations);
3512
+ if (hash === lastHashRef.current) return;
3513
+ lastHashRef.current = hash;
3514
+ processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
3515
+ if (onReady && surfaceHasRenderableContent(operations)) onReady();
3516
+ }, [
3517
+ processMessages,
3518
+ getSurface,
3519
+ surfaceId,
3520
+ operations,
3521
+ onReady
3522
+ ]);
3523
+ return null;
3524
+ }
3525
+ /**
3526
+ * Whether the surface's operations are enough to paint a visible card yet.
3527
+ * A data-bound surface references its data via `path` and renders nothing until
3528
+ * the data model has ≥1 value; a static surface (no path refs) paints from its
3529
+ * components alone. Used to time the loader→surface cross-over to actual content
3530
+ * arrival rather than a fixed delay. (OSS-162)
3531
+ */
3532
+ function surfaceHasRenderableContent(operations) {
3533
+ const componentOps = operations.filter((o) => o?.updateComponents);
3534
+ if (!componentOps.length) return false;
3535
+ if (!JSON.stringify(componentOps).includes("\"path\"")) return true;
3536
+ return operations.some((o) => {
3537
+ const v = o?.updateDataModel?.value;
3538
+ if (!v || typeof v !== "object") return false;
3539
+ return Object.values(v).some((x) => Array.isArray(x) ? x.length > 0 : x !== null && x !== void 0 && x !== "");
3540
+ });
3541
+ }
3542
+ function getOperationSurfaceId(operation) {
3543
+ if (!operation || typeof operation !== "object") return null;
3544
+ if (typeof operation.surfaceId === "string") return operation.surfaceId;
3545
+ return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
3546
+ }
3547
+
3548
+ //#endregion
3549
+ //#region src/v2/a2ui/A2UIToolCallRenderer.tsx
3550
+ /**
3551
+ * Tool name used by the dynamic A2UI generation secondary LLM.
3552
+ */
3553
+ const RENDER_A2UI_TOOL_NAME = "render_a2ui";
3554
+ /**
3555
+ * Registers a no-op renderer for the `render_a2ui` tool call so its raw streamed
3556
+ * args are never surfaced in the transcript.
3557
+ *
3558
+ * The generation skeleton / retry / failure UX is NO LONGER owned here (OSS-162):
3559
+ * the A2UI middleware drives the whole lifecycle on the `a2ui-surface` activity
3560
+ * (one stable messageId, building → retrying → failed → painted), rendered in
3561
+ * place by `createA2UIMessageRenderer`. Owning a skeleton per tool call caused a
3562
+ * duplicate skeleton on retries / multi-call generations and a skeleton that
3563
+ * lingered after the surface painted — both fixed by retiring it here.
3419
3564
  *
3420
- * This ensures user-registered `useRenderTool({ name: "render_a2ui", ... })`
3421
- * hooks automatically override the built-in, since the merge logic in
3422
- * react-core.ts gives hook-based entries priority over prop-based entries.
3565
+ * Users can still override with their own `useRenderTool({ name: "render_a2ui" })`
3566
+ * (hook-based entries take priority over this prop-based registration).
3423
3567
  */
3424
3568
  function A2UIBuiltInToolCallRenderer() {
3425
3569
  const { copilotkit } = useCopilotKit();
@@ -3427,15 +3571,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3427
3571
  const renderer = defineToolCallRenderer({
3428
3572
  name: RENDER_A2UI_TOOL_NAME,
3429
3573
  args: zod.z.any(),
3430
- render: ({ status, args: parameters }) => {
3431
- if (status === "complete") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
3432
- const params = parameters;
3433
- const items = params?.items;
3434
- if (Array.isArray(items) && items.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
3435
- const components = params?.components;
3436
- if (Array.isArray(components) && components.length > 2) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
3437
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIProgressIndicator, { parameters });
3438
- }
3574
+ render: () => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {})
3439
3575
  });
3440
3576
  const existing = copilotkit._renderToolCalls ?? [];
3441
3577
  copilotkit.setRenderToolCalls([...existing.filter((rc) => rc.name !== RENDER_A2UI_TOOL_NAME), renderer]);
@@ -3572,7 +3708,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3572
3708
  if (runtimeA2UIEnabled) renderers.unshift(createA2UIMessageRenderer({
3573
3709
  theme: a2ui?.theme ?? _copilotkit_a2ui_renderer.viewerTheme,
3574
3710
  catalog: a2ui?.catalog,
3575
- loadingComponent: a2ui?.loadingComponent
3711
+ loadingComponent: a2ui?.loadingComponent,
3712
+ recovery: a2ui?.recovery
3576
3713
  }));
3577
3714
  return renderers;
3578
3715
  }, [
@@ -6050,40 +6187,28 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6050
6187
  * rendered by the {@link IntelligenceIndicator} brain and the default
6051
6188
  * value for the `intelligenceIndicator` slot.
6052
6189
  *
6053
- * Single-element three-stage design:
6054
- * 1. **In-progress.** Glassmorphism pill chrome around a 270° arc icon
6055
- * and the label. The arc has a single continuous visible stroke
6056
- * (one `stroke-dasharray` dash + one gap, summing to the path
6057
- * length) and the whole SVG rotates — so the viewer sees one
6058
- * C-shaped arc spinning around the visual center.
6059
- * 2. **Icon morph (~250 ms).** On status flip the single icon path
6060
- * interpolates from the arc to a checkmark via CSS `d:` while the
6061
- * dashed stroke transitions to solid (filling in the gap that was
6062
- * the spinner's open portion). The SVG rotation animation is
6063
- * removed; the snap back to identity is masked by the simultaneous
6064
- * shape change. Chrome and text stay at full opacity throughout.
6065
- * 3. **Settle (~400 ms, starts at +250 ms).** Chrome (background,
6066
- * border, shadow, backdrop-blur) fades to zero opacity. The label
6067
- * and icon stroke color transitions from saturated purple to a
6068
- * true-neutral gray at 0.8 alpha — no hue cast, reads as "settled
6069
- * history metadata." The label simultaneously skews to ~10° (a
6070
- * transform-based italic feel that interpolates smoothly with the
6071
- * color, rather than the discrete `font-style: italic` snap that
6072
- * would cause a layout pop). The label text stays put — only its
6073
- * color and slant change — so there is no "bump" where the brand
6074
- * text disappears and reappears.
6075
- *
6076
- * Hard sequence: stage 3 has a 250 ms transition-delay so it waits
6077
- * for stage 2 to finish. Total settle time ~650 ms in production.
6190
+ * Layout: a glassmorphism pill (the `__chrome` layer) wrapping an icon
6191
+ * and a label. The icon is two overlaid SVG paths — a spinner arc and a
6192
+ * checkmark whose geometry lives in each path's `d` ATTRIBUTE so it
6193
+ * renders in every browser (the CSS `d:` property is Chrome-only).
6078
6194
  *
6079
- * Both shapes are 3-segment cubic Bézier paths with matched command
6080
- * structure (one `M` plus three `C`s), which is what makes the d
6081
- * morph interpolate as a continuous shape change rather than snapping.
6195
+ * Two states, driven by the `data-status` attribute (see globals.css
6196
+ * for the exact timing):
6197
+ * 1. **In-progress.** The arc spins (CSS rotation) inside the pill and
6198
+ * the checkmark is hidden. Label + icon are a saturated purple.
6199
+ * 2. **Finished.** The arc fades out mid-spin while the checkmark draws
6200
+ * itself in upright (animated `stroke-dashoffset`); the pill chrome
6201
+ * fades away; and the label + icon settle from purple to a neutral
6202
+ * gray, with the label slanting slightly (a `transform: skewX`
6203
+ * faux-italic, so it interpolates with the color instead of snapping
6204
+ * and never reflows). The result reads as quiet "history metadata"
6205
+ * rather than an active spinner. The label text itself never changes
6206
+ * — the static check plus the color/slant shift carry the "done"
6207
+ * meaning, so no wording change is needed.
6082
6208
  *
6083
- * The label is identical in both states (default "CopilotKit
6084
- * Intelligence"). The static check icon carries the "done" semantic;
6085
- * the color + slant transition does the "settle" work without needing
6086
- * any wording change.
6209
+ * All motion is gated behind `prefers-reduced-motion` (globals.css):
6210
+ * when reduced motion is requested the arc does not spin and the two
6211
+ * states swap instantly, without transitions.
6087
6212
  *
6088
6213
  * Customize via the `intelligenceIndicator` slot on `CopilotChat`:
6089
6214
  * a className string restyles the wrapper, a props object tweaks
@@ -6104,13 +6229,21 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6104
6229
  "aria-hidden": "true"
6105
6230
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6106
6231
  className: "cpk-intelligence-indicator__content",
6107
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
6232
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
6108
6233
  className: "cpk-intelligence-indicator__icon",
6109
6234
  viewBox: "0 0 24 24",
6110
6235
  width: "14",
6111
6236
  height: "14",
6112
6237
  "aria-hidden": "true",
6113
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { className: "cpk-intelligence-indicator__icon-path" })
6238
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
6239
+ className: "cpk-intelligence-indicator__icon-arc",
6240
+ pathLength: 1,
6241
+ 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"
6242
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
6243
+ className: "cpk-intelligence-indicator__icon-check",
6244
+ pathLength: 1,
6245
+ d: "M 5 12.5 L 9 16.5 L 19 6.5"
6246
+ })]
6114
6247
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6115
6248
  className: "cpk-intelligence-indicator__label",
6116
6249
  children: label
@@ -6330,6 +6463,58 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6330
6463
  //#endregion
6331
6464
  //#region src/v2/components/chat/CopilotChatMessageView.tsx
6332
6465
  /**
6466
+ * Builds a map of message.id → stable per-row React key for an entire message
6467
+ * list. A message's canonical `id` is not stable within a turn: some backends
6468
+ * re-key a message mid-stream (e.g. LangChain replaces its transient
6469
+ * `lc_run--…` streaming id with the provider's final `resp_…` id in the
6470
+ * MESSAGES_SNAPSHOT). Keying rows by `id` made React unmount/remount the row
6471
+ * on that swap — the visible HITL chat flash. Tool-call ids survive the
6472
+ * rename, so an assistant message anchored by a tool call is keyed by its
6473
+ * first tool-call id (`tc:<anchorToolCallId>`); everything else falls back to
6474
+ * `id`.
6475
+ *
6476
+ * Load-bearing assumption: this only helps when the first tool-call id itself
6477
+ * is stable across the rename; backends that re-key tool-call ids mid-stream
6478
+ * still remount.
6479
+ *
6480
+ * Collision rule: two distinct assistant messages can occasionally share a
6481
+ * first-tool-call id (e.g. due to upstream bugs or replayed state). First
6482
+ * occurrence (in list order) claims `tc:<id>`; later collisions fall back to
6483
+ * `message.id`. Every assigned key is checked against the claimed set, so
6484
+ * keys are unique even for pathological ids (e.g. a raw message id beginning
6485
+ * with "tc:"); collisions disambiguate with a deterministic numeric suffix.
6486
+ *
6487
+ * Precondition: callers must pass a deduplicated list (see
6488
+ * `deduplicateMessages`); duplicate message ids would silently overwrite map
6489
+ * entries.
6490
+ *
6491
+ * Order caveat: the collision rule is order-sensitive — if two messages
6492
+ * sharing a first tool-call id swap list positions across renders, the
6493
+ * claimant changes and both rows remount. Acceptable: that situation already
6494
+ * indicates an upstream id bug, and keys remain unique.
6495
+ */
6496
+ function buildRowRenderKeys(messages) {
6497
+ const keys = /* @__PURE__ */ new Map();
6498
+ const claimed = /* @__PURE__ */ new Set();
6499
+ for (const message of messages) {
6500
+ let candidate;
6501
+ if (message.role === "assistant") {
6502
+ const anchorToolCallId = message.toolCalls?.[0]?.id;
6503
+ if (anchorToolCallId) candidate = `tc:${anchorToolCallId}`;
6504
+ }
6505
+ let assigned = message.id;
6506
+ if (candidate && !claimed.has(candidate)) assigned = candidate;
6507
+ else if (claimed.has(assigned)) {
6508
+ let n = 2;
6509
+ while (claimed.has(`${assigned}:${n}`)) n += 1;
6510
+ assigned = `${assigned}:${n}`;
6511
+ }
6512
+ keys.set(message.id, assigned);
6513
+ claimed.add(assigned);
6514
+ }
6515
+ return keys;
6516
+ }
6517
+ /**
6333
6518
  * Resolves a slot value into a { Component, slotProps } pair, handling the three
6334
6519
  * slot forms: a component type, a className string, or a partial-props object.
6335
6520
  */
@@ -6508,6 +6693,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6508
6693
  return copilotkit.getStateByRun(config.agentId, config.threadId, resolvedRunId);
6509
6694
  };
6510
6695
  const deduplicatedMessages = (0, react.useMemo)(() => deduplicateMessages(messages), [messages]);
6696
+ const rowRenderKeys = (0, react.useMemo)(() => buildRowRenderKeys(deduplicatedMessages), [deduplicatedMessages]);
6511
6697
  if (process.env.NODE_ENV === "development" && deduplicatedMessages.length < messages.length) console.warn(`CopilotChatMessageView: Merged ${messages.length - deduplicatedMessages.length} message(s) with duplicate IDs.`);
6512
6698
  const { Component: AssistantComponent, slotProps: assistantSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(assistantMessage, CopilotChatAssistantMessage_default), [assistantMessage]);
6513
6699
  const { Component: UserComponent, slotProps: userSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(userMessage, CopilotChatUserMessage_default), [userMessage]);
@@ -6538,41 +6724,42 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6538
6724
  const renderMessageBlock = (message) => {
6539
6725
  const elements = [];
6540
6726
  const stateSnapshot = getStateSnapshotForMessage(message.id);
6727
+ const rowKey = rowRenderKeys.get(message.id) ?? message.id;
6541
6728
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
6542
6729
  message,
6543
6730
  position: "before",
6544
6731
  renderCustomMessage,
6545
6732
  stateSnapshot
6546
- }, `${message.id}-custom-before`));
6733
+ }, `${rowKey}-custom-before`));
6547
6734
  if (message.role === "assistant") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedAssistantMessage, {
6548
6735
  message,
6549
6736
  messages,
6550
6737
  isRunning,
6551
6738
  AssistantMessageComponent: AssistantComponent,
6552
6739
  slotProps: assistantSlotProps
6553
- }, message.id));
6740
+ }, rowKey));
6554
6741
  else if (message.role === "user") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedUserMessage, {
6555
6742
  message,
6556
6743
  UserMessageComponent: UserComponent,
6557
6744
  slotProps: userSlotProps
6558
- }, message.id));
6745
+ }, rowKey));
6559
6746
  else if (message.role === "activity") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedActivityMessage, {
6560
6747
  message,
6561
6748
  renderActivityMessage
6562
- }, message.id));
6749
+ }, rowKey));
6563
6750
  else if (message.role === "reasoning") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
6564
6751
  message,
6565
6752
  messages,
6566
6753
  isRunning,
6567
6754
  ReasoningMessageComponent: ReasoningComponent,
6568
6755
  slotProps: reasoningSlotProps
6569
- }, message.id));
6756
+ }, rowKey));
6570
6757
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
6571
6758
  message,
6572
6759
  position: "after",
6573
6760
  renderCustomMessage,
6574
6761
  stateSnapshot
6575
- }, `${message.id}-custom-after`));
6762
+ }, `${rowKey}-custom-after`));
6576
6763
  const intelligenceTurnId = intelligenceTurnAnchors.get(message.id);
6577
6764
  if (intelligenceTurnId !== void 0) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IntelligenceIndicator, {
6578
6765
  message,
@@ -6618,7 +6805,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6618
6805
  transform: `translateY(${virtualItem.start}px)`
6619
6806
  },
6620
6807
  children: renderMessageBlock(message)
6621
- }, message.id);
6808
+ }, rowRenderKeys.get(message.id) ?? message.id);
6622
6809
  })
6623
6810
  }) : messageElements,
6624
6811
  interruptElement,
@@ -9330,7 +9517,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
9330
9517
  } };
9331
9518
  case _copilotkit_shared.CopilotKitErrorCode.UPGRADE_REQUIRED_ERROR: return { primary: {
9332
9519
  label: "Upgrade",
9333
- onClick: () => window.open("https://cloud.copilotkit.ai", "_blank", "noopener,noreferrer")
9520
+ onClick: () => window.open("https://dashboard.operations.copilotkit.ai", "_blank", "noopener,noreferrer")
9334
9521
  } };
9335
9522
  default: return;
9336
9523
  }