@librechat/agents 3.3.10 → 3.3.12

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 (211) hide show
  1. package/dist/cjs/graphs/Graph.cjs +248 -27
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/langfuseToolOutputTracing.cjs +228 -16
  4. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  5. package/dist/cjs/llm/bedrock/index.cjs +13 -2
  6. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  7. package/dist/cjs/llm/init.cjs +1 -1
  8. package/dist/cjs/llm/invoke.cjs +160 -43
  9. package/dist/cjs/llm/invoke.cjs.map +1 -1
  10. package/dist/cjs/llm/openai/index.cjs +190 -13
  11. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  12. package/dist/cjs/llm/streamLimits.cjs +723 -0
  13. package/dist/cjs/llm/streamLimits.cjs.map +1 -0
  14. package/dist/cjs/main.cjs +12 -3
  15. package/dist/cjs/messages/core.cjs +592 -27
  16. package/dist/cjs/messages/core.cjs.map +1 -1
  17. package/dist/cjs/run.cjs +7 -3
  18. package/dist/cjs/run.cjs.map +1 -1
  19. package/dist/cjs/session/AgentSession.cjs +4 -1
  20. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  21. package/dist/cjs/stream.cjs +105 -14
  22. package/dist/cjs/stream.cjs.map +1 -1
  23. package/dist/cjs/summarization/node.cjs +157 -37
  24. package/dist/cjs/summarization/node.cjs.map +1 -1
  25. package/dist/cjs/tools/BashExecutor.cjs +3 -2
  26. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  27. package/dist/cjs/tools/CodeExecutor.cjs +4 -3
  28. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  29. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -3
  30. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  31. package/dist/cjs/tools/ToolNode.cjs +77 -4
  32. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  33. package/dist/cjs/tools/ToolSearch.cjs +3 -2
  34. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  35. package/dist/cjs/tools/search/crw-scraper.cjs +7 -1
  36. package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -1
  37. package/dist/cjs/tools/search/crw-search.cjs +3 -1
  38. package/dist/cjs/tools/search/crw-search.cjs.map +1 -1
  39. package/dist/cjs/tools/search/firecrawl.cjs +7 -1
  40. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  41. package/dist/cjs/tools/search/keenable-scraper.cjs +7 -1
  42. package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -1
  43. package/dist/cjs/tools/search/keenable-search.cjs +3 -1
  44. package/dist/cjs/tools/search/keenable-search.cjs.map +1 -1
  45. package/dist/cjs/tools/search/rerankers.cjs +26 -8
  46. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  47. package/dist/cjs/tools/search/search.cjs +30 -10
  48. package/dist/cjs/tools/search/search.cjs.map +1 -1
  49. package/dist/cjs/tools/search/serper-scraper.cjs +7 -1
  50. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
  51. package/dist/cjs/tools/search/tavily-scraper.cjs +7 -1
  52. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
  53. package/dist/cjs/tools/search/tavily-search.cjs +3 -1
  54. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
  55. package/dist/cjs/tools/search/tool.cjs +17 -3
  56. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  57. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +54 -3
  58. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  59. package/dist/cjs/utils/index.cjs +2 -1
  60. package/dist/cjs/utils/misc.cjs +12 -0
  61. package/dist/cjs/utils/misc.cjs.map +1 -1
  62. package/dist/cjs/utils/proxy.cjs +63 -0
  63. package/dist/cjs/utils/proxy.cjs.map +1 -0
  64. package/dist/esm/graphs/Graph.mjs +247 -26
  65. package/dist/esm/graphs/Graph.mjs.map +1 -1
  66. package/dist/esm/langfuseToolOutputTracing.mjs +228 -16
  67. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  68. package/dist/esm/llm/bedrock/index.mjs +13 -2
  69. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  70. package/dist/esm/llm/init.mjs +1 -1
  71. package/dist/esm/llm/invoke.mjs +160 -43
  72. package/dist/esm/llm/invoke.mjs.map +1 -1
  73. package/dist/esm/llm/openai/index.mjs +192 -15
  74. package/dist/esm/llm/openai/index.mjs.map +1 -1
  75. package/dist/esm/llm/streamLimits.mjs +704 -0
  76. package/dist/esm/llm/streamLimits.mjs.map +1 -0
  77. package/dist/esm/main.mjs +8 -6
  78. package/dist/esm/messages/core.mjs +592 -28
  79. package/dist/esm/messages/core.mjs.map +1 -1
  80. package/dist/esm/run.mjs +7 -3
  81. package/dist/esm/run.mjs.map +1 -1
  82. package/dist/esm/session/AgentSession.mjs +4 -1
  83. package/dist/esm/session/AgentSession.mjs.map +1 -1
  84. package/dist/esm/stream.mjs +105 -14
  85. package/dist/esm/stream.mjs.map +1 -1
  86. package/dist/esm/summarization/node.mjs +157 -37
  87. package/dist/esm/summarization/node.mjs.map +1 -1
  88. package/dist/esm/tools/BashExecutor.mjs +3 -2
  89. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  90. package/dist/esm/tools/CodeExecutor.mjs +4 -3
  91. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  92. package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -3
  93. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  94. package/dist/esm/tools/ToolNode.mjs +77 -4
  95. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  96. package/dist/esm/tools/ToolSearch.mjs +3 -2
  97. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  98. package/dist/esm/tools/search/crw-scraper.mjs +7 -1
  99. package/dist/esm/tools/search/crw-scraper.mjs.map +1 -1
  100. package/dist/esm/tools/search/crw-search.mjs +3 -1
  101. package/dist/esm/tools/search/crw-search.mjs.map +1 -1
  102. package/dist/esm/tools/search/firecrawl.mjs +7 -1
  103. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  104. package/dist/esm/tools/search/keenable-scraper.mjs +7 -1
  105. package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -1
  106. package/dist/esm/tools/search/keenable-search.mjs +3 -1
  107. package/dist/esm/tools/search/keenable-search.mjs.map +1 -1
  108. package/dist/esm/tools/search/rerankers.mjs +26 -8
  109. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  110. package/dist/esm/tools/search/search.mjs +30 -10
  111. package/dist/esm/tools/search/search.mjs.map +1 -1
  112. package/dist/esm/tools/search/serper-scraper.mjs +7 -1
  113. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
  114. package/dist/esm/tools/search/tavily-scraper.mjs +7 -1
  115. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
  116. package/dist/esm/tools/search/tavily-search.mjs +3 -1
  117. package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
  118. package/dist/esm/tools/search/tool.mjs +17 -3
  119. package/dist/esm/tools/search/tool.mjs.map +1 -1
  120. package/dist/esm/tools/subagent/SubagentExecutor.mjs +54 -3
  121. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  122. package/dist/esm/utils/index.mjs +2 -1
  123. package/dist/esm/utils/misc.mjs +12 -1
  124. package/dist/esm/utils/misc.mjs.map +1 -1
  125. package/dist/esm/utils/proxy.mjs +62 -0
  126. package/dist/esm/utils/proxy.mjs.map +1 -0
  127. package/dist/types/graphs/Graph.d.ts +53 -1
  128. package/dist/types/index.d.ts +2 -0
  129. package/dist/types/langfuseToolOutputTracing.d.ts +1 -0
  130. package/dist/types/llm/invoke.d.ts +26 -5
  131. package/dist/types/llm/openai/index.d.ts +3 -0
  132. package/dist/types/llm/streamLimits.d.ts +314 -0
  133. package/dist/types/messages/core.d.ts +11 -6
  134. package/dist/types/run.d.ts +1 -0
  135. package/dist/types/summarization/node.d.ts +27 -2
  136. package/dist/types/tools/BashExecutor.d.ts +2 -2
  137. package/dist/types/tools/CodeExecutor.d.ts +3 -3
  138. package/dist/types/tools/ToolNode.d.ts +11 -1
  139. package/dist/types/tools/search/crw-scraper.d.ts +2 -0
  140. package/dist/types/tools/search/firecrawl.d.ts +2 -0
  141. package/dist/types/tools/search/keenable-scraper.d.ts +2 -0
  142. package/dist/types/tools/search/rerankers.d.ts +9 -5
  143. package/dist/types/tools/search/serper-scraper.d.ts +2 -0
  144. package/dist/types/tools/search/tavily-scraper.d.ts +2 -0
  145. package/dist/types/tools/search/types.d.ts +28 -33
  146. package/dist/types/tools/subagent/SubagentExecutor.d.ts +44 -0
  147. package/dist/types/types/graph.d.ts +7 -1
  148. package/dist/types/types/run.d.ts +46 -1
  149. package/dist/types/types/tools.d.ts +21 -0
  150. package/dist/types/utils/index.d.ts +1 -0
  151. package/dist/types/utils/misc.d.ts +7 -0
  152. package/dist/types/utils/proxy.d.ts +31 -0
  153. package/package.json +2 -1
  154. package/src/__tests__/stream.eagerArgsDivergence.test.ts +158 -0
  155. package/src/__tests__/stream.eagerEventExecution.test.ts +1 -0
  156. package/src/__tests__/stream.streamLimits.test.ts +1982 -0
  157. package/src/graphs/Graph.ts +302 -27
  158. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +234 -0
  159. package/src/index.ts +11 -0
  160. package/src/langfuseToolOutputTracing.ts +410 -14
  161. package/src/llm/bedrock/index.ts +22 -2
  162. package/src/llm/custom-chat-models.smoke.test.ts +747 -0
  163. package/src/llm/invoke.streamLimits.test.ts +142 -0
  164. package/src/llm/invoke.test.ts +187 -1
  165. package/src/llm/invoke.ts +231 -43
  166. package/src/llm/openai/cacheWriteTokens.test.ts +112 -0
  167. package/src/llm/openai/index.ts +348 -31
  168. package/src/llm/openai/llm.spec.ts +107 -6
  169. package/src/llm/streamLimits.test.ts +450 -0
  170. package/src/llm/streamLimits.ts +1158 -0
  171. package/src/messages/core.ts +1290 -42
  172. package/src/messages/formatAgentMessages.test.ts +2623 -0
  173. package/src/run.ts +4 -0
  174. package/src/session/AgentSession.ts +5 -0
  175. package/src/specs/langfuse-tool-output-tracing.test.ts +887 -0
  176. package/src/specs/preemptSeal.test.ts +374 -5
  177. package/src/stream.ts +141 -6
  178. package/src/summarization/__tests__/node.test.ts +269 -0
  179. package/src/summarization/chunkHandler.test.ts +196 -0
  180. package/src/summarization/node.ts +203 -6
  181. package/src/tools/BashExecutor.ts +4 -3
  182. package/src/tools/CodeExecutor.ts +5 -4
  183. package/src/tools/ProgrammaticToolCalling.ts +7 -5
  184. package/src/tools/ToolNode.ts +109 -6
  185. package/src/tools/ToolSearch.ts +4 -3
  186. package/src/tools/__tests__/BashExecutor.test.ts +2 -2
  187. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +2 -4
  188. package/src/tools/__tests__/SubagentExecutor.test.ts +126 -0
  189. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +389 -0
  190. package/src/tools/__tests__/ToolNode.streamLimits.test.ts +69 -0
  191. package/src/tools/search/crw-scraper.ts +6 -0
  192. package/src/tools/search/crw-search.ts +6 -1
  193. package/src/tools/search/firecrawl.ts +6 -0
  194. package/src/tools/search/http-agent.test.ts +133 -0
  195. package/src/tools/search/keenable-scraper.ts +6 -0
  196. package/src/tools/search/keenable-search.ts +6 -1
  197. package/src/tools/search/rerankers.ts +36 -10
  198. package/src/tools/search/search.ts +29 -11
  199. package/src/tools/search/serper-scraper.ts +6 -0
  200. package/src/tools/search/tavily-scraper.ts +6 -0
  201. package/src/tools/search/tavily-search.ts +2 -0
  202. package/src/tools/search/tool.ts +16 -0
  203. package/src/tools/search/types.ts +31 -33
  204. package/src/tools/subagent/SubagentExecutor.ts +96 -3
  205. package/src/types/graph.ts +7 -0
  206. package/src/types/run.ts +49 -1
  207. package/src/types/tools.ts +21 -0
  208. package/src/utils/index.ts +1 -0
  209. package/src/utils/misc.ts +19 -0
  210. package/src/utils/proxy.test.ts +176 -0
  211. package/src/utils/proxy.ts +93 -0
@@ -9,21 +9,25 @@ export declare abstract class BaseReranker {
9
9
  export declare class JinaReranker extends BaseReranker {
10
10
  private apiUrl;
11
11
  private timeout;
12
- constructor({ apiKey, apiUrl, timeout, logger, }: {
12
+ private httpAgent?;
13
+ private httpsAgent?;
14
+ constructor({ apiKey, apiUrl, timeout, logger, httpAgent, httpsAgent, }: {
13
15
  apiKey?: string;
14
16
  apiUrl?: string;
15
17
  timeout?: number;
16
18
  logger?: t.Logger;
17
- });
19
+ } & t.HttpAgentConfig);
18
20
  rerank(query: string, documents: string[], topK?: number): Promise<t.Highlight[]>;
19
21
  }
20
22
  export declare class CohereReranker extends BaseReranker {
21
23
  private timeout;
22
- constructor({ apiKey, timeout, logger, }: {
24
+ private httpAgent?;
25
+ private httpsAgent?;
26
+ constructor({ apiKey, timeout, logger, httpAgent, httpsAgent, }: {
23
27
  apiKey?: string;
24
28
  timeout?: number;
25
29
  logger?: t.Logger;
26
- });
30
+ } & t.HttpAgentConfig);
27
31
  rerank(query: string, documents: string[], topK?: number): Promise<t.Highlight[]>;
28
32
  }
29
33
  export declare class InfinityReranker extends BaseReranker {
@@ -40,4 +44,4 @@ export declare const createReranker: (config: {
40
44
  cohereApiKey?: string;
41
45
  rerankerTimeout?: number;
42
46
  logger?: t.Logger;
43
- }) => BaseReranker | undefined;
47
+ } & t.HttpAgentConfig) => BaseReranker | undefined;
@@ -30,6 +30,8 @@ export declare class SerperScraper implements t.BaseScraper {
30
30
  private timeout;
31
31
  private logger;
32
32
  private includeMarkdown;
33
+ private httpAgent?;
34
+ private httpsAgent?;
33
35
  constructor(config?: t.SerperScraperConfig);
34
36
  /**
35
37
  * Scrape a single URL
@@ -9,6 +9,8 @@ export declare class TavilyScraper implements t.BaseScraper {
9
9
  private includeImages;
10
10
  private includeFavicon;
11
11
  private format;
12
+ private httpAgent?;
13
+ private httpsAgent?;
12
14
  constructor(config?: t.TavilyScraperConfig);
13
15
  scrapeUrl(url: string, options?: t.TavilyScrapeOptions): Promise<[string, t.TavilyScrapeResponse]>;
14
16
  scrapeUrls(urls: string[], options?: t.TavilyScrapeOptions): Promise<Array<[string, t.TavilyScrapeResponse]>>;
@@ -1,7 +1,21 @@
1
1
  import type { RunnableConfig } from '@langchain/core/runnables';
2
2
  import type { Logger as WinstonLogger } from 'winston';
3
+ import type { Agent as HttpsAgent } from 'https';
4
+ import type { Agent as HttpAgent } from 'http';
3
5
  import type { BaseReranker } from './rerankers';
4
6
  import { DATE_RANGE } from './schema';
7
+ export type { HttpAgent, HttpsAgent };
8
+ export interface HttpAgentConfig {
9
+ httpAgent?: HttpAgent;
10
+ httpsAgent?: HttpsAgent;
11
+ }
12
+ /** Transport and credential fields shared by every provider scraper config. */
13
+ export interface BaseSearchProviderConfig extends HttpAgentConfig {
14
+ apiKey?: string;
15
+ apiUrl?: string;
16
+ timeout?: number;
17
+ logger?: Logger;
18
+ }
5
19
  export type SearchProvider = 'serper' | 'searxng' | 'tavily' | 'keenable' | 'crw';
6
20
  export type ScraperProvider = 'firecrawl' | 'serper' | 'tavily' | 'crw' | 'keenable';
7
21
  export type RerankerType = 'infinity' | 'jina' | 'cohere' | 'none';
@@ -58,7 +72,7 @@ export interface Source {
58
72
  }
59
73
  export type TavilyTimeRange = 'day' | 'week' | 'month' | 'year';
60
74
  export type TavilyTimeRangeInput = TavilyTimeRange | 'h' | 'd' | 'w' | 'm' | 'y';
61
- export interface TavilySearchOptions {
75
+ export interface TavilySearchOptions extends HttpAgentConfig {
62
76
  searchDepth?: 'basic' | 'advanced' | 'fast' | 'ultra-fast';
63
77
  maxResults?: number;
64
78
  includeImages?: boolean;
@@ -91,7 +105,7 @@ export interface TavilySearchPayload {
91
105
  include_favicon?: boolean;
92
106
  chunks_per_source?: number;
93
107
  }
94
- export interface CrwSearchOptions {
108
+ export interface CrwSearchOptions extends HttpAgentConfig {
95
109
  /** Max results to request (maps to `limit`; clamped 1..20). */
96
110
  maxResults?: number;
97
111
  /** Add 'images' to the sources array. */
@@ -133,7 +147,7 @@ export interface CrwSearchResponse {
133
147
  error?: string;
134
148
  error_code?: string;
135
149
  }
136
- export interface SearchConfig {
150
+ export interface SearchConfig extends HttpAgentConfig {
137
151
  searchProvider?: SearchProvider;
138
152
  serperApiKey?: string;
139
153
  searxngInstanceUrl?: string;
@@ -149,7 +163,7 @@ export interface SearchConfig {
149
163
  crwApiUrl?: string;
150
164
  crwSearchOptions?: CrwSearchOptions;
151
165
  }
152
- export interface KeenableSearchOptions {
166
+ export interface KeenableSearchOptions extends HttpAgentConfig {
153
167
  maxResults?: number;
154
168
  /** Restrict results to a single domain, e.g. "github.com". */
155
169
  site?: string;
@@ -172,17 +186,14 @@ export interface KeenableSearchResult {
172
186
  export interface KeenableSearchResponse {
173
187
  results?: KeenableSearchResult[];
174
188
  }
175
- export interface KeenableScraperConfig {
176
- apiKey?: string;
189
+ export interface KeenableScraperConfig extends BaseSearchProviderConfig {
177
190
  /** Override the fetch endpoint base (default: public keyless, keyed when a
178
191
  * key is set). Env fallback: KEENABLE_FETCH_URL. */
179
192
  apiUrl?: string;
180
- timeout?: number;
181
- logger?: Logger;
182
193
  /** Sent as the X-Keenable-Title attribution header. Defaults to "LibreChat". */
183
194
  attributionTitle?: string;
184
195
  }
185
- export type KeenableScrapeOptions = Omit<KeenableScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
196
+ export type KeenableScrapeOptions = Omit<KeenableScraperConfig, 'apiKey' | 'apiUrl' | 'logger' | 'httpAgent' | 'httpsAgent'>;
186
197
  /** Raw JSON shape returned by GET /v1/fetch{,/public}?url=... */
187
198
  export interface KeenableFetchResult {
188
199
  url?: string;
@@ -241,18 +252,10 @@ export interface FirecrawlConfig {
241
252
  firecrawlVersion?: string;
242
253
  firecrawlOptions?: FirecrawlScraperConfig;
243
254
  }
244
- export interface SerperScraperConfig {
245
- apiKey?: string;
246
- apiUrl?: string;
247
- timeout?: number;
248
- logger?: Logger;
255
+ export interface SerperScraperConfig extends BaseSearchProviderConfig {
249
256
  includeMarkdown?: boolean;
250
257
  }
251
- export interface TavilyScraperConfig {
252
- apiKey?: string;
253
- apiUrl?: string;
254
- timeout?: number;
255
- logger?: Logger;
258
+ export interface TavilyScraperConfig extends BaseSearchProviderConfig {
256
259
  extractDepth?: 'basic' | 'advanced';
257
260
  includeImages?: boolean;
258
261
  includeFavicon?: boolean;
@@ -341,13 +344,9 @@ export interface BaseScraper {
341
344
  extractMetadata(response: AnyScraperResponse): ScrapeMetadata | GenericScrapeMetadata;
342
345
  }
343
346
  /** Firecrawl */
344
- export type FirecrawlScrapeOptions = Omit<FirecrawlScraperConfig, 'apiKey' | 'apiUrl' | 'version' | 'logger'>;
345
- export interface CrwScraperConfig {
346
- apiKey?: string;
347
- apiUrl?: string;
347
+ export type FirecrawlScrapeOptions = Omit<FirecrawlScraperConfig, 'apiKey' | 'apiUrl' | 'version' | 'logger' | 'httpAgent' | 'httpsAgent'>;
348
+ export interface CrwScraperConfig extends BaseSearchProviderConfig {
348
349
  formats?: string[];
349
- timeout?: number;
350
- logger?: Logger;
351
350
  onlyMainContent?: boolean;
352
351
  includeTags?: string[];
353
352
  excludeTags?: string[];
@@ -359,9 +358,9 @@ export interface CrwScraperConfig {
359
358
  proxy?: string;
360
359
  stealth?: boolean;
361
360
  }
362
- export type CrwScrapeOptions = Omit<CrwScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
363
- export type SerperScrapeOptions = Omit<SerperScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
364
- export type TavilyScrapeOptions = Omit<TavilyScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
361
+ export type CrwScrapeOptions = Omit<CrwScraperConfig, 'apiKey' | 'apiUrl' | 'logger' | 'httpAgent' | 'httpsAgent'>;
362
+ export type SerperScrapeOptions = Omit<SerperScraperConfig, 'apiKey' | 'apiUrl' | 'logger' | 'httpAgent' | 'httpsAgent'>;
363
+ export type TavilyScrapeOptions = Omit<TavilyScraperConfig, 'apiKey' | 'apiUrl' | 'logger' | 'httpAgent' | 'httpsAgent'>;
365
364
  export interface TavilyExtractPayload {
366
365
  urls: string[];
367
366
  extract_depth: NonNullable<TavilyScraperConfig['extractDepth']>;
@@ -499,13 +498,9 @@ export interface TavilyExtractResult {
499
498
  favicon?: string;
500
499
  error?: string;
501
500
  }
502
- export interface FirecrawlScraperConfig {
503
- apiKey?: string;
504
- apiUrl?: string;
501
+ export interface FirecrawlScraperConfig extends BaseSearchProviderConfig {
505
502
  version?: string;
506
503
  formats?: string[];
507
- timeout?: number;
508
- logger?: Logger;
509
504
  includeTags?: string[];
510
505
  excludeTags?: string[];
511
506
  waitFor?: number;
@@ -8,6 +8,14 @@ export type SubagentExecuteParams = {
8
8
  description: string;
9
9
  subagentType: string;
10
10
  threadId?: string;
11
+ /**
12
+ * Breaker controller captured at the parent TOOL BATCH's entry, before
13
+ * PreToolUse hooks. Preferred over the live scope accessor: a graph reset
14
+ * during a hook would otherwise bind this child to the NEW run's
15
+ * controller — reviving it on a fresh signal and letting its trips cancel
16
+ * unrelated work.
17
+ */
18
+ breaker?: AbortController;
11
19
  /**
12
20
  * Parent-side `tool_call_id` of the `subagent` tool invocation that
13
21
  * triggered this execution. Surfaced on {@link SubagentUpdateEvent} so
@@ -62,11 +70,31 @@ export type ChildGraphFactory = (input: StandardGraphInput) => StandardGraph;
62
70
  export type SubagentExecutorOptions = {
63
71
  configs: Map<string, ResolvedSubagentConfig>;
64
72
  parentSignal?: AbortSignal;
73
+ /** Run-scoped breaker abort shared by every executor of one graph, so a
74
+ * child tripping a stream limit stops subagents running under OTHER
75
+ * parallel agent nodes too. An accessor rather than a captured controller:
76
+ * the graph recreates its controller per run, and each execution must
77
+ * bind to the controller current when it STARTS — signal and trip target
78
+ * together, so a straggler from a failed run can neither revive on nor
79
+ * circuit-break a later run's controller. Absent (tests, minimal hosts),
80
+ * the executor falls back to its own private controller. */
81
+ breakerScope?: {
82
+ controller: () => AbortController;
83
+ };
65
84
  hookRegistry?: HookRegistry;
66
85
  parentRunId: string;
67
86
  parentAgentId?: string;
68
87
  langfuse?: StandardGraphInput['langfuse'];
69
88
  tokenCounter?: TokenCounter;
89
+ /**
90
+ * Run-level stream circuit breakers, forwarded into every child graph so
91
+ * a host raising, lowering, or disabling the limits governs subagents too.
92
+ * Child model calls run through `attemptInvoke`'s local stream handler
93
+ * (children have no registered dispatcher), which enforces the child
94
+ * graph's own resolved limits; without this the child would silently
95
+ * revert to the defaults.
96
+ */
97
+ streamLimits?: StandardGraphInput['streamLimits'];
70
98
  /** Remaining nesting budget. 0 or negative blocks execution. */
71
99
  maxDepth?: number;
72
100
  /**
@@ -101,16 +129,32 @@ export type SubagentExecutorOptions = {
101
129
  export declare class SubagentExecutor {
102
130
  private readonly configs;
103
131
  private readonly parentSignal?;
132
+ /** Aborted when a child trips a stream circuit breaker: parallel sibling
133
+ * subagents run concurrently on the parent's signal, and rejecting the
134
+ * batch alone would leave their provider requests streaming after the
135
+ * safety abort. One-way by design — a tripped breaker ends the run, so no
136
+ * later child of this executor should start either. Fallback for hosts
137
+ * that do not supply the graph's shared `breakerScope`. */
138
+ private readonly childRunAbort;
139
+ private readonly breakerScope?;
104
140
  private readonly hookRegistry?;
105
141
  private readonly parentRunId;
106
142
  private readonly parentAgentId?;
107
143
  private readonly langfuse?;
108
144
  private readonly tokenCounter?;
145
+ private readonly streamLimits?;
109
146
  private readonly maxDepth;
110
147
  private readonly createChildGraph;
111
148
  private readonly usageSink?;
112
149
  private readonly resolveParentHandlerRegistry?;
113
150
  constructor(options: SubagentExecutorOptions);
151
+ /** The breaker controller current for this execution — read per spawn
152
+ * because the graph recreates its controller each run. */
153
+ private resolveBreakerController;
154
+ /** One signal that fires on the parent's abort or the breaker abort,
155
+ * collapsed to a single signal when possible (mirrors
156
+ * `composeAbortSignals` in Graph.ts). */
157
+ private composeChildSignal;
114
158
  /** Snapshot of the parent's registry at the moment a subagent is dispatched. */
115
159
  private getParentHandlerRegistry;
116
160
  execute(params: SubagentExecuteParams): Promise<SubagentExecuteResult>;
@@ -7,7 +7,7 @@ import type { GoogleAIToolType } from '@langchain/google-common';
7
7
  import type { SummarizationNodeInput, SummarizeCompleteEvent, SummarizationConfig, SummarizeStartEvent, SummarizeDeltaEvent } from '@/types/summarize';
8
8
  import type { ToolMap, ToolEndEvent, GenericTool, LCTool, ToolExecuteBatchRequest } from '@/types/tools';
9
9
  import type { RunStep, RunStepDeltaEvent, MessageDeltaEvent, ReasoningDeltaEvent } from '@/types/stream';
10
- import type { TokenCounter, StreamPreemption, TokenBudgetBreakdown } from '@/types/run';
10
+ import type { TokenCounter, StreamLimits, StreamPreemption, TokenBudgetBreakdown } from '@/types/run';
11
11
  import type { Providers, Callback, GraphNodeKeys } from '@/common';
12
12
  import type { StandardGraph, MultiAgentGraph } from '@/graphs';
13
13
  import type { ClientOptions } from '@/types/llm';
@@ -250,6 +250,12 @@ export type StandardGraphInput = {
250
250
  * this field.
251
251
  */
252
252
  preemption?: StreamPreemption;
253
+ /**
254
+ * Stream circuit breakers, forwarded from `RunConfig.streamLimits` and
255
+ * resolved once at graph construction. Enforced by the stream handler on
256
+ * every streamed chunk event. See {@link StreamLimits}.
257
+ */
258
+ streamLimits?: StreamLimits;
253
259
  };
254
260
  export type GraphEdge = {
255
261
  /** Agent ID, use a list for multiple sources */
@@ -20,7 +20,11 @@ export type BaseGraphConfig = {
20
20
  };
21
21
  export type LegacyGraphConfig = BaseGraphConfig & {
22
22
  type?: 'standard';
23
- } & Omit<g.StandardGraphInput, 'provider' | 'clientOptions' | 'agents'> & Omit<g.AgentInputs, 'provider' | 'clientOptions' | 'agentId'>;
23
+ } & Omit<g.StandardGraphInput,
24
+ /** `streamLimits` is excluded because legacy graphs receive limits only
25
+ * via the top-level `RunConfig.streamLimits`; accepting the field here
26
+ * would type-check but be silently ignored by `createLegacyGraph`. */
27
+ 'provider' | 'clientOptions' | 'agents' | 'streamLimits'> & Omit<g.AgentInputs, 'provider' | 'clientOptions' | 'agentId'>;
24
28
  export type SupervisedGraphConfig = BaseGraphConfig & {
25
29
  type: 'supervised';
26
30
  /** Enable supervised router; when false, fall back to standard loop */
@@ -158,6 +162,40 @@ export type PreemptStats = {
158
162
  seals: number;
159
163
  emptyBoundaries: number;
160
164
  };
165
+ /**
166
+ * Circuit breakers for pathological model streams. A malformed generation
167
+ * can stream a single tool call's arguments for many minutes while they
168
+ * never become executable (observed live: one 149,923-char SQL argument
169
+ * streamed for 26 minutes before the 64k output-token ceiling ended the
170
+ * run). When a limit trips, the run aborts with a `StreamLimitExceededError`
171
+ * and the in-flight provider request is torn down mid-stream.
172
+ */
173
+ export interface StreamLimits {
174
+ /**
175
+ * Max cumulative UTF-8 bytes a single streamed tool call's arguments may
176
+ * reach before the run is aborted. Defaults to
177
+ * `DEFAULT_MAX_TOOL_CALL_ARG_BYTES` (64 KiB); `0` disables the guard.
178
+ */
179
+ maxToolCallArgBytes?: number;
180
+ /**
181
+ * Per-tool overrides for `maxToolCallArgBytes`, keyed by the model-facing
182
+ * tool name. A matching entry replaces the global cap for that tool's
183
+ * calls (`0` disables the guard for that tool only). Tools that
184
+ * legitimately stream whole documents as arguments (e.g. file creation)
185
+ * can run with a higher cap without loosening every other tool's guard.
186
+ * Calls whose tool name has not yet arrived on the stream use the global
187
+ * cap; providers send the name in the first chunk, so this only matters
188
+ * for malformed streams.
189
+ */
190
+ maxToolCallArgBytesByTool?: Record<string, number>;
191
+ /**
192
+ * Max streamed chunk events a single model generation (turn) may emit
193
+ * before the run is aborted. Defense in depth against looping or
194
+ * duplicated provider streams that a byte limit cannot see (e.g. endless
195
+ * empty chunks). Disabled by default; `0` also disables.
196
+ */
197
+ maxDeltaEventsPerTurn?: number;
198
+ }
161
199
  export type RunConfig = {
162
200
  runId: string;
163
201
  graphConfig: LegacyGraphConfig | StandardGraphConfig | MultiAgentGraphConfig;
@@ -197,6 +235,13 @@ export type RunConfig = {
197
235
  * tool boundary.
198
236
  */
199
237
  preemption?: StreamPreemption;
238
+ /**
239
+ * Circuit breakers for pathological model streams (runaway tool-call
240
+ * argument generation, looping delta streams). Omit for defaults: the
241
+ * tool-call argument byte cap is ON by default, the per-turn event cap is
242
+ * opt-in. See {@link StreamLimits}.
243
+ */
244
+ streamLimits?: StreamLimits;
200
245
  returnContent?: boolean;
201
246
  tokenCounter?: TokenCounter;
202
247
  indexTokenCountMap?: Record<string, number>;
@@ -2,6 +2,7 @@ import type { StructuredToolInterface } from '@langchain/core/tools';
2
2
  import type { RunnableToolLike } from '@langchain/core/runnables';
3
3
  import type { ToolCall } from '@langchain/core/messages/tool';
4
4
  import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
5
+ import type { RunBreakerScope } from '@/llm/streamLimits';
5
6
  import type { MessageContentComplex, ToolErrorData } from './stream';
6
7
  import type { HumanInTheLoopConfig } from './hitl';
7
8
  import type { LangfuseConfig } from './graph';
@@ -249,6 +250,20 @@ export type ToolNodeOptions = {
249
250
  * `resolveLocalExecutionTools`.
250
251
  */
251
252
  fileCheckpointer?: LocalFileCheckpointer;
253
+ /**
254
+ * Returns the owning graph's run-scoped breaker signal. Read once per
255
+ * `run()` invocation and composed into the batch config's `signal`, so
256
+ * every tool execution in the batch aborts when a stream circuit breaker
257
+ * trips elsewhere in the run.
258
+ */
259
+ getBreakerSignal?: () => AbortSignal | undefined;
260
+ /**
261
+ * Returns the owning graph's immutable run scope. Read once per batch,
262
+ * BEFORE hooks, and threaded to tools that spawn runs (subagents) so a
263
+ * reset during a hook cannot rebind their children to a newer run's
264
+ * controller.
265
+ */
266
+ getRunScope?: () => RunBreakerScope;
252
267
  };
253
268
  export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
254
269
  export type ToolEndEvent = {
@@ -490,6 +505,12 @@ export type ToolExecuteBatchRequest = {
490
505
  configurable?: Record<string, unknown>;
491
506
  /** Runtime metadata from RunnableConfig (includes thread_id, run_id, provider, etc.) */
492
507
  metadata?: Record<string, unknown>;
508
+ /**
509
+ * Aborts when the run is cancelled or its stream circuit breaker trips.
510
+ * Handlers SHOULD forward this to their tool executions so in-flight work
511
+ * stops consuming quota once the run is already failing.
512
+ */
513
+ signal?: AbortSignal;
493
514
  /** Promise resolver - handler calls this with ALL results */
494
515
  resolve: (results: ToolExecuteResult[]) => void;
495
516
  /** Promise rejector - handler calls this on fatal error */
@@ -1,6 +1,7 @@
1
1
  export * from './graph';
2
2
  export * from './llm';
3
3
  export * from './misc';
4
+ export * from './proxy';
4
5
  export * from './handlers';
5
6
  export * from './run';
6
7
  export * from './tokens';
@@ -6,3 +6,10 @@ export declare function parseBooleanEnv(value: string | undefined): boolean | un
6
6
  * @returns The unescaped object
7
7
  */
8
8
  export declare function unescapeObject(obj: unknown, key?: string): unknown;
9
+ /**
10
+ * One signal that fires when either input fires. `AbortSignal.any` is skipped
11
+ * when the inputs collapse to a single signal — the composite is a fresh
12
+ * object per call, and the common cases (one channel, or the host reusing the
13
+ * same controller for both) don't need one.
14
+ */
15
+ export declare function composeAbortSignals(a: AbortSignal | undefined, b: AbortSignal | undefined): AbortSignal | undefined;
@@ -0,0 +1,31 @@
1
+ import type { RequestInit } from 'node-fetch';
2
+ type FetchAgent = NonNullable<RequestInit['agent']>;
3
+ /**
4
+ * Whether `targetUrl` is excluded from proxying by a NO_PROXY list.
5
+ *
6
+ * Follows the de facto convention: comma or whitespace separated entries, an
7
+ * optional leading dot meaning "this host and its subdomains", an optional
8
+ * `:port` suffix that must match when present, and `*` meaning "bypass
9
+ * everything".
10
+ */
11
+ export declare function shouldBypassProxy(targetUrl: string, noProxy?: string): boolean;
12
+ /**
13
+ * Build the proxy agent for a `node-fetch` request, or `undefined` when the
14
+ * request should go direct.
15
+ *
16
+ * Two rules that a bare `new HttpsProxyAgent(process.env.PROXY)` gets wrong:
17
+ *
18
+ * - The agent must match the proxy scheme. `HttpsProxyAgent` speaks HTTP
19
+ * CONNECT; handing it a `socks5://` URL produces a request the proxy never
20
+ * answers in that protocol, surfacing as "Proxy connection ended before
21
+ * receiving CONNECT response".
22
+ * - NO_PROXY has to be honoured. Code execution targets an internal endpoint
23
+ * (`http://codeapi:3112`), and forcing that through an external proxy makes
24
+ * the whole feature unusable for anyone who has PROXY set, with no way to
25
+ * opt out.
26
+ *
27
+ * @param targetUrl Absolute URL the request is going to.
28
+ * @param proxyUrl Explicit proxy; falls back to the PROXY environment variable.
29
+ */
30
+ export declare function resolveFetchProxyAgent(targetUrl: string, proxyUrl?: string): FetchAgent | undefined;
31
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.3.10",
3
+ "version": "3.3.12",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -249,6 +249,7 @@
249
249
  "okapibm25": "^1.4.1",
250
250
  "openai": "^6.46.0",
251
251
  "reo-census": "^1.2.9",
252
+ "socks-proxy-agent": "^8.0.5",
252
253
  "uuid": "^11.1.1"
253
254
  },
254
255
  "peerDependencies": {
@@ -38,6 +38,10 @@ import {
38
38
  BEDROCK_CONVERSE_STREAMED_TOOL_CALL_ADAPTER,
39
39
  OPENAI_RESPONSES_STREAMED_TOOL_CALL_ADAPTER,
40
40
  } from '@/tools/streamedToolCallSeals';
41
+ import {
42
+ STREAM_LIMIT_EPOCH_KEY,
43
+ StreamLimitExceededError,
44
+ } from '@/llm/streamLimits';
41
45
  import { GraphEvents, Providers, StepTypes } from '@/common';
42
46
  import { ChatModelStreamHandler } from '@/stream';
43
47
  import { ToolNode } from '@/tools/ToolNode';
@@ -59,6 +63,7 @@ function createGraph(overrides: Partial<StandardGraph> = {}): StandardGraph {
59
63
  configurable: { user_id: 'user_1' },
60
64
  metadata: { run_id: 'run_1' },
61
65
  },
66
+ breakerAbort: new AbortController(),
62
67
  eagerEventToolExecution: { enabled: true },
63
68
  eagerEventToolExecutions: new Map(),
64
69
  eagerEventToolUsageCount: eagerUsageCount,
@@ -489,6 +494,159 @@ describe('eager args divergence (LibreChat#14371)', () => {
489
494
  });
490
495
  });
491
496
 
497
+ it('does not prestart eager tools for stale-epoch events', async () => {
498
+ const graph = createGraph({ breakerEpoch: 5 } as Partial<StandardGraph>);
499
+ const { toolExecuteCalls } = installToolExecuteResponder();
500
+ const handler = new ChatModelStreamHandler();
501
+ const metadata = {
502
+ langgraph_node: 'agent',
503
+ [STREAM_LIMIT_EPOCH_KEY]: 4,
504
+ };
505
+
506
+ /** A final tool-call chunk from a failed run handled after resetValues
507
+ * advanced the epoch: dropped outright, so the dead run's call cannot
508
+ * dispatch a host tool into the run now using the live controller. */
509
+ await handler.handle(
510
+ GraphEvents.CHAT_MODEL_STREAM,
511
+ {
512
+ chunk: {
513
+ content: '',
514
+ tool_calls: [{ id: 'call_1', name: 'db_query', args: { sql: 'SELECT 1;' } }],
515
+ response_metadata: { finish_reason: 'tool_calls' },
516
+ } as unknown as t.StreamChunk,
517
+ },
518
+ metadata,
519
+ graph
520
+ );
521
+
522
+ expect(toolExecuteCalls).toHaveLength(0);
523
+ expect(graph.eagerEventToolExecutions.size).toBe(0);
524
+ });
525
+
526
+ it('drops eager dispatch when the run scope is replaced during tool-call handling', async () => {
527
+ const graph = createGraph({
528
+ runScope: Object.freeze({
529
+ epoch: 1,
530
+ controller: new AbortController(),
531
+ }),
532
+ } as Partial<StandardGraph>);
533
+ /** The handler passes its entry checks, then a full reset lands while
534
+ * handleToolCalls awaits the run-step dispatch. The event now belongs
535
+ * to a dead run; dispatching eagerly would compose the NEW run's live
536
+ * controller and config. */
537
+ const originalDispatch = graph.dispatchRunStep;
538
+ graph.dispatchRunStep = (async (
539
+ stepKey: string,
540
+ details: unknown
541
+ ): Promise<string> => {
542
+ const stepId = await (
543
+ originalDispatch as unknown as (
544
+ stepKey: string,
545
+ details: unknown
546
+ ) => Promise<string>
547
+ )(stepKey, details);
548
+ (graph as { runScope: unknown }).runScope = Object.freeze({
549
+ epoch: 2,
550
+ controller: new AbortController(),
551
+ });
552
+ return stepId;
553
+ }) as typeof graph.dispatchRunStep;
554
+ const { toolExecuteCalls } = installToolExecuteResponder();
555
+ const handler = new ChatModelStreamHandler();
556
+ const metadata = { langgraph_node: 'agent' };
557
+
558
+ await expect(
559
+ handler.handle(
560
+ GraphEvents.CHAT_MODEL_STREAM,
561
+ {
562
+ chunk: {
563
+ content: '',
564
+ tool_calls: [
565
+ { id: 'call_1', name: 'db_query', args: { sql: 'SELECT 1;' } },
566
+ ],
567
+ response_metadata: { finish_reason: 'tool_calls' },
568
+ } as unknown as t.StreamChunk,
569
+ },
570
+ metadata,
571
+ graph
572
+ )
573
+ ).resolves.toBeUndefined();
574
+ expect(toolExecuteCalls).toHaveLength(0);
575
+ });
576
+
577
+ it('does not prestart eager tools when the breaker trips during tool-call handling', async () => {
578
+ const graph = createGraph();
579
+ const trip = new StreamLimitExceededError({
580
+ kind: 'tool_call_args',
581
+ limit: 10,
582
+ observed: 11,
583
+ toolName: 'db_query',
584
+ });
585
+ const originalDispatch = graph.dispatchRunStep;
586
+ graph.dispatchRunStep = (async (
587
+ stepKey: string,
588
+ details: unknown
589
+ ): Promise<string> => {
590
+ const stepId = await (
591
+ originalDispatch as unknown as (
592
+ stepKey: string,
593
+ details: unknown
594
+ ) => Promise<string>
595
+ )(stepKey, details);
596
+ graph.breakerAbort.abort(trip);
597
+ return stepId;
598
+ }) as typeof graph.dispatchRunStep;
599
+ const { toolExecuteCalls } = installToolExecuteResponder();
600
+ const handler = new ChatModelStreamHandler();
601
+ const metadata = { langgraph_node: 'agent' };
602
+
603
+ /** The trip lands while handleToolCalls awaits the run-step dispatch —
604
+ * after the queued-event guard already passed. The recheck before the
605
+ * eager path must stop the prestart. */
606
+ await expect(
607
+ handler.handle(
608
+ GraphEvents.CHAT_MODEL_STREAM,
609
+ {
610
+ chunk: {
611
+ content: '',
612
+ tool_calls: [
613
+ { id: 'call_1', name: 'db_query', args: { sql: 'SELECT 1;' } },
614
+ ],
615
+ response_metadata: { finish_reason: 'tool_calls' },
616
+ } as unknown as t.StreamChunk,
617
+ },
618
+ metadata,
619
+ graph
620
+ )
621
+ ).rejects.toBe(trip);
622
+ expect(toolExecuteCalls).toHaveLength(0);
623
+ });
624
+
625
+ it('sends a breaker-composed abort signal with eager prestart requests', async () => {
626
+ const graph = createGraph();
627
+ const { toolExecuteCalls } = installToolExecuteResponder();
628
+ const handler = new ChatModelStreamHandler();
629
+ const metadata = { langgraph_node: 'agent' };
630
+
631
+ await streamChunks({
632
+ handler,
633
+ graph,
634
+ metadata,
635
+ toolCallChunks: toToolCallChunks('call_1', 'db_query', [
636
+ '{"sql":"SELECT 1;"}',
637
+ ]),
638
+ });
639
+ await streamNextToolIndex({ handler, graph, metadata, callId: 'call_2' });
640
+
641
+ expect(toolExecuteCalls).toHaveLength(1);
642
+ const { signal } = toolExecuteCalls[0];
643
+ expect(signal).toBeDefined();
644
+ expect(signal?.aborted).toBe(false);
645
+
646
+ graph.breakerAbort.abort(new Error('stream limit breach'));
647
+ expect(signal?.aborted).toBe(true);
648
+ });
649
+
492
650
  it('the retry path no longer loops: every round executes normally with canonical args', async () => {
493
651
  const metadata = { langgraph_node: 'agent' };
494
652
  const guardErrors: string[] = [];
@@ -44,6 +44,7 @@ function createGraph(overrides: Partial<StandardGraph> = {}): StandardGraph {
44
44
  configurable: { user_id: 'user_1' },
45
45
  metadata: { run_id: 'run_1' },
46
46
  },
47
+ breakerAbort: new AbortController(),
47
48
  eagerEventToolExecution: { enabled: true },
48
49
  eagerEventToolExecutions: new Map(),
49
50
  eagerEventToolUsageCount: eagerUsageCount,