langchain 1.2.24 → 1.2.25
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.
- package/CHANGELOG.md +11 -0
- package/dist/_virtual/{rolldown_runtime.cjs → _rolldown/runtime.cjs} +24 -13
- package/dist/_virtual/_rolldown/runtime.js +36 -0
- package/dist/agents/ReactAgent.cjs +52 -65
- package/dist/agents/ReactAgent.cjs.map +1 -1
- package/dist/agents/ReactAgent.d.cts.map +1 -1
- package/dist/agents/ReactAgent.d.ts.map +1 -1
- package/dist/agents/ReactAgent.js +11 -24
- package/dist/agents/ReactAgent.js.map +1 -1
- package/dist/agents/RunnableCallable.cjs +7 -7
- package/dist/agents/RunnableCallable.cjs.map +1 -1
- package/dist/agents/RunnableCallable.js.map +1 -1
- package/dist/agents/annotation.cjs +41 -37
- package/dist/agents/annotation.cjs.map +1 -1
- package/dist/agents/annotation.js +21 -17
- package/dist/agents/annotation.js.map +1 -1
- package/dist/agents/constants.d.cts.map +1 -1
- package/dist/agents/constants.d.ts.map +1 -1
- package/dist/agents/errors.cjs +3 -3
- package/dist/agents/errors.cjs.map +1 -1
- package/dist/agents/errors.d.cts.map +1 -1
- package/dist/agents/errors.d.ts.map +1 -1
- package/dist/agents/errors.js.map +1 -1
- package/dist/agents/index.cjs.map +1 -1
- package/dist/agents/index.d.cts +2 -2
- package/dist/agents/index.d.cts.map +1 -1
- package/dist/agents/index.d.ts +2 -2
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/middleware/constants.cjs +2 -2
- package/dist/agents/middleware/constants.cjs.map +1 -1
- package/dist/agents/middleware/constants.js.map +1 -1
- package/dist/agents/middleware/contextEditing.cjs +14 -27
- package/dist/agents/middleware/contextEditing.cjs.map +1 -1
- package/dist/agents/middleware/contextEditing.d.cts +2 -3
- package/dist/agents/middleware/contextEditing.d.cts.map +1 -1
- package/dist/agents/middleware/contextEditing.d.ts +2 -3
- package/dist/agents/middleware/contextEditing.d.ts.map +1 -1
- package/dist/agents/middleware/contextEditing.js +5 -18
- package/dist/agents/middleware/contextEditing.js.map +1 -1
- package/dist/agents/middleware/dynamicSystemPrompt.cjs +3 -4
- package/dist/agents/middleware/dynamicSystemPrompt.cjs.map +1 -1
- package/dist/agents/middleware/dynamicSystemPrompt.d.cts +2 -2
- package/dist/agents/middleware/dynamicSystemPrompt.d.cts.map +1 -1
- package/dist/agents/middleware/dynamicSystemPrompt.d.ts +2 -2
- package/dist/agents/middleware/dynamicSystemPrompt.d.ts.map +1 -1
- package/dist/agents/middleware/dynamicSystemPrompt.js +1 -2
- package/dist/agents/middleware/dynamicSystemPrompt.js.map +1 -1
- package/dist/agents/middleware/error.cjs +2 -2
- package/dist/agents/middleware/error.cjs.map +1 -1
- package/dist/agents/middleware/error.js.map +1 -1
- package/dist/agents/middleware/hitl.cjs +16 -26
- package/dist/agents/middleware/hitl.cjs.map +1 -1
- package/dist/agents/middleware/hitl.d.cts.map +1 -1
- package/dist/agents/middleware/hitl.d.ts.map +1 -1
- package/dist/agents/middleware/hitl.js +8 -18
- package/dist/agents/middleware/hitl.js.map +1 -1
- package/dist/agents/middleware/index.js +2 -0
- package/dist/agents/middleware/llmToolSelector.cjs +9 -10
- package/dist/agents/middleware/llmToolSelector.cjs.map +1 -1
- package/dist/agents/middleware/llmToolSelector.d.cts +4 -4
- package/dist/agents/middleware/llmToolSelector.d.cts.map +1 -1
- package/dist/agents/middleware/llmToolSelector.d.ts +4 -4
- package/dist/agents/middleware/llmToolSelector.d.ts.map +1 -1
- package/dist/agents/middleware/llmToolSelector.js +3 -4
- package/dist/agents/middleware/llmToolSelector.js.map +1 -1
- package/dist/agents/middleware/modelCallLimit.cjs +5 -5
- package/dist/agents/middleware/modelCallLimit.cjs.map +1 -1
- package/dist/agents/middleware/modelCallLimit.d.cts +2 -2
- package/dist/agents/middleware/modelCallLimit.d.cts.map +1 -1
- package/dist/agents/middleware/modelCallLimit.d.ts +2 -2
- package/dist/agents/middleware/modelCallLimit.d.ts.map +1 -1
- package/dist/agents/middleware/modelCallLimit.js.map +1 -1
- package/dist/agents/middleware/modelFallback.cjs.map +1 -1
- package/dist/agents/middleware/modelFallback.d.cts +0 -1
- package/dist/agents/middleware/modelFallback.d.cts.map +1 -1
- package/dist/agents/middleware/modelFallback.d.ts +0 -1
- package/dist/agents/middleware/modelFallback.d.ts.map +1 -1
- package/dist/agents/middleware/modelFallback.js.map +1 -1
- package/dist/agents/middleware/modelRetry.cjs +19 -17
- package/dist/agents/middleware/modelRetry.cjs.map +1 -1
- package/dist/agents/middleware/modelRetry.d.cts +0 -1
- package/dist/agents/middleware/modelRetry.d.cts.map +1 -1
- package/dist/agents/middleware/modelRetry.d.ts +0 -1
- package/dist/agents/middleware/modelRetry.d.ts.map +1 -1
- package/dist/agents/middleware/modelRetry.js +15 -13
- package/dist/agents/middleware/modelRetry.js.map +1 -1
- package/dist/agents/middleware/pii.cjs +21 -32
- package/dist/agents/middleware/pii.cjs.map +1 -1
- package/dist/agents/middleware/pii.d.cts +0 -1
- package/dist/agents/middleware/pii.d.cts.map +1 -1
- package/dist/agents/middleware/pii.d.ts +0 -1
- package/dist/agents/middleware/pii.d.ts.map +1 -1
- package/dist/agents/middleware/pii.js +10 -21
- package/dist/agents/middleware/pii.js.map +1 -1
- package/dist/agents/middleware/piiRedaction.cjs +22 -28
- package/dist/agents/middleware/piiRedaction.cjs.map +1 -1
- package/dist/agents/middleware/piiRedaction.d.cts +0 -1
- package/dist/agents/middleware/piiRedaction.d.cts.map +1 -1
- package/dist/agents/middleware/piiRedaction.d.ts +0 -1
- package/dist/agents/middleware/piiRedaction.d.ts.map +1 -1
- package/dist/agents/middleware/piiRedaction.js +8 -14
- package/dist/agents/middleware/piiRedaction.js.map +1 -1
- package/dist/agents/middleware/provider/anthropic/promptCaching.cjs +5 -8
- package/dist/agents/middleware/provider/anthropic/promptCaching.cjs.map +1 -1
- package/dist/agents/middleware/provider/anthropic/promptCaching.d.cts +2 -2
- package/dist/agents/middleware/provider/anthropic/promptCaching.d.cts.map +1 -1
- package/dist/agents/middleware/provider/anthropic/promptCaching.d.ts +2 -2
- package/dist/agents/middleware/provider/anthropic/promptCaching.d.ts.map +1 -1
- package/dist/agents/middleware/provider/anthropic/promptCaching.js +3 -6
- package/dist/agents/middleware/provider/anthropic/promptCaching.js.map +1 -1
- package/dist/agents/middleware/provider/openai/moderation.cjs +17 -22
- package/dist/agents/middleware/provider/openai/moderation.cjs.map +1 -1
- package/dist/agents/middleware/provider/openai/moderation.d.cts +0 -2
- package/dist/agents/middleware/provider/openai/moderation.d.cts.map +1 -1
- package/dist/agents/middleware/provider/openai/moderation.d.ts +0 -2
- package/dist/agents/middleware/provider/openai/moderation.d.ts.map +1 -1
- package/dist/agents/middleware/provider/openai/moderation.js +10 -15
- package/dist/agents/middleware/provider/openai/moderation.js.map +1 -1
- package/dist/agents/middleware/summarization.cjs +39 -62
- package/dist/agents/middleware/summarization.cjs.map +1 -1
- package/dist/agents/middleware/summarization.d.cts +2 -2
- package/dist/agents/middleware/summarization.d.cts.map +1 -1
- package/dist/agents/middleware/summarization.d.ts +2 -2
- package/dist/agents/middleware/summarization.d.ts.map +1 -1
- package/dist/agents/middleware/summarization.js +17 -40
- package/dist/agents/middleware/summarization.js.map +1 -1
- package/dist/agents/middleware/todoListMiddleware.cjs +21 -27
- package/dist/agents/middleware/todoListMiddleware.cjs.map +1 -1
- package/dist/agents/middleware/todoListMiddleware.d.cts +4 -4
- package/dist/agents/middleware/todoListMiddleware.d.cts.map +1 -1
- package/dist/agents/middleware/todoListMiddleware.d.ts +4 -4
- package/dist/agents/middleware/todoListMiddleware.d.ts.map +1 -1
- package/dist/agents/middleware/todoListMiddleware.js +12 -18
- package/dist/agents/middleware/todoListMiddleware.js.map +1 -1
- package/dist/agents/middleware/toolCallLimit.cjs +24 -46
- package/dist/agents/middleware/toolCallLimit.cjs.map +1 -1
- package/dist/agents/middleware/toolCallLimit.d.cts +2 -3
- package/dist/agents/middleware/toolCallLimit.d.cts.map +1 -1
- package/dist/agents/middleware/toolCallLimit.d.ts +2 -3
- package/dist/agents/middleware/toolCallLimit.d.ts.map +1 -1
- package/dist/agents/middleware/toolCallLimit.js +17 -39
- package/dist/agents/middleware/toolCallLimit.js.map +1 -1
- package/dist/agents/middleware/toolEmulator.cjs +8 -13
- package/dist/agents/middleware/toolEmulator.cjs.map +1 -1
- package/dist/agents/middleware/toolEmulator.d.cts +0 -1
- package/dist/agents/middleware/toolEmulator.d.cts.map +1 -1
- package/dist/agents/middleware/toolEmulator.d.ts +0 -1
- package/dist/agents/middleware/toolEmulator.d.ts.map +1 -1
- package/dist/agents/middleware/toolEmulator.js +5 -10
- package/dist/agents/middleware/toolEmulator.js.map +1 -1
- package/dist/agents/middleware/toolRetry.cjs +19 -17
- package/dist/agents/middleware/toolRetry.cjs.map +1 -1
- package/dist/agents/middleware/toolRetry.d.cts +0 -1
- package/dist/agents/middleware/toolRetry.d.cts.map +1 -1
- package/dist/agents/middleware/toolRetry.d.ts +0 -1
- package/dist/agents/middleware/toolRetry.d.ts.map +1 -1
- package/dist/agents/middleware/toolRetry.js +15 -13
- package/dist/agents/middleware/toolRetry.js.map +1 -1
- package/dist/agents/middleware/types.cjs.map +1 -1
- package/dist/agents/middleware/types.d.cts +33 -33
- package/dist/agents/middleware/types.d.cts.map +1 -1
- package/dist/agents/middleware/types.d.ts +33 -33
- package/dist/agents/middleware/types.d.ts.map +1 -1
- package/dist/agents/middleware/types.js.map +1 -1
- package/dist/agents/middleware/utils.cjs +8 -8
- package/dist/agents/middleware/utils.cjs.map +1 -1
- package/dist/agents/middleware/utils.d.cts +0 -1
- package/dist/agents/middleware/utils.d.cts.map +1 -1
- package/dist/agents/middleware/utils.d.ts +0 -1
- package/dist/agents/middleware/utils.d.ts.map +1 -1
- package/dist/agents/middleware/utils.js +3 -3
- package/dist/agents/middleware/utils.js.map +1 -1
- package/dist/agents/middleware.cjs +1 -2
- package/dist/agents/middleware.cjs.map +1 -1
- package/dist/agents/middleware.d.cts +0 -1
- package/dist/agents/middleware.d.cts.map +1 -1
- package/dist/agents/middleware.d.ts +0 -1
- package/dist/agents/middleware.d.ts.map +1 -1
- package/dist/agents/middleware.js +1 -2
- package/dist/agents/middleware.js.map +1 -1
- package/dist/agents/model.cjs.map +1 -1
- package/dist/agents/model.js.map +1 -1
- package/dist/agents/nodes/AfterAgentNode.cjs +1 -2
- package/dist/agents/nodes/AfterAgentNode.cjs.map +1 -1
- package/dist/agents/nodes/AfterAgentNode.js +1 -2
- package/dist/agents/nodes/AfterAgentNode.js.map +1 -1
- package/dist/agents/nodes/AfterModelNode.cjs +1 -2
- package/dist/agents/nodes/AfterModelNode.cjs.map +1 -1
- package/dist/agents/nodes/AfterModelNode.js +1 -2
- package/dist/agents/nodes/AfterModelNode.js.map +1 -1
- package/dist/agents/nodes/AgentNode.cjs +46 -58
- package/dist/agents/nodes/AgentNode.cjs.map +1 -1
- package/dist/agents/nodes/AgentNode.js +16 -28
- package/dist/agents/nodes/AgentNode.js.map +1 -1
- package/dist/agents/nodes/BeforeAgentNode.cjs +1 -2
- package/dist/agents/nodes/BeforeAgentNode.cjs.map +1 -1
- package/dist/agents/nodes/BeforeAgentNode.js +1 -2
- package/dist/agents/nodes/BeforeAgentNode.js.map +1 -1
- package/dist/agents/nodes/BeforeModelNode.cjs +1 -2
- package/dist/agents/nodes/BeforeModelNode.cjs.map +1 -1
- package/dist/agents/nodes/BeforeModelNode.js +1 -2
- package/dist/agents/nodes/BeforeModelNode.js.map +1 -1
- package/dist/agents/nodes/ToolNode.cjs +25 -25
- package/dist/agents/nodes/ToolNode.cjs.map +1 -1
- package/dist/agents/nodes/ToolNode.js +6 -6
- package/dist/agents/nodes/ToolNode.js.map +1 -1
- package/dist/agents/nodes/middleware.cjs +3 -3
- package/dist/agents/nodes/middleware.cjs.map +1 -1
- package/dist/agents/nodes/middleware.js.map +1 -1
- package/dist/agents/nodes/types.d.cts +0 -1
- package/dist/agents/nodes/types.d.cts.map +1 -1
- package/dist/agents/nodes/types.d.ts +0 -1
- package/dist/agents/nodes/types.d.ts.map +1 -1
- package/dist/agents/nodes/utils.cjs +16 -17
- package/dist/agents/nodes/utils.cjs.map +1 -1
- package/dist/agents/nodes/utils.js +2 -3
- package/dist/agents/nodes/utils.js.map +1 -1
- package/dist/agents/responses.cjs +20 -30
- package/dist/agents/responses.cjs.map +1 -1
- package/dist/agents/responses.d.cts +0 -1
- package/dist/agents/responses.d.cts.map +1 -1
- package/dist/agents/responses.d.ts +0 -1
- package/dist/agents/responses.d.ts.map +1 -1
- package/dist/agents/responses.js +12 -22
- package/dist/agents/responses.js.map +1 -1
- package/dist/agents/runtime.d.cts +0 -1
- package/dist/agents/runtime.d.cts.map +1 -1
- package/dist/agents/runtime.d.ts +0 -1
- package/dist/agents/runtime.d.ts.map +1 -1
- package/dist/agents/state.cjs +1 -2
- package/dist/agents/state.cjs.map +1 -1
- package/dist/agents/state.js +1 -2
- package/dist/agents/state.js.map +1 -1
- package/dist/agents/tests/utils.cjs +21 -26
- package/dist/agents/tests/utils.cjs.map +1 -1
- package/dist/agents/tests/utils.d.cts +0 -1
- package/dist/agents/tests/utils.d.cts.map +1 -1
- package/dist/agents/tests/utils.d.ts +0 -1
- package/dist/agents/tests/utils.d.ts.map +1 -1
- package/dist/agents/tests/utils.js +12 -17
- package/dist/agents/tests/utils.js.map +1 -1
- package/dist/agents/types.d.cts +4 -5
- package/dist/agents/types.d.cts.map +1 -1
- package/dist/agents/types.d.ts +4 -5
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/utils.cjs +32 -32
- package/dist/agents/utils.cjs.map +1 -1
- package/dist/agents/utils.js +6 -6
- package/dist/agents/utils.js.map +1 -1
- package/dist/agents/withAgentName.cjs +5 -5
- package/dist/agents/withAgentName.cjs.map +1 -1
- package/dist/agents/withAgentName.js.map +1 -1
- package/dist/chat_models/universal.cjs +34 -41
- package/dist/chat_models/universal.cjs.map +1 -1
- package/dist/chat_models/universal.d.cts.map +1 -1
- package/dist/chat_models/universal.d.ts.map +1 -1
- package/dist/chat_models/universal.js +21 -29
- package/dist/chat_models/universal.js.map +1 -1
- package/dist/hub/base.cjs +3 -4
- package/dist/hub/base.cjs.map +1 -1
- package/dist/hub/base.d.cts +0 -1
- package/dist/hub/base.d.cts.map +1 -1
- package/dist/hub/base.d.ts +0 -1
- package/dist/hub/base.d.ts.map +1 -1
- package/dist/hub/base.js +1 -2
- package/dist/hub/base.js.map +1 -1
- package/dist/hub/index.cjs +2 -2
- package/dist/hub/index.cjs.map +1 -1
- package/dist/hub/index.d.cts +0 -1
- package/dist/hub/index.d.cts.map +1 -1
- package/dist/hub/index.d.ts +0 -1
- package/dist/hub/index.d.ts.map +1 -1
- package/dist/hub/index.js +1 -2
- package/dist/hub/index.js.map +1 -1
- package/dist/hub/node.cjs +7 -13
- package/dist/hub/node.cjs.map +1 -1
- package/dist/hub/node.d.cts +0 -1
- package/dist/hub/node.d.cts.map +1 -1
- package/dist/hub/node.d.ts +0 -1
- package/dist/hub/node.d.ts.map +1 -1
- package/dist/hub/node.js +6 -13
- package/dist/hub/node.js.map +1 -1
- package/dist/index.cjs +48 -48
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -3
- package/dist/load/import_constants.cjs.map +1 -1
- package/dist/load/import_constants.js.map +1 -1
- package/dist/load/import_map.cjs +46 -47
- package/dist/load/import_map.cjs.map +1 -1
- package/dist/load/import_map.js +2 -3
- package/dist/load/import_map.js.map +1 -1
- package/dist/load/import_type.d.cts.map +1 -1
- package/dist/load/import_type.d.ts.map +1 -1
- package/dist/load/index.cjs +13 -3
- package/dist/load/index.cjs.map +1 -1
- package/dist/load/index.d.cts +9 -1
- package/dist/load/index.d.cts.map +1 -1
- package/dist/load/index.d.ts +9 -1
- package/dist/load/index.d.ts.map +1 -1
- package/dist/load/index.js +9 -0
- package/dist/load/index.js.map +1 -1
- package/dist/load/serializable.cjs +7 -6
- package/dist/load/serializable.js +4 -4
- package/dist/storage/encoder_backed.cjs +9 -10
- package/dist/storage/encoder_backed.cjs.map +1 -1
- package/dist/storage/encoder_backed.d.cts +0 -1
- package/dist/storage/encoder_backed.d.cts.map +1 -1
- package/dist/storage/encoder_backed.d.ts +0 -1
- package/dist/storage/encoder_backed.d.ts.map +1 -1
- package/dist/storage/encoder_backed.js +4 -6
- package/dist/storage/encoder_backed.js.map +1 -1
- package/dist/storage/file_system.cjs +12 -11
- package/dist/storage/file_system.cjs.map +1 -1
- package/dist/storage/file_system.d.cts +0 -1
- package/dist/storage/file_system.d.cts.map +1 -1
- package/dist/storage/file_system.d.ts +0 -1
- package/dist/storage/file_system.d.ts.map +1 -1
- package/dist/storage/file_system.js +5 -7
- package/dist/storage/file_system.js.map +1 -1
- package/dist/storage/in_memory.cjs +5 -5
- package/dist/storage/in_memory.js +2 -3
- package/package.json +6 -6
- package/dist/_virtual/rolldown_runtime.js +0 -25
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextEditing.js","names":["config: ClearToolUsesEditConfig","trigger: ContextSize | ContextSize[] | undefined","keep: KeepSize | undefined","#triggerConditions","params: {\n messages: BaseMessage[];\n model: BaseLanguageModel;\n countTokens: TokenCounter;\n }","orphanedIndices: number[]","#findAIMessageForToolCall","#shouldEdit","candidates: { idx: number; msg: ToolMessage }[]","#determineKeepCount","#buildClearedToolInputMessage","messages: BaseMessage[]","totalTokens: number","model: BaseLanguageModel","candidates: Array<{ idx: number; msg: ToolMessage }>","countTokens: TokenCounter","previousMessages: BaseMessage[]","toolCallId: string","message: AIMessage","config: ContextEditingMiddlewareConfig"],"sources":["../../../src/agents/middleware/contextEditing.ts"],"sourcesContent":["/**\n * Context editing middleware.\n *\n * This middleware mirrors Anthropic's context editing capabilities by clearing\n * older tool results once the conversation grows beyond a configurable token\n * threshold. The implementation is intentionally model-agnostic so it can be used\n * with any LangChain chat model.\n */\n\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport type { BaseLanguageModel } from \"@langchain/core/language_models/base\";\nimport {\n AIMessage,\n ToolMessage,\n SystemMessage,\n} from \"@langchain/core/messages\";\n\nimport { countTokensApproximately } from \"./utils.js\";\nimport { createMiddleware } from \"../middleware.js\";\nimport {\n getProfileLimits,\n contextSizeSchema,\n keepSchema,\n type ContextSize,\n type KeepSize,\n type TokenCounter,\n} from \"./summarization.js\";\n\nconst DEFAULT_TOOL_PLACEHOLDER = \"[cleared]\";\nconst DEFAULT_TRIGGER_TOKENS = 100_000;\nconst DEFAULT_KEEP = 3;\n\n/**\n * Protocol describing a context editing strategy.\n *\n * Implement this interface to create custom strategies for managing\n * conversation context size. The `apply` method should modify the\n * messages array in-place and return the updated token count.\n *\n * @example\n * ```ts\n * import { HumanMessage, type ContextEdit, type BaseMessage } from \"langchain\";\n *\n * class RemoveOldHumanMessages implements ContextEdit {\n * constructor(private keepRecent: number = 10) {}\n *\n * async apply({ messages, countTokens }) {\n * // Check current token count\n * const tokens = await countTokens(messages);\n *\n * // Remove old human messages if over limit, keeping the most recent ones\n * if (tokens > 50000) {\n * const humanMessages: number[] = [];\n *\n * // Find all human message indices\n * for (let i = 0; i < messages.length; i++) {\n * if (HumanMessage.isInstance(messages[i])) {\n * humanMessages.push(i);\n * }\n * }\n *\n * // Remove old human messages (keep only the most recent N)\n * const toRemove = humanMessages.slice(0, -this.keepRecent);\n * for (let i = toRemove.length - 1; i >= 0; i--) {\n * messages.splice(toRemove[i]!, 1);\n * }\n * }\n * }\n * }\n * ```\n */\nexport interface ContextEdit {\n /**\n * Apply an edit to the message list, returning the new token count.\n *\n * This method should:\n * 1. Check if editing is needed based on `tokens` parameter\n * 2. Modify the `messages` array in-place (if needed)\n * 3. Return the new token count after modifications\n *\n * @param params - Parameters for the editing operation\n * @returns The updated token count after applying edits\n */\n apply(params: {\n /**\n * Array of messages to potentially edit (modify in-place)\n */\n messages: BaseMessage[];\n /**\n * Function to count tokens in a message array\n */\n countTokens: TokenCounter;\n /**\n * Optional model instance for model profile information\n */\n model?: BaseLanguageModel;\n }): void | Promise<void>;\n}\n\n/**\n * Configuration for clearing tool outputs when token limits are exceeded.\n */\nexport interface ClearToolUsesEditConfig {\n /**\n * Trigger conditions for context editing.\n * Can be a single condition object (all properties must be met) or an array of conditions (any condition must be met).\n *\n * @example\n * ```ts\n * // Single condition: trigger if tokens >= 100000 AND messages >= 50\n * trigger: { tokens: 100000, messages: 50 }\n *\n * // Multiple conditions: trigger if (tokens >= 100000 AND messages >= 50) OR (tokens >= 50000 AND messages >= 100)\n * trigger: [\n * { tokens: 100000, messages: 50 },\n * { tokens: 50000, messages: 100 }\n * ]\n *\n * // Fractional trigger: trigger at 80% of model's max input tokens\n * trigger: { fraction: 0.8 }\n * ```\n */\n trigger?: ContextSize | ContextSize[];\n\n /**\n * Context retention policy applied after editing.\n * Specify how many tool results to preserve using messages, tokens, or fraction.\n *\n * @example\n * ```ts\n * // Keep 3 most recent tool results\n * keep: { messages: 3 }\n *\n * // Keep tool results that fit within 1000 tokens\n * keep: { tokens: 1000 }\n *\n * // Keep tool results that fit within 30% of model's max input tokens\n * keep: { fraction: 0.3 }\n * ```\n */\n keep?: KeepSize;\n\n /**\n * Whether to clear the originating tool call parameters on the AI message.\n * @default false\n */\n clearToolInputs?: boolean;\n\n /**\n * List of tool names to exclude from clearing.\n * @default []\n */\n excludeTools?: string[];\n\n /**\n * Placeholder text inserted for cleared tool outputs.\n * @default \"[cleared]\"\n */\n placeholder?: string;\n\n /**\n * @deprecated Use `trigger: { tokens: value }` instead.\n */\n triggerTokens?: number;\n\n /**\n * @deprecated Use `keep: { messages: value }` instead.\n */\n keepMessages?: number;\n\n /**\n * @deprecated This property is deprecated and will be removed in a future version.\n * Use `keep: { tokens: value }` or `keep: { messages: value }` instead to control retention.\n */\n clearAtLeast?: number;\n}\n\n/**\n * Strategy for clearing tool outputs when token limits are exceeded.\n *\n * This strategy mirrors Anthropic's `clear_tool_uses_20250919` behavior by\n * replacing older tool results with a placeholder text when the conversation\n * grows too large. It preserves the most recent tool results and can exclude\n * specific tools from being cleared.\n *\n * @example\n * ```ts\n * import { ClearToolUsesEdit } from \"langchain\";\n *\n * const edit = new ClearToolUsesEdit({\n * trigger: { tokens: 100000 }, // Start clearing at 100K tokens\n * keep: { messages: 3 }, // Keep 3 most recent tool results\n * excludeTools: [\"important\"], // Never clear \"important\" tool\n * clearToolInputs: false, // Keep tool call arguments\n * placeholder: \"[cleared]\", // Replacement text\n * });\n *\n * // Multiple trigger conditions\n * const edit2 = new ClearToolUsesEdit({\n * trigger: [\n * { tokens: 100000, messages: 50 },\n * { tokens: 50000, messages: 100 }\n * ],\n * keep: { messages: 3 },\n * });\n *\n * // Fractional trigger with model profile\n * const edit3 = new ClearToolUsesEdit({\n * trigger: { fraction: 0.8 }, // Trigger at 80% of model's max tokens\n * keep: { fraction: 0.3 }, // Keep 30% of model's max tokens\n * });\n * ```\n */\nexport class ClearToolUsesEdit implements ContextEdit {\n #triggerConditions: ContextSize[];\n\n trigger: ContextSize | ContextSize[];\n keep: KeepSize;\n clearToolInputs: boolean;\n excludeTools: Set<string>;\n placeholder: string;\n model: BaseLanguageModel;\n clearAtLeast: number;\n\n constructor(config: ClearToolUsesEditConfig = {}) {\n // Handle deprecated parameters\n let trigger: ContextSize | ContextSize[] | undefined = config.trigger;\n if (config.triggerTokens !== undefined) {\n console.warn(\n \"triggerTokens is deprecated. Use `trigger: { tokens: value }` instead.\"\n );\n if (trigger === undefined) {\n trigger = { tokens: config.triggerTokens };\n }\n }\n\n let keep: KeepSize | undefined = config.keep;\n if (config.keepMessages !== undefined) {\n console.warn(\n \"keepMessages is deprecated. Use `keep: { messages: value }` instead.\"\n );\n if (keep === undefined) {\n keep = { messages: config.keepMessages };\n }\n }\n\n // Set defaults\n if (trigger === undefined) {\n trigger = { tokens: DEFAULT_TRIGGER_TOKENS };\n }\n if (keep === undefined) {\n keep = { messages: DEFAULT_KEEP };\n }\n\n // Validate trigger conditions\n if (Array.isArray(trigger)) {\n this.#triggerConditions = trigger.map((t) => contextSizeSchema.parse(t));\n this.trigger = this.#triggerConditions;\n } else {\n const validated = contextSizeSchema.parse(trigger);\n this.#triggerConditions = [validated];\n this.trigger = validated;\n }\n\n // Validate keep\n const validatedKeep = keepSchema.parse(keep);\n this.keep = validatedKeep;\n\n // Handle deprecated clearAtLeast\n if (config.clearAtLeast !== undefined) {\n console.warn(\n \"clearAtLeast is deprecated and will be removed in a future version. \" +\n \"It conflicts with the `keep` property. Use `keep: { tokens: value }` or \" +\n \"`keep: { messages: value }` instead to control retention.\"\n );\n }\n this.clearAtLeast = config.clearAtLeast ?? 0;\n\n this.clearToolInputs = config.clearToolInputs ?? false;\n this.excludeTools = new Set(config.excludeTools ?? []);\n this.placeholder = config.placeholder ?? DEFAULT_TOOL_PLACEHOLDER;\n }\n\n async apply(params: {\n messages: BaseMessage[];\n model: BaseLanguageModel;\n countTokens: TokenCounter;\n }): Promise<void> {\n const { messages, model, countTokens } = params;\n const tokens = await countTokens(messages);\n\n /**\n * Always remove orphaned tool messages (those without corresponding AI messages)\n * regardless of whether editing is triggered\n */\n const orphanedIndices: number[] = [];\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (ToolMessage.isInstance(msg)) {\n // Check if this tool message has a corresponding AI message\n const aiMessage = this.#findAIMessageForToolCall(\n messages.slice(0, i),\n msg.tool_call_id\n );\n\n if (!aiMessage) {\n // Orphaned tool message - mark for removal\n orphanedIndices.push(i);\n } else {\n // Check if the AI message actually has this tool call\n const toolCall = aiMessage.tool_calls?.find(\n (call) => call.id === msg.tool_call_id\n );\n if (!toolCall) {\n // Orphaned tool message - mark for removal\n orphanedIndices.push(i);\n }\n }\n }\n }\n\n /**\n * Remove orphaned tool messages in reverse order to maintain indices\n */\n for (let i = orphanedIndices.length - 1; i >= 0; i--) {\n messages.splice(orphanedIndices[i]!, 1);\n }\n\n /**\n * Recalculate tokens after removing orphaned messages\n */\n let currentTokens = tokens;\n if (orphanedIndices.length > 0) {\n currentTokens = await countTokens(messages);\n }\n\n /**\n * Check if editing should be triggered\n */\n if (!this.#shouldEdit(messages, currentTokens, model)) {\n return;\n }\n\n /**\n * Find all tool message candidates with their actual indices in the messages array\n */\n const candidates: { idx: number; msg: ToolMessage }[] = [];\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (ToolMessage.isInstance(msg)) {\n candidates.push({ idx: i, msg });\n }\n }\n\n if (candidates.length === 0) {\n return;\n }\n\n /**\n * Determine how many tool results to keep based on keep policy\n */\n const keepCount = await this.#determineKeepCount(\n candidates,\n countTokens,\n model\n );\n\n /**\n * Keep the most recent tool messages based on keep policy\n */\n const candidatesToClear =\n keepCount >= candidates.length\n ? []\n : keepCount > 0\n ? candidates.slice(0, -keepCount)\n : candidates;\n\n /**\n * If clearAtLeast is set, we may need to clear more messages to meet the token requirement\n * This is a deprecated feature that conflicts with keep, but we support it for backwards compatibility\n */\n let clearedTokens = 0;\n const initialCandidatesToClear = [...candidatesToClear];\n\n for (const { idx, msg: toolMessage } of initialCandidatesToClear) {\n /**\n * Skip if already cleared\n */\n const contextEditing = toolMessage.response_metadata?.context_editing as\n | { cleared?: boolean }\n | undefined;\n if (contextEditing?.cleared) {\n continue;\n }\n\n /**\n * Find the corresponding AI message\n */\n const aiMessage = this.#findAIMessageForToolCall(\n messages.slice(0, idx),\n toolMessage.tool_call_id\n );\n\n if (!aiMessage) {\n continue;\n }\n\n /**\n * Find the corresponding tool call\n */\n const toolCall = aiMessage.tool_calls?.find(\n (call) => call.id === toolMessage.tool_call_id\n );\n\n if (!toolCall) {\n continue;\n }\n\n /**\n * Skip if tool is excluded\n */\n const toolName = toolMessage.name || toolCall.name;\n if (this.excludeTools.has(toolName)) {\n continue;\n }\n\n /**\n * Clear the tool message\n */\n messages[idx] = new ToolMessage({\n tool_call_id: toolMessage.tool_call_id,\n content: this.placeholder,\n name: toolMessage.name,\n artifact: undefined,\n response_metadata: {\n ...toolMessage.response_metadata,\n context_editing: {\n cleared: true,\n strategy: \"clear_tool_uses\",\n },\n },\n });\n\n /**\n * Optionally clear the tool inputs\n */\n if (this.clearToolInputs) {\n const aiMsgIdx = messages.indexOf(aiMessage);\n if (aiMsgIdx >= 0) {\n messages[aiMsgIdx] = this.#buildClearedToolInputMessage(\n aiMessage,\n toolMessage.tool_call_id\n );\n }\n }\n\n /**\n * Recalculate tokens\n */\n const newTokenCount = await countTokens(messages);\n clearedTokens = Math.max(0, currentTokens - newTokenCount);\n }\n\n /**\n * If clearAtLeast is set and we haven't cleared enough tokens,\n * continue clearing more messages (going backwards from keepCount)\n * This is deprecated behavior but maintained for backwards compatibility\n */\n if (this.clearAtLeast > 0 && clearedTokens < this.clearAtLeast) {\n /**\n * Find remaining candidates that weren't cleared yet (those that were kept)\n */\n const remainingCandidates =\n keepCount > 0 && keepCount < candidates.length\n ? candidates.slice(-keepCount)\n : [];\n\n /**\n * Clear additional messages until we've cleared at least clearAtLeast tokens\n * Go backwards through the kept messages\n */\n for (let i = remainingCandidates.length - 1; i >= 0; i--) {\n if (clearedTokens >= this.clearAtLeast) {\n break;\n }\n\n const { idx, msg: toolMessage } = remainingCandidates[i]!;\n\n /**\n * Skip if already cleared\n */\n const contextEditing = toolMessage.response_metadata\n ?.context_editing as { cleared?: boolean } | undefined;\n if (contextEditing?.cleared) {\n continue;\n }\n\n /**\n * Find the corresponding AI message\n */\n const aiMessage = this.#findAIMessageForToolCall(\n messages.slice(0, idx),\n toolMessage.tool_call_id\n );\n\n if (!aiMessage) {\n continue;\n }\n\n /**\n * Find the corresponding tool call\n */\n const toolCall = aiMessage.tool_calls?.find(\n (call) => call.id === toolMessage.tool_call_id\n );\n\n if (!toolCall) {\n continue;\n }\n\n /**\n * Skip if tool is excluded\n */\n const toolName = toolMessage.name || toolCall.name;\n if (this.excludeTools.has(toolName)) {\n continue;\n }\n\n /**\n * Clear the tool message\n */\n messages[idx] = new ToolMessage({\n tool_call_id: toolMessage.tool_call_id,\n content: this.placeholder,\n name: toolMessage.name,\n artifact: undefined,\n response_metadata: {\n ...toolMessage.response_metadata,\n context_editing: {\n cleared: true,\n strategy: \"clear_tool_uses\",\n },\n },\n });\n\n /**\n * Optionally clear the tool inputs\n */\n if (this.clearToolInputs) {\n const aiMsgIdx = messages.indexOf(aiMessage);\n if (aiMsgIdx >= 0) {\n messages[aiMsgIdx] = this.#buildClearedToolInputMessage(\n aiMessage,\n toolMessage.tool_call_id\n );\n }\n }\n\n /**\n * Recalculate tokens\n */\n const newTokenCount = await countTokens(messages);\n clearedTokens = Math.max(0, currentTokens - newTokenCount);\n }\n }\n }\n\n /**\n * Determine whether editing should run for the current token usage\n */\n #shouldEdit(\n messages: BaseMessage[],\n totalTokens: number,\n model: BaseLanguageModel\n ): boolean {\n /**\n * Check each condition (OR logic between conditions)\n */\n for (const trigger of this.#triggerConditions) {\n /**\n * Within a single condition, all specified properties must be satisfied (AND logic)\n */\n let conditionMet = true;\n let hasAnyProperty = false;\n\n if (trigger.messages !== undefined) {\n hasAnyProperty = true;\n if (messages.length < trigger.messages) {\n conditionMet = false;\n }\n }\n\n if (trigger.tokens !== undefined) {\n hasAnyProperty = true;\n if (totalTokens < trigger.tokens) {\n conditionMet = false;\n }\n }\n\n if (trigger.fraction !== undefined) {\n hasAnyProperty = true;\n if (!model) {\n continue;\n }\n const maxInputTokens = getProfileLimits(model);\n if (typeof maxInputTokens === \"number\") {\n const threshold = Math.floor(maxInputTokens * trigger.fraction);\n if (threshold <= 0) {\n continue;\n }\n if (totalTokens < threshold) {\n conditionMet = false;\n }\n } else {\n /**\n * If fraction is specified but we can't get model limits, skip this condition\n */\n continue;\n }\n }\n\n /**\n * If condition has at least one property and all properties are satisfied, trigger editing\n */\n if (hasAnyProperty && conditionMet) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Determine how many tool results to keep based on keep policy\n */\n async #determineKeepCount(\n candidates: Array<{ idx: number; msg: ToolMessage }>,\n countTokens: TokenCounter,\n model: BaseLanguageModel\n ): Promise<number> {\n if (\"messages\" in this.keep && this.keep.messages !== undefined) {\n return this.keep.messages;\n }\n\n if (\"tokens\" in this.keep && this.keep.tokens !== undefined) {\n /**\n * For token-based keep, count backwards from the end until we exceed the token limit\n * This is a simplified implementation - keeping N most recent tool messages\n * A more sophisticated implementation would count actual tokens\n */\n const targetTokens = this.keep.tokens;\n let tokenCount = 0;\n let keepCount = 0;\n\n for (let i = candidates.length - 1; i >= 0; i--) {\n const candidate = candidates[i];\n /**\n * Estimate tokens for this tool message (simplified - could be improved)\n */\n const msgTokens = await countTokens([candidate.msg]);\n if (tokenCount + msgTokens <= targetTokens) {\n tokenCount += msgTokens;\n keepCount++;\n } else {\n break;\n }\n }\n\n return keepCount;\n }\n\n if (\"fraction\" in this.keep && this.keep.fraction !== undefined) {\n if (!model) {\n return DEFAULT_KEEP;\n }\n const maxInputTokens = getProfileLimits(model);\n if (typeof maxInputTokens === \"number\") {\n const targetTokens = Math.floor(maxInputTokens * this.keep.fraction);\n if (targetTokens <= 0) {\n return DEFAULT_KEEP;\n }\n /**\n * Use token-based logic with fractional target\n */\n let tokenCount = 0;\n let keepCount = 0;\n\n for (let i = candidates.length - 1; i >= 0; i--) {\n const candidate = candidates[i];\n const msgTokens = await countTokens([candidate.msg]);\n if (tokenCount + msgTokens <= targetTokens) {\n tokenCount += msgTokens;\n keepCount++;\n } else {\n break;\n }\n }\n\n return keepCount;\n }\n }\n\n return DEFAULT_KEEP;\n }\n\n #findAIMessageForToolCall(\n previousMessages: BaseMessage[],\n toolCallId: string\n ): AIMessage | null {\n // Search backwards through previous messages\n for (let i = previousMessages.length - 1; i >= 0; i--) {\n const msg = previousMessages[i];\n if (AIMessage.isInstance(msg)) {\n const hasToolCall = msg.tool_calls?.some(\n (call) => call.id === toolCallId\n );\n if (hasToolCall) {\n return msg;\n }\n }\n }\n return null;\n }\n\n #buildClearedToolInputMessage(\n message: AIMessage,\n toolCallId: string\n ): AIMessage {\n const updatedToolCalls = message.tool_calls?.map((toolCall) => {\n if (toolCall.id === toolCallId) {\n return { ...toolCall, args: {} };\n }\n return toolCall;\n });\n\n const metadata = { ...message.response_metadata };\n const contextEntry = {\n ...(metadata.context_editing as Record<string, unknown>),\n };\n\n const clearedIds = new Set<string>(\n contextEntry.cleared_tool_inputs as string[] | undefined\n );\n clearedIds.add(toolCallId);\n contextEntry.cleared_tool_inputs = Array.from(clearedIds).sort();\n metadata.context_editing = contextEntry;\n\n return new AIMessage({\n content: message.content,\n tool_calls: updatedToolCalls,\n response_metadata: metadata,\n id: message.id,\n name: message.name,\n additional_kwargs: message.additional_kwargs,\n });\n }\n}\n\n/**\n * Configuration for the Context Editing Middleware.\n */\nexport interface ContextEditingMiddlewareConfig {\n /**\n * Sequence of edit strategies to apply. Defaults to a single\n * ClearToolUsesEdit mirroring Anthropic defaults.\n */\n edits?: ContextEdit[];\n\n /**\n * Whether to use approximate token counting (faster, less accurate)\n * or exact counting implemented by the chat model (potentially slower, more accurate).\n * Currently only OpenAI models support exact counting.\n * @default \"approx\"\n */\n tokenCountMethod?: \"approx\" | \"model\";\n}\n\n/**\n * Middleware that automatically prunes tool results to manage context size.\n *\n * This middleware applies a sequence of edits when the total input token count\n * exceeds configured thresholds. By default, it uses the `ClearToolUsesEdit` strategy\n * which mirrors Anthropic's `clear_tool_uses_20250919` behaviour by clearing older\n * tool results once the conversation exceeds 100,000 tokens.\n *\n * ## Basic Usage\n *\n * Use the middleware with default settings to automatically manage context:\n *\n * @example Basic usage with defaults\n * ```ts\n * import { contextEditingMiddleware } from \"langchain\";\n * import { createAgent } from \"langchain\";\n *\n * const agent = createAgent({\n * model: \"anthropic:claude-sonnet-4-5\",\n * tools: [searchTool, calculatorTool],\n * middleware: [\n * contextEditingMiddleware(),\n * ],\n * });\n * ```\n *\n * The default configuration:\n * - Triggers when context exceeds **100,000 tokens**\n * - Keeps the **3 most recent** tool results\n * - Uses **approximate token counting** (fast)\n * - Does not clear tool call arguments\n *\n * ## Custom Configuration\n *\n * Customize the clearing behavior with `ClearToolUsesEdit`:\n *\n * @example Custom ClearToolUsesEdit configuration\n * ```ts\n * import { contextEditingMiddleware, ClearToolUsesEdit } from \"langchain\";\n *\n * // Single condition: trigger if tokens >= 50000 AND messages >= 20\n * const agent1 = createAgent({\n * model: \"anthropic:claude-sonnet-4-5\",\n * tools: [searchTool, calculatorTool],\n * middleware: [\n * contextEditingMiddleware({\n * edits: [\n * new ClearToolUsesEdit({\n * trigger: { tokens: 50000, messages: 20 },\n * keep: { messages: 5 },\n * excludeTools: [\"search\"],\n * clearToolInputs: true,\n * }),\n * ],\n * tokenCountMethod: \"approx\",\n * }),\n * ],\n * });\n *\n * // Multiple conditions: trigger if (tokens >= 50000 AND messages >= 20) OR (tokens >= 30000 AND messages >= 50)\n * const agent2 = createAgent({\n * model: \"anthropic:claude-sonnet-4-5\",\n * tools: [searchTool, calculatorTool],\n * middleware: [\n * contextEditingMiddleware({\n * edits: [\n * new ClearToolUsesEdit({\n * trigger: [\n * { tokens: 50000, messages: 20 },\n * { tokens: 30000, messages: 50 },\n * ],\n * keep: { messages: 5 },\n * }),\n * ],\n * }),\n * ],\n * });\n *\n * // Fractional trigger with model profile\n * const agent3 = createAgent({\n * model: chatModel,\n * tools: [searchTool, calculatorTool],\n * middleware: [\n * contextEditingMiddleware({\n * edits: [\n * new ClearToolUsesEdit({\n * trigger: { fraction: 0.8 }, // Trigger at 80% of model's max tokens\n * keep: { fraction: 0.3 }, // Keep 30% of model's max tokens\n * model: chatModel,\n * }),\n * ],\n * }),\n * ],\n * });\n * ```\n *\n * ## Custom Editing Strategies\n *\n * Implement your own context editing strategy by creating a class that\n * implements the `ContextEdit` interface:\n *\n * @example Custom editing strategy\n * ```ts\n * import { contextEditingMiddleware, type ContextEdit, type TokenCounter } from \"langchain\";\n * import type { BaseMessage } from \"@langchain/core/messages\";\n *\n * class CustomEdit implements ContextEdit {\n * async apply(params: {\n * tokens: number;\n * messages: BaseMessage[];\n * countTokens: TokenCounter;\n * }): Promise<number> {\n * // Implement your custom editing logic here\n * // and apply it to the messages array, then\n * // return the new token count after edits\n * return countTokens(messages);\n * }\n * }\n * ```\n *\n * @param config - Configuration options for the middleware\n * @returns A middleware instance that can be used with `createAgent`\n */\nexport function contextEditingMiddleware(\n config: ContextEditingMiddlewareConfig = {}\n) {\n const edits = config.edits ?? [new ClearToolUsesEdit()];\n const tokenCountMethod = config.tokenCountMethod ?? \"approx\";\n\n return createMiddleware({\n name: \"ContextEditingMiddleware\",\n wrapModelCall: async (request, handler) => {\n if (!request.messages || request.messages.length === 0) {\n return handler(request);\n }\n\n /**\n * Use model's token counting method\n */\n const systemMsg = request.systemPrompt\n ? [new SystemMessage(request.systemPrompt)]\n : [];\n\n const countTokens: TokenCounter =\n tokenCountMethod === \"approx\"\n ? countTokensApproximately\n : async (messages: BaseMessage[]): Promise<number> => {\n const allMessages = [...systemMsg, ...messages];\n\n /**\n * Check if model has getNumTokensFromMessages method\n * currently only OpenAI models have this method\n */\n if (\"getNumTokensFromMessages\" in request.model) {\n return (\n request.model as BaseLanguageModel & {\n getNumTokensFromMessages: (\n messages: BaseMessage[]\n ) => Promise<{\n totalCount: number;\n countPerMessage: number[];\n }>;\n }\n )\n .getNumTokensFromMessages(allMessages)\n .then(({ totalCount }) => totalCount);\n }\n\n throw new Error(\n `Model \"${request.model.getName()}\" does not support token counting`\n );\n };\n\n /**\n * Apply each edit in sequence\n */\n for (const edit of edits) {\n await edit.apply({\n messages: request.messages,\n model: request.model as BaseLanguageModel,\n countTokens,\n });\n }\n\n return handler(request);\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"contextEditing.js","names":["#triggerConditions","#findAIMessageForToolCall","#shouldEdit","#determineKeepCount","#buildClearedToolInputMessage"],"sources":["../../../src/agents/middleware/contextEditing.ts"],"sourcesContent":["/**\n * Context editing middleware.\n *\n * This middleware mirrors Anthropic's context editing capabilities by clearing\n * older tool results once the conversation grows beyond a configurable token\n * threshold. The implementation is intentionally model-agnostic so it can be used\n * with any LangChain chat model.\n */\n\nimport type { BaseMessage } from \"@langchain/core/messages\";\nimport type { BaseLanguageModel } from \"@langchain/core/language_models/base\";\nimport {\n AIMessage,\n ToolMessage,\n SystemMessage,\n} from \"@langchain/core/messages\";\n\nimport { countTokensApproximately } from \"./utils.js\";\nimport { createMiddleware } from \"../middleware.js\";\nimport {\n getProfileLimits,\n contextSizeSchema,\n keepSchema,\n type ContextSize,\n type KeepSize,\n type TokenCounter,\n} from \"./summarization.js\";\n\nconst DEFAULT_TOOL_PLACEHOLDER = \"[cleared]\";\nconst DEFAULT_TRIGGER_TOKENS = 100_000;\nconst DEFAULT_KEEP = 3;\n\n/**\n * Protocol describing a context editing strategy.\n *\n * Implement this interface to create custom strategies for managing\n * conversation context size. The `apply` method should modify the\n * messages array in-place and return the updated token count.\n *\n * @example\n * ```ts\n * import { HumanMessage, type ContextEdit, type BaseMessage } from \"langchain\";\n *\n * class RemoveOldHumanMessages implements ContextEdit {\n * constructor(private keepRecent: number = 10) {}\n *\n * async apply({ messages, countTokens }) {\n * // Check current token count\n * const tokens = await countTokens(messages);\n *\n * // Remove old human messages if over limit, keeping the most recent ones\n * if (tokens > 50000) {\n * const humanMessages: number[] = [];\n *\n * // Find all human message indices\n * for (let i = 0; i < messages.length; i++) {\n * if (HumanMessage.isInstance(messages[i])) {\n * humanMessages.push(i);\n * }\n * }\n *\n * // Remove old human messages (keep only the most recent N)\n * const toRemove = humanMessages.slice(0, -this.keepRecent);\n * for (let i = toRemove.length - 1; i >= 0; i--) {\n * messages.splice(toRemove[i]!, 1);\n * }\n * }\n * }\n * }\n * ```\n */\nexport interface ContextEdit {\n /**\n * Apply an edit to the message list, returning the new token count.\n *\n * This method should:\n * 1. Check if editing is needed based on `tokens` parameter\n * 2. Modify the `messages` array in-place (if needed)\n * 3. Return the new token count after modifications\n *\n * @param params - Parameters for the editing operation\n * @returns The updated token count after applying edits\n */\n apply(params: {\n /**\n * Array of messages to potentially edit (modify in-place)\n */\n messages: BaseMessage[];\n /**\n * Function to count tokens in a message array\n */\n countTokens: TokenCounter;\n /**\n * Optional model instance for model profile information\n */\n model?: BaseLanguageModel;\n }): void | Promise<void>;\n}\n\n/**\n * Configuration for clearing tool outputs when token limits are exceeded.\n */\nexport interface ClearToolUsesEditConfig {\n /**\n * Trigger conditions for context editing.\n * Can be a single condition object (all properties must be met) or an array of conditions (any condition must be met).\n *\n * @example\n * ```ts\n * // Single condition: trigger if tokens >= 100000 AND messages >= 50\n * trigger: { tokens: 100000, messages: 50 }\n *\n * // Multiple conditions: trigger if (tokens >= 100000 AND messages >= 50) OR (tokens >= 50000 AND messages >= 100)\n * trigger: [\n * { tokens: 100000, messages: 50 },\n * { tokens: 50000, messages: 100 }\n * ]\n *\n * // Fractional trigger: trigger at 80% of model's max input tokens\n * trigger: { fraction: 0.8 }\n * ```\n */\n trigger?: ContextSize | ContextSize[];\n\n /**\n * Context retention policy applied after editing.\n * Specify how many tool results to preserve using messages, tokens, or fraction.\n *\n * @example\n * ```ts\n * // Keep 3 most recent tool results\n * keep: { messages: 3 }\n *\n * // Keep tool results that fit within 1000 tokens\n * keep: { tokens: 1000 }\n *\n * // Keep tool results that fit within 30% of model's max input tokens\n * keep: { fraction: 0.3 }\n * ```\n */\n keep?: KeepSize;\n\n /**\n * Whether to clear the originating tool call parameters on the AI message.\n * @default false\n */\n clearToolInputs?: boolean;\n\n /**\n * List of tool names to exclude from clearing.\n * @default []\n */\n excludeTools?: string[];\n\n /**\n * Placeholder text inserted for cleared tool outputs.\n * @default \"[cleared]\"\n */\n placeholder?: string;\n\n /**\n * @deprecated Use `trigger: { tokens: value }` instead.\n */\n triggerTokens?: number;\n\n /**\n * @deprecated Use `keep: { messages: value }` instead.\n */\n keepMessages?: number;\n\n /**\n * @deprecated This property is deprecated and will be removed in a future version.\n * Use `keep: { tokens: value }` or `keep: { messages: value }` instead to control retention.\n */\n clearAtLeast?: number;\n}\n\n/**\n * Strategy for clearing tool outputs when token limits are exceeded.\n *\n * This strategy mirrors Anthropic's `clear_tool_uses_20250919` behavior by\n * replacing older tool results with a placeholder text when the conversation\n * grows too large. It preserves the most recent tool results and can exclude\n * specific tools from being cleared.\n *\n * @example\n * ```ts\n * import { ClearToolUsesEdit } from \"langchain\";\n *\n * const edit = new ClearToolUsesEdit({\n * trigger: { tokens: 100000 }, // Start clearing at 100K tokens\n * keep: { messages: 3 }, // Keep 3 most recent tool results\n * excludeTools: [\"important\"], // Never clear \"important\" tool\n * clearToolInputs: false, // Keep tool call arguments\n * placeholder: \"[cleared]\", // Replacement text\n * });\n *\n * // Multiple trigger conditions\n * const edit2 = new ClearToolUsesEdit({\n * trigger: [\n * { tokens: 100000, messages: 50 },\n * { tokens: 50000, messages: 100 }\n * ],\n * keep: { messages: 3 },\n * });\n *\n * // Fractional trigger with model profile\n * const edit3 = new ClearToolUsesEdit({\n * trigger: { fraction: 0.8 }, // Trigger at 80% of model's max tokens\n * keep: { fraction: 0.3 }, // Keep 30% of model's max tokens\n * });\n * ```\n */\nexport class ClearToolUsesEdit implements ContextEdit {\n #triggerConditions: ContextSize[];\n\n trigger: ContextSize | ContextSize[];\n keep: KeepSize;\n clearToolInputs: boolean;\n excludeTools: Set<string>;\n placeholder: string;\n model: BaseLanguageModel;\n clearAtLeast: number;\n\n constructor(config: ClearToolUsesEditConfig = {}) {\n // Handle deprecated parameters\n let trigger: ContextSize | ContextSize[] | undefined = config.trigger;\n if (config.triggerTokens !== undefined) {\n console.warn(\n \"triggerTokens is deprecated. Use `trigger: { tokens: value }` instead.\"\n );\n if (trigger === undefined) {\n trigger = { tokens: config.triggerTokens };\n }\n }\n\n let keep: KeepSize | undefined = config.keep;\n if (config.keepMessages !== undefined) {\n console.warn(\n \"keepMessages is deprecated. Use `keep: { messages: value }` instead.\"\n );\n if (keep === undefined) {\n keep = { messages: config.keepMessages };\n }\n }\n\n // Set defaults\n if (trigger === undefined) {\n trigger = { tokens: DEFAULT_TRIGGER_TOKENS };\n }\n if (keep === undefined) {\n keep = { messages: DEFAULT_KEEP };\n }\n\n // Validate trigger conditions\n if (Array.isArray(trigger)) {\n this.#triggerConditions = trigger.map((t) => contextSizeSchema.parse(t));\n this.trigger = this.#triggerConditions;\n } else {\n const validated = contextSizeSchema.parse(trigger);\n this.#triggerConditions = [validated];\n this.trigger = validated;\n }\n\n // Validate keep\n const validatedKeep = keepSchema.parse(keep);\n this.keep = validatedKeep;\n\n // Handle deprecated clearAtLeast\n if (config.clearAtLeast !== undefined) {\n console.warn(\n \"clearAtLeast is deprecated and will be removed in a future version. \" +\n \"It conflicts with the `keep` property. Use `keep: { tokens: value }` or \" +\n \"`keep: { messages: value }` instead to control retention.\"\n );\n }\n this.clearAtLeast = config.clearAtLeast ?? 0;\n\n this.clearToolInputs = config.clearToolInputs ?? false;\n this.excludeTools = new Set(config.excludeTools ?? []);\n this.placeholder = config.placeholder ?? DEFAULT_TOOL_PLACEHOLDER;\n }\n\n async apply(params: {\n messages: BaseMessage[];\n model: BaseLanguageModel;\n countTokens: TokenCounter;\n }): Promise<void> {\n const { messages, model, countTokens } = params;\n const tokens = await countTokens(messages);\n\n /**\n * Always remove orphaned tool messages (those without corresponding AI messages)\n * regardless of whether editing is triggered\n */\n const orphanedIndices: number[] = [];\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (ToolMessage.isInstance(msg)) {\n // Check if this tool message has a corresponding AI message\n const aiMessage = this.#findAIMessageForToolCall(\n messages.slice(0, i),\n msg.tool_call_id\n );\n\n if (!aiMessage) {\n // Orphaned tool message - mark for removal\n orphanedIndices.push(i);\n } else {\n // Check if the AI message actually has this tool call\n const toolCall = aiMessage.tool_calls?.find(\n (call) => call.id === msg.tool_call_id\n );\n if (!toolCall) {\n // Orphaned tool message - mark for removal\n orphanedIndices.push(i);\n }\n }\n }\n }\n\n /**\n * Remove orphaned tool messages in reverse order to maintain indices\n */\n for (let i = orphanedIndices.length - 1; i >= 0; i--) {\n messages.splice(orphanedIndices[i]!, 1);\n }\n\n /**\n * Recalculate tokens after removing orphaned messages\n */\n let currentTokens = tokens;\n if (orphanedIndices.length > 0) {\n currentTokens = await countTokens(messages);\n }\n\n /**\n * Check if editing should be triggered\n */\n if (!this.#shouldEdit(messages, currentTokens, model)) {\n return;\n }\n\n /**\n * Find all tool message candidates with their actual indices in the messages array\n */\n const candidates: { idx: number; msg: ToolMessage }[] = [];\n for (let i = 0; i < messages.length; i++) {\n const msg = messages[i];\n if (ToolMessage.isInstance(msg)) {\n candidates.push({ idx: i, msg });\n }\n }\n\n if (candidates.length === 0) {\n return;\n }\n\n /**\n * Determine how many tool results to keep based on keep policy\n */\n const keepCount = await this.#determineKeepCount(\n candidates,\n countTokens,\n model\n );\n\n /**\n * Keep the most recent tool messages based on keep policy\n */\n const candidatesToClear =\n keepCount >= candidates.length\n ? []\n : keepCount > 0\n ? candidates.slice(0, -keepCount)\n : candidates;\n\n /**\n * If clearAtLeast is set, we may need to clear more messages to meet the token requirement\n * This is a deprecated feature that conflicts with keep, but we support it for backwards compatibility\n */\n let clearedTokens = 0;\n const initialCandidatesToClear = [...candidatesToClear];\n\n for (const { idx, msg: toolMessage } of initialCandidatesToClear) {\n /**\n * Skip if already cleared\n */\n const contextEditing = toolMessage.response_metadata?.context_editing as\n | { cleared?: boolean }\n | undefined;\n if (contextEditing?.cleared) {\n continue;\n }\n\n /**\n * Find the corresponding AI message\n */\n const aiMessage = this.#findAIMessageForToolCall(\n messages.slice(0, idx),\n toolMessage.tool_call_id\n );\n\n if (!aiMessage) {\n continue;\n }\n\n /**\n * Find the corresponding tool call\n */\n const toolCall = aiMessage.tool_calls?.find(\n (call) => call.id === toolMessage.tool_call_id\n );\n\n if (!toolCall) {\n continue;\n }\n\n /**\n * Skip if tool is excluded\n */\n const toolName = toolMessage.name || toolCall.name;\n if (this.excludeTools.has(toolName)) {\n continue;\n }\n\n /**\n * Clear the tool message\n */\n messages[idx] = new ToolMessage({\n tool_call_id: toolMessage.tool_call_id,\n content: this.placeholder,\n name: toolMessage.name,\n artifact: undefined,\n response_metadata: {\n ...toolMessage.response_metadata,\n context_editing: {\n cleared: true,\n strategy: \"clear_tool_uses\",\n },\n },\n });\n\n /**\n * Optionally clear the tool inputs\n */\n if (this.clearToolInputs) {\n const aiMsgIdx = messages.indexOf(aiMessage);\n if (aiMsgIdx >= 0) {\n messages[aiMsgIdx] = this.#buildClearedToolInputMessage(\n aiMessage,\n toolMessage.tool_call_id\n );\n }\n }\n\n /**\n * Recalculate tokens\n */\n const newTokenCount = await countTokens(messages);\n clearedTokens = Math.max(0, currentTokens - newTokenCount);\n }\n\n /**\n * If clearAtLeast is set and we haven't cleared enough tokens,\n * continue clearing more messages (going backwards from keepCount)\n * This is deprecated behavior but maintained for backwards compatibility\n */\n if (this.clearAtLeast > 0 && clearedTokens < this.clearAtLeast) {\n /**\n * Find remaining candidates that weren't cleared yet (those that were kept)\n */\n const remainingCandidates =\n keepCount > 0 && keepCount < candidates.length\n ? candidates.slice(-keepCount)\n : [];\n\n /**\n * Clear additional messages until we've cleared at least clearAtLeast tokens\n * Go backwards through the kept messages\n */\n for (let i = remainingCandidates.length - 1; i >= 0; i--) {\n if (clearedTokens >= this.clearAtLeast) {\n break;\n }\n\n const { idx, msg: toolMessage } = remainingCandidates[i]!;\n\n /**\n * Skip if already cleared\n */\n const contextEditing = toolMessage.response_metadata\n ?.context_editing as { cleared?: boolean } | undefined;\n if (contextEditing?.cleared) {\n continue;\n }\n\n /**\n * Find the corresponding AI message\n */\n const aiMessage = this.#findAIMessageForToolCall(\n messages.slice(0, idx),\n toolMessage.tool_call_id\n );\n\n if (!aiMessage) {\n continue;\n }\n\n /**\n * Find the corresponding tool call\n */\n const toolCall = aiMessage.tool_calls?.find(\n (call) => call.id === toolMessage.tool_call_id\n );\n\n if (!toolCall) {\n continue;\n }\n\n /**\n * Skip if tool is excluded\n */\n const toolName = toolMessage.name || toolCall.name;\n if (this.excludeTools.has(toolName)) {\n continue;\n }\n\n /**\n * Clear the tool message\n */\n messages[idx] = new ToolMessage({\n tool_call_id: toolMessage.tool_call_id,\n content: this.placeholder,\n name: toolMessage.name,\n artifact: undefined,\n response_metadata: {\n ...toolMessage.response_metadata,\n context_editing: {\n cleared: true,\n strategy: \"clear_tool_uses\",\n },\n },\n });\n\n /**\n * Optionally clear the tool inputs\n */\n if (this.clearToolInputs) {\n const aiMsgIdx = messages.indexOf(aiMessage);\n if (aiMsgIdx >= 0) {\n messages[aiMsgIdx] = this.#buildClearedToolInputMessage(\n aiMessage,\n toolMessage.tool_call_id\n );\n }\n }\n\n /**\n * Recalculate tokens\n */\n const newTokenCount = await countTokens(messages);\n clearedTokens = Math.max(0, currentTokens - newTokenCount);\n }\n }\n }\n\n /**\n * Determine whether editing should run for the current token usage\n */\n #shouldEdit(\n messages: BaseMessage[],\n totalTokens: number,\n model: BaseLanguageModel\n ): boolean {\n /**\n * Check each condition (OR logic between conditions)\n */\n for (const trigger of this.#triggerConditions) {\n /**\n * Within a single condition, all specified properties must be satisfied (AND logic)\n */\n let conditionMet = true;\n let hasAnyProperty = false;\n\n if (trigger.messages !== undefined) {\n hasAnyProperty = true;\n if (messages.length < trigger.messages) {\n conditionMet = false;\n }\n }\n\n if (trigger.tokens !== undefined) {\n hasAnyProperty = true;\n if (totalTokens < trigger.tokens) {\n conditionMet = false;\n }\n }\n\n if (trigger.fraction !== undefined) {\n hasAnyProperty = true;\n if (!model) {\n continue;\n }\n const maxInputTokens = getProfileLimits(model);\n if (typeof maxInputTokens === \"number\") {\n const threshold = Math.floor(maxInputTokens * trigger.fraction);\n if (threshold <= 0) {\n continue;\n }\n if (totalTokens < threshold) {\n conditionMet = false;\n }\n } else {\n /**\n * If fraction is specified but we can't get model limits, skip this condition\n */\n continue;\n }\n }\n\n /**\n * If condition has at least one property and all properties are satisfied, trigger editing\n */\n if (hasAnyProperty && conditionMet) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Determine how many tool results to keep based on keep policy\n */\n async #determineKeepCount(\n candidates: Array<{ idx: number; msg: ToolMessage }>,\n countTokens: TokenCounter,\n model: BaseLanguageModel\n ): Promise<number> {\n if (\"messages\" in this.keep && this.keep.messages !== undefined) {\n return this.keep.messages;\n }\n\n if (\"tokens\" in this.keep && this.keep.tokens !== undefined) {\n /**\n * For token-based keep, count backwards from the end until we exceed the token limit\n * This is a simplified implementation - keeping N most recent tool messages\n * A more sophisticated implementation would count actual tokens\n */\n const targetTokens = this.keep.tokens;\n let tokenCount = 0;\n let keepCount = 0;\n\n for (let i = candidates.length - 1; i >= 0; i--) {\n const candidate = candidates[i];\n /**\n * Estimate tokens for this tool message (simplified - could be improved)\n */\n const msgTokens = await countTokens([candidate.msg]);\n if (tokenCount + msgTokens <= targetTokens) {\n tokenCount += msgTokens;\n keepCount++;\n } else {\n break;\n }\n }\n\n return keepCount;\n }\n\n if (\"fraction\" in this.keep && this.keep.fraction !== undefined) {\n if (!model) {\n return DEFAULT_KEEP;\n }\n const maxInputTokens = getProfileLimits(model);\n if (typeof maxInputTokens === \"number\") {\n const targetTokens = Math.floor(maxInputTokens * this.keep.fraction);\n if (targetTokens <= 0) {\n return DEFAULT_KEEP;\n }\n /**\n * Use token-based logic with fractional target\n */\n let tokenCount = 0;\n let keepCount = 0;\n\n for (let i = candidates.length - 1; i >= 0; i--) {\n const candidate = candidates[i];\n const msgTokens = await countTokens([candidate.msg]);\n if (tokenCount + msgTokens <= targetTokens) {\n tokenCount += msgTokens;\n keepCount++;\n } else {\n break;\n }\n }\n\n return keepCount;\n }\n }\n\n return DEFAULT_KEEP;\n }\n\n #findAIMessageForToolCall(\n previousMessages: BaseMessage[],\n toolCallId: string\n ): AIMessage | null {\n // Search backwards through previous messages\n for (let i = previousMessages.length - 1; i >= 0; i--) {\n const msg = previousMessages[i];\n if (AIMessage.isInstance(msg)) {\n const hasToolCall = msg.tool_calls?.some(\n (call) => call.id === toolCallId\n );\n if (hasToolCall) {\n return msg;\n }\n }\n }\n return null;\n }\n\n #buildClearedToolInputMessage(\n message: AIMessage,\n toolCallId: string\n ): AIMessage {\n const updatedToolCalls = message.tool_calls?.map((toolCall) => {\n if (toolCall.id === toolCallId) {\n return { ...toolCall, args: {} };\n }\n return toolCall;\n });\n\n const metadata = { ...message.response_metadata };\n const contextEntry = {\n ...(metadata.context_editing as Record<string, unknown>),\n };\n\n const clearedIds = new Set<string>(\n contextEntry.cleared_tool_inputs as string[] | undefined\n );\n clearedIds.add(toolCallId);\n contextEntry.cleared_tool_inputs = Array.from(clearedIds).sort();\n metadata.context_editing = contextEntry;\n\n return new AIMessage({\n content: message.content,\n tool_calls: updatedToolCalls,\n response_metadata: metadata,\n id: message.id,\n name: message.name,\n additional_kwargs: message.additional_kwargs,\n });\n }\n}\n\n/**\n * Configuration for the Context Editing Middleware.\n */\nexport interface ContextEditingMiddlewareConfig {\n /**\n * Sequence of edit strategies to apply. Defaults to a single\n * ClearToolUsesEdit mirroring Anthropic defaults.\n */\n edits?: ContextEdit[];\n\n /**\n * Whether to use approximate token counting (faster, less accurate)\n * or exact counting implemented by the chat model (potentially slower, more accurate).\n * Currently only OpenAI models support exact counting.\n * @default \"approx\"\n */\n tokenCountMethod?: \"approx\" | \"model\";\n}\n\n/**\n * Middleware that automatically prunes tool results to manage context size.\n *\n * This middleware applies a sequence of edits when the total input token count\n * exceeds configured thresholds. By default, it uses the `ClearToolUsesEdit` strategy\n * which mirrors Anthropic's `clear_tool_uses_20250919` behaviour by clearing older\n * tool results once the conversation exceeds 100,000 tokens.\n *\n * ## Basic Usage\n *\n * Use the middleware with default settings to automatically manage context:\n *\n * @example Basic usage with defaults\n * ```ts\n * import { contextEditingMiddleware } from \"langchain\";\n * import { createAgent } from \"langchain\";\n *\n * const agent = createAgent({\n * model: \"anthropic:claude-sonnet-4-5\",\n * tools: [searchTool, calculatorTool],\n * middleware: [\n * contextEditingMiddleware(),\n * ],\n * });\n * ```\n *\n * The default configuration:\n * - Triggers when context exceeds **100,000 tokens**\n * - Keeps the **3 most recent** tool results\n * - Uses **approximate token counting** (fast)\n * - Does not clear tool call arguments\n *\n * ## Custom Configuration\n *\n * Customize the clearing behavior with `ClearToolUsesEdit`:\n *\n * @example Custom ClearToolUsesEdit configuration\n * ```ts\n * import { contextEditingMiddleware, ClearToolUsesEdit } from \"langchain\";\n *\n * // Single condition: trigger if tokens >= 50000 AND messages >= 20\n * const agent1 = createAgent({\n * model: \"anthropic:claude-sonnet-4-5\",\n * tools: [searchTool, calculatorTool],\n * middleware: [\n * contextEditingMiddleware({\n * edits: [\n * new ClearToolUsesEdit({\n * trigger: { tokens: 50000, messages: 20 },\n * keep: { messages: 5 },\n * excludeTools: [\"search\"],\n * clearToolInputs: true,\n * }),\n * ],\n * tokenCountMethod: \"approx\",\n * }),\n * ],\n * });\n *\n * // Multiple conditions: trigger if (tokens >= 50000 AND messages >= 20) OR (tokens >= 30000 AND messages >= 50)\n * const agent2 = createAgent({\n * model: \"anthropic:claude-sonnet-4-5\",\n * tools: [searchTool, calculatorTool],\n * middleware: [\n * contextEditingMiddleware({\n * edits: [\n * new ClearToolUsesEdit({\n * trigger: [\n * { tokens: 50000, messages: 20 },\n * { tokens: 30000, messages: 50 },\n * ],\n * keep: { messages: 5 },\n * }),\n * ],\n * }),\n * ],\n * });\n *\n * // Fractional trigger with model profile\n * const agent3 = createAgent({\n * model: chatModel,\n * tools: [searchTool, calculatorTool],\n * middleware: [\n * contextEditingMiddleware({\n * edits: [\n * new ClearToolUsesEdit({\n * trigger: { fraction: 0.8 }, // Trigger at 80% of model's max tokens\n * keep: { fraction: 0.3 }, // Keep 30% of model's max tokens\n * model: chatModel,\n * }),\n * ],\n * }),\n * ],\n * });\n * ```\n *\n * ## Custom Editing Strategies\n *\n * Implement your own context editing strategy by creating a class that\n * implements the `ContextEdit` interface:\n *\n * @example Custom editing strategy\n * ```ts\n * import { contextEditingMiddleware, type ContextEdit, type TokenCounter } from \"langchain\";\n * import type { BaseMessage } from \"@langchain/core/messages\";\n *\n * class CustomEdit implements ContextEdit {\n * async apply(params: {\n * tokens: number;\n * messages: BaseMessage[];\n * countTokens: TokenCounter;\n * }): Promise<number> {\n * // Implement your custom editing logic here\n * // and apply it to the messages array, then\n * // return the new token count after edits\n * return countTokens(messages);\n * }\n * }\n * ```\n *\n * @param config - Configuration options for the middleware\n * @returns A middleware instance that can be used with `createAgent`\n */\nexport function contextEditingMiddleware(\n config: ContextEditingMiddlewareConfig = {}\n) {\n const edits = config.edits ?? [new ClearToolUsesEdit()];\n const tokenCountMethod = config.tokenCountMethod ?? \"approx\";\n\n return createMiddleware({\n name: \"ContextEditingMiddleware\",\n wrapModelCall: async (request, handler) => {\n if (!request.messages || request.messages.length === 0) {\n return handler(request);\n }\n\n /**\n * Use model's token counting method\n */\n const systemMsg = request.systemPrompt\n ? [new SystemMessage(request.systemPrompt)]\n : [];\n\n const countTokens: TokenCounter =\n tokenCountMethod === \"approx\"\n ? countTokensApproximately\n : async (messages: BaseMessage[]): Promise<number> => {\n const allMessages = [...systemMsg, ...messages];\n\n /**\n * Check if model has getNumTokensFromMessages method\n * currently only OpenAI models have this method\n */\n if (\"getNumTokensFromMessages\" in request.model) {\n return (\n request.model as BaseLanguageModel & {\n getNumTokensFromMessages: (\n messages: BaseMessage[]\n ) => Promise<{\n totalCount: number;\n countPerMessage: number[];\n }>;\n }\n )\n .getNumTokensFromMessages(allMessages)\n .then(({ totalCount }) => totalCount);\n }\n\n throw new Error(\n `Model \"${request.model.getName()}\" does not support token counting`\n );\n };\n\n /**\n * Apply each edit in sequence\n */\n for (const edit of edits) {\n await edit.apply({\n messages: request.messages,\n model: request.model as BaseLanguageModel,\n countTokens,\n });\n }\n\n return handler(request);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_runtime = require('../../_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_middleware = require('../middleware.cjs');
|
|
3
|
-
|
|
3
|
+
let _langchain_core_messages = require("@langchain/core/messages");
|
|
4
4
|
|
|
5
5
|
//#region src/agents/middleware/dynamicSystemPrompt.ts
|
|
6
6
|
/**
|
|
@@ -46,8 +46,7 @@ function dynamicSystemPromptMiddleware(fn) {
|
|
|
46
46
|
name: "DynamicSystemPromptMiddleware",
|
|
47
47
|
wrapModelCall: async (request, handler) => {
|
|
48
48
|
const systemPrompt = await fn(request.state, request.runtime);
|
|
49
|
-
|
|
50
|
-
if (!isExpectedType) throw new Error("dynamicSystemPromptMiddleware function must return a string or SystemMessage");
|
|
49
|
+
if (!(typeof systemPrompt === "string" || _langchain_core_messages.SystemMessage.isInstance(systemPrompt))) throw new Error("dynamicSystemPromptMiddleware function must return a string or SystemMessage");
|
|
51
50
|
return handler({
|
|
52
51
|
...request,
|
|
53
52
|
systemMessage: request.systemMessage.concat(systemPrompt)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamicSystemPrompt.cjs","names":["
|
|
1
|
+
{"version":3,"file":"dynamicSystemPrompt.cjs","names":["createMiddleware","SystemMessage"],"sources":["../../../src/agents/middleware/dynamicSystemPrompt.ts"],"sourcesContent":["import { SystemMessage } from \"@langchain/core/messages\";\nimport { createMiddleware } from \"../middleware.js\";\nimport type { Runtime, AgentBuiltInState } from \"../runtime.js\";\n\nexport type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (\n state: AgentBuiltInState,\n runtime: Runtime<TContextSchema>\n) => string | SystemMessage | Promise<string | SystemMessage>;\n\n/**\n * Dynamic System Prompt Middleware\n *\n * Allows setting the system prompt dynamically right before each model invocation.\n * Useful when the prompt depends on the current agent state or per-invocation context.\n *\n * @typeParam TContextSchema - The shape of the runtime context available at invocation time.\n * If your agent defines a `contextSchema`, pass the inferred type here to get full type-safety\n * for `runtime.context`.\n *\n * @param fn - Function that receives the current agent `state` and `runtime`, and\n * returns the system prompt for the next model call as a string.\n *\n * @returns A middleware instance that sets `systemPrompt` for the next model call.\n *\n * @example Basic usage with typed context\n * ```ts\n * import { z } from \"zod\";\n * import { dynamicSystemPrompt } from \"langchain\";\n * import { createAgent, SystemMessage } from \"langchain\";\n *\n * const contextSchema = z.object({ region: z.string().optional() });\n *\n * const middleware = dynamicSystemPrompt<z.infer<typeof contextSchema>>(\n * (_state, runtime) => `You are a helpful assistant. Region: ${runtime.context.region ?? \"n/a\"}`\n * );\n *\n * const agent = createAgent({\n * model: \"anthropic:claude-3-5-sonnet\",\n * contextSchema,\n * middleware: [middleware],\n * });\n *\n * await agent.invoke({ messages }, { context: { region: \"EU\" } });\n * ```\n *\n * @public\n */\nexport function dynamicSystemPromptMiddleware<TContextSchema = unknown>(\n fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>\n) {\n return createMiddleware({\n name: \"DynamicSystemPromptMiddleware\",\n wrapModelCall: async (request, handler) => {\n const systemPrompt = await fn(\n request.state as AgentBuiltInState,\n request.runtime as Runtime<TContextSchema>\n );\n\n const isExpectedType =\n typeof systemPrompt === \"string\" ||\n SystemMessage.isInstance(systemPrompt);\n if (!isExpectedType) {\n throw new Error(\n \"dynamicSystemPromptMiddleware function must return a string or SystemMessage\"\n );\n }\n\n return handler({\n ...request,\n systemMessage: request.systemMessage.concat(systemPrompt),\n });\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,8BACd,IACA;AACA,QAAOA,oCAAiB;EACtB,MAAM;EACN,eAAe,OAAO,SAAS,YAAY;GACzC,MAAM,eAAe,MAAM,GACzB,QAAQ,OACR,QAAQ,QACT;AAKD,OAAI,EAFF,OAAO,iBAAiB,YACxBC,uCAAc,WAAW,aAAa,EAEtC,OAAM,IAAI,MACR,+EACD;AAGH,UAAO,QAAQ;IACb,GAAG;IACH,eAAe,QAAQ,cAAc,OAAO,aAAa;IAC1D,CAAC;;EAEL,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AgentBuiltInState, Runtime } from "../runtime.cjs";
|
|
2
2
|
import { AgentMiddleware } from "./types.cjs";
|
|
3
3
|
import { SystemMessage } from "@langchain/core/messages";
|
|
4
|
-
import * as
|
|
4
|
+
import * as _langchain_core_tools0 from "@langchain/core/tools";
|
|
5
5
|
|
|
6
6
|
//#region src/agents/middleware/dynamicSystemPrompt.d.ts
|
|
7
7
|
type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (state: AgentBuiltInState, runtime: Runtime<TContextSchema>) => string | SystemMessage | Promise<string | SystemMessage>;
|
|
@@ -43,7 +43,7 @@ type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (state: AgentBuiltInS
|
|
|
43
43
|
*
|
|
44
44
|
* @public
|
|
45
45
|
*/
|
|
46
|
-
declare function dynamicSystemPromptMiddleware<TContextSchema = unknown>(fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>): AgentMiddleware<undefined, undefined, unknown, readonly (
|
|
46
|
+
declare function dynamicSystemPromptMiddleware<TContextSchema = unknown>(fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>): AgentMiddleware<undefined, undefined, unknown, readonly (_langchain_core_tools0.ServerTool | _langchain_core_tools0.ClientTool)[]>;
|
|
47
47
|
//#endregion
|
|
48
48
|
export { DynamicSystemPromptMiddlewareConfig, dynamicSystemPromptMiddleware };
|
|
49
49
|
//# sourceMappingURL=dynamicSystemPrompt.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamicSystemPrompt.d.cts","names":[
|
|
1
|
+
{"version":3,"file":"dynamicSystemPrompt.d.cts","names":[],"sources":["../../../src/agents/middleware/dynamicSystemPrompt.ts"],"mappings":";;;;;;KAIY,mCAAA,oBACV,KAAA,EAAO,iBAAA,EACP,OAAA,EAAS,OAAA,CAAQ,cAAA,eACL,aAAA,GAAgB,OAAA,UAAiB,aAAA;;;;AAH/C;;;;;;;;;;;;;;;;;;;;;;AA2CA;;;;;;;;;;;;;iBAAgB,6BAAA,0BAAA,CACd,EAAA,EAAI,mCAAA,CAAoC,cAAA,IAAe,eAAA,0CAAhB,sBAAA,CAAgB,UAAA,GAAA,sBAAA,CAAA,UAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AgentBuiltInState, Runtime } from "../runtime.js";
|
|
2
2
|
import { AgentMiddleware } from "./types.js";
|
|
3
3
|
import { SystemMessage } from "@langchain/core/messages";
|
|
4
|
-
import * as
|
|
4
|
+
import * as _langchain_core_tools0 from "@langchain/core/tools";
|
|
5
5
|
|
|
6
6
|
//#region src/agents/middleware/dynamicSystemPrompt.d.ts
|
|
7
7
|
type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (state: AgentBuiltInState, runtime: Runtime<TContextSchema>) => string | SystemMessage | Promise<string | SystemMessage>;
|
|
@@ -43,7 +43,7 @@ type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (state: AgentBuiltInS
|
|
|
43
43
|
*
|
|
44
44
|
* @public
|
|
45
45
|
*/
|
|
46
|
-
declare function dynamicSystemPromptMiddleware<TContextSchema = unknown>(fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>): AgentMiddleware<undefined, undefined, unknown, readonly (
|
|
46
|
+
declare function dynamicSystemPromptMiddleware<TContextSchema = unknown>(fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>): AgentMiddleware<undefined, undefined, unknown, readonly (_langchain_core_tools0.ServerTool | _langchain_core_tools0.ClientTool)[]>;
|
|
47
47
|
//#endregion
|
|
48
48
|
export { DynamicSystemPromptMiddlewareConfig, dynamicSystemPromptMiddleware };
|
|
49
49
|
//# sourceMappingURL=dynamicSystemPrompt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamicSystemPrompt.d.ts","names":[
|
|
1
|
+
{"version":3,"file":"dynamicSystemPrompt.d.ts","names":[],"sources":["../../../src/agents/middleware/dynamicSystemPrompt.ts"],"mappings":";;;;;;KAIY,mCAAA,oBACV,KAAA,EAAO,iBAAA,EACP,OAAA,EAAS,OAAA,CAAQ,cAAA,eACL,aAAA,GAAgB,OAAA,UAAiB,aAAA;;;;AAH/C;;;;;;;;;;;;;;;;;;;;;;AA2CA;;;;;;;;;;;;;iBAAgB,6BAAA,0BAAA,CACd,EAAA,EAAI,mCAAA,CAAoC,cAAA,IAAe,eAAA,0CAAhB,sBAAA,CAAgB,UAAA,GAAA,sBAAA,CAAA,UAAA"}
|
|
@@ -45,8 +45,7 @@ function dynamicSystemPromptMiddleware(fn) {
|
|
|
45
45
|
name: "DynamicSystemPromptMiddleware",
|
|
46
46
|
wrapModelCall: async (request, handler) => {
|
|
47
47
|
const systemPrompt = await fn(request.state, request.runtime);
|
|
48
|
-
|
|
49
|
-
if (!isExpectedType) throw new Error("dynamicSystemPromptMiddleware function must return a string or SystemMessage");
|
|
48
|
+
if (!(typeof systemPrompt === "string" || SystemMessage.isInstance(systemPrompt))) throw new Error("dynamicSystemPromptMiddleware function must return a string or SystemMessage");
|
|
50
49
|
return handler({
|
|
51
50
|
...request,
|
|
52
51
|
systemMessage: request.systemMessage.concat(systemPrompt)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamicSystemPrompt.js","names":[
|
|
1
|
+
{"version":3,"file":"dynamicSystemPrompt.js","names":[],"sources":["../../../src/agents/middleware/dynamicSystemPrompt.ts"],"sourcesContent":["import { SystemMessage } from \"@langchain/core/messages\";\nimport { createMiddleware } from \"../middleware.js\";\nimport type { Runtime, AgentBuiltInState } from \"../runtime.js\";\n\nexport type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (\n state: AgentBuiltInState,\n runtime: Runtime<TContextSchema>\n) => string | SystemMessage | Promise<string | SystemMessage>;\n\n/**\n * Dynamic System Prompt Middleware\n *\n * Allows setting the system prompt dynamically right before each model invocation.\n * Useful when the prompt depends on the current agent state or per-invocation context.\n *\n * @typeParam TContextSchema - The shape of the runtime context available at invocation time.\n * If your agent defines a `contextSchema`, pass the inferred type here to get full type-safety\n * for `runtime.context`.\n *\n * @param fn - Function that receives the current agent `state` and `runtime`, and\n * returns the system prompt for the next model call as a string.\n *\n * @returns A middleware instance that sets `systemPrompt` for the next model call.\n *\n * @example Basic usage with typed context\n * ```ts\n * import { z } from \"zod\";\n * import { dynamicSystemPrompt } from \"langchain\";\n * import { createAgent, SystemMessage } from \"langchain\";\n *\n * const contextSchema = z.object({ region: z.string().optional() });\n *\n * const middleware = dynamicSystemPrompt<z.infer<typeof contextSchema>>(\n * (_state, runtime) => `You are a helpful assistant. Region: ${runtime.context.region ?? \"n/a\"}`\n * );\n *\n * const agent = createAgent({\n * model: \"anthropic:claude-3-5-sonnet\",\n * contextSchema,\n * middleware: [middleware],\n * });\n *\n * await agent.invoke({ messages }, { context: { region: \"EU\" } });\n * ```\n *\n * @public\n */\nexport function dynamicSystemPromptMiddleware<TContextSchema = unknown>(\n fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>\n) {\n return createMiddleware({\n name: \"DynamicSystemPromptMiddleware\",\n wrapModelCall: async (request, handler) => {\n const systemPrompt = await fn(\n request.state as AgentBuiltInState,\n request.runtime as Runtime<TContextSchema>\n );\n\n const isExpectedType =\n typeof systemPrompt === \"string\" ||\n SystemMessage.isInstance(systemPrompt);\n if (!isExpectedType) {\n throw new Error(\n \"dynamicSystemPromptMiddleware function must return a string or SystemMessage\"\n );\n }\n\n return handler({\n ...request,\n systemMessage: request.systemMessage.concat(systemPrompt),\n });\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,8BACd,IACA;AACA,QAAO,iBAAiB;EACtB,MAAM;EACN,eAAe,OAAO,SAAS,YAAY;GACzC,MAAM,eAAe,MAAM,GACzB,QAAQ,OACR,QAAQ,QACT;AAKD,OAAI,EAFF,OAAO,iBAAiB,YACxB,cAAc,WAAW,aAAa,EAEtC,OAAM,IAAI,MACR,+EACD;AAGH,UAAO,QAAQ;IACb,GAAG;IACH,eAAe,QAAQ,cAAc,OAAO,aAAa;IAC1D,CAAC;;EAEL,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const require_runtime = require('../../_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
let zod_v4 = require("zod/v4");
|
|
3
3
|
|
|
4
4
|
//#region src/agents/middleware/error.ts
|
|
5
5
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.cjs","names":["
|
|
1
|
+
{"version":3,"file":"error.cjs","names":["z4"],"sources":["../../../src/agents/middleware/error.ts"],"sourcesContent":["import { type ZodError } from \"zod/v3\";\nimport { z as z4 } from \"zod/v4\";\n\n/**\n * Error thrown when the configuration for a retry middleware is invalid.\n */\nexport class InvalidRetryConfigError extends Error {\n cause: ZodError;\n\n constructor(error: ZodError) {\n const message = z4.prettifyError(error).slice(2);\n super(message);\n this.name = \"InvalidRetryConfigError\";\n this.cause = error;\n }\n}\n"],"mappings":";;;;;;;AAMA,IAAa,0BAAb,cAA6C,MAAM;CACjD;CAEA,YAAY,OAAiB;EAC3B,MAAM,UAAUA,SAAG,cAAc,MAAM,CAAC,MAAM,EAAE;AAChD,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,QAAQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.js","names":["
|
|
1
|
+
{"version":3,"file":"error.js","names":["z4"],"sources":["../../../src/agents/middleware/error.ts"],"sourcesContent":["import { type ZodError } from \"zod/v3\";\nimport { z as z4 } from \"zod/v4\";\n\n/**\n * Error thrown when the configuration for a retry middleware is invalid.\n */\nexport class InvalidRetryConfigError extends Error {\n cause: ZodError;\n\n constructor(error: ZodError) {\n const message = z4.prettifyError(error).slice(2);\n super(message);\n this.name = \"InvalidRetryConfigError\";\n this.cause = error;\n }\n}\n"],"mappings":";;;;;;AAMA,IAAa,0BAAb,cAA6C,MAAM;CACjD;CAEA,YAAY,OAAiB;EAC3B,MAAM,UAAUA,EAAG,cAAc,MAAM,CAAC,MAAM,EAAE;AAChD,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,QAAQ"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_runtime = require('../../_virtual/_rolldown/runtime.cjs');
|
|
2
2
|
const require_middleware = require('../middleware.cjs');
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
let _langchain_core_messages = require("@langchain/core/messages");
|
|
4
|
+
let _langchain_langgraph = require("@langchain/langgraph");
|
|
5
|
+
let _langchain_core_utils_types = require("@langchain/core/utils/types");
|
|
6
|
+
let zod_v3 = require("zod/v3");
|
|
7
7
|
|
|
8
8
|
//#region src/agents/middleware/hitl.ts
|
|
9
9
|
const DescriptionFunctionSchema = zod_v3.z.function().args(zod_v3.z.custom(), zod_v3.z.custom(), zod_v3.z.custom()).returns(zod_v3.z.union([zod_v3.z.string(), zod_v3.z.promise(zod_v3.z.string())]));
|
|
@@ -292,16 +292,14 @@ function humanInTheLoopMiddleware(options) {
|
|
|
292
292
|
* Validate that message is a string if provided
|
|
293
293
|
*/
|
|
294
294
|
if (decision.message !== void 0 && typeof decision.message !== "string") throw new Error(`Tool call response for "${toolCall.name}" must be a string, got ${typeof decision.message}`);
|
|
295
|
-
const content = decision.message ?? `User rejected the tool call for \`${toolCall.name}\` with id ${toolCall.id}`;
|
|
296
|
-
const toolMessage = new __langchain_core_messages.ToolMessage({
|
|
297
|
-
content,
|
|
298
|
-
name: toolCall.name,
|
|
299
|
-
tool_call_id: toolCall.id,
|
|
300
|
-
status: "error"
|
|
301
|
-
});
|
|
302
295
|
return {
|
|
303
296
|
revisedToolCall: toolCall,
|
|
304
|
-
toolMessage
|
|
297
|
+
toolMessage: new _langchain_core_messages.ToolMessage({
|
|
298
|
+
content: decision.message ?? `User rejected the tool call for \`${toolCall.name}\` with id ${toolCall.id}`,
|
|
299
|
+
name: toolCall.name,
|
|
300
|
+
tool_call_id: toolCall.id,
|
|
301
|
+
status: "error"
|
|
302
|
+
})
|
|
305
303
|
};
|
|
306
304
|
}
|
|
307
305
|
const msg = `Unexpected human decision: ${JSON.stringify(decision)}. Decision type '${decision.type}' is not allowed for tool '${toolCall.name}'. Expected one of ${JSON.stringify(allowedDecisions)} based on the tool's configuration.`;
|
|
@@ -313,7 +311,7 @@ function humanInTheLoopMiddleware(options) {
|
|
|
313
311
|
afterModel: {
|
|
314
312
|
canJumpTo: ["model"],
|
|
315
313
|
hook: async (state, runtime) => {
|
|
316
|
-
const config = (0,
|
|
314
|
+
const config = (0, _langchain_core_utils_types.interopParse)(contextSchema, {
|
|
317
315
|
...options,
|
|
318
316
|
...runtime.context || {}
|
|
319
317
|
});
|
|
@@ -323,7 +321,7 @@ function humanInTheLoopMiddleware(options) {
|
|
|
323
321
|
/**
|
|
324
322
|
* Don't do anything if the last message isn't an AI message with tool calls.
|
|
325
323
|
*/
|
|
326
|
-
const lastMessage = [...messages].reverse().find((msg) =>
|
|
324
|
+
const lastMessage = [...messages].reverse().find((msg) => _langchain_core_messages.AIMessage.isInstance(msg));
|
|
327
325
|
if (!lastMessage || !lastMessage.tool_calls?.length) return;
|
|
328
326
|
/**
|
|
329
327
|
* If the user omits the interruptOn config, we don't do anything.
|
|
@@ -358,18 +356,10 @@ function humanInTheLoopMiddleware(options) {
|
|
|
358
356
|
actionRequests.push(actionRequest);
|
|
359
357
|
reviewConfigs.push(reviewConfig);
|
|
360
358
|
}
|
|
361
|
-
|
|
362
|
-
* Create single HITLRequest with all actions and configs
|
|
363
|
-
*/
|
|
364
|
-
const hitlRequest = {
|
|
359
|
+
const decisions = (await (0, _langchain_langgraph.interrupt)({
|
|
365
360
|
actionRequests,
|
|
366
361
|
reviewConfigs
|
|
367
|
-
};
|
|
368
|
-
/**
|
|
369
|
-
* Send interrupt and get response
|
|
370
|
-
*/
|
|
371
|
-
const hitlResponse = await (0, __langchain_langgraph.interrupt)(hitlRequest);
|
|
372
|
-
const decisions = hitlResponse.decisions;
|
|
362
|
+
})).decisions;
|
|
373
363
|
/**
|
|
374
364
|
* Validate that decisions is a valid array before checking length
|
|
375
365
|
*/
|
|
@@ -395,7 +385,7 @@ function humanInTheLoopMiddleware(options) {
|
|
|
395
385
|
/**
|
|
396
386
|
* Update the AI message to only include approved tool calls
|
|
397
387
|
*/
|
|
398
|
-
if (
|
|
388
|
+
if (_langchain_core_messages.AIMessage.isInstance(lastMessage)) lastMessage.tool_calls = revisedToolCalls;
|
|
399
389
|
const jumpTo = hasRejectedToolCalls ? "model" : void 0;
|
|
400
390
|
return {
|
|
401
391
|
messages: [lastMessage, ...artificialToolMessages],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hitl.cjs","names":["z","options: NonNullable<HumanInTheLoopMiddlewareConfig>","toolCall: ToolCall","config: InterruptOnConfig","state: AgentBuiltInState","runtime: Runtime<unknown>","description: string","actionRequest: ActionRequest","reviewConfig: ReviewConfig","decision: Decision","ToolMessage","createMiddleware","AIMessage","resolvedConfigs: Record<string, InterruptOnConfig>","interruptToolCalls: ToolCall[]","autoApprovedToolCalls: ToolCall[]","actionRequests: ActionRequest[]","reviewConfigs: ReviewConfig[]","hitlRequest: HITLRequest","revisedToolCalls: ToolCall[]","artificialToolMessages: ToolMessage[]","jumpTo: JumpToTarget | undefined"],"sources":["../../../src/agents/middleware/hitl.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { z } from \"zod/v3\";\nimport { AIMessage, ToolMessage, ToolCall } from \"@langchain/core/messages\";\nimport {\n InferInteropZodInput,\n interopParse,\n} from \"@langchain/core/utils/types\";\nimport { interrupt } from \"@langchain/langgraph\";\n\nimport { createMiddleware } from \"../middleware.js\";\nimport type { AgentBuiltInState, Runtime } from \"../runtime.js\";\nimport type { JumpToTarget } from \"../constants.js\";\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\"] 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 argsSchema: z.record(z.any()).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 * Union of all possible decision types.\n */\nexport type Decision = ApproveDecision | EditDecision | RejectDecision;\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 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 approve/edit/reject decisions\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/**\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.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 * - Multiple tools requiring approval are bundled into a single interrupt request\n * - The middleware operates in the `afterModel` phase, intercepting before tool execution\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 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 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 return createMiddleware({\n name: \"HumanInTheLoopMiddleware\",\n contextSchema,\n afterModel: {\n canJumpTo: [\"model\"],\n hook: async (state, runtime) => {\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: Record<string, InterruptOnConfig> = {};\n for (const [toolName, toolConfig] of Object.entries(\n config.interruptOn\n )) {\n if (typeof toolConfig === \"boolean\") {\n if (toolConfig === true) {\n resolvedConfigs[toolName] = {\n allowedDecisions: [...ALLOWED_DECISIONS],\n };\n }\n } else if (toolConfig.allowedDecisions) {\n resolvedConfigs[toolName] = toolConfig as InterruptOnConfig;\n }\n }\n\n const interruptToolCalls: ToolCall[] = [];\n const autoApprovedToolCalls: ToolCall[] = [];\n\n for (const toolCall of lastMessage.tool_calls) {\n if (toolCall.name in resolvedConfigs) {\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 const hasRejectedToolCalls = decisions.some(\n (decision) => decision.type === \"reject\"\n );\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 (\n revisedToolCall &&\n /**\n * If any decision is a rejected, we are going back to the model\n * with only the tool calls that were rejected as we don't know\n * the results of the approved/updated tool calls at this point.\n */\n (!hasRejectedToolCalls || decision.type === \"reject\")\n ) {\n revisedToolCalls.push(revisedToolCall);\n }\n if (toolMessage) {\n artificialToolMessages.push(toolMessage);\n }\n }\n\n /**\n * Update the AI message to only include approved tool calls\n */\n if (AIMessage.isInstance(lastMessage)) {\n lastMessage.tool_calls = revisedToolCalls;\n }\n\n const jumpTo: JumpToTarget | undefined = hasRejectedToolCalls\n ? \"model\"\n : undefined;\n return {\n messages: [lastMessage, ...artificialToolMessages],\n jumpTo,\n };\n },\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hitl.cjs","names":["z","ToolMessage","createMiddleware","AIMessage"],"sources":["../../../src/agents/middleware/hitl.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { z } from \"zod/v3\";\nimport { AIMessage, ToolMessage, ToolCall } from \"@langchain/core/messages\";\nimport {\n InferInteropZodInput,\n interopParse,\n} from \"@langchain/core/utils/types\";\nimport { interrupt } from \"@langchain/langgraph\";\n\nimport { createMiddleware } from \"../middleware.js\";\nimport type { AgentBuiltInState, Runtime } from \"../runtime.js\";\nimport type { JumpToTarget } from \"../constants.js\";\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\"] 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 argsSchema: z.record(z.any()).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 * Union of all possible decision types.\n */\nexport type Decision = ApproveDecision | EditDecision | RejectDecision;\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 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 approve/edit/reject decisions\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/**\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.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 * - Multiple tools requiring approval are bundled into a single interrupt request\n * - The middleware operates in the `afterModel` phase, intercepting before tool execution\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 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 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 return createMiddleware({\n name: \"HumanInTheLoopMiddleware\",\n contextSchema,\n afterModel: {\n canJumpTo: [\"model\"],\n hook: async (state, runtime) => {\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: Record<string, InterruptOnConfig> = {};\n for (const [toolName, toolConfig] of Object.entries(\n config.interruptOn\n )) {\n if (typeof toolConfig === \"boolean\") {\n if (toolConfig === true) {\n resolvedConfigs[toolName] = {\n allowedDecisions: [...ALLOWED_DECISIONS],\n };\n }\n } else if (toolConfig.allowedDecisions) {\n resolvedConfigs[toolName] = toolConfig as InterruptOnConfig;\n }\n }\n\n const interruptToolCalls: ToolCall[] = [];\n const autoApprovedToolCalls: ToolCall[] = [];\n\n for (const toolCall of lastMessage.tool_calls) {\n if (toolCall.name in resolvedConfigs) {\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 const hasRejectedToolCalls = decisions.some(\n (decision) => decision.type === \"reject\"\n );\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 (\n revisedToolCall &&\n /**\n * If any decision is a rejected, we are going back to the model\n * with only the tool calls that were rejected as we don't know\n * the results of the approved/updated tool calls at this point.\n */\n (!hasRejectedToolCalls || decision.type === \"reject\")\n ) {\n revisedToolCalls.push(revisedToolCall);\n }\n if (toolMessage) {\n artificialToolMessages.push(toolMessage);\n }\n }\n\n /**\n * Update the AI message to only include approved tool calls\n */\n if (AIMessage.isInstance(lastMessage)) {\n lastMessage.tool_calls = revisedToolCalls;\n }\n\n const jumpTo: JumpToTarget | undefined = hasRejectedToolCalls\n ? \"model\"\n : undefined;\n return {\n messages: [lastMessage, ...artificialToolMessages],\n jumpTo,\n };\n },\n },\n });\n}\n"],"mappings":"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"}
|