@@ -300,60 +300,81 @@ search result: <search_result>${searchResultContent}</search_result>`,
300300 const toolCalls : Array < { type : string , result ?: any } > = [ ]
301301 let editImageId : string | undefined
302302
303- for await ( const event of stream ) {
304- if ( event . type === 'response.reasoning_summary_text.delta' ) {
305- const delta : string = event . delta || ''
306- responseReasoning += delta
307- process ?.( {
308- delta : { reasoning : delta } ,
309- } )
310- }
311- else if ( event . type === 'response.reasoning_summary_text.done' ) {
312- responseReasoning += '\n'
313- process ?.( {
314- delta : { reasoning : '\n' } ,
315- } )
316- }
317- else if ( event . type === 'response.output_text.delta' ) {
318- const delta : string = event . delta || ''
319- responseText += delta
320- process ?.( {
321- text : responseText ,
322- delta : { text : delta } ,
323- } )
324- }
325- else if ( event . type === 'response.completed' ) {
326- const resp = event . response
327- responseId = resp . id
328- usage . prompt_tokens = resp . usage . input_tokens
329- usage . completion_tokens = resp . usage . output_tokens
330- usage . total_tokens = resp . usage . total_tokens
331-
332- // Extract tool calls from response
333- if ( resp . output && Array . isArray ( resp . output ) ) {
334- editImageId = responseId
335- for ( const output of resp . output ) {
336- if ( output . type === 'image_generation_call' && output . result ) {
337- const base64Data = output . result
338- const fileIdentifier = await saveBase64ToFile ( base64Data )
339-
340- if ( fileIdentifier ) {
341- toolCalls . push ( {
342- type : 'image_generation' ,
343- result : fileIdentifier , // 文件名或S3 URL,前端会自动处理
344- } )
345- }
346- else {
347- toolCalls . push ( {
348- type : 'image_generation' ,
349- result : base64Data ,
350- } )
303+ // 心跳机制:防止生图等长时间操作时连接超时
304+ let heartbeatInterval : NodeJS . Timeout | null = null
305+ const HEARTBEAT_INTERVAL = 30000 // 30秒发送一次心跳
306+
307+ // 启动心跳定时器
308+ heartbeatInterval = setInterval ( ( ) => {
309+ // 发送心跳数据,保持连接活跃
310+ process ?.( {
311+ delta : { heartbeat : true } ,
312+ } )
313+ } , HEARTBEAT_INTERVAL )
314+
315+ try {
316+ for await ( const event of stream ) {
317+ if ( event . type === 'response.reasoning_summary_text.delta' ) {
318+ const delta : string = event . delta || ''
319+ responseReasoning += delta
320+ process ?.( {
321+ delta : { reasoning : delta } ,
322+ } )
323+ }
324+ else if ( event . type === 'response.reasoning_summary_text.done' ) {
325+ responseReasoning += '\n'
326+ process ?.( {
327+ delta : { reasoning : '\n' } ,
328+ } )
329+ }
330+ else if ( event . type === 'response.output_text.delta' ) {
331+ const delta : string = event . delta || ''
332+ responseText += delta
333+ process ?.( {
334+ text : responseText ,
335+ delta : { text : delta } ,
336+ } )
337+ }
338+ else if ( event . type === 'response.completed' ) {
339+ const resp = event . response
340+ responseId = resp . id
341+ usage . prompt_tokens = resp . usage . input_tokens
342+ usage . completion_tokens = resp . usage . output_tokens
343+ usage . total_tokens = resp . usage . total_tokens
344+
345+ // Extract tool calls from response
346+ if ( resp . output && Array . isArray ( resp . output ) ) {
347+ editImageId = responseId
348+ for ( const output of resp . output ) {
349+ if ( output . type === 'image_generation_call' && output . result ) {
350+ const base64Data = output . result
351+ const fileIdentifier = await saveBase64ToFile ( base64Data )
352+
353+ if ( fileIdentifier ) {
354+ toolCalls . push ( {
355+ type : 'image_generation' ,
356+ result : fileIdentifier , // 文件名或S3 URL,前端会自动处理
357+ } )
358+ }
359+ else {
360+ toolCalls . push ( {
361+ type : 'image_generation' ,
362+ result : base64Data ,
363+ } )
364+ }
351365 }
352366 }
353367 }
354368 }
355369 }
356370 }
371+ finally {
372+ // 清除心跳定时器
373+ if ( heartbeatInterval ) {
374+ clearInterval ( heartbeatInterval )
375+ heartbeatInterval = null
376+ }
377+ }
357378
358379 const response = {
359380 id : responseId || messageId ,
0 commit comments