1import type {
2 ProviderUsageLimitsUpdate,
3 ServerProviderUsageLimits,
4 ServerProviderUsageWindow,
5} from "@t3tools/contracts";
6
7const WINDOW_KIND_ORDER: Record<ServerProviderUsageWindow["kind"], number> = {
8 session: 0,
9 weekly: 1,
10 monthly: 2,
11 other: 3,
12};
13
14export function clampPercent(value: number): number {
15 return Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : 0;
16}
17
18function 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
28export 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
35export 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 */
60export 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
100function 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 */
125export 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}