Last active
August 19, 2026 22:55
-
-
Save ericboehs/f9df5b27287021a94a57a1fd0811a16c to your computer and use it in GitHub Desktop.
Compact OpenAI Codex quota window status for pi and pi-footer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { | |
| ExtensionAPI, | |
| ExtensionContext, | |
| } from "@earendil-works/pi-coding-agent"; | |
| const PROVIDER = "openai-codex"; | |
| const STATUS_KEY = "codex-window"; | |
| const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage"; | |
| const REQUEST_TIMEOUT_MS = 10_000; | |
| interface UsageWindow { | |
| used_percent?: number; | |
| reset_at?: number; | |
| reset_after_seconds?: number; | |
| limit_window_seconds?: number; | |
| window_minutes?: number; | |
| } | |
| interface UsageResponse { | |
| rate_limit?: { | |
| primary_window?: UsageWindow | null; | |
| secondary_window?: UsageWindow | null; | |
| }; | |
| } | |
| let requestGeneration = 0; | |
| let lastValue: string | undefined; | |
| function isCodex(ctx: ExtensionContext): boolean { | |
| return ctx.model?.provider === PROVIDER; | |
| } | |
| function setStatus(ctx: ExtensionContext, value?: string): void { | |
| ctx.ui.setStatus(STATUS_KEY, value); | |
| } | |
| function accountIdFromJwt(token: string): string | undefined { | |
| try { | |
| const payloadPart = token.split(".")[1]; | |
| if (!payloadPart) return undefined; | |
| const payload = JSON.parse( | |
| Buffer.from(payloadPart, "base64url").toString("utf8"), | |
| ) as Record<string, unknown>; | |
| const auth = payload["https://api.openai.com/auth"]; | |
| if (!auth || typeof auth !== "object") return undefined; | |
| const accountId = (auth as Record<string, unknown>).chatgpt_account_id; | |
| return typeof accountId === "string" ? accountId : undefined; | |
| } catch { | |
| return undefined; | |
| } | |
| } | |
| function windowSeconds(window: UsageWindow): number | undefined { | |
| if ( | |
| typeof window.limit_window_seconds === "number" && | |
| Number.isFinite(window.limit_window_seconds) && | |
| window.limit_window_seconds > 0 | |
| ) { | |
| return window.limit_window_seconds; | |
| } | |
| if ( | |
| typeof window.window_minutes === "number" && | |
| Number.isFinite(window.window_minutes) && | |
| window.window_minutes > 0 | |
| ) { | |
| return window.window_minutes * 60; | |
| } | |
| return undefined; | |
| } | |
| function selectWindow(usage: UsageResponse): UsageWindow | undefined { | |
| const windows = [ | |
| usage.rate_limit?.primary_window, | |
| usage.rate_limit?.secondary_window, | |
| ].filter((window): window is UsageWindow => { | |
| return ( | |
| window != null && | |
| typeof window.used_percent === "number" && | |
| windowSeconds(window) !== undefined | |
| ); | |
| }); | |
| // Prefer the longest reported window (normally the 7-day quota). | |
| return windows.sort( | |
| (left, right) => (windowSeconds(right) ?? 0) - (windowSeconds(left) ?? 0), | |
| )[0]; | |
| } | |
| function formatNumber(value: number): string { | |
| const rounded = Math.round(value * 10) / 10; | |
| return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1); | |
| } | |
| function formatWindow(window: UsageWindow, nowMs = Date.now()): string | undefined { | |
| const totalSeconds = windowSeconds(window); | |
| const usedPercent = window.used_percent; | |
| if (totalSeconds === undefined || typeof usedPercent !== "number") return undefined; | |
| let remainingSeconds: number | undefined; | |
| if (typeof window.reset_at === "number" && Number.isFinite(window.reset_at)) { | |
| const resetAtSeconds = | |
| window.reset_at > 10_000_000_000 ? window.reset_at / 1000 : window.reset_at; | |
| remainingSeconds = resetAtSeconds - nowMs / 1000; | |
| } else if ( | |
| typeof window.reset_after_seconds === "number" && | |
| Number.isFinite(window.reset_after_seconds) | |
| ) { | |
| remainingSeconds = window.reset_after_seconds; | |
| } | |
| if (remainingSeconds === undefined) return undefined; | |
| const elapsedSeconds = Math.max( | |
| 0, | |
| Math.min(totalSeconds, totalSeconds - remainingSeconds), | |
| ); | |
| const percent = Math.max(0, Math.min(100, usedPercent)); | |
| const expectedPercent = (elapsedSeconds / totalSeconds) * 100; | |
| const pointsAhead = percent - expectedPercent; | |
| const warningCount = pointsAhead > 20 ? 3 : pointsAhead > 10 ? 2 : pointsAhead > 5 ? 1 : 0; | |
| const warning = "!".repeat(warningCount); | |
| if (totalSeconds >= 86_400) { | |
| const elapsedDays = elapsedSeconds / 86_400; | |
| const totalDays = totalSeconds / 86_400; | |
| return `${formatNumber(elapsedDays)}/${formatNumber(totalDays)}D: ${formatNumber(percent)}%${warning}`; | |
| } | |
| const elapsedHours = elapsedSeconds / 3_600; | |
| const totalHours = totalSeconds / 3_600; | |
| return `${formatNumber(elapsedHours)}/${formatNumber(totalHours)}H: ${formatNumber(percent)}%${warning}`; | |
| } | |
| async function fetchValue(ctx: ExtensionContext): Promise<string> { | |
| const resolved = await ctx.modelRegistry.getProviderAuth(PROVIDER); | |
| const accessToken = resolved?.auth.apiKey; | |
| if (!accessToken) throw new Error("OpenAI Codex authentication is unavailable"); | |
| const accountId = accountIdFromJwt(accessToken); | |
| const controller = new AbortController(); | |
| const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); | |
| try { | |
| const response = await fetch(USAGE_URL, { | |
| headers: { | |
| authorization: `Bearer ${accessToken}`, | |
| ...(accountId ? { "chatgpt-account-id": accountId } : {}), | |
| accept: "application/json", | |
| "user-agent": "pi-codex-window/1.0", | |
| }, | |
| signal: controller.signal, | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`usage request failed (${response.status})`); | |
| } | |
| const usage = (await response.json()) as UsageResponse; | |
| const window = selectWindow(usage); | |
| const value = window ? formatWindow(window) : undefined; | |
| if (!value) throw new Error("no timed usage window was returned"); | |
| return value; | |
| } finally { | |
| clearTimeout(timeout); | |
| } | |
| } | |
| async function refresh(ctx: ExtensionContext): Promise<string | undefined> { | |
| const generation = ++requestGeneration; | |
| if (!isCodex(ctx)) { | |
| setStatus(ctx, undefined); | |
| return undefined; | |
| } | |
| if (lastValue) setStatus(ctx, lastValue); | |
| try { | |
| const value = await fetchValue(ctx); | |
| if (generation !== requestGeneration || !isCodex(ctx)) return undefined; | |
| lastValue = value; | |
| setStatus(ctx, value); | |
| return value; | |
| } catch { | |
| if (generation === requestGeneration && !lastValue) { | |
| setStatus(ctx, undefined); | |
| } | |
| return undefined; | |
| } | |
| } | |
| export default function codexWindowUsage(pi: ExtensionAPI): void { | |
| pi.on("session_start", async (_event, ctx) => { | |
| await refresh(ctx); | |
| }); | |
| pi.on("model_select", async (_event, ctx) => { | |
| await refresh(ctx); | |
| }); | |
| pi.on("agent_settled", async (_event, ctx) => { | |
| await refresh(ctx); | |
| }); | |
| pi.on("session_shutdown", async (_event, ctx) => { | |
| requestGeneration += 1; | |
| setStatus(ctx, undefined); | |
| }); | |
| pi.registerCommand("codex-window", { | |
| description: "Refresh the compact Codex usage window", | |
| handler: async (_args, ctx) => { | |
| if (!isCodex(ctx)) { | |
| setStatus(ctx, undefined); | |
| ctx.ui.notify("Codex usage is only shown for openai-codex models", "info"); | |
| return; | |
| } | |
| const value = await refresh(ctx); | |
| ctx.ui.notify(value ?? "Codex usage is unavailable", value ? "info" : "error"); | |
| }, | |
| }); | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Compact Codex Window Usage for pi
A small pi extension that displays OpenAI Codex quota usage in a compact form such as:
This means 2.2 days have elapsed in the 7-day quota window and 22% has been used.
Features
openai-codex!markers when usage is running ahead of elapsed-window pacectx.ui.setStatus()for compatibility withpi-footer/codex-windowfor a manual refreshInstallation
Then run
/reloadinside pi.Requirements
openai-codexprovider configured through pifetchand base64urlBuffersupportpi-footeris optional; pi's normal extension status UI can also render the statusUsage
Select any model whose provider is
openai-codex. The extension publishes:The expected usage percentage is calculated from elapsed time. Warning levels show how many percentage points usage is ahead of that pace:
!: more than 5 points ahead!!: more than 10 points ahead!!!: more than 20 points aheadExamples:
Force a refresh with:
When another provider is selected, the status is removed.
pi-footer
The extension publishes status key:
pi-footercan show it automatically in the extension-status row. For explicit placement, add an Extension Status widget and selectcodex-windowas its status key.Configuration
The constants near the top of the file control the provider, status key, usage endpoint, and request timeout:
Note
The extension uses a private ChatGPT backend endpoint. OpenAI may change its response format or availability without notice.