@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.
Files changed (39) hide show
  1. package/README.md +31 -188
  2. package/dist/index.d.ts +18 -11
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +57 -50
  5. package/dist/index.js.map +1 -1
  6. package/dist/services/searchProvider.d.ts +89 -3
  7. package/dist/services/searchProvider.d.ts.map +1 -1
  8. package/dist/services/searchProvider.js +342 -26
  9. package/dist/services/searchProvider.js.map +1 -1
  10. package/dist/tools/factCheck.d.ts +46 -88
  11. package/dist/tools/factCheck.d.ts.map +1 -1
  12. package/dist/tools/factCheck.js +141 -170
  13. package/dist/tools/factCheck.js.map +1 -1
  14. package/dist/tools/researchAggregator.d.ts +37 -91
  15. package/dist/tools/researchAggregator.d.ts.map +1 -1
  16. package/dist/tools/researchAggregator.js +141 -175
  17. package/dist/tools/researchAggregator.js.map +1 -1
  18. package/dist/tools/webSearch.d.ts +31 -59
  19. package/dist/tools/webSearch.d.ts.map +1 -1
  20. package/dist/tools/webSearch.js +76 -87
  21. package/dist/tools/webSearch.js.map +1 -1
  22. package/dist/types.d.ts.map +1 -1
  23. package/dist/types.js +2 -2
  24. package/dist/types.js.map +1 -1
  25. package/manifest.json +14 -9
  26. package/package.json +26 -16
  27. package/.eslintrc.json +0 -21
  28. package/CHANGELOG.md +0 -16
  29. package/src/index.ts +0 -123
  30. package/src/services/searchProvider.ts +0 -416
  31. package/src/tools/factCheck.ts +0 -318
  32. package/src/tools/researchAggregator.ts +0 -297
  33. package/src/tools/webSearch.ts +0 -128
  34. package/src/types.ts +0 -41
  35. package/test/integration.spec.ts +0 -173
  36. package/test/searchProvider.spec.ts +0 -153
  37. package/test/webSearch.spec.ts +0 -128
  38. package/tsconfig.json +0 -21
  39. package/vitest.config.ts +0 -32
@@ -1,318 +0,0 @@
1
- import { ITool } from '@framers/agentos';
2
- import { SearchProviderService } from '../services/searchProvider';
3
-
4
- /**
5
- * Fact checking tool that verifies statements against web sources
6
- *
7
- * @class FactCheckTool
8
- * @implements {ITool}
9
- *
10
- * @example
11
- * ```typescript
12
- * const tool = new FactCheckTool(searchService);
13
- * const result = await tool.execute({
14
- * statement: 'The Earth orbits the Sun',
15
- * checkSources: true,
16
- * confidence: 'high'
17
- * });
18
- * ```
19
- */
20
- export class FactCheckTool implements ITool {
21
- public readonly id = 'factCheck';
22
- public readonly name = 'Fact Check';
23
- public readonly description = 'Verify facts and statements against web sources';
24
-
25
- /**
26
- * Creates an instance of FactCheckTool
27
- * @param {SearchProviderService} searchService - The search provider service
28
- */
29
- constructor(private searchService: SearchProviderService) {}
30
-
31
- /**
32
- * Executes fact checking on a statement
33
- *
34
- * @param {Object} input - The fact check parameters
35
- * @param {string} input.statement - The statement to fact-check
36
- * @param {boolean} [input.checkSources=true] - Whether to include source citations
37
- * @param {string} [input.confidence='medium'] - Required confidence level
38
- * @returns {Promise<{success: boolean, output?: any, error?: string}>} Fact check results
39
- */
40
- async execute(input: {
41
- statement: string;
42
- checkSources?: boolean;
43
- confidence?: 'low' | 'medium' | 'high';
44
- }): Promise<{ success: boolean; output?: any; error?: string }> {
45
- try {
46
- const checkSources = input.checkSources !== false;
47
- const requiredConfidence = input.confidence || 'medium';
48
-
49
- // Search for information about the statement
50
- const queries = this.generateFactCheckQueries(input.statement);
51
-
52
- // Perform searches in parallel
53
- const searchPromises = queries.map(query =>
54
- this.searchService.search(query, { maxResults: 5 })
55
- .catch(err => ({
56
- provider: 'error',
57
- results: [],
58
- metadata: { query, error: err.message, timestamp: new Date().toISOString() }
59
- }))
60
- );
61
-
62
- const searchResults = await Promise.all(searchPromises);
63
-
64
- // Analyze results for fact verification
65
- const analysis = this.analyzeFactCheckResults(
66
- input.statement,
67
- searchResults,
68
- requiredConfidence
69
- );
70
-
71
- // Compile sources if requested
72
- const sources = checkSources ? this.compileSources(searchResults) : [];
73
-
74
- return {
75
- success: true,
76
- output: {
77
- statement: input.statement,
78
- verdict: analysis.verdict,
79
- confidence: analysis.confidence,
80
- explanation: analysis.explanation,
81
- sources: sources,
82
- contradictingSources: analysis.contradictingSources,
83
- supportingSources: analysis.supportingSources,
84
- timestamp: new Date().toISOString()
85
- }
86
- };
87
- } catch (error: any) {
88
- return {
89
- success: false,
90
- error: error.message
91
- };
92
- }
93
- }
94
-
95
- /**
96
- * Generates queries for fact-checking a statement
97
- *
98
- * @private
99
- * @param {string} statement - The statement to check
100
- * @returns {string[]} Array of search queries
101
- */
102
- private generateFactCheckQueries(statement: string): string[] {
103
- return [
104
- statement, // Original statement
105
- `"${statement}" fact check`, // Exact match with fact check
106
- `${statement} true or false`, // Truth verification
107
- `${statement} debunked myths`, // Check for debunking
108
- `is it true that ${statement}` // Question format
109
- ];
110
- }
111
-
112
- /**
113
- * Analyzes search results to determine fact validity
114
- *
115
- * @private
116
- * @param {string} statement - Original statement
117
- * @param {any[]} searchResults - Search results to analyze
118
- * @param {string} requiredConfidence - Required confidence level
119
- * @returns {Object} Analysis results with verdict and confidence
120
- */
121
- private analyzeFactCheckResults(
122
- statement: string,
123
- searchResults: any[],
124
- requiredConfidence: string
125
- ): any {
126
- let supportingCount = 0;
127
- let contradictingCount = 0;
128
- let neutralCount = 0;
129
- const supportingSources: string[] = [];
130
- const contradictingSources: string[] = [];
131
-
132
- const statementLower = statement.toLowerCase();
133
- const negativeIndicators = ['false', 'myth', 'debunked', 'incorrect', 'wrong', 'not true', 'fake'];
134
- const positiveIndicators = ['true', 'correct', 'accurate', 'confirmed', 'verified', 'fact'];
135
-
136
- for (const searchResult of searchResults) {
137
- if (!searchResult.results) continue;
138
-
139
- for (const result of searchResult.results) {
140
- const contentLower = (result.title + ' ' + result.snippet).toLowerCase();
141
-
142
- // Check for fact-checking sites
143
- const isFactCheckSite = result.url.includes('snopes') ||
144
- result.url.includes('factcheck') ||
145
- result.url.includes('politifact');
146
-
147
- const weight = isFactCheckSite ? 2 : 1; // Give more weight to fact-checking sites
148
-
149
- // Analyze sentiment toward the statement
150
- let hasNegative = negativeIndicators.some(ind => contentLower.includes(ind));
151
- let hasPositive = positiveIndicators.some(ind => contentLower.includes(ind));
152
-
153
- if (hasNegative && !hasPositive) {
154
- contradictingCount += weight;
155
- contradictingSources.push(result.url);
156
- } else if (hasPositive && !hasNegative) {
157
- supportingCount += weight;
158
- supportingSources.push(result.url);
159
- } else {
160
- neutralCount += 1;
161
- }
162
- }
163
- }
164
-
165
- // Calculate confidence and verdict
166
- const total = supportingCount + contradictingCount + neutralCount;
167
- let confidence: number;
168
- let verdict: 'TRUE' | 'FALSE' | 'UNVERIFIED' | 'PARTIALLY TRUE';
169
- let explanation: string;
170
-
171
- if (total === 0) {
172
- verdict = 'UNVERIFIED';
173
- confidence = 0;
174
- explanation = 'No relevant sources found to verify this statement.';
175
- } else {
176
- const supportRatio = supportingCount / total;
177
- const contradictRatio = contradictingCount / total;
178
-
179
- if (supportRatio > 0.7) {
180
- verdict = 'TRUE';
181
- confidence = supportRatio * 100;
182
- explanation = `Strong evidence supports this statement (${supportingCount} supporting sources).`;
183
- } else if (contradictRatio > 0.7) {
184
- verdict = 'FALSE';
185
- confidence = contradictRatio * 100;
186
- explanation = `Strong evidence contradicts this statement (${contradictingCount} contradicting sources).`;
187
- } else if (supportRatio > 0.4 && contradictRatio > 0.4) {
188
- verdict = 'PARTIALLY TRUE';
189
- confidence = 50;
190
- explanation = `Mixed evidence with both supporting and contradicting sources.`;
191
- } else {
192
- verdict = 'UNVERIFIED';
193
- confidence = Math.max(supportRatio, contradictRatio) * 100;
194
- explanation = `Insufficient clear evidence to verify this statement.`;
195
- }
196
- }
197
-
198
- // Adjust confidence based on required level
199
- const confidenceLevel = confidence > 80 ? 'high' : confidence > 50 ? 'medium' : 'low';
200
-
201
- return {
202
- verdict,
203
- confidence: Math.round(confidence),
204
- confidenceLevel,
205
- explanation,
206
- supportingSources: supportingSources.slice(0, 5),
207
- contradictingSources: contradictingSources.slice(0, 5),
208
- meetsRequiredConfidence: this.meetsConfidenceRequirement(confidenceLevel, requiredConfidence)
209
- };
210
- }
211
-
212
- /**
213
- * Checks if confidence level meets requirement
214
- *
215
- * @private
216
- * @param {string} actual - Actual confidence level
217
- * @param {string} required - Required confidence level
218
- * @returns {boolean} True if requirement is met
219
- */
220
- private meetsConfidenceRequirement(actual: string, required: string): boolean {
221
- const levels = { low: 1, medium: 2, high: 3 };
222
- return levels[actual as keyof typeof levels] >= levels[required as keyof typeof levels];
223
- }
224
-
225
- /**
226
- * Compiles relevant sources from search results
227
- *
228
- * @private
229
- * @param {any[]} searchResults - Search results to compile from
230
- * @returns {any[]} Compiled sources with metadata
231
- */
232
- private compileSources(searchResults: any[]): any[] {
233
- const sources: any[] = [];
234
- const seenUrls = new Set<string>();
235
-
236
- for (const searchResult of searchResults) {
237
- if (!searchResult.results) continue;
238
-
239
- for (const result of searchResult.results) {
240
- if (seenUrls.has(result.url)) continue;
241
- seenUrls.add(result.url);
242
-
243
- sources.push({
244
- title: result.title,
245
- url: result.url,
246
- snippet: result.snippet,
247
- provider: searchResult.provider,
248
- query: searchResult.metadata.query
249
- });
250
- }
251
- }
252
-
253
- return sources.slice(0, 10); // Limit to top 10 sources
254
- }
255
-
256
- /**
257
- * Validates input parameters
258
- *
259
- * @param {any} input - Input to validate
260
- * @returns {{valid: boolean, errors: string[]}} Validation result
261
- */
262
- validate(input: any): { valid: boolean; errors: string[] } {
263
- const errors: string[] = [];
264
-
265
- if (!input.statement) {
266
- errors.push('Statement is required');
267
- } else if (typeof input.statement !== 'string') {
268
- errors.push('Statement must be a string');
269
- } else if (input.statement.length < 5) {
270
- errors.push('Statement must be at least 5 characters long');
271
- }
272
-
273
- if (input.confidence !== undefined) {
274
- if (!['low', 'medium', 'high'].includes(input.confidence)) {
275
- errors.push('Confidence must be low, medium, or high');
276
- }
277
- }
278
-
279
- if (input.checkSources !== undefined && typeof input.checkSources !== 'boolean') {
280
- errors.push('checkSources must be a boolean');
281
- }
282
-
283
- return {
284
- valid: errors.length === 0,
285
- errors
286
- };
287
- }
288
-
289
- /**
290
- * Returns the JSON schema for the tool's input
291
- *
292
- * @returns {Object} JSON Schema object
293
- */
294
- getSchema(): any {
295
- return {
296
- type: 'object',
297
- required: ['statement'],
298
- properties: {
299
- statement: {
300
- type: 'string',
301
- description: 'The statement to fact-check',
302
- minLength: 5
303
- },
304
- checkSources: {
305
- type: 'boolean',
306
- description: 'Whether to include source citations',
307
- default: true
308
- },
309
- confidence: {
310
- type: 'string',
311
- description: 'Required confidence level for the verdict',
312
- enum: ['low', 'medium', 'high'],
313
- default: 'medium'
314
- }
315
- }
316
- };
317
- }
318
- }
@@ -1,297 +0,0 @@
1
- import { ITool } from '@framers/agentos';
2
- import { SearchProviderService } from '../services/searchProvider';
3
-
4
- /**
5
- * Research aggregator tool that performs multiple searches and synthesizes results
6
- *
7
- * @class ResearchAggregatorTool
8
- * @implements {ITool}
9
- *
10
- * @example
11
- * ```typescript
12
- * const tool = new ResearchAggregatorTool(searchService);
13
- * const result = await tool.execute({
14
- * topic: 'AI safety research',
15
- * sources: 3,
16
- * depth: 'comprehensive'
17
- * });
18
- * ```
19
- */
20
- export class ResearchAggregatorTool implements ITool {
21
- public readonly id = 'researchAggregator';
22
- public readonly name = 'Research Aggregator';
23
- public readonly description = 'Aggregate research from multiple searches on a topic';
24
-
25
- /**
26
- * Creates an instance of ResearchAggregatorTool
27
- * @param {SearchProviderService} searchService - The search provider service
28
- */
29
- constructor(private searchService: SearchProviderService) {}
30
-
31
- /**
32
- * Executes research aggregation on a topic
33
- *
34
- * @param {Object} input - The research parameters
35
- * @param {string} input.topic - The research topic
36
- * @param {number} [input.sources=3] - Number of different search angles to explore
37
- * @param {string} [input.depth='moderate'] - Research depth (quick, moderate, comprehensive)
38
- * @returns {Promise<{success: boolean, output?: any, error?: string}>} Aggregated research results
39
- */
40
- async execute(input: {
41
- topic: string;
42
- sources?: number;
43
- depth?: 'quick' | 'moderate' | 'comprehensive';
44
- }): Promise<{ success: boolean; output?: any; error?: string }> {
45
- try {
46
- const sources = input.sources || 3;
47
- const depth = input.depth || 'moderate';
48
-
49
- // Generate different search queries for comprehensive coverage
50
- const queries = this.generateSearchQueries(input.topic, sources, depth);
51
-
52
- // Perform searches in parallel
53
- const searchPromises = queries.map(query =>
54
- this.searchService.search(query, {
55
- maxResults: depth === 'quick' ? 5 : depth === 'moderate' ? 10 : 15
56
- }).catch(err => ({
57
- provider: 'error',
58
- results: [],
59
- metadata: { query, error: err.message, timestamp: new Date().toISOString() }
60
- }))
61
- );
62
-
63
- const searchResults = await Promise.all(searchPromises);
64
-
65
- // Aggregate and deduplicate results
66
- const aggregated = this.aggregateResults(searchResults, input.topic);
67
-
68
- return {
69
- success: true,
70
- output: {
71
- topic: input.topic,
72
- sources: sources,
73
- depth: depth,
74
- queries: queries,
75
- aggregatedResults: aggregated,
76
- timestamp: new Date().toISOString()
77
- }
78
- };
79
- } catch (error: any) {
80
- return {
81
- success: false,
82
- error: error.message
83
- };
84
- }
85
- }
86
-
87
- /**
88
- * Generates diverse search queries for comprehensive research
89
- *
90
- * @private
91
- * @param {string} topic - The research topic
92
- * @param {number} count - Number of queries to generate
93
- * @param {string} depth - Research depth level
94
- * @returns {string[]} Array of search queries
95
- */
96
- private generateSearchQueries(topic: string, count: number, depth: string): string[] {
97
- const queries: string[] = [topic]; // Start with the original topic
98
-
99
- const modifiers = {
100
- quick: ['overview', 'summary'],
101
- moderate: ['overview', 'recent developments', 'key concepts', 'applications'],
102
- comprehensive: [
103
- 'overview',
104
- 'recent research',
105
- 'state of the art',
106
- 'challenges',
107
- 'future directions',
108
- 'industry applications',
109
- 'academic papers',
110
- 'expert opinions'
111
- ]
112
- };
113
-
114
- const selectedModifiers = modifiers[depth as keyof typeof modifiers];
115
-
116
- for (let i = 1; i < Math.min(count, selectedModifiers.length + 1); i++) {
117
- if (selectedModifiers[i - 1]) {
118
- queries.push(`${topic} ${selectedModifiers[i - 1]}`);
119
- }
120
- }
121
-
122
- return queries;
123
- }
124
-
125
- /**
126
- * Aggregates and deduplicates search results
127
- *
128
- * @private
129
- * @param {any[]} searchResults - Array of search results from different queries
130
- * @param {string} topic - The original research topic
131
- * @returns {Object} Aggregated and organized results
132
- */
133
- private aggregateResults(searchResults: any[], topic: string): any {
134
- const seenUrls = new Set<string>();
135
- const categories = {
136
- overview: [] as any[],
137
- research: [] as any[],
138
- applications: [] as any[],
139
- resources: [] as any[]
140
- };
141
-
142
- for (const searchResult of searchResults) {
143
- if (!searchResult.results) continue;
144
-
145
- for (const result of searchResult.results) {
146
- // Skip duplicates
147
- if (seenUrls.has(result.url)) continue;
148
- seenUrls.add(result.url);
149
-
150
- // Categorize based on content
151
- const lowerTitle = result.title.toLowerCase();
152
- const lowerSnippet = result.snippet.toLowerCase();
153
-
154
- if (lowerTitle.includes('overview') || lowerTitle.includes('introduction') ||
155
- lowerTitle.includes('what is')) {
156
- categories.overview.push(result);
157
- } else if (lowerTitle.includes('research') || lowerTitle.includes('study') ||
158
- lowerTitle.includes('paper') || lowerTitle.includes('journal')) {
159
- categories.research.push(result);
160
- } else if (lowerTitle.includes('application') || lowerTitle.includes('use case') ||
161
- lowerTitle.includes('implementation')) {
162
- categories.applications.push(result);
163
- } else {
164
- categories.resources.push(result);
165
- }
166
- }
167
- }
168
-
169
- // Calculate relevance scores
170
- const scoredCategories = Object.entries(categories).reduce((acc, [key, items]) => {
171
- acc[key] = items.map((item: any) => ({
172
- ...item,
173
- relevanceScore: this.calculateRelevance(item, topic)
174
- })).sort((a: any, b: any) => b.relevanceScore - a.relevanceScore);
175
- return acc;
176
- }, {} as any);
177
-
178
- return {
179
- categories: scoredCategories,
180
- totalResults: seenUrls.size,
181
- topResults: this.getTopResults(scoredCategories, 10)
182
- };
183
- }
184
-
185
- /**
186
- * Calculates relevance score for a search result
187
- *
188
- * @private
189
- * @param {any} result - Search result object
190
- * @param {string} topic - Original topic for comparison
191
- * @returns {number} Relevance score (0-100)
192
- */
193
- private calculateRelevance(result: any, topic: string): number {
194
- let score = 50; // Base score
195
-
196
- const topicWords = topic.toLowerCase().split(/\s+/);
197
- const titleWords = result.title.toLowerCase();
198
- const snippetWords = result.snippet.toLowerCase();
199
-
200
- // Check title relevance
201
- for (const word of topicWords) {
202
- if (titleWords.includes(word)) score += 15;
203
- if (snippetWords.includes(word)) score += 5;
204
- }
205
-
206
- // Boost for certain indicators
207
- if (titleWords.includes('official')) score += 10;
208
- if (titleWords.includes('guide')) score += 5;
209
- if (titleWords.includes('tutorial')) score += 5;
210
-
211
- return Math.min(100, score);
212
- }
213
-
214
- /**
215
- * Gets top results across all categories
216
- *
217
- * @private
218
- * @param {any} categories - Categorized results
219
- * @param {number} count - Number of top results to return
220
- * @returns {any[]} Top results by relevance
221
- */
222
- private getTopResults(categories: any, count: number): any[] {
223
- const allResults: any[] = [];
224
-
225
- for (const items of Object.values(categories)) {
226
- allResults.push(...(items as any[]));
227
- }
228
-
229
- return allResults
230
- .sort((a, b) => b.relevanceScore - a.relevanceScore)
231
- .slice(0, count);
232
- }
233
-
234
- /**
235
- * Validates input parameters
236
- *
237
- * @param {any} input - Input to validate
238
- * @returns {{valid: boolean, errors: string[]}} Validation result
239
- */
240
- validate(input: any): { valid: boolean; errors: string[] } {
241
- const errors: string[] = [];
242
-
243
- if (!input.topic) {
244
- errors.push('Topic is required');
245
- } else if (typeof input.topic !== 'string') {
246
- errors.push('Topic must be a string');
247
- }
248
-
249
- if (input.sources !== undefined) {
250
- if (typeof input.sources !== 'number' || input.sources < 1 || input.sources > 10) {
251
- errors.push('Sources must be a number between 1 and 10');
252
- }
253
- }
254
-
255
- if (input.depth !== undefined) {
256
- if (!['quick', 'moderate', 'comprehensive'].includes(input.depth)) {
257
- errors.push('Depth must be quick, moderate, or comprehensive');
258
- }
259
- }
260
-
261
- return {
262
- valid: errors.length === 0,
263
- errors
264
- };
265
- }
266
-
267
- /**
268
- * Returns the JSON schema for the tool's input
269
- *
270
- * @returns {Object} JSON Schema object
271
- */
272
- getSchema(): any {
273
- return {
274
- type: 'object',
275
- required: ['topic'],
276
- properties: {
277
- topic: {
278
- type: 'string',
279
- description: 'The research topic to aggregate information about'
280
- },
281
- sources: {
282
- type: 'number',
283
- description: 'Number of different search angles to explore',
284
- default: 3,
285
- minimum: 1,
286
- maximum: 10
287
- },
288
- depth: {
289
- type: 'string',
290
- description: 'Research depth level',
291
- enum: ['quick', 'moderate', 'comprehensive'],
292
- default: 'moderate'
293
- }
294
- }
295
- };
296
- }
297
- }