langchain
Version:
Typescript bindings for langchain
1 lines • 51.2 kB
Source Map (JSON)
{"version":3,"file":"hitl.cjs","names":["z","z4","isZodSchemaV4","interopZodTransformInputSchema","ToolMessage","isCommand","Command","createMiddleware","interopParse","interrupt","AIMessage"],"sources":["../../../src/agents/middleware/hitl.ts"],"sourcesContent":["/* oxlint-disable @typescript-eslint/no-explicit-any */\nimport { z } from \"zod/v3\";\nimport { z as z4 } from \"zod/v4\";\nimport { AIMessage, ToolMessage, ToolCall } from \"@langchain/core/messages\";\nimport {\n InferInteropZodInput,\n interopParse,\n interopZodTransformInputSchema,\n isZodSchemaV4,\n} from \"@langchain/core/utils/types\";\nimport { Command, interrupt, isCommand } from \"@langchain/langgraph\";\n\nimport { createMiddleware } from \"../middleware.js\";\nimport type { AgentBuiltInState, Runtime } from \"../runtime.js\";\nimport type { JumpToTarget } from \"../constants.js\";\nimport type { Interrupt } from \"../types.js\";\nimport type { ToolCallRequest } from \"./types.js\";\n\nconst WhenFunctionSchema = z\n .function()\n .args(z.custom<ToolCallRequest<AgentBuiltInState>>()) // request\n .returns(z.union([z.boolean(), z.promise(z.boolean())]));\n\n/**\n * Predicate controlling whether a given tool call triggers an interrupt.\n *\n * Receives a {@link ToolCallRequest} and returns `true` to interrupt or `false`\n * to auto-approve the tool call.\n *\n * The request is constructed with `tool` set to `undefined` and `runtime` set to\n * the node-level {@link Runtime}, so it reflects the batch (`afterModel`) context\n * rather than a per-call tool execution. In `\"per_call\"` mode the predicate runs\n * in `wrapToolCall` and receives the real request, with `tool` set.\n *\n * In both modes the predicate runs again when the run resumes, so it must return\n * the same answer for the same call. If it returns `false` on resume, the\n * reviewer's answer is skipped and the tool runs, even if they rejected it.\n *\n * @param request - The tool call request being evaluated\n * @returns `true` to interrupt for the tool call, `false` to auto-approve it.\n * May also return a promise resolving to a boolean.\n *\n * @example\n * ```typescript\n * import { type WhenPredicate } from \"langchain\";\n *\n * // Only interrupt delete_file calls targeting /etc\n * const when: WhenPredicate = (request) =>\n * String(request.toolCall.args.path ?? \"\").startsWith(\"/etc\");\n * ```\n */\nexport type WhenPredicate = z.infer<typeof WhenFunctionSchema>;\n\nconst DescriptionFunctionSchema = z\n .function()\n .args(\n z.custom<ToolCall>(), // toolCall\n z.custom<AgentBuiltInState>(), // state\n z.custom<Runtime<unknown>>() // runtime\n )\n .returns(z.union([z.string(), z.promise(z.string())]));\n\n/**\n * Function type that dynamically generates a description for a tool call approval request.\n *\n * @param toolCall - The tool call being reviewed\n * @param state - The current agent state\n * @param runtime - The agent runtime context\n * @returns A string description or Promise that resolves to a string description\n *\n * @example\n * ```typescript\n * import { type DescriptionFactory, type ToolCall } from \"langchain\";\n *\n * const descriptionFactory: DescriptionFactory = (toolCall, state, runtime) => {\n * return `Please review: ${toolCall.name}(${JSON.stringify(toolCall.args)})`;\n * };\n * ```\n */\nexport type DescriptionFactory = z.infer<typeof DescriptionFunctionSchema>;\n\n/**\n * The type of decision a human can make.\n */\nconst ALLOWED_DECISIONS = [\"approve\", \"edit\", \"reject\", \"respond\"] as const;\nconst DecisionType = z.enum(ALLOWED_DECISIONS);\nexport type DecisionType = z.infer<typeof DecisionType>;\n\nconst InterruptOnConfigSchema = z.object({\n /**\n * The decisions that are allowed for this action.\n */\n allowedDecisions: z.array(DecisionType),\n /**\n * The description attached to the request for human input.\n * Can be either:\n * - A static string describing the approval request\n * - A callable that dynamically generates the description based on agent state,\n * runtime, and tool call information\n *\n * @example\n * Static string description\n * ```typescript\n * import type { InterruptOnConfig } from \"langchain\";\n *\n * const config: InterruptOnConfig = {\n * allowedDecisions: [\"approve\", \"reject\"],\n * description: \"Please review this tool execution\"\n * };\n * ```\n *\n * @example\n * Dynamic callable description\n * ```typescript\n * import type {\n * AgentBuiltInState,\n * Runtime,\n * DescriptionFactory,\n * ToolCall,\n * InterruptOnConfig\n * } from \"langchain\";\n *\n * const formatToolDescription: DescriptionFactory = (\n * toolCall: ToolCall,\n * state: AgentBuiltInState,\n * runtime: Runtime<unknown>\n * ) => {\n * return `Tool: ${toolCall.name}\\nArguments:\\n${JSON.stringify(toolCall.args, null, 2)}`;\n * };\n *\n * const config: InterruptOnConfig = {\n * allowedDecisions: [\"approve\", \"edit\"],\n * description: formatToolDescription\n * };\n * ```\n */\n description: z.union([z.string(), DescriptionFunctionSchema]).optional(),\n /**\n * JSON schema for the arguments associated with the action, if edits are allowed.\n *\n * Not used in `\"per_call\"` mode: an edit's `args` are checked against the tool's\n * own schema there.\n */\n argsSchema: z.record(z.any()).optional(),\n /**\n * Optional predicate controlling whether to interrupt for a given tool call.\n *\n * Receives a {@link ToolCallRequest} and returns `true` to interrupt or\n * `false` to auto-approve the tool call.\n *\n * The request is constructed with `tool` set to `undefined` and `runtime` set\n * to the node-level {@link Runtime}, so `request.tool` is not available. In\n * `\"per_call\"` mode the predicate runs in `wrapToolCall` and receives the real\n * request, with `tool` set.\n *\n * @example\n * ```typescript\n * import type { InterruptOnConfig } from \"langchain\";\n *\n * // Only interrupt delete_file calls targeting /etc\n * const config: InterruptOnConfig = {\n * allowedDecisions: [\"approve\", \"reject\"],\n * when: (request) =>\n * String(request.toolCall.args.path ?? \"\").startsWith(\"/etc\"),\n * };\n * ```\n */\n when: WhenFunctionSchema.optional(),\n});\nexport type InterruptOnConfig = z.input<typeof InterruptOnConfigSchema>;\n\n/**\n * Represents an action with a name and arguments.\n */\nexport interface Action {\n /**\n * The type or name of action being requested (e.g., \"add_numbers\").\n */\n name: string;\n /**\n * Key-value pairs of arguments needed for the action (e.g., {\"a\": 1, \"b\": 2}).\n */\n args: Record<string, any>;\n}\n\n/**\n * Represents an action request with a name, arguments, and description.\n */\nexport interface ActionRequest {\n /**\n * The name of the action being requested.\n */\n name: string;\n /**\n * Key-value pairs of arguments needed for the action (e.g., {\"a\": 1, \"b\": 2}).\n */\n args: Record<string, any>;\n /**\n * The description of the action to be reviewed.\n */\n description?: string;\n}\n\n/**\n * Policy for reviewing a HITL request.\n */\nexport interface ReviewConfig {\n /**\n * Name of the action associated with this review configuration.\n */\n actionName: string;\n /**\n * The decisions that are allowed for this request.\n */\n allowedDecisions: DecisionType[];\n /**\n * JSON schema for the arguments associated with the action, if edits are allowed.\n */\n argsSchema?: Record<string, any>;\n}\n\n/**\n * Request for human feedback on a sequence of actions requested by a model.\n *\n * @example\n * ```ts\n * const hitlRequest: HITLRequest = {\n * actionRequests: [\n * { name: \"send_email\", args: { to: \"user@example.com\", subject: \"Hello\" } }\n * ],\n * reviewConfigs: [\n * {\n * actionName: \"send_email\",\n * allowedDecisions: [\"approve\", \"edit\", \"reject\"],\n * description: \"Please review the email before sending\"\n * }\n * ]\n * };\n * const response = interrupt(hitlRequest);\n * ```\n */\nexport interface HITLRequest {\n /**\n * A list of agent actions for human review.\n */\n actionRequests: ActionRequest[];\n /**\n * Review configuration for all possible actions.\n */\n reviewConfigs: ReviewConfig[];\n}\n\n/**\n * Response when a human approves the action.\n */\nexport interface ApproveDecision {\n type: \"approve\";\n}\n\n/**\n * Response when a human edits the action.\n */\nexport interface EditDecision {\n type: \"edit\";\n /**\n * Edited action for the agent to perform.\n * Ex: for a tool call, a human reviewer can edit the tool name and args.\n */\n editedAction: Action;\n}\n\n/**\n * Response when a human rejects the action.\n */\nexport interface RejectDecision {\n type: \"reject\";\n /**\n * The message sent to the model explaining why the action was rejected.\n */\n message?: string;\n}\n\n/**\n * Response when a human answers on behalf of the tool.\n */\nexport interface RespondDecision {\n type: \"respond\";\n /**\n * The message sent to the model as the tool result.\n */\n message: string;\n}\n\n/**\n * Union of all possible decision types.\n */\nexport type Decision =\n | ApproveDecision\n | EditDecision\n | RejectDecision\n | RespondDecision;\n\n/**\n * Response payload for a HITLRequest.\n */\nexport interface HITLResponse {\n /**\n * The decisions made by the human.\n */\n decisions: Decision[];\n}\n\nconst ToolApprovalRequestSchema = z4.object({\n /** Always `\"tool_approval\"`; tells clients how to read this interrupt. */\n type: z4.literal(\"tool_approval\"),\n /** ID of the model's tool call this approval is about. */\n tool_call_id: z4.string(),\n /** Tool name, as the model requested it. */\n name: z4.string(),\n /** Tool arguments, as the model requested them. */\n args: z4.record(z4.string(), z4.unknown()),\n /** Text shown to the reviewer. */\n description: z4.string(),\n});\n\n/**\n * Interrupt value raised once per gated tool call in `\"per_call\"` mode. Keys are\n * snake_case, as in Python.\n */\nexport type ToolApprovalRequest = z4.infer<typeof ToolApprovalRequestSchema>;\n\n/**\n * What an edit's `args` must look like: the input side of the tool's schema, if it's\n * a Zod v4 object. Transforms are left out, so they run once, when the tool does.\n * Args the tool doesn't declare are handled as the tool handles them from the model.\n * Any other tool takes any object, and checks it when it runs.\n */\nfunction editArgs(\n tool?: ToolCallRequest[\"tool\"]\n): z4.ZodType<Record<string, unknown>> {\n const schema = tool && \"schema\" in tool ? tool.schema : undefined;\n const input = isZodSchemaV4(schema)\n ? interopZodTransformInputSchema(schema, true)\n : undefined;\n return input instanceof z4.ZodObject\n ? input\n : z4.record(z4.string(), z4.unknown());\n}\n\n/**\n * The answers a reviewer can give to a `\"per_call\"` approval of tool `name`, by type.\n *\n * The edit can't switch tools: its `name` is pinned.\n */\nconst toolApprovalDecisions = (\n name: string,\n tool?: ToolCallRequest[\"tool\"]\n) => ({\n approve: z4.object({ type: z4.literal(\"approve\") }),\n edit: z4.object({\n type: z4.literal(\"edit\"),\n edited_action: z4.object({\n name: z4.literal(name),\n args: editArgs(tool),\n }),\n }),\n reject: z4.object({\n type: z4.literal(\"reject\"),\n message: z4.string().optional(),\n }),\n respond: z4.object({\n type: z4.literal(\"respond\"),\n message: z4.string(),\n }),\n});\n\n/**\n * A reviewer's answer to a `\"per_call\"` tool approval.\n */\nexport type ToolApprovalDecision = z4.infer<\n ReturnType<typeof toolApprovalDecisions>[DecisionType]\n>;\n\n/**\n * Whether an interrupt is a `\"per_call\"` tool approval, e.g.\n * `result.__interrupt__?.filter(isToolApprovalInterrupt)`.\n */\nexport function isToolApprovalInterrupt(\n interrupt: Interrupt | undefined\n): interrupt is Interrupt<ToolApprovalRequest> {\n return ToolApprovalRequestSchema.safeParse(interrupt?.value).success;\n}\n\n/**\n * The per-call `responseSchema`: the `allowed` decisions for tool `name`.\n *\n * An answer is checked only against the branch its `type` names, so a bad one gets a\n * single error. A one-decision tool gets that decision's plain object schema.\n */\nfunction decisionSchema(\n allowed: readonly DecisionType[],\n name: string,\n tool?: ToolCallRequest[\"tool\"]\n): z4.ZodType<ToolApprovalDecision> {\n const byType = toolApprovalDecisions(name, tool);\n // Drop duplicates, keeping order: a discriminated union can't repeat a `type`.\n const [first, ...rest] = [...new Set(allowed)].map((d) => byType[d]);\n if (first === undefined) {\n throw new Error(\"allowedDecisions must list at least one decision.\");\n }\n return rest.length ? z4.discriminatedUnion(\"type\", [first, ...rest]) : first;\n}\n\nconst contextSchema = z.object({\n /**\n * Mapping of tool name to allowed reviewer responses.\n * If a tool doesn't have an entry, it's auto-approved by default.\n *\n * - `true` -> pause for approval and allow every decision (approve/edit/reject/respond)\n * - `false` -> auto-approve (no human review)\n * - `InterruptOnConfig` -> explicitly specify which decisions are allowed for this tool\n */\n interruptOn: z\n .record(z.union([z.boolean(), InterruptOnConfigSchema]))\n .optional(),\n /**\n * Prefix used when constructing human-facing approval messages.\n * Provides context about the tool call being reviewed; does not change the underlying action.\n *\n * Note: This prefix is only applied for tools that do not provide a custom\n * `description` via their {@link InterruptOnConfig}. If a tool specifies a custom\n * `description`, that per-tool text is used and this prefix is ignored.\n */\n descriptionPrefix: z.string().default(\"Tool execution requires approval\"),\n});\nexport type HumanInTheLoopMiddlewareConfig = InferInteropZodInput<\n typeof contextSchema\n> & {\n /**\n * How the middleware pauses for review. Set at construction only.\n *\n * - `\"batched\"` (default): one interrupt per model turn for all gated tool calls.\n * - `\"per_call\"`: one {@link ToolApprovalRequest} interrupt per gated call, with a\n * typed `responseSchema`, answered with a single decision keyed by interrupt ID.\n *\n * In `\"per_call\"` mode, LangGraph checks each answer before saving it:\n *\n * - An edit can't switch tools, and its args must match the tool's argument\n * types. Args the tool doesn't declare are handled as they are when the model\n * sends them. For a tool without a Zod v4 object schema, any object is accepted\n * and the tool checks it when it runs.\n * - An invalid answer throws a `ZodError` and isn't saved, so the same interrupt\n * can be answered again.\n *\n * If one of several answers sent together is invalid, the others' tools may\n * already have run even if they still show as pending, and answering them again\n * would run them twice. Check answers against `responseSchema` before sending them\n * together, or send one at a time.\n *\n * The review happens as the tool call starts, inside the tool-call middleware\n * chain. List this middleware before tool retry or error-handling middleware so it\n * wraps them. On resume, each paused tool call runs the middleware listed before\n * this one again up to the pause, so that middleware must not have side effects\n * before calling `handler`. The tool itself runs only after the answer.\n */\n interruptMode?: \"batched\" | \"per_call\";\n /**\n * `\"per_call\"` mode only: text prepended to the result of a tool call a reviewer\n * edited, so the model knows the call it made isn't the one that ran. Pass `null`\n * to add nothing.\n */\n editNotice?: string | null;\n};\n\nconst DEFAULT_EDIT_NOTICE =\n \"Note: a human reviewer replaced this tool call before it ran. The call recorded in \" +\n \"your message is the one you produced, not the one that executed. This was \" +\n \"intentional and authorized. Do not re-issue your original call.\";\n\n/**\n * Resolve `interruptOn`: `true` allows every decision; `false` and missing entries\n * auto-approve.\n */\nfunction resolveInterruptOn(\n interruptOn: NonNullable<HumanInTheLoopMiddlewareConfig>[\"interruptOn\"]\n): Record<string, InterruptOnConfig> {\n const resolved: Record<string, InterruptOnConfig> = {};\n for (const [toolName, toolConfig] of Object.entries(interruptOn ?? {})) {\n if (typeof toolConfig === \"boolean\") {\n if (toolConfig === true) {\n resolved[toolName] = { allowedDecisions: [...ALLOWED_DECISIONS] };\n }\n } else if (toolConfig.allowedDecisions) {\n resolved[toolName] = toolConfig;\n }\n }\n return resolved;\n}\n\n/**\n * Return `message` with the reviewer-edit notice prepended, stating the call that ran.\n */\nfunction prependNotice(\n message: ToolMessage,\n notice: string,\n executed: Action\n): ToolMessage {\n const hasContent = message.content.length > 0;\n const text = `${notice} Executed instead: ${executed.name} with arguments ${JSON.stringify(\n executed.args\n )}.${hasContent ? \"\\n\\nTool response:\" : \"\"}`;\n return new ToolMessage({\n content:\n typeof message.content === \"string\"\n ? `${text}${hasContent ? \"\\n\" : \"\"}${message.content}`\n : [{ type: \"text\", text }, ...message.content],\n tool_call_id: message.tool_call_id,\n name: message.name,\n status: message.status,\n artifact: message.artifact,\n id: message.id,\n additional_kwargs: message.additional_kwargs,\n response_metadata: message.response_metadata,\n });\n}\n\n/**\n * Tell the model a reviewer replaced the call, and with what.\n */\nfunction withEditNotice(\n result: ToolMessage | Command,\n toolCallId: string,\n executed: Action,\n notice: string | null\n): ToolMessage | Command {\n if (!notice) {\n return result;\n }\n if (ToolMessage.isInstance(result)) {\n return prependNotice(result, notice, executed);\n }\n // A `Command` carries the `ToolMessage` in its state update.\n const update: unknown = isCommand(result) ? result.update : undefined;\n if (\n typeof update !== \"object\" ||\n update === null ||\n !(\"messages\" in update) ||\n !Array.isArray(update.messages)\n ) {\n return result;\n }\n return new Command({\n update: {\n ...update,\n messages: update.messages.map((message: unknown) =>\n ToolMessage.isInstance(message) && message.tool_call_id === toolCallId\n ? prependNotice(message, notice, executed)\n : message\n ),\n },\n resume: result.resume,\n goto: result.goto,\n graph: result.graph,\n });\n}\n\n/**\n * Creates a Human-in-the-Loop (HITL) middleware for tool approval and oversight.\n *\n * This middleware intercepts tool calls made by an AI agent and provides human oversight\n * capabilities before execution. It enables selective approval workflows where certain tools\n * require human intervention while others can execute automatically.\n *\n * A invocation result that has been interrupted by the middleware will have a `__interrupt__`\n * property that contains the interrupt request.\n *\n * ```ts\n * import { type HITLRequest, type HITLResponse } from \"langchain\";\n * import { type Interrupt } from \"langchain\";\n *\n * const result = await agent.invoke(request);\n * const interruptRequest = result.__interrupt__?.[0] as Interrupt<HITLRequest>;\n *\n * // Examine the action requests and review configs\n * const actionRequests = interruptRequest.value.actionRequests;\n * const reviewConfigs = interruptRequest.value.reviewConfigs;\n *\n * // Create decisions for each action\n * const resume: HITLResponse = {\n * decisions: actionRequests.map((action, i) => {\n * if (action.name === \"calculator\") {\n * return { type: \"approve\" };\n * } else if (action.name === \"write_file\") {\n * return {\n * type: \"edit\",\n * editedAction: { name: \"write_file\", args: { filename: \"safe.txt\", content: \"Safe content\" } }\n * };\n * }\n * return { type: \"reject\", message: \"Action not allowed\" };\n * })\n * };\n *\n * // Resume with decisions\n * await agent.invoke(new Command({ resume }), config);\n * ```\n *\n * ## Features\n *\n * - **Selective Tool Approval**: Configure which tools require human approval\n * - **Multiple Decision Types**: Approve, edit, or reject tool calls\n * - **Asynchronous Workflow**: Uses LangGraph's interrupt mechanism for non-blocking approval\n * - **Custom Approval Messages**: Provide context-specific descriptions for approval requests\n *\n * ## Decision Types\n *\n * When a tool requires approval, the human operator can respond with:\n * - `approve`: Execute the tool with original arguments\n * - `edit`: Modify the tool name and/or arguments before execution\n * - `reject`: Provide a manual response instead of executing the tool\n *\n * @param options - Configuration options for the middleware\n * @param options.interruptOn - Per-tool configuration mapping tool names to their settings\n * @param options.interruptOn[toolName].allowedDecisions - Array of decision types allowed for this tool (e.g., [\"approve\", \"edit\", \"reject\"])\n * @param options.interruptOn[toolName].description - Custom approval message for the tool. Can be either a static string or a callable that dynamically generates the description based on agent state, runtime, and tool call information\n * @param options.interruptOn[toolName].argsSchema - JSON schema for the arguments associated with the action, if edits are allowed\n * @param options.interruptOn[toolName].when - Optional predicate that dynamically controls whether a tool call triggers an interrupt. Returns `true` to interrupt or `false` to auto-approve the tool call.\n * @param options.descriptionPrefix - Default prefix for approval messages (default: \"Tool execution requires approval\"). Only used for tools that do not define a custom `description` in their InterruptOnConfig.\n *\n * @returns A middleware instance that can be passed to `createAgent`\n *\n * @example\n * Basic usage with selective tool approval\n * ```typescript\n * import { humanInTheLoopMiddleware } from \"langchain\";\n * import { createAgent } from \"langchain\";\n *\n * const hitlMiddleware = humanInTheLoopMiddleware({\n * interruptOn: {\n * // Interrupt write_file tool and allow edits or approvals\n * \"write_file\": {\n * allowedDecisions: [\"approve\", \"edit\"],\n * description: \"⚠️ File write operation requires approval\"\n * },\n * // Auto-approve read_file tool\n * \"read_file\": false\n * }\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4\",\n * tools: [writeFileTool, readFileTool],\n * middleware: [hitlMiddleware]\n * });\n * ```\n *\n * @example\n * Handling approval requests\n * ```typescript\n * import { type HITLRequest, type HITLResponse, type Interrupt } from \"langchain\";\n * import { Command } from \"@langchain/langgraph\";\n *\n * // Initial agent invocation\n * const result = await agent.invoke({\n * messages: [new HumanMessage(\"Write 'Hello' to output.txt\")]\n * }, config);\n *\n * // Check if agent is paused for approval\n * if (result.__interrupt__) {\n * const interruptRequest = result.__interrupt__?.[0] as Interrupt<HITLRequest>;\n *\n * // Show tool call details to user\n * console.log(\"Actions:\", interruptRequest.value.actionRequests);\n * console.log(\"Review configs:\", interruptRequest.value.reviewConfigs);\n *\n * // Resume with approval\n * const resume: HITLResponse = {\n * decisions: [{ type: \"approve\" }]\n * };\n * await agent.invoke(\n * new Command({ resume }),\n * config\n * );\n * }\n * ```\n *\n * @example\n * Different decision types\n * ```typescript\n * import { type HITLResponse } from \"langchain\";\n *\n * // Approve the tool call as-is\n * const resume: HITLResponse = {\n * decisions: [{ type: \"approve\" }]\n * };\n *\n * // Edit the tool arguments\n * const resume: HITLResponse = {\n * decisions: [{\n * type: \"edit\",\n * editedAction: { name: \"write_file\", args: { filename: \"safe.txt\", content: \"Modified\" } }\n * }]\n * };\n *\n * // Reject with feedback\n * const resume: HITLResponse = {\n * decisions: [{\n * type: \"reject\",\n * message: \"File operation not allowed in demo mode\"\n * }]\n * };\n * ```\n *\n * @example\n * Production use case with database operations\n * ```typescript\n * const hitlMiddleware = humanInTheLoopMiddleware({\n * interruptOn: {\n * \"execute_sql\": {\n * allowedDecisions: [\"approve\", \"edit\", \"reject\"],\n * description: \"🚨 SQL query requires DBA approval\\nPlease review for safety and performance\"\n * },\n * \"read_schema\": false, // Reading metadata is safe\n * \"delete_records\": {\n * allowedDecisions: [\"approve\", \"reject\"],\n * description: \"⛔ DESTRUCTIVE OPERATION - Requires manager approval\"\n * }\n * },\n * descriptionPrefix: \"Database operation pending approval\"\n * });\n * ```\n *\n * @example\n * Using dynamic callable descriptions\n * ```typescript\n * import { type DescriptionFactory, type ToolCall } from \"langchain\";\n * import type { AgentBuiltInState, Runtime } from \"langchain/agents\";\n *\n * // Define a dynamic description factory\n * const formatToolDescription: DescriptionFactory = (\n * toolCall: ToolCall,\n * state: AgentBuiltInState,\n * runtime: Runtime<unknown>\n * ) => {\n * return `Tool: ${toolCall.name}\\nArguments:\\n${JSON.stringify(toolCall.args, null, 2)}`;\n * };\n *\n * const hitlMiddleware = humanInTheLoopMiddleware({\n * interruptOn: {\n * \"write_file\": {\n * allowedDecisions: [\"approve\", \"edit\"],\n * // Use dynamic description that can access tool call, state, and runtime\n * description: formatToolDescription\n * },\n * // Or use an inline function\n * \"send_email\": {\n * allowedDecisions: [\"approve\", \"reject\"],\n * description: (toolCall, state, runtime) => {\n * const { to, subject } = toolCall.args;\n * return `Email to ${to}\\nSubject: ${subject}\\n\\nRequires approval before sending`;\n * }\n * }\n * }\n * });\n * ```\n *\n * @remarks\n * - Tool calls are processed in the order they appear in the AI message\n * - Auto-approved tools execute immediately without interruption\n * - In the default `\"batched\"` mode, multiple tools requiring approval are bundled into a\n * single interrupt request, raised in the `afterModel` phase before any tool runs. In\n * `\"per_call\"` mode, each gated call raises its own interrupt from `wrapToolCall`\n * - Requires a checkpointer to maintain state across interruptions\n *\n * @see {@link createAgent} for agent creation\n * @see {@link Command} for resuming interrupted execution\n * @public\n */\nexport function humanInTheLoopMiddleware(\n options: NonNullable<HumanInTheLoopMiddlewareConfig>\n) {\n const createActionAndConfig = async (\n toolCall: ToolCall,\n config: InterruptOnConfig,\n state: AgentBuiltInState,\n runtime: Runtime<unknown>\n ): Promise<{\n actionRequest: ActionRequest;\n reviewConfig: ReviewConfig;\n }> => {\n const toolName = toolCall.name;\n const toolArgs = toolCall.args;\n\n // Generate description using the description field (str or callable)\n const descriptionValue = config.description;\n let description: string;\n if (typeof descriptionValue === \"function\") {\n description = await descriptionValue(toolCall, state, runtime);\n } else if (descriptionValue !== undefined) {\n description = descriptionValue;\n } else {\n description = `${\n options.descriptionPrefix ?? \"Tool execution requires approval\"\n }\\n\\nTool: ${toolName}\\nArgs: ${JSON.stringify(toolArgs, null, 2)}`;\n }\n\n /**\n * Create ActionRequest with description\n */\n const actionRequest: ActionRequest = {\n name: toolName,\n args: toolArgs,\n description,\n };\n\n /**\n * Create ReviewConfig\n */\n const reviewConfig: ReviewConfig = {\n actionName: toolName,\n allowedDecisions: config.allowedDecisions,\n };\n\n if (config.argsSchema) {\n reviewConfig.argsSchema = config.argsSchema;\n }\n\n return { actionRequest, reviewConfig };\n };\n\n /**\n * Return `false` if the `when` predicate rejects this tool call, `true` otherwise.\n *\n * When no `when` predicate is configured the tool call always interrupts.\n */\n const shouldInterrupt = async (\n toolCall: ToolCall,\n config: InterruptOnConfig,\n state: AgentBuiltInState,\n runtime: Runtime<unknown>\n ): Promise<boolean> => {\n const { when } = config;\n if (when == null) {\n return true;\n }\n const request: ToolCallRequest<AgentBuiltInState> = {\n toolCall,\n tool: undefined,\n state,\n runtime,\n };\n return when(request);\n };\n\n const processDecision = (\n decision: Decision,\n toolCall: ToolCall,\n config: InterruptOnConfig\n ): { revisedToolCall: ToolCall | null; toolMessage: ToolMessage | null } => {\n const allowedDecisions = config.allowedDecisions;\n if (decision.type === \"approve\" && allowedDecisions.includes(\"approve\")) {\n return { revisedToolCall: toolCall, toolMessage: null };\n }\n\n if (decision.type === \"edit\" && allowedDecisions.includes(\"edit\")) {\n const editedAction = decision.editedAction;\n\n /**\n * Validate edited action structure\n */\n if (!editedAction || typeof editedAction.name !== \"string\") {\n throw new Error(\n `Invalid edited action for tool \"${toolCall.name}\": name must be a string`\n );\n }\n if (!editedAction.args || typeof editedAction.args !== \"object\") {\n throw new Error(\n `Invalid edited action for tool \"${toolCall.name}\": args must be an object`\n );\n }\n\n return {\n revisedToolCall: {\n type: \"tool_call\",\n name: editedAction.name,\n args: editedAction.args,\n id: toolCall.id,\n },\n toolMessage: null,\n };\n }\n\n if (decision.type === \"reject\" && allowedDecisions.includes(\"reject\")) {\n /**\n * Validate that message is a string if provided\n */\n if (\n decision.message !== undefined &&\n typeof decision.message !== \"string\"\n ) {\n throw new Error(\n `Tool call response for \"${\n toolCall.name\n }\" must be a string, got ${typeof decision.message}`\n );\n }\n\n // Create a tool message with the human's text response\n const content =\n decision.message ??\n `User rejected the tool call for \\`${toolCall.name}\\` with id ${toolCall.id}`;\n\n const toolMessage = new ToolMessage({\n content,\n name: toolCall.name,\n tool_call_id: toolCall.id!,\n status: \"error\",\n });\n\n return { revisedToolCall: toolCall, toolMessage };\n }\n\n if (decision.type === \"respond\" && allowedDecisions.includes(\"respond\")) {\n if (typeof decision.message !== \"string\") {\n throw new Error(\n `Tool call response for \"${\n toolCall.name\n }\" must be a string, got ${typeof decision.message}`\n );\n }\n\n const toolMessage = new ToolMessage({\n content: decision.message,\n name: toolCall.name,\n tool_call_id: toolCall.id!,\n status: \"success\",\n });\n\n return { revisedToolCall: toolCall, toolMessage };\n }\n\n const msg = `Unexpected human decision: ${JSON.stringify(\n decision\n )}. Decision type '${decision.type}' is not allowed for tool '${\n toolCall.name\n }'. Expected one of ${JSON.stringify(\n allowedDecisions\n )} based on the tool's configuration.`;\n throw new Error(msg);\n };\n\n const interruptMode = options.interruptMode ?? \"batched\";\n if (interruptMode !== \"batched\" && interruptMode !== \"per_call\") {\n throw new Error(\n `interruptMode must be \"batched\" or \"per_call\", got ${JSON.stringify(interruptMode)}.`\n );\n }\n if (interruptMode === \"per_call\") {\n for (const [toolName, toolConfig] of Object.entries(\n options.interruptOn ?? {}\n )) {\n if (\n typeof toolConfig === \"object\" &&\n !toolConfig.allowedDecisions?.length\n ) {\n throw new Error(\n `Invalid interruptOn config for tool \"${toolName}\": allowedDecisions must list at least one decision.`\n );\n }\n }\n }\n const editNotice =\n options.editNotice === undefined ? DEFAULT_EDIT_NOTICE : options.editNotice;\n\n return createMiddleware({\n name: \"HumanInTheLoopMiddleware\",\n contextSchema,\n /**\n * Per-call mode: pause for each gated tool call as it's about to run.\n */\n wrapToolCall:\n interruptMode === \"per_call\"\n ? async (request, handler) => {\n const { toolCall } = request;\n const { interruptOn } = interopParse(contextSchema, {\n ...options,\n ...(request.runtime.context || {}),\n });\n const toolConfig = resolveInterruptOn(interruptOn)[toolCall.name];\n if (\n !toolConfig ||\n (toolConfig.when && !(await toolConfig.when(request)))\n ) {\n return handler(request);\n }\n // Only a missing ID: an empty one still runs, as in batched mode.\n if (toolCall.id == null) {\n throw new Error(\n `Tool call \\`${toolCall.name}\\` has no ID, so its result can't be matched to it. Make sure the chat model returns tool call IDs.`\n );\n }\n const { actionRequest } = await createActionAndConfig(\n toolCall,\n toolConfig,\n request.state,\n request.runtime\n );\n const value: ToolApprovalRequest = {\n type: \"tool_approval\",\n tool_call_id: toolCall.id,\n name: toolCall.name,\n args: toolCall.args,\n description: actionRequest.description ?? \"\",\n };\n const responseSchema = decisionSchema(\n toolConfig.allowedDecisions,\n toolCall.name,\n request.tool\n );\n // LangGraph parses the answer against `responseSchema` before saving it, so\n // it comes back as one of this tool's allowed decisions.\n const decision = interrupt<\n ToolApprovalRequest,\n ToolApprovalDecision\n >(value, { responseSchema });\n if (decision.type === \"approve\") {\n return handler(request);\n }\n if (decision.type === \"reject\") {\n return new ToolMessage({\n content:\n decision.message ??\n `User rejected the tool call for \\`${toolCall.name}\\` with id ${toolCall.id}`,\n name: toolCall.name,\n tool_call_id: toolCall.id,\n status: \"error\",\n });\n }\n if (decision.type === \"respond\") {\n // The reviewer answers in place of the tool, which doesn't run.\n return new ToolMessage({\n content: decision.message,\n name: toolCall.name,\n tool_call_id: toolCall.id,\n status: \"success\",\n });\n }\n // The schema pinned the tool name, so this is always the same tool.\n const executed = decision.edited_action;\n const result = await handler({\n ...request,\n toolCall: { ...toolCall, args: executed.args },\n });\n return withEditNotice(result, toolCall.id, executed, editNotice);\n }\n : undefined,\n afterModel: {\n canJumpTo: [\"model\"],\n hook: async (state, runtime) => {\n if (interruptMode === \"per_call\") {\n // Interrupts are raised per tool call, in `wrapToolCall`.\n return;\n }\n const config = interopParse(contextSchema, {\n ...options,\n ...(runtime.context || {}),\n });\n if (!config) {\n return;\n }\n\n const { messages } = state;\n if (!messages.length) {\n return;\n }\n\n /**\n * Don't do anything if the last message isn't an AI message with tool calls.\n */\n const lastMessage = [...messages]\n .reverse()\n .find((msg) => AIMessage.isInstance(msg)) as AIMessage;\n if (!lastMessage || !lastMessage.tool_calls?.length) {\n return;\n }\n\n /**\n * If the user omits the interruptOn config, we don't do anything.\n */\n if (!config.interruptOn) {\n return;\n }\n\n /**\n * Resolve per-tool configs (boolean true -> all decisions allowed; false -> auto-approve)\n */\n const resolvedConfigs = resolveInterruptOn(config.interruptOn);\n\n const interruptToolCalls: ToolCall[] = [];\n const autoApprovedToolCalls: ToolCall[] = [];\n\n for (const toolCall of lastMessage.tool_calls) {\n const interruptConfig = resolvedConfigs[toolCall.name];\n /**\n * A tool call is interrupted only when it has a resolved config and its\n * optional `when` predicate doesn't opt it out. Otherwise it is\n * auto-approved.\n */\n if (\n interruptConfig &&\n (await shouldInterrupt(toolCall, interruptConfig, state, runtime))\n ) {\n interruptToolCalls.push(toolCall);\n } else {\n autoApprovedToolCalls.push(toolCall);\n }\n }\n\n /**\n * No interrupt tool calls, so we can just return.\n */\n if (!interruptToolCalls.length) {\n return;\n }\n\n /**\n * Create action requests and review configs for all tools that need approval\n */\n const actionRequests: ActionRequest[] = [];\n const reviewConfigs: ReviewConfig[] = [];\n\n for (const toolCall of interruptToolCalls) {\n const interruptConfig = resolvedConfigs[toolCall.name]!;\n\n /**\n * Create ActionRequest and ReviewConfig using helper method\n */\n const { actionRequest, reviewConfig } = await createActionAndConfig(\n toolCall,\n interruptConfig,\n state,\n runtime\n );\n actionRequests.push(actionRequest);\n reviewConfigs.push(reviewConfig);\n }\n\n /**\n * Create single HITLRequest with all actions and configs\n */\n const hitlRequest: HITLRequest = {\n actionRequests,\n reviewConfigs,\n };\n\n /**\n * Send interrupt and get response\n */\n const hitlResponse = (await interrupt(hitlRequest)) as HITLResponse;\n const decisions = hitlResponse.decisions;\n\n /**\n * Validate that decisions is a valid array before checking length\n */\n if (!decisions || !Array.isArray(decisions)) {\n throw new Error(\n \"Invalid HITLResponse: decisions must be a non-empty array\"\n );\n }\n\n /**\n * Validate that the number of decisions matches the number of interrupt tool calls\n */\n if (decisions.length !== interruptToolCalls.length) {\n throw new Error(\n `Number of human decisions (${decisions.length}) does not match number of hanging tool calls (${interruptToolCalls.length}).`\n );\n }\n\n const revisedToolCalls: ToolCall[] = [...autoApprovedToolCalls];\n const artificialToolMessages: ToolMessage[] = [];\n\n /**\n * Process each decision using helper method\n */\n for (let i = 0; i < decisions.length; i++) {\n const decision = decisions[i]!;\n const toolCall = interruptToolCalls[i]!;\n const interruptConfig = resolvedConfigs[toolCall.name]!;\n\n const { revisedToolCall, toolMessage } = processDecision(\n decision,\n toolCall,\n interruptConfig\n );\n\n if (revisedToolCall) {\n revisedToolCalls.push(revisedToolCall);\n }\n if (toolMessage) {\n artificialToolMessages.push(toolMessage);\n }\n }\n\n /**\n * Keep every tool call on the AI message: a dropped call would never\n * receive a ToolMessage, which provider APIs reject.\n */\n if (AIMessage.isInstance(lastMessage)) {\n lastMessage.tool_calls = revisedToolCalls;\n }\n\n /**\n * Only go back to the model when nothing is left for the tool node to\n * execute, i.e. every remaining tool call already has an artificial\n * ToolMessage (rejected or responded).\n */\n const hasPendingToolCalls = revisedToolCalls.some(\n (toolCall) =>\n !artificialToolMessages.some(\n (message) => message.tool_call_id === toolCall.id\n )\n );\n const jumpTo: JumpToTarget | undefined = hasPendingToolCalls\n ? undefined\n : \"model\";\n return {\n messages: [lastMessage, ...artificialToolMessages],\n jumpTo,\n };\n },\n },\n });\n}\n"],"mappings":"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"}