@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
@@ -20,6 +20,10 @@ import type { BaseMessage, AIMessage } from '@langchain/core/messages';
20
20
  import type { StructuredToolInterface } from '@langchain/core/tools';
21
21
  import type * as t from '@/types';
22
22
  import { RunnableCallable } from '@/utils';
23
+ import {
24
+ calculateMaxToolResultChars,
25
+ truncateToolResultContent,
26
+ } from '@/utils/truncation';
23
27
  import { safeDispatchCustomEvent } from '@/utils/events';
24
28
  import { Constants, GraphEvents } from '@/common';
25
29
 
@@ -53,6 +57,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
53
57
  private agentId?: string;
54
58
  /** Tool names that bypass event dispatch and execute directly (e.g., graph-managed handoff tools) */
55
59
  private directToolNames?: Set<string>;
60
+ /** Maximum characters allowed in a single tool result before truncation. */
61
+ private maxToolResultChars: number;
56
62
 
57
63
  constructor({
58
64
  tools,
@@ -68,6 +74,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
68
74
  eventDrivenMode,
69
75
  agentId,
70
76
  directToolNames,
77
+ maxContextTokens,
78
+ maxToolResultChars,
71
79
  }: t.ToolNodeConstructorParams) {
72
80
  super({ name, tags, func: (input, config) => this.run(input, config) });
73
81
  this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
@@ -81,6 +89,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
81
89
  this.eventDrivenMode = eventDrivenMode ?? false;
82
90
  this.agentId = agentId;
83
91
  this.directToolNames = directToolNames;
92
+ this.maxToolResultChars =
93
+ maxToolResultChars ?? calculateMaxToolResultChars(maxContextTokens);
84
94
  }
85
95
 
86
96
  /**
@@ -201,10 +211,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
201
211
  ) {
202
212
  return output;
203
213
  } else {
214
+ const rawContent =
215
+ typeof output === 'string' ? output : JSON.stringify(output);
204
216
  return new ToolMessage({
205
217
  status: 'success',
206
218
  name: tool.name,
207
- content: typeof output === 'string' ? output : JSON.stringify(output),
219
+ content: truncateToolResultContent(
220
+ rawContent,
221
+ this.maxToolResultChars
222
+ ),
208
223
  tool_call_id: call.id!,
209
224
  });
210
225
  }
@@ -539,10 +554,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
539
554
  tool_call_id: result.toolCallId,
540
555
  });
541
556
  } else {
542
- contentString =
557
+ const rawContent =
543
558
  typeof result.content === 'string'
544
559
  ? result.content
545
560
  : JSON.stringify(result.content);
561
+ contentString = truncateToolResultContent(
562
+ rawContent,
563
+ this.maxToolResultChars
564
+ );
546
565
  toolMessage = new ToolMessage({
547
566
  status: 'success',
548
567
  name: toolName,
@@ -658,7 +677,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
658
677
  */
659
678
  return (
660
679
  (call.id == null || !toolMessageIds.has(call.id)) &&
661
- !(call.id?.startsWith('srvtoolu_') ?? false)
680
+ !(
681
+ call.id?.startsWith(Constants.ANTHROPIC_SERVER_TOOL_PREFIX) ??
682
+ false
683
+ )
662
684
  );
663
685
  }) ?? [];
664
686
 
@@ -22,6 +22,13 @@ import type { ToolMap, ToolEndEvent, GenericTool, LCTool } from '@/types/tools';
22
22
  import type { Providers, Callback, GraphNodeKeys } from '@/common';
23
23
  import type { StandardGraph, MultiAgentGraph } from '@/graphs';
24
24
  import type { ClientOptions } from '@/types/llm';
25
+ import type {
26
+ SummarizationNodeInput,
27
+ SummarizeCompleteEvent,
28
+ SummarizationConfig,
29
+ SummarizeStartEvent,
30
+ SummarizeDeltaEvent,
31
+ } from '@/types/summarize';
25
32
  import type {
26
33
  RunStep,
27
34
  RunStepDeltaEvent,
@@ -66,12 +73,25 @@ export type BaseGraphState = {
66
73
  messages: BaseMessage[];
67
74
  };
68
75
 
76
+ export type AgentSubgraphState = BaseGraphState & {
77
+ summarizationRequest?: SummarizationNodeInput;
78
+ };
79
+
69
80
  export type MultiAgentGraphState = BaseGraphState & {
70
81
  agentMessages?: BaseMessage[];
71
82
  };
72
83
 
73
84
  export type IState = BaseGraphState;
74
85
 
86
+ export interface AgentLogEvent {
87
+ level: 'debug' | 'info' | 'warn' | 'error';
88
+ scope: 'prune' | 'summarize' | 'graph' | 'sanitize' | (string & {});
89
+ message: string;
90
+ data?: Record<string, unknown>;
91
+ runId?: string;
92
+ agentId?: string;
93
+ }
94
+
75
95
  export interface EventHandler {
76
96
  handle(
77
97
  event: string,
@@ -82,6 +102,10 @@ export interface EventHandler {
82
102
  | RunStepDeltaEvent
83
103
  | MessageDeltaEvent
84
104
  | ReasoningDeltaEvent
105
+ | SummarizeStartEvent
106
+ | SummarizeDeltaEvent
107
+ | SummarizeCompleteEvent
108
+ | AgentLogEvent
85
109
  | { result: ToolEndEvent },
86
110
  metadata?: Record<string, unknown>,
87
111
  graph?: StandardGraph | MultiAgentGraph
@@ -142,24 +166,30 @@ export type CompiledMultiAgentWorkflow = CompiledStateGraph<
142
166
  >;
143
167
 
144
168
  export type CompiledAgentWorfklow = CompiledStateGraph<
145
- {
146
- messages: BaseMessage[];
147
- },
148
- {
149
- messages?: BaseMessage[] | undefined;
150
- },
151
- '__start__' | `agent=${string}` | `tools=${string}`,
169
+ AgentSubgraphState,
170
+ Partial<AgentSubgraphState>,
171
+ '__start__' | `agent=${string}` | `tools=${string}` | `summarize=${string}`,
152
172
  {
153
173
  messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
174
+ summarizationRequest: BinaryOperatorAggregate<
175
+ SummarizationNodeInput | undefined,
176
+ SummarizationNodeInput | undefined
177
+ >;
154
178
  },
155
179
  {
156
180
  messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
181
+ summarizationRequest: BinaryOperatorAggregate<
182
+ SummarizationNodeInput | undefined,
183
+ SummarizationNodeInput | undefined
184
+ >;
157
185
  },
158
186
  StateDefinition,
159
187
  {
160
188
  [x: `agent=${string}`]: Partial<BaseGraphState>;
161
189
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
190
  [x: `tools=${string}`]: any;
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
+ [x: `summarize=${string}`]: any;
163
193
  }
164
194
  >;
165
195
 
@@ -314,6 +344,7 @@ export type StandardGraphInput = {
314
344
  agents: AgentInputs[];
315
345
  tokenCounter?: TokenCounter;
316
346
  indexTokenCountMap?: Record<string, number>;
347
+ calibrationRatio?: number;
317
348
  };
318
349
 
319
350
  export type GraphEdge = {
@@ -391,4 +422,28 @@ export interface AgentInputs {
391
422
  * in tool binding without requiring tool_search.
392
423
  */
393
424
  discoveredTools?: string[];
425
+ summarizationEnabled?: boolean;
426
+ summarizationConfig?: SummarizationConfig;
427
+ /** Cross-run summary from a previous run, forwarded from formatAgentMessages.
428
+ * Injected into the system message via AgentContext.buildInstructionsString(). */
429
+ initialSummary?: { text: string; tokenCount: number };
430
+ contextPruningConfig?: ContextPruningConfig;
431
+ maxToolResultChars?: number;
432
+ }
433
+
434
+ export interface ContextPruningConfig {
435
+ enabled?: boolean;
436
+ keepLastAssistants?: number;
437
+ softTrimRatio?: number;
438
+ hardClearRatio?: number;
439
+ minPrunableToolChars?: number;
440
+ softTrim?: {
441
+ maxChars?: number;
442
+ headChars?: number;
443
+ tailChars?: number;
444
+ };
445
+ hardClear?: {
446
+ enabled?: boolean;
447
+ placeholder?: string;
448
+ };
394
449
  }
@@ -4,3 +4,4 @@ export * from './llm';
4
4
  export * from './run';
5
5
  export * from './stream';
6
6
  export * from './tools';
7
+ export * from './summarize';
package/src/types/run.ts CHANGED
@@ -115,6 +115,15 @@ export type RunConfig = {
115
115
  returnContent?: boolean;
116
116
  tokenCounter?: TokenCounter;
117
117
  indexTokenCountMap?: Record<string, number>;
118
+ /**
119
+ * Calibration ratio from a previous run's contextMeta.
120
+ * Seeds the pruner's EMA so new messages are scaled immediately.
121
+ *
122
+ * Hosts should persist the value returned by `Run.getCalibrationRatio()`
123
+ * after each run and pass it back here on subsequent runs for the same
124
+ * conversation. Without this, the EMA resets to 1 on every new Run instance.
125
+ */
126
+ calibrationRatio?: number;
118
127
  /** Skip post-stream cleanup (clearHeavyState) — useful for tests that inspect graph state after processStream */
119
128
  skipCleanup?: boolean;
120
129
  };
@@ -124,6 +133,29 @@ export type ProvidedCallbacks =
124
133
  | undefined;
125
134
 
126
135
  export type TokenCounter = (message: BaseMessage) => number;
136
+
137
+ /** Structured breakdown of how context token budget is consumed. */
138
+ export type TokenBudgetBreakdown = {
139
+ /** Total context window budget (maxContextTokens). */
140
+ maxContextTokens: number;
141
+ /** Total instruction tokens (system + tools + summary). */
142
+ instructionTokens: number;
143
+ /** Tokens from the system message text alone. */
144
+ systemMessageTokens: number;
145
+ /** Tokens from tool schema definitions. */
146
+ toolSchemaTokens: number;
147
+ /** Tokens from the conversation summary. */
148
+ summaryTokens: number;
149
+ /** Number of registered tools. */
150
+ toolCount: number;
151
+ /** Number of messages in the conversation. */
152
+ messageCount: number;
153
+ /** Total tokens consumed by messages (excluding system). */
154
+ messageTokens: number;
155
+ /** Tokens available for messages after instructions. */
156
+ availableForMessages: number;
157
+ };
158
+
127
159
  export type EventStreamOptions = {
128
160
  callbacks?: g.ClientCallbacks;
129
161
  keepContent?: boolean;
@@ -10,6 +10,7 @@ import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
10
10
  import type { LLMResult, Generation } from '@langchain/core/outputs';
11
11
  import type { AnthropicContentBlock } from '@/llm/anthropic/types';
12
12
  import type { Command } from '@langchain/langgraph';
13
+ import type { SummarizeCompleteEvent } from '@/types/summarize';
13
14
  import type { ToolEndEvent } from '@/types/tools';
14
15
  import { StepTypes, ContentTypes, GraphEvents } from '@/common/enum';
15
16
 
@@ -80,6 +81,7 @@ export type RunStep = {
80
81
  index: number; // #new
81
82
  stepIndex?: number; // #new
82
83
  stepDetails: StepDetails;
84
+ summary?: SummaryContentBlock;
83
85
  usage?: null | object;
84
86
  // {
85
87
  // Define usage structure if it's ever non-null
@@ -106,7 +108,12 @@ export interface RunStepDeltaEvent {
106
108
 
107
109
  export type StepDetails = MessageCreationDetails | ToolCallsDetails;
108
110
 
109
- export type StepCompleted = ToolCallCompleted;
111
+ export type SummaryCompleted = {
112
+ type: 'summary';
113
+ summary: SummaryContentBlock;
114
+ };
115
+
116
+ export type StepCompleted = ToolCallCompleted | SummaryCompleted;
110
117
 
111
118
  export type MessageCreationDetails = {
112
119
  type: StepTypes.MESSAGE_CREATION;
@@ -164,6 +171,7 @@ export type ToolCallsDetails = {
164
171
  export type ToolCallDelta = {
165
172
  type: StepTypes;
166
173
  tool_calls?: ToolCallChunk[]; // #new
174
+ summary?: SummaryContentBlock;
167
175
  auth?: string;
168
176
  expires_at?: number;
169
177
  };
@@ -260,13 +268,35 @@ export type MessageDeltaUpdate = {
260
268
  };
261
269
  export type ReasoningDeltaUpdate = { type: ContentTypes.THINK; think: string };
262
270
 
263
- export type ContentType = 'text' | 'image_url' | 'tool_call' | 'think' | string;
271
+ export type ContentType =
272
+ | 'text'
273
+ | 'image_url'
274
+ | 'tool_call'
275
+ | 'think'
276
+ | 'summary'
277
+ | string;
264
278
 
265
279
  export type ReasoningContentText = {
266
280
  type: ContentTypes.THINK;
267
281
  think: string;
268
282
  };
269
283
 
284
+ export type SummaryBoundary = {
285
+ messageId: string;
286
+ contentIndex: number;
287
+ };
288
+
289
+ export type SummaryContentBlock = {
290
+ type: ContentTypes.SUMMARY;
291
+ content?: MessageContentComplex[];
292
+ tokenCount?: number;
293
+ boundary?: SummaryBoundary;
294
+ summaryVersion?: number;
295
+ model?: string;
296
+ provider?: string;
297
+ createdAt?: string;
298
+ };
299
+
270
300
  /** Vertex AI / Google Common - Reasoning Content Block Format */
271
301
  export type GoogleReasoningContentText = {
272
302
  type: ContentTypes.REASONING;
@@ -330,6 +360,7 @@ export type ToolResultContent = {
330
360
  export type MessageContentComplex = (
331
361
  | ToolResultContent
332
362
  | ThinkingContentText
363
+ | SummaryContentBlock
333
364
  | AgentUpdate
334
365
  | ToolCallContent
335
366
  | ReasoningContentText
@@ -399,6 +430,13 @@ export type SplitStreamHandlers = Partial<{
399
430
  }) => void;
400
431
  }>;
401
432
 
433
+ export type SummarizeDeltaData = {
434
+ id: string;
435
+ delta: {
436
+ summary: SummaryContentBlock;
437
+ };
438
+ };
439
+
402
440
  export type ContentAggregator = ({
403
441
  event,
404
442
  data,
@@ -406,11 +444,13 @@ export type ContentAggregator = ({
406
444
  event: GraphEvents;
407
445
  data:
408
446
  | RunStep
447
+ | AgentUpdate
409
448
  | MessageDeltaEvent
449
+ | ReasoningDeltaEvent
410
450
  | RunStepDeltaEvent
411
- | {
412
- result: ToolEndEvent;
413
- };
451
+ | SummarizeDeltaData
452
+ | SummarizeCompleteEvent
453
+ | { result: ToolEndEvent };
414
454
  }) => void;
415
455
  export type ContentAggregatorResult = {
416
456
  stepMap: Map<string, RunStep | undefined>;
@@ -0,0 +1,58 @@
1
+ import type { SummaryContentBlock } from '@/types/stream';
2
+ import type { Providers } from '@/common';
3
+
4
+ export type SummarizationTrigger = {
5
+ type:
6
+ | 'token_ratio'
7
+ | 'remaining_tokens'
8
+ | 'messages_to_refine'
9
+ | (string & {});
10
+ value: number;
11
+ };
12
+
13
+ export type SummarizationConfig = {
14
+ provider?: Providers;
15
+ model?: string;
16
+ parameters?: Record<string, unknown>;
17
+ prompt?: string;
18
+ updatePrompt?: string;
19
+ trigger?: SummarizationTrigger;
20
+ maxSummaryTokens?: number;
21
+ /** Fraction of the token budget reserved as headroom (0–1). Defaults to 0.05. */
22
+ reserveRatio?: number;
23
+ };
24
+
25
+ export interface SummarizeResult {
26
+ text: string;
27
+ tokenCount: number;
28
+ model?: string;
29
+ provider?: string;
30
+ }
31
+
32
+ export interface SummarizationNodeInput {
33
+ remainingContextTokens: number;
34
+ agentId: string;
35
+ }
36
+
37
+ export interface SummarizeStartEvent {
38
+ agentId: string;
39
+ provider: string;
40
+ model?: string;
41
+ messagesToRefineCount: number;
42
+ /** Which summarization cycle this is (1-based, increments each time summarization fires) */
43
+ summaryVersion: number;
44
+ }
45
+
46
+ export interface SummarizeDeltaEvent {
47
+ id: string;
48
+ delta: {
49
+ summary: SummaryContentBlock;
50
+ };
51
+ }
52
+
53
+ export interface SummarizeCompleteEvent {
54
+ id: string;
55
+ agentId: string;
56
+ summary?: SummaryContentBlock;
57
+ error?: string;
58
+ }
@@ -49,6 +49,13 @@ export type ToolNodeOptions = {
49
49
  agentId?: string;
50
50
  /** Tool names that must be executed directly (via runTool) even in event-driven mode (e.g., graph-managed handoff tools) */
51
51
  directToolNames?: Set<string>;
52
+ /** Max context tokens for the agent — used to compute tool result truncation limits. */
53
+ maxContextTokens?: number;
54
+ /**
55
+ * Maximum characters allowed in a single tool result before truncation.
56
+ * When provided, takes precedence over the value computed from maxContextTokens.
57
+ */
58
+ maxToolResultChars?: number;
52
59
  };
53
60
 
54
61
  export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Context overflow error detection utilities.
3
+ *
4
+ * Identifies provider-specific error messages that indicate the request
5
+ * exceeded the model's context window. Used by the overflow recovery loop
6
+ * to decide whether to retry with truncation/compaction vs. propagating
7
+ * the error.
8
+ */
9
+
10
+ /**
11
+ * Exact phrases that definitively indicate a context overflow error.
12
+ * These are returned by various LLM providers when the prompt is too large.
13
+ */
14
+ const CONTEXT_OVERFLOW_PHRASES = [
15
+ 'request_too_large',
16
+ 'context length exceeded',
17
+ 'maximum context length',
18
+ 'prompt is too long',
19
+ 'exceeds model context window',
20
+ 'exceeds the model',
21
+ 'too large for model',
22
+ 'context_length_exceeded',
23
+ 'max_tokens',
24
+ 'token limit',
25
+ 'input too long',
26
+ 'payload too large',
27
+ 'content_too_large',
28
+ ] as const;
29
+
30
+ /**
31
+ * HTTP status codes and broader hints that suggest context overflow.
32
+ * Used by the less-strict `isLikelyContextOverflowError`.
33
+ */
34
+ const CONTEXT_OVERFLOW_HINT_RE =
35
+ /413|too large|too long|context.*exceed|exceed.*context|token.*limit|limit.*token|prompt.*size|size.*limit|maximum.*length|length.*maximum/i;
36
+
37
+ /**
38
+ * Patterns that should NOT be treated as context overflow even if they
39
+ * contain words like "limit" or "too large".
40
+ */
41
+ const FALSE_POSITIVE_RE =
42
+ /rate.?limit|too many requests|quota|billing|auth|permission|forbidden/i;
43
+
44
+ /**
45
+ * Extracts a human-readable error message from an unknown error value.
46
+ */
47
+ export function extractErrorMessage(error: unknown): string {
48
+ if (error == null) {
49
+ return '';
50
+ }
51
+ if (typeof error === 'string') {
52
+ return error;
53
+ }
54
+ if (error instanceof Error) {
55
+ return error.message;
56
+ }
57
+ if (typeof error === 'object') {
58
+ const record = error as Record<string, unknown>;
59
+ if (typeof record.message === 'string') {
60
+ return record.message;
61
+ }
62
+ if (typeof record.error === 'string') {
63
+ return record.error;
64
+ }
65
+ if (
66
+ typeof record.error === 'object' &&
67
+ record.error != null &&
68
+ typeof (record.error as Record<string, unknown>).message === 'string'
69
+ ) {
70
+ return (record.error as Record<string, unknown>).message as string;
71
+ }
72
+ }
73
+ try {
74
+ return JSON.stringify(error);
75
+ } catch {
76
+ return String(error);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Returns true if the error message definitively indicates a context
82
+ * overflow / prompt-too-large error from the provider.
83
+ *
84
+ * This is the strict check: only matches known, unambiguous phrases.
85
+ * Use this when you want high confidence before taking recovery action.
86
+ */
87
+ export function isContextOverflowError(errorMessage?: string): boolean {
88
+ if (!errorMessage) {
89
+ return false;
90
+ }
91
+ const lower = errorMessage.toLowerCase();
92
+ if (FALSE_POSITIVE_RE.test(lower)) {
93
+ return false;
94
+ }
95
+ return CONTEXT_OVERFLOW_PHRASES.some((phrase) => lower.includes(phrase));
96
+ }
97
+
98
+ /**
99
+ * Returns true if the error message likely indicates a context overflow.
100
+ * Uses broader heuristic matching (regex) in addition to exact phrases.
101
+ *
102
+ * May produce false positives for unusual error messages. Use this when
103
+ * the cost of a false positive (one extra retry) is acceptable.
104
+ */
105
+ export function isLikelyContextOverflowError(errorMessage?: string): boolean {
106
+ if (!errorMessage) {
107
+ return false;
108
+ }
109
+ if (isContextOverflowError(errorMessage)) {
110
+ return true;
111
+ }
112
+ const lower = errorMessage.toLowerCase();
113
+ if (FALSE_POSITIVE_RE.test(lower)) {
114
+ return false;
115
+ }
116
+ return CONTEXT_OVERFLOW_HINT_RE.test(lower);
117
+ }
@@ -2,6 +2,8 @@
2
2
  // src/utils/events.ts
3
3
  import { dispatchCustomEvent } from '@langchain/core/callbacks/dispatch';
4
4
  import type { RunnableConfig } from '@langchain/core/runnables';
5
+ import type { AgentLogEvent } from '@/types/graph';
6
+ import { GraphEvents } from '@/common';
5
7
 
6
8
  /**
7
9
  * Safely dispatches a custom event and properly awaits it to avoid
@@ -30,3 +32,32 @@ export async function safeDispatchCustomEvent(
30
32
  console.error('Error dispatching custom event:', e);
31
33
  }
32
34
  }
35
+
36
+ /**
37
+ * Fire-and-forget diagnostic log event.
38
+ * Debug-level logs are gated behind AGENT_DEBUG_LOGGING=true to avoid
39
+ * overhead in production. Info/warn/error always flow through.
40
+ * Pass `force: true` to bypass the env-var gate (e.g. invoke timing).
41
+ */
42
+ export function emitAgentLog(
43
+ config: RunnableConfig | undefined,
44
+ level: AgentLogEvent['level'],
45
+ scope: AgentLogEvent['scope'],
46
+ message: string,
47
+ data?: Record<string, unknown>,
48
+ meta?: { runId?: string; agentId?: string },
49
+ options?: { force?: boolean }
50
+ ): void {
51
+ if (!config) return;
52
+ if (
53
+ level === 'debug' &&
54
+ !(options?.force ?? false) &&
55
+ process.env.AGENT_DEBUG_LOGGING !== 'true'
56
+ )
57
+ return;
58
+ void safeDispatchCustomEvent(
59
+ GraphEvents.ON_AGENT_LOG,
60
+ { level, scope, message, data, ...meta } satisfies AgentLogEvent,
61
+ config
62
+ );
63
+ }
@@ -97,6 +97,24 @@ export function createHandlers(callbacks?: HandlerCallbacks): {
97
97
  callbacks?.onMessageDelta?.(event, data);
98
98
  },
99
99
  },
100
+
101
+ [GraphEvents.ON_SUMMARIZE_DELTA]: {
102
+ handle: (event: string, data: t.StreamEventData): void => {
103
+ aggregateContent({
104
+ event: event as GraphEvents,
105
+ data: data as t.SummarizeDeltaData,
106
+ });
107
+ },
108
+ },
109
+
110
+ [GraphEvents.ON_SUMMARIZE_COMPLETE]: {
111
+ handle: (event: string, data: t.StreamEventData): void => {
112
+ aggregateContent({
113
+ event: event as GraphEvents,
114
+ data: data as t.SummarizeCompleteEvent,
115
+ });
116
+ },
117
+ },
100
118
  };
101
119
 
102
120
  return {
@@ -5,3 +5,5 @@ export * from './handlers';
5
5
  export * from './run';
6
6
  export * from './tokens';
7
7
  export * from './schema';
8
+ export * from './truncation';
9
+ export * from './errors';
package/src/utils/llm.ts CHANGED
@@ -24,3 +24,15 @@ export function isGoogleLike(provider?: string | Providers): boolean {
24
24
  provider
25
25
  );
26
26
  }
27
+
28
+ /** Returns true for native Anthropic or Bedrock running a Claude model. */
29
+ export function isAnthropicLike(
30
+ provider?: string | Providers,
31
+ clientOptions?: { model?: string }
32
+ ): boolean {
33
+ if (provider === Providers.ANTHROPIC) return true;
34
+ if (provider === Providers.BEDROCK) {
35
+ return /claude/i.test(String(clientOptions?.model ?? ''));
36
+ }
37
+ return false;
38
+ }