@@ -47,6 +47,7 @@ import { i18n, type Key } from "~/i18n"
4747import { localeFromRequest } from "~/lib/language"
4848import { createModelTpmLimiter } from "./modelTpmLimiter"
4949import { createModelTpsLimiter } from "./modelTpsLimiter"
50+ import { createProviderBudgetTracker } from "./providerBudgetTracker"
5051import { accumulateUsage , HOT_WORKSPACES } from "./usageBatcher"
5152
5253type ZenData = Awaited < ReturnType < typeof ZenData . list > >
@@ -132,6 +133,10 @@ export async function handler(
132133 const modelTpmLimits = await modelTpmLimiter ?. check ( )
133134 const modelTpsLimiter = createModelTpsLimiter ( modelInfo . providers )
134135 const modelTpsLimits = await modelTpsLimiter ?. check ( )
136+ const providerBudgetTracker = createProviderBudgetTracker (
137+ modelInfo . providers . map ( ( provider ) => ( { ...zenData . providers [ provider . id ] , ...provider } ) ) ,
138+ )
139+ const providerBudgetUsage = await providerBudgetTracker ?. check ( )
135140
136141 const retriableRequest = async ( retry : RetryOptions = { excludeProviders : [ ] , retryCount : 0 } ) => {
137142 const providerInfo = selectProvider (
@@ -145,12 +150,16 @@ export async function handler(
145150 stickyProvider ,
146151 modelTpmLimits ,
147152 modelTpsLimits ,
153+ providerBudgetUsage ,
148154 )
149155 validateModelSettings ( billingSource , authInfo )
150156 updateProviderKey ( authInfo , providerInfo )
151157 logger . metric ( {
152158 provider : providerInfo . id ,
153159 "provider.model" : providerInfo . model ,
160+ ...( providerBudgetUsage ?. [ providerInfo . id ]
161+ ? { "provider.budget_usage" : providerBudgetUsage ?. [ providerInfo . id ] }
162+ : { } ) ,
154163 } )
155164
156165 const startTimestamp = Date . now ( )
@@ -257,6 +266,7 @@ export async function handler(
257266 const costInfo = calculateCost ( modelInfo , usageInfo )
258267 await trialLimiter ?. track ( usageInfo )
259268 await modelTpmLimiter ?. track ( providerInfo . id , providerInfo . model , usageInfo )
269+ await providerBudgetTracker ?. track ( providerInfo . id , costInfo . totalCostInCent )
260270 await trackUsage ( sessionId , billingSource , authInfo , modelInfo , providerInfo , usageInfo , costInfo )
261271 await reload ( billingSource , authInfo , costInfo )
262272 json . cost = calculateOccurredCost ( billingSource , costInfo )
@@ -317,6 +327,7 @@ export async function handler(
317327 timestampLastByte ,
318328 usageInfo ,
319329 )
330+ await providerBudgetTracker ?. track ( providerInfo . id , costInfo . totalCostInCent )
320331 await trackUsage ( sessionId , billingSource , authInfo , modelInfo , providerInfo , usageInfo , costInfo )
321332 await reload ( billingSource , authInfo , costInfo )
322333 const cost = calculateOccurredCost ( billingSource , costInfo )
@@ -483,6 +494,7 @@ export async function handler(
483494 stickyProviderId : string | undefined ,
484495 modelTpmLimits : Record < string , number > | undefined ,
485496 modelTpsLimits : Record < string , { qualify : number ; unqualify : number } > | undefined ,
497+ providerBudgetUsage : Record < string , number > | undefined ,
486498 ) {
487499 const modelProvider = ( ( ) => {
488500 // Byok is top priority b/c if user set their own API key, we should use it
@@ -505,6 +517,12 @@ export async function handler(
505517 const providers = allProviders
506518 . filter ( ( provider ) => provider . weight !== 0 )
507519 . filter ( ( provider ) => ! retry . excludeProviders . includes ( provider . id ) )
520+ . filter ( ( provider ) => {
521+ if ( provider . budgetMode !== "fill" ) return true
522+ const budget = zenData . providers [ provider . id ] ?. budget
523+ if ( budget === undefined ) return false
524+ return ( providerBudgetUsage ?. [ provider . id ] ?? 0 ) < centsToMicroCents ( budget * 100 )
525+ } )
508526 . filter ( ( provider ) => {
509527 if ( ! provider . tpmLimit ) return true
510528 const usage = modelTpmLimits ?. [ `${ provider . id } /${ provider . model } ` ] ?? 0
0 commit comments