langchain 1.0.5 → 1.1.0

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.
Files changed (216) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1 -1
  3. package/chat_models/universal.cjs +1 -0
  4. package/chat_models/universal.d.cts +1 -0
  5. package/chat_models/universal.d.ts +1 -0
  6. package/chat_models/universal.js +1 -0
  7. package/dist/agents/ReactAgent.cjs +43 -39
  8. package/dist/agents/ReactAgent.cjs.map +1 -1
  9. package/dist/agents/ReactAgent.js +46 -42
  10. package/dist/agents/ReactAgent.js.map +1 -1
  11. package/dist/agents/index.d.cts +0 -2
  12. package/dist/agents/index.d.ts +0 -2
  13. package/dist/agents/middleware/constants.cjs +16 -0
  14. package/dist/agents/middleware/constants.cjs.map +1 -0
  15. package/dist/agents/middleware/constants.js +15 -0
  16. package/dist/agents/middleware/constants.js.map +1 -0
  17. package/dist/agents/middleware/contextEditing.cjs.map +1 -1
  18. package/dist/agents/middleware/contextEditing.d.cts +23 -7
  19. package/dist/agents/middleware/contextEditing.d.ts +23 -7
  20. package/dist/agents/middleware/contextEditing.js.map +1 -1
  21. package/dist/agents/middleware/dynamicSystemPrompt.cjs +5 -2
  22. package/dist/agents/middleware/dynamicSystemPrompt.cjs.map +1 -1
  23. package/dist/agents/middleware/dynamicSystemPrompt.d.cts +2 -1
  24. package/dist/agents/middleware/dynamicSystemPrompt.d.ts +2 -1
  25. package/dist/agents/middleware/dynamicSystemPrompt.js +4 -2
  26. package/dist/agents/middleware/dynamicSystemPrompt.js.map +1 -1
  27. package/dist/agents/middleware/error.cjs +20 -0
  28. package/dist/agents/middleware/error.cjs.map +1 -0
  29. package/dist/agents/middleware/error.js +19 -0
  30. package/dist/agents/middleware/error.js.map +1 -0
  31. package/dist/agents/middleware/index.cjs +4 -2
  32. package/dist/agents/middleware/index.d.ts +18 -0
  33. package/dist/agents/middleware/index.js +4 -2
  34. package/dist/agents/middleware/modelRetry.cjs +162 -0
  35. package/dist/agents/middleware/modelRetry.cjs.map +1 -0
  36. package/dist/agents/middleware/modelRetry.d.cts +134 -0
  37. package/dist/agents/middleware/modelRetry.d.ts +134 -0
  38. package/dist/agents/middleware/modelRetry.js +161 -0
  39. package/dist/agents/middleware/modelRetry.js.map +1 -0
  40. package/dist/agents/middleware/{promptCaching.cjs → provider/anthropic/promptCaching.cjs} +3 -3
  41. package/dist/agents/middleware/provider/anthropic/promptCaching.cjs.map +1 -0
  42. package/dist/agents/middleware/{promptCaching.d.cts → provider/anthropic/promptCaching.d.cts} +2 -2
  43. package/dist/agents/middleware/{promptCaching.d.ts → provider/anthropic/promptCaching.d.ts} +2 -2
  44. package/dist/agents/middleware/{promptCaching.js → provider/anthropic/promptCaching.js} +2 -2
  45. package/dist/agents/middleware/provider/anthropic/promptCaching.js.map +1 -0
  46. package/dist/agents/middleware/provider/openai/moderation.cjs +299 -0
  47. package/dist/agents/middleware/provider/openai/moderation.cjs.map +1 -0
  48. package/dist/agents/middleware/provider/openai/moderation.d.cts +133 -0
  49. package/dist/agents/middleware/provider/openai/moderation.d.ts +133 -0
  50. package/dist/agents/middleware/provider/openai/moderation.js +298 -0
  51. package/dist/agents/middleware/provider/openai/moderation.js.map +1 -0
  52. package/dist/agents/middleware/summarization.d.cts +0 -4
  53. package/dist/agents/middleware/summarization.d.ts +0 -4
  54. package/dist/agents/middleware/todoListMiddleware.cjs +1 -1
  55. package/dist/agents/middleware/todoListMiddleware.cjs.map +1 -1
  56. package/dist/agents/middleware/todoListMiddleware.js +1 -1
  57. package/dist/agents/middleware/todoListMiddleware.js.map +1 -1
  58. package/dist/agents/middleware/toolRetry.cjs +32 -44
  59. package/dist/agents/middleware/toolRetry.cjs.map +1 -1
  60. package/dist/agents/middleware/toolRetry.d.cts +16 -36
  61. package/dist/agents/middleware/toolRetry.d.ts +16 -36
  62. package/dist/agents/middleware/toolRetry.js +32 -44
  63. package/dist/agents/middleware/toolRetry.js.map +1 -1
  64. package/dist/agents/middleware/types.d.cts +9 -10
  65. package/dist/agents/middleware/types.d.ts +9 -10
  66. package/dist/agents/middleware/utils.cjs +23 -0
  67. package/dist/agents/middleware/utils.cjs.map +1 -1
  68. package/dist/agents/middleware/utils.d.ts +2 -0
  69. package/dist/agents/middleware/utils.js +23 -1
  70. package/dist/agents/middleware/utils.js.map +1 -1
  71. package/dist/agents/nodes/AgentNode.cjs +72 -28
  72. package/dist/agents/nodes/AgentNode.cjs.map +1 -1
  73. package/dist/agents/nodes/AgentNode.js +74 -31
  74. package/dist/agents/nodes/AgentNode.js.map +1 -1
  75. package/dist/agents/nodes/ToolNode.cjs +5 -0
  76. package/dist/agents/nodes/ToolNode.cjs.map +1 -1
  77. package/dist/agents/nodes/ToolNode.js +5 -1
  78. package/dist/agents/nodes/ToolNode.js.map +1 -1
  79. package/dist/agents/nodes/types.d.cts +39 -3
  80. package/dist/agents/nodes/types.d.ts +39 -3
  81. package/dist/agents/responses.cjs.map +1 -1
  82. package/dist/agents/responses.d.cts +2 -19
  83. package/dist/agents/responses.d.ts +2 -19
  84. package/dist/agents/responses.js.map +1 -1
  85. package/dist/agents/runtime.d.ts +1 -0
  86. package/dist/agents/tests/utils.cjs +10 -1
  87. package/dist/agents/tests/utils.cjs.map +1 -1
  88. package/dist/agents/tests/utils.js +10 -1
  89. package/dist/agents/tests/utils.js.map +1 -1
  90. package/dist/agents/types.d.cts +68 -2
  91. package/dist/agents/types.d.ts +68 -2
  92. package/dist/agents/utils.cjs +15 -12
  93. package/dist/agents/utils.cjs.map +1 -1
  94. package/dist/agents/utils.js +16 -13
  95. package/dist/agents/utils.js.map +1 -1
  96. package/dist/chat_models/universal.cjs +50 -16
  97. package/dist/chat_models/universal.cjs.map +1 -1
  98. package/dist/chat_models/universal.d.cts +19 -1
  99. package/dist/chat_models/universal.d.ts +19 -1
  100. package/dist/chat_models/universal.js +50 -16
  101. package/dist/chat_models/universal.js.map +1 -1
  102. package/dist/index.cjs +8 -2
  103. package/dist/index.d.cts +5 -3
  104. package/dist/index.d.ts +6 -3
  105. package/dist/index.js +7 -3
  106. package/dist/load/import_constants.cjs +2 -1
  107. package/dist/load/import_constants.cjs.map +1 -1
  108. package/dist/load/import_constants.js +2 -1
  109. package/dist/load/import_constants.js.map +1 -1
  110. package/dist/load/import_map.cjs +2 -19
  111. package/dist/load/import_map.cjs.map +1 -1
  112. package/dist/load/import_map.js +2 -19
  113. package/dist/load/import_map.js.map +1 -1
  114. package/hub/node.cjs +1 -0
  115. package/hub/node.d.cts +1 -0
  116. package/hub/node.d.ts +1 -0
  117. package/hub/node.js +1 -0
  118. package/hub.cjs +1 -0
  119. package/hub.d.cts +1 -0
  120. package/hub.d.ts +1 -0
  121. package/hub.js +1 -0
  122. package/load/serializable.cjs +1 -0
  123. package/load/serializable.d.cts +1 -0
  124. package/load/serializable.d.ts +1 -0
  125. package/load/serializable.js +1 -0
  126. package/load.cjs +1 -0
  127. package/load.d.cts +1 -0
  128. package/load.d.ts +1 -0
  129. package/load.js +1 -0
  130. package/package.json +65 -52
  131. package/storage/encoder_backed.cjs +1 -0
  132. package/storage/encoder_backed.d.cts +1 -0
  133. package/storage/encoder_backed.d.ts +1 -0
  134. package/storage/encoder_backed.js +1 -0
  135. package/storage/file_system.cjs +1 -0
  136. package/storage/file_system.d.cts +1 -0
  137. package/storage/file_system.d.ts +1 -0
  138. package/storage/file_system.js +1 -0
  139. package/storage/in_memory.cjs +1 -0
  140. package/storage/in_memory.d.cts +1 -0
  141. package/storage/in_memory.d.ts +1 -0
  142. package/storage/in_memory.js +1 -0
  143. package/dist/agents/ReactAgent.d.cts.map +0 -1
  144. package/dist/agents/ReactAgent.d.ts.map +0 -1
  145. package/dist/agents/constants.cjs +0 -7
  146. package/dist/agents/constants.cjs.map +0 -1
  147. package/dist/agents/constants.d.cts.map +0 -1
  148. package/dist/agents/constants.d.ts.map +0 -1
  149. package/dist/agents/constants.js +0 -6
  150. package/dist/agents/constants.js.map +0 -1
  151. package/dist/agents/errors.d.cts.map +0 -1
  152. package/dist/agents/errors.d.ts.map +0 -1
  153. package/dist/agents/index.d.cts.map +0 -1
  154. package/dist/agents/index.d.ts.map +0 -1
  155. package/dist/agents/middleware/contextEditing.d.cts.map +0 -1
  156. package/dist/agents/middleware/contextEditing.d.ts.map +0 -1
  157. package/dist/agents/middleware/dynamicSystemPrompt.d.cts.map +0 -1
  158. package/dist/agents/middleware/dynamicSystemPrompt.d.ts.map +0 -1
  159. package/dist/agents/middleware/hitl.d.cts.map +0 -1
  160. package/dist/agents/middleware/hitl.d.ts.map +0 -1
  161. package/dist/agents/middleware/llmToolSelector.d.cts.map +0 -1
  162. package/dist/agents/middleware/llmToolSelector.d.ts.map +0 -1
  163. package/dist/agents/middleware/modelCallLimit.d.cts.map +0 -1
  164. package/dist/agents/middleware/modelCallLimit.d.ts.map +0 -1
  165. package/dist/agents/middleware/modelFallback.d.cts.map +0 -1
  166. package/dist/agents/middleware/modelFallback.d.ts.map +0 -1
  167. package/dist/agents/middleware/pii.d.cts.map +0 -1
  168. package/dist/agents/middleware/pii.d.ts.map +0 -1
  169. package/dist/agents/middleware/piiRedaction.d.cts.map +0 -1
  170. package/dist/agents/middleware/piiRedaction.d.ts.map +0 -1
  171. package/dist/agents/middleware/promptCaching.cjs.map +0 -1
  172. package/dist/agents/middleware/promptCaching.d.cts.map +0 -1
  173. package/dist/agents/middleware/promptCaching.d.ts.map +0 -1
  174. package/dist/agents/middleware/promptCaching.js.map +0 -1
  175. package/dist/agents/middleware/summarization.d.cts.map +0 -1
  176. package/dist/agents/middleware/summarization.d.ts.map +0 -1
  177. package/dist/agents/middleware/todoListMiddleware.d.cts.map +0 -1
  178. package/dist/agents/middleware/todoListMiddleware.d.ts.map +0 -1
  179. package/dist/agents/middleware/toolCallLimit.d.cts.map +0 -1
  180. package/dist/agents/middleware/toolCallLimit.d.ts.map +0 -1
  181. package/dist/agents/middleware/toolEmulator.d.cts.map +0 -1
  182. package/dist/agents/middleware/toolEmulator.d.ts.map +0 -1
  183. package/dist/agents/middleware/toolRetry.d.cts.map +0 -1
  184. package/dist/agents/middleware/toolRetry.d.ts.map +0 -1
  185. package/dist/agents/middleware/types.d.cts.map +0 -1
  186. package/dist/agents/middleware/types.d.ts.map +0 -1
  187. package/dist/agents/middleware/utils.d.cts.map +0 -1
  188. package/dist/agents/middleware/utils.d.ts.map +0 -1
  189. package/dist/agents/middleware.d.cts.map +0 -1
  190. package/dist/agents/middleware.d.ts.map +0 -1
  191. package/dist/agents/nodes/types.d.cts.map +0 -1
  192. package/dist/agents/nodes/types.d.ts.map +0 -1
  193. package/dist/agents/responses.d.cts.map +0 -1
  194. package/dist/agents/responses.d.ts.map +0 -1
  195. package/dist/agents/runtime.d.cts.map +0 -1
  196. package/dist/agents/runtime.d.ts.map +0 -1
  197. package/dist/agents/tests/utils.d.cts.map +0 -1
  198. package/dist/agents/tests/utils.d.ts.map +0 -1
  199. package/dist/agents/types.d.cts.map +0 -1
  200. package/dist/agents/types.d.ts.map +0 -1
  201. package/dist/chat_models/universal.d.cts.map +0 -1
  202. package/dist/chat_models/universal.d.ts.map +0 -1
  203. package/dist/hub/base.d.cts.map +0 -1
  204. package/dist/hub/base.d.ts.map +0 -1
  205. package/dist/hub/index.d.cts.map +0 -1
  206. package/dist/hub/index.d.ts.map +0 -1
  207. package/dist/hub/node.d.cts.map +0 -1
  208. package/dist/hub/node.d.ts.map +0 -1
  209. package/dist/load/import_type.d.cts.map +0 -1
  210. package/dist/load/import_type.d.ts.map +0 -1
  211. package/dist/load/index.d.cts.map +0 -1
  212. package/dist/load/index.d.ts.map +0 -1
  213. package/dist/storage/encoder_backed.d.cts.map +0 -1
  214. package/dist/storage/encoder_backed.d.ts.map +0 -1
  215. package/dist/storage/file_system.d.cts.map +0 -1
  216. package/dist/storage/file_system.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"contextEditing.cjs","names":["config: ClearToolUsesEditConfig","trigger: ContextSize | ContextSize[] | undefined","keep: KeepSize | undefined","#triggerConditions","contextSizeSchema","keepSchema","params: {\n messages: BaseMessage[];\n model: BaseLanguageModel;\n countTokens: TokenCounter;\n }","orphanedIndices: number[]","ToolMessage","#findAIMessageForToolCall","#shouldEdit","candidates: { idx: number; msg: ToolMessage }[]","#determineKeepCount","#buildClearedToolInputMessage","messages: BaseMessage[]","totalTokens: number","model: BaseLanguageModel","getProfileLimits","candidates: Array<{ idx: number; msg: ToolMessage }>","countTokens: TokenCounter","previousMessages: BaseMessage[]","toolCallId: string","AIMessage","message: AIMessage","config: ContextEditingMiddlewareConfig","createMiddleware","SystemMessage","countTokensApproximately"],"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 { SystemMessage } from \"langchain\";\n *\n * class RemoveOldSystemMessages implements ContextEdit {\n * async apply({ tokens, messages, countTokens }) {\n * // Remove old system messages if over limit\n * if (tokens > 50000) {\n * messages = messages.filter(SystemMessage.isInstance);\n * return await countTokens(messages);\n * }\n * return tokens;\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.cjs","names":["config: ClearToolUsesEditConfig","trigger: ContextSize | ContextSize[] | undefined","keep: KeepSize | undefined","#triggerConditions","contextSizeSchema","keepSchema","params: {\n messages: BaseMessage[];\n model: BaseLanguageModel;\n countTokens: TokenCounter;\n }","orphanedIndices: number[]","ToolMessage","#findAIMessageForToolCall","#shouldEdit","candidates: { idx: number; msg: ToolMessage }[]","#determineKeepCount","#buildClearedToolInputMessage","messages: BaseMessage[]","totalTokens: number","model: BaseLanguageModel","getProfileLimits","candidates: Array<{ idx: number; msg: ToolMessage }>","countTokens: TokenCounter","previousMessages: BaseMessage[]","toolCallId: string","AIMessage","message: AIMessage","config: ContextEditingMiddlewareConfig","createMiddleware","SystemMessage","countTokensApproximately"],"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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"}
@@ -14,16 +14,32 @@ import { BaseMessage } from "@langchain/core/messages";
14
14
  *
15
15
  * @example
16
16
  * ```ts
17
- * import { SystemMessage } from "langchain";
17
+ * import { HumanMessage, type ContextEdit, type BaseMessage } from "langchain";
18
18
  *
19
- * class RemoveOldSystemMessages implements ContextEdit {
20
- * async apply({ tokens, messages, countTokens }) {
21
- * // Remove old system messages if over limit
19
+ * class RemoveOldHumanMessages implements ContextEdit {
20
+ * constructor(private keepRecent: number = 10) {}
21
+ *
22
+ * async apply({ messages, countTokens }) {
23
+ * // Check current token count
24
+ * const tokens = await countTokens(messages);
25
+ *
26
+ * // Remove old human messages if over limit, keeping the most recent ones
22
27
  * if (tokens > 50000) {
23
- * messages = messages.filter(SystemMessage.isInstance);
24
- * return await countTokens(messages);
28
+ * const humanMessages: number[] = [];
29
+ *
30
+ * // Find all human message indices
31
+ * for (let i = 0; i < messages.length; i++) {
32
+ * if (HumanMessage.isInstance(messages[i])) {
33
+ * humanMessages.push(i);
34
+ * }
35
+ * }
36
+ *
37
+ * // Remove old human messages (keep only the most recent N)
38
+ * const toRemove = humanMessages.slice(0, -this.keepRecent);
39
+ * for (let i = toRemove.length - 1; i >= 0; i--) {
40
+ * messages.splice(toRemove[i]!, 1);
41
+ * }
25
42
  * }
26
- * return tokens;
27
43
  * }
28
44
  * }
29
45
  * ```
@@ -14,16 +14,32 @@ import { BaseLanguageModel } from "@langchain/core/language_models/base";
14
14
  *
15
15
  * @example
16
16
  * ```ts
17
- * import { SystemMessage } from "langchain";
17
+ * import { HumanMessage, type ContextEdit, type BaseMessage } from "langchain";
18
18
  *
19
- * class RemoveOldSystemMessages implements ContextEdit {
20
- * async apply({ tokens, messages, countTokens }) {
21
- * // Remove old system messages if over limit
19
+ * class RemoveOldHumanMessages implements ContextEdit {
20
+ * constructor(private keepRecent: number = 10) {}
21
+ *
22
+ * async apply({ messages, countTokens }) {
23
+ * // Check current token count
24
+ * const tokens = await countTokens(messages);
25
+ *
26
+ * // Remove old human messages if over limit, keeping the most recent ones
22
27
  * if (tokens > 50000) {
23
- * messages = messages.filter(SystemMessage.isInstance);
24
- * return await countTokens(messages);
28
+ * const humanMessages: number[] = [];
29
+ *
30
+ * // Find all human message indices
31
+ * for (let i = 0; i < messages.length; i++) {
32
+ * if (HumanMessage.isInstance(messages[i])) {
33
+ * humanMessages.push(i);
34
+ * }
35
+ * }
36
+ *
37
+ * // Remove old human messages (keep only the most recent N)
38
+ * const toRemove = humanMessages.slice(0, -this.keepRecent);
39
+ * for (let i = toRemove.length - 1; i >= 0; i--) {
40
+ * messages.splice(toRemove[i]!, 1);
41
+ * }
25
42
  * }
26
- * return tokens;
27
43
  * }
28
44
  * }
29
45
  * ```
@@ -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 { SystemMessage } from \"langchain\";\n *\n * class RemoveOldSystemMessages implements ContextEdit {\n * async apply({ tokens, messages, countTokens }) {\n * // Remove old system messages if over limit\n * if (tokens > 50000) {\n * messages = messages.filter(SystemMessage.isInstance);\n * return await countTokens(messages);\n * }\n * return tokens;\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":["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,4 +1,6 @@
1
+ const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
1
2
  const require_middleware = require('../middleware.cjs');
3
+ const __langchain_core_messages = require_rolldown_runtime.__toESM(require("@langchain/core/messages"));
2
4
 
3
5
  //#region src/agents/middleware/dynamicSystemPrompt.ts
4
6
  /**
@@ -44,10 +46,11 @@ function dynamicSystemPromptMiddleware(fn) {
44
46
  name: "DynamicSystemPromptMiddleware",
45
47
  wrapModelCall: async (request, handler) => {
46
48
  const systemPrompt = await fn(request.state, request.runtime);
47
- if (typeof systemPrompt !== "string") throw new Error("dynamicSystemPromptMiddleware function must return a string");
49
+ const isExpectedType = typeof systemPrompt === "string" || __langchain_core_messages.SystemMessage.isInstance(systemPrompt);
50
+ if (!isExpectedType) throw new Error("dynamicSystemPromptMiddleware function must return a string or SystemMessage");
48
51
  return handler({
49
52
  ...request,
50
- systemPrompt
53
+ systemMessage: request.systemMessage.concat(systemPrompt)
51
54
  });
52
55
  }
53
56
  });
@@ -1 +1 @@
1
- {"version":3,"file":"dynamicSystemPrompt.cjs","names":["fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>","createMiddleware"],"sources":["../../../src/agents/middleware/dynamicSystemPrompt.ts"],"sourcesContent":["import { 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 | Promise<string>;\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 if (typeof systemPrompt !== \"string\") {\n throw new Error(\n \"dynamicSystemPromptMiddleware function must return a string\"\n );\n }\n\n return handler({ ...request, systemPrompt });\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,8BACdA,IACA;AACA,QAAOC,oCAAiB;EACtB,MAAM;EACN,eAAe,OAAO,SAAS,YAAY;GACzC,MAAM,eAAe,MAAM,GACzB,QAAQ,OACR,QAAQ,QACT;AAED,OAAI,OAAO,iBAAiB,SAC1B,OAAM,IAAI,MACR;AAIJ,UAAO,QAAQ;IAAE,GAAG;IAAS;GAAc,EAAC;EAC7C;CACF,EAAC;AACH"}
1
+ {"version":3,"file":"dynamicSystemPrompt.cjs","names":["fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>","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,8BACdA,IACA;AACA,QAAOC,oCAAiB;EACtB,MAAM;EACN,eAAe,OAAO,SAAS,YAAY;GACzC,MAAM,eAAe,MAAM,GACzB,QAAQ,OACR,QAAQ,QACT;GAED,MAAM,iBACJ,OAAO,iBAAiB,YACxBC,wCAAc,WAAW,aAAa;AACxC,OAAI,CAAC,eACH,OAAM,IAAI,MACR;AAIJ,UAAO,QAAQ;IACb,GAAG;IACH,eAAe,QAAQ,cAAc,OAAO,aAAa;GAC1D,EAAC;EACH;CACF,EAAC;AACH"}
@@ -1,8 +1,9 @@
1
1
  import { AgentBuiltInState, Runtime } from "../runtime.cjs";
2
2
  import { AgentMiddleware } from "./types.cjs";
3
+ import { SystemMessage } from "@langchain/core/messages";
3
4
 
4
5
  //#region src/agents/middleware/dynamicSystemPrompt.d.ts
5
- type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (state: AgentBuiltInState, runtime: Runtime<TContextSchema>) => string | Promise<string>;
6
+ type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (state: AgentBuiltInState, runtime: Runtime<TContextSchema>) => string | SystemMessage | Promise<string | SystemMessage>;
6
7
  /**
7
8
  * Dynamic System Prompt Middleware
8
9
  *
@@ -1,8 +1,9 @@
1
1
  import { AgentBuiltInState, Runtime } from "../runtime.js";
2
2
  import { AgentMiddleware } from "./types.js";
3
+ import { SystemMessage } from "@langchain/core/messages";
3
4
 
4
5
  //#region src/agents/middleware/dynamicSystemPrompt.d.ts
5
- type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (state: AgentBuiltInState, runtime: Runtime<TContextSchema>) => string | Promise<string>;
6
+ type DynamicSystemPromptMiddlewareConfig<TContextSchema> = (state: AgentBuiltInState, runtime: Runtime<TContextSchema>) => string | SystemMessage | Promise<string | SystemMessage>;
6
7
  /**
7
8
  * Dynamic System Prompt Middleware
8
9
  *
@@ -1,4 +1,5 @@
1
1
  import { createMiddleware } from "../middleware.js";
2
+ import { SystemMessage } from "@langchain/core/messages";
2
3
 
3
4
  //#region src/agents/middleware/dynamicSystemPrompt.ts
4
5
  /**
@@ -44,10 +45,11 @@ function dynamicSystemPromptMiddleware(fn) {
44
45
  name: "DynamicSystemPromptMiddleware",
45
46
  wrapModelCall: async (request, handler) => {
46
47
  const systemPrompt = await fn(request.state, request.runtime);
47
- if (typeof systemPrompt !== "string") throw new Error("dynamicSystemPromptMiddleware function must return a string");
48
+ const isExpectedType = typeof systemPrompt === "string" || SystemMessage.isInstance(systemPrompt);
49
+ if (!isExpectedType) throw new Error("dynamicSystemPromptMiddleware function must return a string or SystemMessage");
48
50
  return handler({
49
51
  ...request,
50
- systemPrompt
52
+ systemMessage: request.systemMessage.concat(systemPrompt)
51
53
  });
52
54
  }
53
55
  });
@@ -1 +1 @@
1
- {"version":3,"file":"dynamicSystemPrompt.js","names":["fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>"],"sources":["../../../src/agents/middleware/dynamicSystemPrompt.ts"],"sourcesContent":["import { 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 | Promise<string>;\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 if (typeof systemPrompt !== \"string\") {\n throw new Error(\n \"dynamicSystemPromptMiddleware function must return a string\"\n );\n }\n\n return handler({ ...request, systemPrompt });\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,8BACdA,IACA;AACA,QAAO,iBAAiB;EACtB,MAAM;EACN,eAAe,OAAO,SAAS,YAAY;GACzC,MAAM,eAAe,MAAM,GACzB,QAAQ,OACR,QAAQ,QACT;AAED,OAAI,OAAO,iBAAiB,SAC1B,OAAM,IAAI,MACR;AAIJ,UAAO,QAAQ;IAAE,GAAG;IAAS;GAAc,EAAC;EAC7C;CACF,EAAC;AACH"}
1
+ {"version":3,"file":"dynamicSystemPrompt.js","names":["fn: DynamicSystemPromptMiddlewareConfig<TContextSchema>"],"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,8BACdA,IACA;AACA,QAAO,iBAAiB;EACtB,MAAM;EACN,eAAe,OAAO,SAAS,YAAY;GACzC,MAAM,eAAe,MAAM,GACzB,QAAQ,OACR,QAAQ,QACT;GAED,MAAM,iBACJ,OAAO,iBAAiB,YACxB,cAAc,WAAW,aAAa;AACxC,OAAI,CAAC,eACH,OAAM,IAAI,MACR;AAIJ,UAAO,QAAQ;IACb,GAAG;IACH,eAAe,QAAQ,cAAc,OAAO,aAAa;GAC1D,EAAC;EACH;CACF,EAAC;AACH"}
@@ -0,0 +1,20 @@
1
+ const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
2
+ const zod_v4 = require_rolldown_runtime.__toESM(require("zod/v4"));
3
+
4
+ //#region src/agents/middleware/error.ts
5
+ /**
6
+ * Error thrown when the configuration for a retry middleware is invalid.
7
+ */
8
+ var InvalidRetryConfigError = class extends Error {
9
+ cause;
10
+ constructor(error) {
11
+ const message = zod_v4.z.prettifyError(error).slice(2);
12
+ super(message);
13
+ this.name = "InvalidRetryConfigError";
14
+ this.cause = error;
15
+ }
16
+ };
17
+
18
+ //#endregion
19
+ exports.InvalidRetryConfigError = InvalidRetryConfigError;
20
+ //# sourceMappingURL=error.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.cjs","names":["error: ZodError","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,YAAYA,OAAiB;EAC3B,MAAM,UAAUC,SAAG,cAAc,MAAM,CAAC,MAAM,EAAE;EAChD,MAAM,QAAQ;EACd,KAAK,OAAO;EACZ,KAAK,QAAQ;CACd;AACF"}
@@ -0,0 +1,19 @@
1
+ import { z } from "zod/v4";
2
+
3
+ //#region src/agents/middleware/error.ts
4
+ /**
5
+ * Error thrown when the configuration for a retry middleware is invalid.
6
+ */
7
+ var InvalidRetryConfigError = class extends Error {
8
+ cause;
9
+ constructor(error) {
10
+ const message = z.prettifyError(error).slice(2);
11
+ super(message);
12
+ this.name = "InvalidRetryConfigError";
13
+ this.cause = error;
14
+ }
15
+ };
16
+
17
+ //#endregion
18
+ export { InvalidRetryConfigError };
19
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","names":["error: ZodError","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,YAAYA,OAAiB;EAC3B,MAAM,UAAUC,EAAG,cAAc,MAAM,CAAC,MAAM,EAAE;EAChD,MAAM,QAAQ;EACd,KAAK,OAAO;EACZ,KAAK,QAAQ;CACd;AACF"}