@@ -84,6 +84,7 @@ export async function handler(
8484 const stickyTracker = createStickyTracker ( modelInfo . stickyProvider , sessionId )
8585 const stickyProvider = await stickyTracker ?. get ( )
8686 const authInfo = await authenticate ( modelInfo )
87+ const billingSource = validateBilling ( authInfo , modelInfo )
8788
8889 const retriableRequest = async ( retry : RetryOptions = { excludeProviders : [ ] , retryCount : 0 } ) => {
8990 const providerInfo = selectProvider (
@@ -96,7 +97,6 @@ export async function handler(
9697 retry ,
9798 stickyProvider ,
9899 )
99- validateBilling ( authInfo , modelInfo )
100100 validateModelSettings ( authInfo )
101101 updateProviderKey ( authInfo , providerInfo )
102102 logger . metric ( { provider : providerInfo . id } )
@@ -183,7 +183,7 @@ export async function handler(
183183 const tokensInfo = providerInfo . normalizeUsage ( json . usage )
184184 await trialLimiter ?. track ( tokensInfo )
185185 await rateLimiter ?. track ( )
186- const costInfo = await trackUsage ( authInfo , modelInfo , providerInfo , tokensInfo )
186+ const costInfo = await trackUsage ( authInfo , modelInfo , providerInfo , billingSource , tokensInfo )
187187 await reload ( authInfo , costInfo )
188188 return new Response ( body , {
189189 status : resStatus ,
@@ -219,7 +219,7 @@ export async function handler(
219219 if ( usage ) {
220220 const tokensInfo = providerInfo . normalizeUsage ( usage )
221221 await trialLimiter ?. track ( tokensInfo )
222- const costInfo = await trackUsage ( authInfo , modelInfo , providerInfo , tokensInfo )
222+ const costInfo = await trackUsage ( authInfo , modelInfo , providerInfo , billingSource , tokensInfo )
223223 await reload ( authInfo , costInfo )
224224 }
225225 c . close ( )
@@ -484,54 +484,58 @@ export async function handler(
484484 }
485485
486486 function validateBilling ( authInfo : AuthInfo , modelInfo : ModelInfo ) {
487- if ( ! authInfo ) return
488- if ( authInfo . provider ?. credentials ) return
489- if ( authInfo . isFree ) return
490- if ( modelInfo . allowAnonymous ) return
487+ if ( ! authInfo ) return "anonymous"
488+ if ( authInfo . provider ?. credentials ) return "free"
489+ if ( authInfo . isFree ) return "free"
490+ if ( modelInfo . allowAnonymous ) return "free"
491491
492492 // Validate subscription billing
493493 if ( authInfo . billing . subscription && authInfo . subscription ) {
494- const sub = authInfo . subscription
495- const plan = authInfo . billing . subscription . plan
496-
497- const formatRetryTime = ( seconds : number ) => {
498- const days = Math . floor ( seconds / 86400 )
499- if ( days >= 1 ) return `${ days } day${ days > 1 ? "s" : "" } `
500- const hours = Math . floor ( seconds / 3600 )
501- const minutes = Math . ceil ( ( seconds % 3600 ) / 60 )
502- if ( hours >= 1 ) return `${ hours } hr ${ minutes } min`
503- return `${ minutes } min`
504- }
494+ try {
495+ const sub = authInfo . subscription
496+ const plan = authInfo . billing . subscription . plan
497+
498+ const formatRetryTime = ( seconds : number ) => {
499+ const days = Math . floor ( seconds / 86400 )
500+ if ( days >= 1 ) return `${ days } day${ days > 1 ? "s" : "" } `
501+ const hours = Math . floor ( seconds / 3600 )
502+ const minutes = Math . ceil ( ( seconds % 3600 ) / 60 )
503+ if ( hours >= 1 ) return `${ hours } hr ${ minutes } min`
504+ return `${ minutes } min`
505+ }
505506
506- // Check weekly limit
507- if ( sub . fixedUsage && sub . timeFixedUpdated ) {
508- const result = Black . analyzeWeeklyUsage ( {
509- plan,
510- usage : sub . fixedUsage ,
511- timeUpdated : sub . timeFixedUpdated ,
512- } )
513- if ( result . status === "rate-limited" )
514- throw new SubscriptionError (
515- `Subscription quota exceeded. Retry in ${ formatRetryTime ( result . resetInSec ) } .` ,
516- result . resetInSec ,
517- )
518- }
507+ // Check weekly limit
508+ if ( sub . fixedUsage && sub . timeFixedUpdated ) {
509+ const result = Black . analyzeWeeklyUsage ( {
510+ plan,
511+ usage : sub . fixedUsage ,
512+ timeUpdated : sub . timeFixedUpdated ,
513+ } )
514+ if ( result . status === "rate-limited" )
515+ throw new SubscriptionError (
516+ `Subscription quota exceeded. Retry in ${ formatRetryTime ( result . resetInSec ) } .` ,
517+ result . resetInSec ,
518+ )
519+ }
519520
520- // Check rolling limit
521- if ( sub . rollingUsage && sub . timeRollingUpdated ) {
522- const result = Black . analyzeRollingUsage ( {
523- plan,
524- usage : sub . rollingUsage ,
525- timeUpdated : sub . timeRollingUpdated ,
526- } )
527- if ( result . status === "rate-limited" )
528- throw new SubscriptionError (
529- `Subscription quota exceeded. Retry in ${ formatRetryTime ( result . resetInSec ) } .` ,
530- result . resetInSec ,
531- )
532- }
521+ // Check rolling limit
522+ if ( sub . rollingUsage && sub . timeRollingUpdated ) {
523+ const result = Black . analyzeRollingUsage ( {
524+ plan,
525+ usage : sub . rollingUsage ,
526+ timeUpdated : sub . timeRollingUpdated ,
527+ } )
528+ if ( result . status === "rate-limited" )
529+ throw new SubscriptionError (
530+ `Subscription quota exceeded. Retry in ${ formatRetryTime ( result . resetInSec ) } .` ,
531+ result . resetInSec ,
532+ )
533+ }
533534
534- return
535+ return "subscription"
536+ } catch ( e ) {
537+ if ( ! authInfo . billing . subscription . useBalance ) throw e
538+ }
535539 }
536540
537541 // Validate pay as you go billing
@@ -571,6 +575,8 @@ export async function handler(
571575 throw new UserLimitError (
572576 `You have reached your monthly spending limit of $${ authInfo . user . monthlyLimit } . Manage your limits here: https://opencode.ai/workspace/${ authInfo . workspaceID } /members` ,
573577 )
578+
579+ return "balance"
574580 }
575581
576582 function validateModelSettings ( authInfo : AuthInfo ) {
@@ -587,6 +593,7 @@ export async function handler(
587593 authInfo : AuthInfo ,
588594 modelInfo : ModelInfo ,
589595 providerInfo : ProviderInfo ,
596+ billingSource : ReturnType < typeof validateBilling > ,
590597 usageInfo : UsageInfo ,
591598 ) {
592599 const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
@@ -643,7 +650,8 @@ export async function handler(
643650 "cost.total" : Math . round ( totalCostInCent ) ,
644651 } )
645652
646- if ( ! authInfo ) return
653+ if ( billingSource === "anonymous" ) return
654+ authInfo = authInfo !
647655
648656 const cost = authInfo . provider ?. credentials ? 0 : centsToMicroCents ( totalCostInCent )
649657 await Database . use ( ( db ) =>
@@ -661,13 +669,13 @@ export async function handler(
661669 cacheWrite1hTokens,
662670 cost,
663671 keyID : authInfo . apiKeyId ,
664- enrichment : authInfo . subscription ? { plan : "sub" } : undefined ,
672+ enrichment : billingSource === " subscription" ? { plan : "sub" } : undefined ,
665673 } ) ,
666674 db
667675 . update ( KeyTable )
668676 . set ( { timeUsed : sql `now()` } )
669677 . where ( and ( eq ( KeyTable . workspaceID , authInfo . workspaceID ) , eq ( KeyTable . id , authInfo . apiKeyId ) ) ) ,
670- ...( authInfo . subscription
678+ ...( billingSource === " subscription"
671679 ? ( ( ) => {
672680 const plan = authInfo . billing . subscription ! . plan
673681 const black = BlackData . getLimits ( { plan } )
0 commit comments