import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import {
alibabaUsageFeed,
anthropicUsageFeed,
antigravityUsageFeed,
codexUsageFeed,
createUsageState,
deepseekUsageFeed,
hyperUsageFeed,
opencodegoUsageFeed,
type SessionUsageCredential,
type UsageCredential,
type UsageResponse,
type UsageStep,
usageProviders,
zaiUsageFeed,
} from "../src/index.js";
/**
* The Zig core is a second implementation of the same rules, so it is checked against
* the reference on the same bodies rather than on hand-written expectations. Both sides
* are computed here at test time, which means a semantic change on either side fails
* this test instead of silently drifting.
*/
const nativeDir = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "native", "usage");
const zig = process.env.ZIG ?? "zig";
function zigVersion(): string | undefined {
try {
return execFileSync(zig, ["version"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
} catch {
return undefined;
}
}
const version = zigVersion();
function advanceZig(
provider: string,
credential: UsageCredential,
responses: UsageResponse[],
nowMs: number = 0,
): UsageStep {
const output = execFileSync(
zig,
[
"build",
"run",
"--",
"advance",
provider,
JSON.stringify(credential),
JSON.stringify(responses),
"--now-ms",
String(nowMs),
],
{
cwd: nativeDir,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
},
)
.trim()
.split("\n")
.at(-1) as string;
return JSON.parse(output);
}
/**
* The same call, but through several rounds in one process. The core owns no cross-call state, so
* this is the only way to reach a feed whose later round depends on an earlier one: the CLI creates
* one state and passes it back on each round, exactly as a native host does.
*/
function advanceZigRounds(
provider: string,
credential: UsageCredential,
rounds: UsageResponse[][],
nowMs: number = 0,
): UsageStep[] {
const output = execFileSync(
zig,
[
"build",
"run",
"--",
"advance",
provider,
JSON.stringify(credential),
...rounds.map((responses) => JSON.stringify(responses)),
"--now-ms",
String(nowMs),
],
{
cwd: nativeDir,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
},
).trim();
return output.split("\n").map((line) => JSON.parse(line) as UsageStep);
}
/** The ABI carries headers as ordered pairs (HTTP allows a repeated name); TS carries a record. */
function pairsToRecord(pairs: [string, string][]): Record<string, string> {
return Object.fromEntries(pairs);
}
describe.skipIf(version === undefined)("zig usage core parity", () => {
it("reports the implemented providers and credential kinds matching their TS definitions", () => {
const output = execFileSync(zig, ["build", "run", "--", "providers"], {
cwd: nativeDir,
encoding: "utf8",
})
.trim()
.split("\n")
.at(-1) as string;
const zigProviders = JSON.parse(output) as {
provider: string;
providerName: string;
credentialKinds: string[];
}[];
const tsAll = new Map(
usageProviders().map((p) => [
p.provider,
{
provider: p.provider,
providerName: p.providerName,
credentialKinds: [...p.credentialKinds],
},
]),
);
for (const zp of zigProviders) {
expect(tsAll.get(zp.provider)).toEqual(zp);
}
expect(zigProviders.map((p) => p.provider)).toEqual(usageProviders().map((p) => p.provider));
});
it("codex-usage-live.json matches the TypeScript reference", () => {
const nowMs = 1_789_514_000_000;
const path = join(nativeDir, "tests", "fixtures", "codex-usage-live.json");
const body = readFileSync(path, "utf8");
const credential: UsageCredential = { kind: "oauth", access: "access-token", email: "user@example.com" };
const responses: UsageResponse[] = [{ status: 200, body }];
const tsStep = codexUsageFeed.advance(createUsageState(credential, nowMs), responses);
const zigStep = advanceZig("openai-codex", credential, responses, nowMs);
expect(zigStep).toEqual(tsStep);
});
it("codex-usage-edge.json matches the TypeScript reference", () => {
const nowMs = 1_700_000_000_000;
const path = join(nativeDir, "tests", "fixtures", "codex-usage-edge.json");
const body = readFileSync(path, "utf8");
const credential: UsageCredential = { kind: "oauth", access: "access-token", email: "user@example.com" };
const responses: UsageResponse[] = [{ status: 200, body }];
const tsStep = codexUsageFeed.advance(createUsageState(credential, nowMs), responses);
const zigStep = advanceZig("openai-codex", credential, responses, nowMs);
expect(zigStep).toEqual(tsStep);
});
it("anthropic usage matches the TypeScript reference", () => {
const usageBody = readFileSync(join(nativeDir, "tests", "fixtures", "anthropic-usage.json"), "utf8");
const profileBody = readFileSync(join(nativeDir, "tests", "fixtures", "anthropic-profile.json"), "utf8");
const credential: UsageCredential = { kind: "oauth", access: "access-token" };
const responses: UsageResponse[] = [
{ status: 200, body: usageBody },
{ status: 200, body: profileBody },
];
const tsStep = anthropicUsageFeed.advance(createUsageState(credential, 0), responses);
const zigStep = advanceZig("anthropic", credential, responses, 0);
expect(zigStep).toEqual(tsStep);
});
it("google-antigravity quota matches the TypeScript reference", () => {
const nowMs = 1_789_514_000_000;
const body = readFileSync(join(nativeDir, "tests", "fixtures", "antigravity-quota.json"), "utf8");
const credential: UsageCredential = {
kind: "oauth",
access: "ya29.test-access-token",
accountId: "aicode-consumers",
email: "dev@example.com",
extra: { projectId: "aicode-consumers" },
};
const responses: UsageResponse[] = [{ status: 200, body }];
const tsStep = antigravityUsageFeed.advance(createUsageState(credential, nowMs), responses);
const zigStep = advanceZig("google-antigravity", credential, responses, nowMs);
expect(zigStep).toEqual(tsStep);
});
it("hyper billing matches the TypeScript reference", () => {
const nowMs = 1_789_514_000_000;
const billingBody = readFileSync(join(nativeDir, "tests", "fixtures", "hyper-billing.json"), "utf8");
const membersBody = readFileSync(join(nativeDir, "tests", "fixtures", "hyper-members.json"), "utf8");
const credential: UsageCredential = {
kind: "apiKey",
apiKey: "sk-hyper-test1234567890abcdef",
accountId: "team_20439d8c54af",
};
const responses: UsageResponse[] = [
{ status: 200, body: billingBody },
{ status: 200, body: membersBody },
];
const tsStep = hyperUsageFeed.advance(createUsageState(credential, nowMs), responses);
const zigStep = advanceZig("hyper", credential, responses, nowMs);
expect(zigStep).toEqual(tsStep);
});
it("hyper hands the team from its teams round to its billing round, on both sides", () => {
const nowMs = 1_789_514_000_000;
// The team name carries a quote and a backslash because the note crosses a JSON boundary: a
// writer that forgets to escape ends the string early, and this case stops parsing.
const teamsBody = JSON.stringify({ items: [{ id: "team_20439d8c54af", name: 'Hyper "Squad" \\ back' }] });
const billingBody = readFileSync(join(nativeDir, "tests", "fixtures", "hyper-billing.json"), "utf8");
const membersBody = readFileSync(join(nativeDir, "tests", "fixtures", "hyper-members.json"), "utf8");
const credential: UsageCredential = { kind: "apiKey", apiKey: "sk-hyper-test1234567890abcdef" };
const rounds: UsageResponse[][] = [
[{ status: 200, body: teamsBody }],
[
{ status: 200, body: billingBody },
{ status: 200, body: membersBody },
],
];
// One state, two rounds: the team is cached per credential in TS and in the host-owned state
// on the Zig side, so a round that reads freed memory or a lost cache reports no team note.
const state = createUsageState(credential, nowMs);
const tsRounds = [hyperUsageFeed.advance(state, rounds[0]!), hyperUsageFeed.advance(state, rounds[1]!)];
const zigRounds = advanceZigRounds("hyper", credential, rounds, nowMs);
expect(zigRounds).toHaveLength(2);
const [zigTeamsStep, zigBillingStep] = zigRounds as [
{ requests: { method: string; url: string; headers: [string, string][] }[] },
UsageStep,
];
const tsTeamsStep = tsRounds[0]!;
if (!("requests" in tsTeamsStep)) throw new Error("round one should ask for teams");
// The request shape is compared too — this is the one step where the two representations
// differ on purpose (ordered pairs vs record).
expect(zigTeamsStep.requests.map((r) => ({ ...r, headers: pairsToRecord(r.headers) }))).toEqual(
tsTeamsStep.requests,
);
expect(zigBillingStep).toEqual(tsRounds[1]!);
});
it("a reset already in the past keeps the reference's window label", () => {
const nowMs = 1_789_514_000_000;
// Only next_at, so the label comes from epochToWindowLabel rather than the last/next difference,
// and it is already behind now_ms: the reference floors that to "1h window".
const billingBody = JSON.stringify({
plan: { name: "Shred", type: "paid", hypercredits: 250 },
status: "active",
balances: { plan: { hypercredits: 237 }, total: { usd: "11.83" } },
hypercredit_refresh: { next_at: new Date(nowMs - 3_600_000).toISOString() },
});
const credential: UsageCredential = {
kind: "apiKey",
apiKey: "sk-hyper-test1234567890abcdef",
accountId: "team_20439d8c54af",
};
const responses: UsageResponse[] = [{ status: 200, body: billingBody }];
const tsStep = hyperUsageFeed.advance(createUsageState(credential, nowMs), responses);
const zigStep = advanceZig("hyper", credential, responses, nowMs);
expect(zigStep).toEqual(tsStep);
});
it("opencode-go usage matches the TypeScript reference", () => {
const body = readFileSync(join(nativeDir, "tests", "fixtures", "opencode-go-usage.json"), "utf8");
const credential: UsageCredential = { kind: "apiKey", apiKey: "test-opencode-key" };
const responses: UsageResponse[] = [{ status: 200, body }];
const tsStep = opencodegoUsageFeed.advance(createUsageState(credential, 0), responses);
const zigStep = advanceZig("opencode-go", credential, responses, 0);
expect(zigStep).toEqual(tsStep);
});
it("zai usage matches the TypeScript reference", () => {
const body = readFileSync(join(nativeDir, "tests", "fixtures", "zai-usage.json"), "utf8");
const credential: UsageCredential = { kind: "apiKey", apiKey: "test-zai-key" };
const responses: UsageResponse[] = [{ status: 200, body }];
const tsStep = zaiUsageFeed.advance(createUsageState(credential, 0), responses);
const zigStep = advanceZig("zai", credential, responses, 0);
expect(zigStep).toEqual(tsStep);
});
it("deepseek balance matches the TypeScript reference", () => {
const body = readFileSync(join(nativeDir, "tests", "fixtures", "deepseek-balance.json"), "utf8");
const credential: UsageCredential = { kind: "apiKey", apiKey: "test-deepseek-key" };
const responses: UsageResponse[] = [{ status: 200, body }];
const tsStep = deepseekUsageFeed.advance(createUsageState(credential, 0), responses);
const zigStep = advanceZig("deepseek", credential, responses, 0);
expect(zigStep).toEqual(tsStep);
});
it("alibaba CLI first request matches the TypeScript reference", () => {
const credential: UsageCredential = { kind: "cli", config: { region: "intl-personal" } };
const tsStep = alibabaUsageFeed.advance(createUsageState(credential, 0), []);
const zigStep = advanceZig("alibaba", credential, [], 0);
expect(zigStep).toEqual(tsStep);
});
it("alibaba CLI answer matches the TypeScript reference", () => {
const credential: UsageCredential = {
kind: "cli",
config: { region: "intl-personal" },
email: "user@example.com",
};
const responses: UsageResponse[] = [
{
status: 0,
body: JSON.stringify({
per5HourPercentage: 0.5,
per1WeekPercentage: 0.83603712139856,
per1WeekResetTime: 1_790_021_160_000,
}),
},
];
const tsStep = alibabaUsageFeed.advance(createUsageState(credential, 1_700_000_000_000), responses);
const zigStep = advanceZig("alibaba", credential, responses, 1_700_000_000_000);
expect(zigStep).toEqual(tsStep);
});
it("alibaba console fallback matches the TypeScript reference", () => {
const usageBody = readFileSync(join(nativeDir, "tests", "fixtures", "alibaba-usage.json"), "utf8");
const subBody = readFileSync(join(nativeDir, "tests", "fixtures", "alibaba-subscription.json"), "utf8");
const quotaBody = readFileSync(join(nativeDir, "tests", "fixtures", "alibaba-quota-config.json"), "utf8");
const cardBody = readFileSync(join(nativeDir, "tests", "fixtures", "alibaba-reset-card_list.json"), "utf8");
const credential: SessionUsageCredential = {
kind: "session",
session: {
url: "https://bailian-singapore-cs.alibabacloud.com/data/api.json",
region: "ap-southeast-1",
secToken: "test-sec-token-123",
headers: { cookie: "login_aliyunid=test; cna=test" },
},
email: "user@example.com",
};
const responses: UsageResponse[] = [
{ status: 200, body: usageBody },
{ status: 200, body: subBody },
{ status: 200, body: quotaBody },
{ status: 200, body: cardBody },
];
const tsStep = alibabaUsageFeed.advance(createUsageState(credential, 0), responses);
const zigStep = advanceZig("alibaba", credential, responses, 0);
expect(zigStep).toEqual(tsStep);
});
});