repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/quota/__tests__/contract.test.ts
Rawimport {
mkdirSync,
mkdtempSync,
readdirSync,
readFileSync,
rmSync,
writeFileSync,
} from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
InMemoryCredentialStore,
type OAuthCredential,
} from "@earendil-works/pi-ai";
import {
type AfterProviderResponseEvent,
initTheme,
} from "@earendil-works/pi-coding-agent";
import { afterEach, describe, expect, it, vi } from "vitest";
import {
createPiSession,
type PiSession,
} from "../../../test/fixture/create-pi-session.ts";
import { __test } from "../index.ts";
/** 4h50m ahead, matching the reset window the footer renders. */
const RESET_AT = Date.now() + 4 * 3_600_000 + 50 * 60_000 + 30_000;
describe("quota contract", () => {
let session: PiSession | undefined;
afterEach(async () => {
vi.unstubAllGlobals();
await session?.dispose();
session = undefined;
});
it("loads in real Pi extension runtime", async () => {
const extensionPath = fileURLToPath(
new URL("../index.ts", import.meta.url),
);
session = await createPiSession({ extensionPaths: [extensionPath] });
expect(session.extensions).toHaveLength(1);
expect(session.extensions[0]!.path).toBe(extensionPath);
}, 15000);
it("writes its opt-in lifecycle trace inside the fixture sandbox", async () => {
const root = mkdtempSync(path.join(tmpdir(), "pi-ext-quota-debug-"));
try {
const extensionPath = fileURLToPath(
new URL("../index.ts", import.meta.url),
);
session = await createPiSession({
cwd: root,
extensionPaths: [extensionPath],
env: { PI_QUOTA_DEBUG: "1" },
});
const runner = session.session.extensionRunner;
await runner.emit({ type: "session_start", reason: "startup" });
await runner.emit({ type: "session_shutdown", reason: "quit" });
const stateHome = session.env["XDG_STATE_HOME"];
if (!stateHome) throw new Error("missing sandbox state home");
const directory = path.join(stateHome, "pi-ext", "debug", "quota");
const [file] = readdirSync(directory);
if (!file) throw new Error("missing debug file");
const events = readFileSync(path.join(directory, file), "utf8")
.trim()
.split("\n")
.map((line) => JSON.parse(line).event);
expect(events).toEqual(
expect.arrayContaining(["session.start", "session.shutdown"]),
);
} finally {
await session?.dispose();
session = undefined;
rmSync(root, { recursive: true, force: true });
}
});
function codexToken(accountId: string) {
const claim = Buffer.from(
JSON.stringify({
"https://api.openai.com/auth": { chatgpt_account_id: accountId },
}),
).toString("base64url");
return `header.${claim}.signature`;
}
function quotaCommandContext() {
if (!session) throw new Error("missing session");
initTheme("default", false);
const runner = session.session.extensionRunner;
const ui = {
...runner.getUIContext(),
setStatus: vi.fn(),
setWidget: vi.fn(),
};
runner.setUIContext(ui, "tui");
const ctx = Object.create(runner.createCommandContext());
Object.defineProperty(ctx, "model", {
value: { provider: "openai-codex" },
});
const command = runner.getCommand("quota");
if (!command) throw new Error("missing /quota command");
return { command, ctx };
}
function installCodexRefresh(
refresh: (credential: OAuthCredential) => Promise<OAuthCredential>,
) {
if (!session) throw new Error("missing session");
const runtime = session.session.modelRuntime;
const provider = runtime.getProvider("openai-codex");
if (!provider?.auth.oauth) throw new Error("missing native Codex OAuth");
runtime.registerNativeProvider({
...provider,
auth: {
...provider.auth,
oauth: {
...provider.auth.oauth,
refresh: (credential, _signal) => refresh(credential),
toAuth: async (credential) => ({ apiKey: credential.access }),
},
},
});
}
it("uses active stored Codex OAuth token, never fixture auth files", async () => {
const credentials = new InMemoryCredentialStore();
const token = codexToken("acct-active");
await credentials.modify("openai-codex", async () => ({
type: "oauth",
access: token,
refresh: "refresh-active",
expires: Date.now() + 3_600_000,
}));
const root = mkdtempSync(path.join(tmpdir(), "pi-ext-quota-auth-"));
try {
const extensionPath = fileURLToPath(
new URL("../index.ts", import.meta.url),
);
session = await createPiSession({
cwd: root,
credentials,
extensionPaths: [extensionPath],
});
writeFileSync(
path.join(session.agentDir, "auth.json"),
JSON.stringify({
"openai-codex": {
type: "oauth",
access: codexToken("acct-default"),
refresh: "refresh-default",
expires: Date.now() + 3_600_000,
accountId: "acct-default",
},
}),
);
const codexHome = path.join(session.env["HOME"]!, ".codex");
mkdirSync(codexHome, { recursive: true });
writeFileSync(
path.join(codexHome, "auth.json"),
JSON.stringify({
tokens: {
access_token: codexToken("acct-codex"),
account_id: "acct-codex",
},
}),
);
const fetch = vi.fn(async (_input: string | URL, _init?: RequestInit) => {
return Response.json({ rate_limit: {} });
});
vi.stubGlobal("fetch", fetch);
const { command, ctx } = quotaCommandContext();
await command.handler("", ctx);
expect(fetch).toHaveBeenCalledTimes(1);
const [url, init] = fetch.mock.calls[0]!;
expect(String(url)).toBe("https://chatgpt.com/backend-api/wham/usage");
const headers = new Headers(init?.headers);
expect(headers.get("Authorization")).toBe(`Bearer ${token}`);
expect(headers.get("ChatGPT-Account-Id")).toBe("acct-active");
expect(session.errors).toEqual([]);
} finally {
await session?.dispose();
session = undefined;
rmSync(root, { recursive: true, force: true });
}
}, 15000);
it.each([
["malformed token", "not-a-jwt"],
["missing Codex account claim", "header.e30.signature"],
["no OAuth credential", undefined],
] as const)(
"does not request Codex quota with %s",
async (_name, access) => {
const credentials = new InMemoryCredentialStore();
if (access) {
await credentials.modify("openai-codex", async () => ({
type: "oauth",
access,
refresh: "refresh",
expires: Date.now() + 3_600_000,
}));
}
const extensionPath = fileURLToPath(
new URL("../index.ts", import.meta.url),
);
session = await createPiSession({
credentials,
extensionPaths: [extensionPath],
});
const fetch = vi.fn(async (input: string | URL) => {
throw new Error(`unexpected network endpoint: ${input}`);
});
vi.stubGlobal("fetch", fetch);
const { command, ctx } = quotaCommandContext();
await command.handler("", ctx);
expect(fetch).not.toHaveBeenCalled();
expect(session.errors).toEqual([]);
},
15000,
);
it("refreshes expired Codex OAuth before polling with refreshed account", async () => {
const credentials = new InMemoryCredentialStore();
await credentials.modify("openai-codex", async () => ({
type: "oauth",
access: codexToken("acct-old"),
refresh: "refresh-old",
expires: Date.now() - 1,
account_id: "acct-old-credential",
}));
const extensionPath = fileURLToPath(
new URL("../index.ts", import.meta.url),
);
session = await createPiSession({
credentials,
extensionPaths: [extensionPath],
});
const refreshed = codexToken("acct-refreshed");
const refresh = vi.fn(async () => ({
type: "oauth" as const,
access: refreshed,
refresh: "refresh-new",
expires: Date.now() + 3_600_000,
account_id: "acct-old-credential",
}));
installCodexRefresh(refresh);
const fetch = vi.fn(async (_input: string | URL, _init?: RequestInit) =>
Response.json({ rate_limit: {} }),
);
vi.stubGlobal("fetch", fetch);
const { command, ctx } = quotaCommandContext();
await command.handler("", ctx);
expect(refresh).toHaveBeenCalledTimes(1);
expect(fetch).toHaveBeenCalledTimes(1);
const [url, init] = fetch.mock.calls[0]!;
expect(String(url)).toBe("https://chatgpt.com/backend-api/wham/usage");
const headers = new Headers(init?.headers);
expect(headers.get("Authorization")).toBe(`Bearer ${refreshed}`);
expect(headers.get("ChatGPT-Account-Id")).toBe("acct-refreshed");
expect(session.errors).toEqual([]);
}, 15000);
it("derives Codex account identity only from the supplied token", () => {
const encode = (value: unknown) =>
Buffer.from(JSON.stringify(value)).toString("base64url");
const token = `header.${encode({ "https://api.openai.com/auth": { chatgpt_account_id: "acct-session" } })}.signature`;
expect(__test.codexAccountId(token)).toBe("acct-session");
expect(
__test.codexAccountId(
`header.${encode({ account_id: "acct-other" })}.signature`,
),
).toBeUndefined();
expect(
__test.codexAccountId(
`header.${encode({ "https://api.openai.com/auth": { chatgpt_account_id: "\n" } })}.signature`,
),
).toBeUndefined();
expect(__test.codexAccountId("malformed-token")).toBeUndefined();
});
it("parses stolen provider quota shapes", () => {
expect(
__test.parseCopilot({
quota_snapshots: {
premium_interactions: { percent_remaining: 75 },
chat: { percent_remaining: 40 },
},
}),
).toMatchObject({
name: "GitHub Copilot",
fiveH: { used: 25, total: 100, remaining: 75 },
weekly: { used: 60, total: 100, remaining: 40 },
});
expect(
__test.parseGemini({
buckets: [
{ modelId: "gemini-pro", remainingFraction: 0.25 },
{ modelId: "gemini-flash", remainingFraction: 0.75 },
],
}),
).toMatchObject({
name: "Gemini",
fiveH: { used: 75, total: 100, remaining: 25 },
weekly: { used: 25, total: 100, remaining: 75 },
});
expect(
__test.parseMinimax({
base_resp: { status_code: 0, status_msg: "" },
model_remains: [
{
model_name: "MiniMax-M2",
current_interval_total_count: 100,
current_interval_usage_count: 30,
current_weekly_total_count: 1000,
current_weekly_usage_count: 800,
},
],
}),
).toMatchObject({
name: "MiniMax",
fiveH: { used: 70, total: 100, remaining: 30 },
weekly: { used: 200, total: 1000, remaining: 800 },
});
expect(
__test.parseMinimax({
base_resp: { status_code: 0, status_msg: "" },
model_remains: [
{
model_name: "general",
current_interval_total_count: 0,
current_interval_usage_count: 0,
current_interval_remaining_percent: 25,
current_weekly_total_count: 0,
current_weekly_usage_count: 0,
current_weekly_remaining_percent: 80,
},
],
}),
).toMatchObject({
name: "MiniMax",
fiveH: { used: 75, total: 100, remaining: 25 },
weekly: { used: 20, total: 100, remaining: 80 },
});
});
it("reads quota from provider response headers", () => {
const anthropic: AfterProviderResponseEvent = {
type: "after_provider_response",
status: 200,
headers: {
"content-type": "text/event-stream",
"anthropic-ratelimit-unified-5h-utilization": "0.26",
"anthropic-ratelimit-unified-5h-reset": "1764554400",
"anthropic-ratelimit-unified-7d-utilization": "0.75",
"anthropic-ratelimit-unified-7d-reset": "1764615600",
},
};
expect(__test.usageFromResponse("anthropic", anthropic)).toEqual({
name: "Anthropic",
fiveH: { used: 26, total: 100, remaining: 74, resetAt: 1764554400000 },
weekly: { used: 75, total: 100, remaining: 25, resetAt: 1764615600000 },
});
const codex: AfterProviderResponseEvent = {
type: "after_provider_response",
status: 200,
headers: {
"x-codex-primary-used-percent": "42",
"x-codex-primary-reset-at": "1900000000",
"x-codex-secondary-used-percent": "7",
},
};
expect(__test.usageFromResponse("openai-codex", codex)).toEqual({
name: "OpenAI Codex",
fiveH: { used: 42, total: 100, remaining: 58, resetAt: 1900000000000 },
weekly: { used: 7, total: 100, remaining: 93, resetAt: undefined },
});
// Wrong provider or missing quota headers leave the poll result alone.
expect(__test.usageFromResponse("klaus", anthropic)).toBeUndefined();
expect(__test.usageFromResponse("anthropic", codex)).toBeUndefined();
expect(
__test.usageFromResponse("anthropic", {
headers: { "anthropic-ratelimit-unified-5h-utilization": "nope" },
}),
).toBeUndefined();
});
it("survives provider responses in the real runtime", async () => {
const extensionPath = fileURLToPath(
new URL("../index.ts", import.meta.url),
);
session = await createPiSession({ extensionPaths: [extensionPath] });
await session.session.extensionRunner.emit({
type: "after_provider_response",
status: 200,
headers: { "anthropic-ratelimit-unified-5h-utilization": "0.5" },
});
expect(session.errors).toEqual([]);
}, 15000);
it("drops the provider name for the active provider only", () => {
const anthropic = {
name: "Anthropic",
fiveH: { used: 4, total: 100, remaining: 96, resetAt: RESET_AT },
weekly: { used: 0, total: 0, remaining: 0 },
};
expect(__test.segmentText(anthropic, true)).toEqual([
"4% (4h50m)",
"4%",
"4%",
]);
expect(__test.segmentText(anthropic, false)).toEqual([
"Anthropic 4% (4h50m)",
"Anthropic 4%",
"4%",
]);
});
it("keeps the name in status fallbacks and spells out the non-tui label", () => {
const providers = [
{
name: "Anthropic",
fiveH: { used: 4, total: 100, remaining: 96, resetAt: RESET_AT },
weekly: { used: 0, total: 0, remaining: 0 },
},
];
expect(__test.quotaStatusLabel(providers, "Anthropic")).toBe(
"Anthropic 4% (4h50m)",
);
expect(__test.quotaPlainLabel(providers, "Anthropic")).toBe(
"Anthropic quota 4% used, resets in 4h50m",
);
expect(__test.quotaPlainLabel([], "Anthropic")).toBeUndefined();
});
});