@@ -3,6 +3,7 @@ import { userStats, workflow } from '@sim/db/schema'
33import { eq , sql } from 'drizzle-orm'
44import { type NextRequest , NextResponse } from 'next/server'
55import OpenAI , { AzureOpenAI } from 'openai'
6+ import { getBYOKKey } from '@/lib/api-key/byok'
67import { getSession } from '@/lib/auth'
78import { logModelUsage } from '@/lib/billing/core/usage-log'
89import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
@@ -75,7 +76,8 @@ async function updateUserStatsForWand(
7576 completion_tokens ?: number
7677 total_tokens ?: number
7778 } ,
78- requestId : string
79+ requestId : string ,
80+ isBYOK = false
7981) : Promise < void > {
8082 if ( ! isBillingEnabled ) {
8183 logger . debug ( `[${ requestId } ] Billing is disabled, skipping wand usage cost update` )
@@ -93,21 +95,24 @@ async function updateUserStatsForWand(
9395 const completionTokens = usage . completion_tokens || 0
9496
9597 const modelName = useWandAzure ? wandModelName : 'gpt-4o'
96- const pricing = getModelPricing ( modelName )
97-
98- const costMultiplier = getCostMultiplier ( )
99- let modelCost = 0
98+ let costToStore = 0
99+
100+ if ( ! isBYOK ) {
101+ const pricing = getModelPricing ( modelName )
102+ const costMultiplier = getCostMultiplier ( )
103+ let modelCost = 0
104+
105+ if ( pricing ) {
106+ const inputCost = ( promptTokens / 1000000 ) * pricing . input
107+ const outputCost = ( completionTokens / 1000000 ) * pricing . output
108+ modelCost = inputCost + outputCost
109+ } else {
110+ modelCost = ( promptTokens / 1000000 ) * 0.005 + ( completionTokens / 1000000 ) * 0.015
111+ }
100112
101- if ( pricing ) {
102- const inputCost = ( promptTokens / 1000000 ) * pricing . input
103- const outputCost = ( completionTokens / 1000000 ) * pricing . output
104- modelCost = inputCost + outputCost
105- } else {
106- modelCost = ( promptTokens / 1000000 ) * 0.005 + ( completionTokens / 1000000 ) * 0.015
113+ costToStore = modelCost * costMultiplier
107114 }
108115
109- const costToStore = modelCost * costMultiplier
110-
111116 await db
112117 . update ( userStats )
113118 . set ( {
@@ -122,6 +127,7 @@ async function updateUserStatsForWand(
122127 userId,
123128 tokensUsed : totalTokens ,
124129 costAdded : costToStore ,
130+ isBYOK,
125131 } )
126132
127133 await logModelUsage ( {
@@ -149,14 +155,6 @@ export async function POST(req: NextRequest) {
149155 return NextResponse . json ( { success : false , error : 'Unauthorized' } , { status : 401 } )
150156 }
151157
152- if ( ! client ) {
153- logger . error ( `[${ requestId } ] AI client not initialized. Missing API key.` )
154- return NextResponse . json (
155- { success : false , error : 'Wand generation service is not configured.' } ,
156- { status : 503 }
157- )
158- }
159-
160158 try {
161159 const body = ( await req . json ( ) ) as RequestBody
162160
@@ -170,6 +168,7 @@ export async function POST(req: NextRequest) {
170168 )
171169 }
172170
171+ let workspaceId : string | null = null
173172 if ( workflowId ) {
174173 const [ workflowRecord ] = await db
175174 . select ( { workspaceId : workflow . workspaceId , userId : workflow . userId } )
@@ -182,6 +181,8 @@ export async function POST(req: NextRequest) {
182181 return NextResponse . json ( { success : false , error : 'Workflow not found' } , { status : 404 } )
183182 }
184183
184+ workspaceId = workflowRecord . workspaceId
185+
185186 if ( workflowRecord . workspaceId ) {
186187 const permission = await verifyWorkspaceMembership (
187188 session . user . id ,
@@ -199,6 +200,28 @@ export async function POST(req: NextRequest) {
199200 }
200201 }
201202
203+ let isBYOK = false
204+ let activeClient = client
205+ let byokApiKey : string | null = null
206+
207+ if ( workspaceId && ! useWandAzure ) {
208+ const byokResult = await getBYOKKey ( workspaceId , 'openai' )
209+ if ( byokResult ) {
210+ isBYOK = true
211+ byokApiKey = byokResult . apiKey
212+ activeClient = new OpenAI ( { apiKey : byokResult . apiKey } )
213+ logger . info ( `[${ requestId } ] Using BYOK OpenAI key for wand generation` )
214+ }
215+ }
216+
217+ if ( ! activeClient ) {
218+ logger . error ( `[${ requestId } ] AI client not initialized. Missing API key.` )
219+ return NextResponse . json (
220+ { success : false , error : 'Wand generation service is not configured.' } ,
221+ { status : 503 }
222+ )
223+ }
224+
202225 const finalSystemPrompt =
203226 systemPrompt ||
204227 'You are a helpful AI assistant. Generate content exactly as requested by the user.'
@@ -241,7 +264,7 @@ export async function POST(req: NextRequest) {
241264 if ( useWandAzure ) {
242265 headers [ 'api-key' ] = azureApiKey !
243266 } else {
244- headers . Authorization = `Bearer ${ openaiApiKey } `
267+ headers . Authorization = `Bearer ${ byokApiKey || openaiApiKey } `
245268 }
246269
247270 logger . debug ( `[${ requestId } ] Making streaming request to: ${ apiUrl } ` )
@@ -310,7 +333,7 @@ export async function POST(req: NextRequest) {
310333 logger . info ( `[${ requestId } ] Received [DONE] signal` )
311334
312335 if ( finalUsage ) {
313- await updateUserStatsForWand ( session . user . id , finalUsage , requestId )
336+ await updateUserStatsForWand ( session . user . id , finalUsage , requestId , isBYOK )
314337 }
315338
316339 controller . enqueue (
@@ -395,7 +418,7 @@ export async function POST(req: NextRequest) {
395418 }
396419 }
397420
398- const completion = await client . chat . completions . create ( {
421+ const completion = await activeClient . chat . completions . create ( {
399422 model : useWandAzure ? wandModelName : 'gpt-4o' ,
400423 messages : messages ,
401424 temperature : 0.3 ,
@@ -417,7 +440,7 @@ export async function POST(req: NextRequest) {
417440 logger . info ( `[${ requestId } ] Wand generation successful` )
418441
419442 if ( completion . usage ) {
420- await updateUserStatsForWand ( session . user . id , completion . usage , requestId )
443+ await updateUserStatsForWand ( session . user . id , completion . usage , requestId , isBYOK )
421444 }
422445
423446 return NextResponse . json ( { success : true , content : generatedContent } )
0 commit comments