@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,128 +0,0 @@
1
- import { ITool } from '@framers/agentos';
2
- import { SearchProviderService } from '../services/searchProvider';
3
-
4
- /**
5
- * Web search tool that queries multiple search providers
6
- *
7
- * @class WebSearchTool
8
- * @implements {ITool}
9
- *
10
- * @example
11
- * ```typescript
12
- * const tool = new WebSearchTool(searchService);
13
- * const result = await tool.execute({
14
- * query: 'AgentOS extensions',
15
- * maxResults: 10,
16
- * provider: 'serper'
17
- * });
18
- * ```
19
- */
20
- export class WebSearchTool implements ITool {
21
- public readonly id = 'webSearch';
22
- public readonly name = 'Web Search';
23
- public readonly description = 'Search the web using multiple providers';
24
-
25
- /**
26
- * Creates an instance of WebSearchTool
27
- * @param {SearchProviderService} searchService - The search provider service
28
- */
29
- constructor(private searchService: SearchProviderService) {}
30
-
31
- /**
32
- * Executes a web search query
33
- *
34
- * @param {Object} input - The search parameters
35
- * @param {string} input.query - The search query string
36
- * @param {number} [input.maxResults=10] - Maximum number of results to return
37
- * @param {string} [input.provider] - Specific provider to use (serper, serpapi, brave, duckduckgo)
38
- * @returns {Promise<{success: boolean, output?: any, error?: string}>} The search results
39
- *
40
- * @throws {Error} Throws if all providers fail
41
- */
42
- async execute(input: {
43
- query: string;
44
- maxResults?: number;
45
- provider?: 'serper' | 'serpapi' | 'brave' | 'duckduckgo';
46
- }): Promise<{ success: boolean; output?: any; error?: string }> {
47
- try {
48
- const results = await this.searchService.search(input.query, {
49
- maxResults: input.maxResults || 10,
50
- provider: input.provider
51
- });
52
-
53
- return {
54
- success: true,
55
- output: results
56
- };
57
- } catch (error: any) {
58
- return {
59
- success: false,
60
- error: error.message
61
- };
62
- }
63
- }
64
-
65
- /**
66
- * Validates input parameters for the web search
67
- *
68
- * @param {any} input - The input to validate
69
- * @returns {{valid: boolean, errors: string[]}} Validation result
70
- */
71
- validate(input: any): { valid: boolean; errors: string[] } {
72
- const errors: string[] = [];
73
-
74
- if (!input.query) {
75
- errors.push('Query is required');
76
- } else if (typeof input.query !== 'string') {
77
- errors.push('Query must be a string');
78
- }
79
-
80
- if (input.maxResults !== undefined) {
81
- if (typeof input.maxResults !== 'number' || input.maxResults <= 0) {
82
- errors.push('maxResults must be a positive number');
83
- }
84
- }
85
-
86
- if (input.provider !== undefined) {
87
- const validProviders = ['serper', 'serpapi', 'brave', 'duckduckgo'];
88
- if (!validProviders.includes(input.provider)) {
89
- errors.push('Invalid provider');
90
- }
91
- }
92
-
93
- return {
94
- valid: errors.length === 0,
95
- errors
96
- };
97
- }
98
-
99
- /**
100
- * Returns the JSON schema for the tool's input
101
- *
102
- * @returns {Object} JSON Schema object
103
- */
104
- getSchema(): any {
105
- return {
106
- type: 'object',
107
- required: ['query'],
108
- properties: {
109
- query: {
110
- type: 'string',
111
- description: 'The search query'
112
- },
113
- maxResults: {
114
- type: 'number',
115
- description: 'Maximum number of results to return',
116
- default: 10,
117
- minimum: 1,
118
- maximum: 50
119
- },
120
- provider: {
121
- type: 'string',
122
- description: 'Specific search provider to use',
123
- enum: ['serper', 'serpapi', 'brave', 'duckduckgo']
124
- }
125
- }
126
- };
127
- }
128
- }
package/src/types.ts DELETED
@@ -1,41 +0,0 @@
1
- /**
2
- * @file Type definitions for search extension
3
- */
4
-
5
- export interface SearchResult {
6
- title: string;
7
- snippet: string;
8
- url: string;
9
- source?: string;
10
- publishedDate?: string;
11
- thumbnail?: string;
12
- }
13
-
14
- export interface SearchResponse {
15
- results: SearchResult[];
16
- query: string;
17
- provider: string;
18
- totalResults?: number;
19
- searchTime?: number;
20
- }
21
-
22
- export interface ResearchResult {
23
- topic: string;
24
- searchCount: number;
25
- aggregatedResults: {
26
- query: string;
27
- results: SearchResult[];
28
- }[];
29
- provider: string;
30
- }
31
-
32
- export interface FactCheckResult {
33
- claim: string;
34
- context?: string;
35
- verificationResults: {
36
- query: string;
37
- results: SearchResult[];
38
- }[];
39
- sourcesChecked: number;
40
- provider: string;
41
- }
@@ -1,173 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { createExtensionPack } from '../src';
3
- import { ExtensionContext } from '@framers/agentos';
4
-
5
- describe('Web Search Extension Integration', () => {
6
- let extensionPack: any;
7
- let context: ExtensionContext;
8
-
9
- beforeEach(() => {
10
- context = {
11
- options: {
12
- serperApiKey: process.env.SERPER_API_KEY,
13
- serpApiKey: process.env.SERPAPI_API_KEY,
14
- braveApiKey: process.env.BRAVE_API_KEY
15
- },
16
- logger: {
17
- info: () => {},
18
- warn: () => {},
19
- error: () => {},
20
- debug: () => {}
21
- }
22
- } as any;
23
-
24
- extensionPack = createExtensionPack(context);
25
- });
26
-
27
- describe('Extension Pack Creation', () => {
28
- it('should create extension pack with correct metadata', () => {
29
- expect(extensionPack.name).toBe('@framers/agentos-ext-web-search');
30
- expect(extensionPack.version).toBe('1.0.0');
31
- expect(extensionPack.descriptors).toHaveLength(3);
32
- });
33
-
34
- it('should register all three tools', () => {
35
- const toolIds = extensionPack.descriptors.map((d: any) => d.id);
36
- expect(toolIds).toContain('webSearch');
37
- expect(toolIds).toContain('researchAggregator');
38
- expect(toolIds).toContain('factCheck');
39
- });
40
-
41
- it('should set correct tool kinds', () => {
42
- extensionPack.descriptors.forEach((descriptor: any) => {
43
- expect(descriptor.kind).toBe('tool');
44
- });
45
- });
46
- });
47
-
48
- describe('Tool Execution', () => {
49
- it('should execute web search tool', async () => {
50
- const webSearchDescriptor = extensionPack.descriptors.find(
51
- (d: any) => d.id === 'webSearch'
52
- );
53
- const webSearchTool = webSearchDescriptor.payload;
54
-
55
- // Test with DuckDuckGo (no API key required)
56
- const result = await webSearchTool.execute({
57
- query: 'AgentOS extensions'
58
- });
59
-
60
- expect(result.success).toBe(true);
61
- expect(result.output).toHaveProperty('provider');
62
- expect(result.output).toHaveProperty('results');
63
- expect(result.output).toHaveProperty('metadata');
64
- });
65
-
66
- it('should execute research aggregator tool', async () => {
67
- const aggregatorDescriptor = extensionPack.descriptors.find(
68
- (d: any) => d.id === 'researchAggregator'
69
- );
70
- const aggregatorTool = aggregatorDescriptor.payload;
71
-
72
- const result = await aggregatorTool.execute({
73
- topic: 'TypeScript testing',
74
- sources: 2
75
- });
76
-
77
- expect(result.success).toBe(true);
78
- expect(result.output).toHaveProperty('topic');
79
- expect(result.output).toHaveProperty('sources');
80
- expect(result.output).toHaveProperty('aggregatedResults');
81
- });
82
-
83
- it('should execute fact check tool', async () => {
84
- const factCheckDescriptor = extensionPack.descriptors.find(
85
- (d: any) => d.id === 'factCheck'
86
- );
87
- const factCheckTool = factCheckDescriptor.payload;
88
-
89
- const result = await factCheckTool.execute({
90
- statement: 'TypeScript was created by Microsoft',
91
- checkSources: true
92
- });
93
-
94
- expect(result.success).toBe(true);
95
- expect(result.output).toHaveProperty('statement');
96
- expect(result.output).toHaveProperty('verdict');
97
- expect(result.output).toHaveProperty('confidence');
98
- expect(result.output).toHaveProperty('sources');
99
- });
100
- });
101
-
102
- describe('Error Handling', () => {
103
- it('should handle missing API keys gracefully', async () => {
104
- const noKeysContext = {
105
- options: {},
106
- logger: context.logger
107
- } as any;
108
-
109
- const pack = createExtensionPack(noKeysContext);
110
- const webSearch = pack.descriptors[0].payload;
111
-
112
- const result = await webSearch.execute({
113
- query: 'test query'
114
- });
115
-
116
- // Should fallback to DuckDuckGo
117
- expect(result.success).toBe(true);
118
- expect(result.output.provider).toBe('duckduckgo');
119
- });
120
-
121
- it('should validate inputs before execution', async () => {
122
- const webSearchDescriptor = extensionPack.descriptors.find(
123
- (d: any) => d.id === 'webSearch'
124
- );
125
- const webSearchTool = webSearchDescriptor.payload;
126
-
127
- const validation = webSearchTool.validate({
128
- // Missing required 'query' field
129
- maxResults: 5
130
- });
131
-
132
- expect(validation.valid).toBe(false);
133
- expect(validation.errors).toContain('Query is required');
134
- });
135
- });
136
-
137
- describe('Lifecycle Hooks', () => {
138
- it('should call onActivate when extension is activated', async () => {
139
- let activated = false;
140
-
141
- const customPack = createExtensionPack({
142
- ...context,
143
- onActivate: async () => {
144
- activated = true;
145
- }
146
- });
147
-
148
- if (customPack.onActivate) {
149
- await customPack.onActivate();
150
- }
151
-
152
- expect(activated).toBe(true);
153
- });
154
-
155
- it('should call onDeactivate when extension is deactivated', async () => {
156
- let deactivated = false;
157
-
158
- const customPack = createExtensionPack({
159
- ...context,
160
- onDeactivate: async () => {
161
- deactivated = true;
162
- }
163
- });
164
-
165
- if (customPack.onDeactivate) {
166
- await customPack.onDeactivate();
167
- }
168
-
169
- expect(deactivated).toBe(true);
170
- });
171
- });
172
- });
173
-
@@ -1,153 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { SearchProviderService } from '../src/services/searchProvider';
3
-
4
- // Mock fetch
5
- global.fetch = vi.fn();
6
-
7
- describe('SearchProviderService', () => {
8
- let service: SearchProviderService;
9
-
10
- beforeEach(() => {
11
- service = new SearchProviderService({
12
- serperApiKey: 'test-serper-key',
13
- serpApiKey: 'test-serpapi-key',
14
- braveApiKey: 'test-brave-key'
15
- });
16
- vi.clearAllMocks();
17
- });
18
-
19
- describe('constructor', () => {
20
- it('should initialize with provided API keys', () => {
21
- expect(service).toBeDefined();
22
- });
23
-
24
- it('should initialize with empty config', () => {
25
- const emptyService = new SearchProviderService({});
26
- expect(emptyService).toBeDefined();
27
- });
28
- });
29
-
30
- describe('search', () => {
31
- it('should fallback through providers on failure', async () => {
32
- const mockFetch = global.fetch as any;
33
-
34
- // First provider fails
35
- mockFetch.mockRejectedValueOnce(new Error('Serper API error'));
36
-
37
- // Second provider succeeds
38
- mockFetch.mockResolvedValueOnce({
39
- ok: true,
40
- json: async () => ({
41
- organic_results: [
42
- { title: 'Test', link: 'https://example.com', snippet: 'Test snippet' }
43
- ]
44
- })
45
- });
46
-
47
- const result = await service.search('test query');
48
-
49
- expect(result.provider).toBe('serpapi');
50
- expect(result.results).toHaveLength(1);
51
- expect(result.results[0].title).toBe('Test');
52
- });
53
-
54
- it('should use DuckDuckGo as final fallback', async () => {
55
- const mockFetch = global.fetch as any;
56
-
57
- // All API providers fail
58
- mockFetch.mockRejectedValue(new Error('API error'));
59
-
60
- // Mock DuckDuckGo HTML response
61
- const duckDuckGoHtml = `
62
- <div class="result">
63
- <h2 class="result__title">
64
- <a href="https://example.com">Test Result</a>
65
- </h2>
66
- <a class="result__snippet">Test snippet from DuckDuckGo</a>
67
- </div>
68
- `;
69
-
70
- mockFetch.mockResolvedValueOnce({
71
- ok: true,
72
- text: async () => duckDuckGoHtml
73
- });
74
-
75
- const serviceNoKeys = new SearchProviderService({});
76
- const result = await serviceNoKeys.search('test query');
77
-
78
- expect(result.provider).toBe('duckduckgo');
79
- expect(result.metadata.fallback).toBe(true);
80
- });
81
-
82
- it('should respect rate limiting', async () => {
83
- const mockFetch = global.fetch as any;
84
- mockFetch.mockResolvedValue({
85
- ok: true,
86
- json: async () => ({ organic: [] })
87
- });
88
-
89
- // Make multiple rapid requests
90
- const promises = [];
91
- for (let i = 0; i < 5; i++) {
92
- promises.push(service.search(`query ${i}`));
93
- }
94
-
95
- const startTime = Date.now();
96
- await Promise.all(promises);
97
- const endTime = Date.now();
98
-
99
- // Should take at least some time due to rate limiting
100
- expect(endTime - startTime).toBeGreaterThanOrEqual(0);
101
- });
102
-
103
- it('should respect maxResults parameter', async () => {
104
- const mockFetch = global.fetch as any;
105
- mockFetch.mockResolvedValue({
106
- ok: true,
107
- json: async () => ({
108
- organic: Array(20).fill({
109
- title: 'Test',
110
- link: 'https://example.com',
111
- snippet: 'Test'
112
- })
113
- })
114
- });
115
-
116
- const result = await service.search('test', { maxResults: 5 });
117
- expect(result.results).toHaveLength(5);
118
- });
119
- });
120
-
121
- describe('searchSerper', () => {
122
- it('should format request correctly', async () => {
123
- const mockFetch = global.fetch as any;
124
- mockFetch.mockResolvedValue({
125
- ok: true,
126
- json: async () => ({ organic: [] })
127
- });
128
-
129
- await service.search('test query', { provider: 'serper' });
130
-
131
- expect(mockFetch).toHaveBeenCalledWith(
132
- 'https://google.serper.dev/search',
133
- expect.objectContaining({
134
- method: 'POST',
135
- headers: {
136
- 'X-API-KEY': 'test-serper-key',
137
- 'Content-Type': 'application/json'
138
- },
139
- body: JSON.stringify({ q: 'test query', num: 10 })
140
- })
141
- );
142
- });
143
- });
144
-
145
- describe('getRecommendedProviders', () => {
146
- it('should return provider recommendations', () => {
147
- const providers = SearchProviderService.getRecommendedProviders();
148
- expect(providers).toHaveLength(4);
149
- expect(providers[0].name).toBe('Serper');
150
- expect(providers[0].signupUrl).toContain('serper.dev');
151
- });
152
- });
153
- });
@@ -1,128 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { WebSearchTool } from '../src/tools/webSearch';
3
- import { SearchProviderService } from '../src/services/searchProvider';
4
-
5
- // Mock the search provider service
6
- vi.mock('../src/services/searchProvider');
7
-
8
- describe('WebSearchTool', () => {
9
- let tool: WebSearchTool;
10
- let mockSearchService: vi.Mocked<SearchProviderService>;
11
-
12
- beforeEach(() => {
13
- mockSearchService = new SearchProviderService({}) as any;
14
- tool = new WebSearchTool(mockSearchService);
15
- vi.clearAllMocks();
16
- });
17
-
18
- describe('constructor', () => {
19
- it('should initialize with correct metadata', () => {
20
- expect(tool.id).toBe('webSearch');
21
- expect(tool.name).toBe('Web Search');
22
- expect(tool.description).toContain('Search the web');
23
- });
24
- });
25
-
26
- describe('execute', () => {
27
- it('should call search service with correct parameters', async () => {
28
- const mockResults = {
29
- provider: 'serper',
30
- results: [
31
- { title: 'Test', url: 'https://example.com', snippet: 'Test snippet' }
32
- ],
33
- metadata: { query: 'test query', timestamp: new Date().toISOString() }
34
- };
35
-
36
- mockSearchService.search = vi.fn().mockResolvedValue(mockResults);
37
-
38
- const input = {
39
- query: 'test query',
40
- maxResults: 5,
41
- provider: 'serper' as const
42
- };
43
-
44
- const result = await tool.execute(input);
45
-
46
- expect(mockSearchService.search).toHaveBeenCalledWith('test query', {
47
- maxResults: 5,
48
- provider: 'serper'
49
- });
50
- expect(result.success).toBe(true);
51
- expect(result.output).toEqual(mockResults);
52
- });
53
-
54
- it('should handle errors gracefully', async () => {
55
- const error = new Error('API error');
56
- mockSearchService.search = vi.fn().mockRejectedValue(error);
57
-
58
- const input = { query: 'test query' };
59
- const result = await tool.execute(input);
60
-
61
- expect(result.success).toBe(false);
62
- expect(result.error).toBe('API error');
63
- });
64
-
65
- it('should use default values when not provided', async () => {
66
- mockSearchService.search = vi.fn().mockResolvedValue({
67
- provider: 'duckduckgo',
68
- results: [],
69
- metadata: {}
70
- });
71
-
72
- const input = { query: 'test query' };
73
- await tool.execute(input);
74
-
75
- expect(mockSearchService.search).toHaveBeenCalledWith('test query', {
76
- maxResults: 10,
77
- provider: undefined
78
- });
79
- });
80
- });
81
-
82
- describe('validate', () => {
83
- it('should validate required query parameter', () => {
84
- const result = tool.validate({});
85
- expect(result.valid).toBe(false);
86
- expect(result.errors).toContain('Query is required');
87
- });
88
-
89
- it('should validate query is a string', () => {
90
- const result = tool.validate({ query: 123 });
91
- expect(result.valid).toBe(false);
92
- expect(result.errors).toContain('Query must be a string');
93
- });
94
-
95
- it('should validate maxResults is a positive number', () => {
96
- const result = tool.validate({ query: 'test', maxResults: -1 });
97
- expect(result.valid).toBe(false);
98
- expect(result.errors).toContain('maxResults must be a positive number');
99
- });
100
-
101
- it('should validate provider is from allowed list', () => {
102
- const result = tool.validate({ query: 'test', provider: 'invalid' });
103
- expect(result.valid).toBe(false);
104
- expect(result.errors).toContain('Invalid provider');
105
- });
106
-
107
- it('should pass validation with correct inputs', () => {
108
- const result = tool.validate({
109
- query: 'test query',
110
- maxResults: 5,
111
- provider: 'serper'
112
- });
113
- expect(result.valid).toBe(true);
114
- expect(result.errors).toHaveLength(0);
115
- });
116
- });
117
-
118
- describe('getSchema', () => {
119
- it('should return proper JSON schema', () => {
120
- const schema = tool.getSchema();
121
- expect(schema.type).toBe('object');
122
- expect(schema.required).toEqual(['query']);
123
- expect(schema.properties.query.type).toBe('string');
124
- expect(schema.properties.maxResults.type).toBe('number');
125
- expect(schema.properties.provider.enum).toContain('serper');
126
- });
127
- });
128
- });
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "commonjs",
5
- "lib": ["ES2022"],
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "declaration": true,
13
- "declarationMap": true,
14
- "sourceMap": true,
15
- "resolveJsonModule": true,
16
- "moduleResolution": "node",
17
- "types": ["node"]
18
- },
19
- "include": ["src/**/*"],
20
- "exclude": ["node_modules", "dist", "test"]
21
- }
package/vitest.config.ts DELETED
@@ -1,32 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
- import path from 'path';
3
-
4
- export default defineConfig({
5
- test: {
6
- globals: true,
7
- environment: 'node',
8
- coverage: {
9
- provider: 'v8',
10
- reporter: ['text', 'json', 'html', 'lcov'],
11
- exclude: [
12
- 'node_modules/',
13
- 'dist/',
14
- 'test/',
15
- '*.config.ts'
16
- ],
17
- thresholds: {
18
- branches: 80,
19
- functions: 80,
20
- lines: 80,
21
- statements: 80
22
- }
23
- },
24
- include: ['test/**/*.spec.ts'],
25
- testTimeout: 10000
26
- },
27
- resolve: {
28
- alias: {
29
- '@framers/agentos': path.resolve(__dirname, '../../../../../packages/agentos/src')
30
- }
31
- }
32
- });