Something went wrong. Try again.
zig library for building requests to query inference providers usage limits
Something went wrong. Try again.
23 kB · 653 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654//! Pure provider usage query core.//!//! Driven by the host: no sockets, no clock, no filesystem.//! 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 = 4;
pub const ProviderInfo = struct { provider: []const u8, providerName: []const u8, credentialKinds: []const []const u8,};
pub const supported_providers = [_]ProviderInfo{ .{ .provider = "alibaba", .providerName = "Alibaba Model Studio", .credentialKinds = &[_][]const u8{ "cli", "session" }, }, .{ .provider = "anthropic", .providerName = "Anthropic", .credentialKinds = &[_][]const u8{"oauth"}, }, .{ .provider = "deepseek", .providerName = "DeepSeek", .credentialKinds = &[_][]const u8{"apiKey"}, }, .{ .provider = "google-antigravity", .providerName = "Google Antigravity", .credentialKinds = &[_][]const u8{"oauth"}, }, .{ .provider = "hyper", .providerName = "Charm Hyper", .credentialKinds = &[_][]const u8{ "oauth", "apiKey" }, }, .{ .provider = "openai-codex", .providerName = "ChatGPT Codex", .credentialKinds = &[_][]const u8{"oauth"}, }, .{ .provider = "opencode-go", .providerName = "OpenCode Zen", .credentialKinds = &[_][]const u8{"apiKey"}, }, .{ .provider = "zai", .providerName = "Z.ai", .credentialKinds = &[_][]const u8{"apiKey"}, },};
/// Cross-round state owned by the host and passed back on each advance call./// The core owns no cross-call state; the host owns it and passes it back.pub const State = struct { hyper_team_id: ?[]u8 = null, hyper_team_name: ?[]u8 = null, alloc: std.mem.Allocator,
pub fn init(alloc: std.mem.Allocator) State { return .{ .alloc = alloc }; }
pub fn deinit(self: *State) void { if (self.hyper_team_id) |id| self.alloc.free(id); if (self.hyper_team_name) |name| self.alloc.free(name); self.hyper_team_id = null; self.hyper_team_name = null; }
pub fn setHyperTeam(self: *State, team_id: []const u8, team_name: ?[]const u8) !void { if (self.hyper_team_id) |id| self.alloc.free(id); if (self.hyper_team_name) |name| self.alloc.free(name); self.hyper_team_id = try self.alloc.dupe(u8, team_id); self.hyper_team_name = if (team_name) |tn| try self.alloc.dupe(u8, tn) else null; }};
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,};
pub const UsageRequestKind = enum { http, command,};
pub const UsageRequest = struct { kind: UsageRequestKind = .http, // http method: []const u8 = "GET", url: []const u8 = "", headers: []const [2][]const u8 = &[_][2][]const u8{}, body: ?[]const u8 = null, // command command: []const u8 = "", args: []const []const u8 = &[_][]const u8{}, stdin: ?[]const u8 = null, timeoutMs: ?i64 = null,};
pub const Outcome = union(enum) { requests: []const UsageRequest, report: ReportOutcome, err: []const u8,};
pub const ReportOutcome = struct { accountId: ?[]const u8 = null, email: ?[]const u8 = null, planType: ?[]const u8 = null, limits: []const UsageLimit = &[_]UsageLimit{}, balance: ?[]const u8 = null, notes: []const []const u8 = &[_][]const u8{}, error_message: ?[]const u8 = null,};
/// Append-only JSON writer over an arena.pub const Writer = struct { buf: std.ArrayList(u8) = .empty, alloc: std.mem.Allocator,
pub fn init(alloc: std.mem.Allocator) Writer { return .{ .alloc = alloc }; }
pub fn raw(self: *Writer, text: []const u8) !void { try self.buf.appendSlice(self.alloc, text); }
pub fn byte(self: *Writer, c: u8) !void { try self.buf.append(self.alloc, c); }
pub fn print(self: *Writer, comptime fmt: []const u8, args: anytype) !void { const text = try std.fmt.allocPrint(self.alloc, fmt, args); try self.buf.appendSlice(self.alloc, text); }
pub fn string(self: *Writer, value: []const u8) !void { try writeJsonString(self, value); }
pub fn items(self: *Writer) []const u8 { return self.buf.items; }};
pub fn writeJsonString(w: *Writer, value: []const u8) !void { try w.byte('"'); for (value) |c| { switch (c) { '"' => try w.raw("\\\""), '\\' => try w.raw("\\\\"), '\n' => try w.raw("\\n"), '\r' => try w.raw("\\r"), '\t' => try w.raw("\\t"), else => { if (c < 0x20) { try w.print("\\u{x:0>4}", .{c}); } else { try w.byte(c); } }, } } try w.byte('"');}
pub fn asObject(value: ?std.json.Value) ?std.json.ObjectMap { const v = value orelse return null; return switch (v) { .object => |o| o, else => null, };}
pub fn asArray(value: ?std.json.Value) ?std.json.Array { const v = value orelse return null; return switch (v) { .array => |a| a, else => null, };}
pub fn asNumber(value: ?std.json.Value) ?f64 { const v = value orelse return null; const f: ?f64 = switch (v) { .integer => |i| @floatFromInt(i), .float => |fl| fl, .number_string => |s| std.fmt.parseFloat(f64, s) catch null, .string => |s| std.fmt.parseFloat(f64, s) catch null, else => null, }; if (f) |num| { if (std.math.isFinite(num)) return num; } return null;}
pub fn asString(value: ?std.json.Value) ?[]const u8 { const v = value orelse return null; return switch (v) { .string => |s| s, else => null, };}
pub fn stringOf(value: ?std.json.Value) ?[]const u8 { const s = asString(value) orelse return null; const trimmed = std.mem.trim(u8, s, " \t\r\n"); if (trimmed.len == 0) return null; return trimmed;}
pub fn toInt(comptime T: type, val: ?f64) ?T { const v = val orelse return null; if (!std.math.isFinite(v)) return null; const min_f = @as(f64, @floatFromInt(std.math.minInt(T))); const max_f = @as(f64, @floatFromInt(std.math.maxInt(T))); if (v < min_f or v > max_f) return null; return @as(T, @intFromFloat(v));}
pub fn roundToInt(comptime T: type, val: ?f64) ?T { const v = val orelse return null; if (!std.math.isFinite(v)) return null; return toInt(T, @round(v));}
pub fn statusFor(used_percent: ?f64) []const u8 { const used = used_percent orelse return "unknown"; if (!std.math.isFinite(used)) return "unknown"; if (used >= 100) return "exhausted"; if (used >= 90) return "warning"; return "ok";}
pub 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; while (parts.next()) |part| { if (part.len == 0) continue; if (!first) try res.append(alloc, ' '); first = false; try res.append(alloc, std.ascii.toUpper(part[0])); for (part[1..]) |c| { try res.append(alloc, std.ascii.toLower(c)); } } return res.items;}
pub fn formatWindowLabel(alloc: std.mem.Allocator, seconds: ?f64) !?[]const u8 { const s = seconds orelse return null; if (!std.math.isFinite(s) or !(s > 0)) return null; if (s >= 86_400) { const days = roundToInt(i64, s / 86_400.0) orelse return null; return try std.fmt.allocPrint(alloc, "{d}d window", .{days}); } const hours = roundToInt(i64, s / 3600.0) orelse return null; return try std.fmt.allocPrint(alloc, "{d}h window", .{if (hours < 1) 1 else hours});}
pub fn epochToWindowLabel(alloc: std.mem.Allocator, resets_at_ms: ?i64, now_ms: i64) !?[]const u8 { const ra = resets_at_ms orelse return null; // A reset already in the past floors to the 1h label rather than disappearing: the reference // (prime-agent's driver.ts epochToWindowLabel) does `Math.max(1, …)`, and a host that shows // nothing would look like a provider with no window at all. const diff = ra - now_ms; const diff_f = @as(f64, @floatFromInt(diff)) / 1000.0; const seconds = @max(1, roundToInt(i64, diff_f) orelse return null); if (seconds >= 86_400) { const days = roundToInt(i64, @as(f64, @floatFromInt(seconds)) / 86_400.0) orelse return null; return try std.fmt.allocPrint(alloc, "{d}d window", .{days}); } const hours = @max(1, roundToInt(i64, @as(f64, @floatFromInt(seconds)) / 3600.0) orelse return null); return try std.fmt.allocPrint(alloc, "{d}h window", .{hours});}
pub fn httpStatusError(alloc: std.mem.Allocator, response_obj: ?std.json.ObjectMap) ![]const u8 { const ro = response_obj orelse return "HTTP no response"; const status_num = asNumber(ro.get("status")); const status = toInt(i64, status_num) orelse return "HTTP no response"; const body_str = asString(ro.get("body")); if (body_str) |bs| { if (std.json.parseFromSlice(std.json.Value, alloc, bs, .{})) |parsed| { if (asObject(parsed.value)) |root| { if (root.get("error")) |err_val| { if (asString(err_val)) |err_str| { const trimmed = std.mem.trim(u8, err_str, " \t\r\n"); if (trimmed.len > 0) { return try std.fmt.allocPrint(alloc, "HTTP {d}: {s}", .{ status, trimmed }); } } else if (asObject(err_val)) |err_obj| { if (asString(err_obj.get("message"))) |msg| { const trimmed = std.mem.trim(u8, msg, " \t\r\n"); if (trimmed.len > 0) { return try std.fmt.allocPrint(alloc, "HTTP {d}: {s}", .{ status, trimmed }); } } } } } } else |_| {} } return try std.fmt.allocPrint(alloc, "HTTP {d}", .{status});}
pub const FirstResponse = struct { obj: std.json.ObjectMap, parsed: std.json.Parsed(std.json.Value),};
pub const FirstResponseOutcome = union(enum) { ok: FirstResponse, err: []const u8,};
pub fn parseFirstResponse( alloc: std.mem.Allocator, resp_array: std.json.Array, not_json_msg: []const u8,) !FirstResponseOutcome { return parseFirstResponseEx(alloc, resp_array, not_json_msg, false);}
pub fn parseFirstResponseEx( alloc: std.mem.Allocator, resp_array: std.json.Array, not_json_msg: []const u8, unwrap_http_error: bool,) !FirstResponseOutcome { if (resp_array.items.len == 0) return .{ .err = "HTTP no response" }; const resp0_obj = asObject(resp_array.items[0]) orelse return .{ .err = "HTTP no response" }; const status_num = asNumber(resp0_obj.get("status")); const status = toInt(i64, status_num); if (status == null or status.? != 200) { if (unwrap_http_error) { return .{ .err = try httpStatusError(alloc, resp0_obj) }; } const err_text = if (status) |st| try std.fmt.allocPrint(alloc, "HTTP {d}", .{st}) else "HTTP no response"; return .{ .err = err_text }; } const body_str = asString(resp0_obj.get("body")) orelse return .{ .err = not_json_msg }; const parsed = std.json.parseFromSlice(std.json.Value, alloc, body_str, .{}) catch { return .{ .err = not_json_msg }; }; return .{ .ok = .{ .obj = resp0_obj, .parsed = parsed } };}
pub fn urlEncode(alloc: std.mem.Allocator, input: []const u8) ![]const u8 { var out = std.ArrayList(u8).empty; const hex = "0123456789ABCDEF"; for (input) |c| { switch (c) { 'a'...'z', 'A'...'Z', '0'...'9', '-', '_', '.', '*' => try out.append(alloc, c), ' ' => try out.append(alloc, '+'), else => { try out.append(alloc, '%'); try out.append(alloc, hex[(c >> 4) & 0x0F]); try out.append(alloc, hex[c & 0x0F]); }, } } return out.items;}
pub fn writeStep(w: *Writer, outcome: Outcome) !void { switch (outcome) { .requests => |reqs| { try w.raw("{\"requests\":["); for (reqs, 0..) |req, i| { if (i > 0) try w.byte(','); switch (req.kind) { .http => { try w.raw("{\"kind\":\"http\",\"method\":"); try w.string(req.method); try w.raw(",\"url\":"); try w.string(req.url); try w.raw(",\"headers\":["); for (req.headers, 0..) |h, hi| { if (hi > 0) try w.byte(','); try w.raw("["); try w.string(h[0]); try w.byte(','); try w.string(h[1]); try w.raw("]"); } try w.raw("]"); if (req.body) |body| { try w.raw(",\"body\":"); try w.string(body); } try w.byte('}'); }, .command => { try w.raw("{\"kind\":\"command\",\"command\":"); try w.string(req.command); try w.raw(",\"args\":["); for (req.args, 0..) |arg, ai| { if (ai > 0) try w.byte(','); try w.string(arg); } try w.byte(']'); if (req.stdin) |st| { try w.raw(",\"stdin\":"); try w.string(st); } if (req.timeoutMs) |to| { try w.print(",\"timeoutMs\":{d}", .{to}); } try w.byte('}'); }, } } try w.raw("]}"); }, .report => |rep| { try w.raw("{\"report\":{"); var wrote = false; if (rep.accountId) |aid| { try w.raw("\"accountId\":"); try w.string(aid); wrote = true; } if (rep.email) |em| { if (wrote) try w.byte(','); try w.raw("\"email\":"); try w.string(em); wrote = true; } if (rep.planType) |pt| { if (wrote) try w.byte(','); try w.raw("\"planType\":"); try w.string(pt); wrote = true; }
if (wrote) try w.byte(','); try w.raw("\"limits\":["); for (rep.limits, 0..) |lim, i| { if (i > 0) try w.byte(','); 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)); try w.print(",\"usedPercent\":{d}", .{clamped}); } } 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); } try w.raw(",\"status\":"); try w.string(lim.status); try w.byte('}'); } try w.byte(']'); wrote = true;
if (rep.balance) |bal| { try w.raw(",\"balance\":"); try w.string(bal); } if (rep.notes.len > 0) { try w.raw(",\"notes\":["); for (rep.notes, 0..) |note, ni| { if (ni > 0) try w.byte(','); try w.string(note); } try w.byte(']'); }
try w.raw(",\"error\":"); if (rep.error_message) |err_msg| { try w.string(err_msg); } else { try w.raw("null"); } try w.raw("}}"); }, .err => |msg| { try w.raw("{\"error\":"); try w.string(msg); try w.raw("}"); }, }}
pub fn writeProviders(w: *Writer) !void { try w.raw("["); for (supported_providers, 0..) |p, i| { if (i > 0) try w.byte(','); try w.raw("{\"provider\":"); try w.string(p.provider); try w.raw(",\"providerName\":"); try w.string(p.providerName); try w.raw(",\"credentialKinds\":["); for (p.credentialKinds, 0..) |k, ki| { if (ki > 0) try w.byte(','); try w.string(k); } try w.raw("]}"); } try w.raw("]");}
// Re-export providerspub const codex = @import("providers/codex.zig");pub const anthropic = @import("providers/anthropic.zig");pub const antigravity = @import("providers/antigravity.zig");pub const hyper = @import("providers/hyper.zig");pub const opencode_go = @import("providers/opencode_go.zig");pub const zai = @import("providers/zai.zig");pub const deepseek = @import("providers/deepseek.zig");pub const alibaba = @import("providers/alibaba.zig");
const ProviderDispatch = struct { name: []const u8, advance: *const fn (std.mem.Allocator, ?*State, []const u8, []const u8, i64) anyerror!Outcome,};
fn wrapStateless(comptime func: anytype) *const fn (std.mem.Allocator, ?*State, []const u8, []const u8, i64) anyerror!Outcome { return struct { fn call(alloc: std.mem.Allocator, state: ?*State, cred: []const u8, resp: []const u8, now_ms: i64) anyerror!Outcome { _ = state; return func(alloc, cred, resp, now_ms); } }.call;}
const provider_table = [_]ProviderDispatch{ .{ .name = "alibaba", .advance = wrapStateless(alibaba.advance) }, .{ .name = "anthropic", .advance = wrapStateless(anthropic.advance) }, .{ .name = "deepseek", .advance = wrapStateless(deepseek.advance) }, .{ .name = "google-antigravity", .advance = wrapStateless(antigravity.advance) }, .{ .name = "hyper", .advance = hyper.advance }, .{ .name = "openai-codex", .advance = wrapStateless(codex.advance) }, .{ .name = "opencode-go", .advance = wrapStateless(opencode_go.advance) }, .{ .name = "zai", .advance = wrapStateless(zai.advance) },};
pub fn advance( alloc: std.mem.Allocator, state: ?*State, provider: []const u8, credential_json: []const u8, responses_json: []const u8, now_ms: i64,) !Outcome { inline for (provider_table) |entry| { if (std.mem.eql(u8, provider, entry.name)) { return entry.advance(alloc, state, credential_json, responses_json, now_ms); } } return .{ .err = "unknown provider" };}
pub const ClaimsOutcome = union(enum) { ok, err: []const u8 };
pub fn claimsFromAccessToken(alloc: std.mem.Allocator, jwt: []const u8, w: *Writer) !ClaimsOutcome { const payload = jwtPayload(alloc, jwt) catch return .{ .err = "access token is not a JWT" }; defer alloc.free(payload);
var parsed = std.json.parseFromSlice(std.json.Value, alloc, payload, .{}) catch { return .{ .err = "access token claims are not JSON" }; }; defer parsed.deinit();
const root = asObject(parsed.value) orelse return .{ .err = "access token claims are not an object" }; const auth = asObject(root.get("https://api.openai.com/auth")); const profile = asObject(root.get("https://api.openai.com/profile"));
const account_id = if (auth) |a| asString(a.get("chatgpt_account_id")) else null; const email_claim = if (profile) |p| asString(p.get("email")) else null; const email: ?[]const u8 = if (email_claim) |raw| blk: { const trimmed = std.mem.trim(u8, raw, " \t\r\n"); if (trimmed.len == 0) break :blk null; const lowered = try alloc.alloc(u8, trimmed.len); break :blk std.ascii.lowerString(lowered, trimmed); } else null;
try w.raw("{"); var wrote = false; if (account_id) |id| { try w.raw("\"accountId\":"); try w.string(id); wrote = true; } if (email) |value| { if (wrote) try w.byte(','); try w.raw("\"email\":"); try w.string(value); } try w.byte('}'); return .ok;}
pub fn jwtPayload(alloc: std.mem.Allocator, jwt: []const u8) ![]u8 { var parts = std.mem.splitScalar(u8, jwt, '.'); _ = parts.next() orelse return error.NotAJwt; const encoded = parts.next() orelse return error.NotAJwt; _ = parts.next() orelse return error.NotAJwt;
const decoder = std.base64.url_safe_no_pad.Decoder; const size = try decoder.calcSizeForSlice(encoded); const decoded = try alloc.alloc(u8, size); errdefer alloc.free(decoded); try decoder.decode(decoded, encoded); return decoded;}