From 022286fc317ff25dca19e48badd6007beb71ad86 Mon Sep 17 00:00:00 2001 From: "harim.kim" Date: Mon, 13 Jul 2026 15:52:53 +0900 Subject: [PATCH 1/3] fix(cursor): show enterprise included and on-demand usage --- .../Providers/Cursor/CursorProvider.swift | 30 +- .../Providers/Cursor/CursorUsageClient.swift | 11 + .../Cursor/CursorUsageSummaryMapper.swift | 254 ++++++++++++++ .../OpenUsage/Services/LocalLimitsAPI.swift | 17 +- .../CursorUsageSummaryTests.swift | 325 ++++++++++++++++++ .../OpenUsageTests/LocalLimitsAPITests.swift | 33 ++ docs/local-http-api.md | 3 + docs/providers/cursor.md | 10 +- docs/research/cursor-enterprise-usage.md | 57 +++ 9 files changed, 732 insertions(+), 8 deletions(-) create mode 100644 Sources/OpenUsage/Providers/Cursor/CursorUsageSummaryMapper.swift create mode 100644 Tests/OpenUsageTests/CursorUsageSummaryTests.swift create mode 100644 docs/research/cursor-enterprise-usage.md diff --git a/Sources/OpenUsage/Providers/Cursor/CursorProvider.swift b/Sources/OpenUsage/Providers/Cursor/CursorProvider.swift index 3a5efa200..4eab03e5c 100644 --- a/Sources/OpenUsage/Providers/Cursor/CursorProvider.swift +++ b/Sources/OpenUsage/Providers/Cursor/CursorProvider.swift @@ -115,12 +115,13 @@ final class CursorProvider: ProviderRuntime { planInfoUnavailable: planInfoUnavailable ) if fallback.shouldFallback { - let mapped = try await requestBasedResult( + var mapped = try await usageSummaryAndRequestResult( accessToken: currentToken, planName: planName, unavailableMessage: fallback.message ) - return snapshot(mapped) + let history = await appendSpendLines(to: &mapped.lines, accessToken: currentToken) + return snapshot(mapped, usageHistory: history) } if shouldTryGenericRequestFallback(usage: usage) { @@ -349,6 +350,31 @@ final class CursorProvider: ProviderRuntime { } } + private func usageSummaryAndRequestResult( + accessToken: String, + planName: String?, + unavailableMessage: String + ) async throws -> CursorMappedUsage { + let summary = await fetchOptionalJSONObject(label: "usage-summary", request: { + try await self.usageClient.fetchUsageSummary(accessToken: accessToken) + }) + if let summary, !CursorUsageSummaryMapper.hasUsableSummaryPayload(summary) { + AppLog.warn(LogTag.plugin("cursor"), "optional usage-summary response contained no usable usage fields") + } + let requestUsage = await fetchOptionalJSONObject(label: "request-based usage", request: { + try await self.usageClient.fetchRequestBasedUsage(accessToken: accessToken) + }) + if let requestUsage, !CursorUsageSummaryMapper.hasUsableRequestPayload(requestUsage) { + AppLog.warn(LogTag.plugin("cursor"), "optional request-based usage response contained no usable usage fields") + } + return try CursorUsageSummaryMapper.map( + summary: summary, + requestUsage: requestUsage, + planName: planName, + unavailableMessage: unavailableMessage + ) + } + private func shouldTryGenericRequestFallback(usage: [String: Any]) -> Bool { CursorPlanUsageFacts(usage: usage).shouldTryGenericRequestFallback } diff --git a/Sources/OpenUsage/Providers/Cursor/CursorUsageClient.swift b/Sources/OpenUsage/Providers/Cursor/CursorUsageClient.swift index 69283ece8..d7eb09739 100644 --- a/Sources/OpenUsage/Providers/Cursor/CursorUsageClient.swift +++ b/Sources/OpenUsage/Providers/Cursor/CursorUsageClient.swift @@ -11,6 +11,7 @@ struct CursorUsageClient: Sendable { static let refreshURL = URL(string: "https://api2.cursor.sh/oauth/token")! static let creditsURL = URL(string: "https://api2.cursor.sh/aiserver.v1.DashboardService/GetCreditGrantsBalance")! static let restUsageURL = URL(string: "https://cursor.com/api/usage")! + static let usageSummaryURL = URL(string: "https://cursor.com/api/usage-summary")! static let stripeURL = URL(string: "https://cursor.com/api/auth/stripe")! static let exportCSVURL = URL(string: "https://cursor.com/api/dashboard/export-usage-events-csv")! static let clientID = "KbZUR41cY7W6zRSdpSUJ7I7mLYBKOCmB" @@ -62,6 +63,16 @@ struct CursorUsageClient: Sendable { )) } + func fetchUsageSummary(accessToken: String) async throws -> HTTPResponse? { + guard let session = Self.session(from: accessToken) else { return nil } + return try await http.send(HTTPRequest( + method: "GET", + url: Self.usageSummaryURL, + headers: ["Cookie": "WorkosCursorSessionToken=\(session.sessionToken)"], + timeout: 10 + )) + } + func fetchStripeBalance(accessToken: String) async throws -> HTTPResponse? { guard let session = Self.session(from: accessToken) else { return nil } return try await http.send(HTTPRequest( diff --git a/Sources/OpenUsage/Providers/Cursor/CursorUsageSummaryMapper.swift b/Sources/OpenUsage/Providers/Cursor/CursorUsageSummaryMapper.swift new file mode 100644 index 000000000..818444bc8 --- /dev/null +++ b/Sources/OpenUsage/Providers/Cursor/CursorUsageSummaryMapper.swift @@ -0,0 +1,254 @@ +import Foundation + +/// Combines the two REST payloads used by Cursor Enterprise/team dashboards. The request endpoint +/// carries the included request allowance, while usage-summary carries structured percentages, +/// user-scoped on-demand spend, and exact billing-cycle bounds. Neither response is sufficient alone. +enum CursorUsageSummaryMapper { + static func hasUsableSummaryPayload(_ summary: [String: Any]) -> Bool { + let start = (summary["billingCycleStart"] as? String).flatMap(OpenUsageISO8601.date(from:)) + let end = (summary["billingCycleEnd"] as? String).flatMap(OpenUsageISO8601.date(from:)) + if let start, let end, end > start { + return true + } + + let individual = summary["individualUsage"] as? [String: Any] + let team = summary["teamUsage"] as? [String: Any] + let plan = individual?["plan"] as? [String: Any] + if ["totalPercentUsed", "autoPercentUsed", "apiPercentUsed"].contains(where: { + ProviderParse.number(plan?[$0]) != nil + }) { + return true + } + return [individual?["onDemand"], individual?["overall"], team?["onDemand"], team?["pooled"]] + .contains(where: usableDollarBucket) + } + + static func hasUsableRequestPayload(_ usage: [String: Any]) -> Bool { + if let requests = usage["gpt-4"] as? [String: Any], + let limit = ProviderParse.number(requests["maxRequestUsage"]), limit > 0 { + return true + } + return (usage["startOfMonth"] as? String).flatMap(OpenUsageISO8601.date(from:)) != nil + } + + static func map( + summary: [String: Any]?, + requestUsage: [String: Any]?, + planName: String?, + unavailableMessage: String + ) throws -> CursorMappedUsage { + let cycle = billingCycle(summary: summary, requestUsage: requestUsage) + var lines: [MetricLine] = [] + + let hasRequests = appendRequests(requestUsage, cycle: cycle, to: &lines) + if !hasRequests { + appendSummaryTotal(summary, cycle: cycle, to: &lines) + } + appendStructuredPercentages(summary, cycle: cycle, to: &lines) + appendOnDemand(summary, cycle: cycle, to: &lines) + + guard !lines.isEmpty else { + throw CursorUsageError.requestBasedUnavailable(unavailableMessage) + } + + return CursorMappedUsage( + plan: planLabel(planName) ?? planLabel(summary?["membershipType"] as? String), + lines: lines + ) + } + + /// The default Total Usage widget must carry the included allowance on request-based Enterprise + /// plans. Keep the legacy Requests line too, so users who manually enabled that optional widget do + /// not lose it after upgrading. + private static func appendRequests( + _ usage: [String: Any]?, + cycle: BillingCycle, + to lines: inout [MetricLine] + ) -> Bool { + guard let requests = usage?["gpt-4"] as? [String: Any], + let limit = ProviderParse.number(requests["maxRequestUsage"]), + limit > 0 + else { + return false + } + let used = max( + 0, + ProviderParse.number(requests["numRequests"]) + ?? ProviderParse.number(requests["numRequestsTotal"]) + ?? 0 + ) + for label in ["Total usage", "Requests"] { + lines.append(.progress( + label: label, + used: used, + limit: limit, + format: .count(suffix: "requests"), + resetsAt: cycle.resetsAt, + periodDurationMs: cycle.periodDurationMs + )) + } + return true + } + + /// Supports both the live `individualUsage.plan` shape and the pooled/overall variants reported by + /// other Enterprise accounts. A request allowance wins because it is the dashboard's included cap. + private static func appendSummaryTotal( + _ summary: [String: Any]?, + cycle: BillingCycle, + to lines: inout [MetricLine] + ) { + let individual = summary?["individualUsage"] as? [String: Any] + let team = summary?["teamUsage"] as? [String: Any] + let limitType = (summary?["limitType"] as? String)?.lowercased() + + if limitType == "team", let pooled = dollarMeter(team?["pooled"]) { + appendDollarProgress(pooled, label: "Total usage", cycle: cycle, to: &lines) + return + } + if let percent = ProviderParse.number((individual?["plan"] as? [String: Any])?["totalPercentUsed"]) { + lines.append(.progress( + label: "Total usage", + used: percent, + limit: 100, + format: .percent, + resetsAt: cycle.resetsAt, + periodDurationMs: cycle.periodDurationMs + )) + return + } + if let overall = dollarMeter(individual?["overall"]) { + appendDollarProgress(overall, label: "Total usage", cycle: cycle, to: &lines) + return + } + if let pooled = dollarMeter(team?["pooled"]) { + appendDollarProgress(pooled, label: "Total usage", cycle: cycle, to: &lines) + } + } + + private static func appendStructuredPercentages( + _ summary: [String: Any]?, + cycle: BillingCycle, + to lines: inout [MetricLine] + ) { + let plan = ((summary?["individualUsage"] as? [String: Any])?["plan"] as? [String: Any]) + for (key, label) in [("autoPercentUsed", "Auto usage"), ("apiPercentUsed", "API usage")] { + guard let percent = ProviderParse.number(plan?[key]) else { continue } + lines.append(.progress( + label: label, + used: percent, + limit: 100, + format: .percent, + resetsAt: cycle.resetsAt, + periodDurationMs: cycle.periodDurationMs + )) + } + } + + /// The dashboard's headline On-Demand card is user-scoped. Only use the organization aggregate + /// when Cursor omits the individual bucket for that account shape. + private static func appendOnDemand( + _ summary: [String: Any]?, + cycle: BillingCycle, + to lines: inout [MetricLine] + ) { + let individual = summary?["individualUsage"] as? [String: Any] + let team = summary?["teamUsage"] as? [String: Any] + if appendOnDemandBucket(individual?["onDemand"], cycle: cycle, to: &lines) { + return + } + _ = appendOnDemandBucket(team?["onDemand"], cycle: cycle, to: &lines) + } + + /// Returns whether a usable row was emitted. An Enterprise response can include an individual + /// placeholder bucket (`enabled: false` or a zero limit) alongside a valid team bucket, so merely + /// finding the individual dictionary must not suppress the fallback. + private static func appendOnDemandBucket( + _ value: Any?, + cycle: BillingCycle, + to lines: inout [MetricLine] + ) -> Bool { + guard let bucket = value as? [String: Any], bucket["enabled"] as? Bool != false else { + return false + } + if let meter = dollarMeter(bucket) { + appendDollarProgress(meter, label: "On-demand", cycle: cycle, to: &lines) + return true + } + if let usedCents = ProviderParse.number(bucket["used"]), usedCents > 0 { + lines.append(.values( + label: "On-demand", + values: [MetricValue(number: ProviderParse.centsToDollars(usedCents), kind: .dollars)] + )) + return true + } + return false + } + + private static func dollarMeter(_ value: Any?) -> (used: Double, limit: Double)? { + guard let bucket = value as? [String: Any], + bucket["enabled"] as? Bool != false, + let limit = ProviderParse.number(bucket["limit"]), + limit > 0 + else { + return nil + } + let reportedUsed = ProviderParse.number(bucket["used"]) + let inferredUsed = max(0, limit - (ProviderParse.number(bucket["remaining"]) ?? limit)) + let used = reportedUsed.flatMap { $0 > 0 ? $0 : nil } ?? inferredUsed + return (max(0, used), limit) + } + + private static func usableDollarBucket(_ value: Any?) -> Bool { + guard let bucket = value as? [String: Any], bucket["enabled"] as? Bool != false else { + return false + } + return dollarMeter(bucket) != nil || (ProviderParse.number(bucket["used"]) ?? 0) > 0 + } + + private static func appendDollarProgress( + _ meter: (used: Double, limit: Double), + label: String, + cycle: BillingCycle, + to lines: inout [MetricLine] + ) { + lines.append(.progress( + label: label, + used: ProviderParse.centsToDollars(meter.used), + limit: ProviderParse.centsToDollars(meter.limit), + format: .dollars, + resetsAt: cycle.resetsAt, + periodDurationMs: cycle.periodDurationMs + )) + } + + private static func billingCycle( + summary: [String: Any]?, + requestUsage: [String: Any]? + ) -> BillingCycle { + let start = (summary?["billingCycleStart"] as? String).flatMap(OpenUsageISO8601.date(from:)) + let end = (summary?["billingCycleEnd"] as? String).flatMap(OpenUsageISO8601.date(from:)) + if let start, let end, end > start { + return BillingCycle( + resetsAt: end, + periodDurationMs: Int(end.timeIntervalSince(start) * 1000) + ) + } + + let requestStart = (requestUsage?["startOfMonth"] as? String).flatMap(OpenUsageISO8601.date(from:)) + return BillingCycle( + resetsAt: requestStart?.addingTimeInterval(TimeInterval(CursorUsageMapper.billingPeriodMs) / 1000), + periodDurationMs: CursorUsageMapper.billingPeriodMs + ) + } + + private static func planLabel(_ value: String?) -> String? { + guard let value else { return nil } + let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed.isEmpty ? nil : trimmed.titleCased(separator: \.isWhitespace) + } + + private struct BillingCycle { + var resetsAt: Date? + var periodDurationMs: Int + } +} diff --git a/Sources/OpenUsage/Services/LocalLimitsAPI.swift b/Sources/OpenUsage/Services/LocalLimitsAPI.swift index 35c086419..a9f9daa97 100644 --- a/Sources/OpenUsage/Services/LocalLimitsAPI.swift +++ b/Sources/OpenUsage/Services/LocalLimitsAPI.swift @@ -95,7 +95,7 @@ enum LocalLimitsAPI { init?(resource: LimitResourceDescriptor, line: MetricLine) { kind = resource.kind - unit = resource.unit + unit = Self.progressUnit(line) ?? resource.unit switch (resource.source, line) { case (.progress, .progress(_, let rawUsed, let rawLimit, _, let reset, let periodMs, _)): @@ -131,6 +131,21 @@ enum LocalLimitsAPI { } } + /// 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, diff --git a/Tests/OpenUsageTests/CursorUsageSummaryTests.swift b/Tests/OpenUsageTests/CursorUsageSummaryTests.swift new file mode 100644 index 000000000..a88c6a3c3 --- /dev/null +++ b/Tests/OpenUsageTests/CursorUsageSummaryTests.swift @@ -0,0 +1,325 @@ +import XCTest +@testable import OpenUsage + +final class CursorUsageSummaryMapperTests: XCTestCase { + func testCombinesLiveEnterpriseRequestAndIndividualOnDemandShapes() throws { + let mapped = try CursorUsageSummaryMapper.map( + summary: [ + "billingCycleStart": "2026-07-01T00:00:00.000Z", + "billingCycleEnd": "2026-08-01T00:00:00.000Z", + "membershipType": "enterprise", + "limitType": "team", + "individualUsage": [ + "plan": [ + "enabled": true, + "limit": 0, + "autoPercentUsed": 0, + "apiPercentUsed": 6.25, + "totalPercentUsed": 6.25 + ], + "onDemand": ["enabled": true, "used": 0, "limit": 25_000, "remaining": 25_000] + ], + "teamUsage": [ + "onDemand": ["enabled": true, "used": 75_000, "limit": 600_000, "remaining": 525_000] + ] + ], + requestUsage: [ + "gpt-4": ["numRequests": 37, "numRequestsTotal": 37, "maxRequestUsage": 750], + "startOfMonth": "2026-07-01T00:00:00.000Z" + ], + planName: "Enterprise", + unavailableMessage: "unavailable" + ) + + XCTAssertEqual(mapped.plan, "Enterprise") + let total = try XCTUnwrap(progress(mapped.lines, "Total usage")) + XCTAssertEqual(total.used, 37) + XCTAssertEqual(total.limit, 750) + XCTAssertEqual(total.format, .count(suffix: "requests")) + XCTAssertEqual(total.resetsAt, OpenUsageISO8601.date(from: "2026-08-01T00:00:00.000Z")) + XCTAssertEqual(total.periodDurationMs, 31 * 24 * 3_600 * 1_000) + + let requests = try XCTUnwrap(progress(mapped.lines, "Requests")) + XCTAssertEqual(requests.used, 37) + XCTAssertEqual(requests.limit, 750) + XCTAssertEqual(progress(mapped.lines, "Auto usage")?.used, 0) + XCTAssertEqual(progress(mapped.lines, "API usage")?.used, 6.25) + + let onDemand = try XCTUnwrap(progress(mapped.lines, "On-demand")) + XCTAssertEqual(onDemand.used, 0) + XCTAssertEqual(onDemand.limit, 250) + XCTAssertEqual(onDemand.format, .dollars) + } + + func testFallsBackToPooledTotalAndTeamOnDemandWhenIndividualBucketsAreMissing() throws { + let mapped = try CursorUsageSummaryMapper.map( + summary: [ + "membershipType": "team", + "limitType": "team", + "teamUsage": [ + "pooled": ["enabled": true, "used": 125_000, "limit": 4_000_000, "remaining": 3_875_000], + "onDemand": ["enabled": true, "used": 50_000, "limit": 500_000, "remaining": 450_000] + ] + ], + requestUsage: nil, + planName: nil, + unavailableMessage: "unavailable" + ) + + XCTAssertEqual(mapped.plan, "Team") + XCTAssertEqual(progress(mapped.lines, "Total usage")?.used, 1_250) + XCTAssertEqual(progress(mapped.lines, "Total usage")?.limit, 40_000) + XCTAssertEqual(progress(mapped.lines, "On-demand")?.used, 500) + XCTAssertEqual(progress(mapped.lines, "On-demand")?.limit, 5_000) + } + + func testFallsBackToTeamOnDemandWhenIndividualBucketIsDisabled() throws { + let mapped = try CursorUsageSummaryMapper.map( + summary: [ + "individualUsage": [ + "plan": ["totalPercentUsed": 12], + "onDemand": ["enabled": false, "used": 0, "limit": 0] + ], + "teamUsage": [ + "onDemand": ["enabled": true, "used": 25_000, "limit": 100_000] + ] + ], + requestUsage: nil, + planName: "Enterprise", + unavailableMessage: "unavailable" + ) + + XCTAssertEqual(progress(mapped.lines, "On-demand")?.used, 250) + XCTAssertEqual(progress(mapped.lines, "On-demand")?.limit, 1_000) + } + + func testPositiveRemainingDeltaWinsOverReportedZeroSpend() throws { + let mapped = try CursorUsageSummaryMapper.map( + summary: [ + "individualUsage": [ + "plan": ["totalPercentUsed": 25], + "onDemand": ["enabled": true, "used": 0, "limit": 100_000, "remaining": 75_000] + ] + ], + requestUsage: nil, + planName: "Enterprise", + unavailableMessage: "unavailable" + ) + + XCTAssertEqual(progress(mapped.lines, "On-demand")?.used, 250) + XCTAssertEqual(progress(mapped.lines, "On-demand")?.limit, 1_000) + } + + func testRequestPayloadAlonePopulatesDefaultTotalAndOptionalRequests() throws { + let mapped = try CursorUsageSummaryMapper.map( + summary: nil, + requestUsage: [ + "gpt-4": ["numRequests": 19, "maxRequestUsage": 300], + "startOfMonth": "2026-07-01T00:00:00.000Z" + ], + planName: "Enterprise", + unavailableMessage: "unavailable" + ) + + XCTAssertEqual(progress(mapped.lines, "Total usage")?.used, 19) + XCTAssertEqual(progress(mapped.lines, "Total usage")?.limit, 300) + XCTAssertEqual(progress(mapped.lines, "Requests")?.used, 19) + XCTAssertEqual(progress(mapped.lines, "Requests")?.limit, 300) + } + + func testThrowsWhenBothFallbackPayloadsHaveNoUsableMetrics() { + XCTAssertThrowsError(try CursorUsageSummaryMapper.map( + summary: ["individualUsage": ["plan": ["limit": 0]]], + requestUsage: ["gpt-4": ["maxRequestUsage": 0]], + planName: "Enterprise", + unavailableMessage: "Enterprise usage unavailable" + )) { error in + XCTAssertEqual( + error as? CursorUsageError, + .requestBasedUnavailable("Enterprise usage unavailable") + ) + } + } + + private func progress( + _ lines: [MetricLine], + _ label: String + ) -> (used: Double, limit: Double, format: ProgressFormat, resetsAt: Date?, periodDurationMs: Int?)? { + guard case .progress(_, let used, let limit, let format, let resetsAt, let periodDurationMs, _) = + lines.first(where: { $0.label == label }) + else { + return nil + } + return (used, limit, format, resetsAt, periodDurationMs) + } +} + +@MainActor +final class CursorEnterpriseProviderTests: XCTestCase { + func testRefreshCombinesEnterpriseMetersAndStillAppendsUsageHistory() async throws { + let now = try XCTUnwrap(OpenUsageISO8601.date(from: "2026-07-13T12:00:00.000Z")) + let accessToken = makeSummaryCursorJWT(sub: "google-oauth2|enterprise-user") + let csv = """ + Date,Model,Max Mode,Input (w/ Cache Write),Input (w/o Cache Write),Cache Read,Output Tokens,Cost + 2026-07-13T10:00:00Z,composer-1,No,0,1000,0,100,Included + """ + let http = RoutingHTTPClient { request in + if request.url == CursorUsageClient.usageURL { + return HTTPResponse( + statusCode: 200, + headers: [:], + body: Data(#"{"billingCycleStart":"1783923142900","billingCycleEnd":"1783923142900","displayThreshold":100}"#.utf8) + ) + } + if request.url == CursorUsageClient.planURL { + return HTTPResponse( + statusCode: 200, + headers: [:], + body: Data(#"{"planInfo":{"planName":"Enterprise","price":"Custom"}}"#.utf8) + ) + } + if request.url == CursorUsageClient.usageSummaryURL { + XCTAssertEqual( + request.headers["Cookie"], + "WorkosCursorSessionToken=enterprise-user%3A%3A\(accessToken)" + ) + return HTTPResponse(statusCode: 200, headers: [:], body: Data(""" + { + "billingCycleStart": "2026-07-01T00:00:00.000Z", + "billingCycleEnd": "2026-08-01T00:00:00.000Z", + "membershipType": "enterprise", + "limitType": "team", + "individualUsage": { + "plan": { "enabled": true, "autoPercentUsed": 0, "apiPercentUsed": 6.25, "totalPercentUsed": 6.25 }, + "onDemand": { "enabled": true, "used": 0, "limit": 25000, "remaining": 25000 } + }, + "teamUsage": { + "onDemand": { "enabled": true, "used": 75000, "limit": 600000, "remaining": 525000 } + } + } + """.utf8)) + } + if request.url.absoluteString.hasPrefix(CursorUsageClient.restUsageURL.absoluteString) { + return HTTPResponse(statusCode: 200, headers: [:], body: Data(""" + { + "gpt-4": { "numRequests": 37, "numRequestsTotal": 37, "maxRequestUsage": 750 }, + "startOfMonth": "2026-07-01T00:00:00.000Z" + } + """.utf8)) + } + if request.url.absoluteString.hasPrefix(CursorUsageClient.exportCSVURL.absoluteString) { + return HTTPResponse(statusCode: 200, headers: [:], body: Data(csv.utf8)) + } + return HTTPResponse(statusCode: 404, headers: [:], body: Data()) + } + let provider = CursorProvider( + authStore: CursorAuthStore( + sqlite: SummaryCursorSQLite(values: [CursorAuthStore.accessTokenKey: accessToken]), + keychain: FakeKeychain() + ), + usageClient: CursorUsageClient(http: http), + now: { now }, + pricing: { TestPricing.bundled } + ) + + let snapshot = await provider.refresh() + + XCTAssertNil(snapshot.errorCategory) + XCTAssertEqual(snapshot.plan, "Enterprise") + XCTAssertEqual(progress(snapshot.lines, "Total usage")?.used, 37) + XCTAssertEqual(progress(snapshot.lines, "Total usage")?.limit, 750) + XCTAssertEqual(progress(snapshot.lines, "On-demand")?.limit, 250) + XCTAssertNotNil(snapshot.lines.first { $0.label == "Usage Trend" }) + XCTAssertNotNil(snapshot.lines.first { $0.label == "Today" }) + XCTAssertEqual(snapshot.usageHistory?.series.daily.count, 1) + XCTAssertEqual(snapshot.usageHistory?.series.daily.first?.date, "2026-07-13") + XCTAssertTrue(http.requests.contains { $0.url == CursorUsageClient.usageSummaryURL }) + XCTAssertTrue(http.requests.contains { request in + guard request.url.path == CursorUsageClient.restUsageURL.path, + let components = URLComponents(url: request.url, resolvingAgainstBaseURL: false) + else { + return false + } + return components.queryItems?.contains { + $0.name == "user" && $0.value == "enterprise-user" + } == true + }) + XCTAssertTrue(http.requests.contains { $0.url.absoluteString.hasPrefix(CursorUsageClient.exportCSVURL.absoluteString) }) + + let descriptors = provider.widgetDescriptors + let runtime = TestProviderRuntime( + provider: provider.provider, + descriptors: descriptors, + snapshot: snapshot + ) + let defaults = isolatedDefaults("default-widget") + let store = WidgetDataStore( + registry: WidgetRegistry(providers: [provider.provider], descriptors: descriptors), + providers: [runtime], + cache: isolatedCache(defaults), + defaults: defaults + ) + await store.refreshAll() + + let totalDescriptor = try XCTUnwrap(descriptors.first { $0.id == "cursor.usage" }) + let totalData = store.data(for: totalDescriptor) + XCTAssertTrue(DefaultLayout.metricIDs.contains("cursor.usage")) + XCTAssertFalse(DefaultLayout.metricIDs.contains("cursor.requests")) + XCTAssertTrue(totalData.hasData) + XCTAssertEqual(totalData.kind, .count) + XCTAssertEqual(totalData.used, 37) + XCTAssertEqual(totalData.limit, 750) + XCTAssertEqual(totalData.countSuffix, "requests") + + let onDemandDescriptor = try XCTUnwrap(descriptors.first { $0.id == "cursor.onDemand" }) + let onDemandData = store.data(for: onDemandDescriptor) + XCTAssertTrue(onDemandData.hasData) + XCTAssertEqual(onDemandData.kind, .dollars) + XCTAssertEqual(onDemandData.used, 0) + XCTAssertEqual(onDemandData.limit, 250) + } + + private func progress(_ lines: [MetricLine], _ label: String) -> (used: Double, limit: Double)? { + guard case .progress(_, let used, let limit, _, _, _, _) = lines.first(where: { $0.label == label }) else { + return nil + } + return (used, limit) + } + + private func isolatedDefaults(_ name: String) -> UserDefaults { + let suiteName = "OpenUsageTests.CursorEnterprise.\(name).\(UUID().uuidString)" + let defaults = UserDefaults(suiteName: suiteName)! + defaults.removePersistentDomain(forName: suiteName) + return defaults + } + + private func isolatedCache(_ defaults: UserDefaults) -> ProviderSnapshotCache { + ProviderSnapshotCache(userDefaults: defaults, storageKey: "snapshots", ttl: 600, now: { Date() }) + } +} + +private func makeSummaryCursorJWT(sub: String, exp: Double = 9_999_999_999) -> String { + let payload = #"{"sub":"\#(sub)","exp":\#(exp)}"# + let encoded = Data(payload.utf8).base64EncodedString() + .replacingOccurrences(of: "=", with: "") + .replacingOccurrences(of: "+", with: "-") + .replacingOccurrences(of: "/", with: "_") + return "a.\(encoded).c" +} + +private final class SummaryCursorSQLite: SQLiteAccessing, @unchecked Sendable { + private let values: [String: String] + + init(values: [String: String]) { + self.values = values + } + + func queryValue(path: String, sql: String) throws -> String? { + for (key, value) in values where sql.contains(key) { + return value + } + return nil + } + + func execute(path: String, sql: String) throws {} +} diff --git a/Tests/OpenUsageTests/LocalLimitsAPITests.swift b/Tests/OpenUsageTests/LocalLimitsAPITests.swift index 3a0b92ed1..771adf3d8 100644 --- a/Tests/OpenUsageTests/LocalLimitsAPITests.swift +++ b/Tests/OpenUsageTests/LocalLimitsAPITests.swift @@ -166,6 +166,39 @@ final class LocalLimitsAPITests: XCTestCase { XCTAssertNil(resource["utilization"]) } + func testProgressResourceUnitFollowsRuntimeMetricFormat() throws { + let provider = Provider(id: "cursor", displayName: "Cursor", icon: .providerMark("cursor")) + let total = WidgetDescriptor.percent( + id: "cursor.usage", provider: provider, title: "Total Usage", metricLabel: "Total usage" + ).exportingLimit("totalUsage", unit: "percent") + let snapshot = ProviderSnapshot( + providerID: "cursor", + displayName: "Cursor", + lines: [.progress( + label: "Total usage", used: 37, limit: 750, + format: .count(suffix: "requests") + )], + refreshedAt: fetchedAt + ) + let state = LocalUsageAPI.State( + enabledOrderedIDs: ["cursor"], + knownIDs: ["cursor"], + snapshots: ["cursor": snapshot], + limitDescriptors: ["cursor": [total]], + generatedAt: generatedAt + ) + + let root = try json(LocalUsageAPI.respond(method: "GET", path: "/v1/limits", state: state).body) + let providers = try XCTUnwrap(root["providers"] as? [String: Any]) + let cursor = try XCTUnwrap(providers["cursor"] as? [String: Any]) + let resources = try XCTUnwrap(cursor["resources"] as? [String: Any]) + let resource = try XCTUnwrap(resources["totalUsage"] as? [String: Any]) + + XCTAssertEqual(resource["unit"] as? String, "requests") + XCTAssertEqual(resource["used"] as? Double, 37) + XCTAssertEqual(resource["limit"] as? Double, 750) + } + @MainActor func testEveryProviderDeclaresTheApprovedPublicResourceKeys() { let defaults = UserDefaults(suiteName: "LocalLimitsAPITests.\(UUID().uuidString)")! diff --git a/docs/local-http-api.md b/docs/local-http-api.md index 652a78350..72f94d4f2 100644 --- a/docs/local-http-api.md +++ b/docs/local-http-api.md @@ -87,6 +87,9 @@ the provider supplies that meaning. A provider or resource with no current value invented as zero. `expiresAt` is always `fetchedAt` plus the same five-minute freshness interval used by the app and CLI; `stale` says whether that instant has passed. Refresh failures appear in `errors` as `{"providerId":"…","message":"…"}` while a last-good provider snapshot remains available. +For bounded progress resources, `unit` follows the provider's live metric format. For example, Cursor +`totalUsage` is `percent` on percentage-based plans, `requests` on request-based Enterprise plans, and +`usd` when Cursor reports a dollar pool. ### Public resources diff --git a/docs/providers/cursor.md b/docs/providers/cursor.md index 652c787c0..78ad7bd70 100644 --- a/docs/providers/cursor.md +++ b/docs/providers/cursor.md @@ -7,11 +7,11 @@ Tracks your Cursor plan usage using the login from the Cursor app. | Metric | Meaning | |---|---| | Credits | Credit balance left from grants and prepaid account balance | -| Total Usage | Plan usage for the billing cycle (percent; dollars on team plans) | -| Requests | Request count vs. cap (team/enterprise accounts) | +| Total Usage | Plan usage for the billing cycle (percent or dollars; included request count vs. cap on request-based Enterprise accounts) | +| Requests | Optional copy of the included request count vs. cap for custom layouts | | Auto Usage | Auto-model usage percent | | API Usage | API usage percent | -| Extra Usage | On-demand spend; shown as a meter only when Cursor returns a limit | +| Extra Usage | On-demand spend; user-scoped when available, otherwise the team aggregate; shown as a meter when Cursor returns a limit | When Cursor reports your plan name, OpenUsage shows it beside the provider name. @@ -26,9 +26,9 @@ Today, Yesterday, Last 30 Days, and Usage Trend come from Cursor's usage export. ## Troubleshooting - **"Not logged in" / token errors** — open Cursor and make sure you're signed in, then refresh. -- **Some metrics missing** — Cursor omits fields depending on plan type (e.g. Requests only exists on request-based accounts); missing metrics simply show "No data". +- **Some metrics missing** — Cursor omits fields depending on plan type; missing metrics simply show "No data". - **Optional lookup failed** — plan, credit-grant, prepaid-balance, and request-fallback failures stay nonfatal when primary usage is available. OpenUsage records fixed, credential-free reasons in the diagnostic log. ## Under the hood -Connect RPC on `api2.cursor.sh` (dashboard usage), REST fallback at `cursor.com/api/usage` for request-based accounts, Stripe balance at `cursor.com/api/auth/stripe`, and the usage-events CSV export at `cursor.com/api/dashboard/export-usage-events-csv`. The primary dashboard usage request refreshes the token and retries once after a 401/403; optional endpoint failures stay nonfatal and are recorded in the diagnostic log. Per-day spend imputation uses exported token counts priced through the shared [model pricing](../pricing.md); Cursor-native models (`auto`, `composer-*`, …) come from its supplement layer, which maintainers sync from [Cursor models & pricing](https://cursor.com/docs/models-and-pricing.md). +Connect RPC on `api2.cursor.sh` (dashboard usage), combined REST fallback at `cursor.com/api/usage` and `cursor.com/api/usage-summary` for Enterprise/team accounts, Stripe balance at `cursor.com/api/auth/stripe`, and the usage-events CSV export at `cursor.com/api/dashboard/export-usage-events-csv`. The fallback combines the included request allowance with structured percentages and user-scoped on-demand spend; neither REST response is treated as the whole account snapshot by itself. The primary dashboard usage request refreshes the token and retries once after a 401/403; optional endpoint failures stay nonfatal when the other fallback response is usable and are recorded in the diagnostic log. Per-day spend imputation uses exported token counts priced through the shared [model pricing](../pricing.md); Cursor-native models (`auto`, `composer-*`, …) come from its supplement layer, which maintainers sync from [Cursor models & pricing](https://cursor.com/docs/models-and-pricing.md). diff --git a/docs/research/cursor-enterprise-usage.md b/docs/research/cursor-enterprise-usage.md new file mode 100644 index 000000000..ab748a404 --- /dev/null +++ b/docs/research/cursor-enterprise-usage.md @@ -0,0 +1,57 @@ +# Cursor Enterprise Included and On-Demand Usage + +## Problem + +Cursor Enterprise accounts can return no usable `planUsage` from +`GetCurrentPeriodUsage`. OpenUsage then returns early with only the legacy +request-based `Requests` line. That line is optional and disabled by default, +so the enabled Cursor widgets all render `No data` even when `/api/usage` +contains a valid included-request allowance. + +The existing fallback also cannot show included requests and on-demand spend +at the same time because it chooses one response and returns before the other +REST data and usage history are appended. + +## Observed API shapes + +A live Enterprise account was checked on 2026-07-13 with identifiers and exact +account totals omitted: + +- `GetCurrentPeriodUsage`: billing-cycle fields only; no `enabled` or + `planUsage`. +- `GetPlanInfo`: `planName = Enterprise`. +- `GET /api/usage`: `gpt-4.numRequests` and a positive + `gpt-4.maxRequestUsage`, plus `startOfMonth`. +- `GET /api/usage-summary`: ISO billing-cycle bounds, + `membershipType = enterprise`, `limitType = team`, structured percentages in + `individualUsage.plan`, a user-scoped `individualUsage.onDemand`, and a + team-scoped `teamUsage.onDemand`. This response did not contain + `teamUsage.pooled` or `individualUsage.overall`. + +## Required behavior + +1. Fetch both usage-summary and request-based usage for the existing strict + Enterprise/team fallback. +2. Use a valid request allowance as the existing default `Total Usage` meter + and keep the optional `Requests` meter for backwards compatibility. +3. Prefer user-scoped on-demand usage over the team aggregate; use the team + bucket only when the user bucket is unavailable. +4. Map structured Auto/API percentages from `individualUsage.plan`. +5. Fall back to the known pooled/overall usage-summary variants when request + counts are unavailable. +6. Append Cursor usage-history rows after fallback mapping, as on the normal + usage path. +7. Add no widget IDs and change no layout defaults. + +## Acceptance checks + +- A live-shaped Enterprise fixture renders included request usage, Auto/API + percentages, and the individual on-demand dollar cap together. +- The team on-demand aggregate does not replace a valid individual cap. +- A pooled usage-summary fixture still maps to Total Usage. +- Request-only Enterprise responses retain the previous Requests output while + also populating the default Total Usage widget. +- When neither REST response has a usable meter, the existing friendly + fallback error remains visible. +- The full Swift test suite and release build pass, followed by a rebuild and + launch against the live Enterprise account. From d5145223aed723ccfe76f179c05099484da1aec5 Mon Sep 17 00:00:00 2001 From: "harim.kim" Date: Mon, 13 Jul 2026 18:03:46 +0900 Subject: [PATCH 2/3] docs: add Cursor Enterprise live screenshot --- docs/assets/cursor-enterprise-live.png | Bin 0 -> 64911 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/cursor-enterprise-live.png diff --git a/docs/assets/cursor-enterprise-live.png b/docs/assets/cursor-enterprise-live.png new file mode 100644 index 0000000000000000000000000000000000000000..a5d3531b02789371516701ceb103bb1a7dc210a3 GIT binary patch literal 64911 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 V>6L>CnArrkMbMC6?I=Td8x8B^Zc<* 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 literal 0 HcmV?d00001 From 937bb39fb1eb7410eb137a7d0c257d2514005df9 Mon Sep 17 00:00:00 2001 From: "harim.kim" Date: Mon, 13 Jul 2026 17:37:10 +0900 Subject: [PATCH 3/3] ci: package PR 986 local test app --- .github/workflows/ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a6d5a01f..07a4ae58b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,3 +30,16 @@ jobs: - name: Run tests run: swift test + + - name: Package PR 986 local development app + run: | + ./script/build_and_run.sh build + ditto -c -k --keepParent dist/OpenUsage.app OpenUsage-PR986.app.zip + + - name: Upload PR 986 local development app + uses: actions/upload-artifact@v4 + with: + name: OpenUsage-PR986-${{ github.event.pull_request.head.sha }} + path: OpenUsage-PR986.app.zip + if-no-files-found: error + retention-days: 1