import CoreFoundation import 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 let overageBalance: Double? public init( daily: DevinQuotaWindow?, weekly: DevinQuotaWindow?, planName: String?, organization: String?, updatedAt: Date, overageBalance: Double? = nil) { self.daily = daily self.weekly = weekly self.planName = planName self.organization = organization self.updatedAt = updatedAt self.overageBalance = overageBalance } 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) let providerCost: ProviderCostSnapshot? = self.overageBalance.map { ProviderCostSnapshot( used: $0, limit: 0, currencyCode: "USD", period: "Extra usage balance", updatedAt: self.updatedAt) } return UsageSnapshot( primary: primary, secondary: secondary, providerCost: providerCost, 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, overageBalance: self.findOverageBalance(in: object)) } private static func findOverageBalance(in object: Any) -> Double? { guard let dictionary = object as? [String: Any] else { return nil } if let value = self.nonnegativeFiniteDouble(dictionary["overage_balance"]) { return value } if let cents = self.nonnegativeFiniteDouble(dictionary["overage_balance_cents"]) { return cents / 100.0 } return nil } private static func nonnegativeFiniteDouble(_ value: Any?) -> Double? { guard let value = self.double(value), value.isFinite, value >= 0 else { return nil } return value } 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: " ") } }