55 */
66import { generateText , streamText , type LanguageModel } from "ai" ;
77import { LineOutputParser , LineListOutputParser } from "../../utils/outputParser" ;
8- import { getDocumentsFromLinks } from "extract-webpage/utils/documents" ;
98import type { Document } from "./document" ;
10- import { searchSearxng } from "extract-webpage/search/public-searxng" ;
11- import { searchTavily , isTavilyConfigured } from "extract-webpage/search/tavily" ;
12- import { scrapeURL } from "extract-webpage/search/url-to-html" ;
139
1410/** Strip HTML tags and decode entities — works in Cloudflare edge runtime */
1511function htmlToText ( html : string ) : string {
@@ -26,7 +22,6 @@ function htmlToText(html: string): string {
2622 . replace ( / \s + / g, ' ' )
2723 . trim ( ) ;
2824}
29- import { getSourceScrapeTimeout } from "../../config/serverRegistry" ;
3025import { formatChatHistoryAsString } from "../../utils" ;
3126import EventEmitter from "events" ;
3227import type {
@@ -127,10 +122,10 @@ class MetaSearchAgent implements MetaSearchAgentType {
127122 question = "latest information" ;
128123 }
129124
130- if ( links . length > 0 ) {
125+ if ( links . length > 0 && this . config . getDocumentsFromLinks ) {
131126 if ( question . length === 0 ) question = "summarize" ;
132127
133- const linkDocs = await getDocumentsFromLinks ( { links } ) ;
128+ const linkDocs = await this . config . getDocumentsFromLinks ( { links } ) ;
134129 const docs = await groupAndSummarizeDocs ( llm , linkDocs , question ) ;
135130
136131 return { query : question , docs } ;
@@ -174,53 +169,60 @@ class MetaSearchAgent implements MetaSearchAgentType {
174169
175170 let res : { results : any [ ] ; suggestions : string [ ] } ;
176171
177- const runSearxng = ( ) => searchSearxng ( question , {
178- language : "en" ,
179- engines : this . config . activeEngines ,
180- categories : [ category ] ,
181- } ) ;
182-
183- if (
184- isTavilyConfigured ( ) &&
185- this . config . activeEngines . length === 0 &&
186- category === "general"
187- ) {
188- try {
189- res = await searchTavily ( question , { searchDepth : "basic" , maxResults : 10 } ) ;
190- } catch ( error ) {
191- console . error ( "Tavily search failed, falling back to SearXNG:" , error ) ;
192- res = await runSearxng ( ) ;
193- }
172+ // If no search functions provided, return empty results
173+ if ( ! this . config . searchSearxng && ! this . config . searchTavily ) {
174+ res = { results : [ ] , suggestions : [ ] } ;
194175 } else {
195- if ( isTavilyConfigured ( ) ) {
176+ const runSearxng = ( ) => this . config . searchSearxng ! ( question , {
177+ language : "en" ,
178+ engines : this . config . activeEngines ,
179+ categories : [ category ] ,
180+ } ) ;
181+
182+ const isTavilyConfigured = this . config . isTavilyConfigured ?.( ) ?? false ;
183+
184+ if (
185+ isTavilyConfigured &&
186+ this . config . activeEngines . length === 0 &&
187+ category === "general"
188+ ) {
196189 try {
197- res = await Promise . race ( [
198- runSearxng ( ) ,
199- new Promise < { results : any [ ] ; suggestions : string [ ] } > ( ( _ , reject ) =>
200- setTimeout ( ( ) => reject ( new Error ( "Timeout" ) ) , 10000 )
201- )
202- ] ) ;
203- } catch ( err : any ) {
204- if ( err . message === "Timeout" ) {
205- console . warn ( "[MetaSearchAgent] SearXNG search did not respond in 10 seconds, falling back to Tavily." ) ;
206- try {
207- res = await searchTavily ( question , { searchDepth : "basic" , maxResults : 10 } ) ;
208- } catch ( tavilyErr ) {
209- console . error ( "[MetaSearchAgent] Tavily fallback also failed, awaiting SearXNG directly:" , tavilyErr ) ;
210- res = await runSearxng ( ) ;
211- }
212- } else {
213- console . error ( "[MetaSearchAgent] SearXNG search failed, falling back to Tavily:" , err ) ;
214- try {
215- res = await searchTavily ( question , { searchDepth : "basic" , maxResults : 10 } ) ;
216- } catch ( tavilyErr ) {
217- console . error ( "[MetaSearchAgent] Tavily fallback also failed:" , tavilyErr ) ;
218- throw err ;
190+ res = await this . config . searchTavily ! ( question , { searchDepth : "basic" , maxResults : 10 } ) ;
191+ } catch ( error ) {
192+ console . error ( "Tavily search failed, falling back to SearXNG:" , error ) ;
193+ res = this . config . searchSearxng ? await runSearxng ( ) : { results : [ ] , suggestions : [ ] } ;
194+ }
195+ } else {
196+ if ( isTavilyConfigured && this . config . searchTavily ) {
197+ try {
198+ res = await Promise . race ( [
199+ runSearxng ( ) ,
200+ new Promise < { results : any [ ] ; suggestions : string [ ] } > ( ( _ , reject ) =>
201+ setTimeout ( ( ) => reject ( new Error ( "Timeout" ) ) , 10000 )
202+ )
203+ ] ) ;
204+ } catch ( err : any ) {
205+ if ( err . message === "Timeout" ) {
206+ console . warn ( "[MetaSearchAgent] SearXNG search did not respond in 10 seconds, falling back to Tavily." ) ;
207+ try {
208+ res = await this . config . searchTavily ( question , { searchDepth : "basic" , maxResults : 10 } ) ;
209+ } catch ( tavilyErr ) {
210+ console . error ( "[MetaSearchAgent] Tavily fallback also failed, awaiting SearXNG directly:" , tavilyErr ) ;
211+ res = this . config . searchSearxng ? await runSearxng ( ) : { results : [ ] , suggestions : [ ] } ;
212+ }
213+ } else {
214+ console . error ( "[MetaSearchAgent] SearXNG search failed, falling back to Tavily:" , err ) ;
215+ try {
216+ res = await this . config . searchTavily ( question , { searchDepth : "basic" , maxResults : 10 } ) ;
217+ } catch ( tavilyErr ) {
218+ console . error ( "[MetaSearchAgent] Tavily fallback also failed:" , tavilyErr ) ;
219+ throw err ;
220+ }
219221 }
220222 }
223+ } else {
224+ res = this . config . searchSearxng ? await runSearxng ( ) : { results : [ ] , suggestions : [ ] } ;
221225 }
222- } else {
223- res = await runSearxng ( ) ;
224226 }
225227 }
226228
@@ -253,7 +255,8 @@ class MetaSearchAgent implements MetaSearchAgentType {
253255 perSourceTimeout = Math . max ( 2 , Math . floor ( thinkingTimeLimit / scrapeCount ) ) ;
254256 } else if ( sourceExtractionEnabled ) {
255257 scrapeCount = 3 ;
256- perSourceTimeout = Math . max ( 1 , getSourceScrapeTimeout ( ) ) ;
258+ // Default to 5 seconds if no config function available
259+ perSourceTimeout = 5 ;
257260 } else {
258261 scrapeCount = 0 ;
259262 perSourceTimeout = 0 ;
@@ -263,12 +266,12 @@ class MetaSearchAgent implements MetaSearchAgentType {
263266 const docsToScrape = documents . slice ( 0 , scrapeCount ) ;
264267 emitSearching ( "running" , `Extracting top ${ docsToScrape . length } sources` , "extract" ) ;
265268
266- const extractionTasks = docsToScrape . map ( async ( doc , idx ) => {
269+ const extractionTasks = this . config . scrapeURL ? docsToScrape . map ( async ( doc , idx ) => {
267270 const url = doc . metadata ?. url ;
268- if ( ! url ) return ;
271+ if ( ! url || ! this . config . scrapeURL ) return ;
269272 try {
270273 const result = await waitWithTimeout (
271- scrapeURL ( url , { timeout : perSourceTimeout } ) ,
274+ this . config . scrapeURL ( url , { timeout : perSourceTimeout } ) ,
272275 perSourceTimeout * 1000 + 1500 ,
273276 ) ;
274277 if ( typeof result === "string" && result . length > 100 ) {
@@ -284,7 +287,7 @@ class MetaSearchAgent implements MetaSearchAgentType {
284287 } catch {
285288 // Keep original snippet on scraping failure or timeout
286289 }
287- } ) ;
290+ } ) : [ ] ;
288291
289292 await Promise . allSettled ( extractionTasks ) ;
290293 emitSearching ( "done" , `Extracting top ${ docsToScrape . length } sources` , "extract" ) ;
0 commit comments