320 lines · 11.0 KB · Swift
RawDownload
import CoreFoundationimport Foundation public enum DevinUsageError: LocalizedError, Sendable {    case noSession    case missingOrganization    case invalidCredentials    case apiError(String)    case parseFailed(String)     public var errorDescription: String? {        switch self {        case .noSession:            "No Devin browser session found. Please log in to app.devin.ai or paste a Bearer token."        case .missingOrganization:            "No Devin organization was found. Open an app.devin.ai/org/... page " +                "or set the organization in Devin settings."        case .invalidCredentials:            "Devin session token is invalid or expired."        case let .apiError(message):            "Devin API error: \(message)"        case let .parseFailed(message):            "Could not parse Devin usage: \(message)"        }    }} public struct DevinQuotaWindow: Sendable, Equatable {    public let usedPercent: Double    public let resetsAt: Date?     public init(usedPercent: Double, resetsAt: Date? = nil) {        self.usedPercent = min(100, max(0, usedPercent))        self.resetsAt = resetsAt    }} public struct DevinUsageSnapshot: Sendable, Equatable {    public let daily: DevinQuotaWindow?    public let weekly: DevinQuotaWindow?    public let planName: String?    public let organization: String?    public let updatedAt: Date     public init(        daily: DevinQuotaWindow?,        weekly: DevinQuotaWindow?,        planName: String?,        organization: String?,        updatedAt: Date)    {        self.daily = daily        self.weekly = weekly        self.planName = planName        self.organization = organization        self.updatedAt = updatedAt    }     public func toUsageSnapshot() -> UsageSnapshot {        let primary = self.daily.map {            RateWindow(                usedPercent: $0.usedPercent,                windowMinutes: 24 * 60,                resetsAt: $0.resetsAt,                resetDescription: "Daily")        }        let secondary = self.weekly.map {            RateWindow(                usedPercent: $0.usedPercent,                windowMinutes: 7 * 24 * 60,                resetsAt: $0.resetsAt,                resetDescription: "Weekly")        }        let identity = ProviderIdentitySnapshot(            providerID: .devin,            accountEmail: nil,            accountOrganization: self.organization,            loginMethod: self.planName)        return UsageSnapshot(            primary: primary,            secondary: secondary,            updatedAt: self.updatedAt,            identity: identity)    }} public enum DevinUsageParser {    public static func parse(_ data: Data, organization: String?, now: Date = Date()) throws -> DevinUsageSnapshot {        let object = try JSONSerialization.jsonObject(with: data)        return try self.parse(object, organization: organization, now: now)    }     public static func parse(_ object: Any, organization: String?, now: Date = Date()) throws -> DevinUsageSnapshot {        let current = (object as? [String: Any]).map(self.currentQuotaWindows)        let daily = current?.daily ?? self.findWindow(in: object, matching: self.isDailyKey)        let weekly = current?.weekly ?? self.findWindow(in: object, matching: self.isWeeklyKey)        guard daily != nil || weekly != nil else {            throw DevinUsageError.parseFailed("Missing Devin quota windows.")        }         return DevinUsageSnapshot(            daily: daily,            weekly: weekly,            planName: self.findPlanName(in: object),            organization: self.displayOrganization(from: organization),            updatedAt: now)    }     private static func currentQuotaWindows(_ dictionary: [String: Any])        -> (daily: DevinQuotaWindow?, weekly: DevinQuotaWindow?)    {        let daily = self.currentQuotaWindow(            percent: dictionary["daily_percentage"],            resetsAt: dictionary["daily_reset_at"])        let weekly = self.currentQuotaWindow(            percent: dictionary["weekly_percentage"],            resetsAt: dictionary["weekly_reset_at"])        return (daily, weekly)    }     private static func currentQuotaWindow(percent: Any?, resetsAt: Any?) -> DevinQuotaWindow? {        guard let usedPercent = self.double(percent) else { return nil }        return DevinQuotaWindow(            usedPercent: usedPercent <= 1 ? usedPercent * 100 : usedPercent,            resetsAt: self.date(from: resetsAt))    }     private static func findWindow(in object: Any, matching keyMatches: (String) -> Bool) -> DevinQuotaWindow? {        if let dictionary = object as? [String: Any] {            for (key, value) in dictionary where keyMatches(key) {                if let window = self.window(from: value) {                    return window                }            }            for value in dictionary.values {                if let found = self.findWindow(in: value, matching: keyMatches) {                    return found                }            }        }         if let array = object as? [Any] {            for value in array {                if let found = self.findWindow(in: value, matching: keyMatches) {                    return found                }            }        }         return nil    }     private static func window(from object: Any) -> DevinQuotaWindow? {        guard let dictionary = object as? [String: Any] else {            guard let percent = self.percent(from: object) else { return nil }            return DevinQuotaWindow(usedPercent: percent, resetsAt: nil)        }         if let percent = self.percent(from: dictionary) {            return DevinQuotaWindow(                usedPercent: percent,                resetsAt: self.findResetDate(in: dictionary))        }         if let nested = dictionary.values.lazy.compactMap({ self.window(from: $0) }).first {            return nested        }         return nil    }     private static func percent(from object: Any) -> Double? {        if let number = self.double(object) {            return number <= 1 ? number * 100 : number        }        guard let dictionary = object as? [String: Any] else { return nil }         let directKeys = [            "used_percent",            "usedPercent",            "usage_percent",            "usagePercent",            "percent_used",            "percentUsed",            "percent",        ]        for key in directKeys {            if let value = self.double(dictionary[key]) {                return value <= 1 ? value * 100 : value            }        }         let remainingKeys = ["remaining_percent", "remainingPercent", "percent_remaining", "percentRemaining"]        for key in remainingKeys {            if let value = self.double(dictionary[key]) {                let percent = value <= 1 ? value * 100 : value                return 100 - percent            }        }         let used = self.firstDouble(in: dictionary, keys: ["used", "usage", "used_count", "usedCount", "consumed"])        let limit = self.firstDouble(in: dictionary, keys: ["limit", "quota", "total", "max", "available"])        if let used, let limit, limit > 0 {            return used / limit * 100        }         let remaining = self.firstDouble(in: dictionary, keys: ["remaining", "left", "available"])        if let remaining, let limit, limit > 0 {            return (limit - remaining) / limit * 100        }         return nil    }     private static func findPlanName(in object: Any) -> String? {        if let dictionary = object as? [String: Any] {            for key in ["plan_name", "planName", "plan", "tier", "subscription_tier", "subscriptionTier"] {                if let value = dictionary[key] as? String,                   let cleaned = self.cleanDisplay(value)                {                    return cleaned                }            }            for value in dictionary.values {                if let found = self.findPlanName(in: value) {                    return found                }            }        }         if let array = object as? [Any] {            for value in array {                if let found = self.findPlanName(in: value) {                    return found                }            }        }         return nil    }     private static func findResetDate(in dictionary: [String: Any]) -> Date? {        for (key, value) in dictionary where key.localizedCaseInsensitiveContains("reset") {            if let date = self.date(from: value) {                return date            }        }        return nil    }     private static func date(from value: Any?) -> Date? {        if let raw = value as? String {            if let date = ISO8601DateFormatter().date(from: raw) {                return date            }            if let number = Double(raw) {                return self.date(from: number)            }        }        if let number = self.double(value) {            return self.date(from: number)        }        return nil    }     private static func date(from number: Double) -> Date? {        guard number > 0 else { return nil }        let seconds = number > 10_000_000_000 ? number / 1000 : number        return Date(timeIntervalSince1970: seconds)    }     private static func firstDouble(in dictionary: [String: Any], keys: [String]) -> Double? {        for key in keys {            if let value = self.double(dictionary[key]) {                return value            }        }        return nil    }     private static func double(_ value: Any?) -> Double? {        switch value {        case let number as NSNumber:            CFGetTypeID(number) == CFBooleanGetTypeID() ? nil : number.doubleValue        case let string as String:            Double(string.trimmingCharacters(in: .whitespacesAndNewlines))        default:            nil        }    }     private static func isDailyKey(_ raw: String) -> Bool {        let key = raw.lowercased()        return !key.contains("hide") && (key.contains("daily") || key.contains("day"))    }     private static func isWeeklyKey(_ raw: String) -> Bool {        let key = raw.lowercased()        return !key.contains("hide") && (key.contains("weekly") || key.contains("week"))    }     private static func displayOrganization(from raw: String?) -> String? {        guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return nil }        if raw.hasPrefix("org/") {            return String(raw.dropFirst(4))        }        if raw.hasPrefix("organizations/") {            return String(raw.dropFirst("organizations/".count))        }        return raw    }     private static func cleanDisplay(_ raw: String) -> String? {        let cleaned = raw.trimmingCharacters(in: .whitespacesAndNewlines)        guard !cleaned.isEmpty else { return nil }        return cleaned.split(separator: "_").flatMap { $0.split(separator: "-") }.map { part in            part.prefix(1).uppercased() + String(part.dropFirst())        }.joined(separator: " ")    }}