11const crypto = require ( 'crypto' ) ;
22const { normalizeError, createHttpError } = require ( './errors' ) ;
33const { normalizeUsage, sanitizeAssistantContent } = require ( './helpers' ) ;
4+ const { hasFunctionCallOpenTag, stripXmlToolCalls } = require ( './parse-xml-tool-calls' ) ;
45
56function writeSseEvent ( response , payload ) {
67 response . write ( `data: ${ payload } \n\n` ) ;
@@ -36,6 +37,8 @@ function beginOpenAiStream(response, model, options = {}) {
3637 usage : undefined ,
3738 sawContent : false ,
3839 accumulatedContent : '' ,
40+ contentBuffer : '' ,
41+ inToolCallMode : false ,
3942 onComplete : typeof options . onComplete === 'function' ? options . onComplete : null ,
4043 toolCallDetector : typeof options . toolCallDetector === 'function' ? options . toolCallDetector : null
4144 } ;
@@ -54,12 +57,31 @@ function normalizeStreamDelta(event) {
5457function emitContentDelta ( response , streamState , delta ) {
5558 if ( ! delta ) return ;
5659
57- streamState . sawContent = true ;
5860 streamState . accumulatedContent += delta ;
61+ streamState . contentBuffer += delta ;
5962
60- writeSseEvent ( response , JSON . stringify (
61- buildChunk ( streamState . id , streamState . created , streamState . model , { content : delta } , null )
62- ) ) ;
63+ if ( ! streamState . inToolCallMode && hasFunctionCallOpenTag ( streamState . accumulatedContent ) ) {
64+ streamState . inToolCallMode = true ;
65+ streamState . contentBuffer = '' ;
66+ return ;
67+ }
68+
69+ if ( streamState . inToolCallMode ) {
70+ streamState . contentBuffer = '' ;
71+ return ;
72+ }
73+
74+ const shouldFlush = streamState . contentBuffer . length >= 40
75+ || / \n / . test ( streamState . contentBuffer )
76+ || ( streamState . contentBuffer . length > 5 && / [ \. \! \? \。 \! \? ] \s * $ / . test ( streamState . contentBuffer ) ) ;
77+
78+ if ( shouldFlush && streamState . contentBuffer ) {
79+ streamState . sawContent = true ;
80+ writeSseEvent ( response , JSON . stringify (
81+ buildChunk ( streamState . id , streamState . created , streamState . model , { content : streamState . contentBuffer } , null )
82+ ) ) ;
83+ streamState . contentBuffer = '' ;
84+ }
6385}
6486
6587function writeOpenAiContentChunk ( response , streamState , event ) {
@@ -72,6 +94,14 @@ function writeOpenAiContentChunk(response, streamState, event) {
7294}
7395
7496async function finishOpenAiStream ( response , streamState ) {
97+ if ( streamState . contentBuffer && ! streamState . inToolCallMode ) {
98+ streamState . sawContent = true ;
99+ writeSseEvent ( response , JSON . stringify (
100+ buildChunk ( streamState . id , streamState . created , streamState . model , { content : streamState . contentBuffer } , null )
101+ ) ) ;
102+ streamState . contentBuffer = '' ;
103+ }
104+
75105 const toolCalls = streamState . toolCallDetector
76106 ? streamState . toolCallDetector ( streamState . accumulatedContent )
77107 : null ;
0 commit comments