@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
package/src/tools/webSearch.ts
DELETED
|
@@ -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
|
-
}
|
package/test/integration.spec.ts
DELETED
|
@@ -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
|
-
});
|
package/test/webSearch.spec.ts
DELETED
|
@@ -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
|
-
});
|