@@ -119,74 +119,145 @@ export async function POST(req: NextRequest) {
119119 `[${ requestId } ] About to create stream with model: ${ useWandAzure ? wandModelName : 'gpt-4o' } `
120120 )
121121
122- // Create the stream without AbortController for Node.js runtime compatibility
123- const streamCompletion = await client . chat . completions . create ( {
124- model : useWandAzure ? wandModelName : 'gpt-4o' ,
125- messages : messages ,
126- temperature : 0.3 ,
127- max_tokens : 10000 ,
128- stream : true ,
129- stream_options : { include_usage : true } ,
122+ // Use native fetch for streaming to avoid OpenAI SDK issues with Node.js runtime
123+ const apiUrl = useWandAzure
124+ ? `${ azureEndpoint } /openai/deployments/${ wandModelName } /chat/completions?api-version=${ azureApiVersion } `
125+ : 'https://api.openai.com/v1/chat/completions'
126+
127+ const headers : Record < string , string > = {
128+ 'Content-Type' : 'application/json' ,
129+ }
130+
131+ if ( useWandAzure ) {
132+ headers [ 'api-key' ] = azureApiKey !
133+ } else {
134+ headers . Authorization = `Bearer ${ openaiApiKey } `
135+ }
136+
137+ logger . debug ( `[${ requestId } ] Making streaming request to: ${ apiUrl } ` )
138+
139+ const response = await fetch ( apiUrl , {
140+ method : 'POST' ,
141+ headers,
142+ body : JSON . stringify ( {
143+ model : useWandAzure ? wandModelName : 'gpt-4o' ,
144+ messages : messages ,
145+ temperature : 0.3 ,
146+ max_tokens : 10000 ,
147+ stream : true ,
148+ stream_options : { include_usage : true } ,
149+ } ) ,
130150 } )
131151
132- logger . info ( `[${ requestId } ] Stream created successfully, starting response` )
152+ if ( ! response . ok ) {
153+ const errorText = await response . text ( )
154+ logger . error ( `[${ requestId } ] API request failed` , {
155+ status : response . status ,
156+ statusText : response . statusText ,
157+ error : errorText ,
158+ } )
159+ throw new Error ( `API request failed: ${ response . status } ${ response . statusText } ` )
160+ }
161+
162+ logger . info ( `[${ requestId } ] Stream response received, starting processing` )
133163
134- // Create a TransformStream for Node.js runtime compatibility
164+ // Create a TransformStream to process the SSE data
135165 const encoder = new TextEncoder ( )
166+ const decoder = new TextDecoder ( )
167+
136168 const readable = new ReadableStream ( {
137169 async start ( controller ) {
170+ const reader = response . body ?. getReader ( )
171+ if ( ! reader ) {
172+ controller . close ( )
173+ return
174+ }
175+
138176 try {
139- logger . info ( `[ ${ requestId } ] Starting stream processing` )
177+ let buffer = ''
140178 let chunkCount = 0
141179
142- for await ( const chunk of streamCompletion ) {
143- chunkCount ++
180+ while ( true ) {
181+ const { done , value } = await reader . read ( )
144182
145- if ( chunkCount === 1 ) {
146- logger . info ( `[${ requestId } ] Received first chunk` )
183+ if ( done ) {
184+ logger . info ( `[${ requestId } ] Stream completed. Total chunks: ${ chunkCount } ` )
185+ controller . enqueue ( encoder . encode ( `data: ${ JSON . stringify ( { done : true } ) } \n\n` ) )
186+ controller . close ( )
187+ break
147188 }
148189
149- // Process the chunk
150- const content = chunk . choices ?. [ 0 ] ?. delta ?. content || ''
151- if ( content ) {
152- // Send data in SSE format
153- const data = `data: ${ JSON . stringify ( { chunk : content } ) } \n\n`
154- controller . enqueue ( encoder . encode ( data ) )
155- }
156-
157- // Check for usage data
158- if ( chunk . usage ) {
159- logger . info ( `[${ requestId } ] Received usage data: ${ JSON . stringify ( chunk . usage ) } ` )
160- }
161-
162- // Log progress periodically
163- if ( chunkCount % 10 === 0 ) {
164- logger . debug ( `[${ requestId } ] Processed ${ chunkCount } chunks` )
190+ // Decode the chunk
191+ buffer += decoder . decode ( value , { stream : true } )
192+
193+ // Process complete SSE messages
194+ const lines = buffer . split ( '\n' )
195+ buffer = lines . pop ( ) || '' // Keep incomplete line in buffer
196+
197+ for ( const line of lines ) {
198+ if ( line . startsWith ( 'data: ' ) ) {
199+ const data = line . slice ( 6 ) . trim ( )
200+
201+ if ( data === '[DONE]' ) {
202+ logger . info ( `[${ requestId } ] Received [DONE] signal` )
203+ controller . enqueue (
204+ encoder . encode ( `data: ${ JSON . stringify ( { done : true } ) } \n\n` )
205+ )
206+ controller . close ( )
207+ return
208+ }
209+
210+ try {
211+ const parsed = JSON . parse ( data )
212+ const content = parsed . choices ?. [ 0 ] ?. delta ?. content
213+
214+ if ( content ) {
215+ chunkCount ++
216+ if ( chunkCount === 1 ) {
217+ logger . info ( `[${ requestId } ] Received first content chunk` )
218+ }
219+
220+ // Forward the content
221+ controller . enqueue (
222+ encoder . encode ( `data: ${ JSON . stringify ( { chunk : content } ) } \n\n` )
223+ )
224+ }
225+
226+ // Log usage if present
227+ if ( parsed . usage ) {
228+ logger . info (
229+ `[${ requestId } ] Received usage data: ${ JSON . stringify ( parsed . usage ) } `
230+ )
231+ }
232+
233+ // Log progress periodically
234+ if ( chunkCount % 10 === 0 ) {
235+ logger . debug ( `[${ requestId } ] Processed ${ chunkCount } chunks` )
236+ }
237+ } catch ( parseError ) {
238+ // Skip invalid JSON lines
239+ logger . debug (
240+ `[${ requestId } ] Skipped non-JSON line: ${ data . substring ( 0 , 100 ) } `
241+ )
242+ }
243+ }
165244 }
166245 }
167246
168- logger . info ( `[${ requestId } ] Stream completed. Total chunks: ${ chunkCount } ` )
169-
170- // Send completion signal
171- controller . enqueue ( encoder . encode ( `data: ${ JSON . stringify ( { done : true } ) } \n\n` ) )
172- controller . close ( )
173-
174247 logger . info ( `[${ requestId } ] Wand generation streaming completed successfully` )
175248 } catch ( streamError : any ) {
176249 logger . error ( `[${ requestId } ] Streaming error` , {
177250 name : streamError ?. name ,
178251 message : streamError ?. message || 'Unknown error' ,
179- code : streamError ?. code ,
180- status : streamError ?. status ,
181252 stack : streamError ?. stack ,
182- useWandAzure,
183- model : useWandAzure ? wandModelName : 'gpt-4o' ,
184253 } )
185254
186255 // Send error to client
187256 const errorData = `data: ${ JSON . stringify ( { error : 'Streaming failed' , done : true } ) } \n\n`
188257 controller . enqueue ( encoder . encode ( errorData ) )
189258 controller . close ( )
259+ } finally {
260+ reader . releaseLock ( )
190261 }
191262 } ,
192263 } )
0 commit comments