feat(codex): report account usage #3

Merged
eleith merged 2 commits from feat/codex-usage into main 2026-10-31 14:18:47 +00:00
9 changed files with 1393 additions and 7 deletions

No files matched your search

+336
View File
@@ -0,0 +1,336 @@
import type { Api, AuthResult, Model } from "@earendil-works/pi-ai";
import { afterEach, expect, it, vi } from "vitest";
import { fetchCodexUsage, type CodexAccountRegistry } from "./client.ts";
const BASE_URL = "https://chatgpt.com/backend-api";
const model: Model<Api> = {
id: "gpt-6-sol",
name: "Sol",
provider: "openai-codex",
api: "openai-codex-responses",
baseUrl: BASE_URL,
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 272_000,
maxTokens: 128_000,
};
function token(account = "test-account"): string {
const payload = { "https://api.openai.com/auth": { chatgpt_account_id: account } };
return `e30.${Buffer.from(JSON.stringify(payload)).toString("base64url")}.fake-signature`;
}
function json(value: unknown): Response {
return new Response(JSON.stringify(value), { headers: { "Content-Type": "application/json" } });
}
function deferred<T>() {
let resolve!: (value: T) => void;
const promise = new Promise<T>((done) => {
resolve = done;
});
return { promise, resolve };
}
function fixture() {
const auth: AuthResult = { source: "OAuth", auth: { apiKey: token() } };
const provider = { baseUrl: BASE_URL, getModels: () => [model] };
const registry = {
getProvider: vi.fn<CodexAccountRegistry["getProvider"]>(() => provider),
getProviderAuth: vi.fn<CodexAccountRegistry["getProviderAuth"]>(async () => auth),
isUsingOAuth: vi.fn<CodexAccountRegistry["isUsingOAuth"]>(() => true),
};
const fetcher = vi.fn<typeof fetch>(async () => json({ plan_type: "plus" }));
return { registry, provider, auth, fetcher };
}
afterEach(() => {
vi.useRealTimers();
});
it("uses Pi's existing Codex auth for the fixed account endpoint", async () => {
const f = fixture();
f.auth.auth.headers = { "X-Unrelated-Secret": "must-not-forward" };
expect(await fetchCodexUsage(f.registry, undefined, f.fetcher)).toEqual({ plan_type: "plus" });
expect(f.registry.getProviderAuth).toHaveBeenCalledExactlyOnceWith("openai-codex");
expect(f.fetcher).toHaveBeenCalledExactlyOnceWith(
`${BASE_URL}/wham/usage`,
expect.objectContaining({ method: "GET", redirect: "error", signal: expect.any(AbortSignal) }),
);
const headers = new Headers(f.fetcher.mock.calls[0][1]?.headers);
expect(Object.fromEntries(headers)).toEqual({
accept: "application/json",
authorization: `Bearer ${f.auth.auth.apiKey}`,
"chatgpt-account-id": "test-account",
});
});
it("derives the account from the token like Pi's legacy Codex transport", async () => {
const f = fixture();
f.auth.auth.headers = { "ChatGPT-Account-Id": "selected-account" };
await fetchCodexUsage(f.registry, undefined, f.fetcher);
const headers = new Headers(f.fetcher.mock.calls[0][1]?.headers);
expect(headers.get("chatgpt-account-id")).toBe("test-account");
});
it("resolves auth afresh rather than retaining another account's credentials", async () => {
const f = fixture();
f.registry.getProviderAuth
.mockResolvedValueOnce({ source: "OAuth", auth: { apiKey: token("first") } })
.mockResolvedValueOnce({ source: "OAuth", auth: { apiKey: token("second") } });
await fetchCodexUsage(f.registry, undefined, f.fetcher);
await fetchCodexUsage(f.registry, undefined, f.fetcher);
const accounts = f.fetcher.mock.calls.map(([, init]) =>
new Headers(init?.headers).get("chatgpt-account-id"),
);
expect(accounts).toEqual(["first", "second"]);
});
it.each(["provider endpoint", "model endpoint", "auth endpoint"] as const)(
"does not send credentials for an unsupported %s configuration",
async (configuration) => {
const f = fixture();
if (configuration === "provider endpoint") f.provider.baseUrl = "https://gateway.invalid";
if (configuration === "model endpoint") {
f.provider.getModels = () => [{ ...model, baseUrl: "https://gateway.invalid" }];
}
if (configuration === "auth endpoint") f.auth.auth.baseUrl = "https://gateway.invalid";
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "unsupported",
});
expect(f.fetcher).not.toHaveBeenCalled();
},
);
it("requires Pi's Codex OAuth login rather than API-key authentication", async () => {
const f = fixture();
f.registry.isUsingOAuth.mockReturnValue(false);
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "auth",
});
expect(f.fetcher).not.toHaveBeenCalled();
});
it.each([
{ label: "opaque", key: "opaque-api-key" },
{ label: "token-shaped", key: token() },
])("rejects resolved $label API keys even if Pi's OAuth snapshot is stale", async ({ key }) => {
const f = fixture();
f.auth.source = "models.json";
f.auth.auth.apiKey = key;
f.auth.auth.headers = { "ChatGPT-Account-Id": "stale-account" };
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "auth",
});
expect(f.fetcher).not.toHaveBeenCalled();
});
it("rechecks the endpoint after Pi finishes resolving credentials", async () => {
const f = fixture();
const auth = deferred<AuthResult>();
f.registry.getProviderAuth.mockReturnValue(auth.promise);
const request = fetchCodexUsage(f.registry, undefined, f.fetcher);
f.provider.baseUrl = "https://gateway.invalid";
auth.resolve(f.auth);
await expect(request).rejects.toMatchObject({ code: "unsupported" });
expect(f.fetcher).not.toHaveBeenCalled();
});
it("does not fetch or expose Pi's raw authentication errors", async () => {
const f = fixture();
f.registry.getProviderAuth.mockRejectedValue(new Error("private token refresh details"));
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "auth",
message: "Codex account access is unavailable. Check your Pi Codex login.",
});
expect(f.fetcher).not.toHaveBeenCalled();
});
it.each([
undefined,
{ source: "OAuth", auth: {} },
{ source: "OAuth", auth: { apiKey: "not-a-codex-token" } },
])("rejects missing or unusable auth without a request: %j", async (auth) => {
const f = fixture();
f.registry.getProviderAuth.mockResolvedValue(auth);
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "auth",
});
expect(f.fetcher).not.toHaveBeenCalled();
});
it("does not expose transport error details", async () => {
const f = fixture();
f.fetcher.mockRejectedValue(new Error("Authorization: Bearer secret"));
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "network",
message: "Could not reach the Codex account service.",
});
});
it.each([401, 403])("reports HTTP %s without exposing the server's error body", async (status) => {
const f = fixture();
const cancel = vi.fn();
const body = new ReadableStream<Uint8Array>({ cancel });
f.fetcher.mockResolvedValue(new Response(body, { status }));
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "auth",
message: "Codex account access is unavailable. Check your Pi Codex login.",
});
expect(cancel).toHaveBeenCalledOnce();
});
it("returns retry-after information without automatically retrying", async () => {
const f = fixture();
f.fetcher.mockResolvedValue(
new Response("private error", { status: 429, headers: { "Retry-After": "60" } }),
);
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "http",
message: "Codex account service is rate limited.",
retryAfterMs: 60_000,
});
expect(f.fetcher).toHaveBeenCalledOnce();
});
it("does not follow or display a redirected endpoint", async () => {
const f = fixture();
f.fetcher.mockResolvedValue(
new Response(null, { status: 302, headers: { Location: "https://other.invalid/private" } }),
);
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "http",
message: "Codex account request failed (HTTP 302).",
});
expect(f.fetcher.mock.calls[0][1]?.redirect).toBe("error");
expect(f.fetcher).toHaveBeenCalledOnce();
});
it("cancels a chunked response before retaining more than the byte limit", async () => {
const f = fixture();
const cancel = vi.fn();
const body = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new Uint8Array(1024 * 1024));
controller.enqueue(new Uint8Array(1));
},
cancel,
});
f.fetcher.mockResolvedValue(new Response(body));
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "response",
message: "Codex account response was too large.",
});
expect(cancel).toHaveBeenCalledOnce();
});
it("reports invalid JSON without repeating returned content", async () => {
const f = fixture();
f.fetcher.mockResolvedValue(new Response("<html>private server details</html>"));
await expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "response",
message: "Codex account service returned invalid JSON.",
});
});
it("cancels during handoff of an already-buffered response", async () => {
const f = fixture();
const started = deferred<void>();
const incoming = deferred<Response>();
const controller = new AbortController();
const response = json({ plan_type: "plus" });
const discard = vi.spyOn(response.body!, "cancel");
f.fetcher.mockImplementation(() => {
started.resolve();
return incoming.promise;
});
const request = fetchCodexUsage(f.registry, controller.signal, f.fetcher);
await started.promise;
incoming.resolve(response);
queueMicrotask(() => controller.abort());
await expect(request).rejects.toMatchObject({ code: "cancelled" });
expect(discard).toHaveBeenCalledOnce();
});
it("cancels before auth resolution if the caller is already aborted", async () => {
const f = fixture();
const controller = new AbortController();
controller.abort();
await expect(fetchCodexUsage(f.registry, controller.signal, f.fetcher)).rejects.toMatchObject({
code: "cancelled",
});
expect(f.registry.getProviderAuth).not.toHaveBeenCalled();
expect(f.fetcher).not.toHaveBeenCalled();
});
it("ignores auth that resolves after the caller cancels", async () => {
const f = fixture();
const auth = deferred<AuthResult>();
f.registry.getProviderAuth.mockReturnValue(auth.promise);
const controller = new AbortController();
const request = fetchCodexUsage(f.registry, controller.signal, f.fetcher);
controller.abort();
await expect(request).rejects.toMatchObject({ code: "cancelled" });
auth.resolve(f.auth);
await auth.promise;
expect(f.fetcher).not.toHaveBeenCalled();
});
it("discards a response arriving after cancellation even if fetch ignores the signal", async () => {
const f = fixture();
const started = deferred<void>();
const response = deferred<Response>();
const discarded = deferred<void>();
f.fetcher.mockImplementation(() => {
started.resolve();
return response.promise;
});
const controller = new AbortController();
const request = fetchCodexUsage(f.registry, controller.signal, f.fetcher);
await started.promise;
controller.abort();
await expect(request).rejects.toMatchObject({ code: "cancelled" });
response.resolve(new Response(new ReadableStream({ cancel: () => discarded.resolve() })));
await discarded.promise;
});
it("bounds the wait for Pi's refresh without taking over refresh cancellation", async () => {
vi.useFakeTimers();
const f = fixture();
f.registry.getProviderAuth.mockReturnValue(new Promise(() => {}));
const result = expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "timeout",
});
await vi.advanceTimersByTimeAsync(10_000);
await result;
expect(f.fetcher).not.toHaveBeenCalled();
expect(vi.getTimerCount()).toBe(0);
});
it("bounds the wait for response headers", async () => {
vi.useFakeTimers();
const f = fixture();
f.fetcher.mockReturnValue(new Promise(() => {}));
const result = expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "timeout",
});
await vi.advanceTimersByTimeAsync(10_000);
await result;
expect(f.fetcher).toHaveBeenCalledOnce();
expect(vi.getTimerCount()).toBe(0);
});
it("keeps the deadline active while consuming the response body", async () => {
vi.useFakeTimers();
const f = fixture();
const cancel = vi.fn();
f.fetcher.mockResolvedValue(new Response(new ReadableStream<Uint8Array>({ cancel })));
const result = expect(fetchCodexUsage(f.registry, undefined, f.fetcher)).rejects.toMatchObject({
code: "timeout",
});
await vi.advanceTimersByTimeAsync(10_000);
await result;
expect(cancel).toHaveBeenCalledOnce();
expect(vi.getTimerCount()).toBe(0);
});
+250
View File
@@ -0,0 +1,250 @@
import type { AuthResult, Provider } from "@earendil-works/pi-ai";
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
const PROVIDER = "openai-codex";
const BASE_URL = "https://chatgpt.com/backend-api";
const USAGE_URL = `${BASE_URL}/wham/usage`;
const TIMEOUT_MS = 10_000;
const MAX_RESPONSE_BYTES = 1024 * 1024;
export type CodexAccountRegistry = Pick<
ExtensionContext["modelRegistry"],
"getProviderAuth" | "isUsingOAuth"
> & {
getProvider(id: string): Pick<Provider, "baseUrl" | "getModels"> | undefined;
};
type Failure = "auth" | "unsupported" | "cancelled" | "timeout" | "network" | "response" | "http";
export class CodexAccountError extends Error {
constructor(
readonly code: Failure,
message: string,
readonly retryAfterMs?: number,
) {
super(message);
this.name = "CodexAccountError";
}
}
/** Read account data with Pi's existing login. Pi owns credential refresh and persistence. */
export async function fetchCodexUsage(
registry: CodexAccountRegistry,
signal?: AbortSignal,
fetcher: typeof fetch = fetch,
): Promise<unknown> {
const deadline = new AbortController();
const timer = setTimeout(() => deadline.abort(), TIMEOUT_MS);
const requestSignal = signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal;
try {
requestSignal.throwIfAborted();
const headers = await resolveAccountHeaders(registry, requestSignal);
requestSignal.throwIfAborted();
const data = await requestUsage(headers, requestSignal, fetcher);
requestSignal.throwIfAborted();
return data;
} catch (error) {
if (signal?.aborted)
throw new CodexAccountError("cancelled", "Codex account request cancelled.");
if (deadline.signal.aborted)
throw new CodexAccountError("timeout", "Codex account request timed out.");
if (error instanceof CodexAccountError) throw error;
// Neither auth errors nor fetch errors are safe to display verbatim.
throw new CodexAccountError("network", "Could not reach the Codex account service.");
} finally {
clearTimeout(timer);
deadline.abort();
}
}
async function resolveAccountHeaders(
registry: CodexAccountRegistry,
signal: AbortSignal,
): Promise<Headers> {
const provider = registry.getProvider(PROVIDER);
const model = provider
?.getModels()
.find(
(candidate) =>
candidate.provider === PROVIDER &&
candidate.api === "openai-codex-responses" &&
officialBase(candidate.baseUrl),
);
if (!officialBase(provider?.baseUrl) || !model) {
throw new CodexAccountError(
"unsupported",
"Account usage requires the official Codex provider.",
);
}
if (!registry.isUsingOAuth(model)) throw authError();
let resolvedAuth: AuthResult | undefined;
try {
// Pi owns token refresh; aborting here only stops our wait for its result.
resolvedAuth = await waitWithAbort(registry.getProviderAuth(PROVIDER), signal);
} catch {
throw authError();
}
signal.throwIfAborted();
// Pi's resolved source describes these credentials, unlike its availability snapshot.
if (!resolvedAuth || resolvedAuth.source !== "OAuth") throw authError();
if (
!officialBase(registry.getProvider(PROVIDER)?.baseUrl) ||
(resolvedAuth.auth.baseUrl !== undefined && !officialBase(resolvedAuth.auth.baseUrl))
) {
throw new CodexAccountError(
"unsupported",
"Account usage does not support endpoint overrides.",
);
}
const accessToken = resolvedAuth.auth.apiKey;
if (!accessToken) throw authError();
// Match Pi's legacy transport: token routing overrides configured account headers.
const accountId = tokenAccountId(accessToken);
if (!accountId) throw authError();
try {
// Forward only the two credentials needed by this fixed endpoint, never
// arbitrary provider headers or provider-scoped environment values.
return new Headers({
Accept: "application/json",
Authorization: `Bearer ${accessToken}`,
"ChatGPT-Account-Id": accountId,
});
} catch {
throw authError();
}
}
function officialBase(url: string | undefined): boolean {
return url?.replace(/\/+$/, "") === BASE_URL;
}
function tokenAccountId(token: string): string | undefined {
try {
const parts = token.split(".");
if (parts.length !== 3) return undefined;
// Routing metadata only, not signature verification. Legacy Pi Codex auth
// returns the access token without the account header its transport derives.
const payload: unknown = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
if (!payload || typeof payload !== "object" || !("https://api.openai.com/auth" in payload))
return undefined;
const claim = payload["https://api.openai.com/auth"];
if (!claim || typeof claim !== "object" || !("chatgpt_account_id" in claim)) return undefined;
const accountId = claim.chatgpt_account_id;
if (typeof accountId !== "string" || !accountId.trim()) return undefined;
return accountId;
} catch {
return undefined;
}
}
function authError(): CodexAccountError {
return new CodexAccountError(
"auth",
"Codex account access is unavailable. Check your Pi Codex login.",
);
}
async function requestUsage(
headers: Headers,
requestSignal: AbortSignal,
fetcher: typeof fetch,
): Promise<unknown> {
let receivedResponse: Response | undefined;
try {
const request = fetcher(USAGE_URL, {
method: "GET",
headers,
redirect: "error",
signal: requestSignal,
}).then((response) => {
receivedResponse = response;
// Fetch adapters can ignore abort and deliver a response after we stopped waiting.
if (requestSignal.aborted) {
discardBody(response);
requestSignal.throwIfAborted();
}
return response;
});
const response = await waitWithAbort(request, requestSignal);
if (!response.ok) throw responseError(response);
return await readJson(response, requestSignal);
} finally {
if (receivedResponse && !receivedResponse.bodyUsed) discardBody(receivedResponse);
}
}
function responseError(response: Response): CodexAccountError {
if (response.status === 401 || response.status === 403) return authError();
if (response.status === 429) {
const header = response.headers.get("retry-after");
let retryAfterMs: number | undefined;
if (header) {
const seconds = Number(header);
const delay = Number.isFinite(seconds) ? seconds * 1000 : Date.parse(header) - Date.now();
if (Number.isFinite(delay) && delay >= 0) retryAfterMs = delay;
}
return new CodexAccountError("http", "Codex account service is rate limited.", retryAfterMs);
}
return new CodexAccountError("http", `Codex account request failed (HTTP ${response.status}).`);
}
function discardBody(response: Response): void {
void response.body?.cancel().catch(() => {});
}
async function readJson(response: Response, signal: AbortSignal): Promise<unknown> {
const bytes = await readResponseBytes(response, signal);
signal.throwIfAborted();
try {
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
return JSON.parse(text);
} catch {
throw new CodexAccountError("response", "Codex account service returned invalid JSON.");
}
}
async function readResponseBytes(response: Response, signal: AbortSignal): Promise<Uint8Array> {
if (!response.body) throw new CodexAccountError("response", "Codex account response was empty.");
const reader = response.body.getReader();
const bytes = new Uint8Array(MAX_RESPONSE_BYTES);
let bytesRead = 0;
let fullyRead = false;
try {
for (;;) {
const { value, done } = await waitWithAbort(reader.read(), signal);
if (done) {
fullyRead = true;
return bytes.subarray(0, bytesRead);
}
if (value.byteLength > bytes.byteLength - bytesRead) {
throw new CodexAccountError("response", "Codex account response was too large.");
}
bytes.set(value, bytesRead);
bytesRead += value.byteLength;
}
} finally {
if (!fullyRead) void reader.cancel().catch(() => {});
reader.releaseLock();
}
}
async function waitWithAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
if (signal.aborted) {
// The operation has already started; observe a late rejection without accepting its result.
void promise.catch(() => {});
signal.throwIfAborted();
}
let abort = () => {};
const cancelled = new Promise<never>((_resolve, reject) => {
abort = () => reject(signal.reason);
signal.addEventListener("abort", abort, { once: true });
});
try {
const value = await Promise.race([promise, cancelled]);
signal.throwIfAborted();
return value;
} finally {
signal.removeEventListener("abort", abort);
}
}
+52 -1
View File
@@ -3,6 +3,12 @@ import { join } from "node:path";
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
import { afterAll, expect, it, vi } from "vitest";
import codex from "./index.ts";
import { fetchCodexUsage } from "./client.ts";
vi.mock("./client.ts", async (importOriginal) => ({
...(await importOriginal<typeof import("./client.ts")>()),
fetchCodexUsage: vi.fn(),
}));
const directory = await vi.hoisted(async () => {
const { mkdtempSync } = await import("node:fs");
@@ -21,6 +27,7 @@ afterAll(() => {
});
async function load(prefix = "eleith") {
vi.mocked(fetchCodexUsage).mockReset();
writeFileSync(configPath, JSON.stringify({ commandPrefix: prefix }));
type Command = Parameters<ExtensionAPI["registerCommand"]>[1];
type Listener = (
@@ -49,6 +56,7 @@ async function load(prefix = "eleith") {
hasUI: false,
isIdle: () => true,
getContextUsage: () => undefined,
modelRegistry: {},
ui: { notify, confirm: vi.fn(async () => false) },
sessionManager: { getBranch: () => [], getSessionId: () => "session", getLeafId: () => null },
} as unknown as ExtensionCommandContext;
@@ -82,6 +90,7 @@ it("registers only the configured native command once after binding", async () =
expect(await context.command().getArgumentCompletions?.("")).toEqual([
{ value: "extend", label: "extend" },
{ value: "restore", label: "restore" },
{ value: "usage", label: "usage" },
]);
});
@@ -93,7 +102,7 @@ it("handles no argument as status rather than an invalid default action", async
expect(context.pi.setModel).not.toHaveBeenCalled();
await context.command().handler("extend restore", context.ctx);
expect(context.notify).toHaveBeenLastCalledWith(
"Usage: /eleith:codex [extend|restore]",
"Usage: /eleith:codex [extend|restore|usage]",
"warning",
);
});
@@ -111,8 +120,50 @@ it("wires restore/reconcile hooks without provider calls for an unsupported mode
expect(context.pi.setModel).not.toHaveBeenCalled();
expect(context.pi.appendEntry).not.toHaveBeenCalled();
expect(context.pi.on.mock.calls.map(([name]) => name)).not.toContain("before_agent_start");
expect(fetchCodexUsage).not.toHaveBeenCalled();
});
it("routes usage through the configured command without requiring an idle Codex selection", async () => {
const context = await load("personal");
vi.mocked(fetchCodexUsage).mockResolvedValue({ plan_type: "plus" });
await context.emit("session_start");
await context.command().handler("usage extra", context.ctx);
expect(fetchCodexUsage).not.toHaveBeenCalled();
context.notify.mockClear();
await context.command().handler("usage", { ...context.ctx, isIdle: () => false });
expect(fetchCodexUsage).toHaveBeenCalledExactlyOnceWith(
context.ctx.modelRegistry,
expect.any(AbortSignal),
);
expect(context.notify).toHaveBeenCalledExactlyOnceWith(
expect.stringContaining("Plan: plus"),
"info",
);
expect(context.pi.setModel).not.toHaveBeenCalled();
expect(context.pi.appendEntry).not.toHaveBeenCalled();
});
it.each(["model_select", "session_tree", "session_shutdown"])(
"%s cancels a pending usage report instead of displaying a late reply",
async (event) => {
const context = await load();
let finish!: (value: unknown) => void;
vi.mocked(fetchCodexUsage).mockReturnValue(
new Promise((resolve) => {
finish = resolve;
}),
);
await context.emit("session_start");
const request = context.command().handler("usage", context.ctx);
const signal = vi.mocked(fetchCodexUsage).mock.calls[0][1]!;
await context.emit(event);
expect(signal.aborted).toBe(true);
finish({ plan_type: "plus" });
await request;
expect(context.notify).not.toHaveBeenCalled();
},
);
it("fails invalid prefix settings before registering any capabilities", async () => {
writeFileSync(configPath, '{"commandPrefix":"Invalid Prefix"}');
const on = vi.fn(),
+20 -6
View File
@@ -2,24 +2,38 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { registerCommand } from "@eleith/pi-internal/commands";
import { getSettings } from "@eleith/pi-internal/config";
import { ContextController } from "./controller.ts";
import { UsageController } from "./usage-controller.ts";
export default async function codex(pi: ExtensionAPI): Promise<void> {
const settings = await getSettings();
const commandName = `${settings.commandPrefix}:codex`;
const controller = new ContextController(pi, commandName);
const usage = new UsageController();
registerCommand(pi, {
name: commandName,
description: "Codex context window controls",
actions: ["extend", "restore"],
description: "Codex context controls and account usage",
actions: ["extend", "restore", "usage"],
defaultAction: "",
handler: (action, ctx) => controller.handle(action, ctx),
handler(action, ctx) {
if (action === "usage") return usage.report(ctx);
return controller.handle(action, ctx);
},
});
pi.on("session_start", (_event, ctx) => controller.restore(ctx));
pi.on("session_tree", (_event, ctx) => controller.restore(ctx));
pi.on("model_select", (_event, ctx) => controller.modelSelected(ctx));
pi.on("session_shutdown", (_event, ctx) => controller.shutdown(ctx));
pi.on("session_tree", (_event, ctx) => {
usage.cancel();
return controller.restore(ctx);
});
pi.on("model_select", (_event, ctx) => {
usage.cancel();
return controller.modelSelected(ctx);
});
pi.on("session_shutdown", (_event, ctx) => {
usage.dispose();
controller.shutdown(ctx);
});
pi.on("input", async (event, ctx) => {
if (event.streamingBehavior === undefined && ctx.isIdle()) await controller.reconcile(ctx);
});
@@ -0,0 +1,146 @@
import { expect, it, vi } from "vitest";
import { CodexAccountError, type CodexAccountRegistry, type fetchCodexUsage } from "./client.ts";
import { UsageController, type UsageHost } from "./usage-controller.ts";
function deferred<T>() {
let resolve!: (value: T) => void;
let reject!: (error: unknown) => void;
const promise = new Promise<T>((done, fail) => {
resolve = done;
reject = fail;
});
return { promise, resolve, reject };
}
function fixture() {
const clock = { now: Date.parse("2026-01-10T12:00:00Z") };
const fetchUsage = vi.fn<typeof fetchCodexUsage>(async () => ({ plan_type: "plus" }));
const ctx = {
modelRegistry: {} as CodexAccountRegistry,
sessionManager: { getSessionId: vi.fn(() => "session") },
ui: { notify: vi.fn() },
} satisfies UsageHost;
const controller = new UsageController(fetchUsage, () => clock.now);
return { controller, ctx, fetchUsage, clock };
}
it("reports the configured Codex account independently of the selected model", async () => {
const f = fixture();
await f.controller.report(f.ctx);
expect(f.fetchUsage).toHaveBeenCalledExactlyOnceWith(
f.ctx.modelRegistry,
expect.any(AbortSignal),
);
expect(f.ctx.ui.notify).toHaveBeenCalledExactlyOnceWith(
expect.stringContaining("codex account usage · openai-codex"),
"info",
);
});
it("supersedes an earlier request and ignores its late reply", async () => {
const f = fixture();
const first = deferred<unknown>();
f.fetchUsage.mockReturnValueOnce(first.promise).mockResolvedValueOnce({ plan_type: "new-plan" });
const oldRequest = f.controller.report(f.ctx);
const oldSignal = f.fetchUsage.mock.calls[0][1]!;
await f.controller.report(f.ctx);
expect(oldSignal.aborted).toBe(true);
first.resolve({ plan_type: "old-plan" });
await oldRequest;
expect(f.ctx.ui.notify).toHaveBeenCalledExactlyOnceWith(
expect.stringContaining("Plan: new-plan"),
"info",
);
});
it.each(["cancel", "dispose"] as const)(
"%s discards a late reply before accessing expired context",
async (action) => {
const f = fixture();
const response = deferred<unknown>();
f.fetchUsage.mockReturnValue(response.promise);
const request = f.controller.report(f.ctx);
const signal = f.fetchUsage.mock.calls[0][1]!;
const notify = f.ctx.ui.notify;
f.controller[action]();
Object.defineProperty(f.ctx, "sessionManager", {
get: () => {
throw new Error("Expired context");
},
});
Object.defineProperty(f.ctx, "ui", {
get: () => {
throw new Error("Expired context");
},
});
response.resolve({ plan_type: "plus" });
await request;
expect(signal.aborted).toBe(true);
expect(notify).not.toHaveBeenCalled();
if (action === "dispose") await f.controller.report(f.ctx);
},
);
it("disposal suppresses a late error and is idempotent", async () => {
const f = fixture();
const response = deferred<unknown>();
f.fetchUsage.mockReturnValue(response.promise);
const request = f.controller.report(f.ctx);
f.controller.dispose();
f.controller.dispose();
response.reject(new Error("private late failure"));
await request;
await f.controller.report(f.ctx);
expect(f.ctx.ui.notify).not.toHaveBeenCalled();
expect(f.fetchUsage).toHaveBeenCalledOnce();
});
it("does not deliver a reply to a replacement session", async () => {
const f = fixture();
const response = deferred<unknown>();
f.fetchUsage.mockReturnValue(response.promise);
const request = f.controller.report(f.ctx);
f.ctx.sessionManager.getSessionId.mockReturnValue("different-session");
response.resolve({ plan_type: "plus" });
await request;
expect(f.ctx.ui.notify).not.toHaveBeenCalled();
});
it("reports an unrecognized schema without dumping account data", async () => {
const f = fixture();
f.fetchUsage.mockResolvedValue({ private_account_data: "do-not-display" });
await f.controller.report(f.ctx);
expect(f.ctx.ui.notify).toHaveBeenCalledExactlyOnceWith(
"Codex account service returned an unrecognized usage response.",
"warning",
);
});
it("does not expose unexpected error details", async () => {
const f = fixture();
f.fetchUsage.mockRejectedValue(new Error("private failure details"));
await f.controller.report(f.ctx);
expect(f.ctx.ui.notify).toHaveBeenCalledExactlyOnceWith(
"Could not read Codex account usage.",
"warning",
);
});
it("honors retry-after without polling or making another request until explicitly asked", async () => {
const f = fixture();
f.fetchUsage.mockRejectedValueOnce(
new CodexAccountError("http", "Codex account service is rate limited.", 60_000),
);
await f.controller.report(f.ctx);
await f.controller.report(f.ctx);
expect(f.fetchUsage).toHaveBeenCalledOnce();
expect(f.ctx.ui.notify).toHaveBeenLastCalledWith(
"Codex account service is rate limited. Try again in 60s.",
"warning",
);
f.clock.now += 60_000;
expect(f.fetchUsage).toHaveBeenCalledOnce();
await f.controller.report(f.ctx);
expect(f.fetchUsage).toHaveBeenCalledTimes(2);
expect(f.ctx.ui.notify).toHaveBeenLastCalledWith(expect.stringContaining("Plan: plus"), "info");
});
+69
View File
@@ -0,0 +1,69 @@
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
import { CodexAccountError, fetchCodexUsage, type CodexAccountRegistry } from "./client.ts";
import { formatCodexUsage } from "./usage.ts";
export interface UsageHost {
readonly modelRegistry: CodexAccountRegistry;
readonly sessionManager: Pick<ExtensionContext["sessionManager"], "getSessionId">;
readonly ui: Pick<ExtensionContext["ui"], "notify">;
}
export class UsageController {
private pending: AbortController | undefined;
private retryAt = 0;
private disposed = false;
constructor(
private readonly fetchUsage: typeof fetchCodexUsage = fetchCodexUsage,
private readonly now: () => number = Date.now,
) {}
async report(ctx: UsageHost): Promise<void> {
if (this.disposed) return;
this.cancel();
if (this.now() < this.retryAt) {
const seconds = Math.ceil((this.retryAt - this.now()) / 1000);
ctx.ui.notify(`Codex account service is rate limited. Try again in ${seconds}s.`, "warning");
return;
}
const sessionId = ctx.sessionManager.getSessionId();
const operation = new AbortController();
this.pending = operation;
try {
const data = await this.fetchUsage(ctx.modelRegistry, operation.signal);
if (!this.canDeliverReport(operation, ctx, sessionId)) return;
const report = formatCodexUsage(data, this.now());
if (report === undefined) {
ctx.ui.notify("Codex account service returned an unrecognized usage response.", "warning");
} else {
ctx.ui.notify(report, "info");
}
} catch (error) {
if (!this.canDeliverReport(operation, ctx, sessionId)) return;
const message =
error instanceof CodexAccountError ? error.message : "Could not read Codex account usage.";
if (error instanceof CodexAccountError && error.retryAfterMs !== undefined) {
this.retryAt = this.now() + error.retryAfterMs;
}
ctx.ui.notify(message, "warning");
} finally {
if (this.pending === operation) this.pending = undefined;
}
}
cancel(): void {
this.pending?.abort();
this.pending = undefined;
}
dispose(): void {
if (this.disposed) return;
this.disposed = true;
this.cancel();
}
private canDeliverReport(operation: AbortController, ctx: UsageHost, sessionId: string): boolean {
if (this.disposed || this.pending !== operation || operation.signal.aborted) return false;
return ctx.sessionManager.getSessionId() === sessionId;
}
}
+169
View File
@@ -0,0 +1,169 @@
import { rmSync } from "node:fs";
import { afterAll, expect, it, vi } from "vitest";
import {
InMemoryModelsStore,
type Api,
type CredentialStore,
type Model,
type OAuthCredential,
} from "@earendil-works/pi-ai";
import {
createAgentSession,
DefaultResourceLoader,
ModelRuntime,
SessionManager,
SettingsManager,
type ExtensionUIContext,
} from "@earendil-works/pi-coding-agent";
import codex from "./index.ts";
const directory = await vi.hoisted(async () => {
const { mkdtempSync } = await import("node:fs");
const { tmpdir } = await import("node:os");
const { join } = await import("node:path");
const directory = mkdtempSync(join(tmpdir(), "codex-usage-sdk-test-"));
vi.stubEnv("PI_CODING_AGENT_DIR", directory);
vi.stubEnv("PI_OFFLINE", "1");
return directory;
});
afterAll(() => {
vi.unstubAllGlobals();
vi.unstubAllEnvs();
rmSync(directory, { recursive: true, force: true });
});
it("SDK usage reuses Pi's OAuth without a model turn or credential write", async () => {
const usageResponse = {
plan_type: "plus",
rate_limit: {
primary_window: {
used_percent: 25,
limit_window_seconds: 18_000,
reset_after_seconds: 1800,
},
},
};
const accountRequest = vi.fn<typeof fetch>(async (url) => {
if (url !== "https://chatgpt.com/backend-api/wham/usage") {
throw new Error("Unexpected network request");
}
return new Response(JSON.stringify(usageResponse));
});
vi.stubGlobal("fetch", accountRequest);
const { runtime, model, accessToken, credentialWrites, modelTurns } =
await createOfflineCodexRuntime();
const { session, sessionManager, notify, extensionErrors, closeSession } =
await openCodexExtensionSession(runtime, model);
try {
const branch = structuredClone(sessionManager.getBranch());
await session.prompt("/eleith:codex");
expect(accountRequest).not.toHaveBeenCalled();
await session.prompt("/eleith:codex usage");
expect(notify).toHaveBeenLastCalledWith(
expect.stringContaining("Primary (5h): 25% used"),
"info",
);
expect(accountRequest).toHaveBeenCalledOnce();
const headers = new Headers(accountRequest.mock.calls[0][1]?.headers);
expect(headers.get("authorization")).toBe(`Bearer ${accessToken}`);
expect(headers.get("chatgpt-account-id")).toBe("sdk-test-account");
expect(sessionManager.getBranch()).toEqual(branch);
expect(session.model).toEqual(model);
expect(credentialWrites).not.toHaveBeenCalled();
expect(modelTurns).not.toHaveBeenCalled();
expect(extensionErrors).toEqual([]);
} finally {
await closeSession();
}
});
async function createOfflineCodexRuntime() {
const payload = { "https://api.openai.com/auth": { chatgpt_account_id: "sdk-test-account" } };
const accessToken = `e30.${Buffer.from(JSON.stringify(payload)).toString("base64url")}.fake-signature`;
const credential: OAuthCredential = {
type: "oauth",
access: accessToken,
refresh: "must-not-refresh",
expires: Date.now() + 3_600_000,
};
const credentialWrites = vi.fn<CredentialStore["modify"]>(async () => {
throw new Error("Credential writes forbidden");
});
const credentials: CredentialStore = {
read: async (id) => (id === "openai-codex" ? credential : undefined),
list: async () => [{ providerId: "openai-codex", type: "oauth" }],
modify: credentialWrites,
delete: async () => {
throw new Error("Credential deletion forbidden");
},
};
const runtime = await ModelRuntime.create({
credentials,
modelsPath: null,
modelsStore: new InMemoryModelsStore(),
allowModelNetwork: false,
refreshOnCreate: false,
});
// Populate Pi's auth-availability snapshot without probing other providers or the network.
const refresh = await runtime.refresh({ providers: ["openai-codex"], allowNetwork: false });
expect(refresh.errors.size).toBe(0);
expect(runtime.isUsingOAuth("openai-codex")).toBe(true);
const modelTurns = vi.spyOn(runtime, "streamSimple").mockImplementation(() => {
throw new Error("Model turns forbidden");
});
const model = runtime.getModel("openai-codex", "gpt-6-sol");
if (!model) throw new Error("Built-in Codex model missing");
return { runtime, model, accessToken, credentialWrites, modelTurns };
}
async function openCodexExtensionSession(runtime: ModelRuntime, model: Model<Api>) {
const settingsManager = SettingsManager.inMemory({
packages: [],
enableInstallTelemetry: false,
enableAnalytics: false,
compaction: { enabled: false },
retry: { enabled: false },
cacheWarming: "off",
});
const resourceLoader = new DefaultResourceLoader({
cwd: directory,
agentDir: directory,
settingsManager,
noExtensions: true,
noSkills: true,
noPromptTemplates: true,
noThemes: true,
noContextFiles: true,
extensionFactories: [codex],
});
await resourceLoader.reload();
const sessionManager = SessionManager.inMemory(directory);
const { session } = await createAgentSession({
cwd: directory,
agentDir: directory,
resourceLoader,
settingsManager,
modelRuntime: runtime,
model,
sessionManager,
noTools: "all",
});
const notify = vi.fn();
const extensionErrors: unknown[] = [];
await session.bindExtensions({
mode: "tui",
uiContext: { notify } as unknown as ExtensionUIContext,
onError: (error) => extensionErrors.push(error),
});
return {
session,
sessionManager,
notify,
extensionErrors,
async closeSession() {
await session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" });
session.dispose();
},
};
}
+154
View File
@@ -0,0 +1,154 @@
import { expect, it } from "vitest";
import { formatCodexUsage } from "./usage.ts";
const NOW = Date.parse("2026-01-10T12:00:00Z");
const primary = {
used_percent: 25,
limit_window_seconds: 1800,
reset_at: NOW / 1000 + 3600,
};
it("formats an exact account report without mutating the payload", () => {
const payload = {
plan_type: "plus",
account_id: "do-not-display-account-id",
rate_limit: { allowed: true, primary_window: primary, secondary_window: null },
credits: { has_credits: true, unlimited: false, balance: "12.50" },
future_field: { anything: true },
};
const original = structuredClone(payload);
const report = formatCodexUsage(payload, NOW)!;
expect(report).toBe(`codex account usage · openai-codex
Plan: plus
Codex
Primary (30m): 25% used · 75% remaining
Resets: 2026-01-10 13:00:00 UTC (in 1h)
Secondary: not reported
Credits: 12.5 balance (available)`);
expect(payload).toEqual(original);
});
it("does not invent windows or unlimited credits for plans with missing quota data", () => {
const report = formatCodexUsage(
{
plan_type: "pro",
rate_limit: null,
code_review_rate_limit: null,
credits: null,
},
NOW,
)!;
expect(report).toContain("Codex\n Not reported");
expect(report).toContain("Code review\n Not reported");
expect(report).toContain("Credits: not reported");
expect(report).not.toMatch(/0%|5h|7d|unlimited/);
});
it.each([
[0, "0% used · 100% remaining"],
[-1, "usage unknown"],
[101, "usage unknown"],
[NaN, "usage unknown"],
["25", "usage unknown"],
] as const)("keeps invalid percentages distinct from genuine zero: %s", (used, expected) => {
const report = formatCodexUsage(
{
rate_limit: { primary_window: { ...primary, used_percent: used } },
},
NOW,
)!;
expect(report).toContain(expected);
});
it("uses supplied relative resets without guessing a window duration", () => {
const report = formatCodexUsage(
{
rate_limit: {
primary_window: {
used_percent: 40,
limit_window_seconds: "18000",
reset_after_seconds: 90,
},
},
},
NOW,
)!;
expect(report).toContain("Primary (duration unknown): 40% used · 60% remaining");
expect(report).toContain("2026-01-10 12:01:30 UTC (in 1m 30s)");
});
it("reports unknown invalid reset times and never resets a measured balance locally", () => {
const report = formatCodexUsage(
{
rate_limit: {
limit_reached: true,
primary_window: { ...primary, used_percent: 100, reset_at: NOW / 1000 - 1 },
secondary_window: { ...primary, reset_at: Infinity },
},
},
NOW,
)!;
expect(report).toContain("100% used · 0% remaining");
expect(report).toContain("reset time passed; refresh usage");
expect(report).toContain("Resets: unknown");
expect(report).toContain("Limit reached");
});
it.each([
[{ unlimited: true }, "unlimited"],
[{ unlimited: true, has_credits: false }, "unlimited (unavailable)"],
[{ has_credits: false, balance: "0" }, "0 balance (unavailable)"],
[{ has_credits: true }, "available; balance not reported"],
[{ has_credits: false, balance: "0x10" }, "unavailable; balance not reported"],
] as const)("reports only supplied credit facts: %j", (credits, expected) => {
expect(formatCodexUsage({ credits }, NOW)).toContain(`Credits: ${expected}`);
});
it("includes review and additional buckets plus explicit account restrictions", () => {
const report = formatCodexUsage(
{
code_review_rate_limit: { primary_window: { ...primary, limit_window_seconds: 604_800 } },
additional_rate_limits: [
{
metered_feature: "codex_other",
limit_name: "Other model pool",
rate_limit: { allowed: false, primary_window: primary },
},
],
spend_control: { reached: true },
rate_limit_reached_type: { type: "workspace_owner_credits_depleted" },
},
NOW,
)!;
expect(report).toContain("Code review\n Primary (7d)");
expect(report).toContain("Other model pool\n Primary (30m)");
expect(report).toContain("Requests not currently allowed");
expect(report).toContain("Workspace spending limit: reached");
expect(report).toContain("Account limit: workspace owner credits depleted");
});
it("bounds and sanitizes backend labels and additional-bucket output", () => {
const payload = {
plan_type: "plus\u001b[31m\n",
additional_rate_limits: Array.from({ length: 10 }, (_, index) => ({
limit_name: `Pool ${index}\r\n${"x".repeat(1000)}`,
rate_limit: { primary_window: primary },
})),
};
const report = formatCodexUsage(payload, NOW)!;
// oxlint-disable-next-line no-control-regex -- Newlines are layout; other terminal controls must be absent.
expect(report).not.toMatch(/[\x00-\x09\x0b-\x1f\x7f-\x9f]/);
expect(report).not.toContain("x".repeat(65));
expect(report).toContain("2 additional entries omitted.");
expect(report).not.toContain("Pool 8");
expect(report.length).toBeLessThan(5000);
});
it.each([null, [], {}, { unrelated: 10 }, { rate_limit: "wrong shape" }])(
"rejects unrecognized response shapes instead of fabricating healthy usage: %j",
(payload) => {
expect(formatCodexUsage(payload, NOW)).toBeUndefined();
},
);
+197
View File
@@ -0,0 +1,197 @@
const MAX_ADDITIONAL_BUCKETS = 8;
const MAX_LABEL_LENGTH = 64;
const NUMBERS = new Intl.NumberFormat("en-US", { maximumFractionDigits: 2 });
type JsonObject = Record<string, unknown>;
interface UsageSections {
plan: string | undefined;
codex: JsonObject | undefined;
includeCodeReview: boolean;
codeReview: JsonObject | undefined;
additional: unknown[] | undefined;
credits: JsonObject | undefined;
spendingLimitReached: boolean;
accountLimit: string | undefined;
}
/** Account allowance only; this is not Pi's context occupancy or a cost estimate. */
export function formatCodexUsage(value: unknown, now = Date.now()): string | undefined {
const sections = readUsageSections(value);
if (!sections) return undefined;
const rows = [
"codex account usage · openai-codex",
`Plan: ${sections.plan ?? "unknown"}`,
"",
...bucketRows("Codex", sections.codex, now),
];
if (sections.includeCodeReview) {
rows.push("", ...bucketRows("Code review", sections.codeReview, now));
}
rows.push(...additionalBucketRows(sections.additional, now));
rows.push("", `Credits: ${creditStatus(sections.credits)}`);
if (sections.spendingLimitReached) rows.push("Workspace spending limit: reached");
if (sections.accountLimit)
rows.push(`Account limit: ${sections.accountLimit.replaceAll("_", " ")}`);
return rows.join("\n");
}
function readUsageSections(value: unknown): UsageSections | undefined {
const payload = readObject(value);
if (!payload) return undefined;
const plan = sanitizeDisplayLabel(payload.plan_type);
const codex = readObject(payload.rate_limit);
const codeReview = readObject(payload.code_review_rate_limit);
const credits = readObject(payload.credits);
const spendingControl = readObject(payload.spend_control);
const reachedLimit = readObject(payload.rate_limit_reached_type);
const additional = Array.isArray(payload.additional_rate_limits)
? payload.additional_rate_limits
: undefined;
const hasUsageData = [
plan,
codex,
codeReview,
credits,
spendingControl,
reachedLimit,
additional,
].some((section) => section !== undefined);
if (!hasUsageData) return undefined;
const includeCodeReview = "code_review_rate_limit" in payload;
const spendingLimitReached = spendingControl?.reached === true;
const accountLimit = sanitizeDisplayLabel(reachedLimit?.type);
return {
plan,
codex,
includeCodeReview,
codeReview,
additional,
credits,
spendingLimitReached,
accountLimit,
};
}
function bucketRows(name: string, bucket: JsonObject | undefined, now: number): string[] {
if (!bucket) return [name, " Not reported"];
const rows = [
name,
...windowRows("Primary", bucket.primary_window, now),
...windowRows("Secondary", bucket.secondary_window, now),
];
if (bucket.limit_reached === true) rows.push(" Limit reached");
else if (bucket.allowed === false) rows.push(" Requests not currently allowed");
return rows;
}
function windowRows(name: string, value: unknown, now: number): string[] {
const window = readObject(value);
if (!window) return [` ${name}: not reported`];
const seconds = finiteNonnegativeNumber(window.limit_window_seconds);
let interval = "duration unknown";
if (seconds !== undefined && Number.isSafeInteger(seconds) && seconds > 0)
interval = duration(seconds);
const used = finiteNonnegativeNumber(window.used_percent);
let usage = "usage unknown";
if (used !== undefined && used <= 100) {
usage = `${NUMBERS.format(used)}% used · ${NUMBERS.format(100 - used)}% remaining`;
}
return [` ${name} (${interval}): ${usage}`, ` Resets: ${resetTime(window, now)}`];
}
function additionalBucketRows(additional: unknown[] | undefined, now: number): string[] {
if (!additional) return [];
const rows: string[] = [];
for (const [index, value] of additional.slice(0, MAX_ADDITIONAL_BUCKETS).entries()) {
const entry = readObject(value);
const name =
sanitizeDisplayLabel(entry?.limit_name) ??
sanitizeDisplayLabel(entry?.metered_feature) ??
`Additional ${index + 1}`;
const quota = readObject(entry?.rate_limit);
rows.push("", ...bucketRows(name, quota, now));
}
if (additional.length > MAX_ADDITIONAL_BUCKETS) {
rows.push("", `${additional.length - MAX_ADDITIONAL_BUCKETS} additional entries omitted.`);
}
return rows;
}
function creditStatus(credits: JsonObject | undefined): string {
if (!credits) return "not reported";
if (credits.unlimited === true) {
return credits.has_credits === false ? "unlimited (unavailable)" : "unlimited";
}
let availability = "availability unknown";
if (credits.has_credits === true) availability = "available";
else if (credits.has_credits === false) availability = "unavailable";
const balance = parseCreditBalance(credits.balance);
if (balance === undefined) return `${availability}; balance not reported`;
return `${NUMBERS.format(balance)} balance (${availability})`;
}
function parseCreditBalance(value: unknown): number | undefined {
if (typeof value === "number") return finiteNonnegativeNumber(value);
if (typeof value !== "string") return undefined;
const decimal = value.trim();
if (!/^\d+(?:\.\d+)?$/.test(decimal)) return undefined;
return finiteNonnegativeNumber(Number(decimal));
}
function resetTime(window: JsonObject, now: number): string {
const absolute = finiteNonnegativeNumber(window.reset_at);
const relative = finiteNonnegativeNumber(window.reset_after_seconds);
let timestamp = NaN;
if (absolute !== undefined) timestamp = absolute * 1000;
else if (relative !== undefined) timestamp = now + relative * 1000;
const date = new Date(timestamp);
if (!Number.isFinite(date.getTime())) return "unknown";
const time = date.toISOString().replace("T", " ").replace(".000Z", " UTC");
const remaining = Math.ceil((timestamp - now) / 1000);
return remaining > 0
? `${time} (in ${duration(remaining)})`
: `${time} (reset time passed; refresh usage)`;
}
function duration(seconds: number): string {
let remaining = Math.ceil(seconds);
const parts: string[] = [];
for (const [unit, size] of [
["d", 86_400],
["h", 3600],
["m", 60],
["s", 1],
] as const) {
const amount = Math.floor(remaining / size);
if (amount > 0) parts.push(`${amount}${unit}`);
remaining %= size;
}
return parts.join(" ") || "0s";
}
function readObject(value: unknown): JsonObject | undefined {
return value !== null && typeof value === "object" && !Array.isArray(value)
? (value as JsonObject)
: undefined;
}
function sanitizeDisplayLabel(value: unknown): string | undefined {
if (typeof value !== "string") return undefined;
return (
value
// oxlint-disable-next-line no-control-regex -- Strip terminal controls from backend labels.
.replace(/[\x00-\x1f\x7f-\x9f]/g, " ")
.replace(/\s+/g, " ")
.trim()
.slice(0, MAX_LABEL_LENGTH) || undefined
);
}
function finiteNonnegativeNumber(value: unknown): number | undefined {
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
}