@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.
- package/dist/cjs/graphs/Graph.cjs +248 -27
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +228 -16
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +13 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +160 -43
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +190 -13
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/streamLimits.cjs +723 -0
- package/dist/cjs/llm/streamLimits.cjs.map +1 -0
- package/dist/cjs/main.cjs +12 -3
- package/dist/cjs/messages/core.cjs +592 -27
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/run.cjs +7 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +4 -1
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/stream.cjs +105 -14
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +157 -37
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +3 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +4 -3
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -3
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +77 -4
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +3 -2
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-scraper.cjs +7 -1
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/crw-search.cjs +3 -1
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -1
- package/dist/cjs/tools/search/firecrawl.cjs +7 -1
- package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs +7 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-search.cjs +3 -1
- package/dist/cjs/tools/search/keenable-search.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +26 -8
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +30 -10
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/serper-scraper.cjs +7 -1
- package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs +7 -1
- package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
- package/dist/cjs/tools/search/tavily-search.cjs +3 -1
- package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +17 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +54 -3
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +2 -1
- package/dist/cjs/utils/misc.cjs +12 -0
- package/dist/cjs/utils/misc.cjs.map +1 -1
- package/dist/cjs/utils/proxy.cjs +63 -0
- package/dist/cjs/utils/proxy.cjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +247 -26
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +228 -16
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +13 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +160 -43
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +192 -15
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/streamLimits.mjs +704 -0
- package/dist/esm/llm/streamLimits.mjs.map +1 -0
- package/dist/esm/main.mjs +8 -6
- package/dist/esm/messages/core.mjs +592 -28
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/run.mjs +7 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +4 -1
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/stream.mjs +105 -14
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +157 -37
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -2
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +4 -3
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -3
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +77 -4
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +3 -2
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/search/crw-scraper.mjs +7 -1
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/crw-search.mjs +3 -1
- package/dist/esm/tools/search/crw-search.mjs.map +1 -1
- package/dist/esm/tools/search/firecrawl.mjs +7 -1
- package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-scraper.mjs +7 -1
- package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-search.mjs +3 -1
- package/dist/esm/tools/search/keenable-search.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +26 -8
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +30 -10
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/serper-scraper.mjs +7 -1
- package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-scraper.mjs +7 -1
- package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
- package/dist/esm/tools/search/tavily-search.mjs +3 -1
- package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +17 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +54 -3
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +2 -1
- package/dist/esm/utils/misc.mjs +12 -1
- package/dist/esm/utils/misc.mjs.map +1 -1
- package/dist/esm/utils/proxy.mjs +62 -0
- package/dist/esm/utils/proxy.mjs.map +1 -0
- package/dist/types/graphs/Graph.d.ts +53 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/langfuseToolOutputTracing.d.ts +1 -0
- package/dist/types/llm/invoke.d.ts +26 -5
- package/dist/types/llm/openai/index.d.ts +3 -0
- package/dist/types/llm/streamLimits.d.ts +314 -0
- package/dist/types/messages/core.d.ts +11 -6
- package/dist/types/run.d.ts +1 -0
- package/dist/types/summarization/node.d.ts +27 -2
- package/dist/types/tools/BashExecutor.d.ts +2 -2
- package/dist/types/tools/CodeExecutor.d.ts +3 -3
- package/dist/types/tools/ToolNode.d.ts +11 -1
- package/dist/types/tools/search/crw-scraper.d.ts +2 -0
- package/dist/types/tools/search/firecrawl.d.ts +2 -0
- package/dist/types/tools/search/keenable-scraper.d.ts +2 -0
- package/dist/types/tools/search/rerankers.d.ts +9 -5
- package/dist/types/tools/search/serper-scraper.d.ts +2 -0
- package/dist/types/tools/search/tavily-scraper.d.ts +2 -0
- package/dist/types/tools/search/types.d.ts +28 -33
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +44 -0
- package/dist/types/types/graph.d.ts +7 -1
- package/dist/types/types/run.d.ts +46 -1
- package/dist/types/types/tools.d.ts +21 -0
- package/dist/types/utils/index.d.ts +1 -0
- package/dist/types/utils/misc.d.ts +7 -0
- package/dist/types/utils/proxy.d.ts +31 -0
- package/package.json +2 -1
- package/src/__tests__/stream.eagerArgsDivergence.test.ts +158 -0
- package/src/__tests__/stream.eagerEventExecution.test.ts +1 -0
- package/src/__tests__/stream.streamLimits.test.ts +1982 -0
- package/src/graphs/Graph.ts +302 -27
- package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +234 -0
- package/src/index.ts +11 -0
- package/src/langfuseToolOutputTracing.ts +410 -14
- package/src/llm/bedrock/index.ts +22 -2
- package/src/llm/custom-chat-models.smoke.test.ts +747 -0
- package/src/llm/invoke.streamLimits.test.ts +142 -0
- package/src/llm/invoke.test.ts +187 -1
- package/src/llm/invoke.ts +231 -43
- package/src/llm/openai/cacheWriteTokens.test.ts +112 -0
- package/src/llm/openai/index.ts +348 -31
- package/src/llm/openai/llm.spec.ts +107 -6
- package/src/llm/streamLimits.test.ts +450 -0
- package/src/llm/streamLimits.ts +1158 -0
- package/src/messages/core.ts +1290 -42
- package/src/messages/formatAgentMessages.test.ts +2623 -0
- package/src/run.ts +4 -0
- package/src/session/AgentSession.ts +5 -0
- package/src/specs/langfuse-tool-output-tracing.test.ts +887 -0
- package/src/specs/preemptSeal.test.ts +374 -5
- package/src/stream.ts +141 -6
- package/src/summarization/__tests__/node.test.ts +269 -0
- package/src/summarization/chunkHandler.test.ts +196 -0
- package/src/summarization/node.ts +203 -6
- package/src/tools/BashExecutor.ts +4 -3
- package/src/tools/CodeExecutor.ts +5 -4
- package/src/tools/ProgrammaticToolCalling.ts +7 -5
- package/src/tools/ToolNode.ts +109 -6
- package/src/tools/ToolSearch.ts +4 -3
- package/src/tools/__tests__/BashExecutor.test.ts +2 -2
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +2 -4
- package/src/tools/__tests__/SubagentExecutor.test.ts +126 -0
- package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +389 -0
- package/src/tools/__tests__/ToolNode.streamLimits.test.ts +69 -0
- package/src/tools/search/crw-scraper.ts +6 -0
- package/src/tools/search/crw-search.ts +6 -1
- package/src/tools/search/firecrawl.ts +6 -0
- package/src/tools/search/http-agent.test.ts +133 -0
- package/src/tools/search/keenable-scraper.ts +6 -0
- package/src/tools/search/keenable-search.ts +6 -1
- package/src/tools/search/rerankers.ts +36 -10
- package/src/tools/search/search.ts +29 -11
- package/src/tools/search/serper-scraper.ts +6 -0
- package/src/tools/search/tavily-scraper.ts +6 -0
- package/src/tools/search/tavily-search.ts +2 -0
- package/src/tools/search/tool.ts +16 -0
- package/src/tools/search/types.ts +31 -33
- package/src/tools/subagent/SubagentExecutor.ts +96 -3
- package/src/types/graph.ts +7 -0
- package/src/types/run.ts +49 -1
- package/src/types/tools.ts +21 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/misc.ts +19 -0
- package/src/utils/proxy.test.ts +176 -0
- 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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -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,
|
|
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 */
|
|
@@ -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.
|
|
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,
|