@@ -28,7 +28,7 @@ type UsageDataField = Exclude<keyof UsageData, 'error'>;
2828export interface FetchUsageDataOptions { requiredFields ?: readonly UsageDataField [ ] }
2929
3030const UsageCredentialsSchema = z . object ( { claudeAiOauth : z . object ( { accessToken : z . string ( ) . nullable ( ) . optional ( ) } ) . optional ( ) } ) ;
31- const UsageLockErrorSchema = z . enum ( [ 'timeout' , 'rate-limited' ] ) ;
31+ const UsageLockErrorSchema = z . enum ( [ 'timeout' , 'rate-limited' , 'parse-error' ] ) ;
3232const UsageLockSchema = z . object ( {
3333 blockedUntil : z . number ( ) ,
3434 error : UsageLockErrorSchema . optional ( )
@@ -50,30 +50,30 @@ const CachedUsageDataSchema = z.object({
5050 error : z . string ( ) . nullable ( ) . optional ( )
5151} ) ;
5252
53- const PerModelWeeklyBucketSchema = z . object ( {
53+ const UsageApiBucketSchema = z . looseObject ( {
5454 utilization : z . number ( ) . nullable ( ) . optional ( ) ,
5555 resets_at : z . string ( ) . nullable ( ) . optional ( )
5656} ) . nullable ( ) . optional ( ) ;
5757
58- const UsageApiResponseSchema = z . object ( {
59- five_hour : z . object ( {
60- utilization : z . number ( ) . nullable ( ) . optional ( ) ,
61- resets_at : z . string ( ) . nullable ( ) . optional ( )
62- } ) . optional ( ) ,
63- seven_day : z . object ( {
64- utilization : z . number ( ) . nullable ( ) . optional ( ) ,
65- resets_at : z . string ( ) . nullable ( ) . optional ( )
66- } ) . optional ( ) ,
67- seven_day_sonnet : PerModelWeeklyBucketSchema ,
68- seven_day_opus : PerModelWeeklyBucketSchema ,
69- extra_usage : z . object ( {
58+ type UsageApiBucket = z . infer < typeof UsageApiBucketSchema > ;
59+
60+ const UsageApiResponseSchema = z . looseObject ( {
61+ five_hour : UsageApiBucketSchema ,
62+ seven_day : UsageApiBucketSchema ,
63+ seven_day_sonnet : UsageApiBucketSchema ,
64+ seven_day_opus : UsageApiBucketSchema ,
65+ extra_usage : z . looseObject ( {
7066 is_enabled : z . boolean ( ) . nullable ( ) . optional ( ) ,
7167 monthly_limit : z . number ( ) . nullable ( ) . optional ( ) ,
7268 used_credits : z . number ( ) . nullable ( ) . optional ( ) ,
7369 utilization : z . number ( ) . nullable ( ) . optional ( )
74- } ) . optional ( )
70+ } ) . nullable ( ) . optional ( )
7571} ) ;
7672
73+ function getUsageApiBucketUtilization ( bucket : UsageApiBucket ) : number | undefined {
74+ return bucket === null ? 0 : bucket ?. utilization ?? undefined ;
75+ }
76+
7777function parseJsonWithSchema < T > ( rawJson : string , schema : z . ZodType < T > ) : T | null {
7878 try {
7979 const parsed = schema . safeParse ( JSON . parse ( rawJson ) ) ;
@@ -120,13 +120,13 @@ function parseUsageApiResponse(rawJson: string): UsageData | null {
120120 }
121121
122122 return {
123- sessionUsage : parsed . five_hour ?. utilization ?? undefined ,
123+ sessionUsage : getUsageApiBucketUtilization ( parsed . five_hour ) ,
124124 sessionResetAt : parsed . five_hour ?. resets_at ?? undefined ,
125- weeklyUsage : parsed . seven_day ?. utilization ?? undefined ,
125+ weeklyUsage : getUsageApiBucketUtilization ( parsed . seven_day ) ,
126126 weeklyResetAt : parsed . seven_day ?. resets_at ?? undefined ,
127- weeklySonnetUsage : parsed . seven_day_sonnet === null ? 0 : parsed . seven_day_sonnet ?. utilization ?? undefined ,
127+ weeklySonnetUsage : getUsageApiBucketUtilization ( parsed . seven_day_sonnet ) ,
128128 weeklySonnetResetAt : parsed . seven_day_sonnet ?. resets_at ?? undefined ,
129- weeklyOpusUsage : parsed . seven_day_opus === null ? 0 : parsed . seven_day_opus ?. utilization ?? undefined ,
129+ weeklyOpusUsage : getUsageApiBucketUtilization ( parsed . seven_day_opus ) ,
130130 weeklyOpusResetAt : parsed . seven_day_opus ?. resets_at ?? undefined ,
131131 extraUsageEnabled : parsed . extra_usage ?. is_enabled ?? undefined ,
132132 extraUsageLimit : parsed . extra_usage ?. monthly_limit ?? undefined ,
@@ -569,11 +569,13 @@ export async function fetchUsageData(options: FetchUsageDataOptions = {}): Promi
569569
570570 const usageData = parseUsageApiResponse ( response . body ) ;
571571 if ( ! usageData ) {
572+ writeUsageLock ( now + LOCK_MAX_AGE , 'parse-error' ) ;
572573 return getStaleUsageOrError ( 'parse-error' , now , LOCK_MAX_AGE , requiredFields ) ;
573574 }
574575
575576 // Validate we got actual data
576577 if ( usageData . sessionUsage === undefined && usageData . weeklyUsage === undefined ) {
578+ writeUsageLock ( now + LOCK_MAX_AGE , 'parse-error' ) ;
577579 return getStaleUsageOrError ( 'parse-error' , now , LOCK_MAX_AGE , requiredFields ) ;
578580 }
579581
@@ -587,6 +589,7 @@ export async function fetchUsageData(options: FetchUsageDataOptions = {}): Promi
587589
588590 return cacheUsageData ( usageData , now ) ;
589591 } catch {
592+ writeUsageLock ( now + LOCK_MAX_AGE , 'parse-error' ) ;
590593 return getStaleUsageOrError ( 'parse-error' , now , LOCK_MAX_AGE , requiredFields ) ;
591594 }
592595}
0 commit comments