@copilotkit/react-core 1.70.1 → 1.70.3

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 (36) hide show
  1. package/dist/{copilotkit-DiUK2Bhq.mjs → copilotkit-Ap_yisA5.mjs} +470 -98
  2. package/dist/copilotkit-Ap_yisA5.mjs.map +1 -0
  3. package/dist/{copilotkit-CxLT6zFx.cjs → copilotkit-BU3OvveB.cjs} +468 -96
  4. package/dist/copilotkit-BU3OvveB.cjs.map +1 -0
  5. package/dist/{copilotkit-DsWuxPUQ.d.mts → copilotkit-Bs98akp9.d.mts} +32 -2
  6. package/dist/{copilotkit-DsWuxPUQ.d.mts.map → copilotkit-Bs98akp9.d.mts.map} +1 -1
  7. package/dist/{copilotkit-CtF2clxZ.d.cts → copilotkit-X2eGbOwf.d.cts} +32 -2
  8. package/dist/{copilotkit-CtF2clxZ.d.cts.map → copilotkit-X2eGbOwf.d.cts.map} +1 -1
  9. package/dist/index.cjs +1 -1
  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 +1 -1
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/index.umd.js +273 -15
  18. package/dist/index.umd.js.map +1 -1
  19. package/dist/v2/headless.cjs +97 -2
  20. package/dist/v2/headless.cjs.map +1 -1
  21. package/dist/v2/headless.d.cts.map +1 -1
  22. package/dist/v2/headless.d.mts.map +1 -1
  23. package/dist/v2/headless.mjs +98 -3
  24. package/dist/v2/headless.mjs.map +1 -1
  25. package/dist/v2/index.cjs +1 -1
  26. package/dist/v2/index.css +1 -1
  27. package/dist/v2/index.d.cts +1 -1
  28. package/dist/v2/index.d.mts +1 -1
  29. package/dist/v2/index.mjs +1 -1
  30. package/dist/v2/index.umd.js +468 -96
  31. package/dist/v2/index.umd.js.map +1 -1
  32. package/package.json +7 -7
  33. package/skills/react-core/SKILL.md +1 -1
  34. package/skills/react-core/references/attachments.md +20 -0
  35. package/dist/copilotkit-CxLT6zFx.cjs.map +0 -1
  36. package/dist/copilotkit-DiUK2Bhq.mjs.map +0 -1
package/dist/index.umd.js CHANGED
@@ -282,6 +282,62 @@ react_markdown = __toESM(react_markdown);
282
282
  const useCopilotChatConfiguration = () => {
283
283
  return (0, react.useContext)(CopilotChatConfiguration);
284
284
  };
285
+ /**
286
+ * Reports modal open/close requests to the host, and — when `open` is
287
+ * supplied — makes the modal state of an already-established chat
288
+ * configuration **controlled** for the subtree it wraps.
289
+ *
290
+ * This is deliberately a scope component rather than another mode inside
291
+ * {@link CopilotChatConfigurationProvider}. The provider resolves modal state
292
+ * across a nested chain (own state, parent sync, drawer mutual-exclusion, the
293
+ * modal-closer registry); a controlled branch inside that resolution would add
294
+ * a fourth interacting mode. Overriding the context for the subtree instead
295
+ * leaves every one of those paths untouched:
296
+ *
297
+ * - `isModalOpen` is replaced with the host's `open`, so the rendered surface
298
+ * follows the prop from the very first frame (no open-then-close flash).
299
+ * - `setModalOpen` still calls the underlying setter, so the existing
300
+ * parent-sync and drawer mutual-exclusion side effects continue to run, and
301
+ * *then* reports the request through `onOpenChange`.
302
+ * - The wrapped setter is registered as the modal closer, so the drawer's
303
+ * mobile mutual-exclusion reaches the host instead of silently flipping
304
+ * state that nothing displays.
305
+ *
306
+ * A host that supplies `open` and ignores `onOpenChange` gets a modal pinned
307
+ * to `open`, which is the standard controlled-component contract. A host that
308
+ * supplies only `onOpenChange` is notified while the modal keeps managing
309
+ * itself.
310
+ *
311
+ * Renders `children` unchanged when no chat configuration is in scope.
312
+ */
313
+ const ControlledModalOpenScope = ({ children, open, onOpenChange }) => {
314
+ const parentConfig = (0, react.useContext)(CopilotChatConfiguration);
315
+ const parentSetModalOpen = parentConfig?.setModalOpen;
316
+ const registerModalCloser = parentConfig?.ɵregisterModalCloser;
317
+ const setModalOpen = (0, react.useCallback)((next) => {
318
+ parentSetModalOpen?.(next);
319
+ onOpenChange?.(next);
320
+ }, [parentSetModalOpen, onOpenChange]);
321
+ (0, react.useEffect)(() => {
322
+ if (!registerModalCloser) return;
323
+ return registerModalCloser(setModalOpen);
324
+ }, [registerModalCloser, setModalOpen]);
325
+ const configurationValue = (0, react.useMemo)(() => parentConfig ? {
326
+ ...parentConfig,
327
+ isModalOpen: open ?? parentConfig.isModalOpen,
328
+ setModalOpen
329
+ } : null, [
330
+ parentConfig,
331
+ open,
332
+ setModalOpen
333
+ ]);
334
+ if (!configurationValue) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children });
335
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfiguration.Provider, {
336
+ value: configurationValue,
337
+ children
338
+ });
339
+ };
340
+ ControlledModalOpenScope.displayName = "ControlledModalOpenScope";
285
341
 
286
342
  //#endregion
287
343
  //#region src/v2/lib/react-core.ts
@@ -448,6 +504,29 @@ react_markdown = __toESM(react_markdown);
448
504
  if (prevProps.RenderComponent !== nextProps.RenderComponent) return false;
449
505
  return true;
450
506
  });
507
+ const IS_DEVELOPMENT$1 = process.env.NODE_ENV !== "production";
508
+ /**
509
+ * Reports tool calls that resolved to no renderer.
510
+ *
511
+ * A tool call with no renderer paints an empty message container and says
512
+ * nothing else, so the only signal a developer gets today is a blank space in
513
+ * the chat. This names the call and the renderers that *are* registered, which
514
+ * is the whole diagnosis when the cause is a name that does not match.
515
+ *
516
+ * @param toolNames - The unmatched tool names collected during render.
517
+ * @param registered - The registry as it stands now, re-read at report time.
518
+ * @param alreadyWarned - Names already reported, mutated to keep this once per name.
519
+ */
520
+ function warnAboutUnrenderedToolCalls(toolNames, registered, alreadyWarned) {
521
+ const registeredNames = Array.from(new Set(registered.map((rc) => rc.name)));
522
+ const hasWildcard = registeredNames.includes("*");
523
+ for (const toolName of toolNames) {
524
+ if (hasWildcard || registeredNames.includes(toolName)) continue;
525
+ if (alreadyWarned.has(toolName)) continue;
526
+ alreadyWarned.add(toolName);
527
+ 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.`);
528
+ }
529
+ }
451
530
  /**
452
531
  * Hook that returns a function to render tool calls based on the render functions
453
532
  * defined in CopilotKitProvider.
@@ -457,13 +536,18 @@ react_markdown = __toESM(react_markdown);
457
536
  function useRenderToolCall$1() {
458
537
  const { copilotkit, executingToolCallIds } = useCopilotKit();
459
538
  const agentId = useCopilotChatConfiguration()?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
539
+ const unrenderedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
540
+ const warnedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
460
541
  const renderToolCalls = (0, react.useSyncExternalStore)((callback) => {
461
542
  return copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe;
462
543
  }, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
463
- return (0, react.useCallback)(({ toolCall, toolMessage }) => {
544
+ const renderToolCall = (0, react.useCallback)(({ toolCall, toolMessage }) => {
464
545
  const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
465
546
  const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*");
466
- if (!renderConfig) return null;
547
+ if (!renderConfig) {
548
+ if (IS_DEVELOPMENT$1) unrenderedToolNames.current.add(toolCall.function.name);
549
+ return null;
550
+ }
467
551
  const RenderComponent = renderConfig.render;
468
552
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallRenderer, {
469
553
  toolCall,
@@ -476,6 +560,17 @@ react_markdown = __toESM(react_markdown);
476
560
  executingToolCallIds,
477
561
  agentId
478
562
  ]);
563
+ (0, react.useEffect)(() => {
564
+ if (!IS_DEVELOPMENT$1) return;
565
+ if (unrenderedToolNames.current.size === 0) return;
566
+ const timer = setTimeout(() => {
567
+ const pending = Array.from(unrenderedToolNames.current);
568
+ unrenderedToolNames.current.clear();
569
+ warnAboutUnrenderedToolCalls(pending, copilotkit.renderToolCalls, warnedToolNames.current);
570
+ }, 0);
571
+ return () => clearTimeout(timer);
572
+ });
573
+ return renderToolCall;
479
574
  }
480
575
 
481
576
  //#endregion
@@ -1922,6 +2017,95 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
1922
2017
  a2ui_operations: zod.z.array(zod.z.any()).optional(),
1923
2018
  ...A2UILifecycleFields
1924
2019
  }).passthrough();
2020
+ const IS_DEVELOPMENT = process.env.NODE_ENV !== "production";
2021
+ /**
2022
+ * How long to wait for a surface to report its first paint before giving up on
2023
+ * the loader cross-over. Reaching it also means `onReady` never fired, which is
2024
+ * the signal the warning below reports.
2025
+ */
2026
+ const PAINT_FALLBACK_MS = 8e3;
2027
+ /**
2028
+ * Names the operation kinds a surface received, for a warning that has to say
2029
+ * what did arrive as well as what did not.
2030
+ *
2031
+ * @param operations - The operations grouped under one surface.
2032
+ * @returns A comma-separated list of operation keys, or "none".
2033
+ */
2034
+ function describeOperationKinds(operations) {
2035
+ const kinds = /* @__PURE__ */ new Set();
2036
+ for (const operation of operations) {
2037
+ if (!operation || typeof operation !== "object") continue;
2038
+ for (const key of Object.keys(operation)) if (key !== "version") kinds.add(key);
2039
+ }
2040
+ return kinds.size === 0 ? "none" : Array.from(kinds).join(", ");
2041
+ }
2042
+ /**
2043
+ * Reports surfaces that received operations and never painted.
2044
+ *
2045
+ * Reaching {@link PAINT_FALLBACK_MS} with no `onReady` means the operations were
2046
+ * accepted, were not malformed enough to raise the provider's error state, and
2047
+ * still put nothing on screen. Left alone that is invisible: the loader drops,
2048
+ * the turn finishes, and the only trace is an empty placeholder above the reply.
2049
+ *
2050
+ * `surfaceHasRenderableContent` already knows which half is missing, so the
2051
+ * warning says which rather than making the reader re-derive it.
2052
+ *
2053
+ * @param grouped - Operations grouped by surface id.
2054
+ */
2055
+ function warnAboutUnpaintedSurfaces(grouped) {
2056
+ for (const [surfaceId, operations] of grouped) {
2057
+ if (surfaceHasRenderableContent(operations)) continue;
2058
+ 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";
2059
+ 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.`);
2060
+ }
2061
+ }
2062
+ /**
2063
+ * Names the component ids a surface was sent, for a warning about the one id
2064
+ * that is missing.
2065
+ *
2066
+ * @param operations - The operations grouped under one surface.
2067
+ * @returns A quoted, comma-separated list of ids, or "none".
2068
+ */
2069
+ function describeComponentIds(operations) {
2070
+ const ids = [];
2071
+ for (const operation of operations) {
2072
+ const components = operation?.updateComponents?.components;
2073
+ if (!Array.isArray(components)) continue;
2074
+ for (const component of components) {
2075
+ const id = component?.id;
2076
+ if (typeof id === "string" && !ids.includes(id)) ids.push(id);
2077
+ }
2078
+ }
2079
+ return ids.length === 0 ? "none" : ids.map((id) => `"${id}"`).join(", ");
2080
+ }
2081
+ /**
2082
+ * Reports a surface that is still waiting for its {@link ROOT_COMPONENT_ID}
2083
+ * component once its operations have stopped arriving.
2084
+ *
2085
+ * Both renderers begin walking a surface at that one id, and treat an id they
2086
+ * cannot find as not arrived yet — an animated placeholder. That is right while
2087
+ * operations stream. Once they have stopped it is not waiting, it is stuck, and
2088
+ * every other check calls it healthy: the surface exists, `processMessages` did
2089
+ * not throw, the component types were never reached, and
2090
+ * `surfaceHasRenderableContent` says yes on the strength of components plus a
2091
+ * data model, so `onReady` fires and the never-painted report is suppressed.
2092
+ * A complete, accepted payload therefore animates a grey box forever in silence.
2093
+ *
2094
+ * Reads the live components model rather than scanning the operations for the
2095
+ * id, so it covers every way the root can fail to resolve — not only a payload
2096
+ * that never named one.
2097
+ *
2098
+ * @param surfaceId - The surface the operations were addressed to.
2099
+ * @param operations - The operations processed for that surface.
2100
+ * @param surface - The live surface model, already known to exist.
2101
+ */
2102
+ function warnAboutUnresolvedRoot(surfaceId, operations, surface) {
2103
+ if (surface?.componentsModel?.get?.(_copilotkit_a2ui_renderer.ROOT_COMPONENT_ID)) return;
2104
+ const componentOps = operations.filter((o) => o?.updateComponents);
2105
+ if (componentOps.length === 0) return;
2106
+ 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`;
2107
+ 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.`);
2108
+ }
1925
2109
  function createA2UIMessageRenderer(options) {
1926
2110
  const { theme, catalog, loadingComponent, recovery, onAction } = options;
1927
2111
  const showAfterMs = recovery?.showAfterMs ?? 2e3;
@@ -1955,6 +2139,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
1955
2139
  return groups;
1956
2140
  }, [operations]);
1957
2141
  const hasOps = groupedOperations.size > 0;
2142
+ const groupedOperationsRef = (0, react.useRef)(groupedOperations);
2143
+ groupedOperationsRef.current = groupedOperations;
1958
2144
  const renderLifecycle = (c) => {
1959
2145
  const status = c?.status;
1960
2146
  const debugExposure = resolveDebugExposure(c, optionDebugExposure);
@@ -1986,7 +2172,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
1986
2172
  readyRef.current = false;
1987
2173
  return;
1988
2174
  }
1989
- const t = setTimeout(() => setSurfaceReady(true), 8e3);
2175
+ const t = setTimeout(() => {
2176
+ setSurfaceReady(true);
2177
+ if (IS_DEVELOPMENT && !readyRef.current) warnAboutUnpaintedSurfaces(groupedOperationsRef.current);
2178
+ }, PAINT_FALLBACK_MS);
1990
2179
  return () => clearTimeout(t);
1991
2180
  }, [hasOps]);
1992
2181
  if (!hasOps) return renderLifecycle(content);
@@ -2110,8 +2299,21 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2110
2299
  const hash = JSON.stringify(operations);
2111
2300
  if (hash === lastHashRef.current) return;
2112
2301
  lastHashRef.current = hash;
2113
- processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
2302
+ const ops = getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations;
2303
+ processMessages(ops);
2114
2304
  if (onReady && surfaceHasRenderableContent(operations)) onReady();
2305
+ if (!IS_DEVELOPMENT) return;
2306
+ if (!getSurface(surfaceId)) {
2307
+ const missingSurfaceCheck = setTimeout(() => {
2308
+ if (getSurface(surfaceId)) return;
2309
+ 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.`);
2310
+ }, 0);
2311
+ return () => clearTimeout(missingSurfaceCheck);
2312
+ }
2313
+ const unresolvedRootCheck = setTimeout(() => {
2314
+ warnAboutUnresolvedRoot(surfaceId, operations, getSurface(surfaceId));
2315
+ }, PAINT_FALLBACK_MS);
2316
+ return () => clearTimeout(unresolvedRootCheck);
2115
2317
  }, [
2116
2318
  processMessages,
2117
2319
  getSurface,
@@ -2138,10 +2340,26 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2138
2340
  return Object.values(v).some((x) => Array.isArray(x) ? x.length > 0 : x !== null && x !== void 0 && x !== "");
2139
2341
  });
2140
2342
  }
2343
+ /**
2344
+ * Resolves the surface an operation addresses.
2345
+ *
2346
+ * The nested v0.9 `surfaceId` wins, because that is the id `MessageProcessor`
2347
+ * creates the surface under. Grouping by a top-level `surfaceId` instead files
2348
+ * the operations against a surface that never exists, which paints nothing —
2349
+ * the silence the missing-surface report above now names. A top-level
2350
+ * `surfaceId` is not the v0.9 shape, so it is honoured only when no nested id is
2351
+ * present. `getSurfaceId` in `@copilotkit/a2ui-renderer`'s web-components path
2352
+ * resolves it in the same order; the two disagreeing is what OSS-1048 recorded.
2353
+ *
2354
+ * @param operation - One A2UI operation, of any shape.
2355
+ * @returns The surface id, or null when the operation names none.
2356
+ */
2141
2357
  function getOperationSurfaceId(operation) {
2142
2358
  if (!operation || typeof operation !== "object") return null;
2359
+ const nested = operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId;
2360
+ if (typeof nested === "string") return nested;
2143
2361
  if (typeof operation.surfaceId === "string") return operation.surfaceId;
2144
- return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
2362
+ return null;
2145
2363
  }
2146
2364
 
2147
2365
  //#endregion
@@ -2259,6 +2477,9 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2259
2477
  const EMPTY_HEADERS = Object.freeze({});
2260
2478
  const EMPTY_PROPERTIES = Object.freeze({});
2261
2479
  const EMPTY_AGENTS = Object.freeze({});
2480
+ /** Registration name of a catch-all tool: handles any otherwise-unhandled call. */
2481
+ const WILDCARD_TOOL_NAME$1 = "*";
2482
+ const HUMAN_IN_THE_LOOP_ABORTED_MESSAGE = "Human-in-the-loop interaction aborted";
2262
2483
  const DEFAULT_DESIGN_SKILL = `When generating UI with generateSandboxedUi, follow these design principles inspired by shadcn/ui:
2263
2484
 
2264
2485
  - Use a minimal, flat aesthetic. Avoid drop shadows and gradients — rely on subtle borders (1px solid, light gray like #e5e7eb) to define surfaces.
@@ -2377,6 +2598,11 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2377
2598
  }
2378
2599
  const chatApiEndpoint = runtimeUrl ?? (resolvedPublicKey ? COPILOT_CLOUD_CHAT_URL$1 : void 0);
2379
2600
  const frontendToolsList = useStableArrayProp(frontendTools, "frontendTools must be a stable array. If you want to dynamically add or remove tools, use `useFrontendTool` instead.");
2601
+ /**
2602
+ * A `humanInTheLoop` prop tool call that is parked, waiting on the user.
2603
+ * Keyed by tool call id, so parallel calls of one tool stay independent.
2604
+ */
2605
+ const pendingHumanInTheLoopRef = (0, react.useRef)(/* @__PURE__ */ new Map());
2380
2606
  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.");
2381
2607
  const sandboxFunctionsList = useStableArrayProp(openGenerativeUI?.sandboxFunctions, "openGenerativeUI.sandboxFunctions must be a stable array.");
2382
2608
  const processedHumanInTheLoopTools = (0, react.useMemo)(() => {
@@ -2389,20 +2615,52 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
2389
2615
  parameters: tool.parameters,
2390
2616
  followUp: tool.followUp,
2391
2617
  ...tool.agentId && { agentId: tool.agentId },
2392
- handler: async () => {
2393
- return new Promise((resolve) => {
2394
- console.warn(`Human-in-the-loop tool '${tool.name}' called but no interactive handler is set up.`);
2395
- resolve(void 0);
2618
+ handler: async (_args, context) => {
2619
+ const signal = context?.signal;
2620
+ const key = context?.toolCall?.id ?? tool.name;
2621
+ return new Promise((resolve, reject) => {
2622
+ if (signal?.aborted) {
2623
+ reject(new Error(HUMAN_IN_THE_LOOP_ABORTED_MESSAGE));
2624
+ return;
2625
+ }
2626
+ const pending = { resolve };
2627
+ pendingHumanInTheLoopRef.current.set(key, pending);
2628
+ if (signal) {
2629
+ const onAbort = () => {
2630
+ pendingHumanInTheLoopRef.current.delete(key);
2631
+ reject(new Error(HUMAN_IN_THE_LOOP_ABORTED_MESSAGE));
2632
+ };
2633
+ signal.addEventListener("abort", onAbort, { once: true });
2634
+ pending.detachAbort = () => {
2635
+ signal.removeEventListener("abort", onAbort);
2636
+ };
2637
+ }
2396
2638
  });
2397
2639
  }
2398
2640
  };
2399
2641
  processedTools.push(frontendTool);
2400
- if (tool.render) processedRenderToolCalls.push({
2401
- name: tool.name,
2402
- args: tool.parameters,
2403
- render: tool.render,
2404
- ...tool.agentId && { agentId: tool.agentId }
2405
- });
2642
+ if (tool.render) {
2643
+ const ToolComponent = tool.render;
2644
+ const RenderComponent = (props) => (0, react.createElement)(ToolComponent, {
2645
+ ...props,
2646
+ name: tool.name === WILDCARD_TOOL_NAME$1 ? props.name : tool.name,
2647
+ description: tool.description || "",
2648
+ agentId: tool.agentId,
2649
+ respond: props.status === _copilotkit_core.ToolCallStatus.Executing ? async (result) => {
2650
+ const pending = pendingHumanInTheLoopRef.current.get(props.toolCallId);
2651
+ if (!pending) return;
2652
+ pending.detachAbort?.();
2653
+ pendingHumanInTheLoopRef.current.delete(props.toolCallId);
2654
+ pending.resolve(result);
2655
+ } : void 0
2656
+ });
2657
+ processedRenderToolCalls.push({
2658
+ name: tool.name,
2659
+ args: tool.parameters,
2660
+ render: RenderComponent,
2661
+ ...tool.agentId && { agentId: tool.agentId }
2662
+ });
2663
+ }
2406
2664
  });
2407
2665
  return {
2408
2666
  tools: processedTools,