Skip to content

Instantly share code, notes, and snippets.

@ericboehs
Last active August 19, 2026 22:55
Show Gist options
  • Select an option

  • Save ericboehs/f9df5b27287021a94a57a1fd0811a16c to your computer and use it in GitHub Desktop.

Select an option

Save ericboehs/f9df5b27287021a94a57a1fd0811a16c to your computer and use it in GitHub Desktop.
Compact OpenAI Codex quota window status for pi and pi-footer
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");
},
});
}
@ericboehs

ericboehs commented Aug 19, 2026

Copy link
Copy Markdown
Author

Compact Codex Window Usage for pi

A small pi extension that displays OpenAI Codex quota usage in a compact form such as:

2.2/7D: 22%

This means 2.2 days have elapsed in the 7-day quota window and 22% has been used.

Companion extension: Compact Copilot Window Usage for pi shows the same style of window for GitHub Copilot.

Features

  • Only appears when the active model provider is openai-codex
  • Selects the longest reported quota window, normally the 7-day window
  • Adds up to three ! markers when usage is running ahead of elapsed-window pace
  • Publishes through ctx.ui.setStatus() for compatibility with pi-footer
  • Refreshes at session start, after model changes, and after completed agent turns
  • Reuses pi's resolved OpenAI Codex OAuth credential
  • Does not print, log, or persist access tokens
  • Provides /codex-window for a manual refresh

Installation

mkdir -p ~/.pi/agent/extensions
curl -fsSL \
  https://gist.githubusercontent.com/ericboehs/f9df5b27287021a94a57a1fd0811a16c/raw/codex-window-usage.ts \
  -o ~/.pi/agent/extensions/codex-window-usage.ts

Then run /reload inside pi.

Requirements

  • A recent pi installation
  • An authenticated openai-codex provider configured through pi
  • Node.js with built-in fetch and base64url Buffer support
  • pi-footer is optional; pi's normal extension status UI can also render the status

Usage

Select any model whose provider is openai-codex. The extension publishes:

<elapsed>/<window>: <used>%[pace warning]

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 ahead

Examples:

2.2/7D: 22%
0.7/7D: 20%!
0.7/7D: 26%!!
0.7/7D: 40%!!!

Force a refresh with:

/codex-window

When another provider is selected, the status is removed.

pi-footer

The extension publishes status key:

codex-window

pi-footer can show it automatically in the extension-status row. For explicit placement, add an Extension Status widget and select codex-window as its status key.

Configuration

The constants near the top of the file control the provider, status key, usage endpoint, and request timeout:

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;

Note

The extension uses a private ChatGPT backend endpoint. OpenAI may change its response format or availability without notice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment