//! 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{ "oauth", "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 providers
pub 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;
}