Skip to content

Commit 9381533

Browse files
t3dotggim-kvijayclaude
authored
fix(usage): Cursor account history loads about 4x faster (pingdotgg#17140)
Keep up to six Cursor usage pages requested ahead of the one being read, still read in page order, and cancel requests left in flight when the read ends. Continues pingdotgg#14384. Co-authored-by: Krishna Vijay <228381532+im-kvijay@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 13c5328 commit 9381533

2 files changed

Lines changed: 92 additions & 2 deletions

File tree

‎apps/server/src/usage/cursorUsageReader.test.ts‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,75 @@ describe("readCursorAccountUsage", () => {
1919
error: "Allow Keychain access on the Mac running T3 Code, then refresh.",
2020
});
2121
});
22+
23+
it("reads the pages behind the first together and keeps them in page order", async () => {
24+
const accessToken = `header.${Buffer.from(JSON.stringify({ sub: "auth|demo" })).toString("base64url")}.signature`;
25+
let inFlight = 0;
26+
let mostInFlight = 0;
27+
const result = await readCursorAccountUsage(
28+
{ kind: "keychain" },
29+
0,
30+
1781000000000,
31+
async (_url, init) => {
32+
const page: number = JSON.parse(String(init.body)).page;
33+
inFlight++;
34+
mostInFlight = Math.max(mostInFlight, inFlight);
35+
// Later pages answer first, so record order cannot come from arrival order.
36+
for (let turn = page; turn < 9; turn++) await Promise.resolve();
37+
inFlight--;
38+
return Response.json({
39+
totalUsageEventsCount: 8001,
40+
usageEventsDisplay: Array.from({ length: page === 9 ? 1 : 1000 }, (_, index) => ({
41+
timestamp: String(1780000000000 + (page - 1) * 1000 + index),
42+
model: "gpt-5",
43+
tokenUsage: { inputTokens: 1 },
44+
})),
45+
});
46+
},
47+
async () => accessToken,
48+
);
49+
assert.isNull(result.error);
50+
assert.strictEqual(mostInFlight, 6);
51+
assert.deepStrictEqual(
52+
result.records.map((record) => record.timestampMs),
53+
Array.from({ length: 8001 }, (_, index) => 1780000000000 + index),
54+
);
55+
});
56+
57+
it("ends like a page-by-page read when a page fails, leaving no request open", async () => {
58+
const accessToken = `header.${Buffer.from(JSON.stringify({ sub: "auth|demo" })).toString("base64url")}.signature`;
59+
const fullPage = {
60+
totalUsageEventsCount: 8001,
61+
usageEventsDisplay: Array.from({ length: 1000 }, () => ({ tokenUsage: null })),
62+
};
63+
// Page 3 fails while the pages after it are still waiting on Cursor.
64+
for (const [secondPage, error] of [
65+
[() => Response.json(fullPage), "Cursor account usage could not be read."],
66+
[() => new Response(null, { status: 401 }), "Sign in to Cursor again to read account usage."],
67+
] as const) {
68+
let open = 0;
69+
const result = await readCursorAccountUsage(
70+
{ kind: "keychain" },
71+
0,
72+
1781000000000,
73+
(_url, init) => {
74+
const page: number = JSON.parse(String(init.body)).page;
75+
if (page === 1) return Promise.resolve(Response.json(fullPage));
76+
if (page === 2) return Promise.resolve(secondPage());
77+
if (page === 3) return Promise.reject(new Error("connection reset"));
78+
open++;
79+
return new Promise((_resolve, reject) => {
80+
init.signal?.addEventListener("abort", () => {
81+
open--;
82+
reject(init.signal?.reason);
83+
});
84+
});
85+
},
86+
async () => accessToken,
87+
);
88+
assert.strictEqual(result.error, error);
89+
assert.deepStrictEqual(result.records, []);
90+
assert.strictEqual(open, 0);
91+
}
92+
});
2293
});

‎apps/server/src/usage/cursorUsageReader.ts‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export async function readCursorAccountUsage(
106106
};
107107
}
108108
let accountKey: string | null = null;
109+
const cancel = new AbortController();
109110
try {
110111
const payload = accessToken.split(".")[1];
111112
const subject = object(
@@ -117,14 +118,14 @@ export async function readCursorAccountUsage(
117118
accountKey = accountHash(subject);
118119
if (!Number.isFinite(sinceMs) || !Number.isFinite(endDate) || sinceMs < 0 || sinceMs > endDate)
119120
throw new Error("Invalid date window");
120-
const deadline = AbortSignal.timeout(60_000);
121+
const deadline = AbortSignal.any([cancel.signal, AbortSignal.timeout(60_000)]);
121122
const records: UsageRecord[] = [];
122123
const occurrences = new Map<string, number>();
123124
const pages: unknown[][] = [];
124125
let completed = false;
125126
const pageSize = 1000;
126127
let total: number | undefined;
127-
for (let page = 1; ; page++) {
128+
const readPage = async (page: number) => {
128129
// A count can include overlapping page boundaries. Allow room to
129130
// reconcile them without imposing a fixed account-size limit.
130131
if (page > (total === undefined ? 1000 : Math.ceil(total / pageSize) * 2 + 1)) {
@@ -181,6 +182,21 @@ export async function readCursorAccountUsage(
181182
throw new Error("Inconsistent account usage page");
182183
}
183184
if (typeof count === "number") total = count;
185+
return events;
186+
};
187+
// Cursor caps a page at 1,000 events and takes about a second to answer one, so the pages
188+
// the first one's count implies are requested up to six ahead and consumed in page order.
189+
const ahead: ReturnType<typeof readPage>[] = [];
190+
for (let page = 1; ; page++) {
191+
while (ahead.length < Math.min(6, Math.ceil((total ?? 0) / pageSize) - page + 1)) {
192+
const pending = readPage(page + ahead.length);
193+
// Handled when its turn comes, or cancelled if the read ends first.
194+
pending.catch(() => undefined);
195+
ahead.push(pending);
196+
}
197+
const events = await (ahead.shift() ?? readPage(page));
198+
// A rejected login comes back as the finished result instead of a page.
199+
if (!Array.isArray(events)) return events;
184200
pages.push(events);
185201
if (events.length < pageSize) {
186202
completed = true;
@@ -268,5 +284,8 @@ export async function readCursorAccountUsage(
268284
missing: false,
269285
error: "Cursor account usage could not be read.",
270286
};
287+
} finally {
288+
// An early exit leaves the pages requested ahead of it in flight.
289+
cancel.abort();
271290
}
272291
}

0 commit comments

Comments
 (0)