@copilotkit/react-core 1.70.2 → 1.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/{copilotkit-DcFo270Y.cjs → copilotkit-B4Jb1QEy.cjs} +582 -168
  2. package/dist/copilotkit-B4Jb1QEy.cjs.map +1 -0
  3. package/dist/{copilotkit--Jyzm6hS.d.mts → copilotkit-ChjzWqn6.d.mts} +34 -9
  4. package/dist/copilotkit-ChjzWqn6.d.mts.map +1 -0
  5. package/dist/{copilotkit-BLYaCGcz.d.cts → copilotkit-DCIXZawe.d.cts} +34 -9
  6. package/dist/copilotkit-DCIXZawe.d.cts.map +1 -0
  7. package/dist/{copilotkit-B1jSvZeb.mjs → copilotkit-snbJkMqQ.mjs} +583 -169
  8. package/dist/copilotkit-snbJkMqQ.mjs.map +1 -0
  9. package/dist/index.cjs +3 -3
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +1 -1
  12. package/dist/index.d.cts.map +1 -1
  13. package/dist/index.d.mts +1 -1
  14. package/dist/index.d.mts.map +1 -1
  15. package/dist/index.mjs +4 -4
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/index.umd.js +393 -153
  18. package/dist/index.umd.js.map +1 -1
  19. package/dist/v2/context.cjs +4 -0
  20. package/dist/v2/context.cjs.map +1 -1
  21. package/dist/v2/context.d.cts +3 -1
  22. package/dist/v2/context.d.cts.map +1 -1
  23. package/dist/v2/context.d.mts +3 -1
  24. package/dist/v2/context.d.mts.map +1 -1
  25. package/dist/v2/context.mjs +3 -1
  26. package/dist/v2/context.mjs.map +1 -1
  27. package/dist/v2/headless.cjs +57 -6
  28. package/dist/v2/headless.cjs.map +1 -1
  29. package/dist/v2/headless.d.cts +9 -4
  30. package/dist/v2/headless.d.cts.map +1 -1
  31. package/dist/v2/headless.d.mts +9 -4
  32. package/dist/v2/headless.d.mts.map +1 -1
  33. package/dist/v2/headless.mjs +58 -7
  34. package/dist/v2/headless.mjs.map +1 -1
  35. package/dist/v2/index.cjs +1 -1
  36. package/dist/v2/index.css +1 -1
  37. package/dist/v2/index.d.cts +1 -1
  38. package/dist/v2/index.d.mts +1 -1
  39. package/dist/v2/index.mjs +1 -1
  40. package/dist/v2/index.umd.js +708 -292
  41. package/dist/v2/index.umd.js.map +1 -1
  42. package/package.json +7 -7
  43. package/skills/react-core/SKILL.md +1 -1
  44. package/skills/react-core/references/agent-access.md +69 -26
  45. package/skills/react-core/references/chat-components.md +22 -5
  46. package/dist/copilotkit--Jyzm6hS.d.mts.map +0 -1
  47. package/dist/copilotkit-B1jSvZeb.mjs.map +0 -1
  48. package/dist/copilotkit-BLYaCGcz.d.cts.map +0 -1
  49. package/dist/copilotkit-DcFo270Y.cjs.map +0 -1
package/dist/index.umd.js CHANGED
@@ -36,6 +36,133 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
36
36
  react = __toESM(react);
37
37
  react_markdown = __toESM(react_markdown);
38
38
 
39
+ //#region src/v2/lib/react-core.ts
40
+ var CopilotKitCoreReact = class extends _copilotkit_core.CopilotKitCore {
41
+ constructor(config) {
42
+ super(config);
43
+ this._renderToolCalls = [];
44
+ this._hookRenderToolCalls = /* @__PURE__ */ new Map();
45
+ this._cachedMergedRenderToolCalls = null;
46
+ this._renderCustomMessages = [];
47
+ this._renderActivityMessages = [];
48
+ this._interruptElement = null;
49
+ this._renderToolCalls = config.renderToolCalls ?? [];
50
+ this._renderCustomMessages = config.renderCustomMessages ?? [];
51
+ this._renderActivityMessages = config.renderActivityMessages ?? [];
52
+ }
53
+ get renderCustomMessages() {
54
+ return this._renderCustomMessages;
55
+ }
56
+ get renderActivityMessages() {
57
+ return this._renderActivityMessages;
58
+ }
59
+ get renderToolCalls() {
60
+ if (this._hookRenderToolCalls.size === 0) return this._renderToolCalls;
61
+ if (this._cachedMergedRenderToolCalls) return this._cachedMergedRenderToolCalls;
62
+ const merged = /* @__PURE__ */ new Map();
63
+ for (const rc of this._renderToolCalls) merged.set(`${rc.agentId ?? ""}:${rc.name}`, rc);
64
+ for (const [key, rc] of this._hookRenderToolCalls) merged.set(key, rc);
65
+ this._cachedMergedRenderToolCalls = Array.from(merged.values());
66
+ return this._cachedMergedRenderToolCalls;
67
+ }
68
+ setRenderActivityMessages(renderers) {
69
+ this._renderActivityMessages = renderers;
70
+ }
71
+ setRenderCustomMessages(renderers) {
72
+ this._renderCustomMessages = renderers;
73
+ }
74
+ setRenderToolCalls(renderToolCalls) {
75
+ this._renderToolCalls = renderToolCalls;
76
+ this._cachedMergedRenderToolCalls = null;
77
+ this._notifyRenderToolCallsChanged();
78
+ }
79
+ addHookRenderToolCall(entry) {
80
+ const key = `${entry.agentId ?? ""}:${entry.name}`;
81
+ this._hookRenderToolCalls.set(key, entry);
82
+ this._cachedMergedRenderToolCalls = null;
83
+ this._notifyRenderToolCallsChanged();
84
+ }
85
+ removeHookRenderToolCall(name, agentId) {
86
+ const key = `${agentId ?? ""}:${name}`;
87
+ if (this._hookRenderToolCalls.delete(key)) {
88
+ this._cachedMergedRenderToolCalls = null;
89
+ this._notifyRenderToolCallsChanged();
90
+ }
91
+ }
92
+ _notifyRenderToolCallsChanged() {
93
+ this.notifySubscribers((subscriber) => {
94
+ const reactSubscriber = subscriber;
95
+ if (reactSubscriber.onRenderToolCallsChanged) reactSubscriber.onRenderToolCallsChanged({
96
+ copilotkit: this,
97
+ renderToolCalls: this.renderToolCalls
98
+ });
99
+ }, "Subscriber onRenderToolCallsChanged error:");
100
+ }
101
+ get interruptElement() {
102
+ return this._interruptElement;
103
+ }
104
+ setInterruptElement(element) {
105
+ this._interruptElement = element;
106
+ this.notifySubscribers((subscriber) => {
107
+ subscriber.onInterruptElementChanged?.({
108
+ copilotkit: this,
109
+ interruptElement: this._interruptElement
110
+ });
111
+ }, "Subscriber onInterruptElementChanged error:");
112
+ }
113
+ subscribe(subscriber) {
114
+ return super.subscribe(subscriber);
115
+ }
116
+ /**
117
+ * Wait for pending React state updates before the follow-up agent run.
118
+ *
119
+ * When a frontend tool handler calls setState(), React 18 batches the update
120
+ * and schedules a commit via its internal scheduler (MessageChannel). The
121
+ * useAgentContext hook registers context via useLayoutEffect, which runs
122
+ * synchronously after React commits that batch.
123
+ *
124
+ * Awaiting a zero-delay timeout yields to the macrotask queue. React's
125
+ * MessageChannel task runs first, committing the pending state and running
126
+ * useLayoutEffect (which updates the context store). The follow-up runAgent
127
+ * call then reads fresh context.
128
+ */
129
+ async waitForPendingFrameworkUpdates() {
130
+ await new Promise((resolve) => setTimeout(resolve, 0));
131
+ }
132
+ };
133
+
134
+ //#endregion
135
+ //#region src/v2/context.ts
136
+ const CopilotKitContext = (0, react.createContext)(null);
137
+ const useCopilotKit = () => {
138
+ const context = (0, react.useContext)(CopilotKitContext);
139
+ const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
140
+ if (!context) throw new Error("useCopilotKit must be used within CopilotKitProvider");
141
+ (0, react.useEffect)(() => {
142
+ const subscription = context.copilotkit.subscribe({
143
+ onRuntimeConnectionStatusChanged: () => {
144
+ forceUpdate();
145
+ },
146
+ onHeadersChanged: () => {
147
+ forceUpdate();
148
+ }
149
+ });
150
+ return () => {
151
+ subscription.unsubscribe();
152
+ };
153
+ }, []);
154
+ return context;
155
+ };
156
+ const LicenseContext = (0, react.createContext)({
157
+ status: null,
158
+ license: null,
159
+ checkFeature: () => true,
160
+ getLimit: () => null
161
+ });
162
+ const CopilotKitAgentIdContext = (0, react.createContext)(void 0);
163
+ const useDefaultAgentId = () => (0, react.useContext)(CopilotKitAgentIdContext);
164
+
165
+ //#endregion
39
166
  //#region src/v2/lib/shallow-stable-ref.ts
40
167
  /**
41
168
  * Shallow equality comparison for objects.
@@ -90,7 +217,12 @@ react_markdown = __toESM(react_markdown);
90
217
  assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
91
218
  assistantMessageToolbarCopyMessageLabel: "Copy",
92
219
  assistantMessageToolbarInspectorLabel: "View in Inspector",
93
- assistantMessageToolbarInspectorLocalOnlyLabel: "Development Only",
220
+ assistantMessageToolbarInspectorDescription: "Open this message in the Inspector",
221
+ assistantMessageToolbarInspectorLocalOnlyLabel: "Local only",
222
+ assistantMessageToolbarInspectorLocalOnlyDescription: "Only visible on localhost or loopback hosts in development. Never shown in production.",
223
+ assistantMessageToolbarInspectorTitle: "CopilotKit Inspector",
224
+ assistantMessageToolbarInspectorHideLabel: "Hide this icon",
225
+ assistantMessageToolbarInspectorHideDescription: "Until you reload this page",
94
226
  assistantMessageToolbarThumbsUpLabel: "Good response",
95
227
  assistantMessageToolbarThumbsDownLabel: "Bad response",
96
228
  assistantMessageToolbarReadAloudLabel: "Read aloud",
@@ -131,7 +263,8 @@ react_markdown = __toESM(react_markdown);
131
263
  ...parentConfig?.labels,
132
264
  ...stableLabels
133
265
  }), [stableLabels, parentConfig?.labels]);
134
- const resolvedAgentId = agentId ?? parentConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
266
+ const providerAgentId = useDefaultAgentId();
267
+ const resolvedAgentId = agentId ?? parentConfig?.agentId ?? providerAgentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
135
268
  const threadIdPropIsAuthoritative = threadId !== void 0 && hasExplicitThreadId !== false;
136
269
  const isThreadIdControlled = threadIdPropIsAuthoritative;
137
270
  const [activeThreadOverride, setActiveThreadOverride] = (0, react.useState)(null);
@@ -339,131 +472,6 @@ react_markdown = __toESM(react_markdown);
339
472
  };
340
473
  ControlledModalOpenScope.displayName = "ControlledModalOpenScope";
341
474
 
342
- //#endregion
343
- //#region src/v2/lib/react-core.ts
344
- var CopilotKitCoreReact = class extends _copilotkit_core.CopilotKitCore {
345
- constructor(config) {
346
- super(config);
347
- this._renderToolCalls = [];
348
- this._hookRenderToolCalls = /* @__PURE__ */ new Map();
349
- this._cachedMergedRenderToolCalls = null;
350
- this._renderCustomMessages = [];
351
- this._renderActivityMessages = [];
352
- this._interruptElement = null;
353
- this._renderToolCalls = config.renderToolCalls ?? [];
354
- this._renderCustomMessages = config.renderCustomMessages ?? [];
355
- this._renderActivityMessages = config.renderActivityMessages ?? [];
356
- }
357
- get renderCustomMessages() {
358
- return this._renderCustomMessages;
359
- }
360
- get renderActivityMessages() {
361
- return this._renderActivityMessages;
362
- }
363
- get renderToolCalls() {
364
- if (this._hookRenderToolCalls.size === 0) return this._renderToolCalls;
365
- if (this._cachedMergedRenderToolCalls) return this._cachedMergedRenderToolCalls;
366
- const merged = /* @__PURE__ */ new Map();
367
- for (const rc of this._renderToolCalls) merged.set(`${rc.agentId ?? ""}:${rc.name}`, rc);
368
- for (const [key, rc] of this._hookRenderToolCalls) merged.set(key, rc);
369
- this._cachedMergedRenderToolCalls = Array.from(merged.values());
370
- return this._cachedMergedRenderToolCalls;
371
- }
372
- setRenderActivityMessages(renderers) {
373
- this._renderActivityMessages = renderers;
374
- }
375
- setRenderCustomMessages(renderers) {
376
- this._renderCustomMessages = renderers;
377
- }
378
- setRenderToolCalls(renderToolCalls) {
379
- this._renderToolCalls = renderToolCalls;
380
- this._cachedMergedRenderToolCalls = null;
381
- this._notifyRenderToolCallsChanged();
382
- }
383
- addHookRenderToolCall(entry) {
384
- const key = `${entry.agentId ?? ""}:${entry.name}`;
385
- this._hookRenderToolCalls.set(key, entry);
386
- this._cachedMergedRenderToolCalls = null;
387
- this._notifyRenderToolCallsChanged();
388
- }
389
- removeHookRenderToolCall(name, agentId) {
390
- const key = `${agentId ?? ""}:${name}`;
391
- if (this._hookRenderToolCalls.delete(key)) {
392
- this._cachedMergedRenderToolCalls = null;
393
- this._notifyRenderToolCallsChanged();
394
- }
395
- }
396
- _notifyRenderToolCallsChanged() {
397
- this.notifySubscribers((subscriber) => {
398
- const reactSubscriber = subscriber;
399
- if (reactSubscriber.onRenderToolCallsChanged) reactSubscriber.onRenderToolCallsChanged({
400
- copilotkit: this,
401
- renderToolCalls: this.renderToolCalls
402
- });
403
- }, "Subscriber onRenderToolCallsChanged error:");
404
- }
405
- get interruptElement() {
406
- return this._interruptElement;
407
- }
408
- setInterruptElement(element) {
409
- this._interruptElement = element;
410
- this.notifySubscribers((subscriber) => {
411
- subscriber.onInterruptElementChanged?.({
412
- copilotkit: this,
413
- interruptElement: this._interruptElement
414
- });
415
- }, "Subscriber onInterruptElementChanged error:");
416
- }
417
- subscribe(subscriber) {
418
- return super.subscribe(subscriber);
419
- }
420
- /**
421
- * Wait for pending React state updates before the follow-up agent run.
422
- *
423
- * When a frontend tool handler calls setState(), React 18 batches the update
424
- * and schedules a commit via its internal scheduler (MessageChannel). The
425
- * useAgentContext hook registers context via useLayoutEffect, which runs
426
- * synchronously after React commits that batch.
427
- *
428
- * Awaiting a zero-delay timeout yields to the macrotask queue. React's
429
- * MessageChannel task runs first, committing the pending state and running
430
- * useLayoutEffect (which updates the context store). The follow-up runAgent
431
- * call then reads fresh context.
432
- */
433
- async waitForPendingFrameworkUpdates() {
434
- await new Promise((resolve) => setTimeout(resolve, 0));
435
- }
436
- };
437
-
438
- //#endregion
439
- //#region src/v2/context.ts
440
- const CopilotKitContext = (0, react.createContext)(null);
441
- const useCopilotKit = () => {
442
- const context = (0, react.useContext)(CopilotKitContext);
443
- const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
444
- if (!context) throw new Error("useCopilotKit must be used within CopilotKitProvider");
445
- (0, react.useEffect)(() => {
446
- const subscription = context.copilotkit.subscribe({
447
- onRuntimeConnectionStatusChanged: () => {
448
- forceUpdate();
449
- },
450
- onHeadersChanged: () => {
451
- forceUpdate();
452
- }
453
- });
454
- return () => {
455
- subscription.unsubscribe();
456
- };
457
- }, []);
458
- return context;
459
- };
460
- const LicenseContext = (0, react.createContext)({
461
- status: null,
462
- license: null,
463
- checkFeature: () => true,
464
- getLimit: () => null
465
- });
466
-
467
475
  //#endregion
468
476
  //#region src/v2/hooks/use-render-tool-call.tsx
469
477
  /**
@@ -504,6 +512,29 @@ react_markdown = __toESM(react_markdown);
504
512
  if (prevProps.RenderComponent !== nextProps.RenderComponent) return false;
505
513
  return true;
506
514
  });
515
+ const IS_DEVELOPMENT$1 = process.env.NODE_ENV !== "production";
516
+ /**
517
+ * Reports tool calls that resolved to no renderer.
518
+ *
519
+ * A tool call with no renderer paints an empty message container and says
520
+ * nothing else, so the only signal a developer gets today is a blank space in
521
+ * the chat. This names the call and the renderers that *are* registered, which
522
+ * is the whole diagnosis when the cause is a name that does not match.
523
+ *
524
+ * @param toolNames - The unmatched tool names collected during render.
525
+ * @param registered - The registry as it stands now, re-read at report time.
526
+ * @param alreadyWarned - Names already reported, mutated to keep this once per name.
527
+ */
528
+ function warnAboutUnrenderedToolCalls(toolNames, registered, alreadyWarned) {
529
+ const registeredNames = Array.from(new Set(registered.map((rc) => rc.name)));
530
+ const hasWildcard = registeredNames.includes("*");
531
+ for (const toolName of toolNames) {
532
+ if (hasWildcard || registeredNames.includes(toolName)) continue;
533
+ if (alreadyWarned.has(toolName)) continue;
534
+ alreadyWarned.add(toolName);
535
+ console.warn(`[CopilotKit] The agent called the tool "${toolName}", and no renderer is registered for it, so that message rendered nothing. ` + (registeredNames.length === 0 ? "No tool-call renderers are registered. " : `Registered renderers: ${registeredNames.map((name) => `"${name}"`).join(", ")}. `) + `Register one with useRenderTool({ name: "${toolName}", ... }), or call useDefaultRenderTool() for a built-in card that covers every tool the agent calls. This warning is development-only.`);
536
+ }
537
+ }
507
538
  /**
508
539
  * Hook that returns a function to render tool calls based on the render functions
509
540
  * defined in CopilotKitProvider.
@@ -513,13 +544,18 @@ react_markdown = __toESM(react_markdown);
513
544
  function useRenderToolCall$1() {
514
545
  const { copilotkit, executingToolCallIds } = useCopilotKit();
515
546
  const agentId = useCopilotChatConfiguration()?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
547
+ const unrenderedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
548
+ const warnedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
516
549
  const renderToolCalls = (0, react.useSyncExternalStore)((callback) => {
517
550
  return copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe;
518
551
  }, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
519
- return (0, react.useCallback)(({ toolCall, toolMessage }) => {
552
+ const renderToolCall = (0, react.useCallback)(({ toolCall, toolMessage }) => {
520
553
  const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
521
554
  const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*");
522
- if (!renderConfig) return null;
555
+ if (!renderConfig) {
556
+ if (IS_DEVELOPMENT$1) unrenderedToolNames.current.add(toolCall.function.name);
557
+ return null;
558
+ }
523
559
  const RenderComponent = renderConfig.render;
524
560
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallRenderer, {
525
561
  toolCall,
@@ -532,25 +568,43 @@ react_markdown = __toESM(react_markdown);
532
568
  executingToolCallIds,
533
569
  agentId
534
570
  ]);
571
+ (0, react.useEffect)(() => {
572
+ if (!IS_DEVELOPMENT$1) return;
573
+ if (unrenderedToolNames.current.size === 0) return;
574
+ const timer = setTimeout(() => {
575
+ const pending = Array.from(unrenderedToolNames.current);
576
+ unrenderedToolNames.current.clear();
577
+ warnAboutUnrenderedToolCalls(pending, copilotkit.renderToolCalls, warnedToolNames.current);
578
+ }, 0);
579
+ return () => clearTimeout(timer);
580
+ });
581
+ return renderToolCall;
535
582
  }
536
583
 
537
584
  //#endregion
538
585
  //#region src/v2/components/CopilotKitInspector.tsx
539
- const CopilotKitInspector = ({ core, openRequest }) => {
586
+ const CopilotKitInspector = ({ core, openRequest, onVisibilityChange }) => {
540
587
  const mountRef = react.useRef(null);
541
588
  const inspectorRef = react.useRef(null);
542
589
  const latestCoreRef = react.useRef(core ?? null);
543
590
  const latestOpenRequestRef = react.useRef(openRequest);
591
+ const visibilityCallbackRef = react.useRef(onVisibilityChange);
592
+ visibilityCallbackRef.current = onVisibilityChange;
544
593
  latestCoreRef.current = core ?? null;
545
594
  latestOpenRequestRef.current = openRequest;
546
595
  react.useEffect(() => {
547
596
  let mounted = true;
548
597
  let inspector = null;
598
+ const handleVisibilityChange = (event) => {
599
+ const visible = event.detail?.visible;
600
+ visibilityCallbackRef.current?.(visible === true);
601
+ };
549
602
  import("@copilotkit/web-inspector").then((mod) => {
550
603
  if (!mounted || !mountRef.current) return;
551
604
  mod.defineWebInspector?.();
552
605
  inspector = mountRef.current.ownerDocument.createElement(mod.WEB_INSPECTOR_TAG);
553
606
  mod.configureWebInspectorElement(inspector, latestCoreRef.current);
607
+ inspector.addEventListener("cpk-inspector-visibility-change", handleVisibilityChange);
554
608
  mountRef.current.appendChild(inspector);
555
609
  inspectorRef.current = inspector;
556
610
  const request = latestOpenRequestRef.current;
@@ -560,7 +614,9 @@ react_markdown = __toESM(react_markdown);
560
614
  });
561
615
  return () => {
562
616
  mounted = false;
617
+ inspector?.removeEventListener("cpk-inspector-visibility-change", handleVisibilityChange);
563
618
  inspector?.remove();
619
+ visibilityCallbackRef.current?.(false);
564
620
  if (inspectorRef.current === inspector) inspectorRef.current = null;
565
621
  };
566
622
  }, []);
@@ -1978,6 +2034,95 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
1978
2034
  a2ui_operations: zod.z.array(zod.z.any()).optional(),
1979
2035
  ...A2UILifecycleFields
1980
2036
  }).passthrough();
2037
+ const IS_DEVELOPMENT = process.env.NODE_ENV !== "production";
2038
+ /**
2039
+ * How long to wait for a surface to report its first paint before giving up on
2040
+ * the loader cross-over. Reaching it also means `onReady` never fired, which is
2041
+ * the signal the warning below reports.
2042
+ */
2043
+ const PAINT_FALLBACK_MS = 8e3;
2044
+ /**
2045
+ * Names the operation kinds a surface received, for a warning that has to say
2046
+ * what did arrive as well as what did not.
2047
+ *
2048
+ * @param operations - The operations grouped under one surface.
2049
+ * @returns A comma-separated list of operation keys, or "none".
2050
+ */
2051
+ function describeOperationKinds(operations) {
2052
+ const kinds = /* @__PURE__ */ new Set();
2053
+ for (const operation of operations) {
2054
+ if (!operation || typeof operation !== "object") continue;
2055
+ for (const key of Object.keys(operation)) if (key !== "version") kinds.add(key);
2056
+ }
2057
+ return kinds.size === 0 ? "none" : Array.from(kinds).join(", ");
2058
+ }
2059
+ /**
2060
+ * Reports surfaces that received operations and never painted.
2061
+ *
2062
+ * Reaching {@link PAINT_FALLBACK_MS} with no `onReady` means the operations were
2063
+ * accepted, were not malformed enough to raise the provider's error state, and
2064
+ * still put nothing on screen. Left alone that is invisible: the loader drops,
2065
+ * the turn finishes, and the only trace is an empty placeholder above the reply.
2066
+ *
2067
+ * `surfaceHasRenderableContent` already knows which half is missing, so the
2068
+ * warning says which rather than making the reader re-derive it.
2069
+ *
2070
+ * @param grouped - Operations grouped by surface id.
2071
+ */
2072
+ function warnAboutUnpaintedSurfaces(grouped) {
2073
+ for (const [surfaceId, operations] of grouped) {
2074
+ if (surfaceHasRenderableContent(operations)) continue;
2075
+ const cause = operations.filter((o) => o?.updateComponents).length === 0 ? "no updateComponents operation arrived, so the surface was never given anything to draw" : "its components address their values by \"path\" and no updateDataModel carried a non-empty value, so every bound component drew empty";
2076
+ console.warn(`[CopilotKit] A2UI surface "${surfaceId}" received operations and never painted after ${String(PAINT_FALLBACK_MS)}ms: ${cause}. Operations received: ${describeOperationKinds(operations)}. The payload was accepted, so check what the agent sent rather than the client wiring. This warning is development-only.`);
2077
+ }
2078
+ }
2079
+ /**
2080
+ * Names the component ids a surface was sent, for a warning about the one id
2081
+ * that is missing.
2082
+ *
2083
+ * @param operations - The operations grouped under one surface.
2084
+ * @returns A quoted, comma-separated list of ids, or "none".
2085
+ */
2086
+ function describeComponentIds(operations) {
2087
+ const ids = [];
2088
+ for (const operation of operations) {
2089
+ const components = operation?.updateComponents?.components;
2090
+ if (!Array.isArray(components)) continue;
2091
+ for (const component of components) {
2092
+ const id = component?.id;
2093
+ if (typeof id === "string" && !ids.includes(id)) ids.push(id);
2094
+ }
2095
+ }
2096
+ return ids.length === 0 ? "none" : ids.map((id) => `"${id}"`).join(", ");
2097
+ }
2098
+ /**
2099
+ * Reports a surface that is still waiting for its {@link ROOT_COMPONENT_ID}
2100
+ * component once its operations have stopped arriving.
2101
+ *
2102
+ * Both renderers begin walking a surface at that one id, and treat an id they
2103
+ * cannot find as not arrived yet — an animated placeholder. That is right while
2104
+ * operations stream. Once they have stopped it is not waiting, it is stuck, and
2105
+ * every other check calls it healthy: the surface exists, `processMessages` did
2106
+ * not throw, the component types were never reached, and
2107
+ * `surfaceHasRenderableContent` says yes on the strength of components plus a
2108
+ * data model, so `onReady` fires and the never-painted report is suppressed.
2109
+ * A complete, accepted payload therefore animates a grey box forever in silence.
2110
+ *
2111
+ * Reads the live components model rather than scanning the operations for the
2112
+ * id, so it covers every way the root can fail to resolve — not only a payload
2113
+ * that never named one.
2114
+ *
2115
+ * @param surfaceId - The surface the operations were addressed to.
2116
+ * @param operations - The operations processed for that surface.
2117
+ * @param surface - The live surface model, already known to exist.
2118
+ */
2119
+ function warnAboutUnresolvedRoot(surfaceId, operations, surface) {
2120
+ if (surface?.componentsModel?.get?.(_copilotkit_a2ui_renderer.ROOT_COMPONENT_ID)) return;
2121
+ const componentOps = operations.filter((o) => o?.updateComponents);
2122
+ if (componentOps.length === 0) return;
2123
+ const cause = componentOps.some((o) => o.updateComponents?.components?.some?.((c) => c?.id === _copilotkit_a2ui_renderer.ROOT_COMPONENT_ID)) ? `a component with id "${_copilotkit_a2ui_renderer.ROOT_COMPONENT_ID}" WAS sent, so the components did not reach the surface's model — check for an A2UI render error above, or a catalog that took none of them` : `the components it received are named ${describeComponentIds(operations)}, and none of them is "${_copilotkit_a2ui_renderer.ROOT_COMPONENT_ID}" — rename the entry-point component to "${_copilotkit_a2ui_renderer.ROOT_COMPONENT_ID}", and make every other component reachable from it through child/children`;
2124
+ console.warn(`[CopilotKit] A2UI surface "${surfaceId}" has no "${_copilotkit_a2ui_renderer.ROOT_COMPONENT_ID}" component ${String(PAINT_FALLBACK_MS)}ms after its last operations were processed, so it is showing the placeholder for a component that has not arrived yet and will keep showing it: ${cause}. Operations received: ${describeOperationKinds(operations)}. This warning is development-only.`);
2125
+ }
1981
2126
  function createA2UIMessageRenderer(options) {
1982
2127
  const { theme, catalog, loadingComponent, recovery, onAction } = options;
1983
2128
  const showAfterMs = recovery?.showAfterMs ?? 2e3;
@@ -2011,6 +2156,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2011
2156
  return groups;
2012
2157
  }, [operations]);
2013
2158
  const hasOps = groupedOperations.size > 0;
2159
+ const groupedOperationsRef = (0, react.useRef)(groupedOperations);
2160
+ groupedOperationsRef.current = groupedOperations;
2014
2161
  const renderLifecycle = (c) => {
2015
2162
  const status = c?.status;
2016
2163
  const debugExposure = resolveDebugExposure(c, optionDebugExposure);
@@ -2042,7 +2189,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2042
2189
  readyRef.current = false;
2043
2190
  return;
2044
2191
  }
2045
- const t = setTimeout(() => setSurfaceReady(true), 8e3);
2192
+ const t = setTimeout(() => {
2193
+ setSurfaceReady(true);
2194
+ if (IS_DEVELOPMENT && !readyRef.current) warnAboutUnpaintedSurfaces(groupedOperationsRef.current);
2195
+ }, PAINT_FALLBACK_MS);
2046
2196
  return () => clearTimeout(t);
2047
2197
  }, [hasOps]);
2048
2198
  if (!hasOps) return renderLifecycle(content);
@@ -2166,8 +2316,21 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2166
2316
  const hash = JSON.stringify(operations);
2167
2317
  if (hash === lastHashRef.current) return;
2168
2318
  lastHashRef.current = hash;
2169
- processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
2319
+ const ops = getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations;
2320
+ processMessages(ops);
2170
2321
  if (onReady && surfaceHasRenderableContent(operations)) onReady();
2322
+ if (!IS_DEVELOPMENT) return;
2323
+ if (!getSurface(surfaceId)) {
2324
+ const missingSurfaceCheck = setTimeout(() => {
2325
+ if (getSurface(surfaceId)) return;
2326
+ console.warn(`[CopilotKit] A2UI processed ${String(ops.length)} operation(s) addressed to surface "${surfaceId}" and no surface by that id exists, so this card rendered nothing. Operations received: ${describeOperationKinds(ops)}. A createSurface for "${surfaceId}" has to arrive before, or with, the operations that target it. This warning is development-only.`);
2327
+ }, 0);
2328
+ return () => clearTimeout(missingSurfaceCheck);
2329
+ }
2330
+ const unresolvedRootCheck = setTimeout(() => {
2331
+ warnAboutUnresolvedRoot(surfaceId, operations, getSurface(surfaceId));
2332
+ }, PAINT_FALLBACK_MS);
2333
+ return () => clearTimeout(unresolvedRootCheck);
2171
2334
  }, [
2172
2335
  processMessages,
2173
2336
  getSurface,
@@ -2194,10 +2357,26 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2194
2357
  return Object.values(v).some((x) => Array.isArray(x) ? x.length > 0 : x !== null && x !== void 0 && x !== "");
2195
2358
  });
2196
2359
  }
2360
+ /**
2361
+ * Resolves the surface an operation addresses.
2362
+ *
2363
+ * The nested v0.9 `surfaceId` wins, because that is the id `MessageProcessor`
2364
+ * creates the surface under. Grouping by a top-level `surfaceId` instead files
2365
+ * the operations against a surface that never exists, which paints nothing —
2366
+ * the silence the missing-surface report above now names. A top-level
2367
+ * `surfaceId` is not the v0.9 shape, so it is honoured only when no nested id is
2368
+ * present. `getSurfaceId` in `@copilotkit/a2ui-renderer`'s web-components path
2369
+ * resolves it in the same order; the two disagreeing is what OSS-1048 recorded.
2370
+ *
2371
+ * @param operation - One A2UI operation, of any shape.
2372
+ * @returns The surface id, or null when the operation names none.
2373
+ */
2197
2374
  function getOperationSurfaceId(operation) {
2198
2375
  if (!operation || typeof operation !== "object") return null;
2376
+ const nested = operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId;
2377
+ if (typeof nested === "string") return nested;
2199
2378
  if (typeof operation.surfaceId === "string") return operation.surfaceId;
2200
- return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
2379
+ return null;
2201
2380
  }
2202
2381
 
2203
2382
  //#endregion
@@ -2315,6 +2494,9 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2315
2494
  const EMPTY_HEADERS = Object.freeze({});
2316
2495
  const EMPTY_PROPERTIES = Object.freeze({});
2317
2496
  const EMPTY_AGENTS = Object.freeze({});
2497
+ /** Registration name of a catch-all tool: handles any otherwise-unhandled call. */
2498
+ const WILDCARD_TOOL_NAME$1 = "*";
2499
+ const HUMAN_IN_THE_LOOP_ABORTED_MESSAGE = "Human-in-the-loop interaction aborted";
2318
2500
  const DEFAULT_DESIGN_SKILL = `When generating UI with generateSandboxedUi, follow these design principles inspired by shadcn/ui:
2319
2501
 
2320
2502
  - Use a minimal, flat aesthetic. Avoid drop shadows and gradients — rely on subtle borders (1px solid, light gray like #e5e7eb) to define surfaces.
@@ -2335,14 +2517,19 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2335
2517
  }, [value, warningMessage]);
2336
2518
  return value;
2337
2519
  }
2338
- const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY_HEADERS, credentials, publicApiKey, publicLicenseKey, licenseToken, properties = EMPTY_PROPERTIES, agents__unsafe_dev_only: agents = EMPTY_AGENTS, selfManagedAgents = EMPTY_AGENTS, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, openGenerativeUI, enableInspector, useSingleEndpoint, onError, a2ui, defaultThrottleMs, debug }) => {
2520
+ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY_HEADERS, credentials, publicApiKey, publicLicenseKey, licenseToken, properties = EMPTY_PROPERTIES, agents__unsafe_dev_only: agents = EMPTY_AGENTS, selfManagedAgents = EMPTY_AGENTS, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, openGenerativeUI, enableInspector, agentId, useSingleEndpoint, onError, a2ui, defaultThrottleMs, debug }) => {
2339
2521
  const [shouldRenderInspector, setShouldRenderInspector] = (0, react.useState)(false);
2522
+ const [inspectorVisible, setInspectorVisible] = (0, react.useState)(false);
2340
2523
  (0, react.useEffect)(() => {
2341
2524
  setShouldRenderInspector((0, _copilotkit_shared.shouldEnableInspector)({
2342
2525
  enableInspector,
2343
2526
  isBrowser: true,
2344
2527
  isDevelopment: process.env.NODE_ENV === "development"
2345
- }));
2528
+ }) && [
2529
+ "localhost",
2530
+ "127.0.0.1",
2531
+ "[::1]"
2532
+ ].includes(window.location.hostname));
2346
2533
  }, [enableInspector]);
2347
2534
  const [inspectorOpenRequest, setInspectorOpenRequest] = (0, react.useState)(null);
2348
2535
  const [runtimeA2UIEnabled, setRuntimeA2UIEnabled] = (0, react.useState)(false);
@@ -2358,9 +2545,15 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2358
2545
  setInspectorOpenRequest({ ...request });
2359
2546
  }, []);
2360
2547
  const inspectorContextValue = (0, react.useMemo)(() => ({
2361
- isInspectorEnabled: shouldRenderInspector,
2548
+ providerEnableInspector: enableInspector,
2549
+ isInspectorEnabled: shouldRenderInspector && inspectorVisible,
2362
2550
  openInspector: requestInspectorOpen
2363
- }), [shouldRenderInspector, requestInspectorOpen]);
2551
+ }), [
2552
+ enableInspector,
2553
+ shouldRenderInspector,
2554
+ inspectorVisible,
2555
+ requestInspectorOpen
2556
+ ]);
2364
2557
  const renderToolCallsList = useStableArrayProp(renderToolCalls, "renderToolCalls must be a stable array. If you want to dynamically add or remove tools, use `useFrontendTool` instead.", (initial, next) => {
2365
2558
  const key = (rc) => `${rc?.agentId ?? ""}:${rc?.name ?? ""}`;
2366
2559
  const setFrom = (arr) => new Set(arr.map(key));
@@ -2433,6 +2626,11 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2433
2626
  }
2434
2627
  const chatApiEndpoint = runtimeUrl ?? (resolvedPublicKey ? COPILOT_CLOUD_CHAT_URL$1 : void 0);
2435
2628
  const frontendToolsList = useStableArrayProp(frontendTools, "frontendTools must be a stable array. If you want to dynamically add or remove tools, use `useFrontendTool` instead.");
2629
+ /**
2630
+ * A `humanInTheLoop` prop tool call that is parked, waiting on the user.
2631
+ * Keyed by tool call id, so parallel calls of one tool stay independent.
2632
+ */
2633
+ const pendingHumanInTheLoopRef = (0, react.useRef)(/* @__PURE__ */ new Map());
2436
2634
  const humanInTheLoopList = useStableArrayProp(humanInTheLoop, "humanInTheLoop must be a stable array. If you want to dynamically add or remove human-in-the-loop tools, use `useHumanInTheLoop` instead.");
2437
2635
  const sandboxFunctionsList = useStableArrayProp(openGenerativeUI?.sandboxFunctions, "openGenerativeUI.sandboxFunctions must be a stable array.");
2438
2636
  const processedHumanInTheLoopTools = (0, react.useMemo)(() => {
@@ -2445,20 +2643,52 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2445
2643
  parameters: tool.parameters,
2446
2644
  followUp: tool.followUp,
2447
2645
  ...tool.agentId && { agentId: tool.agentId },
2448
- handler: async () => {
2449
- return new Promise((resolve) => {
2450
- console.warn(`Human-in-the-loop tool '${tool.name}' called but no interactive handler is set up.`);
2451
- resolve(void 0);
2646
+ handler: async (_args, context) => {
2647
+ const signal = context?.signal;
2648
+ const key = context?.toolCall?.id ?? tool.name;
2649
+ return new Promise((resolve, reject) => {
2650
+ if (signal?.aborted) {
2651
+ reject(new Error(HUMAN_IN_THE_LOOP_ABORTED_MESSAGE));
2652
+ return;
2653
+ }
2654
+ const pending = { resolve };
2655
+ pendingHumanInTheLoopRef.current.set(key, pending);
2656
+ if (signal) {
2657
+ const onAbort = () => {
2658
+ pendingHumanInTheLoopRef.current.delete(key);
2659
+ reject(new Error(HUMAN_IN_THE_LOOP_ABORTED_MESSAGE));
2660
+ };
2661
+ signal.addEventListener("abort", onAbort, { once: true });
2662
+ pending.detachAbort = () => {
2663
+ signal.removeEventListener("abort", onAbort);
2664
+ };
2665
+ }
2452
2666
  });
2453
2667
  }
2454
2668
  };
2455
2669
  processedTools.push(frontendTool);
2456
- if (tool.render) processedRenderToolCalls.push({
2457
- name: tool.name,
2458
- args: tool.parameters,
2459
- render: tool.render,
2460
- ...tool.agentId && { agentId: tool.agentId }
2461
- });
2670
+ if (tool.render) {
2671
+ const ToolComponent = tool.render;
2672
+ const RenderComponent = (props) => (0, react.createElement)(ToolComponent, {
2673
+ ...props,
2674
+ name: tool.name === WILDCARD_TOOL_NAME$1 ? props.name : tool.name,
2675
+ description: tool.description || "",
2676
+ agentId: tool.agentId,
2677
+ respond: props.status === _copilotkit_core.ToolCallStatus.Executing ? async (result) => {
2678
+ const pending = pendingHumanInTheLoopRef.current.get(props.toolCallId);
2679
+ if (!pending) return;
2680
+ pending.detachAbort?.();
2681
+ pendingHumanInTheLoopRef.current.delete(props.toolCallId);
2682
+ pending.resolve(result);
2683
+ } : void 0
2684
+ });
2685
+ processedRenderToolCalls.push({
2686
+ name: tool.name,
2687
+ args: tool.parameters,
2688
+ render: RenderComponent,
2689
+ ...tool.agentId && { agentId: tool.agentId }
2690
+ });
2691
+ }
2462
2692
  });
2463
2693
  return {
2464
2694
  tools: processedTools,
@@ -2720,9 +2950,13 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2720
2950
  }),
2721
2951
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CopilotKitInspectorContextProvider, {
2722
2952
  value: inspectorContextValue,
2723
- children: [children, shouldRenderInspector ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitInspector, {
2953
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitAgentIdContext.Provider, {
2954
+ value: agentId,
2955
+ children
2956
+ }), shouldRenderInspector ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitInspector, {
2724
2957
  core: copilotkit,
2725
- openRequest: inspectorOpenRequest
2958
+ openRequest: inspectorOpenRequest,
2959
+ onVisibilityChange: setInspectorVisible
2726
2960
  }) : null]
2727
2961
  }),
2728
2962
  runtimeLicenseWarningStatus === "none" && !resolvedPublicKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LicenseWarningBanner, { type: "no_license" }),
@@ -2924,7 +3158,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2924
3158
  if (runtimeAgentId != null && threadId == null) throw new Error(`useAgent: \`runtimeAgentId\` requires \`threadId\`. A proxied agent exists to scope a thread to a private instance; without a threadId it behaves like the shared agent while adding a registration and a local agentId to keep unique. Either pass the thread, e.g. useAgent({ agentId: "${agentId ?? "chat-1"}", runtimeAgentId: "${runtimeAgentId}", threadId }), or bind to the agent directly with useAgent({ agentId: "${runtimeAgentId}" }).`);
2925
3159
  if (runtimeAgentId != null && agentId == null) throw new Error(`useAgent: \`runtimeAgentId\` requires an explicit \`agentId\`. The proxied agent is registered under \`agentId\`, and the usual fallbacks (chat configuration, then "${_copilotkit_shared.DEFAULT_AGENT_ID}") name agents that already exist — registering over one throws or shadows it. Pick a local id for this hook, e.g. useAgent({ agentId: "chat-1", runtimeAgentId: "${runtimeAgentId}", threadId }).`);
2926
3160
  const chatConfig = useCopilotChatConfiguration();
2927
- const resolvedAgentId = agentId ?? chatConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
3161
+ const providerAgentId = useDefaultAgentId();
3162
+ const resolvedAgentId = agentId ?? chatConfig?.agentId ?? providerAgentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
2928
3163
  const { copilotkit } = useCopilotKit();
2929
3164
  const providerThrottleMs = copilotkit.defaultThrottleMs;
2930
3165
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
@@ -3107,7 +3342,12 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3107
3342
  function useSuggestions({ agentId } = {}) {
3108
3343
  const { copilotkit } = useCopilotKit();
3109
3344
  const config = useCopilotChatConfiguration();
3110
- const resolvedAgentId = (0, react.useMemo)(() => agentId ?? config?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID, [agentId, config?.agentId]);
3345
+ const providerAgentId = useDefaultAgentId();
3346
+ const resolvedAgentId = (0, react.useMemo)(() => agentId ?? config?.agentId ?? providerAgentId ?? _copilotkit_shared.DEFAULT_AGENT_ID, [
3347
+ agentId,
3348
+ config?.agentId,
3349
+ providerAgentId
3350
+ ]);
3111
3351
  const [suggestions, setSuggestions] = (0, react.useState)(() => {
3112
3352
  return copilotkit.getSuggestions(resolvedAgentId).suggestions;
3113
3353
  });
@@ -6769,7 +7009,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6769
7009
  *
6770
7010
  * ### Example
6771
7011
  * A great example of this is in our Perplexity Clone where we render the progress of an agent's internet search as it is happening.
6772
- * You can play around with it below or learn how to build it with its [demo](/coagents/videos/perplexity-clone).
7012
+ * You can play around with it below or learn how to build it with the [state rendering guide](/generative-ui/state-rendering).
6773
7013
  *
6774
7014
  * <Callout type="info">
6775
7015
  * This example is hosted on Vercel and may take a few seconds to load.
@@ -6957,7 +7197,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6957
7197
  function useCopilotReadable({ description, value, convert, available = "enabled" }, dependencies) {
6958
7198
  const { copilotkit } = useCopilotKit();
6959
7199
  const ctxIdRef = (0, react.useRef)(void 0);
6960
- (0, react.useEffect)(() => {
7200
+ (0, react.useLayoutEffect)(() => {
6961
7201
  if (!copilotkit) return;
6962
7202
  if (available === "disabled") return;
6963
7203
  const id = copilotkit.addContext({