@agentforge/tools 0.3.8 → 0.4.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/LICENSE +22 -0
- package/README.md +91 -5
- package/dist/index.cjs +354 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +248 -1
- package/dist/index.d.ts +248 -1
- package/dist/index.js +347 -1
- package/dist/index.js.map +1 -1
- package/package.json +16 -16
package/dist/index.js
CHANGED
|
@@ -317,6 +317,352 @@ var urlQueryParser = toolBuilder().name("url-query-parser").description("Parse q
|
|
|
317
317
|
count: Object.keys(params).length
|
|
318
318
|
};
|
|
319
319
|
}).build();
|
|
320
|
+
var webSearchSchema = z.object({
|
|
321
|
+
query: z.string().min(1).describe("The search query"),
|
|
322
|
+
maxResults: z.number().min(1).max(50).default(10).describe("Maximum number of results to return (1-50)"),
|
|
323
|
+
preferSerper: z.boolean().default(false).describe("Prefer Serper API if available (requires SERPER_API_KEY)"),
|
|
324
|
+
timeout: z.number().min(1e3).max(6e4).default(3e4).describe("Request timeout in milliseconds (1000-60000, default: 30000)")
|
|
325
|
+
});
|
|
326
|
+
var searchResultSchema = z.object({
|
|
327
|
+
title: z.string(),
|
|
328
|
+
link: z.string().url(),
|
|
329
|
+
snippet: z.string(),
|
|
330
|
+
position: z.number().optional()
|
|
331
|
+
});
|
|
332
|
+
var webSearchOutputSchema = z.object({
|
|
333
|
+
success: z.boolean(),
|
|
334
|
+
source: z.enum(["duckduckgo", "serper"]),
|
|
335
|
+
query: z.string(),
|
|
336
|
+
results: z.array(searchResultSchema),
|
|
337
|
+
totalResults: z.number().optional(),
|
|
338
|
+
error: z.string().optional(),
|
|
339
|
+
metadata: z.object({
|
|
340
|
+
responseTime: z.number().optional(),
|
|
341
|
+
fallbackUsed: z.boolean().optional()
|
|
342
|
+
}).optional()
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// src/web/web-search/utils.ts
|
|
346
|
+
function getSerperApiKey() {
|
|
347
|
+
return process.env.SERPER_API_KEY;
|
|
348
|
+
}
|
|
349
|
+
async function measureTime(fn) {
|
|
350
|
+
const start = Date.now();
|
|
351
|
+
const result = await fn();
|
|
352
|
+
const duration = Date.now() - start;
|
|
353
|
+
return { result, duration };
|
|
354
|
+
}
|
|
355
|
+
function sanitizeQuery(query) {
|
|
356
|
+
return query.trim().replace(/\s+/g, " ");
|
|
357
|
+
}
|
|
358
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
359
|
+
maxRetries: 3,
|
|
360
|
+
initialDelay: 1e3,
|
|
361
|
+
// 1 second
|
|
362
|
+
maxDelay: 1e4,
|
|
363
|
+
// 10 seconds
|
|
364
|
+
backoffMultiplier: 2
|
|
365
|
+
};
|
|
366
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
367
|
+
function isRetryableError(error) {
|
|
368
|
+
if (error.code === "ECONNRESET" || error.code === "ETIMEDOUT" || error.code === "ENOTFOUND") {
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
if (error.code === "ECONNABORTED" || error.message?.includes("timeout")) {
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
if (error.response?.status >= 500 && error.response?.status < 600) {
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
if (error.response?.status === 429) {
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
function sleep(ms) {
|
|
383
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
384
|
+
}
|
|
385
|
+
async function retryWithBackoff(fn, config = DEFAULT_RETRY_CONFIG) {
|
|
386
|
+
let lastError;
|
|
387
|
+
let delay = config.initialDelay;
|
|
388
|
+
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
389
|
+
try {
|
|
390
|
+
return await fn();
|
|
391
|
+
} catch (error) {
|
|
392
|
+
lastError = error;
|
|
393
|
+
if (!isRetryableError(error)) {
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
if (attempt === config.maxRetries) {
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
await sleep(delay);
|
|
400
|
+
delay = Math.min(delay * config.backoffMultiplier, config.maxDelay);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
throw lastError;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/web/web-search/providers/duckduckgo.ts
|
|
407
|
+
var DuckDuckGoProvider = class {
|
|
408
|
+
name = "duckduckgo";
|
|
409
|
+
/**
|
|
410
|
+
* DuckDuckGo is always available (no API key required)
|
|
411
|
+
*/
|
|
412
|
+
isAvailable() {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Search using DuckDuckGo Instant Answer API
|
|
417
|
+
* @param query - Search query
|
|
418
|
+
* @param maxResults - Maximum number of results to return
|
|
419
|
+
* @param timeout - Request timeout in milliseconds (default: 30000)
|
|
420
|
+
*/
|
|
421
|
+
async search(query, maxResults, timeout = DEFAULT_TIMEOUT) {
|
|
422
|
+
return retryWithBackoff(async () => {
|
|
423
|
+
try {
|
|
424
|
+
const response = await axios.get(
|
|
425
|
+
"https://api.duckduckgo.com/",
|
|
426
|
+
{
|
|
427
|
+
params: {
|
|
428
|
+
q: query,
|
|
429
|
+
format: "json"
|
|
430
|
+
},
|
|
431
|
+
headers: {
|
|
432
|
+
"User-Agent": "Mozilla/5.0 (compatible; AgentForge/1.0; +https://github.com/agentforge)"
|
|
433
|
+
},
|
|
434
|
+
timeout
|
|
435
|
+
}
|
|
436
|
+
);
|
|
437
|
+
return this.normalizeResults(response.data, maxResults);
|
|
438
|
+
} catch (error) {
|
|
439
|
+
throw new Error(`DuckDuckGo search failed: ${error.message}`);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Normalize DuckDuckGo response to SearchResult[]
|
|
445
|
+
* Optimized for performance with large result sets
|
|
446
|
+
*/
|
|
447
|
+
normalizeResults(data, maxResults) {
|
|
448
|
+
const results = [];
|
|
449
|
+
if (maxResults <= 0) {
|
|
450
|
+
return results;
|
|
451
|
+
}
|
|
452
|
+
if (data.Abstract && data.AbstractURL) {
|
|
453
|
+
results.push({
|
|
454
|
+
title: data.Heading || "Result",
|
|
455
|
+
link: data.AbstractURL,
|
|
456
|
+
snippet: data.Abstract,
|
|
457
|
+
position: 1
|
|
458
|
+
});
|
|
459
|
+
if (results.length >= maxResults) {
|
|
460
|
+
return results;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (data.RelatedTopics && data.RelatedTopics.length > 0) {
|
|
464
|
+
const remaining = maxResults - results.length;
|
|
465
|
+
const topicsToProcess = data.RelatedTopics.slice(0, remaining);
|
|
466
|
+
for (const topic of topicsToProcess) {
|
|
467
|
+
if (topic.Text && topic.FirstURL) {
|
|
468
|
+
const titleParts = topic.Text.split(" - ");
|
|
469
|
+
results.push({
|
|
470
|
+
title: titleParts[0] || topic.Text,
|
|
471
|
+
link: topic.FirstURL,
|
|
472
|
+
snippet: topic.Text,
|
|
473
|
+
position: results.length + 1
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (results.length >= maxResults) {
|
|
478
|
+
return results;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (data.Results && data.Results.length > 0) {
|
|
482
|
+
const remaining = maxResults - results.length;
|
|
483
|
+
const resultsToProcess = data.Results.slice(0, remaining);
|
|
484
|
+
for (const result of resultsToProcess) {
|
|
485
|
+
if (result.Text && result.FirstURL) {
|
|
486
|
+
const titleParts = result.Text.split(" - ");
|
|
487
|
+
results.push({
|
|
488
|
+
title: titleParts[0] || result.Text,
|
|
489
|
+
link: result.FirstURL,
|
|
490
|
+
snippet: result.Text,
|
|
491
|
+
position: results.length + 1
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return results;
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
function createDuckDuckGoProvider() {
|
|
500
|
+
return new DuckDuckGoProvider();
|
|
501
|
+
}
|
|
502
|
+
var SerperProvider = class {
|
|
503
|
+
name = "serper";
|
|
504
|
+
apiKey;
|
|
505
|
+
constructor() {
|
|
506
|
+
this.apiKey = getSerperApiKey();
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Serper is available if API key is set
|
|
510
|
+
*/
|
|
511
|
+
isAvailable() {
|
|
512
|
+
return !!this.apiKey;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Search using Serper API
|
|
516
|
+
* @param query - Search query
|
|
517
|
+
* @param maxResults - Maximum number of results to return
|
|
518
|
+
* @param timeout - Request timeout in milliseconds (default: 30000)
|
|
519
|
+
*/
|
|
520
|
+
async search(query, maxResults, timeout = DEFAULT_TIMEOUT) {
|
|
521
|
+
if (!this.apiKey) {
|
|
522
|
+
throw new Error(
|
|
523
|
+
"Serper API key not found. Set SERPER_API_KEY environment variable. Get your key at https://serper.dev"
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
return retryWithBackoff(async () => {
|
|
527
|
+
try {
|
|
528
|
+
const response = await axios.post(
|
|
529
|
+
"https://google.serper.dev/search",
|
|
530
|
+
{
|
|
531
|
+
q: query,
|
|
532
|
+
num: maxResults
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
headers: {
|
|
536
|
+
"X-API-KEY": this.apiKey,
|
|
537
|
+
"Content-Type": "application/json"
|
|
538
|
+
},
|
|
539
|
+
timeout
|
|
540
|
+
}
|
|
541
|
+
);
|
|
542
|
+
return this.normalizeResults(response.data, maxResults);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
if (error.response?.status === 401) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
"Invalid Serper API key. Get your key at https://serper.dev"
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
if (error.response?.status === 429) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
"Serper API rate limit exceeded. Please try again later or upgrade your plan at https://serper.dev"
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
throw new Error(`Serper search failed: ${error.message}`);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Normalize Serper response to SearchResult[]
|
|
560
|
+
* Optimized for performance with large result sets
|
|
561
|
+
*/
|
|
562
|
+
normalizeResults(data, maxResults) {
|
|
563
|
+
if (!data.organic || data.organic.length === 0 || maxResults <= 0) {
|
|
564
|
+
return [];
|
|
565
|
+
}
|
|
566
|
+
const results = [];
|
|
567
|
+
const itemsToProcess = Math.min(data.organic.length, maxResults);
|
|
568
|
+
for (let i = 0; i < itemsToProcess; i++) {
|
|
569
|
+
const item = data.organic[i];
|
|
570
|
+
if (!item.title || !item.link || !item.snippet) {
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
results.push({
|
|
574
|
+
title: item.title,
|
|
575
|
+
link: item.link,
|
|
576
|
+
snippet: item.snippet,
|
|
577
|
+
position: item.position ?? i + 1
|
|
578
|
+
});
|
|
579
|
+
if (results.length >= maxResults) {
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return results;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
function createSerperProvider() {
|
|
587
|
+
return new SerperProvider();
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/web/web-search/index.ts
|
|
591
|
+
var webSearch = toolBuilder().name("web-search").description(
|
|
592
|
+
"Search the web for information using DuckDuckGo (free) or Serper API (optional). Returns structured search results with titles, links, and snippets. Automatically falls back to Serper if DuckDuckGo returns no results and API key is available."
|
|
593
|
+
).category(ToolCategory.WEB).tags(["search", "web", "google", "duckduckgo", "serper", "internet"]).schema(webSearchSchema).implement(async (input) => {
|
|
594
|
+
const {
|
|
595
|
+
query,
|
|
596
|
+
maxResults = 10,
|
|
597
|
+
preferSerper = false,
|
|
598
|
+
timeout = DEFAULT_TIMEOUT
|
|
599
|
+
} = input;
|
|
600
|
+
const sanitizedQuery = sanitizeQuery(query);
|
|
601
|
+
const duckduckgo = createDuckDuckGoProvider();
|
|
602
|
+
const serper = createSerperProvider();
|
|
603
|
+
let primaryProvider;
|
|
604
|
+
let fallbackProvider = null;
|
|
605
|
+
if (preferSerper && serper.isAvailable()) {
|
|
606
|
+
primaryProvider = serper;
|
|
607
|
+
fallbackProvider = duckduckgo;
|
|
608
|
+
} else {
|
|
609
|
+
primaryProvider = duckduckgo;
|
|
610
|
+
fallbackProvider = serper.isAvailable() ? serper : null;
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
const { result: results, duration } = await measureTime(
|
|
614
|
+
() => primaryProvider.search(sanitizedQuery, maxResults, timeout)
|
|
615
|
+
);
|
|
616
|
+
if (results.length > 0) {
|
|
617
|
+
return {
|
|
618
|
+
success: true,
|
|
619
|
+
source: primaryProvider.name,
|
|
620
|
+
query: sanitizedQuery,
|
|
621
|
+
results,
|
|
622
|
+
totalResults: results.length,
|
|
623
|
+
metadata: {
|
|
624
|
+
responseTime: duration,
|
|
625
|
+
fallbackUsed: false
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
if (fallbackProvider) {
|
|
630
|
+
const { result: fallbackResults, duration: fallbackDuration } = await measureTime(
|
|
631
|
+
() => fallbackProvider.search(sanitizedQuery, maxResults, timeout)
|
|
632
|
+
);
|
|
633
|
+
return {
|
|
634
|
+
success: true,
|
|
635
|
+
source: fallbackProvider.name,
|
|
636
|
+
query: sanitizedQuery,
|
|
637
|
+
results: fallbackResults,
|
|
638
|
+
totalResults: fallbackResults.length,
|
|
639
|
+
metadata: {
|
|
640
|
+
responseTime: fallbackDuration,
|
|
641
|
+
fallbackUsed: true
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
success: true,
|
|
647
|
+
source: primaryProvider.name,
|
|
648
|
+
query: sanitizedQuery,
|
|
649
|
+
results: [],
|
|
650
|
+
totalResults: 0,
|
|
651
|
+
metadata: {
|
|
652
|
+
responseTime: duration,
|
|
653
|
+
fallbackUsed: false
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
} catch (error) {
|
|
657
|
+
return {
|
|
658
|
+
success: false,
|
|
659
|
+
source: primaryProvider.name,
|
|
660
|
+
query: sanitizedQuery,
|
|
661
|
+
results: [],
|
|
662
|
+
error: error.message
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
}).build();
|
|
320
666
|
var jsonParser = toolBuilder().name("json-parser").description("Parse JSON string into an object. Validates JSON syntax and returns parsed data or error details.").category(ToolCategory.UTILITY).tags(["json", "parse", "data"]).schema(z.object({
|
|
321
667
|
json: z.string().describe("JSON string to parse"),
|
|
322
668
|
strict: z.boolean().default(true).describe("Use strict JSON parsing (no trailing commas, etc.)")
|
|
@@ -1622,6 +1968,6 @@ var uuidValidator = toolBuilder().name("uuid-validator").description("Validate i
|
|
|
1622
1968
|
};
|
|
1623
1969
|
}).build();
|
|
1624
1970
|
|
|
1625
|
-
export { arrayFilter, arrayGroupBy, arrayMap, arraySort, calculator, creditCardValidator, csvGenerator, csvParser, csvToJson, currentDateTime, dateArithmetic, dateComparison, dateDifference, dateFormatter, directoryCreate, directoryDelete, directoryList, emailValidator, extractImages, extractLinks, fileAppend, fileDelete, fileExists, fileReader, fileSearch, fileWriter, htmlParser, httpClient, httpGet, httpPost, ipValidator, jsonMerge, jsonParser, jsonQuery, jsonStringify, jsonToCsv, jsonToXml, jsonValidator, mathFunctions, objectOmit, objectPick, pathBasename, pathDirname, pathExtension, pathJoin, pathNormalize, pathParse, pathRelative, pathResolve, phoneValidator, randomNumber, statistics, stringCaseConverter, stringJoin, stringLength, stringReplace, stringSplit, stringSubstring, stringTrim, urlBuilder, urlQueryParser, urlValidator, urlValidatorSimple, uuidValidator, webScraper, xmlGenerator, xmlParser, xmlToJson };
|
|
1971
|
+
export { DuckDuckGoProvider, SerperProvider, arrayFilter, arrayGroupBy, arrayMap, arraySort, calculator, createDuckDuckGoProvider, createSerperProvider, creditCardValidator, csvGenerator, csvParser, csvToJson, currentDateTime, dateArithmetic, dateComparison, dateDifference, dateFormatter, directoryCreate, directoryDelete, directoryList, emailValidator, extractImages, extractLinks, fileAppend, fileDelete, fileExists, fileReader, fileSearch, fileWriter, htmlParser, httpClient, httpGet, httpPost, ipValidator, jsonMerge, jsonParser, jsonQuery, jsonStringify, jsonToCsv, jsonToXml, jsonValidator, mathFunctions, objectOmit, objectPick, pathBasename, pathDirname, pathExtension, pathJoin, pathNormalize, pathParse, pathRelative, pathResolve, phoneValidator, randomNumber, searchResultSchema, statistics, stringCaseConverter, stringJoin, stringLength, stringReplace, stringSplit, stringSubstring, stringTrim, urlBuilder, urlQueryParser, urlValidator, urlValidatorSimple, uuidValidator, webScraper, webSearch, webSearchOutputSchema, webSearchSchema, xmlGenerator, xmlParser, xmlToJson };
|
|
1626
1972
|
//# sourceMappingURL=index.js.map
|
|
1627
1973
|
//# sourceMappingURL=index.js.map
|