| 1 | import type { |
| 2 | ProviderUsageLimitsUpdate, |
| 3 | ServerProviderUsageLimits, |
| 4 | ServerProviderUsageWindow, |
| 5 | } from "@t3tools/contracts"; |
| 6 | |
| 7 | const WINDOW_KIND_ORDER: Record<ServerProviderUsageWindow["kind"], number> = { |
| 8 | session: 0, |
| 9 | weekly: 1, |
| 10 | monthly: 2, |
| 11 | other: 3, |
| 12 | }; |
| 13 | |
| 14 | export function clampPercent(value: number): number { |
| 15 | return Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : 0; |
| 16 | } |
| 17 | |
| 18 | function sortWindows( |
| 19 | windows: Iterable<ServerProviderUsageWindow>, |
| 20 | ): ReadonlyArray<ServerProviderUsageWindow> { |
| 21 | return [...windows].toSorted( |
| 22 | (left, right) => |
| 23 | WINDOW_KIND_ORDER[left.kind] - WINDOW_KIND_ORDER[right.kind] || |
| 24 | left.id.localeCompare(right.id), |
| 25 | ); |
| 26 | } |
| 27 | |
| 28 | export function makeUsageLimits(input: { |
| 29 | readonly checkedAt: string; |
| 30 | readonly windows: Iterable<ServerProviderUsageWindow>; |
| 31 | }): ServerProviderUsageLimits { |
| 32 | return { checkedAt: input.checkedAt, windows: sortWindows(input.windows) }; |
| 33 | } |
| 34 | |
| 35 | export function makeUnavailableUsageLimits(input: { |
| 36 | readonly checkedAt: string; |
| 37 | readonly reason: "unsupported" | "probeFailed"; |
| 38 | readonly message?: string; |
| 39 | }): ServerProviderUsageLimits { |
| 40 | return { |
| 41 | checkedAt: input.checkedAt, |
| 42 | windows: [], |
| 43 | unavailable: { |
| 44 | reason: input.reason, |
| 45 | ...(input.message ? { message: input.message } : {}), |
| 46 | }, |
| 47 | }; |
| 48 | } |
| 49 | |
| 50 | /** |
| 51 | * Fold a sparse runtime update into the limits a provider currently |
| 52 | * publishes. Windows upsert by `id`; a window the update omits keeps its |
| 53 | * previous values, and a window that arrives without `resetsAt` or |
| 54 | * `windowDurationMins` keeps whatever the last probe resolved for it. An |
| 55 | * update with no windows leaves `previous` untouched. |
| 56 | * |
| 57 | * An `unsupported` snapshot stays unsupported: an account that cannot have |
| 58 | * subscription windows will not start reporting them mid-turn. |
| 59 | */ |
| 60 | export function applyUsageLimitsUpdate(input: { |
| 61 | readonly previous: ServerProviderUsageLimits | undefined; |
| 62 | readonly update: ProviderUsageLimitsUpdate; |
| 63 | readonly checkedAt: string; |
| 64 | }): ServerProviderUsageLimits | undefined { |
| 65 | const { previous, update } = input; |
| 66 | if (update.windows.length === 0 || previous?.unavailable?.reason === "unsupported") { |
| 67 | return previous; |
| 68 | } |
| 69 | const merged = new Map(previous?.windows.map((window) => [window.id, window] as const)); |
| 70 | // Codex sends this notification beside every token-usage tick, almost |
| 71 | // always with unchanged numbers. Decide "nothing changed" per window on |
| 72 | // the way through so the no-op case never allocates a new snapshot. |
| 73 | let changed = false; |
| 74 | for (const window of update.windows) { |
| 75 | const existing = merged.get(window.id); |
| 76 | const next: ServerProviderUsageWindow = { |
| 77 | ...window, |
| 78 | usedPercent: clampPercent(window.usedPercent), |
| 79 | ...(window.resetsAt === undefined && existing?.resetsAt !== undefined |
| 80 | ? { resetsAt: existing.resetsAt } |
| 81 | : {}), |
| 82 | ...(window.windowDurationMins === undefined && existing?.windowDurationMins !== undefined |
| 83 | ? { windowDurationMins: existing.windowDurationMins } |
| 84 | : {}), |
| 85 | }; |
| 86 | if (existing === undefined || !usageWindowEquals(existing, next)) { |
| 87 | merged.set(window.id, next); |
| 88 | changed = true; |
| 89 | } |
| 90 | } |
| 91 | if (!changed && previous !== undefined && previous.unavailable === undefined) { |
| 92 | return previous; |
| 93 | } |
| 94 | return { |
| 95 | ...makeUsageLimits({ checkedAt: input.checkedAt, windows: merged.values() }), |
| 96 | ...(previous?.resetCredits !== undefined ? { resetCredits: previous.resetCredits } : {}), |
| 97 | }; |
| 98 | } |
| 99 | |
| 100 | function usageWindowEquals(a: ServerProviderUsageWindow, b: ServerProviderUsageWindow): boolean { |
| 101 | return ( |
| 102 | a.id === b.id && |
| 103 | a.kind === b.kind && |
| 104 | a.label === b.label && |
| 105 | a.usedPercent === b.usedPercent && |
| 106 | a.resetsAt === b.resetsAt && |
| 107 | a.windowDurationMins === b.windowDurationMins |
| 108 | ); |
| 109 | } |
| 110 | |
| 111 | /** |
| 112 | * Choose what to publish after a status probe finishes. A probe that failed |
| 113 | * this time must not wipe bars a previous probe or a turn already |
| 114 | * established, so the last good snapshot stays; `unsupported` is |
| 115 | * authoritative and replaces them. |
| 116 | * |
| 117 | * A successful probe replaces the published windows outright, including any |
| 118 | * runtime update that landed while it was running. That is a deliberate |
| 119 | * trade-off: the Codex and Claude reads take a few seconds at most, the |
| 120 | * probe is the fresher full read in every case except that window, and the |
| 121 | * per-window epoch bookkeeping needed to reconcile the two was more code |
| 122 | * than the sub-second regression it prevented. The next runtime event |
| 123 | * corrects it. |
| 124 | */ |
| 125 | export function resolveUsageLimitsAfterProbe(input: { |
| 126 | readonly published: ServerProviderUsageLimits | undefined; |
| 127 | readonly probed: ServerProviderUsageLimits | undefined; |
| 128 | }): ServerProviderUsageLimits | undefined { |
| 129 | const { published, probed } = input; |
| 130 | if (probed?.unavailable?.reason === "probeFailed" && published && !published.unavailable) { |
| 131 | return published; |
| 132 | } |
| 133 | return probed; |
| 134 | } |