@@ -737,6 +737,11 @@ async function callAIOnce(
737737 ) ;
738738}
739739
740+ // Supabase answers 504 when a function has not responded within 150 s. Every
741+ // provider call shares one budget below that, so a slow model can use the time
742+ // research left instead of a fixed 60 s. The margin covers logging and saving.
743+ const REQUEST_BUDGET_MS = 140_000 ;
744+
740745async function callOpenRouter (
741746 messages : Array < { role : string ; content : string } > ,
742747 model : string ,
@@ -762,7 +767,7 @@ async function callOpenRouter(
762767 method : "POST" ,
763768 headers : { "Content-Type" : "application/json" , "Authorization" : `Bearer ${ apiKey } ` } ,
764769 body : JSON . stringify ( payload ) ,
765- } , 60000 , signal ) ;
770+ } , REQUEST_BUDGET_MS , signal ) ;
766771 if ( ! response . ok ) throw new Error ( `OpenRouter error: ${ errorBody ?? "" } ` ) ;
767772 if ( ! data ?. choices || ! Array . isArray ( data . choices ) || data . choices . length === 0 ) throw new Error ( "OpenRouter returned no choices" ) ;
768773 const usage = normalizeProviderUsage ( data . usage ) ?? missingOpenRouterUsage ( "response" ) ;
@@ -839,6 +844,7 @@ export async function handleRequest(req: Request): Promise<Response> {
839844 if ( req . method === "OPTIONS" ) {
840845 return new Response ( "ok" , { headers : corsHeaders } ) ;
841846 }
847+ const workSignal = AbortSignal . any ( [ req . signal , AbortSignal . timeout ( REQUEST_BUDGET_MS ) ] ) ;
842848 if ( req . method !== "POST" ) {
843849 return new Response ( "Method Not Allowed" , { status : 405 , headers : corsHeaders } ) ;
844850 }
@@ -1054,7 +1060,7 @@ export async function handleRequest(req: Request): Promise<Response> {
10541060 const evaluated = await evaluateCrossfireExchange ( {
10551061 transcript : crossfireInput . transcript ,
10561062 reply : crossfireInput . reply ,
1057- signal : req . signal ,
1063+ signal : workSignal ,
10581064 } ) ;
10591065 const signalUsage = evaluated . jev ?. usage ?? evaluated . jevUsage ;
10601066 if ( signalUsage ) await logUsage ( userId , "jev_crossfire_signal" , provider , JEV_MODEL , signalUsage , undefined , evaluated . jevUsage ? "error" : "success" , evaluated . jevUsage ? "jev_incomplete" : undefined ) ;
@@ -1235,7 +1241,7 @@ export async function handleRequest(req: Request): Promise<Response> {
12351241 canAccessProModels : Boolean ( rateLimit . canAccessProModels ) ,
12361242 reasoningEffort : requestedReasoningEffort ,
12371243 sourceCount : Array . isArray ( rawSources ) ? rawSources . length : 0 ,
1238- signal : req . signal ,
1244+ signal : workSignal ,
12391245 } ) ;
12401246 model = resolution . resolvedModel ;
12411247 modelEntry = resolution . modelEntry ;
@@ -1285,7 +1291,7 @@ export async function handleRequest(req: Request): Promise<Response> {
12851291 feature,
12861292 requestText : JSON . stringify ( sanitizedInput ) ,
12871293 sources : sourceDecisionCandidates ,
1288- signal : req . signal ,
1294+ signal : workSignal ,
12891295 } ) ;
12901296 sourceAssessments = ranked . assessments ;
12911297 sources = ranked . sources . map ( ( { title, content, origin, type } ) => ( { title, content, ...( origin ? { origin } : { } ) , type } ) ) ;
@@ -1314,7 +1320,7 @@ export async function handleRequest(req: Request): Promise<Response> {
13141320 provider,
13151321 model,
13161322 reasoningEffort,
1317- signal : req . signal ,
1323+ signal : workSignal ,
13181324 onTimeout : ( ) => {
13191325 researchAccountingIncomplete = true ;
13201326 } ,
@@ -1376,7 +1382,7 @@ export async function handleRequest(req: Request): Promise<Response> {
13761382 const systemPrompt = await buildSystemPromptFor ( feature ) ;
13771383 const userPrompt = buildUserPrompt ( feature , sparringRequest as unknown as Record < string , unknown > , "" ) ;
13781384 const messages = [ { role : "user" , content : userPrompt } ] ;
1379- const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , req . signal ) ) ;
1385+ const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , workSignal ) ) ;
13801386 const output = cleanGeneratedText ( aiResult . output ) ;
13811387 const normalizedOutput = normalizeSparringOutput ( output , sparringRequest . action ) ;
13821388 const parsedOutput = parseSparringProviderOutput ( normalizedOutput , sparringRequest . action ) ;
@@ -1445,7 +1451,7 @@ export async function handleRequest(req: Request): Promise<Response> {
14451451 [ { role : "user" , content : userPrompt } ] ,
14461452 temperature ,
14471453 reasoningEffort ,
1448- req . signal ,
1454+ workSignal ,
14491455 ) ) ;
14501456 const cleanedOutput = cleanGeneratedText ( aiResult . output ) ;
14511457 const output = crossfireInput . operation === "next-turn"
@@ -1487,7 +1493,7 @@ export async function handleRequest(req: Request): Promise<Response> {
14871493 const userPrompt = buildUserPrompt ( feature , sanitizedInput , searchContext ) ;
14881494 const messages = [ { role : "user" , content : userPrompt } ] ;
14891495
1490- const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , req . signal ) ) ;
1496+ const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , workSignal ) ) ;
14911497 const output = cleanGeneratedText ( aiResult . output ) ;
14921498
14931499 // Post-process: quality floor. Only citations whose URL matches fetched,
@@ -1545,7 +1551,7 @@ export async function handleRequest(req: Request): Promise<Response> {
15451551 let urlContext = "" ;
15461552 if ( urls . size > 0 ) {
15471553 const contents = await Promise . all ( Array . from ( urls ) . slice ( 0 , 3 ) . map ( async ( u ) => {
1548- const content = await exaGetContents ( u , req . signal ) ;
1554+ const content = await exaGetContents ( u , workSignal ) ;
15491555 return content . trim ( )
15501556 ? `--- Content from ${ u } ---\n${ content . slice ( 0 , 8000 ) } \n--- End of ${ u } ---`
15511557 : "" ;
@@ -1600,7 +1606,7 @@ export async function handleRequest(req: Request): Promise<Response> {
16001606 const systemPrompt = await buildSystemPromptFor ( feature , effectiveResearch ) ;
16011607 const prompt = `${ basePrompt } ${ urlContext } ${ webSearchContext } ` ;
16021608 const messages = [ ...history , { role : "user" , content : prompt } ] ;
1603- const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , req . signal ) ) ;
1609+ const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , workSignal ) ) ;
16041610 const output = cleanGeneratedText ( aiResult . output ) ;
16051611
16061612 if ( output && typeof output === "object" ) {
@@ -1688,7 +1694,7 @@ export async function handleRequest(req: Request): Promise<Response> {
16881694 feature,
16891695 requestText : JSON . stringify ( sanitizedInput ) ,
16901696 sources : discoveredCandidates ,
1691- signal : req . signal ,
1697+ signal : workSignal ,
16921698 } ) ;
16931699 const discoveredRankingUsage = discoveredRanking . jev ?. usage ?? discoveredRanking . jevUsage ;
16941700 if ( discoveredRankingUsage ) await logUsage ( userId , "jev_source_ranking" , provider , JEV_MODEL , discoveredRankingUsage , undefined , discoveredRanking . jevUsage ? "error" : "success" , discoveredRanking . jevUsage ? "jev_incomplete" : undefined ) ;
@@ -1717,7 +1723,7 @@ export async function handleRequest(req: Request): Promise<Response> {
17171723 const systemPrompt = await buildSystemPromptFor ( feature , research ) ;
17181724 const userPrompt = buildUserPrompt ( feature , sanitizedInput , searchContext ) ;
17191725 const messages = [ { role : "user" , content : userPrompt } ] ;
1720- const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , req . signal ) ) ;
1726+ const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , workSignal ) ) ;
17211727 const output = cleanGeneratedText ( aiResult . output ) ;
17221728
17231729 // Preserve model evidence warnings when research exists. If research
@@ -1765,7 +1771,7 @@ export async function handleRequest(req: Request): Promise<Response> {
17651771 const caseUsage = await identifyCaseSourceUsage ( {
17661772 caseText : JSON . stringify ( output ) ,
17671773 sources : usageCandidates ,
1768- signal : req . signal ,
1774+ signal : workSignal ,
17691775 } ) ;
17701776 const caseUsageCost = caseUsage . jev ?. usage ?? caseUsage . jevUsage ;
17711777 if ( caseUsageCost ) await logUsage ( userId , "jev_source_usage" , provider , JEV_MODEL , caseUsageCost , undefined , caseUsage . jevUsage ? "error" : "success" , caseUsage . jevUsage ? "jev_incomplete" : undefined ) ;
@@ -1812,7 +1818,7 @@ export async function handleRequest(req: Request): Promise<Response> {
18121818 const systemPrompt = await buildSystemPromptFor ( feature , research ) ;
18131819 const userPrompt = buildUserPrompt ( feature , sanitizedInput , searchContext ) ;
18141820 const messages = [ { role : "user" , content : userPrompt } ] ;
1815- const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , req . signal ) ) ;
1821+ const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , workSignal ) ) ;
18161822 const output = cleanGeneratedText ( aiResult . output ) ;
18171823 enforceBrainstormControls ( output , side , outputMode ) ;
18181824
@@ -1840,7 +1846,7 @@ export async function handleRequest(req: Request): Promise<Response> {
18401846 const systemPrompt = await buildSystemPromptFor ( feature , research ) ;
18411847 const userPrompt = buildUserPrompt ( feature , sanitizedInput , searchContext ) ;
18421848 const messages = [ { role : "user" , content : userPrompt } ] ;
1843- const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , req . signal ) ) ;
1849+ const aiResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , workSignal ) ) ;
18441850 const output = cleanGeneratedText ( aiResult . output ) ;
18451851 enforceAttackControls ( output , detectFallacies , analyzeWeighing ) ;
18461852
@@ -1856,7 +1862,7 @@ export async function handleRequest(req: Request): Promise<Response> {
18561862 const systemPrompt = await buildSystemPromptFor ( feature ) ;
18571863 const userPrompt = buildUserPrompt ( feature , sanitizedInput , "" ) ;
18581864 const messages = [ { role : "user" , content : userPrompt } ] ;
1859- const fallbackResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , req . signal ) ) ;
1865+ const fallbackResult = recordMainResult ( await callAI ( provider , model , systemPrompt , messages , temperature , reasoningEffort , workSignal ) ) ;
18601866 const output = cleanGeneratedText ( fallbackResult . output ) ;
18611867
18621868 const latencyMs = Date . now ( ) - startTime ;
@@ -1890,7 +1896,7 @@ export async function handleRequest(req: Request): Promise<Response> {
18901896 : undefined ;
18911897 // A successful main call takes precedence over usage attached to a later
18921898 // post-processing error. This avoids counting its repair attempts twice.
1893- const mainErrorUsage = completedMainUsage ?? selectObservedUsage ( usageError , req . signal ?. aborted === true ) ;
1899+ const mainErrorUsage = completedMainUsage ?? selectObservedUsage ( usageError , workSignal ?. aborted === true ) ;
18941900 await logUsage (
18951901 userId ,
18961902 feature ,
0 commit comments