@@ -3,7 +3,6 @@ import { userStats, workflow } from '@sim/db/schema'
33import { createLogger } from '@sim/logger'
44import { eq , sql } from 'drizzle-orm'
55import { type NextRequest , NextResponse } from 'next/server'
6- import OpenAI , { AzureOpenAI } from 'openai'
76import { getBYOKKey } from '@/lib/api-key/byok'
87import { getSession } from '@/lib/auth'
98import { logModelUsage } from '@/lib/billing/core/usage-log'
@@ -12,6 +11,7 @@ import { env } from '@/lib/core/config/env'
1211import { getCostMultiplier , isBillingEnabled } from '@/lib/core/config/feature-flags'
1312import { generateRequestId } from '@/lib/core/utils/request'
1413import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
14+ import { extractResponseText , parseResponsesUsage } from '@/providers/responses-utils'
1515import { getModelPricing } from '@/providers/utils'
1616
1717export const dynamic = 'force-dynamic'
@@ -28,18 +28,6 @@ const openaiApiKey = env.OPENAI_API_KEY
2828
2929const useWandAzure = azureApiKey && azureEndpoint && azureApiVersion
3030
31- const client = useWandAzure
32- ? new AzureOpenAI ( {
33- apiKey : azureApiKey ,
34- apiVersion : azureApiVersion ,
35- endpoint : azureEndpoint ,
36- } )
37- : openaiApiKey
38- ? new OpenAI ( {
39- apiKey : openaiApiKey ,
40- } )
41- : null
42-
4331if ( ! useWandAzure && ! openaiApiKey ) {
4432 logger . warn (
4533 'Neither Azure OpenAI nor OpenAI API key found. Wand generation API will not function.'
@@ -202,20 +190,18 @@ export async function POST(req: NextRequest) {
202190 }
203191
204192 let isBYOK = false
205- let activeClient = client
206- let byokApiKey : string | null = null
193+ let activeOpenAIKey = openaiApiKey
207194
208195 if ( workspaceId && ! useWandAzure ) {
209196 const byokResult = await getBYOKKey ( workspaceId , 'openai' )
210197 if ( byokResult ) {
211198 isBYOK = true
212- byokApiKey = byokResult . apiKey
213- activeClient = new OpenAI ( { apiKey : byokResult . apiKey } )
199+ activeOpenAIKey = byokResult . apiKey
214200 logger . info ( `[${ requestId } ] Using BYOK OpenAI key for wand generation` )
215201 }
216202 }
217203
218- if ( ! activeClient ) {
204+ if ( ! useWandAzure && ! activeOpenAIKey ) {
219205 logger . error ( `[${ requestId } ] AI client not initialized. Missing API key.` )
220206 return NextResponse . json (
221207 { success : false , error : 'Wand generation service is not configured.' } ,
@@ -276,17 +262,18 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
276262 )
277263
278264 const apiUrl = useWandAzure
279- ? `${ azureEndpoint } /openai/deployments/ ${ wandModelName } /chat/completions ?api-version=${ azureApiVersion } `
280- : 'https://api.openai.com/v1/chat/completions '
265+ ? `${ azureEndpoint } /openai/v1/responses ?api-version=${ azureApiVersion } `
266+ : 'https://api.openai.com/v1/responses '
281267
282268 const headers : Record < string , string > = {
283269 'Content-Type' : 'application/json' ,
270+ 'OpenAI-Beta' : 'responses=v1' ,
284271 }
285272
286273 if ( useWandAzure ) {
287274 headers [ 'api-key' ] = azureApiKey !
288275 } else {
289- headers . Authorization = `Bearer ${ byokApiKey || openaiApiKey } `
276+ headers . Authorization = `Bearer ${ activeOpenAIKey } `
290277 }
291278
292279 logger . debug ( `[${ requestId } ] Making streaming request to: ${ apiUrl } ` )
@@ -296,11 +283,10 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
296283 headers,
297284 body : JSON . stringify ( {
298285 model : useWandAzure ? wandModelName : 'gpt-4o' ,
299- messages : messages ,
286+ input : messages ,
300287 temperature : 0.2 ,
301- max_tokens : 10000 ,
288+ max_output_tokens : 10000 ,
302289 stream : true ,
303- stream_options : { include_usage : true } ,
304290 } ) ,
305291 } )
306292
@@ -331,6 +317,7 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
331317 let buffer = ''
332318 let chunkCount = 0
333319 let finalUsage : any = null
320+ let activeEventType : string | undefined
334321
335322 while ( true ) {
336323 const { done, value } = await reader . read ( )
@@ -348,27 +335,49 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
348335 buffer = lines . pop ( ) || ''
349336
350337 for ( const line of lines ) {
351- if ( line . startsWith ( 'data: ' ) ) {
352- const data = line . slice ( 6 ) . trim ( )
338+ const trimmed = line . trim ( )
339+ if ( ! trimmed ) {
340+ continue
341+ }
353342
354- if ( data === '[DONE]' ) {
355- logger . info ( `[${ requestId } ] Received [DONE] signal` )
343+ if ( trimmed . startsWith ( 'event:' ) ) {
344+ activeEventType = trimmed . slice ( 6 ) . trim ( )
345+ continue
346+ }
356347
357- if ( finalUsage ) {
358- await updateUserStatsForWand ( session . user . id , finalUsage , requestId , isBYOK )
359- }
348+ if ( ! trimmed . startsWith ( 'data:' ) ) {
349+ continue
350+ }
351+
352+ const data = trimmed . slice ( 5 ) . trim ( )
353+ if ( data === '[DONE]' ) {
354+ logger . info ( `[${ requestId } ] Received [DONE] signal` )
360355
361- controller . enqueue (
362- encoder . encode ( `data: ${ JSON . stringify ( { done : true } ) } \n\n` )
363- )
364- controller . close ( )
365- return
356+ if ( finalUsage ) {
357+ await updateUserStatsForWand ( session . user . id , finalUsage , requestId , isBYOK )
366358 }
367359
368- try {
369- const parsed = JSON . parse ( data )
370- const content = parsed . choices ?. [ 0 ] ?. delta ?. content
360+ controller . enqueue (
361+ encoder . encode ( `data: ${ JSON . stringify ( { done : true } ) } \n\n` )
362+ )
363+ controller . close ( )
364+ return
365+ }
366+
367+ try {
368+ const parsed = JSON . parse ( data )
369+ const eventType = parsed ?. type ?? activeEventType
370+
371+ if (
372+ eventType === 'response.error' ||
373+ eventType === 'error' ||
374+ activeEventType === 'response.failed'
375+ ) {
376+ throw new Error ( parsed ?. error ?. message || 'Responses stream error' )
377+ }
371378
379+ if ( eventType === 'response.output_text.delta' ) {
380+ const content = parsed . delta
372381 if ( content ) {
373382 chunkCount ++
374383 if ( chunkCount === 1 ) {
@@ -379,18 +388,23 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
379388 encoder . encode ( `data: ${ JSON . stringify ( { chunk : content } ) } \n\n` )
380389 )
381390 }
391+ }
382392
383- if ( parsed . usage ) {
384- finalUsage = parsed . usage
393+ if ( eventType === 'response.completed' ) {
394+ const usage = parseResponsesUsage ( parsed ?. response ?. usage ?? parsed ?. usage )
395+ if ( usage ) {
396+ finalUsage = {
397+ prompt_tokens : usage . promptTokens ,
398+ completion_tokens : usage . completionTokens ,
399+ total_tokens : usage . totalTokens ,
400+ }
385401 logger . info (
386- `[${ requestId } ] Received usage data: ${ JSON . stringify ( parsed . usage ) } `
402+ `[${ requestId } ] Received usage data: ${ JSON . stringify ( finalUsage ) } `
387403 )
388404 }
389- } catch ( parseError ) {
390- logger . debug (
391- `[${ requestId } ] Skipped non-JSON line: ${ data . substring ( 0 , 100 ) } `
392- )
393405 }
406+ } catch ( parseError ) {
407+ logger . debug ( `[${ requestId } ] Skipped non-JSON line: ${ data . substring ( 0 , 100 ) } ` )
394408 }
395409 }
396410 }
@@ -424,8 +438,6 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
424438 message : error ?. message || 'Unknown error' ,
425439 code : error ?. code ,
426440 status : error ?. status ,
427- responseStatus : error ?. response ?. status ,
428- responseData : error ?. response ?. data ? safeStringify ( error . response . data ) : undefined ,
429441 stack : error ?. stack ,
430442 useWandAzure,
431443 model : useWandAzure ? wandModelName : 'gpt-4o' ,
@@ -440,14 +452,43 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
440452 }
441453 }
442454
443- const completion = await activeClient . chat . completions . create ( {
444- model : useWandAzure ? wandModelName : 'gpt-4o' ,
445- messages : messages ,
446- temperature : 0.3 ,
447- max_tokens : 10000 ,
455+ const apiUrl = useWandAzure
456+ ? `${ azureEndpoint } /openai/v1/responses?api-version=${ azureApiVersion } `
457+ : 'https://api.openai.com/v1/responses'
458+
459+ const headers : Record < string , string > = {
460+ 'Content-Type' : 'application/json' ,
461+ 'OpenAI-Beta' : 'responses=v1' ,
462+ }
463+
464+ if ( useWandAzure ) {
465+ headers [ 'api-key' ] = azureApiKey !
466+ } else {
467+ headers . Authorization = `Bearer ${ activeOpenAIKey } `
468+ }
469+
470+ const response = await fetch ( apiUrl , {
471+ method : 'POST' ,
472+ headers,
473+ body : JSON . stringify ( {
474+ model : useWandAzure ? wandModelName : 'gpt-4o' ,
475+ input : messages ,
476+ temperature : 0.3 ,
477+ max_output_tokens : 10000 ,
478+ } ) ,
448479 } )
449480
450- const generatedContent = completion . choices [ 0 ] ?. message ?. content ?. trim ( )
481+ if ( ! response . ok ) {
482+ const errorText = await response . text ( )
483+ const apiError = new Error (
484+ `API request failed: ${ response . status } ${ response . statusText } - ${ errorText } `
485+ )
486+ ; ( apiError as any ) . status = response . status
487+ throw apiError
488+ }
489+
490+ const completion = await response . json ( )
491+ const generatedContent = extractResponseText ( completion . output ) ?. trim ( )
451492
452493 if ( ! generatedContent ) {
453494 logger . error (
@@ -461,8 +502,18 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
461502
462503 logger . info ( `[${ requestId } ] Wand generation successful` )
463504
464- if ( completion . usage ) {
465- await updateUserStatsForWand ( session . user . id , completion . usage , requestId , isBYOK )
505+ const usage = parseResponsesUsage ( completion . usage )
506+ if ( usage ) {
507+ await updateUserStatsForWand (
508+ session . user . id ,
509+ {
510+ prompt_tokens : usage . promptTokens ,
511+ completion_tokens : usage . completionTokens ,
512+ total_tokens : usage . totalTokens ,
513+ } ,
514+ requestId ,
515+ isBYOK
516+ )
466517 }
467518
468519 return NextResponse . json ( { success : true , content : generatedContent } )
@@ -472,10 +523,6 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
472523 message : error ?. message || 'Unknown error' ,
473524 code : error ?. code ,
474525 status : error ?. status ,
475- responseStatus : error instanceof OpenAI . APIError ? error . status : error ?. response ?. status ,
476- responseData : ( error as any ) ?. response ?. data
477- ? safeStringify ( ( error as any ) . response . data )
478- : undefined ,
479526 stack : error ?. stack ,
480527 useWandAzure,
481528 model : useWandAzure ? wandModelName : 'gpt-4o' ,
@@ -484,26 +531,19 @@ Use this context to calculate relative dates like "yesterday", "last week", "beg
484531 } )
485532
486533 let clientErrorMessage = 'Wand generation failed. Please try again later.'
487- let status = 500
534+ let status = typeof ( error as any ) ?. status === 'number' ? ( error as any ) . status : 500
488535
489- if ( error instanceof OpenAI . APIError ) {
490- status = error . status || 500
491- logger . error (
492- `[${ requestId } ] ${ useWandAzure ? 'Azure OpenAI' : 'OpenAI' } API Error: ${ status } - ${ error . message } `
493- )
494-
495- if ( status === 401 ) {
496- clientErrorMessage = 'Authentication failed. Please check your API key configuration.'
497- } else if ( status === 429 ) {
498- clientErrorMessage = 'Rate limit exceeded. Please try again later.'
499- } else if ( status >= 500 ) {
500- clientErrorMessage =
501- 'The wand generation service is currently unavailable. Please try again later.'
502- }
503- } else if ( useWandAzure && error . message ?. includes ( 'DeploymentNotFound' ) ) {
536+ if ( useWandAzure && error ?. message ?. includes ( 'DeploymentNotFound' ) ) {
504537 clientErrorMessage =
505538 'Azure OpenAI deployment not found. Please check your model deployment configuration.'
506539 status = 404
540+ } else if ( status === 401 ) {
541+ clientErrorMessage = 'Authentication failed. Please check your API key configuration.'
542+ } else if ( status === 429 ) {
543+ clientErrorMessage = 'Rate limit exceeded. Please try again later.'
544+ } else if ( status >= 500 ) {
545+ clientErrorMessage =
546+ 'The wand generation service is currently unavailable. Please try again later.'
507547 }
508548
509549 return NextResponse . json (
0 commit comments