chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
import Foundation
|
||||
|
||||
public struct CopilotUsageResponse: Sendable, Decodable {
|
||||
private struct AnyCodingKey: CodingKey {
|
||||
let stringValue: String
|
||||
let intValue: Int?
|
||||
|
||||
init?(stringValue: String) {
|
||||
self.stringValue = stringValue
|
||||
self.intValue = nil
|
||||
}
|
||||
|
||||
init?(intValue: Int) {
|
||||
self.stringValue = String(intValue)
|
||||
self.intValue = intValue
|
||||
}
|
||||
}
|
||||
|
||||
public struct QuotaSnapshot: Sendable, Decodable {
|
||||
public let entitlement: Double
|
||||
public let remaining: Double
|
||||
public let percentRemaining: Double
|
||||
public let quotaId: String
|
||||
public let hasPercentRemaining: Bool
|
||||
public let unlimited: Bool
|
||||
private let entitlementWasDecoded: Bool
|
||||
private let remainingWasDecoded: Bool
|
||||
public var usedPercent: Double {
|
||||
max(0, 100 - self.percentRemaining)
|
||||
}
|
||||
|
||||
public var overQuotaUsedPercent: Double? {
|
||||
self.usedPercent > 100 ? self.usedPercent : nil
|
||||
}
|
||||
|
||||
public var isPlaceholder: Bool {
|
||||
if self.unlimited {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.entitlement == 0,
|
||||
self.remaining == 0,
|
||||
self.percentRemaining == 0,
|
||||
!self.hasPercentRemaining
|
||||
{
|
||||
return true
|
||||
}
|
||||
|
||||
// An explicit zero-entitlement, zero-remaining snapshot carries no usable quota signal.
|
||||
// GitHub returns this shape for token-based billing / Copilot Business seats,
|
||||
// sometimes as percent_remaining=100 with a non-empty quota_id, which would
|
||||
// otherwise render as a misleading "0% used" (100 - 100). Treat it as a
|
||||
// placeholder so the usual handling drops it instead of showing fake usage.
|
||||
return self.entitlementWasDecoded && self.remainingWasDecoded && self.entitlement == 0 && self
|
||||
.remaining == 0
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case entitlement
|
||||
case remaining
|
||||
case percentRemaining = "percent_remaining"
|
||||
case quotaId = "quota_id"
|
||||
case unlimited
|
||||
}
|
||||
|
||||
public init(
|
||||
entitlement: Double,
|
||||
remaining: Double,
|
||||
percentRemaining: Double,
|
||||
quotaId: String,
|
||||
hasPercentRemaining: Bool = true,
|
||||
unlimited: Bool = false)
|
||||
{
|
||||
self.entitlement = entitlement
|
||||
self.remaining = remaining
|
||||
self.percentRemaining = unlimited ? 100 : percentRemaining
|
||||
self.quotaId = quotaId
|
||||
self.hasPercentRemaining = unlimited || hasPercentRemaining
|
||||
self.unlimited = unlimited
|
||||
self.entitlementWasDecoded = true
|
||||
self.remainingWasDecoded = true
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let decodedEntitlement = Self.decodeNumberIfPresent(container: container, key: .entitlement)
|
||||
let decodedRemaining = Self.decodeNumberIfPresent(container: container, key: .remaining)
|
||||
self.entitlement = decodedEntitlement ?? 0
|
||||
self.remaining = decodedRemaining ?? 0
|
||||
self.entitlementWasDecoded = decodedEntitlement != nil
|
||||
self.remainingWasDecoded = decodedRemaining != nil
|
||||
let decodedUnlimited = try container.decodeIfPresent(Bool.self, forKey: .unlimited) ?? false
|
||||
let decodedPercent = Self.decodeNumberIfPresent(container: container, key: .percentRemaining)
|
||||
if decodedUnlimited {
|
||||
self.percentRemaining = 100
|
||||
self.hasPercentRemaining = true
|
||||
} else if let decodedPercent {
|
||||
self.percentRemaining = decodedPercent
|
||||
self.hasPercentRemaining = true
|
||||
} else if let entitlement = decodedEntitlement,
|
||||
entitlement > 0,
|
||||
let remaining = decodedRemaining
|
||||
{
|
||||
let derived = (remaining / entitlement) * 100
|
||||
self.percentRemaining = derived
|
||||
self.hasPercentRemaining = true
|
||||
} else {
|
||||
// Without percent_remaining and both inputs for derivation, the percent is unknown.
|
||||
self.percentRemaining = 0
|
||||
self.hasPercentRemaining = false
|
||||
}
|
||||
self.quotaId = try container.decodeIfPresent(String.self, forKey: .quotaId) ?? ""
|
||||
self.unlimited = decodedUnlimited
|
||||
}
|
||||
|
||||
private static func decodeNumberIfPresent(
|
||||
container: KeyedDecodingContainer<CodingKeys>,
|
||||
key: CodingKeys) -> Double?
|
||||
{
|
||||
if let value = try? container.decodeIfPresent(Double.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decodeIfPresent(Int.self, forKey: key) {
|
||||
return Double(value)
|
||||
}
|
||||
if let value = try? container.decodeIfPresent(String.self, forKey: key) {
|
||||
return Double(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public struct QuotaCounts: Sendable, Decodable {
|
||||
public let chat: Double?
|
||||
public let completions: Double?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case chat
|
||||
case completions
|
||||
}
|
||||
|
||||
public init(chat: Double?, completions: Double?) {
|
||||
self.chat = chat
|
||||
self.completions = completions
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.chat = Self.decodeNumberIfPresent(container: container, key: .chat)
|
||||
self.completions = Self.decodeNumberIfPresent(container: container, key: .completions)
|
||||
}
|
||||
|
||||
private static func decodeNumberIfPresent(
|
||||
container: KeyedDecodingContainer<CodingKeys>,
|
||||
key: CodingKeys) -> Double?
|
||||
{
|
||||
if let value = try? container.decodeIfPresent(Double.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decodeIfPresent(Int.self, forKey: key) {
|
||||
return Double(value)
|
||||
}
|
||||
if let value = try? container.decodeIfPresent(String.self, forKey: key) {
|
||||
return Double(value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public struct QuotaSnapshots: Sendable, Decodable {
|
||||
public let premiumInteractions: QuotaSnapshot?
|
||||
public let chat: QuotaSnapshot?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case premiumInteractions = "premium_interactions"
|
||||
case chat
|
||||
}
|
||||
|
||||
public init(premiumInteractions: QuotaSnapshot?, chat: QuotaSnapshot?) {
|
||||
self.premiumInteractions = premiumInteractions
|
||||
self.chat = chat
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
var premium = try container.decodeIfPresent(QuotaSnapshot.self, forKey: .premiumInteractions)
|
||||
var chat = try container.decodeIfPresent(QuotaSnapshot.self, forKey: .chat)
|
||||
if premium?.isPlaceholder == true {
|
||||
premium = nil
|
||||
}
|
||||
if chat?.isPlaceholder == true {
|
||||
chat = nil
|
||||
}
|
||||
|
||||
if premium == nil || chat == nil {
|
||||
let dynamic = try decoder.container(keyedBy: AnyCodingKey.self)
|
||||
var fallbackPremium: QuotaSnapshot?
|
||||
var fallbackChat: QuotaSnapshot?
|
||||
var firstUsable: QuotaSnapshot?
|
||||
|
||||
for key in dynamic.allKeys {
|
||||
let value: QuotaSnapshot
|
||||
do {
|
||||
guard let decoded = try dynamic.decodeIfPresent(QuotaSnapshot.self, forKey: key) else {
|
||||
continue
|
||||
}
|
||||
guard !decoded.isPlaceholder else { continue }
|
||||
value = decoded
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
|
||||
let name = key.stringValue.lowercased()
|
||||
if firstUsable == nil {
|
||||
firstUsable = value
|
||||
}
|
||||
|
||||
if fallbackChat == nil, name.contains("chat") {
|
||||
fallbackChat = value
|
||||
continue
|
||||
}
|
||||
|
||||
if fallbackPremium == nil,
|
||||
name.contains("premium") || name.contains("completion") || name.contains("code")
|
||||
{
|
||||
fallbackPremium = value
|
||||
}
|
||||
}
|
||||
|
||||
if premium == nil {
|
||||
premium = fallbackPremium
|
||||
}
|
||||
if chat == nil {
|
||||
chat = fallbackChat
|
||||
}
|
||||
if premium == nil, chat == nil {
|
||||
// If keys are unfamiliar, still expose one usable quota instead of failing.
|
||||
chat = firstUsable
|
||||
}
|
||||
}
|
||||
|
||||
self.premiumInteractions = premium
|
||||
self.chat = chat
|
||||
}
|
||||
}
|
||||
|
||||
public let quotaSnapshots: QuotaSnapshots
|
||||
public let copilotPlan: String
|
||||
public let tokenBasedBilling: Bool
|
||||
public let assignedDate: String?
|
||||
public let quotaResetDate: String?
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case quotaSnapshots = "quota_snapshots"
|
||||
case copilotPlan = "copilot_plan"
|
||||
case tokenBasedBilling = "token_based_billing"
|
||||
case assignedDate = "assigned_date"
|
||||
case quotaResetDate = "quota_reset_date"
|
||||
case monthlyQuotas = "monthly_quotas"
|
||||
case limitedUserQuotas = "limited_user_quotas"
|
||||
}
|
||||
|
||||
public init(
|
||||
quotaSnapshots: QuotaSnapshots,
|
||||
copilotPlan: String,
|
||||
tokenBasedBilling: Bool = false,
|
||||
assignedDate: String?,
|
||||
quotaResetDate: String?)
|
||||
{
|
||||
self.quotaSnapshots = quotaSnapshots
|
||||
self.copilotPlan = copilotPlan
|
||||
self.tokenBasedBilling = tokenBasedBilling
|
||||
self.assignedDate = assignedDate
|
||||
self.quotaResetDate = quotaResetDate
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let directSnapshots = try container.decodeIfPresent(QuotaSnapshots.self, forKey: .quotaSnapshots)
|
||||
let monthlyQuotas = try container.decodeIfPresent(QuotaCounts.self, forKey: .monthlyQuotas)
|
||||
let limitedUserQuotas = try container.decodeIfPresent(QuotaCounts.self, forKey: .limitedUserQuotas)
|
||||
let monthlyLimitedSnapshots = Self.makeQuotaSnapshots(monthly: monthlyQuotas, limited: limitedUserQuotas)
|
||||
let premium = Self.usableQuotaSnapshot(from: directSnapshots?.premiumInteractions) ??
|
||||
Self.usableQuotaSnapshot(from: monthlyLimitedSnapshots?.premiumInteractions)
|
||||
let chat = Self.usableQuotaSnapshot(from: directSnapshots?.chat) ??
|
||||
Self.usableQuotaSnapshot(from: monthlyLimitedSnapshots?.chat)
|
||||
if premium != nil || chat != nil {
|
||||
self.quotaSnapshots = QuotaSnapshots(premiumInteractions: premium, chat: chat)
|
||||
} else {
|
||||
self.quotaSnapshots = directSnapshots ?? QuotaSnapshots(premiumInteractions: nil, chat: nil)
|
||||
}
|
||||
self.copilotPlan = try container.decodeIfPresent(String.self, forKey: .copilotPlan) ?? "unknown"
|
||||
self.tokenBasedBilling = try container.decodeIfPresent(Bool.self, forKey: .tokenBasedBilling) ?? false
|
||||
self.assignedDate = try container.decodeIfPresent(String.self, forKey: .assignedDate)
|
||||
self.quotaResetDate = try container.decodeIfPresent(String.self, forKey: .quotaResetDate)
|
||||
}
|
||||
|
||||
private static func makeQuotaSnapshots(monthly: QuotaCounts?, limited: QuotaCounts?) -> QuotaSnapshots? {
|
||||
let premium = Self.makeQuotaSnapshot(
|
||||
monthly: monthly?.completions,
|
||||
limited: limited?.completions,
|
||||
quotaID: "completions")
|
||||
let chat = Self.makeQuotaSnapshot(
|
||||
monthly: monthly?.chat,
|
||||
limited: limited?.chat,
|
||||
quotaID: "chat")
|
||||
guard premium != nil || chat != nil else { return nil }
|
||||
return QuotaSnapshots(premiumInteractions: premium, chat: chat)
|
||||
}
|
||||
|
||||
private static func makeQuotaSnapshot(monthly: Double?, limited: Double?, quotaID: String) -> QuotaSnapshot? {
|
||||
guard monthly != nil || limited != nil else { return nil }
|
||||
guard let monthly else {
|
||||
// Without a monthly denominator, avoid fabricating a misleading percentage.
|
||||
return nil
|
||||
}
|
||||
guard let limited else {
|
||||
// Without the limited/remaining value, usage is unknown.
|
||||
return nil
|
||||
}
|
||||
|
||||
let entitlement = max(0, monthly)
|
||||
guard entitlement > 0 else {
|
||||
// A zero denominator cannot produce a meaningful percentage.
|
||||
return nil
|
||||
}
|
||||
let remaining = max(0, limited)
|
||||
let percentRemaining = max(0, min(100, (remaining / entitlement) * 100))
|
||||
|
||||
return QuotaSnapshot(
|
||||
entitlement: entitlement,
|
||||
remaining: remaining,
|
||||
percentRemaining: percentRemaining,
|
||||
quotaId: quotaID)
|
||||
}
|
||||
|
||||
private static func usableQuotaSnapshot(from snapshot: QuotaSnapshot?) -> QuotaSnapshot? {
|
||||
guard let snapshot, !snapshot.isPlaceholder, snapshot.hasPercentRemaining else {
|
||||
return nil
|
||||
}
|
||||
return snapshot
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user