import { ActionRenderProps, ActionRenderPropsWait, FrontendAction, } from "../types"; import { CopilotKitError, CopilotKitErrorCode, MappedParameterTypes, Parameter, getZodParameters, parseJson, } from "@copilotkit/shared"; import { useHumanInTheLoop as useHumanInTheLoopVNext } from "../v2"; import { ToolCallStatus } from "@copilotkit/core"; import React, { ComponentType, FunctionComponent, useEffect, useRef, } from "react"; type HumanInTheLoopOptions = Parameters[0]; type HumanInTheLoopRender = HumanInTheLoopOptions["render"]; type HumanInTheLoopRenderArgs = HumanInTheLoopRender extends ( props: infer P, ) => any ? P : never; export type UseHumanInTheLoopArgs = { available?: "disabled" | "enabled"; render: FrontendAction["renderAndWaitForResponse"]; followUp?: FrontendAction["followUp"]; } & Pick, "name" | "description" | "parameters">; type HitlRendererArgs = | { name: string; description: string; args: Partial>; status: ToolCallStatus.InProgress; result: undefined; respond: undefined; } | { name: string; description: string; args: Record; status: ToolCallStatus.Executing; result: undefined; respond: (result: unknown) => Promise; } | { name: string; description: string; args: Record; status: ToolCallStatus.Complete; result: string; respond: undefined; }; type HitlRenderer = FunctionComponent; export function useHumanInTheLoop( tool: UseHumanInTheLoopArgs, dependencies?: any[], ) { const { render, ...toolRest } = tool; const { name, description, parameters, followUp } = toolRest; const zodParameters = getZodParameters(parameters); const renderRef = useRef(null); useEffect(() => { renderRef.current = (args: HitlRendererArgs): React.ReactElement | null => { if (typeof render === "string") { return React.createElement(React.Fragment, null, render); } if (!render) { return null; } const renderProps: ActionRenderPropsWait = (() => { const mappedArgs = args.args as unknown as MappedParameterTypes; switch (args.status) { case ToolCallStatus.InProgress: return { args: mappedArgs, respond: args.respond, status: args.status, handler: undefined, }; case ToolCallStatus.Executing: return { args: mappedArgs, respond: args.respond, status: args.status, handler: () => {}, }; case ToolCallStatus.Complete: return { args: mappedArgs, respond: args.respond, status: args.status, result: args.result ? parseJson(args.result, args.result) : args.result, handler: undefined, }; default: throw new CopilotKitError({ code: CopilotKitErrorCode.UNKNOWN, message: `Invalid tool call status: ${(args as unknown as { status: string }).status}`, }); } })(); const rendered = render(renderProps); if (typeof rendered === "string") { return React.createElement(React.Fragment, null, rendered); } return rendered ?? null; }; }, [render, ...(dependencies ?? [])]); useHumanInTheLoopVNext({ name, description, followUp, parameters: zodParameters, render: ((args: HumanInTheLoopRenderArgs) => renderRef.current?.(args as HitlRendererArgs) ?? null) as HumanInTheLoopOptions["render"], }); }