@librechat/agents 3.3.3 → 3.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/agents/AgentContext.cjs +2 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- 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 +793 -111
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -1
- package/dist/cjs/graphs/MultiAgentGraph.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/anthropic/utils/message_inputs.cjs +79 -6
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +38 -13
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +19 -7
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +331 -5
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +41 -5
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +25 -4
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +4 -1
- package/dist/cjs/llm/openrouter/index.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 +36 -5
- package/dist/cjs/messages/alternation.cjs +92 -0
- package/dist/cjs/messages/alternation.cjs.map +1 -0
- package/dist/cjs/messages/cache.cjs +54 -33
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +17 -43
- package/dist/cjs/messages/contextPruning.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +315 -23
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +290 -60
- 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 +4 -1
- package/dist/cjs/messages/injected.cjs +60 -0
- package/dist/cjs/messages/injected.cjs.map +1 -0
- package/dist/cjs/messages/prune.cjs +503 -127
- package/dist/cjs/messages/prune.cjs.map +1 -1
- 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 +35 -10
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +28 -14
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +56 -63
- 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/cjs/tools/toolOutputReferences.cjs +6 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/utils/llm.cjs +1 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +307 -20
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/toolContent.cjs +1514 -0
- package/dist/cjs/utils/toolContent.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +2 -1
- package/dist/esm/agents/AgentContext.mjs.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 +798 -116
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +3 -1
- package/dist/esm/graphs/MultiAgentGraph.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/anthropic/utils/message_inputs.mjs +79 -6
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +38 -13
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +19 -7
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +332 -8
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +41 -5
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +25 -4
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +4 -1
- package/dist/esm/llm/openrouter/index.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 +14 -11
- package/dist/esm/messages/alternation.mjs +91 -0
- package/dist/esm/messages/alternation.mjs.map +1 -0
- package/dist/esm/messages/cache.mjs +55 -34
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +17 -43
- package/dist/esm/messages/contextPruning.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +305 -24
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +290 -61
- 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 +4 -1
- package/dist/esm/messages/injected.mjs +60 -0
- package/dist/esm/messages/injected.mjs.map +1 -0
- package/dist/esm/messages/prune.mjs +500 -128
- package/dist/esm/messages/prune.mjs.map +1 -1
- 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 +34 -11
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +28 -14
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +56 -63
- 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/esm/tools/toolOutputReferences.mjs +6 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/esm/utils/llm.mjs +1 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +307 -21
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/toolContent.mjs +1503 -0
- package/dist/esm/utils/toolContent.mjs.map +1 -0
- package/dist/types/common/constants.d.ts +18 -0
- package/dist/types/graphs/Graph.d.ts +139 -2
- 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 +34 -1
- package/dist/types/llm/openai/index.d.ts +4 -0
- package/dist/types/llm/openai/utils/index.d.ts +2 -0
- package/dist/types/llm/openrouter/index.d.ts +5 -0
- package/dist/types/llm/preempt.d.ts +40 -0
- package/dist/types/messages/alternation.d.ts +23 -0
- package/dist/types/messages/contextPruning.d.ts +2 -1
- package/dist/types/messages/core.d.ts +36 -0
- package/dist/types/messages/format.d.ts +6 -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/messages/prune.d.ts +16 -12
- 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 +1 -26
- package/dist/types/utils/tokens.d.ts +7 -0
- package/dist/types/utils/toolContent.d.ts +107 -0
- package/package.json +1 -1
- package/src/__tests__/stream.eagerEventExecution.test.ts +74 -0
- package/src/agents/AgentContext.ts +1 -0
- 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 +1261 -198
- package/src/graphs/MultiAgentGraph.ts +6 -1
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1139 -4
- 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/anthropic/utils/message_inputs.ts +158 -12
- package/src/llm/anthropic/utils/tool-id-normalization.test.ts +109 -0
- package/src/llm/bedrock/utils/message_inputs.test.ts +131 -1
- package/src/llm/bedrock/utils/message_inputs.ts +100 -24
- package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +15 -0
- package/src/llm/google/utils/common.test.ts +75 -9
- package/src/llm/google/utils/common.ts +34 -3
- package/src/llm/invoke.alternation.test.ts +87 -0
- package/src/llm/invoke.handoffCue.test.ts +112 -0
- package/src/llm/invoke.test.ts +488 -0
- package/src/llm/invoke.ts +611 -8
- package/src/llm/openai/index.ts +106 -6
- package/src/llm/openai/utils/index.ts +109 -53
- package/src/llm/openai/utils/messages.test.ts +330 -1
- package/src/llm/openrouter/index.ts +12 -2
- package/src/llm/openrouter/reasoning.test.ts +314 -0
- package/src/llm/preempt.test.ts +323 -0
- package/src/llm/preempt.ts +178 -0
- package/src/messages/__tests__/observationMasking.test.ts +93 -2
- package/src/messages/alternation.test.ts +212 -0
- package/src/messages/alternation.ts +112 -0
- package/src/messages/cache.tail.test.ts +193 -0
- package/src/messages/cache.test.ts +113 -0
- package/src/messages/cache.ts +92 -60
- package/src/messages/contextPruning.test.ts +184 -0
- package/src/messages/contextPruning.ts +49 -42
- package/src/messages/core.ts +653 -40
- package/src/messages/ensureThinkingBlock.test.ts +49 -1
- package/src/messages/foldToollessToolBlocks.test.ts +163 -5
- package/src/messages/format.ts +454 -91
- package/src/messages/formatAgentMessages.steer.test.ts +267 -0
- package/src/messages/formatAgentMessages.test.ts +604 -14
- package/src/messages/formatAgentMessages.tools.test.ts +103 -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/messages/prune.ts +996 -183
- 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/specs/prune.test.ts +1083 -6
- package/src/specs/summarization-unit.test.ts +105 -0
- package/src/specs/tokens.test.ts +609 -32
- package/src/stream.dispatch.test.ts +63 -0
- package/src/stream.ts +59 -23
- package/src/summarization/__tests__/node.test.ts +77 -0
- package/src/summarization/node.ts +46 -18
- package/src/tools/ToolNode.ts +150 -99
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +148 -1
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +112 -3
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +32 -0
- package/src/tools/__tests__/directToolHooks.test.ts +255 -7
- 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/tools/toolOutputReferences.ts +6 -0
- package/src/types/graph.ts +12 -1
- package/src/types/run.ts +67 -0
- package/src/types/stream.ts +1 -42
- package/src/utils/llm.test.ts +18 -0
- package/src/utils/llm.ts +4 -1
- package/src/utils/tokens.ts +586 -31
- package/src/utils/toolContent.test.ts +1432 -0
- package/src/utils/toolContent.ts +2707 -0
- 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,137 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import type * as t from './types';
|
|
3
|
+
import { createDefaultLogger } from './utils';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_KEENABLE_SCRAPE_TIMEOUT = 15000;
|
|
6
|
+
|
|
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
|
+
|
|
12
|
+
export class KeenableScraper implements t.BaseScraper {
|
|
13
|
+
private apiKey: string | undefined;
|
|
14
|
+
private apiUrl: string;
|
|
15
|
+
private timeout: number;
|
|
16
|
+
private attributionTitle: string;
|
|
17
|
+
private logger: t.Logger;
|
|
18
|
+
|
|
19
|
+
constructor(config: t.KeenableScraperConfig = {}) {
|
|
20
|
+
const resolvedKey = config.apiKey ?? process.env.KEENABLE_API_KEY;
|
|
21
|
+
this.apiKey =
|
|
22
|
+
resolvedKey != null && resolvedKey !== '' ? resolvedKey : undefined;
|
|
23
|
+
this.apiUrl =
|
|
24
|
+
config.apiUrl ??
|
|
25
|
+
process.env.KEENABLE_FETCH_URL ??
|
|
26
|
+
(this.apiKey != null
|
|
27
|
+
? KEENABLE_FETCH_API_URL
|
|
28
|
+
: KEENABLE_FETCH_PUBLIC_URL);
|
|
29
|
+
this.timeout = config.timeout ?? DEFAULT_KEENABLE_SCRAPE_TIMEOUT;
|
|
30
|
+
this.attributionTitle = config.attributionTitle ?? 'LibreChat';
|
|
31
|
+
this.logger = config.logger || createDefaultLogger();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private buildHeaders(): Record<string, string> {
|
|
35
|
+
/** X-Keenable-Title is used for traffic attribution and required on the
|
|
36
|
+
* keyless endpoint; the key only lifts rate limits, so it is sent only when
|
|
37
|
+
* present. */
|
|
38
|
+
const headers: Record<string, string> = {
|
|
39
|
+
'X-Keenable-Title': this.attributionTitle,
|
|
40
|
+
};
|
|
41
|
+
if (this.apiKey != null) {
|
|
42
|
+
headers['X-API-Key'] = this.apiKey;
|
|
43
|
+
}
|
|
44
|
+
return headers;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async scrapeUrl(
|
|
48
|
+
url: string,
|
|
49
|
+
options: t.KeenableScrapeOptions = {}
|
|
50
|
+
): Promise<[string, t.KeenableScrapeResponse]> {
|
|
51
|
+
if (!url || !url.trim()) {
|
|
52
|
+
return [url, { success: false, error: 'URL cannot be empty' }];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const response = await axios.get<t.KeenableFetchResult>(this.apiUrl, {
|
|
57
|
+
params: { url },
|
|
58
|
+
headers: this.buildHeaders(),
|
|
59
|
+
timeout: options.timeout ?? this.timeout,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const data = response.data;
|
|
63
|
+
const content = data.content ?? '';
|
|
64
|
+
if (!content) {
|
|
65
|
+
return [
|
|
66
|
+
url,
|
|
67
|
+
{ success: false, error: 'Keenable Fetch returned no content' },
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return [
|
|
72
|
+
url,
|
|
73
|
+
{
|
|
74
|
+
success: true,
|
|
75
|
+
data: {
|
|
76
|
+
content,
|
|
77
|
+
title: data.title,
|
|
78
|
+
description: data.description,
|
|
79
|
+
url: data.url ?? url,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const errorMessage =
|
|
85
|
+
error instanceof Error ? error.message : String(error);
|
|
86
|
+
return [
|
|
87
|
+
url,
|
|
88
|
+
{
|
|
89
|
+
success: false,
|
|
90
|
+
error: `Keenable Fetch API request failed: ${errorMessage}`,
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async scrapeUrls(
|
|
97
|
+
urls: string[],
|
|
98
|
+
options: t.KeenableScrapeOptions = {}
|
|
99
|
+
): Promise<Array<[string, t.KeenableScrapeResponse]>> {
|
|
100
|
+
/** Keenable fetch is single-URL; run the batch concurrently. */
|
|
101
|
+
return Promise.all(urls.map((url) => this.scrapeUrl(url, options)));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
extractContent(
|
|
105
|
+
response: t.KeenableScrapeResponse
|
|
106
|
+
): [string, undefined | t.References] {
|
|
107
|
+
if (!response.success || !response.data) {
|
|
108
|
+
return ['', undefined];
|
|
109
|
+
}
|
|
110
|
+
/** Keenable returns clean markdown with no separate media arrays, so there
|
|
111
|
+
* are no structured references to surface. */
|
|
112
|
+
return [response.data.content, undefined];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
extractMetadata(response: t.KeenableScrapeResponse): t.GenericScrapeMetadata {
|
|
116
|
+
if (!response.success || !response.data) {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
const metadata: t.GenericScrapeMetadata = {};
|
|
120
|
+
if (response.data.title != null) {
|
|
121
|
+
metadata.title = response.data.title;
|
|
122
|
+
}
|
|
123
|
+
if (response.data.description != null && response.data.description !== '') {
|
|
124
|
+
metadata.description = response.data.description;
|
|
125
|
+
}
|
|
126
|
+
if (response.data.url != null) {
|
|
127
|
+
metadata.url = response.data.url;
|
|
128
|
+
}
|
|
129
|
+
return metadata;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const createKeenableScraper = (
|
|
134
|
+
config: t.KeenableScraperConfig = {}
|
|
135
|
+
): KeenableScraper => {
|
|
136
|
+
return new KeenableScraper(config);
|
|
137
|
+
};
|
package/src/tools/search/tool.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
DATE_RANGE,
|
|
14
14
|
} from './schema';
|
|
15
15
|
import { createSearchAPI, createSourceProcessor } from './search';
|
|
16
|
+
import { createKeenableScraper } from './keenable-scraper';
|
|
16
17
|
import { createSerperScraper } from './serper-scraper';
|
|
17
18
|
import { createTavilyScraper } from './tavily-scraper';
|
|
18
19
|
import { createFirecrawlScraper } from './firecrawl';
|
|
@@ -378,6 +379,7 @@ export const createSearchTool = (
|
|
|
378
379
|
keenableApiKey,
|
|
379
380
|
keenableApiUrl,
|
|
380
381
|
keenableSearchOptions,
|
|
382
|
+
keenableScraperOptions,
|
|
381
383
|
rerankerType = 'cohere',
|
|
382
384
|
rerankerTimeout,
|
|
383
385
|
topResults = 5,
|
|
@@ -476,13 +478,22 @@ export const createSearchTool = (
|
|
|
476
478
|
} else if (scraperProvider === 'crw') {
|
|
477
479
|
scraperInstance = createCrwScraper({
|
|
478
480
|
...crwScraperOptions,
|
|
479
|
-
apiKey:
|
|
480
|
-
crwScraperOptions?.apiKey ?? crwApiKey ?? process.env.CRW_API_KEY,
|
|
481
|
+
apiKey: crwScraperOptions?.apiKey ?? crwApiKey ?? process.env.CRW_API_KEY,
|
|
481
482
|
apiUrl: crwScraperOptions?.apiUrl ?? crwApiUrl,
|
|
482
483
|
timeout: scraperTimeout ?? crwScraperOptions?.timeout,
|
|
483
484
|
formats: crwScraperOptions?.formats ?? ['markdown', 'rawHtml'],
|
|
484
485
|
logger,
|
|
485
486
|
});
|
|
487
|
+
} else if (scraperProvider === 'keenable') {
|
|
488
|
+
scraperInstance = createKeenableScraper({
|
|
489
|
+
...keenableScraperOptions,
|
|
490
|
+
apiKey: keenableScraperOptions?.apiKey ?? keenableApiKey,
|
|
491
|
+
timeout: scraperTimeout ?? keenableScraperOptions?.timeout,
|
|
492
|
+
attributionTitle:
|
|
493
|
+
keenableScraperOptions?.attributionTitle ??
|
|
494
|
+
keenableSearchOptions?.attributionTitle,
|
|
495
|
+
logger,
|
|
496
|
+
});
|
|
486
497
|
} else {
|
|
487
498
|
scraperInstance = createFirecrawlScraper({
|
|
488
499
|
...firecrawlOptions,
|
|
@@ -3,8 +3,18 @@ import type { Logger as WinstonLogger } from 'winston';
|
|
|
3
3
|
import type { BaseReranker } from './rerankers';
|
|
4
4
|
import { DATE_RANGE } from './schema';
|
|
5
5
|
|
|
6
|
-
export type SearchProvider =
|
|
7
|
-
|
|
6
|
+
export type SearchProvider =
|
|
7
|
+
| 'serper'
|
|
8
|
+
| 'searxng'
|
|
9
|
+
| 'tavily'
|
|
10
|
+
| 'keenable'
|
|
11
|
+
| 'crw';
|
|
12
|
+
export type ScraperProvider =
|
|
13
|
+
| 'firecrawl'
|
|
14
|
+
| 'serper'
|
|
15
|
+
| 'tavily'
|
|
16
|
+
| 'crw'
|
|
17
|
+
| 'keenable';
|
|
8
18
|
export type RerankerType = 'infinity' | 'jina' | 'cohere' | 'none';
|
|
9
19
|
|
|
10
20
|
export interface Highlight {
|
|
@@ -198,6 +208,41 @@ export interface KeenableSearchResponse {
|
|
|
198
208
|
results?: KeenableSearchResult[];
|
|
199
209
|
}
|
|
200
210
|
|
|
211
|
+
export interface KeenableScraperConfig {
|
|
212
|
+
apiKey?: string;
|
|
213
|
+
/** Override the fetch endpoint base (default: public keyless, keyed when a
|
|
214
|
+
* key is set). Env fallback: KEENABLE_FETCH_URL. */
|
|
215
|
+
apiUrl?: string;
|
|
216
|
+
timeout?: number;
|
|
217
|
+
logger?: Logger;
|
|
218
|
+
/** Sent as the X-Keenable-Title attribution header. Defaults to "LibreChat". */
|
|
219
|
+
attributionTitle?: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export type KeenableScrapeOptions = Omit<
|
|
223
|
+
KeenableScraperConfig,
|
|
224
|
+
'apiKey' | 'apiUrl' | 'logger'
|
|
225
|
+
>;
|
|
226
|
+
|
|
227
|
+
/** Raw JSON shape returned by GET /v1/fetch{,/public}?url=... */
|
|
228
|
+
export interface KeenableFetchResult {
|
|
229
|
+
url?: string;
|
|
230
|
+
title?: string;
|
|
231
|
+
content?: string;
|
|
232
|
+
description?: string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface KeenableScrapeResponse {
|
|
236
|
+
success: boolean;
|
|
237
|
+
data?: {
|
|
238
|
+
content: string;
|
|
239
|
+
title?: string;
|
|
240
|
+
description?: string;
|
|
241
|
+
url?: string;
|
|
242
|
+
};
|
|
243
|
+
error?: string;
|
|
244
|
+
}
|
|
245
|
+
|
|
201
246
|
export type References = {
|
|
202
247
|
links: MediaReference[];
|
|
203
248
|
images: MediaReference[];
|
|
@@ -311,6 +356,7 @@ export interface SearchToolConfig
|
|
|
311
356
|
FirecrawlConfig {
|
|
312
357
|
tavilyScraperOptions?: TavilyScraperConfig;
|
|
313
358
|
crwScraperOptions?: CrwScraperConfig;
|
|
359
|
+
keenableScraperOptions?: KeenableScraperConfig;
|
|
314
360
|
/** Max chars of highlight content this tool feeds the MODEL per search (the
|
|
315
361
|
* dominant, otherwise-unbounded part of the output). Distinct from
|
|
316
362
|
* `maxContentLength`, which caps scraped/reranked content per source — full
|
|
@@ -351,7 +397,8 @@ export type AnyScraperResponse =
|
|
|
351
397
|
| FirecrawlScrapeResponse
|
|
352
398
|
| SerperScrapeResponse
|
|
353
399
|
| TavilyScrapeResponse
|
|
354
|
-
| CrwScrapeResponse
|
|
400
|
+
| CrwScrapeResponse
|
|
401
|
+
| KeenableScrapeResponse;
|
|
355
402
|
|
|
356
403
|
/** Base Scraper Interface */
|
|
357
404
|
export interface BaseScraper {
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
HARD_MAX_TOOL_RESULT_CHARS,
|
|
30
30
|
HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE,
|
|
31
31
|
} from '@/utils/truncation';
|
|
32
|
+
import { isComputerCallOutputMessage } from '@/utils/toolContent';
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Non-global matcher for a single `{{tool<i>turn<n>}}` placeholder.
|
|
@@ -656,6 +657,11 @@ export function annotateMessagesForLLM(
|
|
|
656
657
|
const hasRefScope = '_refScope' in meta;
|
|
657
658
|
const hasUnresolvedField = '_unresolvedRefs' in meta;
|
|
658
659
|
if (!hasRefKey && !hasRefScope && !hasUnresolvedField) continue;
|
|
660
|
+
if (isComputerCallOutputMessage(m)) {
|
|
661
|
+
out ??= messages.slice();
|
|
662
|
+
out[i] = cloneToolMessageWithContent(m as ToolMessage, m.content);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
659
665
|
|
|
660
666
|
const refKey = readRefKey(meta);
|
|
661
667
|
const unresolved = readUnresolvedRefs(meta);
|
package/src/types/graph.ts
CHANGED
|
@@ -30,7 +30,11 @@ import type {
|
|
|
30
30
|
MessageDeltaEvent,
|
|
31
31
|
ReasoningDeltaEvent,
|
|
32
32
|
} from '@/types/stream';
|
|
33
|
-
import type {
|
|
33
|
+
import type {
|
|
34
|
+
TokenCounter,
|
|
35
|
+
StreamPreemption,
|
|
36
|
+
TokenBudgetBreakdown,
|
|
37
|
+
} from '@/types/run';
|
|
34
38
|
import type { Providers, Callback, GraphNodeKeys } from '@/common';
|
|
35
39
|
import type { StandardGraph, MultiAgentGraph } from '@/graphs';
|
|
36
40
|
import type { ClientOptions } from '@/types/llm';
|
|
@@ -346,6 +350,13 @@ export type StandardGraphInput = {
|
|
|
346
350
|
* hook inputs carry only `executingAgentId`.
|
|
347
351
|
*/
|
|
348
352
|
subagentScope?: boolean;
|
|
353
|
+
/**
|
|
354
|
+
* Cooperative preemption, forwarded from `RunConfig.preemption`. Only ever
|
|
355
|
+
* set on the top-level graph: a steer targets the conversation, so subagent
|
|
356
|
+
* children must run to completion and `buildChildInputs` does not propagate
|
|
357
|
+
* this field.
|
|
358
|
+
*/
|
|
359
|
+
preemption?: StreamPreemption;
|
|
349
360
|
};
|
|
350
361
|
|
|
351
362
|
export type GraphEdge = {
|
package/src/types/run.ts
CHANGED
|
@@ -115,6 +115,65 @@ export type StandardGraphConfig = Omit<
|
|
|
115
115
|
'edges' | 'type'
|
|
116
116
|
> & { type?: 'standard'; signal?: AbortSignal };
|
|
117
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Cooperative mid-generation preemption. Lets a host seal the live model
|
|
120
|
+
* stream at the next provider-safe token boundary — the run is never
|
|
121
|
+
* aborted, the partial assistant turn is kept, and the graph self-loops
|
|
122
|
+
* into a fresh model call once the `PreemptBoundary` hook has injected
|
|
123
|
+
* whatever the host queued.
|
|
124
|
+
*
|
|
125
|
+
* Preconditions the host MUST satisfy:
|
|
126
|
+
* - `shouldPreempt` is polled once per streamed chunk on the top-level
|
|
127
|
+
* graph. It must be synchronous, allocation-free and O(1) — never I/O.
|
|
128
|
+
* It must also be LEVEL-TRIGGERED (non-consuming): the SDK never clears
|
|
129
|
+
* the host's request, and a true result is only honored once the
|
|
130
|
+
* accumulated chunk is provider-safe, so the predicate may be polled
|
|
131
|
+
* many times before a seal. A one-shot read that clears its own pending
|
|
132
|
+
* flag would silently lose the request on an unsafe chunk (leading
|
|
133
|
+
* whitespace/reasoning, an in-flight tool call) — keep returning true
|
|
134
|
+
* until the `PreemptBoundary` drain hands over the queued injection,
|
|
135
|
+
* then disarm there.
|
|
136
|
+
* - Sealing is only honored on the SDK's own dispatch loop. A run whose
|
|
137
|
+
* registered `CHAT_MODEL_STREAM` handler IS the SDK dispatcher — or wraps
|
|
138
|
+
* it, which `composeEventHandlers` and `createRunHandlers` both do —
|
|
139
|
+
* consumes chunks through a decoupled `streamEvents` reader that can lag
|
|
140
|
+
* the accumulated chunk, so those runs never seal.
|
|
141
|
+
*
|
|
142
|
+
* Detection is by capability, not identity: the dispatcher carries
|
|
143
|
+
* `SDK_STREAM_DISPATCH` and the SDK's wrappers propagate it. A handler
|
|
144
|
+
* that merely OBSERVES the raw chunk echo — LibreChat's no-op
|
|
145
|
+
* `OpenAIChatModelStreamHandler`, say — is unbranded and does NOT disable
|
|
146
|
+
* sealing, because it assigns no content-part indices and so cannot be
|
|
147
|
+
* inverted. A host that renders from the raw feed and wants the opt-out
|
|
148
|
+
* should brand its handler with `SDK_STREAM_DISPATCH`.
|
|
149
|
+
* - `RunConfig.tokenCounter` should be set. A sealed turn ends before most
|
|
150
|
+
* providers send their usage chunk, so the synthetic `CHAT_MODEL_END`
|
|
151
|
+
* falls back to the counter to report `output_tokens`. Without one that
|
|
152
|
+
* fallback silently no-ops and the sealed turn's usage is lost — verified
|
|
153
|
+
* live: OpenAI, Azure OpenAI and DeepSeek report no usage for the sealed
|
|
154
|
+
* segment without a counter, while Anthropic streams usage incrementally
|
|
155
|
+
* and reports it either way.
|
|
156
|
+
*/
|
|
157
|
+
export interface StreamPreemption {
|
|
158
|
+
/**
|
|
159
|
+
* Polled once per streamed chunk. Synchronous, allocation-free, O(1), and
|
|
160
|
+
* level-triggered — keep returning true until the `PreemptBoundary` drain
|
|
161
|
+
* consumes the request; a self-clearing read loses it on an unsafe chunk.
|
|
162
|
+
*/
|
|
163
|
+
shouldPreempt: () => boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Max cooperative seals per run. Each seal costs one extra superstep, so
|
|
166
|
+
* this also bounds the recursion-limit headroom the run reserves.
|
|
167
|
+
*/
|
|
168
|
+
maxSeals?: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Seals honored and boundaries that had nothing to inject, per run. */
|
|
172
|
+
export type PreemptStats = {
|
|
173
|
+
seals: number;
|
|
174
|
+
emptyBoundaries: number;
|
|
175
|
+
};
|
|
176
|
+
|
|
118
177
|
export type RunConfig = {
|
|
119
178
|
runId: string;
|
|
120
179
|
graphConfig: LegacyGraphConfig | StandardGraphConfig | MultiAgentGraphConfig;
|
|
@@ -146,6 +205,14 @@ export type RunConfig = {
|
|
|
146
205
|
* block to prevent leaks.
|
|
147
206
|
*/
|
|
148
207
|
hooks?: HookRegistry;
|
|
208
|
+
/**
|
|
209
|
+
* Opt-in cooperative preemption for this run. Requires a `hooks` registry
|
|
210
|
+
* with a `PreemptBoundary` matcher — the seal only stops the stream, the
|
|
211
|
+
* hook is what supplies the messages to resume with. Omit to keep the
|
|
212
|
+
* pre-preemption behavior, where a mid-run injection can only land at a
|
|
213
|
+
* tool boundary.
|
|
214
|
+
*/
|
|
215
|
+
preemption?: StreamPreemption;
|
|
149
216
|
returnContent?: boolean;
|
|
150
217
|
tokenCounter?: TokenCounter;
|
|
151
218
|
indexTokenCountMap?: Record<string, number>;
|
package/src/types/stream.ts
CHANGED
|
@@ -8,7 +8,6 @@ import type {
|
|
|
8
8
|
import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
|
|
9
9
|
import type { LLMResult, Generation } from '@langchain/core/outputs';
|
|
10
10
|
import type { Command } from '@langchain/langgraph';
|
|
11
|
-
import type OpenAITypes from 'openai';
|
|
12
11
|
import type { AnthropicContentBlock } from '@/llm/anthropic/types';
|
|
13
12
|
import type { SummarizeCompleteEvent } from '@/types/summarize';
|
|
14
13
|
import type { ToolEndEvent } from '@/types/tools';
|
|
@@ -336,7 +335,7 @@ export type ToolCallPart = {
|
|
|
336
335
|
/** If provided, an identifier associated with the tool call */
|
|
337
336
|
id?: string;
|
|
338
337
|
/** If provided, the output of the tool call */
|
|
339
|
-
output?:
|
|
338
|
+
output?: ToolResultContent['content'];
|
|
340
339
|
/** Auth URL */
|
|
341
340
|
auth?: string;
|
|
342
341
|
/** Expiration time */
|
|
@@ -393,46 +392,6 @@ export interface TMessage {
|
|
|
393
392
|
|
|
394
393
|
export type TPayload = Array<Partial<TMessage>>;
|
|
395
394
|
|
|
396
|
-
export type CustomChunkDelta =
|
|
397
|
-
| null
|
|
398
|
-
| undefined
|
|
399
|
-
| (Partial<OpenAITypes.Chat.Completions.ChatCompletionChunk.Choice.Delta> & {
|
|
400
|
-
reasoning?: string | null;
|
|
401
|
-
reasoning_content?: string | null;
|
|
402
|
-
});
|
|
403
|
-
export type CustomChunkChoice = Partial<
|
|
404
|
-
Omit<OpenAITypes.Chat.Completions.ChatCompletionChunk.Choice, 'delta'> & {
|
|
405
|
-
delta?: CustomChunkDelta;
|
|
406
|
-
}
|
|
407
|
-
>;
|
|
408
|
-
export type CustomChunk = Partial<OpenAITypes.ChatCompletionChunk> & {
|
|
409
|
-
choices?: Partial<Array<CustomChunkChoice>>;
|
|
410
|
-
};
|
|
411
|
-
|
|
412
|
-
export type SplitStreamHandlers = Partial<{
|
|
413
|
-
[GraphEvents.ON_RUN_STEP]: ({
|
|
414
|
-
event,
|
|
415
|
-
data,
|
|
416
|
-
}: {
|
|
417
|
-
event: GraphEvents;
|
|
418
|
-
data: RunStep;
|
|
419
|
-
}) => void;
|
|
420
|
-
[GraphEvents.ON_MESSAGE_DELTA]: ({
|
|
421
|
-
event,
|
|
422
|
-
data,
|
|
423
|
-
}: {
|
|
424
|
-
event: GraphEvents;
|
|
425
|
-
data: MessageDeltaEvent;
|
|
426
|
-
}) => void;
|
|
427
|
-
[GraphEvents.ON_REASONING_DELTA]: ({
|
|
428
|
-
event,
|
|
429
|
-
data,
|
|
430
|
-
}: {
|
|
431
|
-
event: GraphEvents;
|
|
432
|
-
data: ReasoningDeltaEvent;
|
|
433
|
-
}) => void;
|
|
434
|
-
}>;
|
|
435
|
-
|
|
436
395
|
export type SummarizeDeltaData = {
|
|
437
396
|
id: string;
|
|
438
397
|
delta: {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isAnthropicLike } from './llm';
|
|
2
|
+
import { Providers } from '@/common';
|
|
3
|
+
|
|
4
|
+
describe('isAnthropicLike', () => {
|
|
5
|
+
it('treats the default Bedrock model as Claude', () => {
|
|
6
|
+
expect(isAnthropicLike(Providers.BEDROCK)).toBe(true);
|
|
7
|
+
expect(
|
|
8
|
+
isAnthropicLike(Providers.BEDROCK, {
|
|
9
|
+
model: 'anthropic.claude-sonnet-4-5',
|
|
10
|
+
})
|
|
11
|
+
).toBe(true);
|
|
12
|
+
expect(
|
|
13
|
+
isAnthropicLike(Providers.BEDROCK, {
|
|
14
|
+
model: 'amazon.nova-pro-v1:0',
|
|
15
|
+
})
|
|
16
|
+
).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
});
|
package/src/utils/llm.ts
CHANGED
|
@@ -32,7 +32,10 @@ export function isAnthropicLike(
|
|
|
32
32
|
): boolean {
|
|
33
33
|
if (provider === Providers.ANTHROPIC) return true;
|
|
34
34
|
if (provider === Providers.BEDROCK) {
|
|
35
|
-
return
|
|
35
|
+
return (
|
|
36
|
+
clientOptions?.model == null ||
|
|
37
|
+
/claude/i.test(String(clientOptions.model))
|
|
38
|
+
);
|
|
36
39
|
}
|
|
37
40
|
return false;
|
|
38
41
|
}
|