@librechat/agents 3.3.4 → 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.
- package/dist/cjs/common/constants.cjs +21 -0
- package/dist/cjs/common/constants.cjs.map +1 -1
- package/dist/cjs/events.cjs +10 -1
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +456 -7
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +25 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs +12 -0
- package/dist/cjs/hooks/index.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +1 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +8 -0
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +1 -3
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +268 -3
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/preempt.cjs +132 -0
- package/dist/cjs/llm/preempt.cjs.map +1 -0
- package/dist/cjs/main.cjs +17 -3
- package/dist/cjs/messages/alternation.cjs +92 -0
- package/dist/cjs/messages/alternation.cjs.map +1 -0
- package/dist/cjs/messages/format.cjs +72 -0
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/handoffCue.cjs +63 -0
- package/dist/cjs/messages/handoffCue.cjs.map +1 -0
- package/dist/cjs/messages/index.cjs +3 -0
- package/dist/cjs/messages/injected.cjs +60 -0
- package/dist/cjs/messages/injected.cjs.map +1 -0
- package/dist/cjs/run.cjs +80 -7
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/handlers.cjs +18 -9
- package/dist/cjs/session/handlers.cjs.map +1 -1
- package/dist/cjs/stream.cjs +28 -7
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +3 -23
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/keenable-scraper.cjs +90 -0
- package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/tool.cjs +9 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/esm/common/constants.mjs +19 -1
- package/dist/esm/common/constants.mjs.map +1 -1
- package/dist/esm/events.mjs +10 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +455 -6
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +25 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs +12 -1
- package/dist/esm/hooks/index.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +1 -0
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +9 -1
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +1 -3
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +270 -6
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/preempt.mjs +131 -0
- package/dist/esm/llm/preempt.mjs.map +1 -0
- package/dist/esm/main.mjs +8 -5
- package/dist/esm/messages/alternation.mjs +91 -0
- package/dist/esm/messages/alternation.mjs.map +1 -0
- package/dist/esm/messages/format.mjs +72 -0
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/handoffCue.mjs +61 -0
- package/dist/esm/messages/handoffCue.mjs.map +1 -0
- package/dist/esm/messages/index.mjs +3 -0
- package/dist/esm/messages/injected.mjs +60 -0
- package/dist/esm/messages/injected.mjs.map +1 -0
- package/dist/esm/run.mjs +80 -7
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/handlers.mjs +19 -10
- package/dist/esm/session/handlers.mjs.map +1 -1
- package/dist/esm/stream.mjs +27 -8
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +3 -23
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/keenable-scraper.mjs +88 -0
- package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/tool.mjs +9 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/types/common/constants.d.ts +18 -0
- package/dist/types/graphs/Graph.d.ts +138 -1
- package/dist/types/hooks/HookRegistry.d.ts +15 -0
- package/dist/types/hooks/index.d.ts +12 -1
- package/dist/types/hooks/types.d.ts +45 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/llm/invoke.d.ts +7 -0
- package/dist/types/llm/preempt.d.ts +40 -0
- package/dist/types/messages/alternation.d.ts +23 -0
- package/dist/types/messages/handoffCue.d.ts +40 -0
- package/dist/types/messages/index.d.ts +3 -0
- package/dist/types/messages/injected.d.ts +3 -0
- package/dist/types/run.d.ts +7 -0
- package/dist/types/stream.d.ts +14 -0
- package/dist/types/tools/ToolNode.d.ts +0 -7
- package/dist/types/tools/search/keenable-scraper.d.ts +15 -0
- package/dist/types/tools/search/types.d.ts +31 -2
- package/dist/types/types/graph.d.ts +8 -1
- package/dist/types/types/run.d.ts +65 -0
- package/dist/types/types/stream.d.ts +0 -25
- package/package.json +1 -1
- package/src/{splitStream.test.ts → aggregator.test.ts} +59 -666
- package/src/common/constants.ts +21 -0
- package/src/events.ts +15 -1
- package/src/graphs/Graph.ts +568 -3
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +18 -7
- package/src/graphs/__tests__/Graph.preemptSignal.test.ts +126 -0
- package/src/hooks/HookRegistry.ts +40 -0
- package/src/hooks/__tests__/preemptBoundary.test.ts +152 -0
- package/src/hooks/index.ts +16 -2
- package/src/hooks/types.ts +47 -3
- package/src/index.ts +1 -1
- package/src/langfuse.ts +26 -1
- package/src/llm/bedrock/utils/message_inputs.test.ts +82 -0
- package/src/llm/bedrock/utils/message_inputs.ts +14 -11
- package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +15 -0
- package/src/llm/invoke.alternation.test.ts +87 -0
- package/src/llm/invoke.handoffCue.test.ts +112 -0
- package/src/llm/invoke.ts +417 -8
- package/src/llm/preempt.test.ts +323 -0
- package/src/llm/preempt.ts +178 -0
- package/src/messages/alternation.test.ts +212 -0
- package/src/messages/alternation.ts +112 -0
- package/src/messages/format.ts +91 -0
- package/src/messages/formatAgentMessages.steer.test.ts +267 -0
- package/src/messages/handoffCue.test.ts +96 -0
- package/src/messages/handoffCue.ts +78 -0
- package/src/messages/index.ts +3 -0
- package/src/messages/injected.test.ts +90 -0
- package/src/messages/injected.ts +74 -0
- package/src/run.ts +91 -6
- package/src/scripts/preempt-probe.ts +330 -0
- package/src/scripts/preempt-scenarios.ts +388 -0
- package/src/session/handlers.ts +32 -12
- package/src/specs/handoffCue.test.ts +165 -0
- package/src/specs/langfuse-callbacks.test.ts +352 -2
- package/src/specs/preemptSeal.test.ts +309 -0
- package/src/stream.dispatch.test.ts +63 -0
- package/src/stream.ts +36 -8
- package/src/tools/ToolNode.ts +3 -31
- package/src/tools/search/keenable-scraper.test.ts +153 -0
- package/src/tools/search/keenable-scraper.ts +137 -0
- package/src/tools/search/tool.ts +13 -2
- package/src/tools/search/types.ts +50 -3
- package/src/types/graph.ts +12 -1
- package/src/types/run.ts +67 -0
- package/src/types/stream.ts +0 -41
- package/dist/cjs/splitStream.cjs +0 -151
- package/dist/cjs/splitStream.cjs.map +0 -1
- package/dist/esm/splitStream.mjs +0 -150
- package/dist/esm/splitStream.mjs.map +0 -1
- package/dist/types/mockStream.d.ts +0 -32
- package/dist/types/splitStream.d.ts +0 -37
- package/src/mockStream.ts +0 -99
- package/src/splitStream.ts +0 -234
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_utils = require("./utils.cjs");
|
|
3
|
+
let axios = require("axios");
|
|
4
|
+
axios = require_runtime.__toESM(axios, 1);
|
|
5
|
+
//#region src/tools/search/keenable-scraper.ts
|
|
6
|
+
const DEFAULT_KEENABLE_SCRAPE_TIMEOUT = 15e3;
|
|
7
|
+
/** Keyed and keyless fetch endpoints. Keenable reads any URL as clean markdown
|
|
8
|
+
* without a key via the public endpoint; a key only lifts rate limits. */
|
|
9
|
+
const KEENABLE_FETCH_API_URL = "https://api.keenable.ai/v1/fetch";
|
|
10
|
+
const KEENABLE_FETCH_PUBLIC_URL = "https://api.keenable.ai/v1/fetch/public";
|
|
11
|
+
var KeenableScraper = class {
|
|
12
|
+
apiKey;
|
|
13
|
+
apiUrl;
|
|
14
|
+
timeout;
|
|
15
|
+
attributionTitle;
|
|
16
|
+
logger;
|
|
17
|
+
constructor(config = {}) {
|
|
18
|
+
const resolvedKey = config.apiKey ?? process.env.KEENABLE_API_KEY;
|
|
19
|
+
this.apiKey = resolvedKey != null && resolvedKey !== "" ? resolvedKey : void 0;
|
|
20
|
+
this.apiUrl = config.apiUrl ?? process.env.KEENABLE_FETCH_URL ?? (this.apiKey != null ? KEENABLE_FETCH_API_URL : KEENABLE_FETCH_PUBLIC_URL);
|
|
21
|
+
this.timeout = config.timeout ?? DEFAULT_KEENABLE_SCRAPE_TIMEOUT;
|
|
22
|
+
this.attributionTitle = config.attributionTitle ?? "LibreChat";
|
|
23
|
+
this.logger = config.logger || require_utils.createDefaultLogger();
|
|
24
|
+
}
|
|
25
|
+
buildHeaders() {
|
|
26
|
+
/** X-Keenable-Title is used for traffic attribution and required on the
|
|
27
|
+
* keyless endpoint; the key only lifts rate limits, so it is sent only when
|
|
28
|
+
* present. */
|
|
29
|
+
const headers = { "X-Keenable-Title": this.attributionTitle };
|
|
30
|
+
if (this.apiKey != null) headers["X-API-Key"] = this.apiKey;
|
|
31
|
+
return headers;
|
|
32
|
+
}
|
|
33
|
+
async scrapeUrl(url, options = {}) {
|
|
34
|
+
if (!url || !url.trim()) return [url, {
|
|
35
|
+
success: false,
|
|
36
|
+
error: "URL cannot be empty"
|
|
37
|
+
}];
|
|
38
|
+
try {
|
|
39
|
+
const data = (await axios.default.get(this.apiUrl, {
|
|
40
|
+
params: { url },
|
|
41
|
+
headers: this.buildHeaders(),
|
|
42
|
+
timeout: options.timeout ?? this.timeout
|
|
43
|
+
})).data;
|
|
44
|
+
const content = data.content ?? "";
|
|
45
|
+
if (!content) return [url, {
|
|
46
|
+
success: false,
|
|
47
|
+
error: "Keenable Fetch returned no content"
|
|
48
|
+
}];
|
|
49
|
+
return [url, {
|
|
50
|
+
success: true,
|
|
51
|
+
data: {
|
|
52
|
+
content,
|
|
53
|
+
title: data.title,
|
|
54
|
+
description: data.description,
|
|
55
|
+
url: data.url ?? url
|
|
56
|
+
}
|
|
57
|
+
}];
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return [url, {
|
|
60
|
+
success: false,
|
|
61
|
+
error: `Keenable Fetch API request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
62
|
+
}];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async scrapeUrls(urls, options = {}) {
|
|
66
|
+
/** Keenable fetch is single-URL; run the batch concurrently. */
|
|
67
|
+
return Promise.all(urls.map((url) => this.scrapeUrl(url, options)));
|
|
68
|
+
}
|
|
69
|
+
extractContent(response) {
|
|
70
|
+
if (!response.success || !response.data) return ["", void 0];
|
|
71
|
+
/** Keenable returns clean markdown with no separate media arrays, so there
|
|
72
|
+
* are no structured references to surface. */
|
|
73
|
+
return [response.data.content, void 0];
|
|
74
|
+
}
|
|
75
|
+
extractMetadata(response) {
|
|
76
|
+
if (!response.success || !response.data) return {};
|
|
77
|
+
const metadata = {};
|
|
78
|
+
if (response.data.title != null) metadata.title = response.data.title;
|
|
79
|
+
if (response.data.description != null && response.data.description !== "") metadata.description = response.data.description;
|
|
80
|
+
if (response.data.url != null) metadata.url = response.data.url;
|
|
81
|
+
return metadata;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const createKeenableScraper = (config = {}) => {
|
|
85
|
+
return new KeenableScraper(config);
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
exports.createKeenableScraper = createKeenableScraper;
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=keenable-scraper.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keenable-scraper.cjs","names":["createDefaultLogger"],"sources":["../../../../src/tools/search/keenable-scraper.ts"],"sourcesContent":["import axios from 'axios';\nimport type * as t from './types';\nimport { createDefaultLogger } from './utils';\n\nconst DEFAULT_KEENABLE_SCRAPE_TIMEOUT = 15000;\n\n/** Keyed and keyless fetch endpoints. Keenable reads any URL as clean markdown\n * without a key via the public endpoint; a key only lifts rate limits. */\nconst KEENABLE_FETCH_API_URL = 'https://api.keenable.ai/v1/fetch';\nconst KEENABLE_FETCH_PUBLIC_URL = 'https://api.keenable.ai/v1/fetch/public';\n\nexport class KeenableScraper implements t.BaseScraper {\n private apiKey: string | undefined;\n private apiUrl: string;\n private timeout: number;\n private attributionTitle: string;\n private logger: t.Logger;\n\n constructor(config: t.KeenableScraperConfig = {}) {\n const resolvedKey = config.apiKey ?? process.env.KEENABLE_API_KEY;\n this.apiKey =\n resolvedKey != null && resolvedKey !== '' ? resolvedKey : undefined;\n this.apiUrl =\n config.apiUrl ??\n process.env.KEENABLE_FETCH_URL ??\n (this.apiKey != null\n ? KEENABLE_FETCH_API_URL\n : KEENABLE_FETCH_PUBLIC_URL);\n this.timeout = config.timeout ?? DEFAULT_KEENABLE_SCRAPE_TIMEOUT;\n this.attributionTitle = config.attributionTitle ?? 'LibreChat';\n this.logger = config.logger || createDefaultLogger();\n }\n\n private buildHeaders(): Record<string, string> {\n /** X-Keenable-Title is used for traffic attribution and required on the\n * keyless endpoint; the key only lifts rate limits, so it is sent only when\n * present. */\n const headers: Record<string, string> = {\n 'X-Keenable-Title': this.attributionTitle,\n };\n if (this.apiKey != null) {\n headers['X-API-Key'] = this.apiKey;\n }\n return headers;\n }\n\n async scrapeUrl(\n url: string,\n options: t.KeenableScrapeOptions = {}\n ): Promise<[string, t.KeenableScrapeResponse]> {\n if (!url || !url.trim()) {\n return [url, { success: false, error: 'URL cannot be empty' }];\n }\n\n try {\n const response = await axios.get<t.KeenableFetchResult>(this.apiUrl, {\n params: { url },\n headers: this.buildHeaders(),\n timeout: options.timeout ?? this.timeout,\n });\n\n const data = response.data;\n const content = data.content ?? '';\n if (!content) {\n return [\n url,\n { success: false, error: 'Keenable Fetch returned no content' },\n ];\n }\n\n return [\n url,\n {\n success: true,\n data: {\n content,\n title: data.title,\n description: data.description,\n url: data.url ?? url,\n },\n },\n ];\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n return [\n url,\n {\n success: false,\n error: `Keenable Fetch API request failed: ${errorMessage}`,\n },\n ];\n }\n }\n\n async scrapeUrls(\n urls: string[],\n options: t.KeenableScrapeOptions = {}\n ): Promise<Array<[string, t.KeenableScrapeResponse]>> {\n /** Keenable fetch is single-URL; run the batch concurrently. */\n return Promise.all(urls.map((url) => this.scrapeUrl(url, options)));\n }\n\n extractContent(\n response: t.KeenableScrapeResponse\n ): [string, undefined | t.References] {\n if (!response.success || !response.data) {\n return ['', undefined];\n }\n /** Keenable returns clean markdown with no separate media arrays, so there\n * are no structured references to surface. */\n return [response.data.content, undefined];\n }\n\n extractMetadata(response: t.KeenableScrapeResponse): t.GenericScrapeMetadata {\n if (!response.success || !response.data) {\n return {};\n }\n const metadata: t.GenericScrapeMetadata = {};\n if (response.data.title != null) {\n metadata.title = response.data.title;\n }\n if (response.data.description != null && response.data.description !== '') {\n metadata.description = response.data.description;\n }\n if (response.data.url != null) {\n metadata.url = response.data.url;\n }\n return metadata;\n }\n}\n\nexport const createKeenableScraper = (\n config: t.KeenableScraperConfig = {}\n): KeenableScraper => {\n return new KeenableScraper(config);\n};\n"],"mappings":";;;;;AAIA,MAAM,kCAAkC;;;AAIxC,MAAM,yBAAyB;AAC/B,MAAM,4BAA4B;AAElC,IAAa,kBAAb,MAAsD;CACpD;CACA;CACA;CACA;CACA;CAEA,YAAY,SAAkC,CAAC,GAAG;EAChD,MAAM,cAAc,OAAO,UAAU,QAAQ,IAAI;EACjD,KAAK,SACH,eAAe,QAAQ,gBAAgB,KAAK,cAAc,KAAA;EAC5D,KAAK,SACH,OAAO,UACP,QAAQ,IAAI,uBACX,KAAK,UAAU,OACZ,yBACA;EACN,KAAK,UAAU,OAAO,WAAW;EACjC,KAAK,mBAAmB,OAAO,oBAAoB;EACnD,KAAK,SAAS,OAAO,UAAUA,cAAAA,oBAAoB;CACrD;CAEA,eAA+C;;;;EAI7C,MAAM,UAAkC,EACtC,oBAAoB,KAAK,iBAC3B;EACA,IAAI,KAAK,UAAU,MACjB,QAAQ,eAAe,KAAK;EAE9B,OAAO;CACT;CAEA,MAAM,UACJ,KACA,UAAmC,CAAC,GACS;EAC7C,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,GACpB,OAAO,CAAC,KAAK;GAAE,SAAS;GAAO,OAAO;EAAsB,CAAC;EAG/D,IAAI;GAOF,MAAM,QAAO,MANU,MAAA,QAAM,IAA2B,KAAK,QAAQ;IACnE,QAAQ,EAAE,IAAI;IACd,SAAS,KAAK,aAAa;IAC3B,SAAS,QAAQ,WAAW,KAAK;GACnC,CAAC,EAAA,CAEqB;GACtB,MAAM,UAAU,KAAK,WAAW;GAChC,IAAI,CAAC,SACH,OAAO,CACL,KACA;IAAE,SAAS;IAAO,OAAO;GAAqC,CAChE;GAGF,OAAO,CACL,KACA;IACE,SAAS;IACT,MAAM;KACJ;KACA,OAAO,KAAK;KACZ,aAAa,KAAK;KAClB,KAAK,KAAK,OAAO;IACnB;GACF,CACF;EACF,SAAS,OAAO;GAGd,OAAO,CACL,KACA;IACE,SAAS;IACT,OAAO,sCALT,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAMrD,CACF;EACF;CACF;CAEA,MAAM,WACJ,MACA,UAAmC,CAAC,GACgB;;EAEpD,OAAO,QAAQ,IAAI,KAAK,KAAK,QAAQ,KAAK,UAAU,KAAK,OAAO,CAAC,CAAC;CACpE;CAEA,eACE,UACoC;EACpC,IAAI,CAAC,SAAS,WAAW,CAAC,SAAS,MACjC,OAAO,CAAC,IAAI,KAAA,CAAS;;;EAIvB,OAAO,CAAC,SAAS,KAAK,SAAS,KAAA,CAAS;CAC1C;CAEA,gBAAgB,UAA6D;EAC3E,IAAI,CAAC,SAAS,WAAW,CAAC,SAAS,MACjC,OAAO,CAAC;EAEV,MAAM,WAAoC,CAAC;EAC3C,IAAI,SAAS,KAAK,SAAS,MACzB,SAAS,QAAQ,SAAS,KAAK;EAEjC,IAAI,SAAS,KAAK,eAAe,QAAQ,SAAS,KAAK,gBAAgB,IACrE,SAAS,cAAc,SAAS,KAAK;EAEvC,IAAI,SAAS,KAAK,OAAO,MACvB,SAAS,MAAM,SAAS,KAAK;EAE/B,OAAO;CACT;AACF;AAEA,MAAa,yBACX,SAAkC,CAAC,MACf;CACpB,OAAO,IAAI,gBAAgB,MAAM;AACnC"}
|
|
@@ -4,6 +4,7 @@ const require_utils = require("./utils.cjs");
|
|
|
4
4
|
const require_format = require("./format.cjs");
|
|
5
5
|
const require_schema = require("./schema.cjs");
|
|
6
6
|
const require_search = require("./search.cjs");
|
|
7
|
+
const require_keenable_scraper = require("./keenable-scraper.cjs");
|
|
7
8
|
const require_serper_scraper = require("./serper-scraper.cjs");
|
|
8
9
|
const require_tavily_scraper = require("./tavily-scraper.cjs");
|
|
9
10
|
const require_firecrawl = require("./firecrawl.cjs");
|
|
@@ -181,7 +182,7 @@ function createTool({ schema, search, maxOutputChars, onSearchResults: _onSearch
|
|
|
181
182
|
});
|
|
182
183
|
}
|
|
183
184
|
const createSearchTool = (config = {}) => {
|
|
184
|
-
const { searchProvider = "serper", serperApiKey, searxngInstanceUrl, searxngApiKey, tavilyApiKey, tavilySearchUrl, tavilyExtractUrl, tavilySearchOptions, keenableApiKey, keenableApiUrl, keenableSearchOptions, rerankerType = "cohere", rerankerTimeout, topResults = 5, maxContentLength, chunkSize, chunkOverlap, mainExpandBy, separatorExpandBy, maxOutputChars, strategies = ["no_extraction"], filterContent = true, safeSearch = 1, scraperProvider = "firecrawl", firecrawlApiKey, firecrawlApiUrl, firecrawlVersion, firecrawlOptions, serperScraperOptions, tavilyScraperOptions, crwApiKey, crwApiUrl, crwSearchOptions, crwScraperOptions, scraperTimeout, jinaApiKey, jinaApiUrl, cohereApiKey, onSearchResults: _onSearchResults, onGetHighlights } = config;
|
|
185
|
+
const { searchProvider = "serper", serperApiKey, searxngInstanceUrl, searxngApiKey, tavilyApiKey, tavilySearchUrl, tavilyExtractUrl, tavilySearchOptions, keenableApiKey, keenableApiUrl, keenableSearchOptions, keenableScraperOptions, rerankerType = "cohere", rerankerTimeout, topResults = 5, maxContentLength, chunkSize, chunkOverlap, mainExpandBy, separatorExpandBy, maxOutputChars, strategies = ["no_extraction"], filterContent = true, safeSearch = 1, scraperProvider = "firecrawl", firecrawlApiKey, firecrawlApiUrl, firecrawlVersion, firecrawlOptions, serperScraperOptions, tavilyScraperOptions, crwApiKey, crwApiUrl, crwSearchOptions, crwScraperOptions, scraperTimeout, jinaApiKey, jinaApiUrl, cohereApiKey, onSearchResults: _onSearchResults, onGetHighlights } = config;
|
|
185
186
|
const logger = config.logger || require_utils.createDefaultLogger();
|
|
186
187
|
const effectiveTavilySearchOptions = searchProvider === "tavily" && config.safeSearch != null ? {
|
|
187
188
|
...tavilySearchOptions,
|
|
@@ -238,6 +239,13 @@ const createSearchTool = (config = {}) => {
|
|
|
238
239
|
formats: crwScraperOptions?.formats ?? ["markdown", "rawHtml"],
|
|
239
240
|
logger
|
|
240
241
|
});
|
|
242
|
+
else if (scraperProvider === "keenable") scraperInstance = require_keenable_scraper.createKeenableScraper({
|
|
243
|
+
...keenableScraperOptions,
|
|
244
|
+
apiKey: keenableScraperOptions?.apiKey ?? keenableApiKey,
|
|
245
|
+
timeout: scraperTimeout ?? keenableScraperOptions?.timeout,
|
|
246
|
+
attributionTitle: keenableScraperOptions?.attributionTitle ?? keenableSearchOptions?.attributionTitle,
|
|
247
|
+
logger
|
|
248
|
+
});
|
|
241
249
|
else scraperInstance = require_firecrawl.createFirecrawlScraper({
|
|
242
250
|
...firecrawlOptions,
|
|
243
251
|
apiKey: firecrawlApiKey ?? process.env.FIRECRAWL_API_KEY,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.cjs","names":["expandHighlights","params","formatResultsForLLM","WebSearchToolName","WebSearchToolDescription","createDefaultLogger","querySchema","dateSchema","imagesSchema","videosSchema","newsSchema","countrySchema","createSearchAPI","createSerperScraper","createTavilyScraper","createCrwScraper","createFirecrawlScraper","createReranker","createSourceProcessor"],"sources":["../../../../src/tools/search/tool.ts"],"sourcesContent":["import { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type * as t from './types';\nimport {\n WebSearchToolDescription,\n WebSearchToolName,\n countrySchema,\n imagesSchema,\n videosSchema,\n querySchema,\n dateSchema,\n newsSchema,\n DATE_RANGE,\n} from './schema';\nimport { createSearchAPI, createSourceProcessor } from './search';\nimport { createSerperScraper } from './serper-scraper';\nimport { createTavilyScraper } from './tavily-scraper';\nimport { createFirecrawlScraper } from './firecrawl';\nimport { createCrwScraper } from './crw-scraper';\nimport { expandHighlights } from './highlights';\nimport { formatResultsForLLM } from './format';\nimport { createDefaultLogger } from './utils';\nimport { createReranker } from './rerankers';\nimport { Constants } from '@/common';\n\n/**\n * Executes parallel searches and merges the results,\n * deduplicating top stories by link\n */\nexport async function executeParallelSearches({\n searchAPI,\n query,\n date,\n country,\n safeSearch,\n images,\n videos,\n news,\n logger,\n}: {\n searchAPI: ReturnType<typeof createSearchAPI>;\n query: string;\n date?: DATE_RANGE;\n country?: string;\n safeSearch: t.SearchToolConfig['safeSearch'];\n images: boolean;\n videos: boolean;\n news: boolean;\n logger: t.Logger;\n}): Promise<t.SearchResult> {\n // Prepare all search tasks to run in parallel\n const searchTasks: Promise<t.SearchResult>[] = [\n // Main search\n searchAPI.getSources({\n query,\n date,\n country,\n safeSearch,\n }),\n ];\n\n if (images) {\n searchTasks.push(\n searchAPI\n .getSources({\n query,\n date,\n country,\n safeSearch,\n type: 'images',\n })\n .catch((error) => {\n logger.error('Error fetching images:', error);\n return {\n success: false,\n error: `Images search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n })\n );\n }\n if (videos) {\n searchTasks.push(\n searchAPI\n .getSources({\n query,\n date,\n country,\n safeSearch,\n type: 'videos',\n })\n .catch((error) => {\n logger.error('Error fetching videos:', error);\n return {\n success: false,\n error: `Videos search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n })\n );\n }\n if (news) {\n searchTasks.push(\n searchAPI\n .getSources({\n query,\n date,\n country,\n safeSearch,\n type: 'news',\n })\n .catch((error) => {\n logger.error('Error fetching news:', error);\n return {\n success: false,\n error: `News search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n })\n );\n }\n\n // Run all searches in parallel\n const results = await Promise.all(searchTasks);\n\n // Get the main search result (first result)\n const mainResult = results[0];\n if (!mainResult.success) {\n throw new Error(mainResult.error ?? 'Search failed');\n }\n\n // Merge additional results with the main results\n const mergedResults = { ...mainResult.data };\n\n // Convert existing news to topStories if present\n if (mergedResults.news !== undefined && mergedResults.news.length > 0) {\n const existingNewsAsTopStories = mergedResults.news\n .filter((newsItem) => newsItem.link !== undefined && newsItem.link !== '')\n .map((newsItem) => ({\n title: newsItem.title ?? '',\n link: newsItem.link ?? '',\n source: newsItem.source ?? '',\n date: newsItem.date ?? '',\n imageUrl: newsItem.imageUrl ?? '',\n processed: false,\n }));\n mergedResults.topStories = [\n ...(mergedResults.topStories ?? []),\n ...existingNewsAsTopStories,\n ];\n delete mergedResults.news;\n }\n\n results.slice(1).forEach((result) => {\n if (result.success && result.data !== undefined) {\n if (result.data.images !== undefined && result.data.images.length > 0) {\n mergedResults.images = [\n ...(mergedResults.images ?? []),\n ...result.data.images,\n ];\n }\n if (result.data.videos !== undefined && result.data.videos.length > 0) {\n mergedResults.videos = [\n ...(mergedResults.videos ?? []),\n ...result.data.videos,\n ];\n }\n if (result.data.news !== undefined && result.data.news.length > 0) {\n const newsAsTopStories = result.data.news.map((newsItem) => ({\n ...newsItem,\n link: newsItem.link ?? '',\n }));\n mergedResults.topStories = [\n ...(mergedResults.topStories ?? []),\n ...newsAsTopStories,\n ];\n }\n }\n });\n\n if (\n mergedResults.topStories !== undefined &&\n mergedResults.topStories.length > 1\n ) {\n /** The main search's own news results and the parallel news sub-search\n * frequently return the same stories — keep the first occurrence of each\n * link so duplicates aren't scraped, reranked, and formatted repeatedly */\n const seenLinks = new Set<string>();\n mergedResults.topStories = mergedResults.topStories.filter((story) => {\n if (!story.link || seenLinks.has(story.link)) {\n return false;\n }\n seenLinks.add(story.link);\n return true;\n });\n }\n\n return { success: true, data: mergedResults };\n}\n\nfunction createSearchProcessor({\n searchAPI,\n safeSearch,\n supportsImages,\n supportsVideos,\n supportsNews,\n sourceProcessor,\n onGetHighlights,\n mainExpandBy,\n separatorExpandBy,\n logger,\n}: {\n safeSearch: t.SearchToolConfig['safeSearch'];\n supportsImages: boolean;\n supportsVideos: boolean;\n supportsNews: boolean;\n searchAPI: ReturnType<typeof createSearchAPI>;\n sourceProcessor: ReturnType<typeof createSourceProcessor>;\n onGetHighlights: t.SearchToolConfig['onGetHighlights'];\n mainExpandBy: t.SearchToolConfig['mainExpandBy'];\n separatorExpandBy: t.SearchToolConfig['separatorExpandBy'];\n logger: t.Logger;\n}) {\n return async function ({\n query,\n date,\n country,\n proMode = true,\n maxSources = 5,\n onSearchResults,\n images = false,\n videos = false,\n news = false,\n }: {\n query: string;\n country?: string;\n date?: DATE_RANGE;\n proMode?: boolean;\n maxSources?: number;\n onSearchResults: t.SearchToolConfig['onSearchResults'];\n images?: boolean;\n videos?: boolean;\n news?: boolean;\n }): Promise<t.SearchResultData> {\n try {\n // Execute parallel searches and merge results\n const searchResult = await executeParallelSearches({\n searchAPI,\n query,\n date,\n country,\n safeSearch,\n images: supportsImages && images,\n videos: supportsVideos && videos,\n news: supportsNews && news,\n logger,\n });\n\n onSearchResults?.(searchResult);\n\n const processedSources = await sourceProcessor.processSources({\n query,\n news,\n result: searchResult,\n proMode,\n onGetHighlights,\n numElements: maxSources,\n });\n\n return expandHighlights(\n processedSources,\n mainExpandBy,\n separatorExpandBy\n );\n } catch (error) {\n logger.error('Error in search:', error);\n return {\n organic: [],\n topStories: [],\n images: [],\n videos: [],\n news: [],\n relatedSearches: [],\n error: error instanceof Error ? error.message : String(error),\n };\n }\n };\n}\n\nfunction createOnSearchResults({\n runnableConfig,\n onSearchResults,\n}: {\n runnableConfig: RunnableConfig;\n onSearchResults: t.SearchToolConfig['onSearchResults'];\n}) {\n return function (results: t.SearchResult): void {\n if (!onSearchResults) {\n return;\n }\n onSearchResults(results, runnableConfig);\n };\n}\n\nfunction createTool({\n schema,\n search,\n maxOutputChars,\n onSearchResults: _onSearchResults,\n}: {\n schema: Record<string, unknown>;\n search: ReturnType<typeof createSearchProcessor>;\n maxOutputChars?: number;\n onSearchResults: t.SearchToolConfig['onSearchResults'];\n}): DynamicStructuredTool {\n return tool(\n async (rawParams, runnableConfig) => {\n const params = rawParams as SearchToolParams;\n const { query, date, country: _c, images, videos, news } = params;\n const country = typeof _c === 'string' && _c ? _c : undefined;\n const searchResult = await search({\n query,\n date,\n country,\n images,\n videos,\n news,\n onSearchResults: createOnSearchResults({\n runnableConfig,\n onSearchResults: _onSearchResults,\n }),\n });\n const turn = runnableConfig.toolCall?.turn ?? 0;\n const { output, references } = formatResultsForLLM(\n turn,\n searchResult,\n maxOutputChars\n );\n const data: t.SearchResultData = { turn, ...searchResult, references };\n return [output, { [Constants.WEB_SEARCH]: data }];\n },\n {\n name: WebSearchToolName,\n description: WebSearchToolDescription,\n schema: schema,\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\n/**\n * Creates a search tool with configurable search and scraper providers.\n *\n * Search providers: Serper (Google results), SearXNG (self-hosted meta-search), Tavily (AI-optimized), fastCRW (Firecrawl-compatible, self-host or cloud).\n * Scraper providers: Firecrawl (default, full-featured), Serper (lightweight), Tavily (batch extraction), fastCRW (Firecrawl-compatible, self-host or cloud).\n *\n * The country schema field is exposed to the LLM for providers that support localized results.\n */\n/** Input params type for search tool */\ninterface SearchToolParams {\n query: string;\n date?: DATE_RANGE;\n country?: string;\n images?: boolean;\n videos?: boolean;\n news?: boolean;\n}\n\nexport const createSearchTool = (\n config: t.SearchToolConfig = {}\n): DynamicStructuredTool => {\n const {\n searchProvider = 'serper',\n serperApiKey,\n searxngInstanceUrl,\n searxngApiKey,\n tavilyApiKey,\n tavilySearchUrl,\n tavilyExtractUrl,\n tavilySearchOptions,\n keenableApiKey,\n keenableApiUrl,\n keenableSearchOptions,\n rerankerType = 'cohere',\n rerankerTimeout,\n topResults = 5,\n maxContentLength,\n chunkSize,\n chunkOverlap,\n mainExpandBy,\n separatorExpandBy,\n maxOutputChars,\n strategies = ['no_extraction'],\n filterContent = true,\n safeSearch = 1,\n scraperProvider = 'firecrawl',\n firecrawlApiKey,\n firecrawlApiUrl,\n firecrawlVersion,\n firecrawlOptions,\n serperScraperOptions,\n tavilyScraperOptions,\n crwApiKey,\n crwApiUrl,\n crwSearchOptions,\n crwScraperOptions,\n scraperTimeout,\n jinaApiKey,\n jinaApiUrl,\n cohereApiKey,\n onSearchResults: _onSearchResults,\n onGetHighlights,\n } = config;\n\n const logger = config.logger || createDefaultLogger();\n const effectiveTavilySearchOptions =\n searchProvider === 'tavily' && config.safeSearch != null\n ? {\n ...tavilySearchOptions,\n safeSearch: config.safeSearch !== 0,\n }\n : tavilySearchOptions;\n\n const schemaProperties: Record<string, unknown> = {\n query: querySchema,\n date: dateSchema,\n images: imagesSchema,\n videos: videosSchema,\n news: newsSchema,\n };\n\n if (searchProvider === 'serper' || searchProvider === 'tavily') {\n schemaProperties.country = countrySchema;\n }\n\n const toolSchema = {\n type: 'object',\n properties: schemaProperties,\n required: ['query'],\n };\n\n const searchAPI = createSearchAPI({\n searchProvider,\n serperApiKey,\n searxngInstanceUrl,\n searxngApiKey,\n tavilyApiKey,\n tavilySearchUrl,\n tavilySearchOptions: effectiveTavilySearchOptions,\n keenableApiKey,\n keenableApiUrl,\n keenableSearchOptions,\n crwApiKey,\n crwApiUrl,\n crwSearchOptions,\n });\n\n /** Create scraper based on scraperProvider */\n let scraperInstance: t.BaseScraper;\n\n if (scraperProvider === 'serper') {\n scraperInstance = createSerperScraper({\n ...serperScraperOptions,\n apiKey: serperApiKey,\n timeout: scraperTimeout ?? serperScraperOptions?.timeout,\n logger,\n });\n } else if (scraperProvider === 'tavily') {\n scraperInstance = createTavilyScraper({\n ...tavilyScraperOptions,\n apiKey:\n tavilyScraperOptions?.apiKey ??\n tavilyApiKey ??\n process.env.TAVILY_API_KEY,\n apiUrl: tavilyScraperOptions?.apiUrl ?? tavilyExtractUrl,\n timeout: scraperTimeout ?? tavilyScraperOptions?.timeout,\n logger,\n });\n } else if (scraperProvider === 'crw') {\n scraperInstance = createCrwScraper({\n ...crwScraperOptions,\n apiKey:\n crwScraperOptions?.apiKey ?? crwApiKey ?? process.env.CRW_API_KEY,\n apiUrl: crwScraperOptions?.apiUrl ?? crwApiUrl,\n timeout: scraperTimeout ?? crwScraperOptions?.timeout,\n formats: crwScraperOptions?.formats ?? ['markdown', 'rawHtml'],\n logger,\n });\n } else {\n scraperInstance = createFirecrawlScraper({\n ...firecrawlOptions,\n apiKey: firecrawlApiKey ?? process.env.FIRECRAWL_API_KEY,\n apiUrl: firecrawlApiUrl,\n version: firecrawlVersion,\n timeout: scraperTimeout ?? firecrawlOptions?.timeout,\n formats: firecrawlOptions?.formats ?? ['markdown', 'rawHtml'],\n logger,\n });\n }\n\n const selectedReranker = createReranker({\n rerankerType,\n jinaApiKey,\n jinaApiUrl,\n cohereApiKey,\n rerankerTimeout,\n logger,\n });\n\n if (!selectedReranker) {\n logger.warn('No reranker selected. Using default ranking.');\n }\n\n const sourceProcessor = createSourceProcessor(\n {\n reranker: selectedReranker,\n topResults,\n maxContentLength,\n chunkSize,\n chunkOverlap,\n strategies,\n filterContent,\n logger,\n },\n scraperInstance\n );\n\n const search = createSearchProcessor({\n searchAPI,\n safeSearch,\n // Keenable is organic-only: its API ignores `type`, so image/news\n // sub-searches would spend rate limit and merge nothing.\n supportsImages: searchProvider !== 'keenable',\n supportsVideos:\n searchProvider !== 'tavily' &&\n searchProvider !== 'keenable' &&\n searchProvider !== 'crw',\n supportsNews: searchProvider !== 'keenable',\n sourceProcessor,\n onGetHighlights,\n mainExpandBy,\n separatorExpandBy,\n logger,\n });\n\n return createTool({\n search,\n schema: toolSchema,\n maxOutputChars,\n onSearchResults: _onSearchResults,\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool.cjs","names":["expandHighlights","params","formatResultsForLLM","WebSearchToolName","WebSearchToolDescription","createDefaultLogger","querySchema","dateSchema","imagesSchema","videosSchema","newsSchema","countrySchema","createSearchAPI","createSerperScraper","createTavilyScraper","createCrwScraper","createKeenableScraper","createFirecrawlScraper","createReranker","createSourceProcessor"],"sources":["../../../../src/tools/search/tool.ts"],"sourcesContent":["import { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type * as t from './types';\nimport {\n WebSearchToolDescription,\n WebSearchToolName,\n countrySchema,\n imagesSchema,\n videosSchema,\n querySchema,\n dateSchema,\n newsSchema,\n DATE_RANGE,\n} from './schema';\nimport { createSearchAPI, createSourceProcessor } from './search';\nimport { createKeenableScraper } from './keenable-scraper';\nimport { createSerperScraper } from './serper-scraper';\nimport { createTavilyScraper } from './tavily-scraper';\nimport { createFirecrawlScraper } from './firecrawl';\nimport { createCrwScraper } from './crw-scraper';\nimport { expandHighlights } from './highlights';\nimport { formatResultsForLLM } from './format';\nimport { createDefaultLogger } from './utils';\nimport { createReranker } from './rerankers';\nimport { Constants } from '@/common';\n\n/**\n * Executes parallel searches and merges the results,\n * deduplicating top stories by link\n */\nexport async function executeParallelSearches({\n searchAPI,\n query,\n date,\n country,\n safeSearch,\n images,\n videos,\n news,\n logger,\n}: {\n searchAPI: ReturnType<typeof createSearchAPI>;\n query: string;\n date?: DATE_RANGE;\n country?: string;\n safeSearch: t.SearchToolConfig['safeSearch'];\n images: boolean;\n videos: boolean;\n news: boolean;\n logger: t.Logger;\n}): Promise<t.SearchResult> {\n // Prepare all search tasks to run in parallel\n const searchTasks: Promise<t.SearchResult>[] = [\n // Main search\n searchAPI.getSources({\n query,\n date,\n country,\n safeSearch,\n }),\n ];\n\n if (images) {\n searchTasks.push(\n searchAPI\n .getSources({\n query,\n date,\n country,\n safeSearch,\n type: 'images',\n })\n .catch((error) => {\n logger.error('Error fetching images:', error);\n return {\n success: false,\n error: `Images search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n })\n );\n }\n if (videos) {\n searchTasks.push(\n searchAPI\n .getSources({\n query,\n date,\n country,\n safeSearch,\n type: 'videos',\n })\n .catch((error) => {\n logger.error('Error fetching videos:', error);\n return {\n success: false,\n error: `Videos search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n })\n );\n }\n if (news) {\n searchTasks.push(\n searchAPI\n .getSources({\n query,\n date,\n country,\n safeSearch,\n type: 'news',\n })\n .catch((error) => {\n logger.error('Error fetching news:', error);\n return {\n success: false,\n error: `News search failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n })\n );\n }\n\n // Run all searches in parallel\n const results = await Promise.all(searchTasks);\n\n // Get the main search result (first result)\n const mainResult = results[0];\n if (!mainResult.success) {\n throw new Error(mainResult.error ?? 'Search failed');\n }\n\n // Merge additional results with the main results\n const mergedResults = { ...mainResult.data };\n\n // Convert existing news to topStories if present\n if (mergedResults.news !== undefined && mergedResults.news.length > 0) {\n const existingNewsAsTopStories = mergedResults.news\n .filter((newsItem) => newsItem.link !== undefined && newsItem.link !== '')\n .map((newsItem) => ({\n title: newsItem.title ?? '',\n link: newsItem.link ?? '',\n source: newsItem.source ?? '',\n date: newsItem.date ?? '',\n imageUrl: newsItem.imageUrl ?? '',\n processed: false,\n }));\n mergedResults.topStories = [\n ...(mergedResults.topStories ?? []),\n ...existingNewsAsTopStories,\n ];\n delete mergedResults.news;\n }\n\n results.slice(1).forEach((result) => {\n if (result.success && result.data !== undefined) {\n if (result.data.images !== undefined && result.data.images.length > 0) {\n mergedResults.images = [\n ...(mergedResults.images ?? []),\n ...result.data.images,\n ];\n }\n if (result.data.videos !== undefined && result.data.videos.length > 0) {\n mergedResults.videos = [\n ...(mergedResults.videos ?? []),\n ...result.data.videos,\n ];\n }\n if (result.data.news !== undefined && result.data.news.length > 0) {\n const newsAsTopStories = result.data.news.map((newsItem) => ({\n ...newsItem,\n link: newsItem.link ?? '',\n }));\n mergedResults.topStories = [\n ...(mergedResults.topStories ?? []),\n ...newsAsTopStories,\n ];\n }\n }\n });\n\n if (\n mergedResults.topStories !== undefined &&\n mergedResults.topStories.length > 1\n ) {\n /** The main search's own news results and the parallel news sub-search\n * frequently return the same stories — keep the first occurrence of each\n * link so duplicates aren't scraped, reranked, and formatted repeatedly */\n const seenLinks = new Set<string>();\n mergedResults.topStories = mergedResults.topStories.filter((story) => {\n if (!story.link || seenLinks.has(story.link)) {\n return false;\n }\n seenLinks.add(story.link);\n return true;\n });\n }\n\n return { success: true, data: mergedResults };\n}\n\nfunction createSearchProcessor({\n searchAPI,\n safeSearch,\n supportsImages,\n supportsVideos,\n supportsNews,\n sourceProcessor,\n onGetHighlights,\n mainExpandBy,\n separatorExpandBy,\n logger,\n}: {\n safeSearch: t.SearchToolConfig['safeSearch'];\n supportsImages: boolean;\n supportsVideos: boolean;\n supportsNews: boolean;\n searchAPI: ReturnType<typeof createSearchAPI>;\n sourceProcessor: ReturnType<typeof createSourceProcessor>;\n onGetHighlights: t.SearchToolConfig['onGetHighlights'];\n mainExpandBy: t.SearchToolConfig['mainExpandBy'];\n separatorExpandBy: t.SearchToolConfig['separatorExpandBy'];\n logger: t.Logger;\n}) {\n return async function ({\n query,\n date,\n country,\n proMode = true,\n maxSources = 5,\n onSearchResults,\n images = false,\n videos = false,\n news = false,\n }: {\n query: string;\n country?: string;\n date?: DATE_RANGE;\n proMode?: boolean;\n maxSources?: number;\n onSearchResults: t.SearchToolConfig['onSearchResults'];\n images?: boolean;\n videos?: boolean;\n news?: boolean;\n }): Promise<t.SearchResultData> {\n try {\n // Execute parallel searches and merge results\n const searchResult = await executeParallelSearches({\n searchAPI,\n query,\n date,\n country,\n safeSearch,\n images: supportsImages && images,\n videos: supportsVideos && videos,\n news: supportsNews && news,\n logger,\n });\n\n onSearchResults?.(searchResult);\n\n const processedSources = await sourceProcessor.processSources({\n query,\n news,\n result: searchResult,\n proMode,\n onGetHighlights,\n numElements: maxSources,\n });\n\n return expandHighlights(\n processedSources,\n mainExpandBy,\n separatorExpandBy\n );\n } catch (error) {\n logger.error('Error in search:', error);\n return {\n organic: [],\n topStories: [],\n images: [],\n videos: [],\n news: [],\n relatedSearches: [],\n error: error instanceof Error ? error.message : String(error),\n };\n }\n };\n}\n\nfunction createOnSearchResults({\n runnableConfig,\n onSearchResults,\n}: {\n runnableConfig: RunnableConfig;\n onSearchResults: t.SearchToolConfig['onSearchResults'];\n}) {\n return function (results: t.SearchResult): void {\n if (!onSearchResults) {\n return;\n }\n onSearchResults(results, runnableConfig);\n };\n}\n\nfunction createTool({\n schema,\n search,\n maxOutputChars,\n onSearchResults: _onSearchResults,\n}: {\n schema: Record<string, unknown>;\n search: ReturnType<typeof createSearchProcessor>;\n maxOutputChars?: number;\n onSearchResults: t.SearchToolConfig['onSearchResults'];\n}): DynamicStructuredTool {\n return tool(\n async (rawParams, runnableConfig) => {\n const params = rawParams as SearchToolParams;\n const { query, date, country: _c, images, videos, news } = params;\n const country = typeof _c === 'string' && _c ? _c : undefined;\n const searchResult = await search({\n query,\n date,\n country,\n images,\n videos,\n news,\n onSearchResults: createOnSearchResults({\n runnableConfig,\n onSearchResults: _onSearchResults,\n }),\n });\n const turn = runnableConfig.toolCall?.turn ?? 0;\n const { output, references } = formatResultsForLLM(\n turn,\n searchResult,\n maxOutputChars\n );\n const data: t.SearchResultData = { turn, ...searchResult, references };\n return [output, { [Constants.WEB_SEARCH]: data }];\n },\n {\n name: WebSearchToolName,\n description: WebSearchToolDescription,\n schema: schema,\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\n/**\n * Creates a search tool with configurable search and scraper providers.\n *\n * Search providers: Serper (Google results), SearXNG (self-hosted meta-search), Tavily (AI-optimized), fastCRW (Firecrawl-compatible, self-host or cloud).\n * Scraper providers: Firecrawl (default, full-featured), Serper (lightweight), Tavily (batch extraction), fastCRW (Firecrawl-compatible, self-host or cloud).\n *\n * The country schema field is exposed to the LLM for providers that support localized results.\n */\n/** Input params type for search tool */\ninterface SearchToolParams {\n query: string;\n date?: DATE_RANGE;\n country?: string;\n images?: boolean;\n videos?: boolean;\n news?: boolean;\n}\n\nexport const createSearchTool = (\n config: t.SearchToolConfig = {}\n): DynamicStructuredTool => {\n const {\n searchProvider = 'serper',\n serperApiKey,\n searxngInstanceUrl,\n searxngApiKey,\n tavilyApiKey,\n tavilySearchUrl,\n tavilyExtractUrl,\n tavilySearchOptions,\n keenableApiKey,\n keenableApiUrl,\n keenableSearchOptions,\n keenableScraperOptions,\n rerankerType = 'cohere',\n rerankerTimeout,\n topResults = 5,\n maxContentLength,\n chunkSize,\n chunkOverlap,\n mainExpandBy,\n separatorExpandBy,\n maxOutputChars,\n strategies = ['no_extraction'],\n filterContent = true,\n safeSearch = 1,\n scraperProvider = 'firecrawl',\n firecrawlApiKey,\n firecrawlApiUrl,\n firecrawlVersion,\n firecrawlOptions,\n serperScraperOptions,\n tavilyScraperOptions,\n crwApiKey,\n crwApiUrl,\n crwSearchOptions,\n crwScraperOptions,\n scraperTimeout,\n jinaApiKey,\n jinaApiUrl,\n cohereApiKey,\n onSearchResults: _onSearchResults,\n onGetHighlights,\n } = config;\n\n const logger = config.logger || createDefaultLogger();\n const effectiveTavilySearchOptions =\n searchProvider === 'tavily' && config.safeSearch != null\n ? {\n ...tavilySearchOptions,\n safeSearch: config.safeSearch !== 0,\n }\n : tavilySearchOptions;\n\n const schemaProperties: Record<string, unknown> = {\n query: querySchema,\n date: dateSchema,\n images: imagesSchema,\n videos: videosSchema,\n news: newsSchema,\n };\n\n if (searchProvider === 'serper' || searchProvider === 'tavily') {\n schemaProperties.country = countrySchema;\n }\n\n const toolSchema = {\n type: 'object',\n properties: schemaProperties,\n required: ['query'],\n };\n\n const searchAPI = createSearchAPI({\n searchProvider,\n serperApiKey,\n searxngInstanceUrl,\n searxngApiKey,\n tavilyApiKey,\n tavilySearchUrl,\n tavilySearchOptions: effectiveTavilySearchOptions,\n keenableApiKey,\n keenableApiUrl,\n keenableSearchOptions,\n crwApiKey,\n crwApiUrl,\n crwSearchOptions,\n });\n\n /** Create scraper based on scraperProvider */\n let scraperInstance: t.BaseScraper;\n\n if (scraperProvider === 'serper') {\n scraperInstance = createSerperScraper({\n ...serperScraperOptions,\n apiKey: serperApiKey,\n timeout: scraperTimeout ?? serperScraperOptions?.timeout,\n logger,\n });\n } else if (scraperProvider === 'tavily') {\n scraperInstance = createTavilyScraper({\n ...tavilyScraperOptions,\n apiKey:\n tavilyScraperOptions?.apiKey ??\n tavilyApiKey ??\n process.env.TAVILY_API_KEY,\n apiUrl: tavilyScraperOptions?.apiUrl ?? tavilyExtractUrl,\n timeout: scraperTimeout ?? tavilyScraperOptions?.timeout,\n logger,\n });\n } else if (scraperProvider === 'crw') {\n scraperInstance = createCrwScraper({\n ...crwScraperOptions,\n apiKey: crwScraperOptions?.apiKey ?? crwApiKey ?? process.env.CRW_API_KEY,\n apiUrl: crwScraperOptions?.apiUrl ?? crwApiUrl,\n timeout: scraperTimeout ?? crwScraperOptions?.timeout,\n formats: crwScraperOptions?.formats ?? ['markdown', 'rawHtml'],\n logger,\n });\n } else if (scraperProvider === 'keenable') {\n scraperInstance = createKeenableScraper({\n ...keenableScraperOptions,\n apiKey: keenableScraperOptions?.apiKey ?? keenableApiKey,\n timeout: scraperTimeout ?? keenableScraperOptions?.timeout,\n attributionTitle:\n keenableScraperOptions?.attributionTitle ??\n keenableSearchOptions?.attributionTitle,\n logger,\n });\n } else {\n scraperInstance = createFirecrawlScraper({\n ...firecrawlOptions,\n apiKey: firecrawlApiKey ?? process.env.FIRECRAWL_API_KEY,\n apiUrl: firecrawlApiUrl,\n version: firecrawlVersion,\n timeout: scraperTimeout ?? firecrawlOptions?.timeout,\n formats: firecrawlOptions?.formats ?? ['markdown', 'rawHtml'],\n logger,\n });\n }\n\n const selectedReranker = createReranker({\n rerankerType,\n jinaApiKey,\n jinaApiUrl,\n cohereApiKey,\n rerankerTimeout,\n logger,\n });\n\n if (!selectedReranker) {\n logger.warn('No reranker selected. Using default ranking.');\n }\n\n const sourceProcessor = createSourceProcessor(\n {\n reranker: selectedReranker,\n topResults,\n maxContentLength,\n chunkSize,\n chunkOverlap,\n strategies,\n filterContent,\n logger,\n },\n scraperInstance\n );\n\n const search = createSearchProcessor({\n searchAPI,\n safeSearch,\n // Keenable is organic-only: its API ignores `type`, so image/news\n // sub-searches would spend rate limit and merge nothing.\n supportsImages: searchProvider !== 'keenable',\n supportsVideos:\n searchProvider !== 'tavily' &&\n searchProvider !== 'keenable' &&\n searchProvider !== 'crw',\n supportsNews: searchProvider !== 'keenable',\n sourceProcessor,\n onGetHighlights,\n mainExpandBy,\n separatorExpandBy,\n logger,\n });\n\n return createTool({\n search,\n schema: toolSchema,\n maxOutputChars,\n onSearchResults: _onSearchResults,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA8BA,eAAsB,wBAAwB,EAC5C,WACA,OACA,MACA,SACA,YACA,QACA,QACA,MACA,UAW0B;CAE1B,MAAM,cAAyC,CAE7C,UAAU,WAAW;EACnB;EACA;EACA;EACA;CACF,CAAC,CACH;CAEA,IAAI,QACF,YAAY,KACV,UACG,WAAW;EACV;EACA;EACA;EACA;EACA,MAAM;CACR,CAAC,CAAC,CACD,OAAO,UAAU;EAChB,OAAO,MAAM,0BAA0B,KAAK;EAC5C,OAAO;GACL,SAAS;GACT,OAAO,yBAAyB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACvF;CACF,CAAC,CACL;CAEF,IAAI,QACF,YAAY,KACV,UACG,WAAW;EACV;EACA;EACA;EACA;EACA,MAAM;CACR,CAAC,CAAC,CACD,OAAO,UAAU;EAChB,OAAO,MAAM,0BAA0B,KAAK;EAC5C,OAAO;GACL,SAAS;GACT,OAAO,yBAAyB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACvF;CACF,CAAC,CACL;CAEF,IAAI,MACF,YAAY,KACV,UACG,WAAW;EACV;EACA;EACA;EACA;EACA,MAAM;CACR,CAAC,CAAC,CACD,OAAO,UAAU;EAChB,OAAO,MAAM,wBAAwB,KAAK;EAC1C,OAAO;GACL,SAAS;GACT,OAAO,uBAAuB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACrF;CACF,CAAC,CACL;CAIF,MAAM,UAAU,MAAM,QAAQ,IAAI,WAAW;CAG7C,MAAM,aAAa,QAAQ;CAC3B,IAAI,CAAC,WAAW,SACd,MAAM,IAAI,MAAM,WAAW,SAAS,eAAe;CAIrD,MAAM,gBAAgB,EAAE,GAAG,WAAW,KAAK;CAG3C,IAAI,cAAc,SAAS,KAAA,KAAa,cAAc,KAAK,SAAS,GAAG;EACrE,MAAM,2BAA2B,cAAc,KAC5C,QAAQ,aAAa,SAAS,SAAS,KAAA,KAAa,SAAS,SAAS,EAAE,CAAC,CACzE,KAAK,cAAc;GAClB,OAAO,SAAS,SAAS;GACzB,MAAM,SAAS,QAAQ;GACvB,QAAQ,SAAS,UAAU;GAC3B,MAAM,SAAS,QAAQ;GACvB,UAAU,SAAS,YAAY;GAC/B,WAAW;EACb,EAAE;EACJ,cAAc,aAAa,CACzB,GAAI,cAAc,cAAc,CAAC,GACjC,GAAG,wBACL;EACA,OAAO,cAAc;CACvB;CAEA,QAAQ,MAAM,CAAC,CAAC,CAAC,SAAS,WAAW;EACnC,IAAI,OAAO,WAAW,OAAO,SAAS,KAAA,GAAW;GAC/C,IAAI,OAAO,KAAK,WAAW,KAAA,KAAa,OAAO,KAAK,OAAO,SAAS,GAClE,cAAc,SAAS,CACrB,GAAI,cAAc,UAAU,CAAC,GAC7B,GAAG,OAAO,KAAK,MACjB;GAEF,IAAI,OAAO,KAAK,WAAW,KAAA,KAAa,OAAO,KAAK,OAAO,SAAS,GAClE,cAAc,SAAS,CACrB,GAAI,cAAc,UAAU,CAAC,GAC7B,GAAG,OAAO,KAAK,MACjB;GAEF,IAAI,OAAO,KAAK,SAAS,KAAA,KAAa,OAAO,KAAK,KAAK,SAAS,GAAG;IACjE,MAAM,mBAAmB,OAAO,KAAK,KAAK,KAAK,cAAc;KAC3D,GAAG;KACH,MAAM,SAAS,QAAQ;IACzB,EAAE;IACF,cAAc,aAAa,CACzB,GAAI,cAAc,cAAc,CAAC,GACjC,GAAG,gBACL;GACF;EACF;CACF,CAAC;CAED,IACE,cAAc,eAAe,KAAA,KAC7B,cAAc,WAAW,SAAS,GAClC;;;;EAIA,MAAM,4BAAY,IAAI,IAAY;EAClC,cAAc,aAAa,cAAc,WAAW,QAAQ,UAAU;GACpE,IAAI,CAAC,MAAM,QAAQ,UAAU,IAAI,MAAM,IAAI,GACzC,OAAO;GAET,UAAU,IAAI,MAAM,IAAI;GACxB,OAAO;EACT,CAAC;CACH;CAEA,OAAO;EAAE,SAAS;EAAM,MAAM;CAAc;AAC9C;AAEA,SAAS,sBAAsB,EAC7B,WACA,YACA,gBACA,gBACA,cACA,iBACA,iBACA,cACA,mBACA,UAYC;CACD,OAAO,eAAgB,EACrB,OACA,MACA,SACA,UAAU,MACV,aAAa,GACb,iBACA,SAAS,OACT,SAAS,OACT,OAAO,SAWuB;EAC9B,IAAI;GAEF,MAAM,eAAe,MAAM,wBAAwB;IACjD;IACA;IACA;IACA;IACA;IACA,QAAQ,kBAAkB;IAC1B,QAAQ,kBAAkB;IAC1B,MAAM,gBAAgB;IACtB;GACF,CAAC;GAED,kBAAkB,YAAY;GAW9B,OAAOA,mBAAAA,iBACL,MAV6B,gBAAgB,eAAe;IAC5D;IACA;IACA,QAAQ;IACR;IACA;IACA,aAAa;GACf,CAAC,GAIC,cACA,iBACF;EACF,SAAS,OAAO;GACd,OAAO,MAAM,oBAAoB,KAAK;GACtC,OAAO;IACL,SAAS,CAAC;IACV,YAAY,CAAC;IACb,QAAQ,CAAC;IACT,QAAQ,CAAC;IACT,MAAM,CAAC;IACP,iBAAiB,CAAC;IAClB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D;EACF;CACF;AACF;AAEA,SAAS,sBAAsB,EAC7B,gBACA,mBAIC;CACD,OAAO,SAAU,SAA+B;EAC9C,IAAI,CAAC,iBACH;EAEF,gBAAgB,SAAS,cAAc;CACzC;AACF;AAEA,SAAS,WAAW,EAClB,QACA,QACA,gBACA,iBAAiB,oBAMO;CACxB,QAAA,GAAA,sBAAA,KAAA,CACE,OAAO,WAAW,mBAAmB;EAEnC,MAAM,EAAE,OAAO,MAAM,SAAS,IAAI,QAAQ,QAAQ,SAASC;EAE3D,MAAM,eAAe,MAAM,OAAO;GAChC;GACA;GACA,SAJc,OAAO,OAAO,YAAY,KAAK,KAAK,KAAA;GAKlD;GACA;GACA;GACA,iBAAiB,sBAAsB;IACrC;IACA,iBAAiB;GACnB,CAAC;EACH,CAAC;EACD,MAAM,OAAO,eAAe,UAAU,QAAQ;EAC9C,MAAM,EAAE,QAAQ,eAAeC,eAAAA,oBAC7B,MACA,cACA,cACF;EACA,MAAM,OAA2B;GAAE;GAAM,GAAG;GAAc;EAAW;EACrE,OAAO,CAAC,QAAQ,GAAA,eAA0B,KAAK,CAAC;CAClD,GACA;EACE,MAAMC,eAAAA;EACN,aAAaC,eAAAA;EACL;EACR,gBAAA;CACF,CACF;AACF;AAoBA,MAAa,oBACX,SAA6B,CAAC,MACJ;CAC1B,MAAM,EACJ,iBAAiB,UACjB,cACA,oBACA,eACA,cACA,iBACA,kBACA,qBACA,gBACA,gBACA,uBACA,wBACA,eAAe,UACf,iBACA,aAAa,GACb,kBACA,WACA,cACA,cACA,mBACA,gBACA,aAAa,CAAC,eAAe,GAC7B,gBAAgB,MAChB,aAAa,GACb,kBAAkB,aAClB,iBACA,iBACA,kBACA,kBACA,sBACA,sBACA,WACA,WACA,kBACA,mBACA,gBACA,YACA,YACA,cACA,iBAAiB,kBACjB,oBACE;CAEJ,MAAM,SAAS,OAAO,UAAUC,cAAAA,oBAAoB;CACpD,MAAM,+BACJ,mBAAmB,YAAY,OAAO,cAAc,OAChD;EACA,GAAG;EACH,YAAY,OAAO,eAAe;CACpC,IACE;CAEN,MAAM,mBAA4C;EAChD,OAAOC,eAAAA;EACP,MAAMC,eAAAA;EACN,QAAQC,eAAAA;EACR,QAAQC,eAAAA;EACR,MAAMC,eAAAA;CACR;CAEA,IAAI,mBAAmB,YAAY,mBAAmB,UACpD,iBAAiB,UAAUC,eAAAA;CAG7B,MAAM,aAAa;EACjB,MAAM;EACN,YAAY;EACZ,UAAU,CAAC,OAAO;CACpB;CAEA,MAAM,YAAYC,eAAAA,gBAAgB;EAChC;EACA;EACA;EACA;EACA;EACA;EACA,qBAAqB;EACrB;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;;CAGD,IAAI;CAEJ,IAAI,oBAAoB,UACtB,kBAAkBC,uBAAAA,oBAAoB;EACpC,GAAG;EACH,QAAQ;EACR,SAAS,kBAAkB,sBAAsB;EACjD;CACF,CAAC;MACI,IAAI,oBAAoB,UAC7B,kBAAkBC,uBAAAA,oBAAoB;EACpC,GAAG;EACH,QACE,sBAAsB,UACtB,gBACA,QAAQ,IAAI;EACd,QAAQ,sBAAsB,UAAU;EACxC,SAAS,kBAAkB,sBAAsB;EACjD;CACF,CAAC;MACI,IAAI,oBAAoB,OAC7B,kBAAkBC,oBAAAA,iBAAiB;EACjC,GAAG;EACH,QAAQ,mBAAmB,UAAU,aAAa,QAAQ,IAAI;EAC9D,QAAQ,mBAAmB,UAAU;EACrC,SAAS,kBAAkB,mBAAmB;EAC9C,SAAS,mBAAmB,WAAW,CAAC,YAAY,SAAS;EAC7D;CACF,CAAC;MACI,IAAI,oBAAoB,YAC7B,kBAAkBC,yBAAAA,sBAAsB;EACtC,GAAG;EACH,QAAQ,wBAAwB,UAAU;EAC1C,SAAS,kBAAkB,wBAAwB;EACnD,kBACE,wBAAwB,oBACxB,uBAAuB;EACzB;CACF,CAAC;MAED,kBAAkBC,kBAAAA,uBAAuB;EACvC,GAAG;EACH,QAAQ,mBAAmB,QAAQ,IAAI;EACvC,QAAQ;EACR,SAAS;EACT,SAAS,kBAAkB,kBAAkB;EAC7C,SAAS,kBAAkB,WAAW,CAAC,YAAY,SAAS;EAC5D;CACF,CAAC;CAGH,MAAM,mBAAmBC,kBAAAA,eAAe;EACtC;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CAED,IAAI,CAAC,kBACH,OAAO,KAAK,8CAA8C;CAG5D,MAAM,kBAAkBC,eAAAA,sBACtB;EACE,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;CACF,GACA,eACF;CAoBA,OAAO,WAAW;EAChB,QAnBa,sBAAsB;GACnC;GACA;GAGA,gBAAgB,mBAAmB;GACnC,gBACE,mBAAmB,YACnB,mBAAmB,cACnB,mBAAmB;GACrB,cAAc,mBAAmB;GACjC;GACA;GACA;GACA;GACA;EACF,CAGO;EACL,QAAQ;EACR;EACA,iBAAiB;CACnB,CAAC;AACH"}
|
|
@@ -12,7 +12,25 @@ const ANTHROPIC_TOOL_TOKEN_MULTIPLIER = 2.6;
|
|
|
12
12
|
* Empirically calibrated at ~1.4× the raw JSON token count.
|
|
13
13
|
*/
|
|
14
14
|
const DEFAULT_TOOL_TOKEN_MULTIPLIER = 1.4;
|
|
15
|
+
/**
|
|
16
|
+
* Default ceiling on cooperative stream seals per run. Each seal costs one
|
|
17
|
+
* extra superstep, so this also sizes the recursion-limit headroom a
|
|
18
|
+
* preemption-enabled run reserves.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_MAX_SEALS = 8;
|
|
21
|
+
/**
|
|
22
|
+
* Per-hook timeout for `PreemptBoundary`, deliberately far above
|
|
23
|
+
* `DEFAULT_HOOK_TIMEOUT_MS`. A host drain has already popped its queue and
|
|
24
|
+
* persisted content by the time this fires, so a timeout does not cancel the
|
|
25
|
+
* work — it only loses the messages the run was about to inject.
|
|
26
|
+
*/
|
|
27
|
+
const PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS = 12e4;
|
|
28
|
+
/**
|
|
29
|
+
* Default LangGraph recursion limit for a run. Callers may override it via
|
|
30
|
+
* the stream config; a preemption-enabled run adds its seal budget on top.
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_RECURSION_LIMIT = 50;
|
|
15
33
|
//#endregion
|
|
16
|
-
export { ANTHROPIC_TOOL_TOKEN_MULTIPLIER, DEFAULT_TOOL_TOKEN_MULTIPLIER };
|
|
34
|
+
export { ANTHROPIC_TOOL_TOKEN_MULTIPLIER, DEFAULT_MAX_SEALS, DEFAULT_RECURSION_LIMIT, DEFAULT_TOOL_TOKEN_MULTIPLIER, PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS };
|
|
17
35
|
|
|
18
36
|
//# sourceMappingURL=constants.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.mjs","names":[],"sources":["../../../src/common/constants.ts"],"sourcesContent":["/**\n * Anthropic direct API tool schema overhead multiplier.\n * Empirically calibrated against real MCP tool sets (29 tools).\n * Accounts for Anthropic's internal XML-like tool encoding plus\n * a ~300-token hidden tool-system preamble.\n */\nexport const ANTHROPIC_TOOL_TOKEN_MULTIPLIER = 2.6;\n\n/**\n * Default tool schema overhead multiplier for all non-Anthropic providers.\n * Covers OpenAI function-calling format, Bedrock, and other providers.\n * Empirically calibrated at ~1.4× the raw JSON token count.\n */\nexport const DEFAULT_TOOL_TOKEN_MULTIPLIER = 1.4;\n"],"mappings":";;;;;;;AAMA,MAAa,kCAAkC;;;;;;AAO/C,MAAa,gCAAgC"}
|
|
1
|
+
{"version":3,"file":"constants.mjs","names":[],"sources":["../../../src/common/constants.ts"],"sourcesContent":["/**\n * Anthropic direct API tool schema overhead multiplier.\n * Empirically calibrated against real MCP tool sets (29 tools).\n * Accounts for Anthropic's internal XML-like tool encoding plus\n * a ~300-token hidden tool-system preamble.\n */\nexport const ANTHROPIC_TOOL_TOKEN_MULTIPLIER = 2.6;\n\n/**\n * Default tool schema overhead multiplier for all non-Anthropic providers.\n * Covers OpenAI function-calling format, Bedrock, and other providers.\n * Empirically calibrated at ~1.4× the raw JSON token count.\n */\nexport const DEFAULT_TOOL_TOKEN_MULTIPLIER = 1.4;\n\n/**\n * Default ceiling on cooperative stream seals per run. Each seal costs one\n * extra superstep, so this also sizes the recursion-limit headroom a\n * preemption-enabled run reserves.\n */\nexport const DEFAULT_MAX_SEALS = 8;\n\n/**\n * Per-hook timeout for `PreemptBoundary`, deliberately far above\n * `DEFAULT_HOOK_TIMEOUT_MS`. A host drain has already popped its queue and\n * persisted content by the time this fires, so a timeout does not cancel the\n * work — it only loses the messages the run was about to inject.\n */\nexport const PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS = 120_000;\n\n/**\n * Default LangGraph recursion limit for a run. Callers may override it via\n * the stream config; a preemption-enabled run adds its seal budget on top.\n */\nexport const DEFAULT_RECURSION_LIMIT = 50;\n"],"mappings":";;;;;;;AAMA,MAAa,kCAAkC;;;;;;AAO/C,MAAa,gCAAgC;;;;;;AAO7C,MAAa,oBAAoB;;;;;;;AAQjC,MAAa,mCAAmC;;;;;AAMhD,MAAa,0BAA0B"}
|
package/dist/esm/events.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "./common/enum.mjs";
|
|
2
2
|
import "./common/index.mjs";
|
|
3
|
+
import { SDK_STREAM_DISPATCH, dispatchesChatModelStream } from "./stream.mjs";
|
|
3
4
|
//#region src/events.ts
|
|
4
5
|
var HandlerRegistry = class {
|
|
5
6
|
handlers = /* @__PURE__ */ new Map();
|
|
@@ -20,10 +21,18 @@ function composeEventHandlers(...handlerSets) {
|
|
|
20
21
|
composed[eventType] = handler;
|
|
21
22
|
continue;
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
const wrapper = { handle: async (...args) => {
|
|
24
25
|
await previous.handle(...args);
|
|
25
26
|
await handler.handle(...args);
|
|
26
27
|
} };
|
|
28
|
+
/**
|
|
29
|
+
* Carry the stream-dispatch brand across the wrapper. Without this a
|
|
30
|
+
* composed handler fails every capability check made about it — notably
|
|
31
|
+
* the one deciding whether a run may seal — even though it still drives
|
|
32
|
+
* the SDK's content-part dispatch.
|
|
33
|
+
*/
|
|
34
|
+
if (dispatchesChatModelStream(previous) || dispatchesChatModelStream(handler)) Object.defineProperty(wrapper, SDK_STREAM_DISPATCH, { value: true });
|
|
35
|
+
composed[eventType] = wrapper;
|
|
27
36
|
}
|
|
28
37
|
}
|
|
29
38
|
return composed;
|
package/dist/esm/events.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.mjs","names":[],"sources":["../../src/events.ts"],"sourcesContent":["/* eslint-disable no-console */\n// src/events.ts\nimport type {\n BaseMessageFields,\n UsageMetadata,\n} from '@langchain/core/messages';\nimport type { Logger } from 'winston';\nimport type { MultiAgentGraph, StandardGraph } from '@/graphs';\nimport type * as t from '@/types';\nimport { Constants } from '@/common';\n\nexport class HandlerRegistry {\n private handlers: Map<string, t.EventHandler> = new Map();\n\n register(eventType: string, handler: t.EventHandler): void {\n this.handlers.set(eventType, handler);\n }\n\n getHandler(eventType: string): t.EventHandler | undefined {\n return this.handlers.get(eventType);\n }\n}\n\nexport function composeEventHandlers(\n ...handlerSets: Array<Record<string, t.EventHandler> | undefined>\n): Record<string, t.EventHandler> {\n const composed: Partial<Record<string, t.EventHandler>> = {};\n\n for (const handlerSet of handlerSets) {\n if (!handlerSet) {\n continue;\n }\n for (const [eventType, handler] of Object.entries(handlerSet)) {\n const previous = composed[eventType];\n if (previous === undefined) {\n composed[eventType] = handler;\n continue;\n }\n composed[eventType] = {\n handle: async (\n ...args: Parameters<t.EventHandler['handle']>\n ): Promise<void> => {\n await previous.handle(...args);\n await handler.handle(...args);\n },\n };\n }\n }\n\n return composed as Record<string, t.EventHandler>;\n}\n\nexport class ModelEndHandler implements t.EventHandler {\n collectedUsage?: UsageMetadata[];\n constructor(collectedUsage?: UsageMetadata[]) {\n if (collectedUsage && !Array.isArray(collectedUsage)) {\n throw new Error('collectedUsage must be an array');\n }\n this.collectedUsage = collectedUsage;\n }\n\n async handle(\n event: string,\n data: t.ModelEndData,\n metadata?: Record<string, unknown>,\n graph?: StandardGraph | MultiAgentGraph\n ): Promise<void> {\n if (!graph || !metadata) {\n console.warn(`Graph or metadata not found in ${event} event`);\n return;\n }\n\n const usage = data?.output?.usage_metadata;\n if (usage != null && this.collectedUsage != null) {\n this.collectedUsage.push(usage);\n }\n }\n}\n\nexport class ToolEndHandler implements t.EventHandler {\n private callback?: t.ToolEndCallback;\n private logger?: Logger;\n constructor(callback?: t.ToolEndCallback, logger?: Logger) {\n this.callback = callback;\n this.logger = logger;\n }\n\n /**\n * Handles on_tool_end events from the for-await stream consumer.\n *\n * This handler is now purely a consumer callback — tool completion\n * (ON_RUN_STEP_COMPLETED dispatch + session context storage) is handled\n * in graph context by ToolNode directly, eliminating the race between\n * the stream consumer and graph execution.\n */\n async handle(\n event: string,\n data: t.StreamEventData | undefined,\n metadata?: Record<string, unknown>,\n graph?: StandardGraph | MultiAgentGraph\n ): Promise<void> {\n try {\n if (!graph || !metadata) {\n if (this.logger) {\n this.logger.warn(`Graph or metadata not found in ${event} event`);\n } else {\n console.warn(`Graph or metadata not found in ${event} event`);\n }\n return;\n }\n\n const toolEndData = data as t.ToolEndData | undefined;\n if (!toolEndData?.output) {\n if (this.logger) {\n this.logger.warn('No output found in tool_end event');\n } else {\n console.warn('No output found in tool_end event');\n }\n return;\n }\n\n if (\n metadata[Constants.PROGRAMMATIC_TOOL_CALLING] === true ||\n metadata[Constants.BASH_PROGRAMMATIC_TOOL_CALLING] === true\n ) {\n return;\n }\n\n if (this.callback) {\n await this.callback(toolEndData, metadata);\n }\n } catch (error) {\n if (this.logger) {\n this.logger.error('Error handling tool_end event:', error);\n } else {\n console.error('Error handling tool_end event:', error);\n }\n }\n }\n}\n\nexport class TestLLMStreamHandler implements t.EventHandler {\n handle(event: string, data: t.StreamEventData | undefined): void {\n const chunk = data?.chunk;\n const isMessageChunk = !!(chunk && 'message' in chunk);\n const msg = isMessageChunk ? chunk.message : undefined;\n if (msg && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) {\n console.log(msg.tool_call_chunks);\n } else if (msg && typeof msg.content === 'string') {\n process.stdout.write(msg.content);\n }\n }\n}\n\nexport class TestChatStreamHandler implements t.EventHandler {\n handle(event: string, data: t.StreamEventData | undefined): void {\n const chunk = data?.chunk;\n const isContentChunk = !!(chunk && 'content' in chunk);\n if (!isContentChunk) {\n return;\n }\n\n const content = chunk.content;\n\n if (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) {\n console.dir(chunk.tool_call_chunks, { depth: null });\n }\n\n if (typeof content === 'string') {\n process.stdout.write(content);\n } else {\n console.dir(content, { depth: null });\n }\n }\n}\n\nexport class LLMStreamHandler implements t.EventHandler {\n handle(\n event: string,\n data: t.StreamEventData | undefined,\n metadata?: Record<string, unknown>\n ): void {\n const chunk = data?.chunk;\n const isMessageChunk = !!(chunk && 'message' in chunk);\n const msg = isMessageChunk ? chunk.message : undefined;\n if (metadata) {\n console.log(metadata);\n }\n if (msg && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) {\n console.log(msg.tool_call_chunks);\n } else if (msg && typeof msg.content === 'string') {\n process.stdout.write(msg.content);\n }\n }\n}\n\nexport const createMetadataAggregator = (\n _collected?: Record<\n string,\n NonNullable<BaseMessageFields['response_metadata']>\n >[]\n): t.MetadataAggregatorResult => {\n const collected = _collected || [];\n\n const handleLLMEnd: t.HandleLLMEnd = (output) => {\n const { generations } = output;\n const lastMessageOutput = (\n generations[generations.length - 1] as\n | (t.StreamGeneration | undefined)[]\n | undefined\n )?.[0];\n if (!lastMessageOutput) {\n return;\n }\n const { message } = lastMessageOutput;\n if (message?.response_metadata) {\n collected.push(message.response_metadata);\n }\n };\n\n return { handleLLMEnd, collected };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"events.mjs","names":[],"sources":["../../src/events.ts"],"sourcesContent":["/* eslint-disable no-console */\n// src/events.ts\nimport type {\n BaseMessageFields,\n UsageMetadata,\n} from '@langchain/core/messages';\nimport type { Logger } from 'winston';\nimport type { MultiAgentGraph, StandardGraph } from '@/graphs';\nimport type * as t from '@/types';\nimport { dispatchesChatModelStream, SDK_STREAM_DISPATCH } from '@/stream';\nimport { Constants } from '@/common';\n\nexport class HandlerRegistry {\n private handlers: Map<string, t.EventHandler> = new Map();\n\n register(eventType: string, handler: t.EventHandler): void {\n this.handlers.set(eventType, handler);\n }\n\n getHandler(eventType: string): t.EventHandler | undefined {\n return this.handlers.get(eventType);\n }\n}\n\nexport function composeEventHandlers(\n ...handlerSets: Array<Record<string, t.EventHandler> | undefined>\n): Record<string, t.EventHandler> {\n const composed: Partial<Record<string, t.EventHandler>> = {};\n\n for (const handlerSet of handlerSets) {\n if (!handlerSet) {\n continue;\n }\n for (const [eventType, handler] of Object.entries(handlerSet)) {\n const previous = composed[eventType];\n if (previous === undefined) {\n composed[eventType] = handler;\n continue;\n }\n const wrapper: t.EventHandler = {\n handle: async (\n ...args: Parameters<t.EventHandler['handle']>\n ): Promise<void> => {\n await previous.handle(...args);\n await handler.handle(...args);\n },\n };\n /**\n * Carry the stream-dispatch brand across the wrapper. Without this a\n * composed handler fails every capability check made about it — notably\n * the one deciding whether a run may seal — even though it still drives\n * the SDK's content-part dispatch.\n */\n if (\n dispatchesChatModelStream(previous) ||\n dispatchesChatModelStream(handler)\n ) {\n Object.defineProperty(wrapper, SDK_STREAM_DISPATCH, { value: true });\n }\n composed[eventType] = wrapper;\n }\n }\n\n return composed as Record<string, t.EventHandler>;\n}\n\nexport class ModelEndHandler implements t.EventHandler {\n collectedUsage?: UsageMetadata[];\n constructor(collectedUsage?: UsageMetadata[]) {\n if (collectedUsage && !Array.isArray(collectedUsage)) {\n throw new Error('collectedUsage must be an array');\n }\n this.collectedUsage = collectedUsage;\n }\n\n async handle(\n event: string,\n data: t.ModelEndData,\n metadata?: Record<string, unknown>,\n graph?: StandardGraph | MultiAgentGraph\n ): Promise<void> {\n if (!graph || !metadata) {\n console.warn(`Graph or metadata not found in ${event} event`);\n return;\n }\n\n const usage = data?.output?.usage_metadata;\n if (usage != null && this.collectedUsage != null) {\n this.collectedUsage.push(usage);\n }\n }\n}\n\nexport class ToolEndHandler implements t.EventHandler {\n private callback?: t.ToolEndCallback;\n private logger?: Logger;\n constructor(callback?: t.ToolEndCallback, logger?: Logger) {\n this.callback = callback;\n this.logger = logger;\n }\n\n /**\n * Handles on_tool_end events from the for-await stream consumer.\n *\n * This handler is now purely a consumer callback — tool completion\n * (ON_RUN_STEP_COMPLETED dispatch + session context storage) is handled\n * in graph context by ToolNode directly, eliminating the race between\n * the stream consumer and graph execution.\n */\n async handle(\n event: string,\n data: t.StreamEventData | undefined,\n metadata?: Record<string, unknown>,\n graph?: StandardGraph | MultiAgentGraph\n ): Promise<void> {\n try {\n if (!graph || !metadata) {\n if (this.logger) {\n this.logger.warn(`Graph or metadata not found in ${event} event`);\n } else {\n console.warn(`Graph or metadata not found in ${event} event`);\n }\n return;\n }\n\n const toolEndData = data as t.ToolEndData | undefined;\n if (!toolEndData?.output) {\n if (this.logger) {\n this.logger.warn('No output found in tool_end event');\n } else {\n console.warn('No output found in tool_end event');\n }\n return;\n }\n\n if (\n metadata[Constants.PROGRAMMATIC_TOOL_CALLING] === true ||\n metadata[Constants.BASH_PROGRAMMATIC_TOOL_CALLING] === true\n ) {\n return;\n }\n\n if (this.callback) {\n await this.callback(toolEndData, metadata);\n }\n } catch (error) {\n if (this.logger) {\n this.logger.error('Error handling tool_end event:', error);\n } else {\n console.error('Error handling tool_end event:', error);\n }\n }\n }\n}\n\nexport class TestLLMStreamHandler implements t.EventHandler {\n handle(event: string, data: t.StreamEventData | undefined): void {\n const chunk = data?.chunk;\n const isMessageChunk = !!(chunk && 'message' in chunk);\n const msg = isMessageChunk ? chunk.message : undefined;\n if (msg && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) {\n console.log(msg.tool_call_chunks);\n } else if (msg && typeof msg.content === 'string') {\n process.stdout.write(msg.content);\n }\n }\n}\n\nexport class TestChatStreamHandler implements t.EventHandler {\n handle(event: string, data: t.StreamEventData | undefined): void {\n const chunk = data?.chunk;\n const isContentChunk = !!(chunk && 'content' in chunk);\n if (!isContentChunk) {\n return;\n }\n\n const content = chunk.content;\n\n if (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) {\n console.dir(chunk.tool_call_chunks, { depth: null });\n }\n\n if (typeof content === 'string') {\n process.stdout.write(content);\n } else {\n console.dir(content, { depth: null });\n }\n }\n}\n\nexport class LLMStreamHandler implements t.EventHandler {\n handle(\n event: string,\n data: t.StreamEventData | undefined,\n metadata?: Record<string, unknown>\n ): void {\n const chunk = data?.chunk;\n const isMessageChunk = !!(chunk && 'message' in chunk);\n const msg = isMessageChunk ? chunk.message : undefined;\n if (metadata) {\n console.log(metadata);\n }\n if (msg && msg.tool_call_chunks && msg.tool_call_chunks.length > 0) {\n console.log(msg.tool_call_chunks);\n } else if (msg && typeof msg.content === 'string') {\n process.stdout.write(msg.content);\n }\n }\n}\n\nexport const createMetadataAggregator = (\n _collected?: Record<\n string,\n NonNullable<BaseMessageFields['response_metadata']>\n >[]\n): t.MetadataAggregatorResult => {\n const collected = _collected || [];\n\n const handleLLMEnd: t.HandleLLMEnd = (output) => {\n const { generations } = output;\n const lastMessageOutput = (\n generations[generations.length - 1] as\n | (t.StreamGeneration | undefined)[]\n | undefined\n )?.[0];\n if (!lastMessageOutput) {\n return;\n }\n const { message } = lastMessageOutput;\n if (message?.response_metadata) {\n collected.push(message.response_metadata);\n }\n };\n\n return { handleLLMEnd, collected };\n};\n"],"mappings":"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"}
|