import React, { useEffect, useMemo, useRef } from "react"; import { ActionRenderProps, FrontendAction } from "../types/frontend-action"; import { Parameter, getZodParameters, MappedParameterTypes, } from "@copilotkit/shared"; import { parseJson } from "@copilotkit/shared"; import { ToolCallStatus } from "@copilotkit/core"; import { type ReactFrontendTool, useFrontendTool as useFrontendToolVNext, } from "../v2"; type FrontendToolOptions = ReactFrontendTool< MappedParameterTypes >; type FrontendToolRenderArgs = | { name: string; args: Partial>; status: ToolCallStatus.InProgress; result: undefined; } | { name: string; args: MappedParameterTypes; status: ToolCallStatus.Executing; result: undefined; } | { name: string; args: MappedParameterTypes; status: ToolCallStatus.Complete; result: string; }; export type UseFrontendToolArgs = { available?: "disabled" | "enabled"; } & Pick< FrontendAction, "name" | "description" | "parameters" | "handler" | "followUp" | "render" >; export function useFrontendTool( tool: UseFrontendToolArgs, dependencies?: any[], ) { const { name, description, parameters, render, followUp, available } = tool; const zodParameters = getZodParameters(parameters); const renderRef = useRef(render); useEffect(() => { renderRef.current = render; }, [render, ...(dependencies ?? [])]); const normalizedRender: FrontendToolOptions["render"] | undefined = useMemo(() => { if (typeof render === "undefined") { return undefined; } return ((args: FrontendToolRenderArgs) => { const currentRender = renderRef.current; if (typeof currentRender === "undefined") { return null; } if (typeof currentRender === "string") { return React.createElement(React.Fragment, null, currentRender); } const renderArgs = { ...args, result: typeof args.result === "string" ? parseJson(args.result, args.result) : args.result, } as ActionRenderProps; const rendered = currentRender(renderArgs); if (typeof rendered === "string") { return React.createElement(React.Fragment, null, rendered); } return rendered ?? null; }) as FrontendToolOptions["render"]; }, []); // Handler ref to avoid stale closures const handlerRef = useRef(tool.handler); useEffect(() => { handlerRef.current = tool.handler; }, [tool.handler, ...(dependencies ?? [])]); const normalizedHandler = tool.handler ? (args: MappedParameterTypes) => handlerRef.current?.(args) : undefined; useFrontendToolVNext>({ name, description, parameters: zodParameters, handler: normalizedHandler, followUp, render: normalizedRender, available: available === undefined ? undefined : available !== "disabled", }); }