@framers/agentos-ext-web-search 1.1.1 → 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.
@@ -1,398 +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
- searxngUrl: 'http://localhost:8888',
16
- });
17
- vi.clearAllMocks();
18
- });
19
-
20
- describe('constructor', () => {
21
- it('should initialize with provided API keys', () => {
22
- expect(service).toBeDefined();
23
- });
24
-
25
- it('should initialize with empty config', () => {
26
- const emptyService = new SearchProviderService({});
27
- expect(emptyService).toBeDefined();
28
- });
29
- });
30
-
31
- describe('search', () => {
32
- it('should fallback through providers on failure', async () => {
33
- const mockFetch = global.fetch as any;
34
-
35
- // First provider fails
36
- mockFetch.mockRejectedValueOnce(new Error('Serper API error'));
37
-
38
- // Second provider succeeds
39
- mockFetch.mockResolvedValueOnce({
40
- ok: true,
41
- json: async () => ({
42
- organic_results: [
43
- { title: 'Test', link: 'https://example.com', snippet: 'Test snippet' }
44
- ]
45
- })
46
- });
47
-
48
- const result = await service.search('test query');
49
-
50
- expect(result.provider).toBe('serpapi');
51
- expect(result.results).toHaveLength(1);
52
- expect(result.results[0].title).toBe('Test');
53
- });
54
-
55
- it('should use DuckDuckGo as final fallback', async () => {
56
- const mockFetch = global.fetch as any;
57
-
58
- mockFetch.mockResolvedValueOnce({
59
- ok: true,
60
- json: async () => ({
61
- Heading: 'Test Result',
62
- AbstractText: 'Test snippet from DuckDuckGo',
63
- AbstractURL: 'https://example.com',
64
- RelatedTopics: [],
65
- }),
66
- });
67
-
68
- const serviceNoKeys = new SearchProviderService({});
69
- const result = await serviceNoKeys.search('test query');
70
-
71
- expect(result.provider).toBe('duckduckgo');
72
- expect(result.metadata.fallback).toBe(true);
73
- });
74
-
75
- it('should respect rate limiting', async () => {
76
- const mockFetch = global.fetch as any;
77
- mockFetch.mockResolvedValue({
78
- ok: true,
79
- json: async () => ({ organic: [] })
80
- });
81
-
82
- // Make multiple rapid requests
83
- const promises = [];
84
- for (let i = 0; i < 5; i++) {
85
- promises.push(service.search(`query ${i}`));
86
- }
87
-
88
- const startTime = Date.now();
89
- await Promise.all(promises);
90
- const endTime = Date.now();
91
-
92
- // Should take at least some time due to rate limiting
93
- expect(endTime - startTime).toBeGreaterThanOrEqual(0);
94
- });
95
-
96
- it('should respect maxResults parameter', async () => {
97
- const mockFetch = global.fetch as any;
98
- mockFetch.mockResolvedValue({
99
- ok: true,
100
- json: async () => ({
101
- organic: Array(20).fill({
102
- title: 'Test',
103
- link: 'https://example.com',
104
- snippet: 'Test'
105
- })
106
- })
107
- });
108
-
109
- const result = await service.search('test', { maxResults: 5 });
110
- expect(result.results).toHaveLength(5);
111
- });
112
- });
113
-
114
- describe('searchSerper', () => {
115
- it('should format request correctly', async () => {
116
- const mockFetch = global.fetch as any;
117
- mockFetch.mockResolvedValue({
118
- ok: true,
119
- json: async () => ({ organic: [] })
120
- });
121
-
122
- await service.search('test query', { provider: 'serper' });
123
-
124
- expect(mockFetch).toHaveBeenCalledWith(
125
- 'https://google.serper.dev/search',
126
- expect.objectContaining({
127
- method: 'POST',
128
- headers: {
129
- 'X-API-KEY': 'test-serper-key',
130
- 'Content-Type': 'application/json'
131
- },
132
- body: JSON.stringify({ q: 'test query', num: 10 })
133
- })
134
- );
135
- });
136
- });
137
-
138
- describe('getAvailableProviders', () => {
139
- it('should return providers with configured API keys', () => {
140
- const providers = service.getAvailableProviders();
141
- expect(providers).toEqual(['serper', 'serpapi', 'brave', 'searxng']);
142
- });
143
-
144
- it('should include searxng when URL is configured', () => {
145
- const searxngService = new SearchProviderService({ searxngUrl: 'http://searxng:8080' });
146
- expect(searxngService.getAvailableProviders()).toEqual(['searxng']);
147
- });
148
-
149
- it('should return empty array with no API keys', () => {
150
- const emptyService = new SearchProviderService({});
151
- expect(emptyService.getAvailableProviders()).toEqual([]);
152
- });
153
- });
154
-
155
- describe('searchSearXNG', () => {
156
- it('should format request with correct URL params', async () => {
157
- const mockFetch = global.fetch as any;
158
- mockFetch.mockResolvedValue({
159
- ok: true,
160
- json: async () => ({
161
- results: [
162
- { title: 'SearXNG Result', url: 'https://example.com/searxng', content: 'SearXNG snippet' },
163
- ],
164
- }),
165
- });
166
-
167
- const searxngService = new SearchProviderService({ searxngUrl: 'http://searxng:8080' });
168
- const result = await searxngService.search('test query', { provider: 'searxng' });
169
-
170
- expect(mockFetch).toHaveBeenCalledWith(
171
- expect.stringContaining('http://searxng:8080/search?'),
172
- );
173
- const calledUrl = mockFetch.mock.calls[0][0] as string;
174
- expect(calledUrl).toContain('format=json');
175
- expect(calledUrl).toContain('categories=general');
176
- expect(result.provider).toBe('searxng');
177
- expect(result.results).toHaveLength(1);
178
- });
179
-
180
- it('should map SearXNG "content" field to "snippet"', async () => {
181
- const mockFetch = global.fetch as any;
182
- mockFetch.mockResolvedValue({
183
- ok: true,
184
- json: async () => ({
185
- results: [
186
- { title: 'Title', url: 'https://example.com', content: 'SearXNG content field' },
187
- ],
188
- }),
189
- });
190
-
191
- const searxngService = new SearchProviderService({ searxngUrl: 'http://searxng:8080' });
192
- const result = await searxngService.search('test', { provider: 'searxng' });
193
-
194
- expect(result.results[0].snippet).toBe('SearXNG content field');
195
- });
196
-
197
- it('should pass category parameter to SearXNG', async () => {
198
- const mockFetch = global.fetch as any;
199
- mockFetch.mockResolvedValue({
200
- ok: true,
201
- json: async () => ({ results: [] }),
202
- });
203
-
204
- const searxngService = new SearchProviderService({ searxngUrl: 'http://searxng:8080' });
205
- await searxngService.search('AI news', { provider: 'searxng', category: 'news' });
206
-
207
- const calledUrl = mockFetch.mock.calls[0][0] as string;
208
- expect(calledUrl).toContain('categories=news');
209
- });
210
-
211
- it('should handle SearXNG API errors', async () => {
212
- const mockFetch = global.fetch as any;
213
- mockFetch.mockResolvedValueOnce({ ok: false, statusText: 'Service Unavailable' });
214
- // DuckDuckGo fallback
215
- mockFetch.mockResolvedValueOnce({
216
- ok: true,
217
- json: async () => ({ Heading: 'DDG', AbstractText: 'Fallback', AbstractURL: 'https://example.com', RelatedTopics: [] }),
218
- });
219
-
220
- const searxngService = new SearchProviderService({ searxngUrl: 'http://searxng:8080' });
221
- const result = await searxngService.search('test');
222
-
223
- // Should fall through to DuckDuckGo
224
- expect(result.provider).toBe('duckduckgo');
225
- });
226
-
227
- it('should strip trailing slashes from SearXNG URL', async () => {
228
- const mockFetch = global.fetch as any;
229
- mockFetch.mockResolvedValue({
230
- ok: true,
231
- json: async () => ({ results: [] }),
232
- });
233
-
234
- const searxngService = new SearchProviderService({ searxngUrl: 'http://searxng:8080///' });
235
- await searxngService.search('test', { provider: 'searxng' });
236
-
237
- const calledUrl = mockFetch.mock.calls[0][0] as string;
238
- expect(calledUrl).toMatch(/^http:\/\/searxng:8080\/search\?/);
239
- });
240
- });
241
-
242
- describe('normalizeUrl', () => {
243
- it('should strip www prefix', () => {
244
- expect(SearchProviderService.normalizeUrl('https://www.example.com/page'))
245
- .toBe('https://example.com/page');
246
- });
247
-
248
- it('should strip tracking params (utm_*, fbclid, gclid)', () => {
249
- const url = 'https://example.com/page?utm_source=google&utm_medium=cpc&fbclid=abc&real=keep';
250
- const normalized = SearchProviderService.normalizeUrl(url);
251
- expect(normalized).toContain('real=keep');
252
- expect(normalized).not.toContain('utm_source');
253
- expect(normalized).not.toContain('fbclid');
254
- });
255
-
256
- it('should strip trailing slashes', () => {
257
- expect(SearchProviderService.normalizeUrl('https://example.com/page/'))
258
- .toBe('https://example.com/page');
259
- });
260
-
261
- it('should sort remaining query params', () => {
262
- const url = 'https://example.com/page?z=1&a=2';
263
- const normalized = SearchProviderService.normalizeUrl(url);
264
- expect(normalized).toBe('https://example.com/page?a=2&z=1');
265
- });
266
-
267
- it('should handle invalid URLs gracefully', () => {
268
- expect(SearchProviderService.normalizeUrl('not a url')).toBe('not a url');
269
- });
270
- });
271
-
272
- describe('multiSearch', () => {
273
- it('should fan out to all available providers + duckduckgo', async () => {
274
- const mockFetch = global.fetch as any;
275
- mockFetch.mockImplementation(async (url: string) => {
276
- const urlStr = typeof url === 'string' ? url : url.toString();
277
- if (urlStr.includes('serper.dev')) {
278
- return { ok: true, json: async () => ({ organic: [
279
- { title: 'Result A', link: 'https://example.com/a', snippet: 'Snippet A', position: 1 },
280
- { title: 'Result B', link: 'https://example.com/b', snippet: 'Snippet B', position: 2 },
281
- ] }) };
282
- }
283
- if (urlStr.includes('serpapi.com')) {
284
- return { ok: true, json: async () => ({ organic_results: [
285
- { title: 'Result A', link: 'https://example.com/a', snippet: 'Snippet A longer', position: 1 },
286
- { title: 'Result C', link: 'https://example.com/c', snippet: 'Snippet C', position: 2 },
287
- ] }) };
288
- }
289
- if (urlStr.includes('brave.com')) {
290
- return { ok: true, json: async () => ({ web: { results: [
291
- { title: 'Result A', url: 'https://example.com/a', description: 'Snippet A brave' },
292
- ] } }) };
293
- }
294
- if (urlStr.includes('localhost:8888')) {
295
- return { ok: true, json: async () => ({ results: [
296
- { title: 'Result A', url: 'https://example.com/a', content: 'Snippet A searxng' },
297
- { title: 'Result E', url: 'https://example.com/e', content: 'Snippet E' },
298
- ] }) };
299
- }
300
- // duckduckgo
301
- return { ok: true, json: async () => ({ Heading: 'Result D', AbstractText: 'DDG snippet', AbstractURL: 'https://example.com/d', RelatedTopics: [] }) };
302
- });
303
-
304
- const result = await service.multiSearch('test query');
305
-
306
- expect(result.metadata.providersQueried).toContain('serper');
307
- expect(result.metadata.providersQueried).toContain('searxng');
308
- expect(result.metadata.providersQueried).toContain('duckduckgo');
309
- expect(result.metadata.deduplicatedCount).toBeLessThanOrEqual(result.metadata.totalRawResults);
310
- });
311
-
312
- it('should deduplicate results across providers', async () => {
313
- const mockFetch = global.fetch as any;
314
- // Route mocks by URL since Promise.allSettled doesn't guarantee call order
315
- mockFetch.mockImplementation(async (url: string) => {
316
- const urlStr = typeof url === 'string' ? url : url.toString();
317
- if (urlStr.includes('serper.dev')) {
318
- return { ok: true, json: async () => ({ organic: [{ title: 'Same', link: 'https://example.com/same', snippet: 'Same snippet', position: 1 }] }) };
319
- }
320
- if (urlStr.includes('serpapi.com')) {
321
- return { ok: true, json: async () => ({ organic_results: [{ title: 'Same', link: 'https://example.com/same', snippet: 'Same snippet', position: 1 }] }) };
322
- }
323
- if (urlStr.includes('brave.com')) {
324
- return { ok: true, json: async () => ({ web: { results: [{ title: 'Same', url: 'https://example.com/same', description: 'Same snippet' }] } }) };
325
- }
326
- if (urlStr.includes('localhost:8888')) {
327
- return { ok: true, json: async () => ({ results: [{ title: 'Same', url: 'https://example.com/same', content: 'Same snippet from SearXNG' }] }) };
328
- }
329
- // duckduckgo
330
- return { ok: true, json: async () => ({ Heading: 'Same', AbstractText: 'Same', AbstractURL: 'https://example.com/same', RelatedTopics: [] }) };
331
- });
332
-
333
- const result = await service.multiSearch('test');
334
-
335
- // Should be deduplicated to 1 result
336
- expect(result.results).toHaveLength(1);
337
- expect(result.results[0].agreementCount).toBeGreaterThanOrEqual(4);
338
- });
339
-
340
- it('should rank results with more provider agreement higher', async () => {
341
- const mockFetch = global.fetch as any;
342
- mockFetch.mockImplementation(async (url: string) => {
343
- const urlStr = typeof url === 'string' ? url : url.toString();
344
- if (urlStr.includes('serper.dev')) {
345
- return { ok: true, json: async () => ({ organic: [
346
- { title: 'A', link: 'https://example.com/a', snippet: 'A', position: 1 },
347
- { title: 'B', link: 'https://example.com/b', snippet: 'B', position: 2 },
348
- ] }) };
349
- }
350
- if (urlStr.includes('serpapi.com')) {
351
- return { ok: true, json: async () => ({ organic_results: [
352
- { title: 'A', link: 'https://example.com/a', snippet: 'A', position: 1 },
353
- ] }) };
354
- }
355
- if (urlStr.includes('brave.com')) {
356
- return { ok: true, json: async () => ({ web: { results: [
357
- { title: 'A', url: 'https://example.com/a', description: 'A' },
358
- ] } }) };
359
- }
360
- if (urlStr.includes('localhost:8888')) {
361
- return { ok: true, json: async () => ({ results: [
362
- { title: 'A', url: 'https://example.com/a', content: 'A' },
363
- ] }) };
364
- }
365
- // duckduckgo returns B only
366
- return { ok: true, json: async () => ({ Heading: 'B', AbstractText: 'B', AbstractURL: 'https://example.com/b', RelatedTopics: [] }) };
367
- });
368
-
369
- const result = await service.multiSearch('test');
370
-
371
- // A appears in 4 providers, B in 2 — A should rank first
372
- expect(result.results[0].url).toBe('https://example.com/a');
373
- expect(result.results[0].confidenceScore).toBeGreaterThan(result.results[1].confidenceScore);
374
- });
375
-
376
- it('should handle all providers failing gracefully', async () => {
377
- const mockFetch = global.fetch as any;
378
- mockFetch.mockRejectedValue(new Error('Network error'));
379
-
380
- const result = await service.multiSearch('test');
381
-
382
- expect(result.results).toHaveLength(0);
383
- expect(result.metadata.providersFailed.length).toBeGreaterThan(0);
384
- });
385
- });
386
-
387
- describe('getRecommendedProviders', () => {
388
- it('should return provider recommendations including SearXNG', () => {
389
- const providers = SearchProviderService.getRecommendedProviders();
390
- expect(providers).toHaveLength(5);
391
- expect(providers[0].name).toBe('Serper');
392
- expect(providers[0].signupUrl).toContain('serper.dev');
393
- const searxng = providers.find((p) => p.name === 'SearXNG');
394
- expect(searxng).toBeDefined();
395
- expect(searxng!.freeQuota).toContain('Unlimited');
396
- });
397
- });
398
- });
@@ -1,230 +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
- const ctx: any = {
13
- gmiId: 'test-gmi',
14
- personaId: 'test-persona',
15
- userContext: { userId: 'test-user' },
16
- };
17
-
18
- beforeEach(() => {
19
- mockSearchService = new SearchProviderService({}) as any;
20
- tool = new WebSearchTool(mockSearchService);
21
- vi.clearAllMocks();
22
- });
23
-
24
- describe('constructor', () => {
25
- it('should initialize with correct metadata', () => {
26
- expect(tool.id).toBe('web-search-v1');
27
- expect(tool.name).toBe('web_search');
28
- expect(tool.displayName).toBe('Web Search');
29
- expect(tool.description).toContain('Search the web');
30
- });
31
- });
32
-
33
- describe('execute', () => {
34
- it('should call search service with correct parameters', async () => {
35
- const mockResults = {
36
- provider: 'serper',
37
- results: [
38
- { title: 'Test', url: 'https://example.com', snippet: 'Test snippet' }
39
- ],
40
- metadata: { query: 'test query', timestamp: new Date().toISOString() }
41
- };
42
-
43
- mockSearchService.search = vi.fn().mockResolvedValue(mockResults);
44
-
45
- const input = {
46
- query: 'test query',
47
- maxResults: 5,
48
- provider: 'serper' as const
49
- };
50
-
51
- const result = await tool.execute(input, ctx);
52
-
53
- expect(mockSearchService.search).toHaveBeenCalledWith('test query', {
54
- maxResults: 5,
55
- provider: 'serper',
56
- category: undefined,
57
- });
58
- expect(result.success).toBe(true);
59
- expect(result.output).toEqual(mockResults);
60
- });
61
-
62
- it('should handle errors gracefully', async () => {
63
- const error = new Error('API error');
64
- mockSearchService.search = vi.fn().mockRejectedValue(error);
65
-
66
- const input = { query: 'test query' };
67
- const result = await tool.execute(input as any, ctx);
68
-
69
- expect(result.success).toBe(false);
70
- expect(result.error).toBe('API error');
71
- });
72
-
73
- it('should use default values when not provided', async () => {
74
- mockSearchService.search = vi.fn().mockResolvedValue({
75
- provider: 'duckduckgo',
76
- results: [],
77
- metadata: {}
78
- });
79
-
80
- const input = { query: 'test query' };
81
- await tool.execute(input as any, ctx);
82
-
83
- expect(mockSearchService.search).toHaveBeenCalledWith('test query', {
84
- maxResults: 10,
85
- provider: undefined,
86
- category: undefined,
87
- });
88
- });
89
-
90
- it('should pass category to search service', async () => {
91
- mockSearchService.search = vi.fn().mockResolvedValue({
92
- provider: 'searxng',
93
- results: [],
94
- metadata: {}
95
- });
96
-
97
- await tool.execute({ query: 'AI news', provider: 'searxng', category: 'news' }, ctx);
98
-
99
- expect(mockSearchService.search).toHaveBeenCalledWith('AI news', {
100
- maxResults: 10,
101
- provider: 'searxng',
102
- category: 'news',
103
- });
104
- });
105
- });
106
-
107
- describe('validateArgs', () => {
108
- it('should validate required query parameter', () => {
109
- const result = tool.validateArgs({});
110
- expect(result.isValid).toBe(false);
111
- expect(result.errors).toContain('Query is required');
112
- });
113
-
114
- it('should validate query is a string', () => {
115
- const result = tool.validateArgs({ query: 123 });
116
- expect(result.isValid).toBe(false);
117
- expect(result.errors).toContain('Query must be a string');
118
- });
119
-
120
- it('should validate maxResults is a positive number', () => {
121
- const result = tool.validateArgs({ query: 'test', maxResults: -1 });
122
- expect(result.isValid).toBe(false);
123
- expect(result.errors).toContain('maxResults must be a positive number');
124
- });
125
-
126
- it('should validate provider is from allowed list', () => {
127
- const result = tool.validateArgs({ query: 'test', provider: 'invalid' });
128
- expect(result.isValid).toBe(false);
129
- expect(result.errors).toContain('Invalid provider');
130
- });
131
-
132
- it('should pass validation with correct inputs', () => {
133
- const result = tool.validateArgs({
134
- query: 'test query',
135
- maxResults: 5,
136
- provider: 'serper'
137
- });
138
- expect(result.isValid).toBe(true);
139
- });
140
-
141
- it('should accept searxng as a valid provider', () => {
142
- const result = tool.validateArgs({ query: 'test', provider: 'searxng' });
143
- expect(result.isValid).toBe(true);
144
- });
145
- });
146
-
147
- describe('multiSearch', () => {
148
- it('should call multiSearch when multiSearch input is true', async () => {
149
- const mockMultiResults = {
150
- results: [{ title: 'Multi', url: 'https://example.com', snippet: 'Multi result', providers: ['serper', 'brave'], agreementCount: 2, confidenceScore: 80, providerPositions: { serper: 1, brave: 1 } }],
151
- metadata: { query: 'test', timestamp: new Date().toISOString(), totalResponseTime: 100, providersQueried: ['serper', 'brave'], providersSucceeded: ['serper', 'brave'], providersFailed: [], totalRawResults: 2, deduplicatedCount: 1 },
152
- };
153
- mockSearchService.multiSearch = vi.fn().mockResolvedValue(mockMultiResults);
154
-
155
- const result = await tool.execute({ query: 'test', multiSearch: true }, ctx);
156
-
157
- expect(mockSearchService.multiSearch).toHaveBeenCalledWith('test', { maxResults: 10 });
158
- expect(result.success).toBe(true);
159
- expect(result.output).toEqual(mockMultiResults);
160
- });
161
-
162
- it('should use defaultMultiSearch from constructor when input omits multiSearch', async () => {
163
- const toolWithDefault = new WebSearchTool(mockSearchService, true);
164
- const mockMultiResults = {
165
- results: [],
166
- metadata: { query: 'test', timestamp: new Date().toISOString(), totalResponseTime: 50, providersQueried: [], providersSucceeded: [], providersFailed: [], totalRawResults: 0, deduplicatedCount: 0 },
167
- };
168
- mockSearchService.multiSearch = vi.fn().mockResolvedValue(mockMultiResults);
169
-
170
- await toolWithDefault.execute({ query: 'test' }, ctx);
171
-
172
- expect(mockSearchService.multiSearch).toHaveBeenCalled();
173
- });
174
-
175
- it('should fall back to single search when provider is specified even with multiSearch', async () => {
176
- mockSearchService.search = vi.fn().mockResolvedValue({ provider: 'serper', results: [], metadata: {} });
177
-
178
- await tool.execute({ query: 'test', multiSearch: true, provider: 'serper' }, ctx);
179
-
180
- // When provider is specified, multiSearch is ignored — single provider search is used
181
- expect(mockSearchService.search).toHaveBeenCalled();
182
- });
183
- });
184
-
185
- describe('validateArgs - multiSearch', () => {
186
- it('should reject multiSearch + provider combination', () => {
187
- const result = tool.validateArgs({ query: 'test', multiSearch: true, provider: 'serper' });
188
- expect(result.isValid).toBe(false);
189
- expect(result.errors).toContain('Cannot specify both multiSearch and a specific provider');
190
- });
191
-
192
- it('should reject non-boolean multiSearch', () => {
193
- const result = tool.validateArgs({ query: 'test', multiSearch: 'yes' });
194
- expect(result.isValid).toBe(false);
195
- expect(result.errors).toContain('multiSearch must be a boolean');
196
- });
197
-
198
- it('should accept multiSearch: true without provider', () => {
199
- const result = tool.validateArgs({ query: 'test', multiSearch: true });
200
- expect(result.isValid).toBe(true);
201
- });
202
- });
203
-
204
- describe('inputSchema', () => {
205
- it('should return proper JSON schema', () => {
206
- const schema = tool.inputSchema;
207
- expect(schema.type).toBe('object');
208
- expect(schema.required).toEqual(['query']);
209
- expect(schema.properties.query.type).toBe('string');
210
- expect(schema.properties.maxResults.type).toBe('integer');
211
- expect(schema.properties.provider.enum).toContain('serper');
212
- expect(schema.properties.provider.enum).toContain('searxng');
213
- });
214
-
215
- it('should include multiSearch in schema', () => {
216
- const schema = tool.inputSchema;
217
- expect(schema.properties.multiSearch).toBeDefined();
218
- expect(schema.properties.multiSearch.type).toBe('boolean');
219
- expect(schema.properties.multiSearch.default).toBe(false);
220
- });
221
-
222
- it('should include category field for SearXNG', () => {
223
- const schema = tool.inputSchema;
224
- expect(schema.properties.category).toBeDefined();
225
- expect(schema.properties.category.type).toBe('string');
226
- expect(schema.properties.category.enum).toContain('news');
227
- expect(schema.properties.category.enum).toContain('science');
228
- });
229
- });
230
- });
package/tsconfig.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022"],
7
- "baseUrl": ".",
8
- "paths": {
9
- "@framers/agentos": ["../../../../../agentos/dist/index.d.ts"],
10
- "@framers/agentos/*": ["../../../../../agentos/dist/*"]
11
- },
12
- "outDir": "./dist",
13
- "rootDir": "./src",
14
- "strict": true,
15
- "esModuleInterop": true,
16
- "skipLibCheck": true,
17
- "forceConsistentCasingInFileNames": true,
18
- "declaration": true,
19
- "declarationMap": true,
20
- "sourceMap": true,
21
- "resolveJsonModule": true,
22
- "types": ["node"]
23
- },
24
- "include": ["src/**/*"],
25
- "exclude": ["node_modules", "dist", "test"]
26
- }
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
- });