@@ -372,8 +372,13 @@ export async function handleOpenRouterStream({
372372 cancel ( ) {
373373 clearInterval ( heartbeatInterval )
374374 clientDisconnected = true
375+ // Log truncated state to prevent OOM during logging (state can be up to 2MB)
375376 logger . warn (
376- { clientDisconnected, state } ,
377+ {
378+ clientDisconnected,
379+ responseTextLength : state . responseText . length ,
380+ reasoningTextLength : state . reasoningText . length ,
381+ } ,
377382 'Client cancelled stream, continuing OpenRouter consumption for billing' ,
378383 )
379384 } ,
@@ -549,6 +554,10 @@ async function handleStreamChunk({
549554 agentId : string
550555 model : string | undefined
551556} ) : Promise < StreamState > {
557+ // Define a safe buffer limit to prevent OOM errors on the server while
558+ // still storing enough data for logging and billing. 1MB is a generous limit.
559+ const MAX_BUFFER_SIZE = 1 * 1024 * 1024 // 1MB
560+
552561 if ( 'error' in data ) {
553562 // Log detailed error information for stream errors (e.g., Forbidden from Anthropic)
554563 const errorData = data . error as {
@@ -581,8 +590,34 @@ async function handleStreamChunk({
581590 return state
582591 }
583592 const choice = data . choices [ 0 ]
584- state . responseText += choice . delta ?. content ?? ''
585- state . reasoningText += choice . delta ?. reasoning ?? ''
593+
594+ // Append content and reasoning, but only up to the buffer limit.
595+ const contentDelta = choice . delta ?. content ?? ''
596+ if ( state . responseText . length < MAX_BUFFER_SIZE ) {
597+ state . responseText += contentDelta
598+ if ( state . responseText . length >= MAX_BUFFER_SIZE ) {
599+ state . responseText =
600+ state . responseText . slice ( 0 , MAX_BUFFER_SIZE ) + '\n---[TRUNCATED]---'
601+ logger . warn (
602+ { userId, agentId, model } ,
603+ 'Response text buffer truncated at 1MB' ,
604+ )
605+ }
606+ }
607+
608+ const reasoningDelta = choice . delta ?. reasoning ?? ''
609+ if ( state . reasoningText . length < MAX_BUFFER_SIZE ) {
610+ state . reasoningText += reasoningDelta
611+ if ( state . reasoningText . length >= MAX_BUFFER_SIZE ) {
612+ state . reasoningText =
613+ state . reasoningText . slice ( 0 , MAX_BUFFER_SIZE ) + '\n---[TRUNCATED]---'
614+ logger . warn (
615+ { userId, agentId, model } ,
616+ 'Reasoning text buffer truncated at 1MB' ,
617+ )
618+ }
619+ }
620+
586621 return state
587622}
588623
0 commit comments