diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index 36702689be9e8..e372b1b9466b0 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -4304,7 +4304,7 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( yield* client.handleServerNotification("account/rateLimits/updated", (payload) => Effect.gen(function* () { - yield* Ref.update(rateLimitSnapshot, (previous) => + const previous = yield* Ref.getAndUpdate(rateLimitSnapshot, (previous) => mergeCodexRateLimits(previous, payload.rateLimits), ); const resetAt = codexUsageLimitResetAt(yield* Ref.get(rateLimitSnapshot)); @@ -4324,7 +4324,7 @@ export const makeCodexAdapterV2 = Effect.fn("makeCodexAdapterV2")(function* ( turnItem: updated, }); } - const update = codexRateLimitsToUpdate(payload.rateLimits); + const update = codexRateLimitsToUpdate(payload.rateLimits, previous); if (update && adapterOptions.onUsageLimits) { const checkedAt = DateTime.formatIso(yield* DateTime.now); yield* adapterOptions.onUsageLimits({ ...update, checkedAt }); diff --git a/apps/server/src/provider/codexUsageLimits.test.ts b/apps/server/src/provider/codexUsageLimits.test.ts index 934412bd3bb41..9c863f54c0867 100644 --- a/apps/server/src/provider/codexUsageLimits.test.ts +++ b/apps/server/src/provider/codexUsageLimits.test.ts @@ -128,6 +128,29 @@ describe("codexRateLimitsToLimits", () => { }); describe("codexRateLimitsToUpdate", () => { + it("does not republish cached windows over a newer account probe", () => { + const cached = { + primary: { usedPercent: 0, resetsAt: 1_784_000_000, windowDurationMins: 300 }, + secondary: { usedPercent: 20, resetsAt: 1_784_500_000, windowDurationMins: 10080 }, + }; + expect(codexRateLimitsToUpdate(cached)?.windows).toHaveLength(2); + expect(codexRateLimitsToUpdate(structuredClone(cached), cached)).toBeUndefined(); + + const changed = { ...cached, secondary: { ...cached.secondary, usedPercent: 21 } }; + expect(codexRateLimitsToUpdate(changed, cached)?.windows.map((window) => window.id)).toEqual([ + "secondary", + ]); + expect(codexRateLimitsToUpdate({ primary: cached.primary }, cached)).toBeUndefined(); + expect( + codexRateLimitsToUpdate({ primary: { ...cached.primary, resetsAt: 1_784_001_000 } }, cached) + ?.windows, + ).toHaveLength(1); + expect( + codexRateLimitsToUpdate({ primary: { ...cached.primary, usedPercent: 76 } }, cached) + ?.windows[0]?.usedPercent, + ).toBe(76); + }); + it("carries only the windows the notification names", () => { expect( codexRateLimitsToUpdate({ diff --git a/apps/server/src/provider/codexUsageLimits.ts b/apps/server/src/provider/codexUsageLimits.ts index f8718cdda03d9..d4f9c621119df 100644 --- a/apps/server/src/provider/codexUsageLimits.ts +++ b/apps/server/src/provider/codexUsageLimits.ts @@ -135,8 +135,20 @@ export function codexRateLimitsToLimits(input: { export function codexRateLimitsToUpdate( snapshot: CodexRateLimitSnapshot, + previous?: CodexRateLimitSnapshot, ): ProviderUsageLimitsUpdate | undefined { - const windows = codexRateLimitsToWindows(snapshot); + const previousWindows = previous ? codexRateLimitsToWindows(previous) : []; + // Token ticks repeat session-cached limits that may predate an account probe or reset. + const windows = codexRateLimitsToWindows(snapshot).filter((window) => { + const existing = previousWindows.find((candidate) => candidate.id === window.id); + return ( + !existing || + existing.kind !== window.kind || + existing.usedPercent !== window.usedPercent || + existing.resetsAt !== window.resetsAt || + existing.windowDurationMins !== window.windowDurationMins + ); + }); return windows.length > 0 ? { windows } : undefined; }