Skip to content
Open
51 changes: 51 additions & 0 deletions packages/shared/src/usageLimits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,57 @@ describe("pooled account columns", () => {
expect(accounts).toEqual(snapshot);
});

it("treats OpenCode Go session and weekly quota as spent while the monthly quota is", () => {
const go = (key: string, rolling: number, weeklyUsed: number, monthly: number) => ({
...account(key, [
{ ...window, id: "go_rolling", usedPercent: rolling },
{ ...weekly, id: "go_weekly", usedPercent: weeklyUsed },
{
...window,
id: "go_monthly",
kind: "monthly",
usedPercent: monthly,
windowDurationMins: undefined,
resetsAt: "2026-09-20T12:00:00.000Z",
},
]),
driver: ProviderDriverKind.make("opencode"),
});
const [pool] = collectLimitPools([go("a", 1, 24, 20), go("b", 0, 0, 100)], now);
expect
.soft(pool!.windows.map((row) => row.columns[1]?.window?.usedPercent))
.toEqual([100, 100, 100]);
expect(pool!.windows.map((row) => row.remainingPercent)).toEqual([50, 38, 40]);
});

it("holds an OpenCode Go session spent until the last spent longer quota resets", () => {
const monthlyReset = "2026-09-20T12:00:00.000Z";
const weeklyLater = "2026-09-25T12:00:00.000Z";
const go = (key: string, monthlyUsed: number, weeklyReset = "2026-09-05T12:00:00.000Z") => ({
...account(key, [
{ ...window, id: "go_rolling", usedPercent: 0 },
{ ...weekly, id: "go_weekly", usedPercent: 100, resetsAt: weeklyReset },
{
...window,
id: "go_monthly",
kind: "monthly",
usedPercent: monthlyUsed,
windowDurationMins: undefined,
resetsAt: monthlyReset,
},
]),
driver: ProviderDriverKind.make("opencode"),
});
const sessions = [go("weekly", 40), go("both", 100), go("weekly-later", 100, weeklyLater)].map(
(account) => collectLimitPools([account], now)[0]!.windows[0]!,
);
expect.soft(sessions.map((session) => session.remainingPercent)).toEqual([0, 0, 0]);
expect(sessions.slice(1).map((session) => session.resets[0]?.at)).toEqual([
Date.parse(monthlyReset),
Date.parse(weeklyLater),
]);
});

it("keeps session columns across rows with opposite reset and usage orders", () => {
const accounts = [
account("a", [
Expand Down
61 changes: 41 additions & 20 deletions packages/shared/src/usageLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,45 +511,66 @@ function poolKey(driver: ServerProvider["driver"], window: ServerProviderUsageWi
return driver === "codex" ? window.kind : `${window.kind}:${window.id}`;
}

// A spent blocker window leaves no usable quota in the windows it blocks.
const BLOCKS = new Map<
string,
(blocker: ServerProviderUsageWindow, reported: ServerProviderUsageWindow) => boolean
>([
["claudeAgent", (blocker, reported) => reported.kind === "session" && blocker.id === "seven_day"],
["codex", (blocker, reported) => reported.kind === "session" && blocker.kind === "weekly"],
[
"opencode",
(blocker, reported) =>
blocker.id === "go_monthly"
? reported.id === "go_rolling" || reported.id === "go_weekly"
: blocker.id === "go_weekly" && reported.id === "go_rolling",
],
]);

function poolMember(account: LimitAccount, reported: ServerProviderUsageWindow, now: number) {
const spentWeekly =
reported.kind === "session"
? account.limits.windows.find(
(window) =>
window.usedPercent >= 100 &&
(account.driver === "claudeAgent"
? window.id === "seven_day"
: account.driver === "codex" && window.kind === "weekly"),
const blocks = BLOCKS.get(account.driver);
// With several spent blockers, quota returns only when the last of them resets.
const blocker = blocks
? account.limits.windows
.filter((window) => window.usedPercent >= 100 && blocks(window, reported))
.reduce<ServerProviderUsageWindow | undefined>(
(latest, window) =>
latest &&
(resetMillis(latest) ?? Number.POSITIVE_INFINITY) >=
(resetMillis(window) ?? Number.POSITIVE_INFINITY)
? latest
: window,
undefined,
)
: undefined;
if (!spentWeekly) return { account, window: reported, refillPercent: reported.usedPercent };
: undefined;
if (!blocker) return { account, window: reported, refillPercent: reported.usedPercent };

const weeklyReset = resetMillis(spentWeekly);
const sessionReset = resetMillis(reported);
const blockerReset = resetMillis(blocker);
const reportedReset = resetMillis(reported);
const cutoff = Math.max(now, Date.parse(account.limits.checkedAt));
let resetsAt: string | undefined;
let refillPercent = 0;
// A stale or missing clock cannot tell us when blocked quota becomes usable.
if (
weeklyReset !== null &&
sessionReset !== null &&
weeklyReset > cutoff &&
sessionReset > cutoff
blockerReset !== null &&
reportedReset !== null &&
blockerReset > cutoff &&
reportedReset > cutoff
) {
if (sessionReset <= weeklyReset) {
resetsAt = spentWeekly.resetsAt;
if (reportedReset <= blockerReset) {
resetsAt = blocker.resetsAt;
refillPercent = 100;
} else if (reported.usedPercent >= 100) {
resetsAt = reported.resetsAt;
refillPercent = 100;
} else {
resetsAt = spentWeekly.resetsAt;
resetsAt = blocker.resetsAt;
refillPercent = 100 - Math.max(0, Math.min(100, reported.usedPercent));
}
}
return {
account,
// Suppression is not session spending, so it has no session pace clock.
// Suppression is not spending in the reported window, so it has no pace clock.
window: { ...reported, usedPercent: 100, resetsAt, windowDurationMins: undefined },
refillPercent,
};
Expand Down
Loading