langchain
Version:
Typescript bindings for langchain
832 lines • 35.2 kB
text/typescript
import { AgentMiddleware, ToolCallRequest } from "./types.cjs";
import { AgentBuiltInState, Runtime } from "../runtime.cjs";
import { Interrupt } from "../types.cjs";
import { ToolCall } from "@langchain/core/messages";
import { InferInteropZodInput } from "@langchain/core/utils/types";
import { z } from "zod/v3";
import { z as z$1 } from "zod/v4";
declare const WhenFunctionSchema: z.ZodFunction<z.ZodTuple<[z.ZodType<ToolCallRequest<AgentBuiltInState, unknown>, z.ZodTypeDef, ToolCallRequest<AgentBuiltInState, unknown>>], z.ZodUnknown>, z.ZodUnion<[z.ZodBoolean, z.ZodPromise<z.ZodBoolean>]>>;
/**
* Predicate controlling whether a given tool call triggers an interrupt.
*
* Receives a {@link ToolCallRequest} and returns `true` to interrupt or `false`
* to auto-approve the tool call.
*
* The request is constructed with `tool` set to `undefined` and `runtime` set to
* the node-level {@link Runtime}, so it reflects the batch (`afterModel`) context
* rather than a per-call tool execution. In `"per_call"` mode the predicate runs
* in `wrapToolCall` and receives the real request, with `tool` set.
*
* In both modes the predicate runs again when the run resumes, so it must return
* the same answer for the same call. If it returns `false` on resume, the
* reviewer's answer is skipped and the tool runs, even if they rejected it.
*
* @param request - The tool call request being evaluated
* @returns `true` to interrupt for the tool call, `false` to auto-approve it.
* May also return a promise resolving to a boolean.
*
* @example
* ```typescript
* import { type WhenPredicate } from "langchain";
*
* // Only interrupt delete_file calls targeting /etc
* const when: WhenPredicate = (request) =>
* String(request.toolCall.args.path ?? "").startsWith("/etc");
* ```
*/
export type WhenPredicate = z.infer<typeof WhenFunctionSchema>;
declare const DescriptionFunctionSchema: z.ZodFunction<z.ZodTuple<[z.ZodType<ToolCall<string, Record<string, any>>, z.ZodTypeDef, ToolCall<string, Record<string, any>>>, z.ZodType<AgentBuiltInState, z.ZodTypeDef, AgentBuiltInState>, z.ZodType<Runtime<unknown>, z.ZodTypeDef, Runtime<unknown>>], z.ZodUnknown>, z.ZodUnion<[z.ZodString, z.ZodPromise<z.ZodString>]>>;
/**
* Function type that dynamically generates a description for a tool call approval request.
*
* @param toolCall - The tool call being reviewed
* @param state - The current agent state
* @param runtime - The agent runtime context
* @returns A string description or Promise that resolves to a string description
*
* @example
* ```typescript
* import { type DescriptionFactory, type ToolCall } from "langchain";
*
* const descriptionFactory: DescriptionFactory = (toolCall, state, runtime) => {
* return `Please review: ${toolCall.name}(${JSON.stringify(toolCall.args)})`;
* };
* ```
*/
export type DescriptionFactory = z.infer<typeof DescriptionFunctionSchema>;
declare const DecisionType: z.ZodEnum<["approve", "edit", "reject", "respond"]>;
type DecisionType = z.infer<typeof DecisionType>;
declare const InterruptOnConfigSchema: z.ZodObject<{
/**
* The decisions that are allowed for this action.
*/
allowedDecisions: z.ZodArray<z.ZodEnum<["approve", "edit", "reject", "respond"]>, "many">;
/**
* The description attached to the request for human input.
* Can be either:
* - A static string describing the approval request
* - A callable that dynamically generates the description based on agent state,
* runtime, and tool call information
*
* @example
* Static string description
* ```typescript
* import type { InterruptOnConfig } from "langchain";
*
* const config: InterruptOnConfig = {
* allowedDecisions: ["approve", "reject"],
* description: "Please review this tool execution"
* };
* ```
*
* @example
* Dynamic callable description
* ```typescript
* import type {
* AgentBuiltInState,
* Runtime,
* DescriptionFactory,
* ToolCall,
* InterruptOnConfig
* } from "langchain";
*
* const formatToolDescription: DescriptionFactory = (
* toolCall: ToolCall,
* state: AgentBuiltInState,
* runtime: Runtime<unknown>
* ) => {
* return `Tool: ${toolCall.name}\nArguments:\n${JSON.stringify(toolCall.args, null, 2)}`;
* };
*
* const config: InterruptOnConfig = {
* allowedDecisions: ["approve", "edit"],
* description: formatToolDescription
* };
* ```
*/
description: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodFunction<z.ZodTuple<[z.ZodType<ToolCall<string, Record<string, any>>, z.ZodTypeDef, ToolCall<string, Record<string, any>>>, z.ZodType<AgentBuiltInState, z.ZodTypeDef, AgentBuiltInState>, z.ZodType<Runtime<unknown>, z.ZodTypeDef, Runtime<unknown>>], z.ZodUnknown>, z.ZodUnion<[z.ZodString, z.ZodPromise<z.ZodString>]>>]>>;
/**
* JSON schema for the arguments associated with the action, if edits are allowed.
*
* Not used in `"per_call"` mode: an edit's `args` are checked against the tool's
* own schema there.
*/
argsSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
/**
* Optional predicate controlling whether to interrupt for a given tool call.
*
* Receives a {@link ToolCallRequest} and returns `true` to interrupt or
* `false` to auto-approve the tool call.
*
* The request is constructed with `tool` set to `undefined` and `runtime` set
* to the node-level {@link Runtime}, so `request.tool` is not available. In
* `"per_call"` mode the predicate runs in `wrapToolCall` and receives the real
* request, with `tool` set.
*
* @example
* ```typescript
* import type { InterruptOnConfig } from "langchain";
*
* // Only interrupt delete_file calls targeting /etc
* const config: InterruptOnConfig = {
* allowedDecisions: ["approve", "reject"],
* when: (request) =>
* String(request.toolCall.args.path ?? "").startsWith("/etc"),
* };
* ```
*/
when: z.ZodOptional<z.ZodFunction<z.ZodTuple<[z.ZodType<ToolCallRequest<AgentBuiltInState, unknown>, z.ZodTypeDef, ToolCallRequest<AgentBuiltInState, unknown>>], z.ZodUnknown>, z.ZodUnion<[z.ZodBoolean, z.ZodPromise<z.ZodBoolean>]>>>;
}, "strip", z.ZodTypeAny, {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}, {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}>;
export type InterruptOnConfig = z.input<typeof InterruptOnConfigSchema>;
/**
* Represents an action with a name and arguments.
*/
export interface Action {
/**
* The type or name of action being requested (e.g., "add_numbers").
*/
name: string;
/**
* Key-value pairs of arguments needed for the action (e.g., {"a": 1, "b": 2}).
*/
args: Record<string, any>;
}
/**
* Represents an action request with a name, arguments, and description.
*/
export interface ActionRequest {
/**
* The name of the action being requested.
*/
name: string;
/**
* Key-value pairs of arguments needed for the action (e.g., {"a": 1, "b": 2}).
*/
args: Record<string, any>;
/**
* The description of the action to be reviewed.
*/
description?: string;
}
/**
* Policy for reviewing a HITL request.
*/
export interface ReviewConfig {
/**
* Name of the action associated with this review configuration.
*/
actionName: string;
/**
* The decisions that are allowed for this request.
*/
allowedDecisions: DecisionType[];
/**
* JSON schema for the arguments associated with the action, if edits are allowed.
*/
argsSchema?: Record<string, any>;
}
/**
* Request for human feedback on a sequence of actions requested by a model.
*
* @example
* ```ts
* const hitlRequest: HITLRequest = {
* actionRequests: [
* { name: "send_email", args: { to: "user@example.com", subject: "Hello" } }
* ],
* reviewConfigs: [
* {
* actionName: "send_email",
* allowedDecisions: ["approve", "edit", "reject"],
* description: "Please review the email before sending"
* }
* ]
* };
* const response = interrupt(hitlRequest);
* ```
*/
export interface HITLRequest {
/**
* A list of agent actions for human review.
*/
actionRequests: ActionRequest[];
/**
* Review configuration for all possible actions.
*/
reviewConfigs: ReviewConfig[];
}
/**
* Response when a human approves the action.
*/
export interface ApproveDecision {
type: "approve";
}
/**
* Response when a human edits the action.
*/
export interface EditDecision {
type: "edit";
/**
* Edited action for the agent to perform.
* Ex: for a tool call, a human reviewer can edit the tool name and args.
*/
editedAction: Action;
}
/**
* Response when a human rejects the action.
*/
export interface RejectDecision {
type: "reject";
/**
* The message sent to the model explaining why the action was rejected.
*/
message?: string;
}
/**
* Response when a human answers on behalf of the tool.
*/
export interface RespondDecision {
type: "respond";
/**
* The message sent to the model as the tool result.
*/
message: string;
}
/**
* Union of all possible decision types.
*/
export type Decision = ApproveDecision | EditDecision | RejectDecision | RespondDecision;
/**
* Response payload for a HITLRequest.
*/
export interface HITLResponse {
/**
* The decisions made by the human.
*/
decisions: Decision[];
}
declare const ToolApprovalRequestSchema: z$1.ZodObject<{
type: z$1.ZodLiteral<"tool_approval">;
tool_call_id: z$1.ZodString;
name: z$1.ZodString;
args: z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>;
description: z$1.ZodString;
}, z$1.core.$strip>;
/**
* Interrupt value raised once per gated tool call in `"per_call"` mode. Keys are
* snake_case, as in Python.
*/
export type ToolApprovalRequest = z$1.infer<typeof ToolApprovalRequestSchema>;
/**
* The answers a reviewer can give to a `"per_call"` approval of tool `name`, by type.
*
* The edit can't switch tools: its `name` is pinned.
*/
declare const toolApprovalDecisions: (name: string, tool?: ToolCallRequest["tool"]) => {
approve: z$1.ZodObject<{
type: z$1.ZodLiteral<"approve">;
}, z$1.core.$strip>;
edit: z$1.ZodObject<{
type: z$1.ZodLiteral<"edit">;
edited_action: z$1.ZodObject<{
name: z$1.ZodLiteral<string>;
args: z$1.ZodType<Record<string, unknown>, unknown, z$1.core.$ZodTypeInternals<Record<string, unknown>, unknown>>;
}, z$1.core.$strip>;
}, z$1.core.$strip>;
reject: z$1.ZodObject<{
type: z$1.ZodLiteral<"reject">;
message: z$1.ZodOptional<z$1.ZodString>;
}, z$1.core.$strip>;
respond: z$1.ZodObject<{
type: z$1.ZodLiteral<"respond">;
message: z$1.ZodString;
}, z$1.core.$strip>;
};
/**
* A reviewer's answer to a `"per_call"` tool approval.
*/
export type ToolApprovalDecision = z$1.infer<ReturnType<typeof toolApprovalDecisions>[DecisionType]>;
/**
* Whether an interrupt is a `"per_call"` tool approval, e.g.
* `result.__interrupt__?.filter(isToolApprovalInterrupt)`.
*/
export declare function isToolApprovalInterrupt(interrupt: Interrupt | undefined): interrupt is Interrupt<ToolApprovalRequest>;
declare const contextSchema: z.ZodObject<{
/**
* Mapping of tool name to allowed reviewer responses.
* If a tool doesn't have an entry, it's auto-approved by default.
*
* - `true` -> pause for approval and allow every decision (approve/edit/reject/respond)
* - `false` -> auto-approve (no human review)
* - `InterruptOnConfig` -> explicitly specify which decisions are allowed for this tool
*/
interruptOn: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodObject<{
/**
* The decisions that are allowed for this action.
*/
allowedDecisions: z.ZodArray<z.ZodEnum<["approve", "edit", "reject", "respond"]>, "many">;
/**
* The description attached to the request for human input.
* Can be either:
* - A static string describing the approval request
* - A callable that dynamically generates the description based on agent state,
* runtime, and tool call information
*
* @example
* Static string description
* ```typescript
* import type { InterruptOnConfig } from "langchain";
*
* const config: InterruptOnConfig = {
* allowedDecisions: ["approve", "reject"],
* description: "Please review this tool execution"
* };
* ```
*
* @example
* Dynamic callable description
* ```typescript
* import type {
* AgentBuiltInState,
* Runtime,
* DescriptionFactory,
* ToolCall,
* InterruptOnConfig
* } from "langchain";
*
* const formatToolDescription: DescriptionFactory = (
* toolCall: ToolCall,
* state: AgentBuiltInState,
* runtime: Runtime<unknown>
* ) => {
* return `Tool: ${toolCall.name}\nArguments:\n${JSON.stringify(toolCall.args, null, 2)}`;
* };
*
* const config: InterruptOnConfig = {
* allowedDecisions: ["approve", "edit"],
* description: formatToolDescription
* };
* ```
*/
description: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodFunction<z.ZodTuple<[z.ZodType<ToolCall<string, Record<string, any>>, z.ZodTypeDef, ToolCall<string, Record<string, any>>>, z.ZodType<AgentBuiltInState, z.ZodTypeDef, AgentBuiltInState>, z.ZodType<Runtime<unknown>, z.ZodTypeDef, Runtime<unknown>>], z.ZodUnknown>, z.ZodUnion<[z.ZodString, z.ZodPromise<z.ZodString>]>>]>>;
/**
* JSON schema for the arguments associated with the action, if edits are allowed.
*
* Not used in `"per_call"` mode: an edit's `args` are checked against the tool's
* own schema there.
*/
argsSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
/**
* Optional predicate controlling whether to interrupt for a given tool call.
*
* Receives a {@link ToolCallRequest} and returns `true` to interrupt or
* `false` to auto-approve the tool call.
*
* The request is constructed with `tool` set to `undefined` and `runtime` set
* to the node-level {@link Runtime}, so `request.tool` is not available. In
* `"per_call"` mode the predicate runs in `wrapToolCall` and receives the real
* request, with `tool` set.
*
* @example
* ```typescript
* import type { InterruptOnConfig } from "langchain";
*
* // Only interrupt delete_file calls targeting /etc
* const config: InterruptOnConfig = {
* allowedDecisions: ["approve", "reject"],
* when: (request) =>
* String(request.toolCall.args.path ?? "").startsWith("/etc"),
* };
* ```
*/
when: z.ZodOptional<z.ZodFunction<z.ZodTuple<[z.ZodType<ToolCallRequest<AgentBuiltInState, unknown>, z.ZodTypeDef, ToolCallRequest<AgentBuiltInState, unknown>>], z.ZodUnknown>, z.ZodUnion<[z.ZodBoolean, z.ZodPromise<z.ZodBoolean>]>>>;
}, "strip", z.ZodTypeAny, {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}, {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}>]>>>;
/**
* Prefix used when constructing human-facing approval messages.
* Provides context about the tool call being reviewed; does not change the underlying action.
*
* Note: This prefix is only applied for tools that do not provide a custom
* `description` via their {@link InterruptOnConfig}. If a tool specifies a custom
* `description`, that per-tool text is used and this prefix is ignored.
*/
descriptionPrefix: z.ZodDefault<z.ZodString>;
}, "strip", z.ZodTypeAny, {
interruptOn?: Record<string, boolean | {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}> | undefined;
descriptionPrefix: string;
}, {
interruptOn?: Record<string, boolean | {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}> | undefined;
descriptionPrefix?: string | undefined;
}>;
export type HumanInTheLoopMiddlewareConfig = InferInteropZodInput<typeof contextSchema> & {
/**
* How the middleware pauses for review. Set at construction only.
*
* - `"batched"` (default): one interrupt per model turn for all gated tool calls.
* - `"per_call"`: one {@link ToolApprovalRequest} interrupt per gated call, with a
* typed `responseSchema`, answered with a single decision keyed by interrupt ID.
*
* In `"per_call"` mode, LangGraph checks each answer before saving it:
*
* - An edit can't switch tools, and its args must match the tool's argument
* types. Args the tool doesn't declare are handled as they are when the model
* sends them. For a tool without a Zod v4 object schema, any object is accepted
* and the tool checks it when it runs.
* - An invalid answer throws a `ZodError` and isn't saved, so the same interrupt
* can be answered again.
*
* If one of several answers sent together is invalid, the others' tools may
* already have run even if they still show as pending, and answering them again
* would run them twice. Check answers against `responseSchema` before sending them
* together, or send one at a time.
*
* The review happens as the tool call starts, inside the tool-call middleware
* chain. List this middleware before tool retry or error-handling middleware so it
* wraps them. On resume, each paused tool call runs the middleware listed before
* this one again up to the pause, so that middleware must not have side effects
* before calling `handler`. The tool itself runs only after the answer.
*/
interruptMode?: "batched" | "per_call";
/**
* `"per_call"` mode only: text prepended to the result of a tool call a reviewer
* edited, so the model knows the call it made isn't the one that ran. Pass `null`
* to add nothing.
*/
editNotice?: string | null;
};
/**
* Creates a Human-in-the-Loop (HITL) middleware for tool approval and oversight.
*
* This middleware intercepts tool calls made by an AI agent and provides human oversight
* capabilities before execution. It enables selective approval workflows where certain tools
* require human intervention while others can execute automatically.
*
* A invocation result that has been interrupted by the middleware will have a `__interrupt__`
* property that contains the interrupt request.
*
* ```ts
* import { type HITLRequest, type HITLResponse } from "langchain";
* import { type Interrupt } from "langchain";
*
* const result = await agent.invoke(request);
* const interruptRequest = result.__interrupt__?.[0] as Interrupt<HITLRequest>;
*
* // Examine the action requests and review configs
* const actionRequests = interruptRequest.value.actionRequests;
* const reviewConfigs = interruptRequest.value.reviewConfigs;
*
* // Create decisions for each action
* const resume: HITLResponse = {
* decisions: actionRequests.map((action, i) => {
* if (action.name === "calculator") {
* return { type: "approve" };
* } else if (action.name === "write_file") {
* return {
* type: "edit",
* editedAction: { name: "write_file", args: { filename: "safe.txt", content: "Safe content" } }
* };
* }
* return { type: "reject", message: "Action not allowed" };
* })
* };
*
* // Resume with decisions
* await agent.invoke(new Command({ resume }), config);
* ```
*
* ## Features
*
* - **Selective Tool Approval**: Configure which tools require human approval
* - **Multiple Decision Types**: Approve, edit, or reject tool calls
* - **Asynchronous Workflow**: Uses LangGraph's interrupt mechanism for non-blocking approval
* - **Custom Approval Messages**: Provide context-specific descriptions for approval requests
*
* ## Decision Types
*
* When a tool requires approval, the human operator can respond with:
* - `approve`: Execute the tool with original arguments
* - `edit`: Modify the tool name and/or arguments before execution
* - `reject`: Provide a manual response instead of executing the tool
*
* @param options - Configuration options for the middleware
* @param options.interruptOn - Per-tool configuration mapping tool names to their settings
* @param options.interruptOn[toolName].allowedDecisions - Array of decision types allowed for this tool (e.g., ["approve", "edit", "reject"])
* @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
* @param options.interruptOn[toolName].argsSchema - JSON schema for the arguments associated with the action, if edits are allowed
* @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.
* @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.
*
* @returns A middleware instance that can be passed to `createAgent`
*
* @example
* Basic usage with selective tool approval
* ```typescript
* import { humanInTheLoopMiddleware } from "langchain";
* import { createAgent } from "langchain";
*
* const hitlMiddleware = humanInTheLoopMiddleware({
* interruptOn: {
* // Interrupt write_file tool and allow edits or approvals
* "write_file": {
* allowedDecisions: ["approve", "edit"],
* description: "⚠️ File write operation requires approval"
* },
* // Auto-approve read_file tool
* "read_file": false
* }
* });
*
* const agent = createAgent({
* model: "openai:gpt-4",
* tools: [writeFileTool, readFileTool],
* middleware: [hitlMiddleware]
* });
* ```
*
* @example
* Handling approval requests
* ```typescript
* import { type HITLRequest, type HITLResponse, type Interrupt } from "langchain";
* import { Command } from "@langchain/langgraph";
*
* // Initial agent invocation
* const result = await agent.invoke({
* messages: [new HumanMessage("Write 'Hello' to output.txt")]
* }, config);
*
* // Check if agent is paused for approval
* if (result.__interrupt__) {
* const interruptRequest = result.__interrupt__?.[0] as Interrupt<HITLRequest>;
*
* // Show tool call details to user
* console.log("Actions:", interruptRequest.value.actionRequests);
* console.log("Review configs:", interruptRequest.value.reviewConfigs);
*
* // Resume with approval
* const resume: HITLResponse = {
* decisions: [{ type: "approve" }]
* };
* await agent.invoke(
* new Command({ resume }),
* config
* );
* }
* ```
*
* @example
* Different decision types
* ```typescript
* import { type HITLResponse } from "langchain";
*
* // Approve the tool call as-is
* const resume: HITLResponse = {
* decisions: [{ type: "approve" }]
* };
*
* // Edit the tool arguments
* const resume: HITLResponse = {
* decisions: [{
* type: "edit",
* editedAction: { name: "write_file", args: { filename: "safe.txt", content: "Modified" } }
* }]
* };
*
* // Reject with feedback
* const resume: HITLResponse = {
* decisions: [{
* type: "reject",
* message: "File operation not allowed in demo mode"
* }]
* };
* ```
*
* @example
* Production use case with database operations
* ```typescript
* const hitlMiddleware = humanInTheLoopMiddleware({
* interruptOn: {
* "execute_sql": {
* allowedDecisions: ["approve", "edit", "reject"],
* description: "🚨 SQL query requires DBA approval\nPlease review for safety and performance"
* },
* "read_schema": false, // Reading metadata is safe
* "delete_records": {
* allowedDecisions: ["approve", "reject"],
* description: "⛔ DESTRUCTIVE OPERATION - Requires manager approval"
* }
* },
* descriptionPrefix: "Database operation pending approval"
* });
* ```
*
* @example
* Using dynamic callable descriptions
* ```typescript
* import { type DescriptionFactory, type ToolCall } from "langchain";
* import type { AgentBuiltInState, Runtime } from "langchain/agents";
*
* // Define a dynamic description factory
* const formatToolDescription: DescriptionFactory = (
* toolCall: ToolCall,
* state: AgentBuiltInState,
* runtime: Runtime<unknown>
* ) => {
* return `Tool: ${toolCall.name}\nArguments:\n${JSON.stringify(toolCall.args, null, 2)}`;
* };
*
* const hitlMiddleware = humanInTheLoopMiddleware({
* interruptOn: {
* "write_file": {
* allowedDecisions: ["approve", "edit"],
* // Use dynamic description that can access tool call, state, and runtime
* description: formatToolDescription
* },
* // Or use an inline function
* "send_email": {
* allowedDecisions: ["approve", "reject"],
* description: (toolCall, state, runtime) => {
* const { to, subject } = toolCall.args;
* return `Email to ${to}\nSubject: ${subject}\n\nRequires approval before sending`;
* }
* }
* }
* });
* ```
*
* @remarks
* - Tool calls are processed in the order they appear in the AI message
* - Auto-approved tools execute immediately without interruption
* - In the default `"batched"` mode, multiple tools requiring approval are bundled into a
* single interrupt request, raised in the `afterModel` phase before any tool runs. In
* `"per_call"` mode, each gated call raises its own interrupt from `wrapToolCall`
* - Requires a checkpointer to maintain state across interruptions
*
* @see {@link createAgent} for agent creation
* @see {@link Command} for resuming interrupted execution
* @public
*/
export declare function humanInTheLoopMiddleware(options: NonNullable<HumanInTheLoopMiddlewareConfig>): AgentMiddleware<undefined, z.ZodObject<{
/**
* Mapping of tool name to allowed reviewer responses.
* If a tool doesn't have an entry, it's auto-approved by default.
*
* - `true` -> pause for approval and allow every decision (approve/edit/reject/respond)
* - `false` -> auto-approve (no human review)
* - `InterruptOnConfig` -> explicitly specify which decisions are allowed for this tool
*/
interruptOn: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodBoolean, z.ZodObject<{
/**
* The decisions that are allowed for this action.
*/
allowedDecisions: z.ZodArray<z.ZodEnum<["approve", "edit", "reject", "respond"]>, "many">;
/**
* The description attached to the request for human input.
* Can be either:
* - A static string describing the approval request
* - A callable that dynamically generates the description based on agent state,
* runtime, and tool call information
*
* @example
* Static string description
* ```typescript
* import type { InterruptOnConfig } from "langchain";
*
* const config: InterruptOnConfig = {
* allowedDecisions: ["approve", "reject"],
* description: "Please review this tool execution"
* };
* ```
*
* @example
* Dynamic callable description
* ```typescript
* import type {
* AgentBuiltInState,
* Runtime,
* DescriptionFactory,
* ToolCall,
* InterruptOnConfig
* } from "langchain";
*
* const formatToolDescription: DescriptionFactory = (
* toolCall: ToolCall,
* state: AgentBuiltInState,
* runtime: Runtime<unknown>
* ) => {
* return `Tool: ${toolCall.name}\nArguments:\n${JSON.stringify(toolCall.args, null, 2)}`;
* };
*
* const config: InterruptOnConfig = {
* allowedDecisions: ["approve", "edit"],
* description: formatToolDescription
* };
* ```
*/
description: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodFunction<z.ZodTuple<[z.ZodType<ToolCall<string, Record<string, any>>, z.ZodTypeDef, ToolCall<string, Record<string, any>>>, z.ZodType<AgentBuiltInState, z.ZodTypeDef, AgentBuiltInState>, z.ZodType<Runtime<unknown>, z.ZodTypeDef, Runtime<unknown>>], z.ZodUnknown>, z.ZodUnion<[z.ZodString, z.ZodPromise<z.ZodString>]>>]>>;
/**
* JSON schema for the arguments associated with the action, if edits are allowed.
*
* Not used in `"per_call"` mode: an edit's `args` are checked against the tool's
* own schema there.
*/
argsSchema: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
/**
* Optional predicate controlling whether to interrupt for a given tool call.
*
* Receives a {@link ToolCallRequest} and returns `true` to interrupt or
* `false` to auto-approve the tool call.
*
* The request is constructed with `tool` set to `undefined` and `runtime` set
* to the node-level {@link Runtime}, so `request.tool` is not available. In
* `"per_call"` mode the predicate runs in `wrapToolCall` and receives the real
* request, with `tool` set.
*
* @example
* ```typescript
* import type { InterruptOnConfig } from "langchain";
*
* // Only interrupt delete_file calls targeting /etc
* const config: InterruptOnConfig = {
* allowedDecisions: ["approve", "reject"],
* when: (request) =>
* String(request.toolCall.args.path ?? "").startsWith("/etc"),
* };
* ```
*/
when: z.ZodOptional<z.ZodFunction<z.ZodTuple<[z.ZodType<ToolCallRequest<AgentBuiltInState, unknown>, z.ZodTypeDef, ToolCallRequest<AgentBuiltInState, unknown>>], z.ZodUnknown>, z.ZodUnion<[z.ZodBoolean, z.ZodPromise<z.ZodBoolean>]>>>;
}, "strip", z.ZodTypeAny, {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}, {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}>]>>>;
/**
* Prefix used when constructing human-facing approval messages.
* Provides context about the tool call being reviewed; does not change the underlying action.
*
* Note: This prefix is only applied for tools that do not provide a custom
* `description` via their {@link InterruptOnConfig}. If a tool specifies a custom
* `description`, that per-tool text is used and this prefix is ignored.
*/
descriptionPrefix: z.ZodDefault<z.ZodString>;
}, "strip", z.ZodTypeAny, {
interruptOn?: Record<string, boolean | {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}> | undefined;
descriptionPrefix: string;
}, {
interruptOn?: Record<string, boolean | {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}> | undefined;
descriptionPrefix?: string | undefined;
}>, {
interruptOn?: Record<string, boolean | {
allowedDecisions: ("approve" | "edit" | "reject" | "respond")[];
description?: string | ((args_0: ToolCall<string, Record<string, any>>, args_1: AgentBuiltInState, args_2: Runtime<unknown>, ...args: unknown[]) => string | Promise<string>) | undefined;
argsSchema?: Record<string, any> | undefined;
when?: ((args_0: ToolCallRequest<AgentBuiltInState, unknown>, ...args: unknown[]) => boolean | Promise<boolean>) | undefined;
}> | undefined;
descriptionPrefix: string;
}, readonly (import("@langchain/core/tools").ServerTool | import("@langchain/core/tools").ClientTool)[], readonly []>;
export { DecisionType };
//# sourceMappingURL=hitl.d.cts.map