@librechat/agents 3.3.3 → 3.3.5

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 (274) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +2 -1
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/constants.cjs +21 -0
  4. package/dist/cjs/common/constants.cjs.map +1 -1
  5. package/dist/cjs/events.cjs +10 -1
  6. package/dist/cjs/events.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +793 -111
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -1
  10. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  11. package/dist/cjs/hooks/HookRegistry.cjs +25 -0
  12. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  13. package/dist/cjs/hooks/index.cjs +12 -0
  14. package/dist/cjs/hooks/index.cjs.map +1 -1
  15. package/dist/cjs/hooks/types.cjs +1 -0
  16. package/dist/cjs/hooks/types.cjs.map +1 -1
  17. package/dist/cjs/langfuse.cjs +8 -0
  18. package/dist/cjs/langfuse.cjs.map +1 -1
  19. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -6
  20. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  21. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +38 -13
  22. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  23. package/dist/cjs/llm/google/utils/common.cjs +19 -7
  24. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  25. package/dist/cjs/llm/invoke.cjs +331 -5
  26. package/dist/cjs/llm/invoke.cjs.map +1 -1
  27. package/dist/cjs/llm/openai/index.cjs +41 -5
  28. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  29. package/dist/cjs/llm/openai/utils/index.cjs +25 -4
  30. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  31. package/dist/cjs/llm/openrouter/index.cjs +4 -1
  32. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  33. package/dist/cjs/llm/preempt.cjs +132 -0
  34. package/dist/cjs/llm/preempt.cjs.map +1 -0
  35. package/dist/cjs/main.cjs +36 -5
  36. package/dist/cjs/messages/alternation.cjs +92 -0
  37. package/dist/cjs/messages/alternation.cjs.map +1 -0
  38. package/dist/cjs/messages/cache.cjs +54 -33
  39. package/dist/cjs/messages/cache.cjs.map +1 -1
  40. package/dist/cjs/messages/contextPruning.cjs +17 -43
  41. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  42. package/dist/cjs/messages/core.cjs +315 -23
  43. package/dist/cjs/messages/core.cjs.map +1 -1
  44. package/dist/cjs/messages/format.cjs +290 -60
  45. package/dist/cjs/messages/format.cjs.map +1 -1
  46. package/dist/cjs/messages/handoffCue.cjs +63 -0
  47. package/dist/cjs/messages/handoffCue.cjs.map +1 -0
  48. package/dist/cjs/messages/index.cjs +4 -1
  49. package/dist/cjs/messages/injected.cjs +60 -0
  50. package/dist/cjs/messages/injected.cjs.map +1 -0
  51. package/dist/cjs/messages/prune.cjs +503 -127
  52. package/dist/cjs/messages/prune.cjs.map +1 -1
  53. package/dist/cjs/run.cjs +80 -7
  54. package/dist/cjs/run.cjs.map +1 -1
  55. package/dist/cjs/session/handlers.cjs +18 -9
  56. package/dist/cjs/session/handlers.cjs.map +1 -1
  57. package/dist/cjs/stream.cjs +35 -10
  58. package/dist/cjs/stream.cjs.map +1 -1
  59. package/dist/cjs/summarization/node.cjs +28 -14
  60. package/dist/cjs/summarization/node.cjs.map +1 -1
  61. package/dist/cjs/tools/ToolNode.cjs +56 -63
  62. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  63. package/dist/cjs/tools/search/keenable-scraper.cjs +90 -0
  64. package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -0
  65. package/dist/cjs/tools/search/tool.cjs +9 -1
  66. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  67. package/dist/cjs/tools/toolOutputReferences.cjs +6 -0
  68. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  69. package/dist/cjs/utils/index.cjs +1 -1
  70. package/dist/cjs/utils/llm.cjs +1 -1
  71. package/dist/cjs/utils/llm.cjs.map +1 -1
  72. package/dist/cjs/utils/tokens.cjs +307 -20
  73. package/dist/cjs/utils/tokens.cjs.map +1 -1
  74. package/dist/cjs/utils/toolContent.cjs +1514 -0
  75. package/dist/cjs/utils/toolContent.cjs.map +1 -0
  76. package/dist/esm/agents/AgentContext.mjs +2 -1
  77. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  78. package/dist/esm/common/constants.mjs +19 -1
  79. package/dist/esm/common/constants.mjs.map +1 -1
  80. package/dist/esm/events.mjs +10 -1
  81. package/dist/esm/events.mjs.map +1 -1
  82. package/dist/esm/graphs/Graph.mjs +798 -116
  83. package/dist/esm/graphs/Graph.mjs.map +1 -1
  84. package/dist/esm/graphs/MultiAgentGraph.mjs +3 -1
  85. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  86. package/dist/esm/hooks/HookRegistry.mjs +25 -0
  87. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  88. package/dist/esm/hooks/index.mjs +12 -1
  89. package/dist/esm/hooks/index.mjs.map +1 -1
  90. package/dist/esm/hooks/types.mjs +1 -0
  91. package/dist/esm/hooks/types.mjs.map +1 -1
  92. package/dist/esm/langfuse.mjs +9 -1
  93. package/dist/esm/langfuse.mjs.map +1 -1
  94. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -6
  95. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  96. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +38 -13
  97. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  98. package/dist/esm/llm/google/utils/common.mjs +19 -7
  99. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  100. package/dist/esm/llm/invoke.mjs +332 -8
  101. package/dist/esm/llm/invoke.mjs.map +1 -1
  102. package/dist/esm/llm/openai/index.mjs +41 -5
  103. package/dist/esm/llm/openai/index.mjs.map +1 -1
  104. package/dist/esm/llm/openai/utils/index.mjs +25 -4
  105. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  106. package/dist/esm/llm/openrouter/index.mjs +4 -1
  107. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  108. package/dist/esm/llm/preempt.mjs +131 -0
  109. package/dist/esm/llm/preempt.mjs.map +1 -0
  110. package/dist/esm/main.mjs +14 -11
  111. package/dist/esm/messages/alternation.mjs +91 -0
  112. package/dist/esm/messages/alternation.mjs.map +1 -0
  113. package/dist/esm/messages/cache.mjs +55 -34
  114. package/dist/esm/messages/cache.mjs.map +1 -1
  115. package/dist/esm/messages/contextPruning.mjs +17 -43
  116. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  117. package/dist/esm/messages/core.mjs +305 -24
  118. package/dist/esm/messages/core.mjs.map +1 -1
  119. package/dist/esm/messages/format.mjs +290 -61
  120. package/dist/esm/messages/format.mjs.map +1 -1
  121. package/dist/esm/messages/handoffCue.mjs +61 -0
  122. package/dist/esm/messages/handoffCue.mjs.map +1 -0
  123. package/dist/esm/messages/index.mjs +4 -1
  124. package/dist/esm/messages/injected.mjs +60 -0
  125. package/dist/esm/messages/injected.mjs.map +1 -0
  126. package/dist/esm/messages/prune.mjs +500 -128
  127. package/dist/esm/messages/prune.mjs.map +1 -1
  128. package/dist/esm/run.mjs +80 -7
  129. package/dist/esm/run.mjs.map +1 -1
  130. package/dist/esm/session/handlers.mjs +19 -10
  131. package/dist/esm/session/handlers.mjs.map +1 -1
  132. package/dist/esm/stream.mjs +34 -11
  133. package/dist/esm/stream.mjs.map +1 -1
  134. package/dist/esm/summarization/node.mjs +28 -14
  135. package/dist/esm/summarization/node.mjs.map +1 -1
  136. package/dist/esm/tools/ToolNode.mjs +56 -63
  137. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  138. package/dist/esm/tools/search/keenable-scraper.mjs +88 -0
  139. package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -0
  140. package/dist/esm/tools/search/tool.mjs +9 -1
  141. package/dist/esm/tools/search/tool.mjs.map +1 -1
  142. package/dist/esm/tools/toolOutputReferences.mjs +6 -0
  143. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  144. package/dist/esm/utils/index.mjs +1 -1
  145. package/dist/esm/utils/llm.mjs +1 -1
  146. package/dist/esm/utils/llm.mjs.map +1 -1
  147. package/dist/esm/utils/tokens.mjs +307 -21
  148. package/dist/esm/utils/tokens.mjs.map +1 -1
  149. package/dist/esm/utils/toolContent.mjs +1503 -0
  150. package/dist/esm/utils/toolContent.mjs.map +1 -0
  151. package/dist/types/common/constants.d.ts +18 -0
  152. package/dist/types/graphs/Graph.d.ts +139 -2
  153. package/dist/types/hooks/HookRegistry.d.ts +15 -0
  154. package/dist/types/hooks/index.d.ts +12 -1
  155. package/dist/types/hooks/types.d.ts +45 -6
  156. package/dist/types/index.d.ts +1 -1
  157. package/dist/types/llm/invoke.d.ts +34 -1
  158. package/dist/types/llm/openai/index.d.ts +4 -0
  159. package/dist/types/llm/openai/utils/index.d.ts +2 -0
  160. package/dist/types/llm/openrouter/index.d.ts +5 -0
  161. package/dist/types/llm/preempt.d.ts +40 -0
  162. package/dist/types/messages/alternation.d.ts +23 -0
  163. package/dist/types/messages/contextPruning.d.ts +2 -1
  164. package/dist/types/messages/core.d.ts +36 -0
  165. package/dist/types/messages/format.d.ts +6 -0
  166. package/dist/types/messages/handoffCue.d.ts +40 -0
  167. package/dist/types/messages/index.d.ts +3 -0
  168. package/dist/types/messages/injected.d.ts +3 -0
  169. package/dist/types/messages/prune.d.ts +16 -12
  170. package/dist/types/run.d.ts +7 -0
  171. package/dist/types/stream.d.ts +14 -0
  172. package/dist/types/tools/ToolNode.d.ts +0 -7
  173. package/dist/types/tools/search/keenable-scraper.d.ts +15 -0
  174. package/dist/types/tools/search/types.d.ts +31 -2
  175. package/dist/types/types/graph.d.ts +8 -1
  176. package/dist/types/types/run.d.ts +65 -0
  177. package/dist/types/types/stream.d.ts +1 -26
  178. package/dist/types/utils/tokens.d.ts +7 -0
  179. package/dist/types/utils/toolContent.d.ts +107 -0
  180. package/package.json +1 -1
  181. package/src/__tests__/stream.eagerEventExecution.test.ts +74 -0
  182. package/src/agents/AgentContext.ts +1 -0
  183. package/src/{splitStream.test.ts → aggregator.test.ts} +59 -666
  184. package/src/common/constants.ts +21 -0
  185. package/src/events.ts +15 -1
  186. package/src/graphs/Graph.ts +1261 -198
  187. package/src/graphs/MultiAgentGraph.ts +6 -1
  188. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1139 -4
  189. package/src/graphs/__tests__/Graph.preemptSignal.test.ts +126 -0
  190. package/src/hooks/HookRegistry.ts +40 -0
  191. package/src/hooks/__tests__/preemptBoundary.test.ts +152 -0
  192. package/src/hooks/index.ts +16 -2
  193. package/src/hooks/types.ts +47 -3
  194. package/src/index.ts +1 -1
  195. package/src/langfuse.ts +26 -1
  196. package/src/llm/anthropic/utils/message_inputs.ts +158 -12
  197. package/src/llm/anthropic/utils/tool-id-normalization.test.ts +109 -0
  198. package/src/llm/bedrock/utils/message_inputs.test.ts +131 -1
  199. package/src/llm/bedrock/utils/message_inputs.ts +100 -24
  200. package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +15 -0
  201. package/src/llm/google/utils/common.test.ts +75 -9
  202. package/src/llm/google/utils/common.ts +34 -3
  203. package/src/llm/invoke.alternation.test.ts +87 -0
  204. package/src/llm/invoke.handoffCue.test.ts +112 -0
  205. package/src/llm/invoke.test.ts +488 -0
  206. package/src/llm/invoke.ts +611 -8
  207. package/src/llm/openai/index.ts +106 -6
  208. package/src/llm/openai/utils/index.ts +109 -53
  209. package/src/llm/openai/utils/messages.test.ts +330 -1
  210. package/src/llm/openrouter/index.ts +12 -2
  211. package/src/llm/openrouter/reasoning.test.ts +314 -0
  212. package/src/llm/preempt.test.ts +323 -0
  213. package/src/llm/preempt.ts +178 -0
  214. package/src/messages/__tests__/observationMasking.test.ts +93 -2
  215. package/src/messages/alternation.test.ts +212 -0
  216. package/src/messages/alternation.ts +112 -0
  217. package/src/messages/cache.tail.test.ts +193 -0
  218. package/src/messages/cache.test.ts +113 -0
  219. package/src/messages/cache.ts +92 -60
  220. package/src/messages/contextPruning.test.ts +184 -0
  221. package/src/messages/contextPruning.ts +49 -42
  222. package/src/messages/core.ts +653 -40
  223. package/src/messages/ensureThinkingBlock.test.ts +49 -1
  224. package/src/messages/foldToollessToolBlocks.test.ts +163 -5
  225. package/src/messages/format.ts +454 -91
  226. package/src/messages/formatAgentMessages.steer.test.ts +267 -0
  227. package/src/messages/formatAgentMessages.test.ts +604 -14
  228. package/src/messages/formatAgentMessages.tools.test.ts +103 -0
  229. package/src/messages/handoffCue.test.ts +96 -0
  230. package/src/messages/handoffCue.ts +78 -0
  231. package/src/messages/index.ts +3 -0
  232. package/src/messages/injected.test.ts +90 -0
  233. package/src/messages/injected.ts +74 -0
  234. package/src/messages/prune.ts +996 -183
  235. package/src/run.ts +91 -6
  236. package/src/scripts/preempt-probe.ts +330 -0
  237. package/src/scripts/preempt-scenarios.ts +388 -0
  238. package/src/session/handlers.ts +32 -12
  239. package/src/specs/handoffCue.test.ts +165 -0
  240. package/src/specs/langfuse-callbacks.test.ts +352 -2
  241. package/src/specs/preemptSeal.test.ts +309 -0
  242. package/src/specs/prune.test.ts +1083 -6
  243. package/src/specs/summarization-unit.test.ts +105 -0
  244. package/src/specs/tokens.test.ts +609 -32
  245. package/src/stream.dispatch.test.ts +63 -0
  246. package/src/stream.ts +59 -23
  247. package/src/summarization/__tests__/node.test.ts +77 -0
  248. package/src/summarization/node.ts +46 -18
  249. package/src/tools/ToolNode.ts +150 -99
  250. package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +148 -1
  251. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +112 -3
  252. package/src/tools/__tests__/annotateMessagesForLLM.test.ts +32 -0
  253. package/src/tools/__tests__/directToolHooks.test.ts +255 -7
  254. package/src/tools/search/keenable-scraper.test.ts +153 -0
  255. package/src/tools/search/keenable-scraper.ts +137 -0
  256. package/src/tools/search/tool.ts +13 -2
  257. package/src/tools/search/types.ts +50 -3
  258. package/src/tools/toolOutputReferences.ts +6 -0
  259. package/src/types/graph.ts +12 -1
  260. package/src/types/run.ts +67 -0
  261. package/src/types/stream.ts +1 -42
  262. package/src/utils/llm.test.ts +18 -0
  263. package/src/utils/llm.ts +4 -1
  264. package/src/utils/tokens.ts +586 -31
  265. package/src/utils/toolContent.test.ts +1432 -0
  266. package/src/utils/toolContent.ts +2707 -0
  267. package/dist/cjs/splitStream.cjs +0 -151
  268. package/dist/cjs/splitStream.cjs.map +0 -1
  269. package/dist/esm/splitStream.mjs +0 -150
  270. package/dist/esm/splitStream.mjs.map +0 -1
  271. package/dist/types/mockStream.d.ts +0 -32
  272. package/dist/types/splitStream.d.ts +0 -37
  273. package/src/mockStream.ts +0 -99
  274. package/src/splitStream.ts +0 -234
@@ -101,6 +101,63 @@ export type StandardGraphConfig = Omit<MultiAgentGraphConfig, 'edges' | 'type'>
101
101
  type?: 'standard';
102
102
  signal?: AbortSignal;
103
103
  };
104
+ /**
105
+ * Cooperative mid-generation preemption. Lets a host seal the live model
106
+ * stream at the next provider-safe token boundary — the run is never
107
+ * aborted, the partial assistant turn is kept, and the graph self-loops
108
+ * into a fresh model call once the `PreemptBoundary` hook has injected
109
+ * whatever the host queued.
110
+ *
111
+ * Preconditions the host MUST satisfy:
112
+ * - `shouldPreempt` is polled once per streamed chunk on the top-level
113
+ * graph. It must be synchronous, allocation-free and O(1) — never I/O.
114
+ * It must also be LEVEL-TRIGGERED (non-consuming): the SDK never clears
115
+ * the host's request, and a true result is only honored once the
116
+ * accumulated chunk is provider-safe, so the predicate may be polled
117
+ * many times before a seal. A one-shot read that clears its own pending
118
+ * flag would silently lose the request on an unsafe chunk (leading
119
+ * whitespace/reasoning, an in-flight tool call) — keep returning true
120
+ * until the `PreemptBoundary` drain hands over the queued injection,
121
+ * then disarm there.
122
+ * - Sealing is only honored on the SDK's own dispatch loop. A run whose
123
+ * registered `CHAT_MODEL_STREAM` handler IS the SDK dispatcher — or wraps
124
+ * it, which `composeEventHandlers` and `createRunHandlers` both do —
125
+ * consumes chunks through a decoupled `streamEvents` reader that can lag
126
+ * the accumulated chunk, so those runs never seal.
127
+ *
128
+ * Detection is by capability, not identity: the dispatcher carries
129
+ * `SDK_STREAM_DISPATCH` and the SDK's wrappers propagate it. A handler
130
+ * that merely OBSERVES the raw chunk echo — LibreChat's no-op
131
+ * `OpenAIChatModelStreamHandler`, say — is unbranded and does NOT disable
132
+ * sealing, because it assigns no content-part indices and so cannot be
133
+ * inverted. A host that renders from the raw feed and wants the opt-out
134
+ * should brand its handler with `SDK_STREAM_DISPATCH`.
135
+ * - `RunConfig.tokenCounter` should be set. A sealed turn ends before most
136
+ * providers send their usage chunk, so the synthetic `CHAT_MODEL_END`
137
+ * falls back to the counter to report `output_tokens`. Without one that
138
+ * fallback silently no-ops and the sealed turn's usage is lost — verified
139
+ * live: OpenAI, Azure OpenAI and DeepSeek report no usage for the sealed
140
+ * segment without a counter, while Anthropic streams usage incrementally
141
+ * and reports it either way.
142
+ */
143
+ export interface StreamPreemption {
144
+ /**
145
+ * Polled once per streamed chunk. Synchronous, allocation-free, O(1), and
146
+ * level-triggered — keep returning true until the `PreemptBoundary` drain
147
+ * consumes the request; a self-clearing read loses it on an unsafe chunk.
148
+ */
149
+ shouldPreempt: () => boolean;
150
+ /**
151
+ * Max cooperative seals per run. Each seal costs one extra superstep, so
152
+ * this also bounds the recursion-limit headroom the run reserves.
153
+ */
154
+ maxSeals?: number;
155
+ }
156
+ /** Seals honored and boundaries that had nothing to inject, per run. */
157
+ export type PreemptStats = {
158
+ seals: number;
159
+ emptyBoundaries: number;
160
+ };
104
161
  export type RunConfig = {
105
162
  runId: string;
106
163
  graphConfig: LegacyGraphConfig | StandardGraphConfig | MultiAgentGraphConfig;
@@ -132,6 +189,14 @@ export type RunConfig = {
132
189
  * block to prevent leaks.
133
190
  */
134
191
  hooks?: HookRegistry;
192
+ /**
193
+ * Opt-in cooperative preemption for this run. Requires a `hooks` registry
194
+ * with a `PreemptBoundary` matcher — the seal only stops the stream, the
195
+ * hook is what supplies the messages to resume with. Omit to keep the
196
+ * pre-preemption behavior, where a mid-run injection can only land at a
197
+ * tool boundary.
198
+ */
199
+ preemption?: StreamPreemption;
135
200
  returnContent?: boolean;
136
201
  tokenCounter?: TokenCounter;
137
202
  indexTokenCountMap?: Record<string, number>;
@@ -2,7 +2,6 @@ import type { MessageContentImageUrl, MessageContentText, ToolMessage, BaseMessa
2
2
  import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
3
3
  import type { LLMResult, Generation } from '@langchain/core/outputs';
4
4
  import type { Command } from '@langchain/langgraph';
5
- import type OpenAITypes from 'openai';
6
5
  import type { AnthropicContentBlock } from '@/llm/anthropic/types';
7
6
  import type { SummarizeCompleteEvent } from '@/types/summarize';
8
7
  import type { ToolEndEvent } from '@/types/tools';
@@ -265,7 +264,7 @@ export type ToolCallPart = {
265
264
  /** If provided, an identifier associated with the tool call */
266
265
  id?: string;
267
266
  /** If provided, the output of the tool call */
268
- output?: string;
267
+ output?: ToolResultContent['content'];
269
268
  /** Auth URL */
270
269
  auth?: string;
271
270
  /** Expiration time */
@@ -297,30 +296,6 @@ export interface TMessage {
297
296
  [key: string]: unknown;
298
297
  }
299
298
  export type TPayload = Array<Partial<TMessage>>;
300
- export type CustomChunkDelta = null | undefined | (Partial<OpenAITypes.Chat.Completions.ChatCompletionChunk.Choice.Delta> & {
301
- reasoning?: string | null;
302
- reasoning_content?: string | null;
303
- });
304
- export type CustomChunkChoice = Partial<Omit<OpenAITypes.Chat.Completions.ChatCompletionChunk.Choice, 'delta'> & {
305
- delta?: CustomChunkDelta;
306
- }>;
307
- export type CustomChunk = Partial<OpenAITypes.ChatCompletionChunk> & {
308
- choices?: Partial<Array<CustomChunkChoice>>;
309
- };
310
- export type SplitStreamHandlers = Partial<{
311
- [GraphEvents.ON_RUN_STEP]: ({ event, data, }: {
312
- event: GraphEvents;
313
- data: RunStep;
314
- }) => void;
315
- [GraphEvents.ON_MESSAGE_DELTA]: ({ event, data, }: {
316
- event: GraphEvents;
317
- data: MessageDeltaEvent;
318
- }) => void;
319
- [GraphEvents.ON_REASONING_DELTA]: ({ event, data, }: {
320
- event: GraphEvents;
321
- data: ReasoningDeltaEvent;
322
- }) => void;
323
- }>;
324
299
  export type SummarizeDeltaData = {
325
300
  id: string;
326
301
  delta: {
@@ -44,6 +44,13 @@ export declare function estimateDocumentBlockTokens(block: Record<string, unknow
44
44
  */
45
45
  export declare function estimateTimedMediaBlockTokens(block: Record<string, unknown>): number;
46
46
  export declare function encodingForModel(model: string): EncodingName;
47
+ /**
48
+ * Detects values whose native JSON representation can differ after local
49
+ * measurement. Accessor properties and `toJSON` hooks are deliberately not
50
+ * invoked; values too large to inspect within bounded work are treated as
51
+ * unsafe so the caller can normalize or reject them conservatively.
52
+ */
53
+ export declare function hasUnsafeStructuredSerialization(value: unknown): boolean;
47
54
  export declare function getTokenCountForMessage(message: BaseMessage, getTokenCount: (text: string) => number, encoding?: EncodingName): number;
48
55
  /**
49
56
  * Largest-remainder apportionment: scales each count by `multiplier` and
@@ -0,0 +1,107 @@
1
+ import { ToolMessage, type BaseMessage } from '@langchain/core/messages';
2
+ type ToolContent = BaseMessage['content'];
3
+ /**
4
+ * Serializes values that providers render as JSON without throwing on values
5
+ * such as bigint or circular diagnostic metadata.
6
+ */
7
+ export declare function serializeStructuredValue(value: unknown): string;
8
+ type StructuredWorkState = {
9
+ remaining: number;
10
+ characterRemaining: number;
11
+ exceeded: boolean;
12
+ failurePlaceholder?: string;
13
+ propertyCache: WeakMap<object, Map<string, unknown>>;
14
+ propertyCacheEntries: number;
15
+ };
16
+ export type BoundedStructuredSerialization = {
17
+ /** Provider-facing head/tail preview, bounded by `maxChars`. */
18
+ content: string;
19
+ /** Exact serialized prefix up to the defensive traversal-work ceiling. */
20
+ prefix: string;
21
+ /** Exact length, or `Number.MAX_SAFE_INTEGER` when traversal was capped. */
22
+ originalChars: number;
23
+ truncated: boolean;
24
+ };
25
+ /**
26
+ * Serializes structured output while retaining at most the requested provider
27
+ * preview and registry prefix. The traversal never calls `toJSON` and never
28
+ * materializes the complete serialized string when it exceeds those limits.
29
+ *
30
+ * `prefixChars` is useful for the tool-output registry: it receives the exact
31
+ * serialized prefix up to `registry.perOutputLimit`, while `content` retains
32
+ * the provider-facing head/tail truncation behavior at `maxChars`. Property
33
+ * accessors are represented by a fixed placeholder instead of being invoked.
34
+ */
35
+ export declare function serializeStructuredValueBounded(value: unknown, maxChars: number, prefixChars?: number, work?: StructuredWorkState): BoundedStructuredSerialization;
36
+ export declare function isAtomicToolContentBlock(value: unknown): boolean;
37
+ /**
38
+ * Produces the text representation providers use for structured tool results.
39
+ * Text-only block arrays stay readable; opaque blocks use canonical JSON.
40
+ */
41
+ export declare function serializeToolContent(content: unknown): string;
42
+ export declare function getToolContentCharLength(content: unknown): number;
43
+ /**
44
+ * Produces provider-facing tool-result text without ever exceeding `maxChars`.
45
+ */
46
+ export declare function serializeToolContentBounded(content: unknown, maxChars: number): string;
47
+ export type CompactToolContentResult = {
48
+ content: ToolContent;
49
+ changed: boolean;
50
+ originalChars: number;
51
+ };
52
+ export type CacheControlledTextToolContent = [
53
+ {
54
+ type: 'text';
55
+ text: string;
56
+ cache_control: {
57
+ type: 'ephemeral';
58
+ ttl?: '1h';
59
+ };
60
+ }
61
+ ];
62
+ /** Reads one safe text block and returns its bounded text payload. */
63
+ export declare function getBoundedSingleTextToolContent(content: unknown, maxChars: number): string | undefined;
64
+ /**
65
+ * Canonicalizes the single cache-decorated text block produced by tail prompt
66
+ * caching without invoking accessors or custom serialization hooks.
67
+ */
68
+ export declare function getBoundedCacheControlledTextToolContent(content: unknown, maxChars: number): CacheControlledTextToolContent | undefined;
69
+ export type ComputerCallOutputScreenshot = {
70
+ type: 'input_image';
71
+ image_url: string;
72
+ detail?: InputImageDetail;
73
+ } | {
74
+ type: 'input_image';
75
+ file_id: string;
76
+ detail?: InputImageDetail;
77
+ } | {
78
+ type: 'computer_screenshot';
79
+ image_url: string;
80
+ } | {
81
+ type: 'computer_screenshot';
82
+ file_id: string;
83
+ };
84
+ type InputImageDetail = 'low' | 'high' | 'auto' | 'original';
85
+ /**
86
+ * Selects the exact screenshot shape accepted by LangChain's Responses
87
+ * converter without invoking accessors or user-defined serialization hooks.
88
+ */
89
+ export declare function getComputerCallOutputScreenshot(content: unknown): ComputerCallOutputScreenshot | undefined;
90
+ /** Returns the provider image URL when a screenshot is URL-backed. */
91
+ export declare function getComputerCallOutputImageUrl(content: unknown): string | undefined;
92
+ /** Returns whether content can be sent as a native computer screenshot. */
93
+ export declare function isComputerCallOutputContent(content: unknown): content is ToolContent;
94
+ /** Returns whether a ToolMessage carries the native computer-output marker. */
95
+ export declare function hasComputerCallOutputMarker(message: BaseMessage): boolean;
96
+ /** Native Responses computer screenshots are indivisible provider payloads. */
97
+ export declare function isComputerCallOutputMessage(message: BaseMessage): boolean;
98
+ /**
99
+ * Bounds structured tool output without slicing binary/media payloads.
100
+ * Purely textual/structured arrays become a universally valid string tool
101
+ * result. For mixed-media arrays, compactable blocks become one text preview
102
+ * while atomic blocks remain intact.
103
+ */
104
+ export declare function compactToolContent(content: unknown, maxChars: number): CompactToolContentResult;
105
+ /** Clones a ToolMessage while retaining fields omitted by ad-hoc clones. */
106
+ export declare function cloneToolMessageWithContent(message: ToolMessage, content: ToolContent, artifact?: unknown): ToolMessage;
107
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.3.3",
3
+ "version": "3.3.5",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -2498,6 +2498,80 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
2498
2498
  ).toBe(true);
2499
2499
  });
2500
2500
 
2501
+ it('serializes bigint output before eager completion dispatch', async () => {
2502
+ const graph = createGraph();
2503
+ const completedEvents: Array<{ result: t.ToolEndEvent }> = [];
2504
+ jest
2505
+ .spyOn(events, 'safeDispatchCustomEvent')
2506
+ .mockImplementation(async (event, data): Promise<void> => {
2507
+ if (event === GraphEvents.ON_RUN_STEP_COMPLETED) {
2508
+ completedEvents.push(data as { result: t.ToolEndEvent });
2509
+ return;
2510
+ }
2511
+ if (event !== GraphEvents.ON_TOOL_EXECUTE) {
2512
+ return;
2513
+ }
2514
+ const batch = data as t.ToolExecuteBatchRequest;
2515
+ batch.resolve([
2516
+ {
2517
+ toolCallId: 'call_weather',
2518
+ status: 'success',
2519
+ content: [{ rowsRead: BigInt(42) }],
2520
+ },
2521
+ ]);
2522
+ });
2523
+
2524
+ const handler = new ChatModelStreamHandler();
2525
+ const metadata = { langgraph_node: 'agent' };
2526
+ await handler.handle(
2527
+ GraphEvents.CHAT_MODEL_STREAM,
2528
+ {
2529
+ chunk: {
2530
+ content: '',
2531
+ tool_call_chunks: [
2532
+ {
2533
+ id: 'call_weather',
2534
+ name: 'weather',
2535
+ args: '{"city":"NYC"}',
2536
+ index: 0,
2537
+ },
2538
+ ],
2539
+ } as unknown as t.StreamChunk,
2540
+ },
2541
+ metadata,
2542
+ graph
2543
+ );
2544
+ await handler.handle(
2545
+ GraphEvents.CHAT_MODEL_STREAM,
2546
+ {
2547
+ chunk: {
2548
+ content: '',
2549
+ tool_call_chunks: [
2550
+ {
2551
+ id: 'call_stock',
2552
+ name: 'stock',
2553
+ args: '{"ticker":"C',
2554
+ index: 1,
2555
+ },
2556
+ ],
2557
+ } as unknown as t.StreamChunk,
2558
+ },
2559
+ metadata,
2560
+ graph
2561
+ );
2562
+
2563
+ await graph.eagerEventToolExecutions.get('call_weather')?.promise;
2564
+
2565
+ expect(completedEvents).toHaveLength(1);
2566
+ expect(
2567
+ (
2568
+ completedEvents[0].result as {
2569
+ tool_call?: { output?: string };
2570
+ }
2571
+ ).tool_call?.output
2572
+ ).toBe('[{"rowsRead":"42"}]');
2573
+ });
2574
+
2501
2575
  it('does not emit a stale eager completion after the active execution is invalidated', async () => {
2502
2576
  const graph = createGraph({
2503
2577
  getAgentContext: jest.fn(
@@ -1638,6 +1638,7 @@ export class AgentContext {
1638
1638
  provider: this.provider,
1639
1639
  tokenCounter,
1640
1640
  maxTokens: this.maxContextTokens,
1641
+ maxToolResultChars: this.maxToolResultChars,
1641
1642
  thinkingEnabled: isThinkingEnabled(this.provider, this.clientOptions),
1642
1643
  indexTokenCountMap,
1643
1644
  contextPruningConfig: this.contextPruningConfig,