forked from robinebers/openusage
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLocalLimitsAPI.swift
More file actions
168 lines (151 loc) · 6.68 KB
/
Copy pathLocalLimitsAPI.swift
File metadata and controls
168 lines (151 loc) · 6.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import Foundation
/// Machine-facing limits serializer shared by the one-shot CLI and local HTTP API.
/// Provider refresh/mapping remains the single source of truth; this edge only selects scalar resources
/// explicitly declared on `WidgetDescriptor` and gives them stable public names.
enum LocalLimitsAPI {
static let schema = "openusage.limits.v1"
static func encode(providerIDs: [String], state: LocalUsageAPI.State) -> Data {
var providers: [String: WireProvider] = [:]
for providerID in providerIDs {
guard let snapshot = state.snapshots[providerID] else { continue }
let descriptors = state.limitDescriptors[providerID] ?? []
providers[providerID] = WireProvider(
snapshot: snapshot,
descriptors: descriptors,
generatedAt: state.generatedAt
)
}
let errors = providerIDs.compactMap { providerID in
state.errors[providerID].map { WireError(providerID: providerID, message: $0) }
}
return encode(WireEnvelope(
schema: schema,
generatedAt: OpenUsageISO8601.string(from: state.generatedAt),
providers: providers,
errors: errors
))
}
private static func encode(_ value: some Encodable) -> Data {
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
return (try? encoder.encode(value)) ?? Data(#"{"errors":[],"providers":{},"schema":"openusage.limits.v1"}"#.utf8)
}
private struct WireEnvelope: Encodable {
let schema: String
let generatedAt: String
let providers: [String: WireProvider]
let errors: [WireError]
}
private struct WireError: Encodable {
let providerID: String
let message: String
enum CodingKeys: String, CodingKey {
case providerID = "providerId"
case message
}
}
private struct WireProvider: Encodable {
let displayName: String
let plan: String?
let fetchedAt: String
let expiresAt: String
let stale: Bool
let resources: [String: WireResource]
init(snapshot: ProviderSnapshot, descriptors: [WidgetDescriptor], generatedAt: Date) {
displayName = snapshot.displayName
plan = snapshot.plan
fetchedAt = OpenUsageISO8601.string(from: snapshot.refreshedAt)
let expiry = snapshot.refreshedAt.addingTimeInterval(RefreshSetting.interval)
expiresAt = OpenUsageISO8601.string(from: expiry)
stale = generatedAt >= expiry
var resources: [String: WireResource] = [:]
for descriptor in descriptors {
guard let line = snapshot.line(label: descriptor.metricLabel) else { continue }
for resource in descriptor.limitResources {
if let value = WireResource(resource: resource, line: line) {
resources[resource.key] = value
}
}
}
self.resources = resources
}
}
private struct WireResource: Encodable {
let kind: LimitResourceDescriptor.Kind
let unit: String
var used: Double?
var available: Double?
var limit: Double?
var remaining: Double?
var utilization: Double?
var resetsAt: String?
var windowSeconds: Double?
var expiresAt: [String]?
var estimated: Bool?
init?(resource: LimitResourceDescriptor, line: MetricLine) {
kind = resource.kind
unit = Self.progressUnit(line) ?? resource.unit
switch (resource.source, line) {
case (.progress, .progress(_, let rawUsed, let rawLimit, _, let reset, let periodMs, _)):
applyProgress(
used: rawUsed, limit: rawLimit, reset: reset, periodMs: periodMs,
resource: resource
)
case (.progressOrValue(_, _),
.progress(_, let rawUsed, let rawLimit, _, let reset, let periodMs, _)):
applyProgress(
used: rawUsed, limit: rawLimit, reset: reset, periodMs: periodMs,
resource: resource
)
case (.value(let expectedKind, let expectedLabel),
.values(_, let values, _, let expiries, _, _)),
(.progressOrValue(let expectedKind, let expectedLabel),
.values(_, let values, _, let expiries, _, _)):
guard let metric = values.first(where: { value in
value.kind == expectedKind && (expectedLabel == nil || value.label == expectedLabel)
}) else { return nil }
if resource.kind == .balance {
available = metric.number
} else {
used = metric.number
}
expiresAt = expiries.isEmpty ? nil : expiries.sorted().map(OpenUsageISO8601.string(from:))
estimated = resource.estimated || metric.estimated ? true : nil
default:
return nil
}
}
/// A descriptor names the stable resource and supplies a fallback unit for value rows. Progress
/// rows carry their actual runtime unit, which can vary by plan (for example Cursor Total Usage
/// is percent on individual plans and requests on request-based Enterprise plans).
private static func progressUnit(_ line: MetricLine) -> String? {
guard case .progress(_, _, _, let format, _, _, _) = line else { return nil }
switch format {
case .percent:
return "percent"
case .dollars:
return "usd"
case .count(let suffix):
return suffix.trimmingCharacters(in: .whitespacesAndNewlines).nilIfEmpty
}
}
private mutating func applyProgress(
used rawUsed: Double,
limit rawLimit: Double,
reset: Date?,
periodMs: Int?,
resource: LimitResourceDescriptor
) {
let boundedLimit = max(0, rawLimit)
let boundedUsed = max(0, rawUsed)
used = resource.kind == .consumption ? boundedUsed : nil
available = resource.kind == .balance ? boundedUsed : nil
limit = boundedLimit
remaining = max(0, boundedLimit - boundedUsed)
utilization = boundedLimit > 0 ? boundedUsed / boundedLimit : nil
resetsAt = reset.map(OpenUsageISO8601.string(from:))
windowSeconds = periodMs.map { Double($0) / 1_000 }
estimated = resource.estimated ? true : nil
}
}
}