Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 18 additions & 17 deletions src/codex/quota-auto-refresh.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mutatePersistedConfig } from "../config";
import { registerStateSweepAfterTick } from "../lib/state-store-sweeper";
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
import { normalizeResetAt } from "../providers/quota-wire";
import { providerCodexAccountMode } from "../providers/registry";
import type { OcxConfig } from "../types";
import { isSelectableCodexPoolAccount } from "./account-id";
Expand All @@ -18,6 +19,7 @@ export const FIVE_HOUR_WINDOW_SECONDS = 5 * 60 * 60;
const RETRY_MS = 5 * 60_000;
const CONCURRENCY = 4;

/** Completed/due markers use epoch milliseconds; persisted legacy markers may use seconds. */
export type CodexQuotaAutoRefreshWindows = { fiveHour?: number; weekly?: number };

export interface CodexQuotaAutoRefreshStatus {
Expand All @@ -41,11 +43,6 @@ let inFlight: Promise<void> | null = null;
const completedByAccount = new Map<string, CodexQuotaAutoRefreshWindows>();
const retryAfterByAccount = new Map<string, number>();

function resetAtMs(resetAt: number): number {
// WHAM reports seconds; quota parsed from response headers may already be milliseconds.
return resetAt < 100_000_000_000 ? resetAt * 1000 : resetAt;
}

export function codexQuotaAutoRefreshStatus(
config: OcxConfig,
accountId: string,
Expand All @@ -71,20 +68,22 @@ export function dueCodexQuotaAutoRefreshWindows(
if (!quota) return null;
const saved = config.codexQuotaAutoRefresh?.[accountId];
const due: CodexQuotaAutoRefreshWindows = {};
const shortResetAt = normalizeResetAt(quota.shortResetAt);
const weeklyResetAt = normalizeResetAt(quota.weeklyResetAt);
if (saved?.fiveHour === true
&& quota.shortWindowSeconds === FIVE_HOUR_WINDOW_SECONDS
&& typeof quota.shortResetAt === "number"
&& resetAtMs(quota.shortResetAt) <= now
&& saved.lastFiveHourResetAt !== quota.shortResetAt
&& completed?.fiveHour !== quota.shortResetAt) {
due.fiveHour = quota.shortResetAt;
&& shortResetAt !== undefined
&& shortResetAt <= now
&& normalizeResetAt(saved.lastFiveHourResetAt) !== shortResetAt
&& normalizeResetAt(completed?.fiveHour) !== shortResetAt) {
due.fiveHour = shortResetAt;
}
if (saved?.weekly === true
&& typeof quota.weeklyResetAt === "number"
&& resetAtMs(quota.weeklyResetAt) <= now
&& saved.lastWeeklyResetAt !== quota.weeklyResetAt
&& completed?.weekly !== quota.weeklyResetAt) {
due.weekly = quota.weeklyResetAt;
&& weeklyResetAt !== undefined
&& weeklyResetAt <= now
&& normalizeResetAt(saved.lastWeeklyResetAt) !== weeklyResetAt
&& normalizeResetAt(completed?.weekly) !== weeklyResetAt) {
due.weekly = weeklyResetAt;
}
return due.fiveHour === undefined && due.weekly === undefined ? null : due;
}
Expand Down Expand Up @@ -139,8 +138,10 @@ function retryPendingMarkers(
for (const [accountId, completed] of completedByAccount) {
const saved = config.codexQuotaAutoRefresh?.[accountId];
if (!saved) continue;
if ((completed.fiveHour === undefined || saved.lastFiveHourResetAt === completed.fiveHour)
&& (completed.weekly === undefined || saved.lastWeeklyResetAt === completed.weekly)) continue;
if ((completed.fiveHour === undefined
|| normalizeResetAt(saved.lastFiveHourResetAt) === normalizeResetAt(completed.fiveHour))
&& (completed.weekly === undefined
|| normalizeResetAt(saved.lastWeeklyResetAt) === normalizeResetAt(completed.weekly))) continue;
persist(config, accountId, completed);
}
}
Expand Down
88 changes: 80 additions & 8 deletions tests/codex-integration/codex-quota-auto-refresh.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ describe("Codex quota window auto refresh", () => {
test("accepts reset timestamps in seconds or milliseconds and ignores completed windows", () => {
const cfg = config();
expect(dueCodexQuotaAutoRefreshWindows(cfg, "pool-a", quota(), NOW)).toEqual({
fiveHour: RESET_SECONDS,
weekly: RESET_SECONDS,
fiveHour: NOW,
weekly: NOW,
});
expect(dueCodexQuotaAutoRefreshWindows(
cfg,
Expand All @@ -134,6 +134,62 @@ describe("Codex quota window auto refresh", () => {
)).toBeNull();
});

test.each([
[RESET_SECONDS, NOW],
[NOW, RESET_SECONDS],
])("deduplicates saved and completed markers across units (%i -> %i)", (marker, observed) => {
const cfg = config();
const snapshot = quota({ shortResetAt: observed, weeklyResetAt: observed });
expect(dueCodexQuotaAutoRefreshWindows(cfg, "pool-a", snapshot, NOW, {
fiveHour: marker,
weekly: marker,
})).toBeNull();
cfg.codexQuotaAutoRefresh = {
"pool-a": { fiveHour: true, weekly: true, lastFiveHourResetAt: marker, lastWeeklyResetAt: marker },
};
expect(dueCodexQuotaAutoRefreshWindows(cfg, "pool-a", snapshot, NOW)).toBeNull();
expect(dueCodexQuotaAutoRefreshWindows(cfg, "pool-a", quota({
shortResetAt: RESET_SECONDS + 1,
weeklyResetAt: NOW + 1000,
}), NOW)).toBeNull();
expect(dueCodexQuotaAutoRefreshWindows(cfg, "pool-a", quota({
shortResetAt: RESET_SECONDS + 1,
weeklyResetAt: NOW + 1000,
}), NOW + 1000)).toEqual({ fiveHour: NOW + 1000, weekly: NOW + 1000 });
});

test.each([
[RESET_SECONDS, NOW],
[NOW, RESET_SECONDS],
])("persists canonical markers and avoids warmup after a unit change and restart (%i -> %i)", async (first, second) => {
const cfg = config();
writeFileSync(join(testHome, "config.json"), JSON.stringify(cfg));
let observed = first;
let warmups = 0;
const deps = {
getQuota: (id: string) => id === "pool-a"
? quota({ shortResetAt: observed, weeklyResetAt: observed }) : null,
warmAccount: async () => { warmups += 1; },
};
await runCodexQuotaAutoRefresh(cfg, NOW, deps);
expect(readConfigDiagnostics().config.codexQuotaAutoRefresh?.["pool-a"]).toMatchObject({
lastFiveHourResetAt: NOW,
lastWeeklyResetAt: NOW,
});
observed = second;
await runCodexQuotaAutoRefresh(cfg, NOW + 1, deps);
resetCodexQuotaAutoRefreshForTests();
await runCodexQuotaAutoRefresh(loadConfig(), NOW + 2, deps);
expect(warmups).toBe(1);
observed = RESET_SECONDS + 1;
await runCodexQuotaAutoRefresh(loadConfig(), NOW + 1000, deps);
expect(warmups).toBe(2);
expect(readConfigDiagnostics().config.codexQuotaAutoRefresh?.["pool-a"]).toMatchObject({
lastFiveHourResetAt: NOW + 1000,
lastWeeklyResetAt: NOW + 1000,
});
});

test("coalesces simultaneous windows into one warmup and persists both markers", async () => {
const cfg = config();
const warmed: string[] = [];
Expand All @@ -149,8 +205,8 @@ describe("Codex quota window auto refresh", () => {
});
expect(warmed).toEqual(["pool-a"]);
expect(cfg.codexQuotaAutoRefresh?.["pool-a"]).toMatchObject({
lastFiveHourResetAt: RESET_SECONDS,
lastWeeklyResetAt: RESET_SECONDS,
lastFiveHourResetAt: NOW,
lastWeeklyResetAt: NOW,
});
});

Expand All @@ -170,20 +226,36 @@ describe("Codex quota window auto refresh", () => {
const cfg = config();
let warmups = 0;
let writes = 0;
let observed = RESET_SECONDS;
const persist = (target: OcxConfig, id: string, completed: CodexQuotaAutoRefreshWindows) => {
writes += 1;
return writes > 1 ? recordMarkers(target, id, completed) : false;
return writes > 2 ? recordMarkers(target, id, completed) : false;
};
const deps = {
getQuota: (id: string) => id === "pool-a" ? quota() : null,
getQuota: (id: string) => id === "pool-a"
? quota({ shortResetAt: observed, weeklyResetAt: observed }) : null,
warmAccount: async () => { warmups += 1; },
persistCompleted: persist,
};
await runCodexQuotaAutoRefresh(cfg, NOW, deps);
observed = NOW;
await runCodexQuotaAutoRefresh(cfg, NOW + 1, deps);
expect(warmups).toBe(1);
expect(writes).toBe(2);
expect(cfg.codexQuotaAutoRefresh?.["pool-a"]?.lastWeeklyResetAt).toBe(RESET_SECONDS);
await runCodexQuotaAutoRefresh(cfg, NOW + 2, deps);
expect(warmups).toBe(1);
expect(writes).toBe(3);
expect(cfg.codexQuotaAutoRefresh?.["pool-a"]).toMatchObject({
lastFiveHourResetAt: NOW,
lastWeeklyResetAt: NOW,
});
// Equivalent legacy markers must not cause another persistence attempt either.
cfg.codexQuotaAutoRefresh = {
"pool-a": { fiveHour: true, weekly: true, lastFiveHourResetAt: RESET_SECONDS, lastWeeklyResetAt: RESET_SECONDS },
};
await runCodexQuotaAutoRefresh(cfg, NOW + 3, deps);
expect(warmups).toBe(1);
expect(writes).toBe(3);
});

test("backs a failed main-account claim or warmup off for five minutes", async () => {
Expand All @@ -202,7 +274,7 @@ describe("Codex quota window auto refresh", () => {
await runCodexQuotaAutoRefresh(cfg, NOW + 5 * 60_000 - 1, deps);
await runCodexQuotaAutoRefresh(cfg, NOW + 5 * 60_000, deps);
expect(attempts).toBe(2);
expect(cfg.codexQuotaAutoRefresh?.__main__?.lastFiveHourResetAt).toBe(RESET_SECONDS);
expect(cfg.codexQuotaAutoRefresh?.__main__?.lastFiveHourResetAt).toBe(NOW);
});

test("settings route persists supported toggles and rejects unavailable windows", async () => {
Expand Down
Loading