feat(core): give limits an id, windowSeconds and scope, and read advance from stdin
A host tracking quota over time needs to key a limit, know its window, and know which usage counts against it; `label` is display text and says none of that reliably. ABI 4 adds three fields to every limit: - `id`, a stable machine key within the provider: anthropic's `kind`, codex's `primary`/`secondary` and `<metered_feature>.<window>`, antigravity's family, opencode's window key, zai's `<type>:<unit>:<number>` (one type can repeat with another period), alibaba's `five_hour`/`weekly`, hyper's `hypercredits`. - `windowSeconds`, when the provider states it (codex's `limit_window_seconds`, hyper's refresh endpoints) or the plan fixes it; absent when unknown, as for zai's units other than hours. - `scope`, the model family or feature a limit is restricted to: anthropic's body `scope`, or the kind's suffix (`weekly_opus` -> `opus`), codex's additional limit name, antigravity's family. Absent means the limit counts all usage. `advance -` takes the whole call as one JSON envelope on stdin, `{provider, credential, responses: [round...], nowMs?}`, so a credential never lands in argv where other local processes can read it. It stays stateless: the host keeps every round's responses and the CLI replays them through one fresh state, as the argument form does. Mirrored in the TypeScript reference in prime-agent.
53 unmodified lines545556575859606162636465666768697071727374757677782 unmodified lines8182838485868788899091929394959697989938 unmodified lines53 unmodified lineszig build test # core unit tests + the C ABI smoke testzig build smoke # C ABI smoke test alonezig build run -- providerszig build run -- advance <provider> <credential.json> <responses.json>... [--now-ms N]zig build run -- advance - # the same call as one JSON envelope on stdinzig build run -- claims <access-token>```
The `smoke` step is not decoration: it compiles `tests/c_smoke.c` with the toolchain, linksthe installed static library, exercises multi-round state, and asserts against recorded provider bodies.
`advance -` takes the whole call as one JSON envelope on stdin instead, so a credential neverlands in argv where other local processes can read it. It is as stateless as the argument form:the host keeps every round's responses and replays them.
```shecho '{"provider":"anthropic","credential":{"kind":"oauth","access":"…"},"responses":[],"nowMs":0}' \ | zig build run -- advance -```
```shswift build --package-path swiftswift run --package-path swift usage-smoke tests/fixtures```
2 unmodified lines```shzig build -Dtarget=aarch64-macos.13```
## Limits
A report's `limits[]` carries what a host needs to track a limit over time, beside the displaytext:
| field | meaning || --- | --- || `id` | Stable machine key within the provider (`weekly_opus`, `primary`, `codex_bengalfox.secondary`). `label` is display text and can change. || `windowSeconds` | Window length when the provider states it or the plan fixes it; absent when unknown or calendar-shaped. || `scope` | The provider's own name for the model family or feature the limit is restricted to; absent means it counts all usage on the account. |
## Parity with the TypeScript reference
`prime-agent` carries the reference implementation in `packages/ai/src/utils/usage/`, andits docs in `packages/ai/docs/usage-query.md` define the JSON shapes both sides speak. Both38 unmodified lines17 unmodified lines1819202122222324252639 unmodified lines17 unmodified lines#ifdef __cplusplusextern "C" {#endif
/* ABI version. Bumped to 3 for explicit host-owned cross-call state. *//* ABI version. Bumped to 4 for a limit's id, windowSeconds and scope. */uint32_t usage_abi_version(void);
/* Release a string returned by this library. NULL is accepted. */void usage_free(char *ptr);39 unmodified lines6 unmodified lines7891011121314151625 unmodified lines424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011027 unmodified lines1101111121131145911511611711811914 unmodified lines1341351361378283841381391401411421439091929394959697989914414510114614714814915013 unmodified lines6 unmodified lines \\usage <command> \\ providers supported providers info \\ advance <provider> <credential.json> <responses.json>... advance feed state across rounds \\ [--now-ms N] \\ advance - the same, from one JSON envelope on stdin: \\ {"provider","credential","responses":[round...],"nowMs"?} \\ claims <access-token> account id and email from an OAuth token \\;
25 unmodified linesfn readFile(init: std.process.Init, path: []const u8, arena: std.mem.Allocator) ![]u8 { return std.Io.Dir.cwd().readFileAlloc(init.io, path, arena, std.Io.Limit.limited(8 * 1024 * 1024));}
/// Replays every round through one fresh state, printing one step per line: a host keeps the/// responses, and the process keeps nothing.fn advanceRounds( init: std.process.Init, arena: std.mem.Allocator, provider: []const u8, credential: []const u8, rounds: []const []const u8, now_ms: i64,) !u8 { var state = usage.State.init(arena); defer state.deinit();
for (rounds) |responses| { var w = usage.Writer.init(arena); const outcome = usage.advance(arena, &state, provider, credential, responses, now_ms) catch |e| { var err_w = usage.Writer.init(arena); usage.writeStep(&err_w, .{ .err = @errorName(e) }) catch return 1; try emit(init, err_w.items()); return 0; }; try usage.writeStep(&w, outcome); try emit(init, w.items()); } return 0;}
/// `advance -`: the whole call as one JSON envelope on stdin, so a credential never reaches argv,/// where any local process can read it.fn advanceEnvelope(init: std.process.Init, arena: std.mem.Allocator) !u8 { var buffer: [8192]u8 = undefined; var reader = std.Io.File.stdin().readerStreaming(init.io, &buffer); const input = reader.interface.allocRemaining(arena, std.Io.Limit.limited(8 * 1024 * 1024)) catch |e| return fail(init, @errorName(e));
const parsed = std.json.parseFromSliceLeaky(std.json.Value, arena, input, .{}) catch return fail(init, "the envelope is not JSON"); const root = usage.asObject(parsed) orelse return fail(init, "the envelope is not an object"); const provider = usage.asString(root.get("provider")) orelse return fail(init, "the envelope needs a provider"); const credential = root.get("credential") orelse return fail(init, "the envelope needs a credential"); const responses = usage.asArray(root.get("responses")) orelse return fail(init, "the envelope needs responses"); const now_ms = usage.toInt(i64, usage.asNumber(root.get("nowMs"))) orelse nowMs();
// An envelope asking for the first requests has no rounds yet; that is one empty round. var rounds = std.ArrayList([]const u8).empty; if (responses.items.len == 0) try rounds.append(arena, "[]"); for (responses.items) |round| { try rounds.append(arena, try std.json.Stringify.valueAlloc(arena, round, .{})); } const credential_text = try std.json.Stringify.valueAlloc(arena, credential, .{}); return advanceRounds(init, arena, provider, credential_text, rounds.items, now_ms);}
pub fn main(init: std.process.Init) !u8 { const arena = init.arena.allocator(); const argv = try std.process.Args.toSlice(init.minimal.args, arena); if (argv.len < 2) return fail(init, "missing command");7 unmodified lines return 0; }
if (std.mem.eql(u8, command, "advance")) { if (argv.len == 3 and std.mem.eql(u8, argv[2], "-")) return advanceEnvelope(init, arena); if (argv.len < 5) return fail(init, "advance needs <provider> <credential.json> <responses.json>..."); if (argv.len < 5) return fail(init, "advance needs <provider> <credential.json> <responses.json>... or -"); const provider = argv[2]; const cred_arg = argv[3];
var resp_args = std.ArrayList([]const u8).empty;14 unmodified lines cred_arg else readFile(init, cred_arg, arena) catch |e| return fail(init, @errorName(e));
var state = usage.State.init(arena); defer state.deinit();
var rounds = std.ArrayList([]const u8).empty; for (resp_args.items) |resp_arg| { const resp_str = if (std.mem.startsWith(u8, resp_arg, "[") or std.mem.startsWith(u8, resp_arg, "{")) resp_arg else readFile(init, resp_arg, arena) catch |e| return fail(init, @errorName(e));
var w = usage.Writer.init(arena); const outcome = usage.advance(arena, &state, provider, cred_str, resp_str, now_ms) catch |e| { var err_w = usage.Writer.init(arena); usage.writeStep(&err_w, .{ .err = @errorName(e) }) catch return 1; try emit(init, err_w.items()); return 0; }; try usage.writeStep(&w, outcome); try emit(init, w.items()); try rounds.append(arena, resp_str); } return 0; return advanceRounds(init, arena, provider, cred_str, rounds.items, now_ms); }
if (std.mem.eql(u8, command, "claims")) { if (argv.len < 3) return fail(init, "claims needs an access token");13 unmodified lines123445678991011121369 unmodified lines8384858687888990919293949596979899100101102361 unmodified linesExpand all4644654664674604684694704714724734743 unmodified lines478479480481482483484485486487488489490491492493494495158 unmodified linesExpand all//! Pure provider usage query core.//!//! Driven by the host: no sockets, no clock, no filesystem.//! Implements ABI 3 (explicit host-owned cross-call state).//! Implements ABI 4 (limit id, windowSeconds and scope).//! The core owns no cross-call state; the host owns it and passes it back.const std = @import("std");pub const iso8601 = @import("iso8601.zig");
pub const abi_version: u32 = 3;pub const abi_version: u32 = 4;
pub const ProviderInfo = struct { provider: []const u8, providerName: []const u8,69 unmodified lines }};
pub const UsageLimit = struct { /// Stable machine key for this limit within its provider; `label` is display text. id: []const u8, label: []const u8, usedPercent: ?f64 = null, windowLabel: ?[]const u8 = null, /// Window length when the provider states it or the plan fixes it; null when unknown or /// calendar-shaped (a month). windowSeconds: ?i64 = null, resetsAt: ?i64 = null, /// The provider's own name for the model family or feature this limit is restricted to; /// null means it counts all usage on the account. scope: ?[]const u8 = null, status: []const u8, detail: ?[]const u8 = null,};
361 unmodified linesExpand all if (wrote) try w.byte(','); try w.raw("\"limits\":["); for (rep.limits, 0..) |lim, i| { if (i > 0) try w.byte(','); try w.raw("{\"label\":"); try w.raw("{\"id\":"); try w.string(lim.id); try w.raw(",\"label\":"); try w.string(lim.label); if (lim.usedPercent) |up| { if (std.math.isFinite(up)) { const clamped = @max(0.0, @min(100.0, up));3 unmodified lines if (lim.windowLabel) |wl| { try w.raw(",\"windowLabel\":"); try w.string(wl); } if (lim.windowSeconds) |ws| { try w.print(",\"windowSeconds\":{d}", .{ws}); } if (lim.resetsAt) |ra| { try w.print(",\"resetsAt\":{d}", .{ra}); } if (lim.scope) |sc| { try w.raw(",\"scope\":"); try w.string(sc); } if (lim.detail) |dt| { try w.raw(",\"detail\":"); try w.string(dt); }158 unmodified linesExpand all58 unmodified lines5960616263636465666768 unmodified lines58 unmodified lines
int main(int argc, char **argv) { if (argc < 2) return 2;
check(usage_abi_version() == 3, "ABI version must be 3"); check(usage_abi_version() == 4, "ABI version must be 4");
char *providers = usage_providers(); check(providers != NULL, "providers must not be NULL"); check(strstr(providers, "\"provider\":\"openai-codex\"") != NULL, "codex provider present");68 unmodified lines80 unmodified lines81828384858685868788899022 unmodified lines1131141151161171181171181191201211225 unmodified lines12812913013113213213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018122 unmodified lines20420520620716620820921021121221343 unmodified lines2572582592602182612622632642652667 unmodified lines27427527627723427827923628028128228328413 unmodified lines29829930030125830230326030430530630730833 unmodified lines342343344345302346347348349350262 unmodified linesExpand all80 unmodified lines const now: i64 = 1_700_000_000_000; const text = try advance(a, "openai-codex", cred, body, now);
try std.testing.expect(std.mem.indexOf(u8, text, "\"planType\":\"prolite\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Primary\",\"usedPercent\":100,\"windowLabel\":\"7d window\",\"status\":\"exhausted\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Spark · Primary\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"primary\",\"label\":\"Primary\",\"usedPercent\":100,\"windowLabel\":\"7d window\",\"windowSeconds\":604800,\"status\":\"exhausted\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"Spark.primary\",\"label\":\"Spark · Primary\",\"usedPercent\":0,\"windowLabel\":\"5h window\",\"windowSeconds\":18000,\"scope\":\"Spark\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"resetsAt\":1700003600000") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"email\":\"user@example.com\"") != null);}
22 unmodified lines
try std.testing.expect(std.mem.indexOf(u8, text, "\"email\":\"user@example.com\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"accountId\":\"uuid-123\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"planType\":\"team_standard\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Session\",\"usedPercent\":0,\"windowLabel\":\"5h window\",\"status\":\"ok\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Weekly\",\"usedPercent\":94,\"windowLabel\":\"7d window\",\"resetsAt\":1789545600037,\"status\":\"warning\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"session\",\"label\":\"Session\",\"usedPercent\":0,\"windowLabel\":\"5h window\",\"windowSeconds\":18000,\"status\":\"ok\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"weekly_all\",\"label\":\"Weekly\",\"usedPercent\":94,\"windowLabel\":\"7d window\",\"windowSeconds\":604800,\"resetsAt\":1789545600037,\"status\":\"warning\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"notes\":[\"Extra usage is off\"]") != null);}
test "anthropic extra usage enabled" {5 unmodified lines const body = \\[{"status":200,"body":"{\"limits\":[],\"extra_usage\":{\"is_enabled\":true,\"monthly_limit\":100,\"used_credits\":25}}"},{"status":200,"body":"{}"}] ; const text = try advance(a, "anthropic", cred, body, 0); try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Extra usage\",\"usedPercent\":25") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"extra_usage\",\"label\":\"Extra usage\",\"usedPercent\":25") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"detail\":\"25 of 100 credits\"") != null);}
test "anthropic scoped windows carry their scope, and the body's scope wins" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const a = arena.allocator();
const cred = "{\"kind\":\"oauth\",\"access\":\"ant-token\"}"; const body = \\[{"status":200,"body":"{\"limits\":[{\"kind\":\"weekly_opus\",\"percent\":40},{\"kind\":\"daily_all\",\"percent\":5},{\"kind\":\"weekly_sonnet\",\"percent\":1,\"scope\":\"claude-sonnet\"}]}"},{"status":200,"body":"{}"}] ; const text = try advance(a, "anthropic", cred, body, 0); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"weekly_opus\",\"label\":\"Weekly (Opus)\",\"usedPercent\":40,\"windowLabel\":\"7d window\",\"windowSeconds\":604800,\"scope\":\"opus\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"daily_all\",\"label\":\"Daily (All)\",\"usedPercent\":5,\"windowLabel\":\"24h window\",\"windowSeconds\":86400,\"status\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"weekly_sonnet\",\"label\":\"Weekly (Sonnet)\",\"usedPercent\":1,\"windowLabel\":\"7d window\",\"windowSeconds\":604800,\"scope\":\"claude-sonnet\"") != null);}
test "codex additional limits are keyed by their metered feature" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const a = arena.allocator();
const cred = "{\"kind\":\"oauth\",\"access\":\"test-token\"}"; const body = \\[{"status":200,"body":"{\"additional_rate_limits\":[{\"limit_name\":\"GPT-5.3-Codex-Spark\",\"metered_feature\":\"codex_bengalfox\",\"rate_limit\":{\"secondary_window\":{\"used_percent\":3,\"limit_window_seconds\":604800}}}]}"}] ; const text = try advance(a, "openai-codex", cred, body, 0); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"codex_bengalfox.secondary\",\"label\":\"GPT-5.3-Codex-Spark · Secondary\",\"usedPercent\":3,\"windowLabel\":\"7d window\",\"windowSeconds\":604800,\"scope\":\"GPT-5.3-Codex-Spark\"") != null);}
test "zai keys a limit by type and period, and counts unit 3 in hours" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const a = arena.allocator();
const cred = "{\"kind\":\"apiKey\",\"apiKey\":\"key\"}"; const body = \\[{"status":200,"body":"{\"code\":200,\"success\":true,\"data\":{\"limits\":[{\"type\":\"TOKENS_LIMIT\",\"unit\":3,\"number\":5,\"percentage\":12},{\"type\":\"TIME_LIMIT\",\"unit\":5,\"number\":1,\"usage\":100,\"currentValue\":0,\"percentage\":0}]}}"}] ; const text = try advance(a, "zai", cred, body, 0); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"tokens_limit:3:5\",\"label\":\"Tokens\",\"usedPercent\":12,\"windowLabel\":\"5h window\",\"windowSeconds\":18000,") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"time_limit:5:1\",\"label\":\"Requests\",\"usedPercent\":0,\"detail\"") != null);}
test "access token claims give account id and a normalized email" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); const a = arena.allocator();22 unmodified lines \\[{"status":200,"body":"{\"buckets\":[{\"modelId\":\"gemini-2.5-flash\",\"remainingFraction\":0.16592255,\"resetTime\":\"2026-09-15T23:46:00Z\"},{\"modelId\":\"chat_tab_drop\",\"remainingFraction\":1.0}]}"}] ; const now: i64 = 1_789_514_000_000; const rep_text = try advance(a, "google-antigravity", cred, body, now); try std.testing.expect(std.mem.indexOf(u8, rep_text, "\"label\":\"Gemini\"") != null); try std.testing.expect(std.mem.indexOf(u8, rep_text, "\"id\":\"gemini\",\"label\":\"Gemini\"") != null); try std.testing.expect(std.mem.indexOf(u8, rep_text, "\"scope\":\"Gemini\"") != null); try std.testing.expect(std.mem.indexOf(u8, rep_text, "\"usedPercent\":83.4") != null); try std.testing.expect(std.mem.indexOf(u8, rep_text, "\"detail\":\"17% free\"") != null); try std.testing.expect(std.mem.indexOf(u8, rep_text, "\"windowLabel\":\"1h window\"") != null); try std.testing.expect(std.mem.indexOf(u8, rep_text, "\"notes\":[\"Project: test-proj\"]") != null);43 unmodified lines try std.testing.expect(std.mem.indexOf(u8, step2, "\"accountId\":\"team_123\"") != null); try std.testing.expect(std.mem.indexOf(u8, step2, "\"email\":\"user@example.com\"") != null); try std.testing.expect(std.mem.indexOf(u8, step2, "\"usedPercent\":5.2") != null); try std.testing.expect(std.mem.indexOf(u8, step2, "\"detail\":\"237 of 250 credits\"") != null); try std.testing.expect(std.mem.indexOf(u8, step2, "\"windowLabel\":\"24h window\"") != null); try std.testing.expect(std.mem.indexOf(u8, step2, "\"id\":\"hypercredits\"") != null); try std.testing.expect(std.mem.indexOf(u8, step2, "\"windowLabel\":\"24h window\",\"windowSeconds\":86400") != null); try std.testing.expect(std.mem.indexOf(u8, step2, "\"balance\":\"11.83\"") != null); try std.testing.expect(std.mem.indexOf(u8, step2, "\"notes\":[\"Team: My Team\"]") != null);}
7 unmodified lines \\[{"status":200,"body":"{\"usage\":{\"rolling\":{\"status\":\"ok\",\"percent\":10,\"resetsAt\":\"2026-09-16T04:40:14Z\"},\"monthly\":{\"status\":\"rate-limited\",\"percent\":50,\"resetsAt\":\"2026-09-29T07:01:56Z\"}}}"}] ; const text = try advance(a, "opencode-go", cred, body, 0); try std.testing.expect(std.mem.indexOf(u8, text, "\"planType\":\"Go\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Session\",\"usedPercent\":10,\"windowLabel\":\"5h window\",\"resetsAt\":1789533614000,\"status\":\"ok\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"rolling\",\"label\":\"Session\",\"usedPercent\":10,\"windowLabel\":\"5h window\",\"windowSeconds\":18000,\"resetsAt\":1789533614000,\"status\":\"ok\"") != null); // rate-limited status -> exhausted even though percent is 50 try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Monthly\",\"usedPercent\":50,\"windowLabel\":\"30d window\",\"resetsAt\":1790665316000,\"status\":\"exhausted\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"monthly\",\"label\":\"Monthly\",\"usedPercent\":50,\"windowLabel\":\"30d window\",\"windowSeconds\":2592000,\"resetsAt\":1790665316000,\"status\":\"exhausted\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"notes\":[\"OpenCode Go unified models\"]") != null);}
test "zai authorization without Bearer, epoch seconds/ms, and ratio fallback" {13 unmodified lines \\[{"status":200,"body":"{\"code\":200,\"success\":true,\"data\":{\"level\":\"lite\",\"limits\":[{\"type\":\"TIME_LIMIT\",\"unit\":5,\"currentValue\":10,\"usage\":100,\"percentage\":10,\"nextResetTime\":1792027143998},{\"type\":\"TOKENS_LIMIT\",\"currentValue\":25,\"number\":100,\"nextResetTime\":1792027143}]}}"}] ; const text = try advance(a, "zai", cred, body, 0); try std.testing.expect(std.mem.indexOf(u8, text, "\"planType\":\"Lite\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Requests\",\"usedPercent\":10,\"resetsAt\":1792027143998,\"detail\":\"10 / 100 reqs\",\"status\":\"ok\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"time_limit\",\"label\":\"Requests\",\"usedPercent\":10,\"resetsAt\":1792027143998,\"detail\":\"10 / 100 reqs\",\"status\":\"ok\"") != null); // 1792027143 (< 1e11) converted from epoch seconds to 1792027143000 ms, ratio 25% try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Tokens\",\"usedPercent\":25,\"windowLabel\":\"5h window\",\"resetsAt\":1792027143000,\"status\":\"ok\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"tokens_limit\",\"label\":\"Tokens\",\"usedPercent\":25,\"windowLabel\":\"5h window\",\"resetsAt\":1792027143000,\"status\":\"ok\"") != null);}
test "zai error return when code != 200" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator);33 unmodified lines const cli_body = \\[{"status":0,"body":"{\"per5HourPercentage\":0.5,\"per1WeekPercentage\":0.83603712139856,\"per1WeekResetTime\":1790021160000}"}] ; const text = try advance(a, "alibaba", cred, cli_body, 1_700_000_000_000); try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Session\",\"usedPercent\":50,\"windowLabel\":\"5h window\",\"status\":\"ok\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"id\":\"five_hour\",\"label\":\"Session\",\"usedPercent\":50,\"windowLabel\":\"5h window\",\"windowSeconds\":18000,\"status\":\"ok\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"label\":\"Weekly\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"resetsAt\":1790021160000") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"windowLabel\":\"7d window\"") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\"notes\":[\"read through the bailian CLI (intl-personal)\"]") != null);262 unmodified linesExpand all120 unmodified linesExpand all1211221231241251261271281291301311321333 unmodified lines137138139140141142143144145146147148149507 unmodified linesExpand all120 unmodified linesExpand all const used_pct = sf * 100.0; const reset = usage.asNumber(ud.get("per5HourResetTime")); const reset_ms = usage.toInt(i64, reset); try limits.append(alloc, .{ .id = "five_hour", .label = "Session", .usedPercent = used_pct, .windowLabel = "5h window", .windowSeconds = 5 * 3600, .resetsAt = reset_ms, .status = usage.statusFor(used_pct), }); }3 unmodified lines const used_pct = wf * 100.0; const reset = usage.asNumber(ud.get("per1WeekResetTime")); const reset_ms = usage.toInt(i64, reset); try limits.append(alloc, .{ .id = "weekly", .label = "Weekly", .usedPercent = used_pct, .windowLabel = "7d window", .windowSeconds = 7 * 86_400, .resetsAt = reset_ms, .status = usage.statusFor(used_pct), }); }507 unmodified linesExpand all10 unmodified lines1112131415161718192021222324252627282930319 unmodified lines414243443435454647484950515253544055565758445960614762636465666753686970717233 unmodified lines10610710810911011111211311411511611711811912012120 unmodified lines142143144145146147148149150123 unmodified linesExpand all10 unmodified lines
pub const WindowInfo = struct { label: []const u8, windowLabel: ?[]const u8 = null, windowSeconds: ?i64 = null, scope: ?[]const u8 = null,};
const five_hours = 5 * 3600;const one_day = 86_400;const one_week = 7 * one_day;
/// The part of a kind that names what the window is restricted to; `all` restricts nothing.fn kindScope(scope: []const u8) ?[]const u8 { return if (scope.len == 0 or std.mem.eql(u8, scope, "all")) null else scope;}
fn titleCase(alloc: std.mem.Allocator, value: []const u8) ![]const u8 { var parts = std.mem.splitAny(u8, value, "_-"); var res = std.ArrayList(u8).empty; var first = true;9 unmodified lines return res.items;}
pub fn windowForKind(alloc: std.mem.Allocator, kind: []const u8) !WindowInfo { if (std.mem.eql(u8, kind, "session") or std.mem.endsWith(u8, kind, "_session")) { return .{ .label = "Session", .windowLabel = "5h window" }; if (std.mem.eql(u8, kind, "session")) { return .{ .label = "Session", .windowLabel = "5h window", .windowSeconds = five_hours }; } if (std.mem.endsWith(u8, kind, "_session")) { const scope = kindScope(kind[0 .. kind.len - "_session".len]); return .{ .label = "Session", .windowLabel = "5h window", .windowSeconds = five_hours, .scope = scope }; } if (std.mem.startsWith(u8, kind, "weekly_")) { const scope = kind["weekly_".len..]; if (std.mem.eql(u8, scope, "all")) { return .{ .label = "Weekly", .windowLabel = "7d window" }; return .{ .label = "Weekly", .windowLabel = "7d window", .windowSeconds = one_week }; } const tc = try titleCase(alloc, scope); const label = try std.fmt.allocPrint(alloc, "Weekly ({s})", .{tc}); return .{ .label = label, .windowLabel = "7d window" }; return .{ .label = label, .windowLabel = "7d window", .windowSeconds = one_week, .scope = kindScope(scope) }; } if (std.mem.eql(u8, kind, "weekly")) { return .{ .label = "Weekly", .windowLabel = "7d window" }; return .{ .label = "Weekly", .windowLabel = "7d window", .windowSeconds = one_week }; } if (std.mem.startsWith(u8, kind, "daily_")) { const scope = kind["daily_".len..]; const tc = try titleCase(alloc, scope); const label = try std.fmt.allocPrint(alloc, "Daily ({s})", .{tc}); return .{ .label = label, .windowLabel = "24h window" }; return .{ .label = label, .windowLabel = "24h window", .windowSeconds = one_day, .scope = kindScope(scope) }; } const label = try titleCase(alloc, kind); return .{ .label = label, .windowLabel = null };}33 unmodified lines const resets_at_str = usage.asString(limit.get("resets_at")); const resets_at_ms: ?i64 = if (resets_at_str) |s| iso8601.parseIso8601(s) else null;
try limits.append(alloc, .{ .id = kind, .label = win.label, .usedPercent = used_percent, .windowLabel = win.windowLabel, .windowSeconds = win.windowSeconds, .resetsAt = resets_at_ms, // The body's own scope wins over the one read from the kind. .scope = usage.stringOf(limit.get("scope")) orelse win.scope, .status = final_status, }); } }20 unmodified lines break :blk try std.fmt.allocPrint(alloc, "{d} of {d} credits", .{ uc_int, ml_int }); } else null;
try limits.append(alloc, .{ .id = "extra_usage", .label = "Extra usage", .usedPercent = percent, .resetsAt = null, .status = usage.statusFor(percent),123 unmodified linesExpand all64 unmodified lines6566676869707172697071727374757613 unmodified lines9091929394959697989910010110210310410577 unmodified lines64 unmodified lines } }
var limits = std.ArrayList(usage.UsageLimit).empty; const families = [_]struct { label: []const u8, info: *const FamilyInfo }{ .{ .label = "Gemini", .info = &gemini }, .{ .label = "Claude", .info = &claude }, .{ .label = "GPT-OSS", .info = &gpt }, const families = [_]struct { id: []const u8, label: []const u8, info: *const FamilyInfo }{ .{ .id = "gemini", .label = "Gemini", .info = &gemini }, .{ .id = "claude", .label = "Claude", .info = &claude }, .{ .id = "gpt-oss", .label = "GPT-OSS", .info = &gpt }, };
for (families) |f| { if (!f.info.present) continue;13 unmodified lines } else null;
const status = if (used_percent) |up| usage.statusFor(up) else "unknown";
// Each family's buckets are its own quota, so the family is also the scope. try limits.append(alloc, .{ .id = f.id, .label = f.label, .usedPercent = used_percent, .windowLabel = window_label, .resetsAt = resets_at, .scope = f.label, .detail = detail, .status = status, }); }77 unmodified lines4 unmodified lines567899101112137 unmodified lines2122232425262728293031323334353637387 unmodified lines4647484946505152535455565357585960612 unmodified lines646566676465666869707172737475697677787980818276838485868799 unmodified lines4 unmodified linesconst usage = @import("../usage.zig");
pub const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
fn parseWindow(alloc: std.mem.Allocator, window_val: ?std.json.Value, now_ms: i64) !?usage.UsageLimit {fn parseWindow(alloc: std.mem.Allocator, id: []const u8, window_val: ?std.json.Value, now_ms: i64, scope: ?[]const u8) !?usage.UsageLimit { const value = usage.asObject(window_val) orelse return null; const used_percent = usage.asNumber(value.get("used_percent")); const reset_at = usage.asNumber(value.get("reset_at")); const reset_after = usage.asNumber(value.get("reset_after_seconds"));7 unmodified lines } else null;
const window_lbl = try usage.formatWindowLabel(alloc, limit_window); const window_seconds: ?i64 = if (limit_window) |lw| (if (lw > 0) usage.roundToInt(i64, lw) else null) else null;
return usage.UsageLimit{ .id = id, .label = "Usage", .usedPercent = used_percent, .windowLabel = window_lbl, .windowSeconds = window_seconds, .resetsAt = reset_ms, .scope = scope, .status = usage.statusFor(used_percent), };}
7 unmodified lines const rate_limit = if (root) |r| usage.asObject(r.get("rate_limit")) else null; var limits = std.ArrayList(usage.UsageLimit).empty;
const primary_val = if (rate_limit) |rl| rl.get("primary_window") else null; if (try parseWindow(alloc, primary_val, now_ms)) |pw| { if (try parseWindow(alloc, "primary", primary_val, now_ms, null)) |pw| { var w = pw; w.label = "Primary"; try limits.append(alloc, w); }
const secondary_val = if (rate_limit) |rl| rl.get("secondary_window") else null; if (try parseWindow(alloc, secondary_val, now_ms)) |sw| { if (try parseWindow(alloc, "secondary", secondary_val, now_ms, null)) |sw| { var w = sw; w.label = "Secondary"; try limits.append(alloc, w); }2 unmodified lines if (additional) |arr| { for (arr.items) |entry| { const item = usage.asObject(entry) orelse continue; const extra = usage.asObject(item.get("rate_limit")); const name = usage.asString(item.get("limit_name")) orelse usage.asString(item.get("metered_feature")) orelse "Additional"; const limit_name = usage.asString(item.get("limit_name")); const feature = usage.asString(item.get("metered_feature")); const name = limit_name orelse feature orelse "Additional"; // The metered feature is the machine key; the limit name is what it restricts. const key = feature orelse limit_name orelse "additional"; const scope = limit_name orelse feature;
const extra_primary = if (extra) |e| e.get("primary_window") else null; if (try parseWindow(alloc, extra_primary, now_ms)) |ep| { if (try parseWindow(alloc, try std.fmt.allocPrint(alloc, "{s}.primary", .{key}), extra_primary, now_ms, scope)) |ep| { var w = ep; w.label = try std.fmt.allocPrint(alloc, "{s} · Primary", .{name}); try limits.append(alloc, w); }
const extra_secondary = if (extra) |e| e.get("secondary_window") else null; if (try parseWindow(alloc, extra_secondary, now_ms)) |es| { if (try parseWindow(alloc, try std.fmt.allocPrint(alloc, "{s}.secondary", .{key}), extra_secondary, now_ms, scope)) |es| { var w = es; w.label = try std.fmt.allocPrint(alloc, "{s} · Secondary", .{name}); try limits.append(alloc, w); }99 unmodified lines