@@ -40,6 +40,24 @@ interface ResearchContext {
4040 verifiedUrls : Set < string > ;
4141}
4242
43+ interface ResearchOptions {
44+ maxFollowUpQueries : number ; // 0 = skip AI follow-up entirely
45+ maxUrlsToRead : number ; // How many pages to read in full
46+ overallTimeoutMs : number ; // Hard timeout for entire research
47+ }
48+
49+ const LIGHT_RESEARCH : ResearchOptions = {
50+ maxFollowUpQueries : 0 ,
51+ maxUrlsToRead : 2 ,
52+ overallTimeoutMs : 20000 ,
53+ } ;
54+
55+ const DEEP_RESEARCH : ResearchOptions = {
56+ maxFollowUpQueries : 3 ,
57+ maxUrlsToRead : 5 ,
58+ overallTimeoutMs : 45000 ,
59+ } ;
60+
4361// Generate unique request ID for tracing
4462function generateRequestId ( ) : string {
4563 return `edge_${ Date . now ( ) } _${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) } ` ;
@@ -384,50 +402,51 @@ async function generateFollowUpQueries(
384402 }
385403}
386404
387- async function deepResearch (
405+ async function deepResearchInner (
388406 claim : string ,
389407 region : string ,
390408 provider : Provider ,
391- model : string
409+ model : string ,
410+ options : ResearchOptions
392411) : Promise < ResearchContext > {
393412 const context : ResearchContext = {
394413 searches : [ ] ,
395414 readContent : [ ] ,
396415 verifiedUrls : new Set ( ) ,
397416 } ;
398417
399- console . log ( "[DEEP RESEARCH] Starting for claim:" , claim ) ;
418+ console . log ( `[ RESEARCH] Starting (mode: ${ options . maxFollowUpQueries > 0 ? "deep" : "light" } ) for: ${ claim } ` ) ;
400419
401420 // Round 1: Initial search
402- const initialQuery = `${ claim } ${ region } statistics data` ;
421+ const initialQuery = `${ claim } ${ region } statistics data` . trim ( ) ;
403422 const round1Results = await jinaSearch ( initialQuery ) ;
404423 const round1Text = formatSearchResults ( round1Results ) ;
405424 context . searches . push ( { query : initialQuery , results : round1Text } ) ;
406- console . log ( `[DEEP RESEARCH] Round 1: ${ round1Results . length } results` ) ;
425+ console . log ( `[RESEARCH] Round 1: ${ round1Results . length } results` ) ;
407426
408427 // Extract URLs for reading
409428 const urlsToRead = round1Results . slice ( 0 , 3 ) . map ( ( r ) => r . url ) . filter ( Boolean ) as string [ ] ;
410429
411- // Round 2: AI generates follow-up queries
412- const followUpQueries = await generateFollowUpQueries ( claim , round1Text , provider , model ) ;
413- console . log ( `[DEEP RESEARCH] Follow-up queries: ${ followUpQueries . join ( ", " ) } ` ) ;
414-
415- // Execute follow-up searches in parallel
416- const followUpPromises = followUpQueries . map ( async ( query ) => {
417- const results = await jinaSearch ( query ) ;
418- const text = formatSearchResults ( results ) ;
419- context . searches . push ( { query, results : text } ) ;
420- // Add more URLs to read
421- results . slice ( 0 , 2 ) . forEach ( ( r ) => {
422- if ( r . url && ! urlsToRead . includes ( r . url ) ) urlsToRead . push ( r . url ) ;
430+ // Round 2: AI generates follow-up queries (skipped in light mode)
431+ if ( options . maxFollowUpQueries > 0 ) {
432+ const followUpQueries = await generateFollowUpQueries ( claim , round1Text , provider , model ) ;
433+ console . log ( `[RESEARCH] Follow-up queries: ${ followUpQueries . join ( ", " ) } ` ) ;
434+
435+ const followUpPromises = followUpQueries . slice ( 0 , options . maxFollowUpQueries ) . map ( async ( query ) => {
436+ const results = await jinaSearch ( query ) ;
437+ const text = formatSearchResults ( results ) ;
438+ context . searches . push ( { query, results : text } ) ;
439+ results . slice ( 0 , 2 ) . forEach ( ( r ) => {
440+ if ( r . url && ! urlsToRead . includes ( r . url ) ) urlsToRead . push ( r . url ) ;
441+ } ) ;
442+ return results . length ;
423443 } ) ;
424- return results . length ;
425- } ) ;
426- await Promise . all ( followUpPromises ) ;
427- console . log ( `[DEEP RESEARCH] Round 2 complete, ${ context . searches . length } total searches` ) ;
444+ await Promise . all ( followUpPromises ) ;
445+ console . log ( `[RESEARCH] Round 2 complete, ${ context . searches . length } total searches` ) ;
446+ }
428447
429448 // Round 3: Read top URLs for full content
430- const readPromises = urlsToRead . slice ( 0 , 5 ) . map ( async ( url ) => {
449+ const readPromises = urlsToRead . slice ( 0 , options . maxUrlsToRead ) . map ( async ( url ) => {
431450 const content = await jinaRead ( url ) ;
432451 if ( content ) {
433452 context . readContent . push ( { url, content } ) ;
@@ -436,11 +455,34 @@ async function deepResearch(
436455 return content . length > 0 ;
437456 } ) ;
438457 await Promise . all ( readPromises ) ;
439- console . log ( `[DEEP RESEARCH] Read ${ context . readContent . length } pages` ) ;
458+ console . log ( `[RESEARCH] Read ${ context . readContent . length } pages` ) ;
440459
441460 return context ;
442461}
443462
463+ // Wrapper with overall timeout - returns partial results if research exceeds time limit
464+ async function deepResearch (
465+ claim : string ,
466+ region : string ,
467+ provider : Provider ,
468+ model : string ,
469+ options : ResearchOptions = DEEP_RESEARCH
470+ ) : Promise < ResearchContext > {
471+ try {
472+ const result = await Promise . race ( [
473+ deepResearchInner ( claim , region , provider , model , options ) ,
474+ new Promise < never > ( ( _ , reject ) =>
475+ setTimeout ( ( ) => reject ( new Error ( "Research timeout" ) ) , options . overallTimeoutMs )
476+ ) ,
477+ ] ) ;
478+ return result ;
479+ } catch ( err ) {
480+ console . warn ( `[RESEARCH] Timed out or failed after ${ options . overallTimeoutMs } ms:` , err ) ;
481+ // Return empty context so the AI call still proceeds without research
482+ return { searches : [ ] , readContent : [ ] , verifiedUrls : new Set ( ) } ;
483+ }
484+ }
485+
444486function buildResearchContext ( research : ResearchContext ) : string {
445487 let ctx = "=== WEB RESEARCH RESULTS ===\n\n" ;
446488
@@ -832,7 +874,7 @@ Deno.serve(async (req) => {
832874 const region = String ( sanitizedInput . region || "" ) ;
833875
834876 // Perform deep research with multiple search rounds
835- const research = await deepResearch ( claim , region , provider , model ) ;
877+ const research = await deepResearch ( claim , region , provider , model , DEEP_RESEARCH ) ;
836878 const searchContext = buildResearchContext ( research ) ;
837879
838880 const userPrompt = buildUserPrompt ( feature , sanitizedInput , searchContext ) ;
@@ -888,9 +930,9 @@ Deno.serve(async (req) => {
888930 const motion = String ( sanitizedInput . motion || sanitizedInput . topic || "" ) ;
889931 const side = String ( sanitizedInput . side || "pro" ) ;
890932
891- // Perform research to find supporting evidence
892- console . log ( "[GENERATE] Starting deep research for motion:" , motion ) ;
893- const research = await deepResearch ( motion , "" , provider , model ) ;
933+ // Perform light research to find supporting evidence (avoids timeout)
934+ console . log ( "[GENERATE] Starting light research for motion:" , motion ) ;
935+ const research = await deepResearch ( motion , "" , provider , model , LIGHT_RESEARCH ) ;
894936 const searchContext = buildResearchContext ( research ) ;
895937
896938 // Build prompt with research context
@@ -927,9 +969,9 @@ Deno.serve(async (req) => {
927969 if ( feature === "brainstorm" ) {
928970 const motion = String ( sanitizedInput . motion || sanitizedInput . topic || "" ) ;
929971
930- // Perform research to find relevant context
931- console . log ( "[BRAINSTORM] Starting deep research for motion:" , motion ) ;
932- const research = await deepResearch ( motion , "" , provider , model ) ;
972+ // Perform light research to find relevant context (avoids timeout)
973+ console . log ( "[BRAINSTORM] Starting light research for motion:" , motion ) ;
974+ const research = await deepResearch ( motion , "" , provider , model , LIGHT_RESEARCH ) ;
933975 const searchContext = buildResearchContext ( research ) ;
934976
935977 // Build prompt with research context
0 commit comments