1
1
import ivm from "isolated-vm" ;
2
2
import DDG from "duck-duck-scrape" ;
3
+ import { ProxyAgent } from "proxy-agent" ;
3
4
import { JSDOM } from "jsdom" ;
4
5
import { inspect } from "util" ;
5
6
import { parseDocument } from "./parsers.js" ;
@@ -10,7 +11,7 @@ const log = (value) => console.log(inspect(value, { depth: null, colors: true, c
10
11
const modelId = "amazon.nova-pro-v1:0" ;
11
12
12
13
const DEFAULT_TOOLS = {
13
- search,
14
+ search : ddgSearch ,
14
15
runJavascript,
15
16
} ;
16
17
@@ -373,12 +374,63 @@ Note: Please use runJavascript for all mathematical operations, including basic
373
374
}
374
375
375
376
/**
376
- *
377
- * @param {* } param0
378
- * @returns
377
+ * @param {Object } opts - Search options (q, count, offset, freshness, goggles)
378
+ * @param {string } apiKey - Brave Search API key
379
379
*/
380
- export async function search ( { keywords, offset = 0 , time, vqd } ) {
381
- const response = await DDG . search ( keywords , { offset, time, vqd } ) ;
380
+ export async function braveSearch ( opts , apiKey = process . env . BRAVE_SEARCH_API_KEY ) {
381
+ for ( let key in opts ) {
382
+ if ( opts [ key ] === undefined ) {
383
+ delete opts [ key ] ;
384
+ }
385
+ }
386
+ const url = `https://api.search.brave.com/res/v1/web/search?${ new URLSearchParams ( opts ) } ` ;
387
+ const response = await fetch ( url , {
388
+ headers : {
389
+ "Accept" : "application/json" ,
390
+ "Accept-Encoding" : "gzip" ,
391
+ "X-Subscription-Token" : apiKey ,
392
+ } ,
393
+ } ) ;
394
+
395
+ if ( ! response . ok ) {
396
+ throw new Error ( `HTTP ${ response . status } : ${ response . statusText } ` ) ;
397
+ }
398
+
399
+ return await response . json ( ) ;
400
+ }
401
+
402
+ /**
403
+ *
404
+ * @param {* } param0
405
+ * @returns
406
+ */
407
+ export async function ddgSearch ( { keywords, offset = 0 , time, vqd } , env = process . env ) {
408
+ const { PROXY_SOURCE } = env ;
409
+ const response = await retry ( 10 , 500 , async ( ) => {
410
+ let agent ;
411
+ if ( PROXY_SOURCE ) {
412
+ const parseProxies = ( proxies ) => {
413
+ try {
414
+ const results = JSON . parse ( proxies ) ;
415
+ if ( Array . isArray ( results ?. data ) ) {
416
+ return results . data . map ( ( proxy ) => `${ proxy . protocols [ 0 ] } ://${ proxy . ip } :${ proxy . port } ` ) ;
417
+ }
418
+ } catch ( error ) {
419
+ return proxies . split ( "\n" ) ;
420
+ }
421
+ } ;
422
+ const proxyResponse = await fetch ( PROXY_SOURCE ) . then ( ( res ) => res . text ( ) ) ;
423
+ const proxies = parseProxies ( proxyResponse ) ;
424
+ let proxy = proxies [ Math . floor ( Math . random ( ) * proxies . length ) ] ;
425
+ // prepend protocol if missing (use https:// by default)
426
+ if ( ! / .+ : \/ \/ / . test ( proxy ) ) {
427
+ proxy = `https://${ proxy } ` ;
428
+ }
429
+ console . log ( "using some proxy" , proxy ) ;
430
+ agent = new ProxyAgent ( proxy ) ;
431
+ }
432
+ return await DDG . search ( keywords , { offset, time, vqd } , { agent } ) ;
433
+ } ) ;
382
434
const appendBody = async ( result ) => ( { ...result , body : await extractTextFromUrl ( result . url ) } ) ;
383
435
const results = await Promise . all ( ( response . results || [ ] ) . map ( appendBody ) ) ;
384
436
return { vqd, results } ;
@@ -430,3 +482,38 @@ export async function runJavascript({ code, globalContext = {}, memoryLimit = 12
430
482
const script = await isolate . compileScript ( String ( code ) ) ;
431
483
return await script . run ( context ) ;
432
484
}
485
+
486
+ /**
487
+ * Retries a function with exponential backoff
488
+ * @param {number } maxAttempts - Maximum number of retry attempts
489
+ * @param {number } initialDelay - Initial delay in milliseconds
490
+ * @param {Function } fn - Async function to retry
491
+ * @returns {Promise<any> } - Result of the function execution
492
+ * @throws {Error } - Throws the last error encountered after all retries are exhausted
493
+ */
494
+ export async function retry ( maxAttempts , initialDelay , fn ) {
495
+ let lastError ;
496
+
497
+ for ( let attempt = 1 ; attempt <= maxAttempts ; attempt ++ ) {
498
+ try {
499
+ return await fn ( ) ;
500
+ } catch ( error ) {
501
+ lastError = error ;
502
+ console . error ( `Attempt ${ attempt } failed:` , error ) ;
503
+
504
+ if ( attempt === maxAttempts ) {
505
+ break ;
506
+ }
507
+
508
+ // Calculate delay with exponential backoff: initialDelay * 2^(attempt-1)
509
+ const delay = initialDelay * Math . pow ( 2 , attempt - 1 ) ;
510
+
511
+ // Add some jitter to prevent thundering herd problem
512
+ const jitter = Math . random ( ) * 100 ;
513
+
514
+ await new Promise ( ( resolve ) => setTimeout ( resolve , delay + jitter ) ) ;
515
+ }
516
+ }
517
+
518
+ throw new Error ( `Failed after ${ maxAttempts } attempts. Last error: ${ lastError . message } ` ) ;
519
+ }
0 commit comments