@framers/agentos-ext-web-search 1.1.0 → 1.2.0
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/README.md +31 -188
- package/dist/index.d.ts +18 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -50
- package/dist/index.js.map +1 -1
- package/dist/services/searchProvider.d.ts +89 -3
- package/dist/services/searchProvider.d.ts.map +1 -1
- package/dist/services/searchProvider.js +342 -26
- package/dist/services/searchProvider.js.map +1 -1
- package/dist/tools/factCheck.d.ts +46 -88
- package/dist/tools/factCheck.d.ts.map +1 -1
- package/dist/tools/factCheck.js +141 -170
- package/dist/tools/factCheck.js.map +1 -1
- package/dist/tools/researchAggregator.d.ts +37 -91
- package/dist/tools/researchAggregator.d.ts.map +1 -1
- package/dist/tools/researchAggregator.js +141 -175
- package/dist/tools/researchAggregator.js.map +1 -1
- package/dist/tools/webSearch.d.ts +31 -59
- package/dist/tools/webSearch.d.ts.map +1 -1
- package/dist/tools/webSearch.js +76 -87
- package/dist/tools/webSearch.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/dist/types.js.map +1 -1
- package/manifest.json +14 -9
- package/package.json +26 -16
- package/.eslintrc.json +0 -21
- package/CHANGELOG.md +0 -16
- package/src/index.ts +0 -123
- package/src/services/searchProvider.ts +0 -416
- package/src/tools/factCheck.ts +0 -318
- package/src/tools/researchAggregator.ts +0 -297
- package/src/tools/webSearch.ts +0 -128
- package/src/types.ts +0 -41
- package/test/integration.spec.ts +0 -173
- package/test/searchProvider.spec.ts +0 -153
- package/test/webSearch.spec.ts +0 -128
- package/tsconfig.json +0 -21
- package/vitest.config.ts +0 -32
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration for search providers
|
|
3
|
-
*/
|
|
4
|
-
export interface SearchProviderConfig {
|
|
5
|
-
serperApiKey?: string;
|
|
6
|
-
serpApiKey?: string;
|
|
7
|
-
braveApiKey?: string;
|
|
8
|
-
maxRetries?: number;
|
|
9
|
-
rateLimit?: {
|
|
10
|
-
maxRequests: number;
|
|
11
|
-
windowMs: number;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Search result structure
|
|
17
|
-
*/
|
|
18
|
-
export interface SearchResult {
|
|
19
|
-
title: string;
|
|
20
|
-
url: string;
|
|
21
|
-
snippet: string;
|
|
22
|
-
position?: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Provider-specific response
|
|
27
|
-
*/
|
|
28
|
-
export interface ProviderResponse {
|
|
29
|
-
provider: string;
|
|
30
|
-
results: SearchResult[];
|
|
31
|
-
metadata: {
|
|
32
|
-
query: string;
|
|
33
|
-
timestamp: string;
|
|
34
|
-
responseTime?: number;
|
|
35
|
-
fallback?: boolean;
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Service for managing multiple search providers with fallback support
|
|
41
|
-
*
|
|
42
|
-
* @class SearchProviderService
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```typescript
|
|
46
|
-
* const service = new SearchProviderService({
|
|
47
|
-
* serperApiKey: 'your-key',
|
|
48
|
-
* rateLimit: { maxRequests: 10, windowMs: 60000 }
|
|
49
|
-
* });
|
|
50
|
-
*
|
|
51
|
-
* const results = await service.search('query');
|
|
52
|
-
* ```
|
|
53
|
-
*/
|
|
54
|
-
export class SearchProviderService {
|
|
55
|
-
private config: SearchProviderConfig;
|
|
56
|
-
private rateLimitState: Map<string, { count: number; resetTime: number }>;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Creates an instance of SearchProviderService
|
|
60
|
-
*
|
|
61
|
-
* @param {SearchProviderConfig} config - Configuration for the service
|
|
62
|
-
*/
|
|
63
|
-
constructor(config: SearchProviderConfig) {
|
|
64
|
-
this.config = {
|
|
65
|
-
maxRetries: 3,
|
|
66
|
-
rateLimit: {
|
|
67
|
-
maxRequests: 10,
|
|
68
|
-
windowMs: 60000
|
|
69
|
-
},
|
|
70
|
-
...config
|
|
71
|
-
};
|
|
72
|
-
this.rateLimitState = new Map();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Performs a search across available providers with automatic fallback
|
|
77
|
-
*
|
|
78
|
-
* @param {string} query - The search query
|
|
79
|
-
* @param {Object} options - Search options
|
|
80
|
-
* @param {number} [options.maxResults=10] - Maximum results to return
|
|
81
|
-
* @param {string} [options.provider] - Specific provider to use
|
|
82
|
-
* @returns {Promise<ProviderResponse>} Search results with metadata
|
|
83
|
-
*
|
|
84
|
-
* @throws {Error} If all providers fail and no fallback is available
|
|
85
|
-
*/
|
|
86
|
-
async search(
|
|
87
|
-
query: string,
|
|
88
|
-
options: {
|
|
89
|
-
maxResults?: number;
|
|
90
|
-
provider?: string
|
|
91
|
-
} = {}
|
|
92
|
-
): Promise<ProviderResponse> {
|
|
93
|
-
const startTime = Date.now();
|
|
94
|
-
const maxResults = options.maxResults || 10;
|
|
95
|
-
|
|
96
|
-
// If specific provider requested, try only that one
|
|
97
|
-
if (options.provider) {
|
|
98
|
-
return this.searchWithProvider(query, options.provider, maxResults, startTime);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Try providers in order of preference
|
|
102
|
-
const providers = this.getAvailableProviders();
|
|
103
|
-
|
|
104
|
-
for (const provider of providers) {
|
|
105
|
-
try {
|
|
106
|
-
if (await this.checkRateLimit(provider)) {
|
|
107
|
-
return await this.searchWithProvider(query, provider, maxResults, startTime);
|
|
108
|
-
}
|
|
109
|
-
} catch (error) {
|
|
110
|
-
console.warn(`Provider ${provider} failed:`, error);
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Final fallback to DuckDuckGo (no API key required)
|
|
116
|
-
return this.searchDuckDuckGo(query, maxResults, startTime);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Searches using a specific provider
|
|
121
|
-
*
|
|
122
|
-
* @private
|
|
123
|
-
* @param {string} query - Search query
|
|
124
|
-
* @param {string} provider - Provider name
|
|
125
|
-
* @param {number} maxResults - Maximum results
|
|
126
|
-
* @param {number} startTime - Request start timestamp
|
|
127
|
-
* @returns {Promise<ProviderResponse>} Search results
|
|
128
|
-
*/
|
|
129
|
-
private async searchWithProvider(
|
|
130
|
-
query: string,
|
|
131
|
-
provider: string,
|
|
132
|
-
maxResults: number,
|
|
133
|
-
startTime: number
|
|
134
|
-
): Promise<ProviderResponse> {
|
|
135
|
-
let results: SearchResult[];
|
|
136
|
-
|
|
137
|
-
switch (provider) {
|
|
138
|
-
case 'serper':
|
|
139
|
-
results = await this.searchSerper(query, maxResults);
|
|
140
|
-
break;
|
|
141
|
-
case 'serpapi':
|
|
142
|
-
results = await this.searchSerpApi(query, maxResults);
|
|
143
|
-
break;
|
|
144
|
-
case 'brave':
|
|
145
|
-
results = await this.searchBrave(query, maxResults);
|
|
146
|
-
break;
|
|
147
|
-
case 'duckduckgo':
|
|
148
|
-
default:
|
|
149
|
-
results = await this.searchDuckDuckGoAPI(query, maxResults);
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
provider,
|
|
155
|
-
results: results.slice(0, maxResults),
|
|
156
|
-
metadata: {
|
|
157
|
-
query,
|
|
158
|
-
timestamp: new Date().toISOString(),
|
|
159
|
-
responseTime: Date.now() - startTime
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Gets list of available providers based on configured API keys
|
|
166
|
-
*
|
|
167
|
-
* @private
|
|
168
|
-
* @returns {string[]} Array of available provider names
|
|
169
|
-
*/
|
|
170
|
-
private getAvailableProviders(): string[] {
|
|
171
|
-
const providers: string[] = [];
|
|
172
|
-
|
|
173
|
-
if (this.config.serperApiKey) providers.push('serper');
|
|
174
|
-
if (this.config.serpApiKey) providers.push('serpapi');
|
|
175
|
-
if (this.config.braveApiKey) providers.push('brave');
|
|
176
|
-
|
|
177
|
-
return providers;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Checks if a provider is within rate limits
|
|
182
|
-
*
|
|
183
|
-
* @private
|
|
184
|
-
* @param {string} provider - Provider name
|
|
185
|
-
* @returns {Promise<boolean>} True if within limits
|
|
186
|
-
*/
|
|
187
|
-
private async checkRateLimit(provider: string): Promise<boolean> {
|
|
188
|
-
const now = Date.now();
|
|
189
|
-
const state = this.rateLimitState.get(provider);
|
|
190
|
-
|
|
191
|
-
if (!state || now > state.resetTime) {
|
|
192
|
-
this.rateLimitState.set(provider, {
|
|
193
|
-
count: 1,
|
|
194
|
-
resetTime: now + this.config.rateLimit!.windowMs
|
|
195
|
-
});
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (state.count >= this.config.rateLimit!.maxRequests) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
state.count++;
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Search using Serper.dev API
|
|
209
|
-
*
|
|
210
|
-
* @private
|
|
211
|
-
* @param {string} query - Search query
|
|
212
|
-
* @param {number} maxResults - Maximum results
|
|
213
|
-
* @returns {Promise<SearchResult[]>} Search results
|
|
214
|
-
*/
|
|
215
|
-
private async searchSerper(query: string, maxResults: number): Promise<SearchResult[]> {
|
|
216
|
-
const response = await fetch('https://google.serper.dev/search', {
|
|
217
|
-
method: 'POST',
|
|
218
|
-
headers: {
|
|
219
|
-
'X-API-KEY': this.config.serperApiKey!,
|
|
220
|
-
'Content-Type': 'application/json'
|
|
221
|
-
},
|
|
222
|
-
body: JSON.stringify({ q: query, num: maxResults })
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
if (!response.ok) throw new Error(`Serper API error: ${response.statusText}`);
|
|
226
|
-
|
|
227
|
-
const data = await response.json();
|
|
228
|
-
return (data.organic || []).map((item: any) => ({
|
|
229
|
-
title: item.title,
|
|
230
|
-
url: item.link,
|
|
231
|
-
snippet: item.snippet,
|
|
232
|
-
position: item.position
|
|
233
|
-
}));
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Search using SerpAPI
|
|
238
|
-
*
|
|
239
|
-
* @private
|
|
240
|
-
* @param {string} query - Search query
|
|
241
|
-
* @param {number} maxResults - Maximum results
|
|
242
|
-
* @returns {Promise<SearchResult[]>} Search results
|
|
243
|
-
*/
|
|
244
|
-
private async searchSerpApi(query: string, maxResults: number): Promise<SearchResult[]> {
|
|
245
|
-
const params = new URLSearchParams({
|
|
246
|
-
q: query,
|
|
247
|
-
api_key: this.config.serpApiKey!,
|
|
248
|
-
num: maxResults.toString()
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
const response = await fetch(`https://serpapi.com/search?${params}`);
|
|
252
|
-
if (!response.ok) throw new Error(`SerpAPI error: ${response.statusText}`);
|
|
253
|
-
|
|
254
|
-
const data = await response.json();
|
|
255
|
-
return (data.organic_results || []).map((item: any) => ({
|
|
256
|
-
title: item.title,
|
|
257
|
-
url: item.link,
|
|
258
|
-
snippet: item.snippet,
|
|
259
|
-
position: item.position
|
|
260
|
-
}));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Search using Brave Search API
|
|
265
|
-
*
|
|
266
|
-
* @private
|
|
267
|
-
* @param {string} query - Search query
|
|
268
|
-
* @param {number} maxResults - Maximum results
|
|
269
|
-
* @returns {Promise<SearchResult[]>} Search results
|
|
270
|
-
*/
|
|
271
|
-
private async searchBrave(query: string, maxResults: number): Promise<SearchResult[]> {
|
|
272
|
-
const params = new URLSearchParams({
|
|
273
|
-
q: query,
|
|
274
|
-
count: maxResults.toString()
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
const response = await fetch(`https://api.search.brave.com/res/v1/web/search?${params}`, {
|
|
278
|
-
headers: {
|
|
279
|
-
'X-Subscription-Token': this.config.braveApiKey!
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
if (!response.ok) throw new Error(`Brave API error: ${response.statusText}`);
|
|
284
|
-
|
|
285
|
-
const data = await response.json();
|
|
286
|
-
return (data.web?.results || []).map((item: any, index: number) => ({
|
|
287
|
-
title: item.title,
|
|
288
|
-
url: item.url,
|
|
289
|
-
snippet: item.description,
|
|
290
|
-
position: index + 1
|
|
291
|
-
}));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Search using DuckDuckGo (HTML scraping fallback)
|
|
296
|
-
*
|
|
297
|
-
* @private
|
|
298
|
-
* @param {string} query - Search query
|
|
299
|
-
* @param {number} maxResults - Maximum results
|
|
300
|
-
* @param {number} startTime - Request start timestamp
|
|
301
|
-
* @returns {Promise<ProviderResponse>} Search results
|
|
302
|
-
*/
|
|
303
|
-
private async searchDuckDuckGo(
|
|
304
|
-
query: string,
|
|
305
|
-
maxResults: number,
|
|
306
|
-
startTime: number
|
|
307
|
-
): Promise<ProviderResponse> {
|
|
308
|
-
try {
|
|
309
|
-
const results = await this.searchDuckDuckGoAPI(query, maxResults);
|
|
310
|
-
return {
|
|
311
|
-
provider: 'duckduckgo',
|
|
312
|
-
results,
|
|
313
|
-
metadata: {
|
|
314
|
-
query,
|
|
315
|
-
timestamp: new Date().toISOString(),
|
|
316
|
-
responseTime: Date.now() - startTime,
|
|
317
|
-
fallback: true
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
} catch (error) {
|
|
321
|
-
// Return empty results rather than throwing
|
|
322
|
-
return {
|
|
323
|
-
provider: 'duckduckgo',
|
|
324
|
-
results: [],
|
|
325
|
-
metadata: {
|
|
326
|
-
query,
|
|
327
|
-
timestamp: new Date().toISOString(),
|
|
328
|
-
responseTime: Date.now() - startTime,
|
|
329
|
-
fallback: true
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Search using DuckDuckGo instant answer API
|
|
337
|
-
*
|
|
338
|
-
* @private
|
|
339
|
-
* @param {string} query - Search query
|
|
340
|
-
* @param {number} maxResults - Maximum results
|
|
341
|
-
* @returns {Promise<SearchResult[]>} Search results
|
|
342
|
-
*/
|
|
343
|
-
private async searchDuckDuckGoAPI(query: string, maxResults: number): Promise<SearchResult[]> {
|
|
344
|
-
// DuckDuckGo instant answer API (limited but free)
|
|
345
|
-
const params = new URLSearchParams({
|
|
346
|
-
q: query,
|
|
347
|
-
format: 'json',
|
|
348
|
-
no_html: '1',
|
|
349
|
-
skip_disambig: '1'
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
const response = await fetch(`https://api.duckduckgo.com/?${params}`);
|
|
353
|
-
const data = await response.json();
|
|
354
|
-
|
|
355
|
-
const results: SearchResult[] = [];
|
|
356
|
-
|
|
357
|
-
// Add instant answer if available
|
|
358
|
-
if (data.AbstractText && data.AbstractURL) {
|
|
359
|
-
results.push({
|
|
360
|
-
title: data.Heading || query,
|
|
361
|
-
url: data.AbstractURL,
|
|
362
|
-
snippet: data.AbstractText
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Add related topics
|
|
367
|
-
if (data.RelatedTopics) {
|
|
368
|
-
for (const topic of data.RelatedTopics.slice(0, maxResults - 1)) {
|
|
369
|
-
if (topic.FirstURL && topic.Text) {
|
|
370
|
-
results.push({
|
|
371
|
-
title: topic.Text.split(' - ')[0] || topic.Text,
|
|
372
|
-
url: topic.FirstURL,
|
|
373
|
-
snippet: topic.Text
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return results.slice(0, maxResults);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Gets recommended search providers with signup information
|
|
384
|
-
*
|
|
385
|
-
* @static
|
|
386
|
-
* @returns {Array} Array of provider recommendations
|
|
387
|
-
*/
|
|
388
|
-
static getRecommendedProviders() {
|
|
389
|
-
return [
|
|
390
|
-
{
|
|
391
|
-
name: 'Serper',
|
|
392
|
-
signupUrl: 'https://serper.dev',
|
|
393
|
-
freeQuota: '2,500 queries/month',
|
|
394
|
-
description: 'Google search results API'
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
name: 'SerpAPI',
|
|
398
|
-
signupUrl: 'https://serpapi.com',
|
|
399
|
-
freeQuota: '100 searches/month',
|
|
400
|
-
description: 'Multiple search engines API'
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
name: 'Brave Search',
|
|
404
|
-
signupUrl: 'https://brave.com/search-api',
|
|
405
|
-
freeQuota: '2,000 queries/month',
|
|
406
|
-
description: 'Privacy-focused search API'
|
|
407
|
-
},
|
|
408
|
-
{
|
|
409
|
-
name: 'DuckDuckGo',
|
|
410
|
-
signupUrl: 'No signup required',
|
|
411
|
-
freeQuota: 'Unlimited (rate limited)',
|
|
412
|
-
description: 'Privacy-focused, no API key needed'
|
|
413
|
-
}
|
|
414
|
-
];
|
|
415
|
-
}
|
|
416
|
-
}
|