@@ -143,6 +143,53 @@ public struct QuotaReport: Decodable, Equatable, Sendable {
143143 public let updatedAt : Double ?
144144}
145145
146+ public enum ProviderQuotaAvailabilityState : Equatable , Sendable , Decodable {
147+ case available
148+ case stale
149+ case unavailable
150+ case unknown
151+
152+ public init ( from decoder : Decoder ) throws {
153+ switch try decoder . singleValueContainer ( ) . decode ( String . self ) {
154+ case " available " : self = . available
155+ case " stale " : self = . stale
156+ case " unavailable " : self = . unavailable
157+ default : self = . unknown
158+ }
159+ }
160+ }
161+
162+ public enum ProviderQuotaUnavailableReason : Equatable , Sendable , Decodable {
163+ case reauthRequired
164+ case localCLIRefreshRequired
165+ case upstreamUnavailable
166+ case unknown
167+
168+ public init ( from decoder : Decoder ) throws {
169+ switch try decoder . singleValueContainer ( ) . decode ( String . self ) {
170+ case " reauth_required " : self = . reauthRequired
171+ case " local_cli_refresh_required " : self = . localCLIRefreshRequired
172+ case " upstream_unavailable " : self = . upstreamUnavailable
173+ default : self = . unknown
174+ }
175+ }
176+ }
177+
178+ public struct ProviderQuotaAvailability : Decodable , Equatable , Sendable {
179+ public let provider : String
180+ public let status : ProviderQuotaAvailabilityState
181+ public let reason : ProviderQuotaUnavailableReason ?
182+ public let checkedAt : Double ?
183+ }
184+
185+ /// Additive `/api/provider-quotas` envelope. Older proxies omit `availability`; the
186+ /// companion preserves its existing generic unavailable fallback in that case.
187+ public struct ProviderQuotaEnvelope : Decodable , Equatable , Sendable {
188+ public let generatedAt : Double ?
189+ public let reports : [ QuotaReport ] ?
190+ public let availability : [ ProviderQuotaAvailability ] ?
191+ }
192+
146193public enum AgentActivityRole : String , Decodable , Equatable , Sendable {
147194 case primary
148195 case subagent
@@ -308,28 +355,35 @@ public struct ProviderSummary: Decodable, Equatable, Sendable {
308355/// Tray-only display state. An unavailable row is structurally incapable of carrying
309356/// a fabricated percentage, reset, source, or freshness timestamp.
310357public enum ProviderQuotaRow : Equatable , Sendable {
311- case available ( QuotaReport )
312- case unavailable ( ProviderSummary )
358+ case available ( QuotaReport , ProviderQuotaAvailability ? )
359+ case unavailable ( ProviderSummary , ProviderQuotaAvailability ? )
313360
314361 public var provider : String {
315362 switch self {
316- case . available ( let report ) : return report . provider
317- case . unavailable ( let summary ) : return summary . name
363+ case . available ( let report , _ ) : return report . provider
364+ case . unavailable ( let summary , _ ) : return summary . name
318365 }
319366 }
320367
321368 public var label : String ? {
322- if case . available ( let report ) = self { return report . label }
369+ if case . available ( let report , _ ) = self { return report . label }
323370 return nil
324371 }
325372
326373 public var report : QuotaReport ? {
327- if case . available ( let report ) = self { return report }
374+ if case . available ( let report , _ ) = self { return report }
328375 return nil
329376 }
330377
378+ public var availability : ProviderQuotaAvailability ? {
379+ switch self {
380+ case . available ( _, let availability ) , . unavailable ( _, let availability ) :
381+ return availability
382+ }
383+ }
384+
331385 public var isUnavailable : Bool {
332- if case . unavailable = self { return true }
386+ if case . unavailable ( _ , _ ) = self { return true }
333387 return false
334388 }
335389
@@ -340,7 +394,15 @@ public extension ProxySnapshot {
340394 /// Join one completed provider inventory with the latest completed quota snapshot.
341395 /// Enabled configured providers form the left side; actual reports always win.
342396 var providerQuotaRows : [ ProviderQuotaRow ] {
343- guard providersLoaded , quotasLoaded else { return quotas . map ( ProviderQuotaRow . available ) }
397+ let availabilityByProvider = Dictionary (
398+ quotaAvailability . map { ( $0 . provider . lowercased ( ) , $0 ) } ,
399+ uniquingKeysWith : { first , _ in first }
400+ )
401+ guard providersLoaded , quotasLoaded else {
402+ return quotas . map {
403+ . available ( $0 , availabilityByProvider [ $0 . provider . lowercased ( ) ] )
404+ }
405+ }
344406
345407 var reportsByProvider : [ String : QuotaReport ] = [ : ]
346408 for report in quotas {
@@ -350,10 +412,11 @@ public extension ProxySnapshot {
350412
351413 var rows : [ ProviderQuotaRow ] = [ ]
352414 for provider in providers where provider . isEnabled {
415+ let availability = availabilityByProvider [ provider . name . lowercased ( ) ]
353416 if let report = reportsByProvider . removeValue ( forKey : provider . name . lowercased ( ) ) {
354- rows . append ( . available ( report ) )
417+ rows . append ( . available ( report , availability ) )
355418 } else if provider . supportsQuotaReporting {
356- rows . append ( . unavailable ( provider ) )
419+ rows . append ( . unavailable ( provider , availability ) )
357420 }
358421 }
359422 return rows
0 commit comments