@librechat/agents 3.1.56 → 3.1.60

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 (214) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +326 -62
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +13 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/events.cjs +7 -27
  6. package/dist/cjs/events.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +303 -222
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +4 -4
  10. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +6 -2
  12. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  13. package/dist/cjs/llm/init.cjs +60 -0
  14. package/dist/cjs/llm/init.cjs.map +1 -0
  15. package/dist/cjs/llm/invoke.cjs +90 -0
  16. package/dist/cjs/llm/invoke.cjs.map +1 -0
  17. package/dist/cjs/llm/openai/index.cjs +2 -0
  18. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  19. package/dist/cjs/llm/request.cjs +41 -0
  20. package/dist/cjs/llm/request.cjs.map +1 -0
  21. package/dist/cjs/main.cjs +40 -0
  22. package/dist/cjs/main.cjs.map +1 -1
  23. package/dist/cjs/messages/cache.cjs +76 -89
  24. package/dist/cjs/messages/cache.cjs.map +1 -1
  25. package/dist/cjs/messages/contextPruning.cjs +156 -0
  26. package/dist/cjs/messages/contextPruning.cjs.map +1 -0
  27. package/dist/cjs/messages/contextPruningSettings.cjs +53 -0
  28. package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -0
  29. package/dist/cjs/messages/core.cjs +23 -37
  30. package/dist/cjs/messages/core.cjs.map +1 -1
  31. package/dist/cjs/messages/format.cjs +156 -11
  32. package/dist/cjs/messages/format.cjs.map +1 -1
  33. package/dist/cjs/messages/prune.cjs +1161 -49
  34. package/dist/cjs/messages/prune.cjs.map +1 -1
  35. package/dist/cjs/messages/reducer.cjs +87 -0
  36. package/dist/cjs/messages/reducer.cjs.map +1 -0
  37. package/dist/cjs/run.cjs +81 -42
  38. package/dist/cjs/run.cjs.map +1 -1
  39. package/dist/cjs/stream.cjs +54 -7
  40. package/dist/cjs/stream.cjs.map +1 -1
  41. package/dist/cjs/summarization/index.cjs +75 -0
  42. package/dist/cjs/summarization/index.cjs.map +1 -0
  43. package/dist/cjs/summarization/node.cjs +663 -0
  44. package/dist/cjs/summarization/node.cjs.map +1 -0
  45. package/dist/cjs/tools/ToolNode.cjs +16 -8
  46. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  47. package/dist/cjs/tools/handlers.cjs +2 -0
  48. package/dist/cjs/tools/handlers.cjs.map +1 -1
  49. package/dist/cjs/utils/errors.cjs +115 -0
  50. package/dist/cjs/utils/errors.cjs.map +1 -0
  51. package/dist/cjs/utils/events.cjs +17 -0
  52. package/dist/cjs/utils/events.cjs.map +1 -1
  53. package/dist/cjs/utils/handlers.cjs +16 -0
  54. package/dist/cjs/utils/handlers.cjs.map +1 -1
  55. package/dist/cjs/utils/llm.cjs +10 -0
  56. package/dist/cjs/utils/llm.cjs.map +1 -1
  57. package/dist/cjs/utils/tokens.cjs +247 -14
  58. package/dist/cjs/utils/tokens.cjs.map +1 -1
  59. package/dist/cjs/utils/truncation.cjs +107 -0
  60. package/dist/cjs/utils/truncation.cjs.map +1 -0
  61. package/dist/esm/agents/AgentContext.mjs +325 -61
  62. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  63. package/dist/esm/common/enum.mjs +13 -0
  64. package/dist/esm/common/enum.mjs.map +1 -1
  65. package/dist/esm/events.mjs +8 -28
  66. package/dist/esm/events.mjs.map +1 -1
  67. package/dist/esm/graphs/Graph.mjs +307 -226
  68. package/dist/esm/graphs/Graph.mjs.map +1 -1
  69. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +4 -4
  70. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  71. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +6 -2
  72. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  73. package/dist/esm/llm/init.mjs +58 -0
  74. package/dist/esm/llm/init.mjs.map +1 -0
  75. package/dist/esm/llm/invoke.mjs +87 -0
  76. package/dist/esm/llm/invoke.mjs.map +1 -0
  77. package/dist/esm/llm/openai/index.mjs +2 -0
  78. package/dist/esm/llm/openai/index.mjs.map +1 -1
  79. package/dist/esm/llm/request.mjs +38 -0
  80. package/dist/esm/llm/request.mjs.map +1 -0
  81. package/dist/esm/main.mjs +13 -3
  82. package/dist/esm/main.mjs.map +1 -1
  83. package/dist/esm/messages/cache.mjs +76 -89
  84. package/dist/esm/messages/cache.mjs.map +1 -1
  85. package/dist/esm/messages/contextPruning.mjs +154 -0
  86. package/dist/esm/messages/contextPruning.mjs.map +1 -0
  87. package/dist/esm/messages/contextPruningSettings.mjs +50 -0
  88. package/dist/esm/messages/contextPruningSettings.mjs.map +1 -0
  89. package/dist/esm/messages/core.mjs +23 -37
  90. package/dist/esm/messages/core.mjs.map +1 -1
  91. package/dist/esm/messages/format.mjs +156 -11
  92. package/dist/esm/messages/format.mjs.map +1 -1
  93. package/dist/esm/messages/prune.mjs +1158 -52
  94. package/dist/esm/messages/prune.mjs.map +1 -1
  95. package/dist/esm/messages/reducer.mjs +83 -0
  96. package/dist/esm/messages/reducer.mjs.map +1 -0
  97. package/dist/esm/run.mjs +82 -43
  98. package/dist/esm/run.mjs.map +1 -1
  99. package/dist/esm/stream.mjs +54 -7
  100. package/dist/esm/stream.mjs.map +1 -1
  101. package/dist/esm/summarization/index.mjs +73 -0
  102. package/dist/esm/summarization/index.mjs.map +1 -0
  103. package/dist/esm/summarization/node.mjs +659 -0
  104. package/dist/esm/summarization/node.mjs.map +1 -0
  105. package/dist/esm/tools/ToolNode.mjs +16 -8
  106. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  107. package/dist/esm/tools/handlers.mjs +2 -0
  108. package/dist/esm/tools/handlers.mjs.map +1 -1
  109. package/dist/esm/utils/errors.mjs +111 -0
  110. package/dist/esm/utils/errors.mjs.map +1 -0
  111. package/dist/esm/utils/events.mjs +17 -1
  112. package/dist/esm/utils/events.mjs.map +1 -1
  113. package/dist/esm/utils/handlers.mjs +16 -0
  114. package/dist/esm/utils/handlers.mjs.map +1 -1
  115. package/dist/esm/utils/llm.mjs +10 -1
  116. package/dist/esm/utils/llm.mjs.map +1 -1
  117. package/dist/esm/utils/tokens.mjs +245 -15
  118. package/dist/esm/utils/tokens.mjs.map +1 -1
  119. package/dist/esm/utils/truncation.mjs +102 -0
  120. package/dist/esm/utils/truncation.mjs.map +1 -0
  121. package/dist/types/agents/AgentContext.d.ts +124 -6
  122. package/dist/types/common/enum.d.ts +14 -1
  123. package/dist/types/graphs/Graph.d.ts +22 -27
  124. package/dist/types/index.d.ts +5 -0
  125. package/dist/types/llm/init.d.ts +18 -0
  126. package/dist/types/llm/invoke.d.ts +48 -0
  127. package/dist/types/llm/request.d.ts +14 -0
  128. package/dist/types/messages/contextPruning.d.ts +42 -0
  129. package/dist/types/messages/contextPruningSettings.d.ts +44 -0
  130. package/dist/types/messages/core.d.ts +1 -1
  131. package/dist/types/messages/format.d.ts +17 -1
  132. package/dist/types/messages/index.d.ts +3 -0
  133. package/dist/types/messages/prune.d.ts +162 -1
  134. package/dist/types/messages/reducer.d.ts +18 -0
  135. package/dist/types/run.d.ts +12 -1
  136. package/dist/types/summarization/index.d.ts +20 -0
  137. package/dist/types/summarization/node.d.ts +29 -0
  138. package/dist/types/tools/ToolNode.d.ts +3 -1
  139. package/dist/types/types/graph.d.ts +44 -6
  140. package/dist/types/types/index.d.ts +1 -0
  141. package/dist/types/types/run.d.ts +30 -0
  142. package/dist/types/types/stream.d.ts +31 -4
  143. package/dist/types/types/summarize.d.ts +47 -0
  144. package/dist/types/types/tools.d.ts +7 -0
  145. package/dist/types/utils/errors.d.ts +28 -0
  146. package/dist/types/utils/events.d.ts +13 -0
  147. package/dist/types/utils/index.d.ts +2 -0
  148. package/dist/types/utils/llm.d.ts +4 -0
  149. package/dist/types/utils/tokens.d.ts +14 -1
  150. package/dist/types/utils/truncation.d.ts +49 -0
  151. package/package.json +2 -2
  152. package/src/agents/AgentContext.ts +388 -58
  153. package/src/agents/__tests__/AgentContext.test.ts +265 -5
  154. package/src/common/enum.ts +13 -0
  155. package/src/events.ts +9 -39
  156. package/src/graphs/Graph.ts +468 -331
  157. package/src/index.ts +7 -0
  158. package/src/llm/anthropic/llm.spec.ts +3 -3
  159. package/src/llm/anthropic/utils/message_inputs.ts +6 -4
  160. package/src/llm/bedrock/llm.spec.ts +1 -1
  161. package/src/llm/bedrock/utils/message_inputs.ts +6 -2
  162. package/src/llm/init.ts +63 -0
  163. package/src/llm/invoke.ts +144 -0
  164. package/src/llm/request.ts +55 -0
  165. package/src/messages/__tests__/observationMasking.test.ts +221 -0
  166. package/src/messages/cache.ts +77 -102
  167. package/src/messages/contextPruning.ts +191 -0
  168. package/src/messages/contextPruningSettings.ts +90 -0
  169. package/src/messages/core.ts +32 -53
  170. package/src/messages/ensureThinkingBlock.test.ts +39 -39
  171. package/src/messages/format.ts +227 -15
  172. package/src/messages/formatAgentMessages.test.ts +511 -1
  173. package/src/messages/index.ts +3 -0
  174. package/src/messages/prune.ts +1548 -62
  175. package/src/messages/reducer.ts +22 -0
  176. package/src/run.ts +104 -51
  177. package/src/scripts/bedrock-merge-test.ts +1 -1
  178. package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
  179. package/src/scripts/test-thinking-handoff.ts +1 -1
  180. package/src/scripts/thinking-bedrock.ts +1 -1
  181. package/src/scripts/thinking.ts +1 -1
  182. package/src/specs/anthropic.simple.test.ts +1 -1
  183. package/src/specs/multi-agent-summarization.test.ts +396 -0
  184. package/src/specs/prune.test.ts +1196 -23
  185. package/src/specs/summarization-unit.test.ts +868 -0
  186. package/src/specs/summarization.test.ts +3810 -0
  187. package/src/specs/summarize-prune.test.ts +376 -0
  188. package/src/specs/thinking-handoff.test.ts +10 -10
  189. package/src/specs/thinking-prune.test.ts +7 -4
  190. package/src/specs/token-accounting-e2e.test.ts +1034 -0
  191. package/src/specs/token-accounting-pipeline.test.ts +882 -0
  192. package/src/specs/token-distribution-edge-case.test.ts +25 -26
  193. package/src/splitStream.test.ts +42 -33
  194. package/src/stream.ts +64 -11
  195. package/src/summarization/__tests__/aggregator.test.ts +153 -0
  196. package/src/summarization/__tests__/node.test.ts +708 -0
  197. package/src/summarization/__tests__/trigger.test.ts +50 -0
  198. package/src/summarization/index.ts +102 -0
  199. package/src/summarization/node.ts +982 -0
  200. package/src/tools/ToolNode.ts +25 -3
  201. package/src/types/graph.ts +62 -7
  202. package/src/types/index.ts +1 -0
  203. package/src/types/run.ts +32 -0
  204. package/src/types/stream.ts +45 -5
  205. package/src/types/summarize.ts +58 -0
  206. package/src/types/tools.ts +7 -0
  207. package/src/utils/errors.ts +117 -0
  208. package/src/utils/events.ts +31 -0
  209. package/src/utils/handlers.ts +18 -0
  210. package/src/utils/index.ts +2 -0
  211. package/src/utils/llm.ts +12 -0
  212. package/src/utils/tokens.ts +336 -18
  213. package/src/utils/truncation.ts +124 -0
  214. package/src/scripts/image.ts +0 -180
@@ -1,11 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var nanoid = require('nanoid');
4
- var stream$1 = require('@langchain/core/utils/stream');
5
- var googleVertexai = require('@langchain/google-vertexai');
6
- var langgraph = require('@langchain/langgraph');
7
- var runnables = require('@langchain/core/runnables');
8
4
  var messages = require('@langchain/core/messages');
5
+ var langgraph = require('@langchain/langgraph');
9
6
  var core = require('../messages/core.cjs');
10
7
  var ids = require('../messages/ids.cjs');
11
8
  var prune = require('../messages/prune.cjs');
@@ -13,25 +10,29 @@ var format = require('../messages/format.cjs');
13
10
  var cache = require('../messages/cache.cjs');
14
11
  var content = require('../messages/content.cjs');
15
12
  var tools = require('../messages/tools.cjs');
13
+ var reducer = require('../messages/reducer.cjs');
16
14
  var _enum = require('../common/enum.cjs');
17
15
  var graph = require('../utils/graph.cjs');
18
16
  var llm = require('../utils/llm.cjs');
19
- var stream = require('../stream.cjs');
20
17
  var handlers = require('../tools/handlers.cjs');
21
18
  var run = require('../utils/run.cjs');
22
19
  require('ai-tokenizer');
23
20
  require('zod-to-json-schema');
24
- var providers = require('../llm/providers.cjs');
25
21
  var ToolNode = require('../tools/ToolNode.cjs');
26
- var index = require('../llm/openai/index.cjs');
27
22
  var events = require('../utils/events.cjs');
23
+ var invoke = require('../llm/invoke.cjs');
24
+ var index = require('../summarization/index.cjs');
25
+ var node = require('../summarization/node.cjs');
28
26
  var schema = require('../tools/schema.cjs');
29
27
  var AgentContext = require('../agents/AgentContext.cjs');
30
28
  var fake = require('../llm/fake.cjs');
29
+ var request = require('../llm/request.cjs');
30
+ var init = require('../llm/init.cjs');
31
31
 
32
32
  /* eslint-disable no-console */
33
- // src/graphs/Graph.ts
34
- const { AGENT, TOOLS } = _enum.GraphNodeKeys;
33
+ const { AGENT, TOOLS, SUMMARIZE } = _enum.GraphNodeKeys;
34
+ /** Minimum relative variance before calibrated toolSchemaTokens overrides current value. */
35
+ const CALIBRATION_VARIANCE_THRESHOLD = 0.15;
35
36
  class Graph {
36
37
  messageStepHasToolCalls = new Map();
37
38
  messageIdsByStepKey = new Map();
@@ -41,6 +42,12 @@ class Graph {
41
42
  stepKeyIds = new Map();
42
43
  contentIndexMap = new Map();
43
44
  toolCallStepIds = new Map();
45
+ /**
46
+ * Step IDs that have been dispatched via handler registry directly
47
+ * (in dispatchRunStep). Used by the custom event callback to skip
48
+ * duplicate dispatch through the LangGraph callback chain.
49
+ */
50
+ handlerDispatchedStepIds = new Set();
44
51
  signal;
45
52
  /** Set of invoked tool call IDs from non-message run steps completed mid-run, if any */
46
53
  invokedToolIds;
@@ -76,16 +83,23 @@ class StandardGraph extends Graph {
76
83
  /** Optional compile options passed into workflow.compile() */
77
84
  compileOptions;
78
85
  messages = [];
86
+ /** Cached run messages preserved before clearHeavyState() so getRunMessages() works after cleanup. */
87
+ cachedRunMessages;
79
88
  runId;
89
+ /**
90
+ * Boundary between historical messages (loaded from conversation state)
91
+ * and messages produced during the current run. Set once in the state
92
+ * reducer when messages first arrive. Used by `getRunMessages()` and
93
+ * multi-agent message filtering — NOT for pruner token counting (the
94
+ * pruner maintains its own `lastTurnStartIndex` in its closure).
95
+ */
80
96
  startIndex = 0;
81
97
  signal;
82
98
  /** Map of agent contexts by agent ID */
83
99
  agentContexts = new Map();
84
100
  /** Default agent ID to use */
85
101
  defaultAgentId;
86
- constructor({
87
- // parent-level graph inputs
88
- runId, signal, agents, tokenCounter, indexTokenCountMap, }) {
102
+ constructor({ runId, signal, agents, tokenCounter, indexTokenCountMap, calibrationRatio, }) {
89
103
  super();
90
104
  this.runId = runId;
91
105
  this.signal = signal;
@@ -94,6 +108,9 @@ class StandardGraph extends Graph {
94
108
  }
95
109
  for (const agentConfig of agents) {
96
110
  const agentContext = AgentContext.AgentContext.fromConfig(agentConfig, tokenCounter, indexTokenCountMap);
111
+ if (calibrationRatio != null && calibrationRatio > 0) {
112
+ agentContext.calibrationRatio = calibrationRatio;
113
+ }
97
114
  this.agentContexts.set(agentConfig.agentId, agentContext);
98
115
  }
99
116
  this.defaultAgentId = agents[0].agentId;
@@ -101,6 +118,7 @@ class StandardGraph extends Graph {
101
118
  /* Init */
102
119
  resetValues(keepContent) {
103
120
  this.messages = [];
121
+ this.cachedRunMessages = undefined;
104
122
  this.config = graph.resetIfNotEmpty(this.config, undefined);
105
123
  if (keepContent !== true) {
106
124
  this.contentData = graph.resetIfNotEmpty(this.contentData, []);
@@ -114,6 +132,7 @@ class StandardGraph extends Graph {
114
132
  * a stale reference on 2nd+ processStream calls.
115
133
  */
116
134
  this.toolCallStepIds.clear();
135
+ this.handlerDispatchedStepIds = graph.resetIfNotEmpty(this.handlerDispatchedStepIds, new Set());
117
136
  this.messageIdsByStepKey = graph.resetIfNotEmpty(this.messageIdsByStepKey, new Map());
118
137
  this.messageStepHasToolCalls = graph.resetIfNotEmpty(this.messageStepHasToolCalls, new Map());
119
138
  this.prelimMessageIdsByStepKey = graph.resetIfNotEmpty(this.prelimMessageIdsByStepKey, new Map());
@@ -123,6 +142,7 @@ class StandardGraph extends Graph {
123
142
  }
124
143
  }
125
144
  clearHeavyState() {
145
+ this.cachedRunMessages = this.messages.slice(this.startIndex);
126
146
  super.clearHeavyState();
127
147
  this.messages = [];
128
148
  this.overrideModel = undefined;
@@ -153,6 +173,9 @@ class StandardGraph extends Graph {
153
173
  else if (currentNode.startsWith(TOOLS)) {
154
174
  agentId = currentNode.substring(TOOLS.length);
155
175
  }
176
+ else if (currentNode.startsWith(SUMMARIZE)) {
177
+ agentId = currentNode.substring(SUMMARIZE.length);
178
+ }
156
179
  const agentContext = this.agentContexts.get(agentId ?? '');
157
180
  if (!agentContext) {
158
181
  throw new Error(`No agent context found for agent ID ${agentId}`);
@@ -222,11 +245,26 @@ class StandardGraph extends Graph {
222
245
  }
223
246
  /* Misc.*/
224
247
  getRunMessages() {
248
+ if (this.messages.length === 0 && this.cachedRunMessages != null) {
249
+ return this.cachedRunMessages;
250
+ }
225
251
  return this.messages.slice(this.startIndex);
226
252
  }
227
253
  getContentParts() {
228
254
  return core.convertMessagesToContent(this.messages.slice(this.startIndex));
229
255
  }
256
+ getCalibrationRatio() {
257
+ const context = this.agentContexts.get(this.defaultAgentId);
258
+ return context?.calibrationRatio ?? 1;
259
+ }
260
+ getResolvedInstructionOverhead() {
261
+ const context = this.agentContexts.get(this.defaultAgentId);
262
+ return context?.resolvedInstructionOverhead;
263
+ }
264
+ getToolCount() {
265
+ const context = this.agentContexts.get(this.defaultAgentId);
266
+ return ((context?.tools?.length ?? 0) + (context?.toolDefinitions?.length ?? 0));
267
+ }
230
268
  /**
231
269
  * Get all run steps, optionally filtered by agent ID
232
270
  */
@@ -278,35 +316,6 @@ class StandardGraph extends Graph {
278
316
  return contentPartAgentMap;
279
317
  }
280
318
  /* Graph */
281
- createSystemRunnable({ provider, clientOptions, instructions, additional_instructions, }) {
282
- let finalInstructions = instructions;
283
- if (additional_instructions != null && additional_instructions !== '') {
284
- finalInstructions =
285
- finalInstructions != null && finalInstructions
286
- ? `${finalInstructions}\n\n${additional_instructions}`
287
- : additional_instructions;
288
- }
289
- if (finalInstructions != null &&
290
- finalInstructions &&
291
- provider === _enum.Providers.ANTHROPIC &&
292
- clientOptions.promptCache === true) {
293
- finalInstructions = {
294
- content: [
295
- {
296
- type: 'text',
297
- text: instructions,
298
- cache_control: { type: 'ephemeral' },
299
- },
300
- ],
301
- };
302
- }
303
- if (finalInstructions != null && finalInstructions !== '') {
304
- const systemMessage = new messages.SystemMessage(finalInstructions);
305
- return runnables.RunnableLambda.from((messages) => {
306
- return [systemMessage, ...messages];
307
- }).withConfig({ runName: 'prompt' });
308
- }
309
- }
310
319
  initializeTools({ currentTools, currentToolMap, agentContext, }) {
311
320
  const toolDefinitions = agentContext?.toolDefinitions;
312
321
  const eventDrivenMode = toolDefinitions != null && toolDefinitions.length > 0;
@@ -336,6 +345,8 @@ class StandardGraph extends Graph {
336
345
  toolCallStepIds: this.toolCallStepIds,
337
346
  toolRegistry: agentContext?.toolRegistry,
338
347
  directToolNames: directToolNames.size > 0 ? directToolNames : undefined,
348
+ maxContextTokens: agentContext?.maxContextTokens,
349
+ maxToolResultChars: agentContext?.maxToolResultChars,
339
350
  errorHandler: (data, metadata) => StandardGraph.handleToolCallErrorStatic(this, data, metadata),
340
351
  });
341
352
  }
@@ -359,42 +370,10 @@ class StandardGraph extends Graph {
359
370
  errorHandler: (data, metadata) => StandardGraph.handleToolCallErrorStatic(this, data, metadata),
360
371
  toolRegistry: agentContext?.toolRegistry,
361
372
  sessions: this.sessions,
373
+ maxContextTokens: agentContext?.maxContextTokens,
374
+ maxToolResultChars: agentContext?.maxToolResultChars,
362
375
  });
363
376
  }
364
- initializeModel({ provider, tools, clientOptions, }) {
365
- const ChatModelClass = providers.getChatModelClass(provider);
366
- const model = new ChatModelClass(clientOptions ?? {});
367
- if (llm.isOpenAILike(provider) &&
368
- (model instanceof index.ChatOpenAI || model instanceof index.AzureChatOpenAI)) {
369
- model.temperature = clientOptions
370
- .temperature;
371
- model.topP = clientOptions.topP;
372
- model.frequencyPenalty = clientOptions
373
- .frequencyPenalty;
374
- model.presencePenalty = clientOptions
375
- .presencePenalty;
376
- model.n = clientOptions.n;
377
- }
378
- else if (provider === _enum.Providers.VERTEXAI &&
379
- model instanceof googleVertexai.ChatVertexAI) {
380
- model.temperature = clientOptions
381
- .temperature;
382
- model.topP = clientOptions.topP;
383
- model.topK = clientOptions.topK;
384
- model.topLogprobs = clientOptions
385
- .topLogprobs;
386
- model.frequencyPenalty = clientOptions
387
- .frequencyPenalty;
388
- model.presencePenalty = clientOptions
389
- .presencePenalty;
390
- model.maxOutputTokens = clientOptions
391
- .maxOutputTokens;
392
- }
393
- if (!tools || tools.length === 0) {
394
- return model;
395
- }
396
- return model.bindTools(tools);
397
- }
398
377
  overrideTestModel(responses, sleep, toolCalls) {
399
378
  this.overrideModel = fake.createFakeStreamingLLM({
400
379
  responses,
@@ -402,10 +381,6 @@ class StandardGraph extends Graph {
402
381
  toolCalls,
403
382
  });
404
383
  }
405
- getNewModel({ provider, clientOptions, }) {
406
- const ChatModelClass = providers.getChatModelClass(provider);
407
- return new ChatModelClass(clientOptions ?? {});
408
- }
409
384
  getUsageMetadata(finalMessage) {
410
385
  if (finalMessage &&
411
386
  'usage_metadata' in finalMessage &&
@@ -413,58 +388,6 @@ class StandardGraph extends Graph {
413
388
  return finalMessage.usage_metadata;
414
389
  }
415
390
  }
416
- /** Execute model invocation with streaming support */
417
- async attemptInvoke({ currentModel, finalMessages, provider, tools: _tools, }, config) {
418
- const model = this.overrideModel ?? currentModel;
419
- if (!model) {
420
- throw new Error('No model found');
421
- }
422
- if (model.stream) {
423
- /**
424
- * Process all model output through a local ChatModelStreamHandler in the
425
- * graph execution context. Each chunk is awaited before the next one is
426
- * consumed, so by the time the stream is exhausted every run step
427
- * (MESSAGE_CREATION, TOOL_CALLS) has been created and toolCallStepIds is
428
- * fully populated — the graph will not transition to ToolNode until this
429
- * is done.
430
- *
431
- * This replaces the previous pattern where ChatModelStreamHandler lived
432
- * in the for-await stream consumer (handler registry). That consumer
433
- * runs concurrently with graph execution, so the graph could advance to
434
- * ToolNode before the consumer had processed all events. By handling
435
- * chunks here, inside the agent node, the race is eliminated.
436
- *
437
- * The for-await consumer no longer needs a ChatModelStreamHandler; its
438
- * on_chat_model_stream events are simply ignored (no handler registered).
439
- * The dispatched custom events (ON_RUN_STEP, ON_MESSAGE_DELTA, etc.)
440
- * still reach the content aggregator and SSE handlers through the custom
441
- * event callback in Run.createCustomEventCallback.
442
- */
443
- const metadata = config?.metadata;
444
- const streamHandler = new stream.ChatModelStreamHandler();
445
- const stream$2 = await model.stream(finalMessages, config);
446
- let finalChunk;
447
- for await (const chunk of stream$2) {
448
- await streamHandler.handle(_enum.GraphEvents.CHAT_MODEL_STREAM, { chunk }, metadata, this);
449
- finalChunk = finalChunk ? stream$1.concat(finalChunk, chunk) : chunk;
450
- }
451
- if (providers.manualToolStreamProviders.has(provider)) {
452
- finalChunk = core.modifyDeltaProperties(provider, finalChunk);
453
- }
454
- if ((finalChunk?.tool_calls?.length ?? 0) > 0) {
455
- finalChunk.tool_calls = finalChunk.tool_calls?.filter((tool_call) => !!tool_call.name);
456
- }
457
- return { messages: [finalChunk] };
458
- }
459
- else {
460
- /** Fallback for models without stream support. */
461
- const finalMessage = await model.invoke(finalMessages, config);
462
- if ((finalMessage.tool_calls?.length ?? 0) > 0) {
463
- finalMessage.tool_calls = finalMessage.tool_calls?.filter((tool_call) => !!tool_call.name);
464
- }
465
- return { messages: [finalMessage] };
466
- }
467
- }
468
391
  cleanupSignalListener(currentModel) {
469
392
  if (!this.signal) {
470
393
  return;
@@ -482,9 +405,6 @@ class StandardGraph extends Graph {
482
405
  }
483
406
  createCallModel(agentId = 'default') {
484
407
  return async (state, config) => {
485
- /**
486
- * Get agent context - it must exist by this point
487
- */
488
408
  const agentContext = this.agentContexts.get(agentId);
489
409
  if (!agentContext) {
490
410
  throw new Error(`Agent context not found for agentId: ${agentId}`);
@@ -493,14 +413,13 @@ class StandardGraph extends Graph {
493
413
  throw new Error('No config provided');
494
414
  }
495
415
  const { messages: messages$1 } = state;
496
- // Extract tool discoveries from current turn only (similar to formatArtifactPayload pattern)
497
416
  const discoveredNames = tools.extractToolDiscoveries(messages$1);
498
417
  if (discoveredNames.length > 0) {
499
418
  agentContext.markToolsAsDiscovered(discoveredNames);
500
419
  }
501
420
  const toolsForBinding = agentContext.getToolsForBinding();
502
421
  let model = this.overrideModel ??
503
- this.initializeModel({
422
+ init.initializeModel({
504
423
  tools: toolsForBinding,
505
424
  provider: agentContext.provider,
506
425
  clientOptions: agentContext.clientOptions,
@@ -518,34 +437,97 @@ class StandardGraph extends Graph {
518
437
  let messagesToUse = messages$1;
519
438
  if (!agentContext.pruneMessages &&
520
439
  agentContext.tokenCounter &&
521
- agentContext.maxContextTokens != null &&
522
- agentContext.indexTokenCountMap[0] != null) {
523
- const isAnthropicWithThinking = (agentContext.provider === _enum.Providers.ANTHROPIC &&
524
- agentContext.clientOptions.thinking !=
525
- null) ||
526
- (agentContext.provider === _enum.Providers.BEDROCK &&
527
- agentContext.clientOptions
528
- .additionalModelRequestFields?.['thinking'] != null) ||
529
- (agentContext.provider === _enum.Providers.OPENAI &&
530
- agentContext.clientOptions.modelKwargs
531
- ?.thinking?.type === 'enabled');
440
+ agentContext.maxContextTokens != null) {
532
441
  agentContext.pruneMessages = prune.createPruneMessages({
533
- startIndex: this.startIndex,
442
+ startIndex: agentContext.indexTokenCountMap[0] != null ? this.startIndex : 0,
534
443
  provider: agentContext.provider,
535
444
  tokenCounter: agentContext.tokenCounter,
536
445
  maxTokens: agentContext.maxContextTokens,
537
- thinkingEnabled: isAnthropicWithThinking,
446
+ thinkingEnabled: request.isThinkingEnabled(agentContext.provider, agentContext.clientOptions),
538
447
  indexTokenCountMap: agentContext.indexTokenCountMap,
448
+ contextPruningConfig: agentContext.contextPruningConfig,
449
+ summarizationEnabled: agentContext.summarizationEnabled,
450
+ reserveRatio: agentContext.summarizationConfig?.reserveRatio,
451
+ calibrationRatio: agentContext.calibrationRatio,
452
+ getInstructionTokens: () => agentContext.instructionTokens,
453
+ log: (level, message, data) => {
454
+ events.emitAgentLog(config, level, 'prune', message, data, {
455
+ runId: this.runId,
456
+ agentId,
457
+ });
458
+ },
539
459
  });
540
460
  }
541
461
  if (agentContext.pruneMessages) {
542
- const { context, indexTokenCountMap } = agentContext.pruneMessages({
462
+ const { context, indexTokenCountMap, messagesToRefine, prePruneContextTokens, remainingContextTokens, originalToolContent, calibrationRatio, resolvedInstructionOverhead, } = agentContext.pruneMessages({
543
463
  messages: messages$1,
544
464
  usageMetadata: agentContext.currentUsage,
545
- // startOnMessageType: 'human',
465
+ lastCallUsage: agentContext.lastCallUsage,
466
+ totalTokensFresh: agentContext.totalTokensFresh,
546
467
  });
547
468
  agentContext.indexTokenCountMap = indexTokenCountMap;
469
+ if (calibrationRatio != null && calibrationRatio > 0) {
470
+ agentContext.calibrationRatio = calibrationRatio;
471
+ }
472
+ if (resolvedInstructionOverhead != null) {
473
+ agentContext.resolvedInstructionOverhead =
474
+ resolvedInstructionOverhead;
475
+ const nonToolOverhead = agentContext.instructionTokens - agentContext.toolSchemaTokens;
476
+ const calibratedToolTokens = Math.max(0, resolvedInstructionOverhead - nonToolOverhead);
477
+ const currentToolTokens = agentContext.toolSchemaTokens;
478
+ const variance = currentToolTokens > 0
479
+ ? Math.abs(calibratedToolTokens - currentToolTokens) /
480
+ currentToolTokens
481
+ : 1;
482
+ if (variance > CALIBRATION_VARIANCE_THRESHOLD) {
483
+ agentContext.toolSchemaTokens = calibratedToolTokens;
484
+ }
485
+ }
548
486
  messagesToUse = context;
487
+ const hasPrunedMessages = agentContext.summarizationEnabled === true &&
488
+ Array.isArray(messagesToRefine) &&
489
+ messagesToRefine.length > 0;
490
+ if (hasPrunedMessages) {
491
+ const shouldSkip = agentContext.shouldSkipSummarization(messages$1.length);
492
+ const triggerResult = !shouldSkip &&
493
+ index.shouldTriggerSummarization({
494
+ trigger: agentContext.summarizationConfig?.trigger,
495
+ maxContextTokens: agentContext.maxContextTokens,
496
+ prePruneContextTokens: prePruneContextTokens != null
497
+ ? prePruneContextTokens + agentContext.instructionTokens
498
+ : undefined,
499
+ remainingContextTokens,
500
+ messagesToRefineCount: messagesToRefine.length,
501
+ });
502
+ if (triggerResult) {
503
+ if (originalToolContent != null && originalToolContent.size > 0) {
504
+ agentContext.pendingOriginalToolContent = originalToolContent;
505
+ }
506
+ events.emitAgentLog(config, 'info', 'graph', 'Summarization triggered', undefined, { runId: this.runId, agentId });
507
+ events.emitAgentLog(config, 'debug', 'graph', 'Summarization trigger details', {
508
+ totalMessages: messages$1.length,
509
+ remainingContextTokens: remainingContextTokens ?? 0,
510
+ summaryVersion: agentContext.summaryVersion + 1,
511
+ toolSchemaTokens: agentContext.toolSchemaTokens,
512
+ instructionTokens: agentContext.instructionTokens,
513
+ systemMessageTokens: agentContext.systemMessageTokens,
514
+ }, { runId: this.runId, agentId });
515
+ agentContext.markSummarizationTriggered(messages$1.length);
516
+ return {
517
+ summarizationRequest: {
518
+ remainingContextTokens: remainingContextTokens ?? 0,
519
+ agentId: agentId || agentContext.agentId,
520
+ },
521
+ };
522
+ }
523
+ if (shouldSkip) {
524
+ events.emitAgentLog(config, 'debug', 'graph', 'Summarization skipped — no new messages or per-run cap reached', {
525
+ messageCount: messages$1.length,
526
+ messagesToRefineCount: messagesToRefine.length,
527
+ contextLength: context.length,
528
+ }, { runId: this.runId, agentId });
529
+ }
530
+ }
549
531
  }
550
532
  let finalMessages = messagesToUse;
551
533
  if (agentContext.useLegacyContent) {
@@ -557,26 +539,29 @@ class StandardGraph extends Graph {
557
539
  const lastMessageY = finalMessages.length >= 1
558
540
  ? finalMessages[finalMessages.length - 1]
559
541
  : null;
542
+ const anthropicLike = llm.isAnthropicLike(agentContext.provider, agentContext.clientOptions);
560
543
  if (agentContext.provider === _enum.Providers.BEDROCK &&
561
544
  lastMessageX instanceof messages.AIMessageChunk &&
562
545
  lastMessageY instanceof messages.ToolMessage &&
563
546
  typeof lastMessageX.content === 'string') {
564
- finalMessages[finalMessages.length - 2].content = '';
547
+ const trimmed = lastMessageX.content.trim();
548
+ finalMessages[finalMessages.length - 2].content =
549
+ trimmed.length > 0 ? [{ type: 'text', text: trimmed }] : '';
565
550
  }
566
- const isLatestToolMessage = lastMessageY instanceof messages.ToolMessage;
567
- if (isLatestToolMessage &&
568
- agentContext.provider === _enum.Providers.ANTHROPIC) {
569
- core.formatAnthropicArtifactContent(finalMessages);
570
- }
571
- else if (isLatestToolMessage &&
572
- ((llm.isOpenAILike(agentContext.provider) &&
551
+ if (lastMessageY instanceof messages.ToolMessage) {
552
+ if (anthropicLike) {
553
+ core.formatAnthropicArtifactContent(finalMessages);
554
+ }
555
+ else if ((llm.isOpenAILike(agentContext.provider) &&
573
556
  agentContext.provider !== _enum.Providers.DEEPSEEK) ||
574
- llm.isGoogleLike(agentContext.provider))) {
575
- core.formatArtifactPayload(finalMessages);
557
+ llm.isGoogleLike(agentContext.provider)) {
558
+ core.formatArtifactPayload(finalMessages);
559
+ }
576
560
  }
577
561
  if (agentContext.provider === _enum.Providers.ANTHROPIC) {
578
562
  const anthropicOptions = agentContext.clientOptions;
579
- if (anthropicOptions?.promptCache === true) {
563
+ if (anthropicOptions?.promptCache === true &&
564
+ !agentContext.systemRunnable) {
580
565
  finalMessages = cache.addCacheControl(finalMessages);
581
566
  }
582
567
  }
@@ -586,19 +571,25 @@ class StandardGraph extends Graph {
586
571
  finalMessages = cache.addBedrockCacheControl(finalMessages);
587
572
  }
588
573
  }
589
- /**
590
- * Handle edge case: when switching from a non-thinking agent to a thinking-enabled agent,
591
- * convert AI messages with tool calls to HumanMessages to avoid thinking block requirements.
592
- * This is required by Anthropic/Bedrock when thinking is enabled.
593
- */
594
- const isAnthropicWithThinking = (agentContext.provider === _enum.Providers.ANTHROPIC &&
595
- agentContext.clientOptions.thinking !=
596
- null) ||
597
- (agentContext.provider === _enum.Providers.BEDROCK &&
598
- agentContext.clientOptions
599
- .additionalModelRequestFields?.['thinking'] != null);
600
- if (isAnthropicWithThinking) {
601
- finalMessages = format.ensureThinkingBlockInMessages(finalMessages, agentContext.provider);
574
+ if (request.isThinkingEnabled(agentContext.provider, agentContext.clientOptions)) {
575
+ finalMessages = format.ensureThinkingBlockInMessages(finalMessages, agentContext.provider, config);
576
+ }
577
+ // Intentionally broad: runs when the pruner wasn't used OR any post-pruning
578
+ // transform (addCacheControl, ensureThinkingBlock, etc.) reassigned finalMessages.
579
+ // sanitizeOrphanToolBlocks fast-paths to a Set diff check when no orphans exist,
580
+ // so the cost is negligible and this acts as a safety net for Anthropic/Bedrock.
581
+ const needsOrphanSanitize = anthropicLike &&
582
+ (!agentContext.pruneMessages || finalMessages !== messagesToUse);
583
+ if (needsOrphanSanitize) {
584
+ const beforeSanitize = finalMessages.length;
585
+ finalMessages = prune.sanitizeOrphanToolBlocks(finalMessages);
586
+ if (finalMessages.length !== beforeSanitize) {
587
+ events.emitAgentLog(config, 'warn', 'sanitize', 'Orphan tool blocks removed', {
588
+ before: beforeSanitize,
589
+ after: finalMessages.length,
590
+ dropped: beforeSanitize - finalMessages.length,
591
+ }, { runId: this.runId, agentId });
592
+ }
602
593
  }
603
594
  if (agentContext.lastStreamCall != null &&
604
595
  agentContext.streamBuffer != null) {
@@ -610,52 +601,68 @@ class StandardGraph extends Graph {
610
601
  }
611
602
  }
612
603
  agentContext.lastStreamCall = Date.now();
604
+ agentContext.markTokensStale();
613
605
  let result;
614
606
  const fallbacks = agentContext.clientOptions?.fallbacks ??
615
607
  [];
616
- if (finalMessages.length === 0) {
608
+ if (finalMessages.length === 0 &&
609
+ !agentContext.hasPendingCompactionSummary()) {
610
+ const budgetBreakdown = agentContext.getTokenBudgetBreakdown(messages$1);
611
+ const breakdown = agentContext.formatTokenBudgetBreakdown(messages$1);
612
+ const instructionsExceedBudget = budgetBreakdown.instructionTokens > budgetBreakdown.maxContextTokens;
613
+ let guidance;
614
+ if (instructionsExceedBudget) {
615
+ const toolPct = budgetBreakdown.toolSchemaTokens > 0
616
+ ? Math.round((budgetBreakdown.toolSchemaTokens /
617
+ budgetBreakdown.instructionTokens) *
618
+ 100)
619
+ : 0;
620
+ guidance =
621
+ toolPct > 50
622
+ ? `Tool definitions consume ${budgetBreakdown.toolSchemaTokens} tokens (${toolPct}% of instructions) across ${budgetBreakdown.toolCount} tools, exceeding maxContextTokens (${budgetBreakdown.maxContextTokens}). Reduce the number of tools or increase maxContextTokens.`
623
+ : `Instructions (${budgetBreakdown.instructionTokens} tokens) exceed maxContextTokens (${budgetBreakdown.maxContextTokens}). Increase maxContextTokens or shorten the system prompt.`;
624
+ if (agentContext.summarizationEnabled === true) {
625
+ guidance +=
626
+ ' Summarization was skipped because the summary would further increase the instruction overhead.';
627
+ }
628
+ }
629
+ else {
630
+ guidance =
631
+ 'Please increase the context window size or make your message shorter.';
632
+ }
633
+ events.emitAgentLog(config, 'error', 'graph', 'Empty messages after pruning', {
634
+ messageCount: messages$1.length,
635
+ instructionsExceedBudget,
636
+ breakdown,
637
+ }, { runId: this.runId, agentId });
617
638
  throw new Error(JSON.stringify({
618
639
  type: 'empty_messages',
619
- info: 'Message pruning removed all messages as none fit in the context window. Please increase the context window size or make your message shorter.',
640
+ info: `Message pruning removed all messages as none fit in the context window. ${guidance}\n${breakdown}`,
620
641
  }));
621
642
  }
643
+ const invokeStart = Date.now();
644
+ const invokeMeta = { runId: this.runId, agentId };
645
+ events.emitAgentLog(config, 'debug', 'graph', 'Invoking LLM', {
646
+ messageCount: finalMessages.length,
647
+ provider: agentContext.provider,
648
+ }, invokeMeta, { force: true });
622
649
  try {
623
- result = await this.attemptInvoke({
624
- currentModel: model,
625
- finalMessages,
650
+ result = await invoke.attemptInvoke({
651
+ model: (this.overrideModel ?? model),
652
+ messages: finalMessages,
626
653
  provider: agentContext.provider,
627
- tools: agentContext.tools,
654
+ context: this,
628
655
  }, config);
629
656
  }
630
657
  catch (primaryError) {
631
- let lastError = primaryError;
632
- for (const fb of fallbacks) {
633
- try {
634
- let model = this.getNewModel({
635
- provider: fb.provider,
636
- clientOptions: fb.clientOptions,
637
- });
638
- const bindableTools = agentContext.tools;
639
- model = (!bindableTools || bindableTools.length === 0
640
- ? model
641
- : model.bindTools(bindableTools));
642
- result = await this.attemptInvoke({
643
- currentModel: model,
644
- finalMessages,
645
- provider: fb.provider,
646
- tools: agentContext.tools,
647
- }, config);
648
- lastError = undefined;
649
- break;
650
- }
651
- catch (e) {
652
- lastError = e;
653
- continue;
654
- }
655
- }
656
- if (lastError !== undefined) {
657
- throw lastError;
658
- }
658
+ result = await invoke.tryFallbackProviders({
659
+ fallbacks,
660
+ tools: agentContext.tools,
661
+ messages: finalMessages,
662
+ config,
663
+ primaryError,
664
+ context: this,
665
+ });
659
666
  }
660
667
  if (!result) {
661
668
  throw new Error('No result after model invocation');
@@ -755,27 +762,53 @@ class StandardGraph extends Graph {
755
762
  }
756
763
  }
757
764
  }
765
+ const invokeElapsed = ((Date.now() - invokeStart) / 1000).toFixed(2);
758
766
  agentContext.currentUsage = this.getUsageMetadata(result.messages?.[0]);
767
+ if (agentContext.currentUsage) {
768
+ agentContext.updateLastCallUsage(agentContext.currentUsage);
769
+ events.emitAgentLog(config, 'debug', 'graph', `LLM call complete (${invokeElapsed}s)`, {
770
+ ...agentContext.currentUsage,
771
+ elapsedSeconds: Number(invokeElapsed),
772
+ instructionTokens: agentContext.instructionTokens,
773
+ toolSchemaTokens: agentContext.toolSchemaTokens,
774
+ messageCount: finalMessages.length,
775
+ }, invokeMeta, { force: true });
776
+ }
777
+ else {
778
+ events.emitAgentLog(config, 'debug', 'graph', `LLM call complete (${invokeElapsed}s)`, {
779
+ elapsedSeconds: Number(invokeElapsed),
780
+ messageCount: finalMessages.length,
781
+ }, invokeMeta, { force: true });
782
+ }
759
783
  this.cleanupSignalListener();
760
784
  return result;
761
785
  };
762
786
  }
763
787
  createAgentNode(agentId) {
788
+ const getConfig = () => this.config;
764
789
  const agentContext = this.agentContexts.get(agentId);
765
790
  if (!agentContext) {
766
791
  throw new Error(`Agent context not found for agentId: ${agentId}`);
767
792
  }
768
793
  const agentNode = `${AGENT}${agentId}`;
769
794
  const toolNode = `${TOOLS}${agentId}`;
795
+ const summarizeNode = `${SUMMARIZE}${agentId}`;
770
796
  const routeMessage = (state, config) => {
771
797
  this.config = config;
798
+ if (state.summarizationRequest != null) {
799
+ return summarizeNode;
800
+ }
772
801
  return ToolNode.toolsCondition(state, toolNode, this.invokedToolIds);
773
802
  };
774
803
  const StateAnnotation = langgraph.Annotation.Root({
775
804
  messages: langgraph.Annotation({
776
- reducer: langgraph.messagesStateReducer,
805
+ reducer: reducer.messagesStateReducer,
777
806
  default: () => [],
778
807
  }),
808
+ summarizationRequest: langgraph.Annotation({
809
+ reducer: (_, b) => b,
810
+ default: () => undefined,
811
+ }),
779
812
  });
780
813
  const workflow = new langgraph.StateGraph(StateAnnotation)
781
814
  .addNode(agentNode, this.createCallModel(agentId))
@@ -783,15 +816,54 @@ class StandardGraph extends Graph {
783
816
  currentTools: agentContext.tools,
784
817
  currentToolMap: agentContext.toolMap,
785
818
  agentContext,
819
+ }))
820
+ .addNode(summarizeNode, node.createSummarizeNode({
821
+ agentContext,
822
+ graph: {
823
+ contentData: this.contentData,
824
+ contentIndexMap: this.contentIndexMap,
825
+ get config() {
826
+ return getConfig();
827
+ },
828
+ runId: this.runId,
829
+ isMultiAgent: this.isMultiAgentGraph(),
830
+ dispatchRunStep: async (runStep, nodeConfig) => {
831
+ this.contentData.push(runStep);
832
+ this.contentIndexMap.set(runStep.id, runStep.index);
833
+ const resolvedConfig = nodeConfig ?? this.config;
834
+ const handler = this.handlerRegistry?.getHandler(_enum.GraphEvents.ON_RUN_STEP);
835
+ if (handler) {
836
+ await handler.handle(_enum.GraphEvents.ON_RUN_STEP, runStep, resolvedConfig?.configurable, this);
837
+ this.handlerDispatchedStepIds.add(runStep.id);
838
+ }
839
+ if (resolvedConfig) {
840
+ await events.safeDispatchCustomEvent(_enum.GraphEvents.ON_RUN_STEP, runStep, resolvedConfig);
841
+ }
842
+ },
843
+ dispatchRunStepCompleted: async (stepId, result, nodeConfig) => {
844
+ const resolvedConfig = nodeConfig ?? this.config;
845
+ const runStep = this.contentData.find((s) => s.id === stepId);
846
+ const handler = this.handlerRegistry?.getHandler(_enum.GraphEvents.ON_RUN_STEP_COMPLETED);
847
+ if (handler) {
848
+ await handler.handle(_enum.GraphEvents.ON_RUN_STEP_COMPLETED, {
849
+ result: {
850
+ ...result,
851
+ id: stepId,
852
+ index: runStep?.index ?? 0,
853
+ },
854
+ }, resolvedConfig?.configurable, this);
855
+ }
856
+ },
857
+ },
858
+ generateStepId: (stepKey) => this.generateStepId(stepKey),
786
859
  }))
787
860
  .addEdge(langgraph.START, agentNode)
788
861
  .addConditionalEdges(agentNode, routeMessage)
862
+ .addEdge(summarizeNode, agentNode)
789
863
  .addEdge(toolNode, agentContext.toolEnd ? langgraph.END : agentNode);
790
- // Cast to unknown to avoid tight coupling to external types; options are opt-in
791
- return workflow.compile(this.compileOptions);
864
+ return workflow.compile();
792
865
  }
793
866
  createWorkflow() {
794
- /** Use the default (first) agent for now */
795
867
  const agentNode = this.createAgentNode(this.defaultAgentId);
796
868
  const StateAnnotation = langgraph.Annotation.Root({
797
869
  messages: langgraph.Annotation({
@@ -799,7 +871,7 @@ class StandardGraph extends Graph {
799
871
  if (!a.length) {
800
872
  this.startIndex = a.length + b.length;
801
873
  }
802
- const result = langgraph.messagesStateReducer(a, b);
874
+ const result = reducer.messagesStateReducer(a, b);
803
875
  this.messages = result;
804
876
  return result;
805
877
  },
@@ -809,7 +881,8 @@ class StandardGraph extends Graph {
809
881
  const workflow = new langgraph.StateGraph(StateAnnotation)
810
882
  .addNode(this.defaultAgentId, agentNode, { ends: [langgraph.END] })
811
883
  .addEdge(langgraph.START, this.defaultAgentId)
812
- .compile();
884
+ // LangGraph compile() types are overly strict for opt-in options
885
+ .compile(this.compileOptions);
813
886
  return workflow;
814
887
  }
815
888
  /**
@@ -860,18 +933,11 @@ class StandardGraph extends Graph {
860
933
  if (runId) {
861
934
  runStep.runId = runId;
862
935
  }
863
- /**
864
- * Extract agentId and parallelGroupId from metadata
865
- * Only set agentId for MultiAgentGraph (so frontend knows when to show agent labels)
866
- */
867
936
  if (metadata) {
868
937
  try {
869
938
  const agentContext = this.getAgentContext(metadata);
870
939
  if (this.isMultiAgentGraph() && agentContext.agentId) {
871
- // Only include agentId for MultiAgentGraph - enables frontend to show agent labels
872
940
  runStep.agentId = agentContext.agentId;
873
- // Set group ID if this agent is part of a parallel group
874
- // Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order
875
941
  const groupId = this.getParallelGroupIdForAgent(agentContext.agentId);
876
942
  if (groupId != null) {
877
943
  runStep.groupId = groupId;
@@ -884,6 +950,21 @@ class StandardGraph extends Graph {
884
950
  }
885
951
  this.contentData.push(runStep);
886
952
  this.contentIndexMap.set(stepId, runStep.index);
953
+ // Primary dispatch: handler registry (reliable, always works).
954
+ // This mirrors how handleToolCallCompleted dispatches ON_RUN_STEP_COMPLETED
955
+ // via the handler registry, ensuring the event always reaches the handler
956
+ // even when LangGraph's callback system drops the custom event.
957
+ const handler = this.handlerRegistry?.getHandler(_enum.GraphEvents.ON_RUN_STEP);
958
+ if (handler) {
959
+ await handler.handle(_enum.GraphEvents.ON_RUN_STEP, runStep, metadata, this);
960
+ this.handlerDispatchedStepIds.add(stepId);
961
+ }
962
+ // Secondary dispatch: custom event for LangGraph callback chain
963
+ // (tracing, Langfuse, external consumers). May be silently dropped
964
+ // in some scenarios (stale run ID, subgraph callback propagation issues),
965
+ // but the primary dispatch above guarantees the event reaches the handler.
966
+ // The customEventCallback in run.ts skips events already dispatched above
967
+ // to prevent double handling.
887
968
  await events.safeDispatchCustomEvent(_enum.GraphEvents.ON_RUN_STEP, runStep, this.config);
888
969
  return stepId;
889
970
  }