langchain 1.0.1 → 1.0.3

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 (148) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/_virtual/rolldown_runtime.cjs +0 -4
  3. package/dist/_virtual/rolldown_runtime.js +1 -10
  4. package/dist/agents/ReactAgent.cjs +21 -41
  5. package/dist/agents/ReactAgent.cjs.map +1 -1
  6. package/dist/agents/ReactAgent.js +21 -41
  7. package/dist/agents/ReactAgent.js.map +1 -1
  8. package/dist/agents/annotation.cjs +1 -0
  9. package/dist/agents/annotation.cjs.map +1 -1
  10. package/dist/agents/annotation.js +1 -0
  11. package/dist/agents/annotation.js.map +1 -1
  12. package/dist/agents/index.cjs.map +1 -1
  13. package/dist/agents/index.d.cts +11 -11
  14. package/dist/agents/index.d.cts.map +1 -1
  15. package/dist/agents/index.d.ts +11 -11
  16. package/dist/agents/index.d.ts.map +1 -1
  17. package/dist/agents/index.js.map +1 -1
  18. package/dist/agents/middleware/callLimit.d.cts.map +1 -1
  19. package/dist/agents/middleware/callLimit.d.ts.map +1 -1
  20. package/dist/agents/middleware/contextEditing.d.cts.map +1 -1
  21. package/dist/agents/middleware/contextEditing.d.ts.map +1 -1
  22. package/dist/agents/middleware/dynamicSystemPrompt.d.cts.map +1 -1
  23. package/dist/agents/middleware/dynamicSystemPrompt.d.ts.map +1 -1
  24. package/dist/agents/middleware/hitl.cjs +7 -2
  25. package/dist/agents/middleware/hitl.cjs.map +1 -1
  26. package/dist/agents/middleware/hitl.d.cts.map +1 -1
  27. package/dist/agents/middleware/hitl.d.ts.map +1 -1
  28. package/dist/agents/middleware/hitl.js +7 -2
  29. package/dist/agents/middleware/hitl.js.map +1 -1
  30. package/dist/agents/middleware/index.cjs +2 -1
  31. package/dist/agents/middleware/index.d.cts +15 -0
  32. package/dist/agents/middleware/index.d.ts +14 -0
  33. package/dist/agents/middleware/index.js +2 -1
  34. package/dist/agents/middleware/modelCallLimit.cjs +145 -0
  35. package/dist/agents/middleware/modelCallLimit.cjs.map +1 -0
  36. package/dist/agents/middleware/modelCallLimit.d.cts +128 -0
  37. package/dist/agents/middleware/modelCallLimit.d.cts.map +1 -0
  38. package/dist/agents/middleware/modelCallLimit.d.ts +128 -0
  39. package/dist/agents/middleware/modelCallLimit.d.ts.map +1 -0
  40. package/dist/agents/middleware/modelCallLimit.js +144 -0
  41. package/dist/agents/middleware/modelCallLimit.js.map +1 -0
  42. package/dist/agents/middleware/promptCaching.d.cts.map +1 -1
  43. package/dist/agents/middleware/summarization.cjs +4 -2
  44. package/dist/agents/middleware/summarization.cjs.map +1 -1
  45. package/dist/agents/middleware/summarization.d.cts +7 -6
  46. package/dist/agents/middleware/summarization.d.cts.map +1 -1
  47. package/dist/agents/middleware/summarization.d.ts +7 -6
  48. package/dist/agents/middleware/summarization.d.ts.map +1 -1
  49. package/dist/agents/middleware/summarization.js +4 -2
  50. package/dist/agents/middleware/summarization.js.map +1 -1
  51. package/dist/agents/middleware/todoListMiddleware.d.cts.map +1 -1
  52. package/dist/agents/middleware/toolCallLimit.cjs +205 -92
  53. package/dist/agents/middleware/toolCallLimit.cjs.map +1 -1
  54. package/dist/agents/middleware/toolCallLimit.d.cts +46 -37
  55. package/dist/agents/middleware/toolCallLimit.d.cts.map +1 -1
  56. package/dist/agents/middleware/toolCallLimit.d.ts +46 -37
  57. package/dist/agents/middleware/toolCallLimit.d.ts.map +1 -1
  58. package/dist/agents/middleware/toolCallLimit.js +206 -93
  59. package/dist/agents/middleware/toolCallLimit.js.map +1 -1
  60. package/dist/agents/middleware/toolRetry.cjs +224 -0
  61. package/dist/agents/middleware/toolRetry.cjs.map +1 -0
  62. package/dist/agents/middleware/toolRetry.d.cts +179 -0
  63. package/dist/agents/middleware/toolRetry.d.cts.map +1 -0
  64. package/dist/agents/middleware/toolRetry.d.ts +179 -0
  65. package/dist/agents/middleware/toolRetry.d.ts.map +1 -0
  66. package/dist/agents/middleware/toolRetry.js +223 -0
  67. package/dist/agents/middleware/toolRetry.js.map +1 -0
  68. package/dist/agents/middleware/types.d.cts +21 -19
  69. package/dist/agents/middleware/types.d.cts.map +1 -1
  70. package/dist/agents/middleware/types.d.ts +21 -19
  71. package/dist/agents/middleware/types.d.ts.map +1 -1
  72. package/dist/agents/middleware/utils.cjs +7 -0
  73. package/dist/agents/middleware/utils.cjs.map +1 -1
  74. package/dist/agents/middleware/utils.d.cts.map +1 -1
  75. package/dist/agents/middleware/utils.d.ts.map +1 -1
  76. package/dist/agents/middleware/utils.js +7 -1
  77. package/dist/agents/middleware/utils.js.map +1 -1
  78. package/dist/agents/middleware.cjs.map +1 -1
  79. package/dist/agents/middleware.d.cts +4 -4
  80. package/dist/agents/middleware.d.cts.map +1 -1
  81. package/dist/agents/middleware.d.ts +4 -4
  82. package/dist/agents/middleware.d.ts.map +1 -1
  83. package/dist/agents/middleware.js.map +1 -1
  84. package/dist/agents/nodes/AgentNode.cjs +21 -47
  85. package/dist/agents/nodes/AgentNode.cjs.map +1 -1
  86. package/dist/agents/nodes/AgentNode.js +22 -48
  87. package/dist/agents/nodes/AgentNode.js.map +1 -1
  88. package/dist/agents/nodes/ToolNode.cjs +12 -18
  89. package/dist/agents/nodes/ToolNode.cjs.map +1 -1
  90. package/dist/agents/nodes/ToolNode.js +12 -18
  91. package/dist/agents/nodes/ToolNode.js.map +1 -1
  92. package/dist/agents/nodes/middleware.cjs +9 -7
  93. package/dist/agents/nodes/middleware.cjs.map +1 -1
  94. package/dist/agents/nodes/middleware.js +10 -8
  95. package/dist/agents/nodes/middleware.js.map +1 -1
  96. package/dist/agents/nodes/types.d.cts +1 -1
  97. package/dist/agents/nodes/types.d.cts.map +1 -1
  98. package/dist/agents/nodes/types.d.ts +1 -1
  99. package/dist/agents/nodes/types.d.ts.map +1 -1
  100. package/dist/agents/nodes/utils.cjs +5 -1
  101. package/dist/agents/nodes/utils.cjs.map +1 -1
  102. package/dist/agents/nodes/utils.js +5 -1
  103. package/dist/agents/nodes/utils.js.map +1 -1
  104. package/dist/agents/runtime.d.cts +11 -27
  105. package/dist/agents/runtime.d.cts.map +1 -1
  106. package/dist/agents/runtime.d.ts +11 -27
  107. package/dist/agents/runtime.d.ts.map +1 -1
  108. package/dist/agents/state.cjs +45 -0
  109. package/dist/agents/state.cjs.map +1 -0
  110. package/dist/agents/state.js +44 -0
  111. package/dist/agents/state.js.map +1 -0
  112. package/dist/agents/types.d.cts +1 -1
  113. package/dist/agents/types.d.cts.map +1 -1
  114. package/dist/agents/types.d.ts +1 -1
  115. package/dist/agents/types.d.ts.map +1 -1
  116. package/dist/agents/utils.cjs +10 -2
  117. package/dist/agents/utils.cjs.map +1 -1
  118. package/dist/agents/utils.js +10 -2
  119. package/dist/agents/utils.js.map +1 -1
  120. package/dist/chat_models/universal.cjs +4 -3
  121. package/dist/chat_models/universal.cjs.map +1 -1
  122. package/dist/chat_models/universal.js +4 -3
  123. package/dist/chat_models/universal.js.map +1 -1
  124. package/dist/embeddings/cache_backed.cjs +140 -0
  125. package/dist/embeddings/cache_backed.cjs.map +1 -0
  126. package/dist/embeddings/cache_backed.d.cts +107 -0
  127. package/dist/embeddings/cache_backed.d.cts.map +1 -0
  128. package/dist/embeddings/cache_backed.d.ts +107 -0
  129. package/dist/embeddings/cache_backed.d.ts.map +1 -0
  130. package/dist/embeddings/cache_backed.js +134 -0
  131. package/dist/embeddings/cache_backed.js.map +1 -0
  132. package/dist/embeddings/fake.cjs +22 -0
  133. package/dist/embeddings/fake.cjs.map +1 -0
  134. package/dist/embeddings/fake.d.cts +1 -0
  135. package/dist/embeddings/fake.d.ts +1 -0
  136. package/dist/embeddings/fake.js +12 -0
  137. package/dist/embeddings/fake.js.map +1 -0
  138. package/dist/hub/base.cjs +4 -17
  139. package/dist/hub/base.cjs.map +1 -1
  140. package/dist/hub/base.js +2 -15
  141. package/dist/hub/base.js.map +1 -1
  142. package/dist/index.cjs +6 -18
  143. package/dist/index.cjs.map +1 -1
  144. package/dist/index.d.cts +3 -3
  145. package/dist/index.d.ts +3 -3
  146. package/dist/index.js +4 -5
  147. package/dist/index.js.map +1 -1
  148. package/package.json +6 -9
@@ -5,59 +5,50 @@ const zod_v3 = require_rolldown_runtime.__toESM(require("zod/v3"));
5
5
 
6
6
  //#region src/agents/middleware/toolCallLimit.ts
7
7
  /**
8
- * Count tool calls in a list of messages.
8
+ * Build the error message content for ToolMessage when limit is exceeded.
9
9
  *
10
- * @param messages - List of messages to count tool calls in.
11
- * @param toolName - If specified, only count calls to this specific tool.
12
- * If undefined, count all tool calls.
13
- * @returns The total number of tool calls (optionally filtered by toolName).
14
- */
15
- function countToolCallsInMessages(messages, toolName) {
16
- let count = 0;
17
- for (const message of messages) if (__langchain_core_messages.AIMessage.isInstance(message) && message.tool_calls) if (toolName === void 0) count += message.tool_calls.length;
18
- else count += message.tool_calls.filter((tc) => tc.name === toolName).length;
19
- return count;
20
- }
21
- /**
22
- * Get messages from the current run (after the last HumanMessage).
10
+ * This message is sent to the model, so it should not reference thread/run concepts
11
+ * that the model has no notion of.
23
12
  *
24
- * @param messages - Full list of messages.
25
- * @returns Messages from the current run (after last HumanMessage).
13
+ * @param toolName - Tool name being limited (if specific tool), or undefined for all tools.
14
+ * @param threadCount - Current thread tool call count.
15
+ * @param runCount - Current run tool call count.
16
+ * @param threadLimit - Thread tool call limit (if set).
17
+ * @param runLimit - Run tool call limit (if set).
18
+ * @returns A concise message. If only run limit is exceeded (not thread limit),
19
+ * returns a simple "limit exceeded" message without instructing model to stop.
20
+ * If thread limit is exceeded, includes instruction not to call again.
26
21
  */
27
- function getRunMessages(messages) {
28
- /**
29
- * Find the last HumanMessage
30
- */
31
- let lastHumanIndex = -1;
32
- for (let i = messages.length - 1; i >= 0; i--) if (__langchain_core_messages.HumanMessage.isInstance(messages[i])) {
33
- lastHumanIndex = i;
34
- break;
22
+ function buildToolMessageContent(toolName, threadCount, runCount, threadLimit, runLimit) {
23
+ const threadExceeded = threadLimit !== void 0 && threadCount > threadLimit;
24
+ const onlyRunExceeded = runLimit !== void 0 && runCount > runLimit && !threadExceeded;
25
+ if (onlyRunExceeded) {
26
+ if (toolName) return `Tool call limit exceeded for '${toolName}'.`;
27
+ return "Tool call limit exceeded.";
35
28
  }
36
- /**
37
- * If no HumanMessage found, return all messages
38
- */
39
- if (lastHumanIndex === -1) return messages;
40
- /**
41
- * Return messages after the last HumanMessage
42
- */
43
- return messages.slice(lastHumanIndex + 1);
29
+ if (toolName) return `Tool call limit exceeded. Do not call '${toolName}' again.`;
30
+ return "Tool call limit exceeded. Do not make additional tool calls.";
44
31
  }
45
32
  /**
46
- * Build a message indicating which tool call limits were reached.
33
+ * Build the final AI message content for 'end' behavior.
34
+ *
35
+ * This message is displayed to the user, so it should include detailed information
36
+ * about which limits were exceeded.
47
37
  *
48
38
  * @param threadCount - Current thread tool call count.
49
39
  * @param runCount - Current run tool call count.
50
40
  * @param threadLimit - Thread tool call limit (if set).
51
41
  * @param runLimit - Run tool call limit (if set).
52
42
  * @param toolName - Tool name being limited (if specific tool), or undefined for all tools.
53
- * @returns A formatted message describing which limits were reached.
43
+ * @returns A formatted message describing which limits were exceeded.
54
44
  */
55
- function buildToolLimitExceededMessage(threadCount, runCount, threadLimit, runLimit, toolName) {
56
- const toolDesc = toolName ? `'${toolName}' tool call` : "Tool call";
45
+ function buildFinalAIMessageContent(threadCount, runCount, threadLimit, runLimit, toolName) {
46
+ const toolDesc = toolName ? `'${toolName}' tool` : "Tool";
57
47
  const exceededLimits = [];
58
- if (threadLimit !== void 0 && threadCount >= threadLimit) exceededLimits.push(`thread limit reached (${threadCount}/${threadLimit})`);
59
- if (runLimit !== void 0 && runCount >= runLimit) exceededLimits.push(`run limit reached (${runCount}/${runLimit})`);
60
- return `${toolDesc} limit${exceededLimits.length > 1 ? "s" : ""}: ${exceededLimits.join(", ")}. Stopping to prevent further tool calls.`;
48
+ if (threadLimit !== void 0 && threadCount > threadLimit) exceededLimits.push(`thread limit exceeded (${threadCount}/${threadLimit} calls)`);
49
+ if (runLimit !== void 0 && runCount > runLimit) exceededLimits.push(`run limit exceeded (${runCount}/${runLimit} calls)`);
50
+ const limitsText = exceededLimits.join(" and ");
51
+ return `${toolDesc} call limit reached: ${limitsText}.`;
61
52
  }
62
53
  /**
63
54
  * Exception raised when tool call limits are exceeded.
@@ -87,7 +78,7 @@ var ToolCallLimitExceededError = class extends Error {
87
78
  */
88
79
  toolName;
89
80
  constructor(threadCount, runCount, threadLimit, runLimit, toolName = void 0) {
90
- const message = buildToolLimitExceededMessage(threadCount, runCount, threadLimit, runLimit, toolName);
81
+ const message = buildFinalAIMessageContent(threadCount, runCount, threadLimit, runLimit, toolName);
91
82
  super(message);
92
83
  this.name = "ToolCallLimitExceededError";
93
84
  this.threadCount = threadCount;
@@ -104,8 +95,20 @@ const ToolCallLimitOptionsSchema = zod_v3.z.object({
104
95
  toolName: zod_v3.z.string().optional(),
105
96
  threadLimit: zod_v3.z.number().optional(),
106
97
  runLimit: zod_v3.z.number().optional(),
107
- exitBehavior: zod_v3.z.enum(["end", "error"]).default("end")
98
+ exitBehavior: zod_v3.z.enum([
99
+ "continue",
100
+ "error",
101
+ "end"
102
+ ]).default("continue")
103
+ });
104
+ /**
105
+ * Middleware state schema to track the number of model calls made at the thread and run level.
106
+ */
107
+ const stateSchema = zod_v3.z.object({
108
+ threadToolCallCount: zod_v3.z.record(zod_v3.z.string(), zod_v3.z.number()).default({}),
109
+ runToolCallCount: zod_v3.z.record(zod_v3.z.string(), zod_v3.z.number()).default({})
108
110
  });
111
+ const DEFAULT_TOOL_COUNT_KEY = "__all__";
109
112
  /**
110
113
  * Middleware that tracks tool call counts and enforces limits.
111
114
  *
@@ -124,68 +127,66 @@ const ToolCallLimitOptionsSchema = zod_v3.z.object({
124
127
  * @param options.threadLimit - Maximum number of tool calls allowed per thread. undefined means no limit.
125
128
  * @param options.runLimit - Maximum number of tool calls allowed per run. undefined means no limit.
126
129
  * @param options.exitBehavior - What to do when limits are exceeded.
127
- * - "end": Jump to the end of the agent execution and inject an artificial AI message indicating that the limit was exceeded.
128
- * - "error": throws a ToolCallLimitExceededError
130
+ * - "continue": Block exceeded tools with error messages, let other tools continue. Model decides when to end. (default)
131
+ * - "error": Raise a ToolCallLimitExceededError exception
132
+ * - "end": Stop execution immediately with a ToolMessage + AI message for the single tool call that exceeded the limit. Raises NotImplementedError if there are multiple tool calls.
129
133
  *
130
- * @throws {Error} If both limits are undefined or if exitBehavior is invalid.
134
+ * @throws {Error} If both limits are undefined.
135
+ * @throws {NotImplementedError} If exitBehavior is "end" and there are multiple tool calls.
131
136
  *
132
- * @example Limit all tool calls globally
137
+ * @example Continue execution with blocked tools (default)
133
138
  * ```ts
134
139
  * import { toolCallLimitMiddleware } from "@langchain/langchain/agents/middleware";
135
140
  * import { createAgent } from "@langchain/langchain/agents";
136
141
  *
137
- * const globalLimiter = toolCallLimitMiddleware({
142
+ * // Block exceeded tools but let other tools and model continue
143
+ * const limiter = toolCallLimitMiddleware({
138
144
  * threadLimit: 20,
139
145
  * runLimit: 10,
140
- * exitBehavior: "end"
146
+ * exitBehavior: "continue", // default
141
147
  * });
142
148
  *
143
149
  * const agent = createAgent({
144
150
  * model: "openai:gpt-4o",
145
- * middleware: [globalLimiter]
151
+ * middleware: [limiter]
146
152
  * });
147
153
  * ```
148
154
  *
149
- * @example Limit a specific tool
155
+ * @example Stop immediately when limit exceeded
150
156
  * ```ts
151
- * import { toolCallLimitMiddleware } from "@langchain/langchain/agents/middleware";
152
- * import { createAgent } from "@langchain/langchain/agents";
153
- *
154
- * const searchLimiter = toolCallLimitMiddleware({
155
- * toolName: "search",
156
- * threadLimit: 5,
157
- * runLimit: 3,
157
+ * // End execution immediately with an AI message
158
+ * const limiter = toolCallLimitMiddleware({
159
+ * runLimit: 5,
158
160
  * exitBehavior: "end"
159
161
  * });
160
162
  *
161
163
  * const agent = createAgent({
162
164
  * model: "openai:gpt-4o",
163
- * middleware: [searchLimiter]
165
+ * middleware: [limiter]
164
166
  * });
165
167
  * ```
166
168
  *
167
- * @example Use both in the same agent
169
+ * @example Raise exception on limit
168
170
  * ```ts
169
- * import { toolCallLimitMiddleware } from "@langchain/langchain/agents/middleware";
170
- * import { createAgent } from "@langchain/langchain/agents";
171
- *
172
- * const globalLimiter = toolCallLimitMiddleware({
173
- * threadLimit: 20,
174
- * runLimit: 10,
175
- * exitBehavior: "end"
176
- * });
177
- *
178
- * const searchLimiter = toolCallLimitMiddleware({
171
+ * // Strict limit with exception handling
172
+ * const limiter = toolCallLimitMiddleware({
179
173
  * toolName: "search",
180
174
  * threadLimit: 5,
181
- * runLimit: 3,
182
- * exitBehavior: "end"
175
+ * exitBehavior: "error"
183
176
  * });
184
177
  *
185
178
  * const agent = createAgent({
186
179
  * model: "openai:gpt-4o",
187
- * middleware: [globalLimiter, searchLimiter]
180
+ * middleware: [limiter]
188
181
  * });
182
+ *
183
+ * try {
184
+ * const result = await agent.invoke({ messages: [new HumanMessage("Task")] });
185
+ * } catch (error) {
186
+ * if (error instanceof ToolCallLimitExceededError) {
187
+ * console.log(`Search limit exceeded: ${error}`);
188
+ * }
189
+ * }
189
190
  * ```
190
191
  */
191
192
  function toolCallLimitMiddleware(options) {
@@ -194,47 +195,159 @@ function toolCallLimitMiddleware(options) {
194
195
  */
195
196
  if (options.threadLimit === void 0 && options.runLimit === void 0) throw new Error("At least one limit must be specified (threadLimit or runLimit)");
196
197
  /**
197
- * Apply default for exitBehavior and validate
198
+ * Apply default for exitBehavior
198
199
  */
199
- const exitBehavior = options.exitBehavior ?? "end";
200
- if (exitBehavior !== "end" && exitBehavior !== "error") throw new Error(`Invalid exit behavior: ${exitBehavior}. Must be 'end' or 'error'`);
200
+ const exitBehavior = options.exitBehavior ?? "continue";
201
201
  /**
202
202
  * Generate the middleware name based on the tool name
203
203
  */
204
204
  const middlewareName = options.toolName ? `ToolCallLimitMiddleware[${options.toolName}]` : "ToolCallLimitMiddleware";
205
205
  return require_middleware.createMiddleware({
206
206
  name: middlewareName,
207
- beforeModel: {
207
+ stateSchema,
208
+ afterModel: {
208
209
  canJumpTo: ["end"],
209
210
  hook: (state) => {
210
- const messages = state.messages;
211
211
  /**
212
- * Count tool calls in entire thread
212
+ * Get the last AI message to check for tool calls
213
+ */
214
+ const lastAIMessage = [...state.messages].reverse().find(__langchain_core_messages.AIMessage.isInstance);
215
+ if (!lastAIMessage || !lastAIMessage.tool_calls) return void 0;
216
+ /**
217
+ * Helper to check if limit would be exceeded by one more call
218
+ */
219
+ const wouldExceedLimit = (threadCount, runCount) => {
220
+ return options.threadLimit !== void 0 && threadCount + 1 > options.threadLimit || options.runLimit !== void 0 && runCount + 1 > options.runLimit;
221
+ };
222
+ /**
223
+ * Helper to check if a tool call matches our filter
224
+ */
225
+ const matchesToolFilter = (toolCall) => {
226
+ return options.toolName === void 0 || toolCall.name === options.toolName;
227
+ };
228
+ /**
229
+ * Separate tool calls into allowed and blocked based on limits
230
+ */
231
+ const separateToolCalls = (toolCalls, threadCount, runCount) => {
232
+ const allowed$1 = [];
233
+ const blocked$1 = [];
234
+ let tempThreadCount = threadCount;
235
+ let tempRunCount = runCount;
236
+ for (const toolCall of toolCalls) {
237
+ if (!matchesToolFilter(toolCall)) continue;
238
+ if (wouldExceedLimit(tempThreadCount, tempRunCount)) blocked$1.push(toolCall);
239
+ else {
240
+ allowed$1.push(toolCall);
241
+ tempThreadCount += 1;
242
+ tempRunCount += 1;
243
+ }
244
+ }
245
+ return {
246
+ allowed: allowed$1,
247
+ blocked: blocked$1,
248
+ finalThreadCount: tempThreadCount + blocked$1.length,
249
+ finalRunCount: tempRunCount + blocked$1.length
250
+ };
251
+ };
252
+ /**
253
+ * Get the count key for this middleware instance
254
+ */
255
+ const countKey = options.toolName ?? DEFAULT_TOOL_COUNT_KEY;
256
+ /**
257
+ * Get current counts
258
+ */
259
+ const threadCounts = { ...state.threadToolCallCount ?? {} };
260
+ const runCounts = { ...state.runToolCallCount ?? {} };
261
+ const currentThreadCount = threadCounts[countKey] ?? 0;
262
+ const currentRunCount = runCounts[countKey] ?? 0;
263
+ /**
264
+ * Separate tool calls into allowed and blocked
265
+ */
266
+ const { allowed, blocked, finalThreadCount, finalRunCount } = separateToolCalls(lastAIMessage.tool_calls, currentThreadCount, currentRunCount);
267
+ /**
268
+ * Update counts to include ALL tool call attempts (both allowed and blocked)
269
+ */
270
+ threadCounts[countKey] = finalThreadCount;
271
+ runCounts[countKey] = finalRunCount;
272
+ /**
273
+ * If no tool calls are blocked, just update counts
274
+ */
275
+ if (blocked.length === 0) {
276
+ if (allowed.length > 0) return {
277
+ threadToolCallCount: threadCounts,
278
+ runToolCallCount: runCounts
279
+ };
280
+ return void 0;
281
+ }
282
+ /**
283
+ * Handle different exit behaviors
213
284
  */
214
- const threadCount = countToolCallsInMessages(messages, options.toolName);
285
+ if (exitBehavior === "error") throw new ToolCallLimitExceededError(finalThreadCount, finalRunCount, options.threadLimit, options.runLimit, options.toolName);
215
286
  /**
216
- * Count tool calls in current run (after last HumanMessage)
287
+ * Build tool message content (sent to model - no thread/run details)
217
288
  */
218
- const runMessages = getRunMessages(messages);
219
- const runCount = countToolCallsInMessages(runMessages, options.toolName);
289
+ const toolMsgContent = buildToolMessageContent(options.toolName, finalThreadCount, finalRunCount, options.threadLimit, options.runLimit);
220
290
  /**
221
- * Check if any limits are exceeded
291
+ * Inject artificial error ToolMessages for blocked tool calls
222
292
  */
223
- const threadLimitExceeded = options.threadLimit !== void 0 && threadCount >= options.threadLimit;
224
- const runLimitExceeded = options.runLimit !== void 0 && runCount >= options.runLimit;
225
- if (!threadLimitExceeded && !runLimitExceeded) return void 0;
226
- if (exitBehavior === "error") throw new ToolCallLimitExceededError(threadCount, runCount, options.threadLimit, options.runLimit, options.toolName);
293
+ const artificialMessages = blocked.map((toolCall) => new __langchain_core_messages.ToolMessage({
294
+ content: toolMsgContent,
295
+ tool_call_id: toolCall.id,
296
+ name: toolCall.name,
297
+ status: "error"
298
+ }));
299
+ if (exitBehavior === "end") {
300
+ /**
301
+ * Check if there are tool calls to other tools that would continue executing
302
+ * For tool-specific limiters: check for calls to other tools
303
+ * For global limiters: check if there are multiple different tool types
304
+ */
305
+ let otherTools = [];
306
+ if (options.toolName !== void 0)
307
+ /**
308
+ * Tool-specific limiter: check for calls to other tools
309
+ */
310
+ otherTools = lastAIMessage.tool_calls.filter((tc) => tc.name !== options.toolName);
311
+ else {
312
+ /**
313
+ * Global limiter: check if there are multiple different tool types
314
+ * If there are allowed calls, those would execute
315
+ * But even if all are blocked, we can't handle multiple tool types with "end"
316
+ */
317
+ const uniqueToolNames = new Set(lastAIMessage.tool_calls.map((tc) => tc.name).filter(Boolean));
318
+ if (uniqueToolNames.size > 1)
319
+ /**
320
+ * Multiple different tool types - use allowed calls to show which ones
321
+ */
322
+ otherTools = allowed.length > 0 ? allowed : lastAIMessage.tool_calls;
323
+ }
324
+ if (otherTools.length > 0) {
325
+ const toolNames = Array.from(new Set(otherTools.map((tc) => tc.name).filter(Boolean))).join(", ");
326
+ throw new Error(`Cannot end execution with other tool calls pending. Found calls to: ${toolNames}. Use 'continue' or 'error' behavior instead.`);
327
+ }
328
+ /**
329
+ * Build final AI message content (displayed to user - includes thread/run details)
330
+ */
331
+ const finalMsgContent = buildFinalAIMessageContent(finalThreadCount, finalRunCount, options.threadLimit, options.runLimit, options.toolName);
332
+ artificialMessages.push(new __langchain_core_messages.AIMessage(finalMsgContent));
333
+ return {
334
+ threadToolCallCount: threadCounts,
335
+ runToolCallCount: runCounts,
336
+ jumpTo: "end",
337
+ messages: artificialMessages
338
+ };
339
+ }
227
340
  /**
228
- * Create a message indicating the limit was exceeded
341
+ * For exit_behavior="continue", return error messages to block exceeded tools
229
342
  */
230
- const limitMessage = buildToolLimitExceededMessage(threadCount, runCount, options.threadLimit, options.runLimit, options.toolName);
231
- const limitAiMessage = new __langchain_core_messages.AIMessage(limitMessage);
232
343
  return {
233
- jumpTo: "end",
234
- messages: [limitAiMessage]
344
+ threadToolCallCount: threadCounts,
345
+ runToolCallCount: runCounts,
346
+ messages: artificialMessages
235
347
  };
236
348
  }
237
- }
349
+ },
350
+ afterAgent: () => ({ runToolCallCount: {} })
238
351
  });
239
352
  }
240
353
 
@@ -1 +1 @@
1
- {"version":3,"file":"toolCallLimit.cjs","names":["messages: BaseMessage[]","toolName?: string","AIMessage","HumanMessage","threadCount: number","runCount: number","threadLimit: number | undefined","runLimit: number | undefined","toolName: string | undefined","exceededLimits: string[]","z","options: ToolCallLimitConfig","createMiddleware"],"sources":["../../../src/agents/middleware/toolCallLimit.ts"],"sourcesContent":["/**\n * Tool call limit middleware for agents.\n */\n\nimport { AIMessage, BaseMessage, HumanMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod/v3\";\nimport type { InferInteropZodInput } from \"@langchain/core/utils/types\";\n\nimport { createMiddleware } from \"../middleware.js\";\n\n/**\n * Count tool calls in a list of messages.\n *\n * @param messages - List of messages to count tool calls in.\n * @param toolName - If specified, only count calls to this specific tool.\n * If undefined, count all tool calls.\n * @returns The total number of tool calls (optionally filtered by toolName).\n */\nfunction countToolCallsInMessages(\n messages: BaseMessage[],\n toolName?: string\n): number {\n let count = 0;\n for (const message of messages) {\n if (AIMessage.isInstance(message) && message.tool_calls) {\n if (toolName === undefined) {\n // Count all tool calls\n count += message.tool_calls.length;\n } else {\n // Count only calls to the specified tool\n count += message.tool_calls.filter((tc) => tc.name === toolName).length;\n }\n }\n }\n return count;\n}\n\n/**\n * Get messages from the current run (after the last HumanMessage).\n *\n * @param messages - Full list of messages.\n * @returns Messages from the current run (after last HumanMessage).\n */\nfunction getRunMessages(messages: BaseMessage[]): BaseMessage[] {\n /**\n * Find the last HumanMessage\n */\n let lastHumanIndex = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (HumanMessage.isInstance(messages[i])) {\n lastHumanIndex = i;\n break;\n }\n }\n\n /**\n * If no HumanMessage found, return all messages\n */\n if (lastHumanIndex === -1) {\n return messages;\n }\n\n /**\n * Return messages after the last HumanMessage\n */\n return messages.slice(lastHumanIndex + 1);\n}\n\n/**\n * Build a message indicating which tool call limits were reached.\n *\n * @param threadCount - Current thread tool call count.\n * @param runCount - Current run tool call count.\n * @param threadLimit - Thread tool call limit (if set).\n * @param runLimit - Run tool call limit (if set).\n * @param toolName - Tool name being limited (if specific tool), or undefined for all tools.\n * @returns A formatted message describing which limits were reached.\n */\nfunction buildToolLimitExceededMessage(\n threadCount: number,\n runCount: number,\n threadLimit: number | undefined,\n runLimit: number | undefined,\n toolName: string | undefined\n): string {\n const toolDesc = toolName ? `'${toolName}' tool call` : \"Tool call\";\n const exceededLimits: string[] = [];\n\n if (threadLimit !== undefined && threadCount >= threadLimit) {\n exceededLimits.push(`thread limit reached (${threadCount}/${threadLimit})`);\n }\n if (runLimit !== undefined && runCount >= runLimit) {\n exceededLimits.push(`run limit reached (${runCount}/${runLimit})`);\n }\n\n return `${toolDesc} limit${\n exceededLimits.length > 1 ? \"s\" : \"\"\n }: ${exceededLimits.join(\", \")}. Stopping to prevent further tool calls.`;\n}\n\n/**\n * Exception raised when tool call limits are exceeded.\n *\n * This exception is raised when the configured exit behavior is 'error'\n * and either the thread or run tool call limit has been exceeded.\n */\nexport class ToolCallLimitExceededError extends Error {\n /**\n * Current thread tool call count.\n */\n threadCount: number;\n /**\n * Current run tool call count.\n */\n runCount: number;\n /**\n * Thread tool call limit (if set).\n */\n threadLimit: number | undefined;\n /**\n * Run tool call limit (if set).\n */\n runLimit: number | undefined;\n /**\n * Tool name being limited (if specific tool), or undefined for all tools.\n */\n toolName: string | undefined;\n\n constructor(\n threadCount: number,\n runCount: number,\n threadLimit: number | undefined,\n runLimit: number | undefined,\n toolName: string | undefined = undefined\n ) {\n const message = buildToolLimitExceededMessage(\n threadCount,\n runCount,\n threadLimit,\n runLimit,\n toolName\n );\n super(message);\n\n this.name = \"ToolCallLimitExceededError\";\n this.threadCount = threadCount;\n this.runCount = runCount;\n this.threadLimit = threadLimit;\n this.runLimit = runLimit;\n this.toolName = toolName;\n }\n}\n\n/**\n * Options for configuring the Tool Call Limit middleware.\n */\nexport const ToolCallLimitOptionsSchema = z.object({\n /**\n * Name of the specific tool to limit. If undefined, limits apply to all tools.\n */\n toolName: z.string().optional(),\n /**\n * Maximum number of tool calls allowed per thread.\n * undefined means no limit.\n */\n threadLimit: z.number().optional(),\n /**\n * Maximum number of tool calls allowed per run.\n * undefined means no limit.\n */\n runLimit: z.number().optional(),\n /**\n * What to do when limits are exceeded.\n * - \"end\": Jump to the end of the agent execution and inject an artificial\n * AI message indicating that the limit was exceeded.\n * - \"error\": throws a ToolCallLimitExceededError\n */\n exitBehavior: z.enum([\"end\", \"error\"]).default(\"end\"),\n});\n\nexport type ToolCallLimitConfig = InferInteropZodInput<\n typeof ToolCallLimitOptionsSchema\n>;\n\n/**\n * Middleware that tracks tool call counts and enforces limits.\n *\n * This middleware monitors the number of tool calls made during agent execution\n * and can terminate the agent when specified limits are reached. It supports\n * both thread-level and run-level call counting with configurable exit behaviors.\n *\n * Thread-level: The middleware counts all tool calls in the entire message history\n * and persists this count across multiple runs (invocations) of the agent.\n *\n * Run-level: The middleware counts tool calls made after the last HumanMessage,\n * representing the current run (invocation) of the agent.\n *\n * @param options - Configuration options for the middleware\n * @param options.toolName - Name of the specific tool to limit. If undefined, limits apply to all tools.\n * @param options.threadLimit - Maximum number of tool calls allowed per thread. undefined means no limit.\n * @param options.runLimit - Maximum number of tool calls allowed per run. undefined means no limit.\n * @param options.exitBehavior - What to do when limits are exceeded.\n * - \"end\": Jump to the end of the agent execution and inject an artificial AI message indicating that the limit was exceeded.\n * - \"error\": throws a ToolCallLimitExceededError\n *\n * @throws {Error} If both limits are undefined or if exitBehavior is invalid.\n *\n * @example Limit all tool calls globally\n * ```ts\n * import { toolCallLimitMiddleware } from \"@langchain/langchain/agents/middleware\";\n * import { createAgent } from \"@langchain/langchain/agents\";\n *\n * const globalLimiter = toolCallLimitMiddleware({\n * threadLimit: 20,\n * runLimit: 10,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [globalLimiter]\n * });\n * ```\n *\n * @example Limit a specific tool\n * ```ts\n * import { toolCallLimitMiddleware } from \"@langchain/langchain/agents/middleware\";\n * import { createAgent } from \"@langchain/langchain/agents\";\n *\n * const searchLimiter = toolCallLimitMiddleware({\n * toolName: \"search\",\n * threadLimit: 5,\n * runLimit: 3,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [searchLimiter]\n * });\n * ```\n *\n * @example Use both in the same agent\n * ```ts\n * import { toolCallLimitMiddleware } from \"@langchain/langchain/agents/middleware\";\n * import { createAgent } from \"@langchain/langchain/agents\";\n *\n * const globalLimiter = toolCallLimitMiddleware({\n * threadLimit: 20,\n * runLimit: 10,\n * exitBehavior: \"end\"\n * });\n *\n * const searchLimiter = toolCallLimitMiddleware({\n * toolName: \"search\",\n * threadLimit: 5,\n * runLimit: 3,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [globalLimiter, searchLimiter]\n * });\n * ```\n */\nexport function toolCallLimitMiddleware(options: ToolCallLimitConfig) {\n /**\n * Validate that at least one limit is specified\n */\n if (options.threadLimit === undefined && options.runLimit === undefined) {\n throw new Error(\n \"At least one limit must be specified (threadLimit or runLimit)\"\n );\n }\n\n /**\n * Apply default for exitBehavior and validate\n */\n const exitBehavior = options.exitBehavior ?? \"end\";\n if (exitBehavior !== \"end\" && exitBehavior !== \"error\") {\n throw new Error(\n `Invalid exit behavior: ${exitBehavior}. Must be 'end' or 'error'`\n );\n }\n\n /**\n * Generate the middleware name based on the tool name\n */\n const middlewareName = options.toolName\n ? `ToolCallLimitMiddleware[${options.toolName}]`\n : \"ToolCallLimitMiddleware\";\n\n return createMiddleware({\n name: middlewareName,\n beforeModel: {\n canJumpTo: [\"end\"],\n hook: (state) => {\n const messages = state.messages;\n\n /**\n * Count tool calls in entire thread\n */\n const threadCount = countToolCallsInMessages(\n messages,\n options.toolName\n );\n\n /**\n * Count tool calls in current run (after last HumanMessage)\n */\n const runMessages = getRunMessages(messages);\n const runCount = countToolCallsInMessages(\n runMessages,\n options.toolName\n );\n\n /**\n * Check if any limits are exceeded\n */\n const threadLimitExceeded =\n options.threadLimit !== undefined &&\n threadCount >= options.threadLimit;\n const runLimitExceeded =\n options.runLimit !== undefined && runCount >= options.runLimit;\n\n if (!threadLimitExceeded && !runLimitExceeded) {\n return undefined;\n }\n\n if (exitBehavior === \"error\") {\n throw new ToolCallLimitExceededError(\n threadCount,\n runCount,\n options.threadLimit,\n options.runLimit,\n options.toolName\n );\n }\n\n /**\n * Create a message indicating the limit was exceeded\n */\n const limitMessage = buildToolLimitExceededMessage(\n threadCount,\n runCount,\n options.threadLimit,\n options.runLimit,\n options.toolName\n );\n const limitAiMessage = new AIMessage(limitMessage);\n\n return {\n jumpTo: \"end\",\n messages: [limitAiMessage],\n };\n },\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"toolCallLimit.cjs","names":["toolName: string | undefined","threadCount: number","runCount: number","threadLimit: number | undefined","runLimit: number | undefined","exceededLimits: string[]","z","options: ToolCallLimitConfig","createMiddleware","AIMessage","toolCall: { name?: string }","toolCalls: ToolCall[]","allowed: ToolCall[]","blocked: ToolCall[]","blocked","allowed","artificialMessages: Array<ToolMessage | AIMessage>","ToolMessage","otherTools: ToolCall[]"],"sources":["../../../src/agents/middleware/toolCallLimit.ts"],"sourcesContent":["import { AIMessage, ToolMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod/v3\";\nimport type { InferInteropZodInput } from \"@langchain/core/utils/types\";\nimport type { ToolCall } from \"@langchain/core/messages/tool\";\n\nimport { createMiddleware } from \"../middleware.js\";\n\n/**\n * Build the error message content for ToolMessage when limit is exceeded.\n *\n * This message is sent to the model, so it should not reference thread/run concepts\n * that the model has no notion of.\n *\n * @param toolName - Tool name being limited (if specific tool), or undefined for all tools.\n * @param threadCount - Current thread tool call count.\n * @param runCount - Current run tool call count.\n * @param threadLimit - Thread tool call limit (if set).\n * @param runLimit - Run tool call limit (if set).\n * @returns A concise message. If only run limit is exceeded (not thread limit),\n * returns a simple \"limit exceeded\" message without instructing model to stop.\n * If thread limit is exceeded, includes instruction not to call again.\n */\nfunction buildToolMessageContent(\n toolName: string | undefined,\n threadCount: number,\n runCount: number,\n threadLimit: number | undefined,\n runLimit: number | undefined\n): string {\n // Check if thread limit is exceeded\n const threadExceeded = threadLimit !== undefined && threadCount > threadLimit;\n // Check if only run limit is exceeded (not thread limit)\n const onlyRunExceeded =\n runLimit !== undefined && runCount > runLimit && !threadExceeded;\n\n if (onlyRunExceeded) {\n // Run limit exceeded but thread limit not exceeded - simpler message\n if (toolName) {\n return `Tool call limit exceeded for '${toolName}'.`;\n }\n return \"Tool call limit exceeded.\";\n }\n\n // Thread limit exceeded (or both) - include instruction not to call again\n if (toolName) {\n return `Tool call limit exceeded. Do not call '${toolName}' again.`;\n }\n return \"Tool call limit exceeded. Do not make additional tool calls.\";\n}\n\n/**\n * Build the final AI message content for 'end' behavior.\n *\n * This message is displayed to the user, so it should include detailed information\n * about which limits were exceeded.\n *\n * @param threadCount - Current thread tool call count.\n * @param runCount - Current run tool call count.\n * @param threadLimit - Thread tool call limit (if set).\n * @param runLimit - Run tool call limit (if set).\n * @param toolName - Tool name being limited (if specific tool), or undefined for all tools.\n * @returns A formatted message describing which limits were exceeded.\n */\nfunction buildFinalAIMessageContent(\n threadCount: number,\n runCount: number,\n threadLimit: number | undefined,\n runLimit: number | undefined,\n toolName: string | undefined\n): string {\n const toolDesc = toolName ? `'${toolName}' tool` : \"Tool\";\n const exceededLimits: string[] = [];\n\n if (threadLimit !== undefined && threadCount > threadLimit) {\n exceededLimits.push(\n `thread limit exceeded (${threadCount}/${threadLimit} calls)`\n );\n }\n if (runLimit !== undefined && runCount > runLimit) {\n exceededLimits.push(`run limit exceeded (${runCount}/${runLimit} calls)`);\n }\n\n const limitsText = exceededLimits.join(\" and \");\n return `${toolDesc} call limit reached: ${limitsText}.`;\n}\n\n/**\n * Exception raised when tool call limits are exceeded.\n *\n * This exception is raised when the configured exit behavior is 'error'\n * and either the thread or run tool call limit has been exceeded.\n */\nexport class ToolCallLimitExceededError extends Error {\n /**\n * Current thread tool call count.\n */\n threadCount: number;\n /**\n * Current run tool call count.\n */\n runCount: number;\n /**\n * Thread tool call limit (if set).\n */\n threadLimit: number | undefined;\n /**\n * Run tool call limit (if set).\n */\n runLimit: number | undefined;\n /**\n * Tool name being limited (if specific tool), or undefined for all tools.\n */\n toolName: string | undefined;\n\n constructor(\n threadCount: number,\n runCount: number,\n threadLimit: number | undefined,\n runLimit: number | undefined,\n toolName: string | undefined = undefined\n ) {\n const message = buildFinalAIMessageContent(\n threadCount,\n runCount,\n threadLimit,\n runLimit,\n toolName\n );\n super(message);\n\n this.name = \"ToolCallLimitExceededError\";\n this.threadCount = threadCount;\n this.runCount = runCount;\n this.threadLimit = threadLimit;\n this.runLimit = runLimit;\n this.toolName = toolName;\n }\n}\n\n/**\n * Options for configuring the Tool Call Limit middleware.\n */\nexport const ToolCallLimitOptionsSchema = z.object({\n /**\n * Name of the specific tool to limit. If undefined, limits apply to all tools.\n */\n toolName: z.string().optional(),\n /**\n * Maximum number of tool calls allowed per thread.\n * undefined means no limit.\n */\n threadLimit: z.number().optional(),\n /**\n * Maximum number of tool calls allowed per run.\n * undefined means no limit.\n */\n runLimit: z.number().optional(),\n /**\n * What to do when limits are exceeded.\n * - \"continue\": Block exceeded tools with error messages, let other tools continue (default)\n * - \"error\": Raise a ToolCallLimitExceededError exception\n * - \"end\": Stop execution immediately, injecting a ToolMessage and an AI message\n * for the single tool call that exceeded the limit. Raises NotImplementedError\n * if there are multiple tool calls.\n */\n exitBehavior: z.enum([\"continue\", \"error\", \"end\"]).default(\"continue\"),\n});\n\nexport type ToolCallLimitConfig = InferInteropZodInput<\n typeof ToolCallLimitOptionsSchema\n>;\n\n/**\n * Middleware state schema to track the number of model calls made at the thread and run level.\n */\nconst stateSchema = z.object({\n threadToolCallCount: z.record(z.string(), z.number()).default({}),\n runToolCallCount: z.record(z.string(), z.number()).default({}),\n});\n\nconst DEFAULT_TOOL_COUNT_KEY = \"__all__\";\n\n/**\n * Middleware that tracks tool call counts and enforces limits.\n *\n * This middleware monitors the number of tool calls made during agent execution\n * and can terminate the agent when specified limits are reached. It supports\n * both thread-level and run-level call counting with configurable exit behaviors.\n *\n * Thread-level: The middleware counts all tool calls in the entire message history\n * and persists this count across multiple runs (invocations) of the agent.\n *\n * Run-level: The middleware counts tool calls made after the last HumanMessage,\n * representing the current run (invocation) of the agent.\n *\n * @param options - Configuration options for the middleware\n * @param options.toolName - Name of the specific tool to limit. If undefined, limits apply to all tools.\n * @param options.threadLimit - Maximum number of tool calls allowed per thread. undefined means no limit.\n * @param options.runLimit - Maximum number of tool calls allowed per run. undefined means no limit.\n * @param options.exitBehavior - What to do when limits are exceeded.\n * - \"continue\": Block exceeded tools with error messages, let other tools continue. Model decides when to end. (default)\n * - \"error\": Raise a ToolCallLimitExceededError exception\n * - \"end\": Stop execution immediately with a ToolMessage + AI message for the single tool call that exceeded the limit. Raises NotImplementedError if there are multiple tool calls.\n *\n * @throws {Error} If both limits are undefined.\n * @throws {NotImplementedError} If exitBehavior is \"end\" and there are multiple tool calls.\n *\n * @example Continue execution with blocked tools (default)\n * ```ts\n * import { toolCallLimitMiddleware } from \"@langchain/langchain/agents/middleware\";\n * import { createAgent } from \"@langchain/langchain/agents\";\n *\n * // Block exceeded tools but let other tools and model continue\n * const limiter = toolCallLimitMiddleware({\n * threadLimit: 20,\n * runLimit: 10,\n * exitBehavior: \"continue\", // default\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [limiter]\n * });\n * ```\n *\n * @example Stop immediately when limit exceeded\n * ```ts\n * // End execution immediately with an AI message\n * const limiter = toolCallLimitMiddleware({\n * runLimit: 5,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [limiter]\n * });\n * ```\n *\n * @example Raise exception on limit\n * ```ts\n * // Strict limit with exception handling\n * const limiter = toolCallLimitMiddleware({\n * toolName: \"search\",\n * threadLimit: 5,\n * exitBehavior: \"error\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [limiter]\n * });\n *\n * try {\n * const result = await agent.invoke({ messages: [new HumanMessage(\"Task\")] });\n * } catch (error) {\n * if (error instanceof ToolCallLimitExceededError) {\n * console.log(`Search limit exceeded: ${error}`);\n * }\n * }\n * ```\n */\nexport function toolCallLimitMiddleware(options: ToolCallLimitConfig) {\n /**\n * Validate that at least one limit is specified\n */\n if (options.threadLimit === undefined && options.runLimit === undefined) {\n throw new Error(\n \"At least one limit must be specified (threadLimit or runLimit)\"\n );\n }\n\n /**\n * Apply default for exitBehavior\n */\n const exitBehavior = options.exitBehavior ?? \"continue\";\n\n /**\n * Generate the middleware name based on the tool name\n */\n const middlewareName = options.toolName\n ? `ToolCallLimitMiddleware[${options.toolName}]`\n : \"ToolCallLimitMiddleware\";\n\n return createMiddleware({\n name: middlewareName,\n stateSchema,\n afterModel: {\n canJumpTo: [\"end\"],\n hook: (state) => {\n /**\n * Get the last AI message to check for tool calls\n */\n const lastAIMessage = [...state.messages]\n .reverse()\n .find(AIMessage.isInstance);\n\n if (!lastAIMessage || !lastAIMessage.tool_calls) {\n return undefined;\n }\n\n /**\n * Helper to check if limit would be exceeded by one more call\n */\n const wouldExceedLimit = (\n threadCount: number,\n runCount: number\n ): boolean => {\n return (\n (options.threadLimit !== undefined &&\n threadCount + 1 > options.threadLimit) ||\n (options.runLimit !== undefined && runCount + 1 > options.runLimit)\n );\n };\n\n /**\n * Helper to check if a tool call matches our filter\n */\n const matchesToolFilter = (toolCall: { name?: string }): boolean => {\n return (\n options.toolName === undefined || toolCall.name === options.toolName\n );\n };\n\n /**\n * Separate tool calls into allowed and blocked based on limits\n */\n const separateToolCalls = (\n toolCalls: ToolCall[],\n threadCount: number,\n runCount: number\n ): {\n allowed: ToolCall[];\n blocked: ToolCall[];\n finalThreadCount: number;\n finalRunCount: number;\n } => {\n const allowed: ToolCall[] = [];\n const blocked: ToolCall[] = [];\n let tempThreadCount = threadCount;\n let tempRunCount = runCount;\n\n for (const toolCall of toolCalls) {\n if (!matchesToolFilter(toolCall)) {\n // Tool call doesn't match our filter, skip it\n continue;\n }\n\n if (wouldExceedLimit(tempThreadCount, tempRunCount)) {\n blocked.push(toolCall);\n } else {\n allowed.push(toolCall);\n tempThreadCount += 1;\n tempRunCount += 1;\n }\n }\n\n return {\n allowed,\n blocked,\n finalThreadCount: tempThreadCount + blocked.length,\n finalRunCount: tempRunCount + blocked.length,\n };\n };\n\n /**\n * Get the count key for this middleware instance\n */\n const countKey = options.toolName ?? DEFAULT_TOOL_COUNT_KEY;\n\n /**\n * Get current counts\n */\n const threadCounts = { ...(state.threadToolCallCount ?? {}) };\n const runCounts = { ...(state.runToolCallCount ?? {}) };\n const currentThreadCount = threadCounts[countKey] ?? 0;\n const currentRunCount = runCounts[countKey] ?? 0;\n\n /**\n * Separate tool calls into allowed and blocked\n */\n const { allowed, blocked, finalThreadCount, finalRunCount } =\n separateToolCalls(\n lastAIMessage.tool_calls,\n currentThreadCount,\n currentRunCount\n );\n\n /**\n * Update counts to include ALL tool call attempts (both allowed and blocked)\n */\n threadCounts[countKey] = finalThreadCount;\n runCounts[countKey] = finalRunCount;\n\n /**\n * If no tool calls are blocked, just update counts\n */\n if (blocked.length === 0) {\n if (allowed.length > 0) {\n return {\n threadToolCallCount: threadCounts,\n runToolCallCount: runCounts,\n };\n }\n return undefined;\n }\n\n /**\n * Handle different exit behaviors\n */\n if (exitBehavior === \"error\") {\n throw new ToolCallLimitExceededError(\n finalThreadCount,\n finalRunCount,\n options.threadLimit,\n options.runLimit,\n options.toolName\n );\n }\n\n /**\n * Build tool message content (sent to model - no thread/run details)\n */\n const toolMsgContent = buildToolMessageContent(\n options.toolName,\n finalThreadCount,\n finalRunCount,\n options.threadLimit,\n options.runLimit\n );\n\n /**\n * Inject artificial error ToolMessages for blocked tool calls\n */\n const artificialMessages: Array<ToolMessage | AIMessage> = blocked.map(\n (toolCall) =>\n new ToolMessage({\n content: toolMsgContent,\n tool_call_id: toolCall.id!,\n name: toolCall.name,\n status: \"error\",\n })\n );\n\n if (exitBehavior === \"end\") {\n /**\n * Check if there are tool calls to other tools that would continue executing\n * For tool-specific limiters: check for calls to other tools\n * For global limiters: check if there are multiple different tool types\n */\n let otherTools: ToolCall[] = [];\n if (options.toolName !== undefined) {\n /**\n * Tool-specific limiter: check for calls to other tools\n */\n otherTools = lastAIMessage.tool_calls.filter(\n (tc) => tc.name !== options.toolName\n );\n } else {\n /**\n * Global limiter: check if there are multiple different tool types\n * If there are allowed calls, those would execute\n * But even if all are blocked, we can't handle multiple tool types with \"end\"\n */\n const uniqueToolNames = new Set(\n lastAIMessage.tool_calls.map((tc) => tc.name).filter(Boolean)\n );\n if (uniqueToolNames.size > 1) {\n /**\n * Multiple different tool types - use allowed calls to show which ones\n */\n otherTools =\n allowed.length > 0 ? allowed : lastAIMessage.tool_calls;\n }\n }\n\n if (otherTools.length > 0) {\n const toolNames = Array.from(\n new Set(otherTools.map((tc) => tc.name).filter(Boolean))\n ).join(\", \");\n throw new Error(\n `Cannot end execution with other tool calls pending. Found calls to: ${toolNames}. Use 'continue' or 'error' behavior instead.`\n );\n }\n\n /**\n * Build final AI message content (displayed to user - includes thread/run details)\n */\n const finalMsgContent = buildFinalAIMessageContent(\n finalThreadCount,\n finalRunCount,\n options.threadLimit,\n options.runLimit,\n options.toolName\n );\n artificialMessages.push(new AIMessage(finalMsgContent));\n\n return {\n threadToolCallCount: threadCounts,\n runToolCallCount: runCounts,\n jumpTo: \"end\" as const,\n messages: artificialMessages,\n };\n }\n\n /**\n * For exit_behavior=\"continue\", return error messages to block exceeded tools\n */\n return {\n threadToolCallCount: threadCounts,\n runToolCallCount: runCounts,\n messages: artificialMessages,\n };\n },\n },\n /**\n * reset the run tool call count after the agent execution completes\n */\n afterAgent: () => ({\n runToolCallCount: {},\n }),\n });\n}\n"],"mappings":"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"}
@@ -53,21 +53,23 @@ declare const ToolCallLimitOptionsSchema: z.ZodObject<{
53
53
  runLimit: z.ZodOptional<z.ZodNumber>;
54
54
  /**
55
55
  * What to do when limits are exceeded.
56
- * - "end": Jump to the end of the agent execution and inject an artificial
57
- * AI message indicating that the limit was exceeded.
58
- * - "error": throws a ToolCallLimitExceededError
56
+ * - "continue": Block exceeded tools with error messages, let other tools continue (default)
57
+ * - "error": Raise a ToolCallLimitExceededError exception
58
+ * - "end": Stop execution immediately, injecting a ToolMessage and an AI message
59
+ * for the single tool call that exceeded the limit. Raises NotImplementedError
60
+ * if there are multiple tool calls.
59
61
  */
60
- exitBehavior: z.ZodDefault<z.ZodEnum<["end", "error"]>>;
62
+ exitBehavior: z.ZodDefault<z.ZodEnum<["continue", "error", "end"]>>;
61
63
  }, "strip", z.ZodTypeAny, {
62
64
  toolName?: string | undefined;
63
65
  threadLimit?: number | undefined;
64
66
  runLimit?: number | undefined;
65
- exitBehavior: "end" | "error";
67
+ exitBehavior: "continue" | "end" | "error";
66
68
  }, {
67
69
  toolName?: string | undefined;
68
70
  threadLimit?: number | undefined;
69
71
  runLimit?: number | undefined;
70
- exitBehavior?: "end" | "error" | undefined;
72
+ exitBehavior?: "continue" | "end" | "error" | undefined;
71
73
  }>;
72
74
  type ToolCallLimitConfig = InferInteropZodInput<typeof ToolCallLimitOptionsSchema>;
73
75
  /**
@@ -88,71 +90,78 @@ type ToolCallLimitConfig = InferInteropZodInput<typeof ToolCallLimitOptionsSchem
88
90
  * @param options.threadLimit - Maximum number of tool calls allowed per thread. undefined means no limit.
89
91
  * @param options.runLimit - Maximum number of tool calls allowed per run. undefined means no limit.
90
92
  * @param options.exitBehavior - What to do when limits are exceeded.
91
- * - "end": Jump to the end of the agent execution and inject an artificial AI message indicating that the limit was exceeded.
92
- * - "error": throws a ToolCallLimitExceededError
93
+ * - "continue": Block exceeded tools with error messages, let other tools continue. Model decides when to end. (default)
94
+ * - "error": Raise a ToolCallLimitExceededError exception
95
+ * - "end": Stop execution immediately with a ToolMessage + AI message for the single tool call that exceeded the limit. Raises NotImplementedError if there are multiple tool calls.
93
96
  *
94
- * @throws {Error} If both limits are undefined or if exitBehavior is invalid.
97
+ * @throws {Error} If both limits are undefined.
98
+ * @throws {NotImplementedError} If exitBehavior is "end" and there are multiple tool calls.
95
99
  *
96
- * @example Limit all tool calls globally
100
+ * @example Continue execution with blocked tools (default)
97
101
  * ```ts
98
102
  * import { toolCallLimitMiddleware } from "@langchain/langchain/agents/middleware";
99
103
  * import { createAgent } from "@langchain/langchain/agents";
100
104
  *
101
- * const globalLimiter = toolCallLimitMiddleware({
105
+ * // Block exceeded tools but let other tools and model continue
106
+ * const limiter = toolCallLimitMiddleware({
102
107
  * threadLimit: 20,
103
108
  * runLimit: 10,
104
- * exitBehavior: "end"
109
+ * exitBehavior: "continue", // default
105
110
  * });
106
111
  *
107
112
  * const agent = createAgent({
108
113
  * model: "openai:gpt-4o",
109
- * middleware: [globalLimiter]
114
+ * middleware: [limiter]
110
115
  * });
111
116
  * ```
112
117
  *
113
- * @example Limit a specific tool
118
+ * @example Stop immediately when limit exceeded
114
119
  * ```ts
115
- * import { toolCallLimitMiddleware } from "@langchain/langchain/agents/middleware";
116
- * import { createAgent } from "@langchain/langchain/agents";
117
- *
118
- * const searchLimiter = toolCallLimitMiddleware({
119
- * toolName: "search",
120
- * threadLimit: 5,
121
- * runLimit: 3,
120
+ * // End execution immediately with an AI message
121
+ * const limiter = toolCallLimitMiddleware({
122
+ * runLimit: 5,
122
123
  * exitBehavior: "end"
123
124
  * });
124
125
  *
125
126
  * const agent = createAgent({
126
127
  * model: "openai:gpt-4o",
127
- * middleware: [searchLimiter]
128
+ * middleware: [limiter]
128
129
  * });
129
130
  * ```
130
131
  *
131
- * @example Use both in the same agent
132
+ * @example Raise exception on limit
132
133
  * ```ts
133
- * import { toolCallLimitMiddleware } from "@langchain/langchain/agents/middleware";
134
- * import { createAgent } from "@langchain/langchain/agents";
135
- *
136
- * const globalLimiter = toolCallLimitMiddleware({
137
- * threadLimit: 20,
138
- * runLimit: 10,
139
- * exitBehavior: "end"
140
- * });
141
- *
142
- * const searchLimiter = toolCallLimitMiddleware({
134
+ * // Strict limit with exception handling
135
+ * const limiter = toolCallLimitMiddleware({
143
136
  * toolName: "search",
144
137
  * threadLimit: 5,
145
- * runLimit: 3,
146
- * exitBehavior: "end"
138
+ * exitBehavior: "error"
147
139
  * });
148
140
  *
149
141
  * const agent = createAgent({
150
142
  * model: "openai:gpt-4o",
151
- * middleware: [globalLimiter, searchLimiter]
143
+ * middleware: [limiter]
152
144
  * });
145
+ *
146
+ * try {
147
+ * const result = await agent.invoke({ messages: [new HumanMessage("Task")] });
148
+ * } catch (error) {
149
+ * if (error instanceof ToolCallLimitExceededError) {
150
+ * console.log(`Search limit exceeded: ${error}`);
151
+ * }
152
+ * }
153
153
  * ```
154
154
  */
155
- declare function toolCallLimitMiddleware(options: ToolCallLimitConfig): AgentMiddleware<undefined, undefined, any>;
155
+ declare function toolCallLimitMiddleware(options: ToolCallLimitConfig): AgentMiddleware<z.ZodObject<{
156
+ threadToolCallCount: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
157
+ runToolCallCount: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
158
+ }, "strip", z.ZodTypeAny, {
159
+ threadToolCallCount: Record<string, number>;
160
+ runToolCallCount: Record<string, number>;
161
+ }, {
162
+ threadToolCallCount?: Record<string, number> | undefined;
163
+ runToolCallCount?: Record<string, number> | undefined;
164
+ }>, undefined, any>;
156
165
  //#endregion
157
166
  export { ToolCallLimitConfig, ToolCallLimitExceededError, toolCallLimitMiddleware };
158
167
  //# sourceMappingURL=toolCallLimit.d.cts.map