@librechat/agents 3.3.4 → 3.3.6
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/createWorkspacePolicyHook.cjs +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/openai/index.cjs +51 -6
- package/dist/cjs/llm/openai/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 +31 -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/JsonlSessionStore.cjs +1 -1
- package/dist/cjs/session/handlers.cjs +18 -9
- package/dist/cjs/session/handlers.cjs.map +1 -1
- package/dist/cjs/stream.cjs +34 -8
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +3 -1
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -0
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +19 -16
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +2 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ReadFile.cjs +8 -4
- package/dist/cjs/tools/ReadFile.cjs.map +1 -1
- package/dist/cjs/tools/SkillTool.cjs +2 -0
- package/dist/cjs/tools/SkillTool.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +3 -0
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +91 -50
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +5 -1
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/intentArg.cjs +230 -0
- package/dist/cjs/tools/intentArg.cjs.map +1 -0
- package/dist/cjs/tools/local/CompileCheckTool.cjs +3 -2
- package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +13 -12
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +2 -2
- package/dist/cjs/tools/ptcTimeout.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/schema.cjs +2 -0
- package/dist/cjs/tools/search/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +40 -2
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +41 -7
- package/dist/cjs/tools/toolOutputReferences.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/createWorkspacePolicyHook.mjs +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/openai/index.mjs +51 -6
- package/dist/esm/llm/openai/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 +11 -7
- 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/JsonlSessionStore.mjs +1 -1
- package/dist/esm/session/handlers.mjs +19 -10
- package/dist/esm/session/handlers.mjs.map +1 -1
- package/dist/esm/stream.mjs +33 -9
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -1
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -0
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +19 -16
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +2 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ReadFile.mjs +8 -4
- package/dist/esm/tools/ReadFile.mjs.map +1 -1
- package/dist/esm/tools/SkillTool.mjs +2 -0
- package/dist/esm/tools/SkillTool.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +3 -0
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +91 -51
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +5 -1
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/intentArg.mjs +220 -0
- package/dist/esm/tools/intentArg.mjs.map +1 -0
- package/dist/esm/tools/local/CompileCheckTool.mjs +3 -2
- package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +13 -12
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +2 -2
- package/dist/esm/tools/ptcTimeout.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/schema.mjs +2 -0
- package/dist/esm/tools/search/schema.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +40 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +41 -7
- package/dist/esm/tools/toolOutputReferences.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 +2 -1
- package/dist/types/llm/invoke.d.ts +7 -0
- package/dist/types/llm/openai/index.d.ts +0 -11
- 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/BashExecutor.d.ts +18 -0
- package/dist/types/tools/CodeExecutor.d.ts +18 -0
- package/dist/types/tools/ReadFile.d.ts +18 -0
- package/dist/types/tools/SkillTool.d.ts +18 -0
- package/dist/types/tools/SubagentTool.d.ts +9 -0
- package/dist/types/tools/ToolNode.d.ts +37 -12
- package/dist/types/tools/ToolSearch.d.ts +18 -0
- package/dist/types/tools/intentArg.d.ts +119 -0
- package/dist/types/tools/ptcTimeout.d.ts +2 -0
- package/dist/types/tools/search/keenable-scraper.d.ts +15 -0
- package/dist/types/tools/search/schema.d.ts +18 -0
- package/dist/types/tools/search/tool.d.ts +18 -0
- package/dist/types/tools/search/types.d.ts +31 -2
- package/dist/types/tools/toolOutputReferences.d.ts +12 -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 +12 -25
- package/dist/types/types/tools.d.ts +27 -2
- 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 +2 -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/openai/index.ts +74 -10
- package/src/llm/openai/llm.spec.ts +105 -0
- 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 +48 -9
- package/src/tools/BashExecutor.ts +6 -0
- package/src/tools/BashProgrammaticToolCalling.ts +2 -0
- package/src/tools/CodeExecutor.ts +6 -0
- package/src/tools/ProgrammaticToolCalling.ts +2 -0
- package/src/tools/ReadFile.ts +2 -0
- package/src/tools/SkillTool.ts +2 -0
- package/src/tools/SubagentTool.ts +3 -0
- package/src/tools/ToolNode.ts +193 -74
- package/src/tools/ToolSearch.ts +3 -0
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +174 -0
- package/src/tools/__tests__/intentArg.test.ts +281 -0
- package/src/tools/__tests__/intentCoverage.test.ts +139 -0
- package/src/tools/__tests__/toolOutputReferences.test.ts +83 -0
- package/src/tools/intentArg.ts +323 -0
- package/src/tools/local/CompileCheckTool.ts +4 -3
- package/src/tools/local/LocalCodingTools.ts +14 -13
- package/src/tools/ptcTimeout.ts +2 -0
- package/src/tools/search/keenable-scraper.test.ts +153 -0
- package/src/tools/search/keenable-scraper.ts +137 -0
- package/src/tools/search/outcome.test.ts +90 -0
- package/src/tools/search/schema.ts +3 -0
- package/src/tools/search/tool.ts +60 -3
- package/src/tools/search/types.ts +50 -3
- package/src/tools/subagent/SubagentExecutor.ts +2 -1
- package/src/tools/toolOutputReferences.ts +77 -9
- package/src/types/graph.ts +12 -1
- package/src/types/run.ts +67 -0
- package/src/types/stream.ts +12 -41
- package/src/types/tools.ts +24 -2
- 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
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, it, expect } from '@jest/globals';
|
|
2
|
+
import type * as t from './types';
|
|
3
|
+
import { resolveSearchOutcome } from './tool';
|
|
4
|
+
|
|
5
|
+
const data = (partial: Partial<t.SearchResultData>): t.SearchResultData =>
|
|
6
|
+
({ turn: 0, ...partial }) as t.SearchResultData;
|
|
7
|
+
|
|
8
|
+
describe('resolveSearchOutcome', () => {
|
|
9
|
+
it('authors a FAILURE label when the processor caught an error', () => {
|
|
10
|
+
expect(
|
|
11
|
+
resolveSearchOutcome(
|
|
12
|
+
data({ error: 'provider timeout', organic: [] }),
|
|
13
|
+
'oauth handling'
|
|
14
|
+
)
|
|
15
|
+
).toBe('Search failed for "oauth handling"');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('prefers the failure label over any partial results', () => {
|
|
19
|
+
expect(
|
|
20
|
+
resolveSearchOutcome(
|
|
21
|
+
data({
|
|
22
|
+
error: 'partial failure',
|
|
23
|
+
organic: [{ link: 'a' }] as t.SearchResultData['organic'],
|
|
24
|
+
}),
|
|
25
|
+
'oauth'
|
|
26
|
+
)
|
|
27
|
+
).toBe('Search failed for "oauth"');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('ignores an empty-string error', () => {
|
|
31
|
+
expect(
|
|
32
|
+
resolveSearchOutcome(
|
|
33
|
+
data({
|
|
34
|
+
error: '',
|
|
35
|
+
organic: [{ link: 'a' }] as t.SearchResultData['organic'],
|
|
36
|
+
}),
|
|
37
|
+
'oauth'
|
|
38
|
+
)
|
|
39
|
+
).toBe('Found 1 result for "oauth"');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('counts every rendered collection kind', () => {
|
|
43
|
+
expect(
|
|
44
|
+
resolveSearchOutcome(
|
|
45
|
+
data({
|
|
46
|
+
organic: [
|
|
47
|
+
{ link: 'a' },
|
|
48
|
+
{ link: 'b' },
|
|
49
|
+
] as t.SearchResultData['organic'],
|
|
50
|
+
topStories: [{ link: 'c' }] as t.SearchResultData['topStories'],
|
|
51
|
+
images: [{ imageUrl: 'd' }] as t.SearchResultData['images'],
|
|
52
|
+
videos: [{ link: 'e' }] as t.SearchResultData['videos'],
|
|
53
|
+
places: [{ name: 'f' }] as t.SearchResultData['places'],
|
|
54
|
+
peopleAlsoAsk: [
|
|
55
|
+
{ question: 'g' },
|
|
56
|
+
] as t.SearchResultData['peopleAlsoAsk'],
|
|
57
|
+
}),
|
|
58
|
+
'oauth'
|
|
59
|
+
)
|
|
60
|
+
).toBe('Found 7 results for "oauth"');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('counts singleton structured results (knowledge graph / answer box)', () => {
|
|
64
|
+
expect(
|
|
65
|
+
resolveSearchOutcome(
|
|
66
|
+
data({
|
|
67
|
+
knowledgeGraph: {
|
|
68
|
+
title: 'OAuth',
|
|
69
|
+
} as t.SearchResultData['knowledgeGraph'],
|
|
70
|
+
}),
|
|
71
|
+
'oauth'
|
|
72
|
+
)
|
|
73
|
+
).toBe('Found 1 result for "oauth"');
|
|
74
|
+
expect(
|
|
75
|
+
resolveSearchOutcome(
|
|
76
|
+
data({
|
|
77
|
+
answerBox: { snippet: 'x' } as t.SearchResultData['answerBox'],
|
|
78
|
+
knowledgeGraph: {
|
|
79
|
+
title: 'OAuth',
|
|
80
|
+
} as t.SearchResultData['knowledgeGraph'],
|
|
81
|
+
}),
|
|
82
|
+
'oauth'
|
|
83
|
+
)
|
|
84
|
+
).toBe('Found 2 results for "oauth"');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('leaves a genuine zero-result search to the mechanical transform', () => {
|
|
88
|
+
expect(resolveSearchOutcome(data({ organic: [] }), 'oauth')).toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
2
|
+
|
|
1
3
|
export enum DATE_RANGE {
|
|
2
4
|
PAST_HOUR = 'h',
|
|
3
5
|
PAST_24_HOURS = 'd',
|
|
@@ -71,6 +73,7 @@ export const newsSchema = {
|
|
|
71
73
|
export const WebSearchToolSchema = {
|
|
72
74
|
type: 'object',
|
|
73
75
|
properties: {
|
|
76
|
+
intent: { ...INTENT_PROPERTY },
|
|
74
77
|
query: querySchema,
|
|
75
78
|
date: dateSchema,
|
|
76
79
|
country: countrySchema,
|
package/src/tools/search/tool.ts
CHANGED
|
@@ -13,9 +13,11 @@ 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';
|
|
20
|
+
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
19
21
|
import { createCrwScraper } from './crw-scraper';
|
|
20
22
|
import { expandHighlights } from './highlights';
|
|
21
23
|
import { formatResultsForLLM } from './format';
|
|
@@ -23,6 +25,46 @@ import { createDefaultLogger } from './utils';
|
|
|
23
25
|
import { createReranker } from './rerankers';
|
|
24
26
|
import { Constants } from '@/common';
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Settled label for a `web_search` call's intent (see `intentArg.ts`).
|
|
30
|
+
*
|
|
31
|
+
* Counts the result kinds `formatResultsForLLM` actually renders —
|
|
32
|
+
* `references` only tracks links embedded in extracted highlights, so it
|
|
33
|
+
* undercounts ordinary results and can overcount when one highlight embeds
|
|
34
|
+
* several links.
|
|
35
|
+
*
|
|
36
|
+
* A caught provider or processing failure is reported through `data.error`
|
|
37
|
+
* while the tool still returns NORMALLY, so that case must author its own
|
|
38
|
+
* label: the `ToolMessage` carries success status, and a bare intent would
|
|
39
|
+
* otherwise settle mechanically from "Searching…" to "Searched…" and present
|
|
40
|
+
* a failed search as a successful one.
|
|
41
|
+
*
|
|
42
|
+
* Returns undefined for a genuine zero-result search, leaving the host's
|
|
43
|
+
* mechanical past-tense transform to label it.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveSearchOutcome(
|
|
46
|
+
data: t.SearchResultData,
|
|
47
|
+
query: string
|
|
48
|
+
): string | undefined {
|
|
49
|
+
if (data.error != null && data.error !== '') {
|
|
50
|
+
return `Search failed for "${query}"`;
|
|
51
|
+
}
|
|
52
|
+
const count =
|
|
53
|
+
(data.organic?.length ?? 0) +
|
|
54
|
+
(data.topStories?.length ?? 0) +
|
|
55
|
+
(data.news?.length ?? 0) +
|
|
56
|
+
(data.images?.length ?? 0) +
|
|
57
|
+
(data.videos?.length ?? 0) +
|
|
58
|
+
(data.places?.length ?? 0) +
|
|
59
|
+
(data.peopleAlsoAsk?.length ?? 0) +
|
|
60
|
+
(data.knowledgeGraph != null ? 1 : 0) +
|
|
61
|
+
(data.answerBox != null ? 1 : 0);
|
|
62
|
+
if (count === 0) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return `Found ${count} result${count === 1 ? '' : 's'} for "${query}"`;
|
|
66
|
+
}
|
|
67
|
+
|
|
26
68
|
/**
|
|
27
69
|
* Executes parallel searches and merges the results,
|
|
28
70
|
* deduplicating top stories by link
|
|
@@ -334,7 +376,11 @@ function createTool({
|
|
|
334
376
|
maxOutputChars
|
|
335
377
|
);
|
|
336
378
|
const data: t.SearchResultData = { turn, ...searchResult, references };
|
|
337
|
-
|
|
379
|
+
const outcome = resolveSearchOutcome(data, query);
|
|
380
|
+
return [
|
|
381
|
+
output,
|
|
382
|
+
{ [Constants.WEB_SEARCH]: data, ...(outcome != null && { outcome }) },
|
|
383
|
+
];
|
|
338
384
|
},
|
|
339
385
|
{
|
|
340
386
|
name: WebSearchToolName,
|
|
@@ -378,6 +424,7 @@ export const createSearchTool = (
|
|
|
378
424
|
keenableApiKey,
|
|
379
425
|
keenableApiUrl,
|
|
380
426
|
keenableSearchOptions,
|
|
427
|
+
keenableScraperOptions,
|
|
381
428
|
rerankerType = 'cohere',
|
|
382
429
|
rerankerTimeout,
|
|
383
430
|
topResults = 5,
|
|
@@ -419,6 +466,7 @@ export const createSearchTool = (
|
|
|
419
466
|
: tavilySearchOptions;
|
|
420
467
|
|
|
421
468
|
const schemaProperties: Record<string, unknown> = {
|
|
469
|
+
intent: { ...INTENT_PROPERTY },
|
|
422
470
|
query: querySchema,
|
|
423
471
|
date: dateSchema,
|
|
424
472
|
images: imagesSchema,
|
|
@@ -476,13 +524,22 @@ export const createSearchTool = (
|
|
|
476
524
|
} else if (scraperProvider === 'crw') {
|
|
477
525
|
scraperInstance = createCrwScraper({
|
|
478
526
|
...crwScraperOptions,
|
|
479
|
-
apiKey:
|
|
480
|
-
crwScraperOptions?.apiKey ?? crwApiKey ?? process.env.CRW_API_KEY,
|
|
527
|
+
apiKey: crwScraperOptions?.apiKey ?? crwApiKey ?? process.env.CRW_API_KEY,
|
|
481
528
|
apiUrl: crwScraperOptions?.apiUrl ?? crwApiUrl,
|
|
482
529
|
timeout: scraperTimeout ?? crwScraperOptions?.timeout,
|
|
483
530
|
formats: crwScraperOptions?.formats ?? ['markdown', 'rawHtml'],
|
|
484
531
|
logger,
|
|
485
532
|
});
|
|
533
|
+
} else if (scraperProvider === 'keenable') {
|
|
534
|
+
scraperInstance = createKeenableScraper({
|
|
535
|
+
...keenableScraperOptions,
|
|
536
|
+
apiKey: keenableScraperOptions?.apiKey ?? keenableApiKey,
|
|
537
|
+
timeout: scraperTimeout ?? keenableScraperOptions?.timeout,
|
|
538
|
+
attributionTitle:
|
|
539
|
+
keenableScraperOptions?.attributionTitle ??
|
|
540
|
+
keenableSearchOptions?.attributionTitle,
|
|
541
|
+
logger,
|
|
542
|
+
});
|
|
486
543
|
} else {
|
|
487
544
|
scraperInstance = createFirecrawlScraper({
|
|
488
545
|
...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 {
|
|
@@ -115,7 +115,7 @@ type SanitizedStepCompleted =
|
|
|
115
115
|
};
|
|
116
116
|
|
|
117
117
|
type SanitizedProcessedToolCall = Partial<
|
|
118
|
-
Pick<ProcessedToolCall, 'args' | 'id' | 'name' | 'output' | 'progress'>
|
|
118
|
+
Pick<ProcessedToolCall, 'args' | 'id' | 'name' | 'output' | 'progress' | 'outcome'>
|
|
119
119
|
>;
|
|
120
120
|
|
|
121
121
|
type SanitizedRunStepCompleted = {
|
|
@@ -1195,6 +1195,7 @@ function sanitizeProcessedToolCall(
|
|
|
1195
1195
|
sanitized.args = call.args;
|
|
1196
1196
|
}
|
|
1197
1197
|
assignString(sanitized, 'output', call.output);
|
|
1198
|
+
assignString(sanitized, 'outcome', call.outcome);
|
|
1198
1199
|
assignNumber(sanitized, 'progress', call.progress);
|
|
1199
1200
|
return sanitized;
|
|
1200
1201
|
}
|
|
@@ -30,6 +30,25 @@ import {
|
|
|
30
30
|
HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE,
|
|
31
31
|
} from '@/utils/truncation';
|
|
32
32
|
import { isComputerCallOutputMessage } from '@/utils/toolContent';
|
|
33
|
+
import { INTENT_ARG } from '@/tools/intentArg';
|
|
34
|
+
|
|
35
|
+
/** Parses a stringified JSON object arg; undefined for anything else. */
|
|
36
|
+
function parseStringifiedArgsObject(
|
|
37
|
+
value: string
|
|
38
|
+
): Record<string, unknown> | undefined {
|
|
39
|
+
if (!value.trim().startsWith('{')) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(value) as unknown;
|
|
44
|
+
if (parsed != null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
45
|
+
return parsed as Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
33
52
|
|
|
34
53
|
/**
|
|
35
54
|
* Non-global matcher for a single `{{tool<i>turn<n>}}` placeholder.
|
|
@@ -89,9 +108,20 @@ export type ResolveResult<T> = {
|
|
|
89
108
|
* point in time, ignoring any subsequent registrations.
|
|
90
109
|
*/
|
|
91
110
|
export interface ToolOutputResolveView {
|
|
92
|
-
resolve<T>(args: T): ResolveResult<T>;
|
|
111
|
+
resolve<T>(args: T, options?: ResolveOptions): ResolveResult<T>;
|
|
93
112
|
}
|
|
94
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Per-call resolution options. `substituteIntentKey` opts the top-level
|
|
116
|
+
* `intent` key back INTO placeholder substitution: the exemption protects
|
|
117
|
+
* the injected display label, but a tool whose own schema declares a
|
|
118
|
+
* business parameter named `intent` (the injectors skip such tools) still
|
|
119
|
+
* needs references piped into it like any other argument.
|
|
120
|
+
*/
|
|
121
|
+
export type ResolveOptions = {
|
|
122
|
+
substituteIntentKey?: boolean;
|
|
123
|
+
};
|
|
124
|
+
|
|
95
125
|
/**
|
|
96
126
|
* Pre-resolved arg map keyed by `toolCallId`. Used by the mixed
|
|
97
127
|
* direct+event dispatch path to feed event calls' resolved args
|
|
@@ -333,12 +363,16 @@ export class ToolOutputReferenceRegistry {
|
|
|
333
363
|
* the serialized args, the original input is returned without
|
|
334
364
|
* walking the tree.
|
|
335
365
|
*/
|
|
336
|
-
resolve<T>(
|
|
366
|
+
resolve<T>(
|
|
367
|
+
runId: string | undefined,
|
|
368
|
+
args: T,
|
|
369
|
+
options?: ResolveOptions
|
|
370
|
+
): ResolveResult<T> {
|
|
337
371
|
if (!hasAnyPlaceholder(args)) {
|
|
338
372
|
return { resolved: args, unresolved: [] };
|
|
339
373
|
}
|
|
340
374
|
const bucket = this.runStates.get(this.keyFor(runId));
|
|
341
|
-
return this.resolveAgainst(bucket?.entries ?? EMPTY_ENTRIES, args);
|
|
375
|
+
return this.resolveAgainst(bucket?.entries ?? EMPTY_ENTRIES, args, options);
|
|
342
376
|
}
|
|
343
377
|
|
|
344
378
|
/**
|
|
@@ -358,27 +392,52 @@ export class ToolOutputReferenceRegistry {
|
|
|
358
392
|
? new Map(bucket.entries)
|
|
359
393
|
: EMPTY_ENTRIES;
|
|
360
394
|
return {
|
|
361
|
-
resolve: <T>(args: T): ResolveResult<T> =>
|
|
362
|
-
this.resolveAgainst(entries, args),
|
|
395
|
+
resolve: <T>(args: T, options?: ResolveOptions): ResolveResult<T> =>
|
|
396
|
+
this.resolveAgainst(entries, args, options),
|
|
363
397
|
};
|
|
364
398
|
}
|
|
365
399
|
|
|
366
400
|
private resolveAgainst<T>(
|
|
367
401
|
entries: ReadonlyMap<string, string>,
|
|
368
|
-
args: T
|
|
402
|
+
args: T,
|
|
403
|
+
options?: ResolveOptions
|
|
369
404
|
): ResolveResult<T> {
|
|
370
405
|
if (!hasAnyPlaceholder(args)) {
|
|
371
406
|
return { resolved: args, unresolved: [] };
|
|
372
407
|
}
|
|
408
|
+
const exemptIntentKey = options?.substituteIntentKey !== true;
|
|
373
409
|
const unresolved = new Set<string>();
|
|
374
|
-
|
|
410
|
+
/**
|
|
411
|
+
* Providers may deliver the args OBJECT as a JSON string. A plain
|
|
412
|
+
* string-root transform would expand placeholders inside the `intent`
|
|
413
|
+
* label too, bypassing the top-level exclusion below — parse, transform
|
|
414
|
+
* key-aware, and re-serialize so the label stays verbatim while every
|
|
415
|
+
* other field still substitutes. Only taken when an `intent` key is
|
|
416
|
+
* actually present; other strings keep the fast raw-string path.
|
|
417
|
+
*/
|
|
418
|
+
if (exemptIntentKey && typeof args === 'string') {
|
|
419
|
+
const parsedRoot = parseStringifiedArgsObject(args);
|
|
420
|
+
if (parsedRoot != null && INTENT_ARG in parsedRoot) {
|
|
421
|
+
const resolved = JSON.stringify(
|
|
422
|
+
this.transform(entries, parsedRoot, unresolved, true)
|
|
423
|
+
) as T;
|
|
424
|
+
return { resolved, unresolved: Array.from(unresolved) };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
const resolved = this.transform(
|
|
428
|
+
entries,
|
|
429
|
+
args,
|
|
430
|
+
unresolved,
|
|
431
|
+
exemptIntentKey
|
|
432
|
+
) as T;
|
|
375
433
|
return { resolved, unresolved: Array.from(unresolved) };
|
|
376
434
|
}
|
|
377
435
|
|
|
378
436
|
private transform(
|
|
379
437
|
entries: ReadonlyMap<string, string>,
|
|
380
438
|
value: unknown,
|
|
381
|
-
unresolved: Set<string
|
|
439
|
+
unresolved: Set<string>,
|
|
440
|
+
exemptRootIntent = false
|
|
382
441
|
): unknown {
|
|
383
442
|
if (typeof value === 'string') {
|
|
384
443
|
return this.replaceInString(entries, value, unresolved);
|
|
@@ -390,7 +449,16 @@ export class ToolOutputReferenceRegistry {
|
|
|
390
449
|
const source = value as Record<string, unknown>;
|
|
391
450
|
const next: Record<string, unknown> = {};
|
|
392
451
|
for (const [key, item] of Object.entries(source)) {
|
|
393
|
-
|
|
452
|
+
/**
|
|
453
|
+
* The top-level `intent` arg is a display label, never a data
|
|
454
|
+
* channel: expanding a `{{tool<i>turn<n>}}` placeholder there would
|
|
455
|
+
* dump a stored output (up to the registry cap) into a single-line
|
|
456
|
+
* UI label and persist it with the message. Left verbatim instead.
|
|
457
|
+
*/
|
|
458
|
+
next[key] =
|
|
459
|
+
exemptRootIntent && key === INTENT_ARG
|
|
460
|
+
? item
|
|
461
|
+
: this.transform(entries, item, unresolved);
|
|
394
462
|
}
|
|
395
463
|
return next;
|
|
396
464
|
}
|
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 = {
|