@librechat/agents 3.2.64 → 3.2.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/graphs/Graph.cjs +3 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +73 -9
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +41 -9
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +3 -1
- package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs +337 -0
- package/dist/cjs/llm/anthropic/utils/stream_events.cjs.map +1 -0
- package/dist/cjs/tools/search/crw-scraper.cjs +165 -0
- package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -0
- package/dist/cjs/tools/search/crw-search.cjs +105 -0
- package/dist/cjs/tools/search/crw-search.cjs.map +1 -0
- package/dist/cjs/tools/search/search.cjs +4 -2
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +15 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +3 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +73 -9
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +41 -9
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs +3 -2
- package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/stream_events.mjs +337 -0
- package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -0
- package/dist/esm/tools/search/crw-scraper.mjs +163 -0
- package/dist/esm/tools/search/crw-scraper.mjs.map +1 -0
- package/dist/esm/tools/search/crw-search.mjs +103 -0
- package/dist/esm/tools/search/crw-search.mjs.map +1 -0
- package/dist/esm/tools/search/search.mjs +4 -2
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +15 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/types/llm/anthropic/index.d.ts +2 -0
- package/dist/types/llm/anthropic/types.d.ts +2 -0
- package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -2
- package/dist/types/llm/anthropic/utils/stream_events.d.ts +25 -0
- package/dist/types/tools/search/crw-scraper.d.ts +41 -0
- package/dist/types/tools/search/crw-search.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +88 -3
- package/package.json +1 -1
- package/src/graphs/Graph.ts +15 -0
- package/src/llm/anthropic/index.ts +134 -10
- package/src/llm/anthropic/inherited-content-utils.spec.ts +10 -5
- package/src/llm/anthropic/inherited-stream-events.spec.ts +513 -9
- package/src/llm/anthropic/llm.spec.ts +100 -16
- package/src/llm/anthropic/types.ts +3 -0
- package/src/llm/anthropic/utils/message_inputs.ts +60 -3
- package/src/llm/anthropic/utils/message_outputs.ts +10 -2
- package/src/llm/anthropic/utils/stream_events.ts +471 -0
- package/src/tools/search/crw-scraper.ts +244 -0
- package/src/tools/search/crw-search.ts +167 -0
- package/src/tools/search/crw.test.ts +836 -0
- package/src/tools/search/search.ts +7 -1
- package/src/tools/search/tool.ts +23 -3
- package/src/tools/search/types.ts +103 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import type * as t from './types';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_CRW_TIMEOUT = 15000;
|
|
5
|
+
|
|
6
|
+
const getHostname = (link: string): string => {
|
|
7
|
+
try {
|
|
8
|
+
return new URL(link).hostname;
|
|
9
|
+
} catch {
|
|
10
|
+
return link;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** The published OpenAPI (docs.fastcrw.com) documents `data` as a flat
|
|
15
|
+
* SearchResult[] with a `category` field; route rows into source groups. */
|
|
16
|
+
const splitByCategory = (rows: t.CrwSearchResult[]): t.CrwSearchGroups => {
|
|
17
|
+
const groups: Required<t.CrwSearchGroups> = { web: [], images: [], news: [] };
|
|
18
|
+
for (const row of rows) {
|
|
19
|
+
if (row.category === 'news') {
|
|
20
|
+
groups.news.push(row);
|
|
21
|
+
} else if (row.category === 'images') {
|
|
22
|
+
groups.images.push({ ...row, imageUrl: row.url });
|
|
23
|
+
} else {
|
|
24
|
+
groups.web.push(row);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return groups;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const createCrwAPI = (
|
|
31
|
+
apiKey?: string,
|
|
32
|
+
apiUrl?: string,
|
|
33
|
+
options?: t.CrwSearchOptions
|
|
34
|
+
): {
|
|
35
|
+
getSources: (params: t.GetSourcesParams) => Promise<t.SearchResult>;
|
|
36
|
+
} => {
|
|
37
|
+
// NOTE: fastCRW /v1/search is cloud-only, but we still allow a base-URL
|
|
38
|
+
// override for parity with the scraper. Self-host may have no auth, so —
|
|
39
|
+
// unlike Tavily (createTavilyAPI throws on missing key) — we do NOT throw
|
|
40
|
+
// here; we only attach the Authorization header when a key is present.
|
|
41
|
+
const base = (
|
|
42
|
+
apiUrl ??
|
|
43
|
+
process.env.CRW_API_URL ??
|
|
44
|
+
'https://api.fastcrw.com'
|
|
45
|
+
).replace(/\/+$/, '');
|
|
46
|
+
const config = {
|
|
47
|
+
apiKey: apiKey ?? process.env.CRW_API_KEY,
|
|
48
|
+
apiUrl: `${base}/v1/search`,
|
|
49
|
+
timeout: options?.timeout ?? DEFAULT_CRW_TIMEOUT,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const getSources = async ({
|
|
53
|
+
query,
|
|
54
|
+
date,
|
|
55
|
+
numResults = 8,
|
|
56
|
+
type,
|
|
57
|
+
}: t.GetSourcesParams): Promise<t.SearchResult> => {
|
|
58
|
+
if (!query.trim()) {
|
|
59
|
+
return { success: false, error: 'Query cannot be empty' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const limit = Math.min(
|
|
64
|
+
Math.max(1, options?.maxResults ?? numResults),
|
|
65
|
+
20
|
|
66
|
+
);
|
|
67
|
+
// Mirror Serper's verticals: image/news requests hit only their native
|
|
68
|
+
// fastCRW source; plain web optionally adds images via includeImages.
|
|
69
|
+
let sources: t.CrwSearchSource[];
|
|
70
|
+
if (type === 'images') {
|
|
71
|
+
sources = ['images'];
|
|
72
|
+
} else if (type === 'news') {
|
|
73
|
+
sources = ['news'];
|
|
74
|
+
} else {
|
|
75
|
+
sources = options?.includeImages === true ? ['web', 'images'] : ['web'];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const payload: t.CrwSearchPayload = { query, limit, sources };
|
|
79
|
+
if (date != null) {
|
|
80
|
+
// Serper-style qdr filter; live-verified to constrain results.
|
|
81
|
+
payload.tbs = `qdr:${date}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const headers: Record<string, string> = {
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
};
|
|
87
|
+
if (config.apiKey != null && config.apiKey !== '') {
|
|
88
|
+
headers.Authorization = `Bearer ${config.apiKey}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const response = await axios.post<t.CrwSearchResponse>(
|
|
92
|
+
config.apiUrl,
|
|
93
|
+
payload,
|
|
94
|
+
{ headers, timeout: config.timeout }
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const body = response.data;
|
|
98
|
+
if (body.success === false) {
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: `fastCRW search failed: ${
|
|
102
|
+
body.error_code != null ? `[${body.error_code}] ` : ''
|
|
103
|
+
}${body.error ?? 'Unknown error'}`,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// fastCRW cloud keys results by source: {data: {web: [...], images:
|
|
108
|
+
// [...], news: [...]}} (live-verified 2026-07-02); the published
|
|
109
|
+
// OpenAPI documents a flat SearchResult[] instead (self-host), and a
|
|
110
|
+
// {data: {results: {...groups}}} wrapper also exists. Accept all three.
|
|
111
|
+
// OrganicResult.link is a REQUIRED string (types.ts), so defend against
|
|
112
|
+
// null/empty urls to never feed a broken '' link into the scraper.
|
|
113
|
+
const container = body.data ?? {};
|
|
114
|
+
const data: t.CrwSearchGroups = Array.isArray(container)
|
|
115
|
+
? splitByCategory(container)
|
|
116
|
+
: (container.results ?? container);
|
|
117
|
+
const organicResults: t.OrganicResult[] = (data.web ?? [])
|
|
118
|
+
.filter((r) => r.url != null && r.url !== '')
|
|
119
|
+
.map((r) => ({
|
|
120
|
+
title: r.title ?? '',
|
|
121
|
+
link: r.url as string,
|
|
122
|
+
snippet: r.description ?? r.snippet ?? '',
|
|
123
|
+
position: r.position,
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
const imageResults: t.ImageResult[] = (data.images ?? [])
|
|
127
|
+
.filter((r) => r.imageUrl != null && r.imageUrl !== '')
|
|
128
|
+
.map((r) => ({
|
|
129
|
+
title: r.title,
|
|
130
|
+
imageUrl: r.imageUrl,
|
|
131
|
+
link: r.url,
|
|
132
|
+
position: r.position,
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
const newsResults: t.NewsResult[] = (data.news ?? [])
|
|
136
|
+
.filter((r) => r.url != null && r.url !== '')
|
|
137
|
+
.map((r) => ({
|
|
138
|
+
title: r.title ?? '',
|
|
139
|
+
link: r.url as string,
|
|
140
|
+
snippet: r.description ?? r.snippet ?? '',
|
|
141
|
+
date: r.publishedDate,
|
|
142
|
+
source: getHostname(r.url as string),
|
|
143
|
+
position: r.position,
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
const results: t.SearchResultData = {
|
|
147
|
+
organic: organicResults,
|
|
148
|
+
images: imageResults,
|
|
149
|
+
topStories: [],
|
|
150
|
+
videos: [],
|
|
151
|
+
news: newsResults,
|
|
152
|
+
relatedSearches: [],
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return { success: true, data: results };
|
|
156
|
+
} catch (error) {
|
|
157
|
+
const errorMessage =
|
|
158
|
+
error instanceof Error ? error.message : String(error);
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
error: `fastCRW search request failed: ${errorMessage}`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return { getSources };
|
|
167
|
+
};
|