Something went wrong. Try again.
zig library for building requests to query inference providers usage limits
Something went wrong. Try again.
7.4 kB · 184 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185//! Google Antigravity usage feed implementation.//!//! Mirrors prime-agent:packages/ai/src/utils/usage/providers/antigravity.ts field for field.const std = @import("std");const usage = @import("../usage.zig");const iso8601 = @import("../iso8601.zig");
pub const ANTIGRAVITY_USAGE_URL = "https://daily-cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota";pub const ANTIGRAVITY_USER_AGENT = "antigravity/hub/2.8.0 (aidev_client; os_type=darwin; arch=arm64; cl=963137146)";
const FamilyInfo = struct { min_remaining: ?f64 = null, reset_time: ?[]const u8 = null, present: bool = false,};
fn modelFamily(alloc: std.mem.Allocator, model_id: []const u8) !?[]const u8 { const lower = try alloc.alloc(u8, model_id.len); _ = std.ascii.lowerString(lower, model_id); if (std.mem.startsWith(u8, lower, "chat_") or std.mem.startsWith(u8, lower, "tab_")) { return null; } if (std.mem.indexOf(u8, lower, "gemini") != null) return "Gemini"; if (std.mem.indexOf(u8, lower, "claude") != null) return "Claude"; if (std.mem.indexOf(u8, lower, "gpt") != null) return "GPT-OSS"; return null;}
pub fn parseAntigravityUsage(alloc: std.mem.Allocator, payload: ?std.json.Value, now_ms: i64) ![]const usage.UsageLimit { _ = now_ms; const root = usage.asObject(payload); const buckets = if (root) |r| usage.asArray(r.get("buckets")) else null;
var gemini = FamilyInfo{}; var claude = FamilyInfo{}; var gpt = FamilyInfo{};
if (buckets) |arr| { for (arr.items) |entry| { const bucket = usage.asObject(entry) orelse continue; const model_id = usage.asString(bucket.get("modelId")) orelse ""; const family = try modelFamily(alloc, model_id) orelse continue;
const remaining_fraction = usage.asNumber(bucket.get("remainingFraction")); const reset_time = usage.asString(bucket.get("resetTime"));
const target: *FamilyInfo = if (std.mem.eql(u8, family, "Gemini")) &gemini else if (std.mem.eql(u8, family, "Claude")) &claude else &gpt;
if (!target.present) { target.present = true; target.min_remaining = remaining_fraction; target.reset_time = reset_time; } else { if (remaining_fraction != null and (target.min_remaining == null or remaining_fraction.? < target.min_remaining.?)) { target.min_remaining = remaining_fraction; target.reset_time = reset_time; } else if (target.reset_time == null and reset_time != null) { target.reset_time = reset_time; } } } }
var limits = std.ArrayList(usage.UsageLimit).empty; 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; const rem = f.info.min_remaining;
const used_percent: ?f64 = if (rem) |r| @max(0.0, @min(100.0, @round((1.0 - r) * 1000.0) / 10.0)) else null;
const resets_at: ?i64 = if (f.info.reset_time) |rt| iso8601.parseIso8601(rt) else null; // The feed states when a family resets, never how long its window is, so there is // no honest label: `epochToWindowLabel` would print the time left, which reads as // "3h window" and then "1h window" for one fixed five-hour window.
const detail: ?[]const u8 = if (rem) |r| blk: { const free_pct = usage.roundToInt(i64, r * 100.0) orelse break :blk null; break :blk try std.fmt.allocPrint(alloc, "{d}% free", .{free_pct}); } 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, .resetsAt = resets_at, .scope = f.label, .detail = detail, .status = status, }); }
return limits.items;}
pub fn advance( alloc: std.mem.Allocator, credential_json: []const u8, responses_json: []const u8, now_ms: i64,) !usage.Outcome { const cred_parsed = std.json.parseFromSlice(std.json.Value, alloc, credential_json, .{}) catch { return .{ .report = .{ .error_message = "invalid credential JSON" } }; }; const cred_root = usage.asObject(cred_parsed.value) orelse { return .{ .report = .{ .error_message = "invalid credential JSON" } }; }; const kind = usage.asString(cred_root.get("kind")) orelse ""; if (!std.mem.eql(u8, kind, "oauth")) { return .{ .report = .{ .error_message = "google-antigravity needs an OAuth account" } }; }
const access = usage.asString(cred_root.get("access")) orelse ""; const extra_obj = usage.asObject(cred_root.get("extra")); const project_id = if (extra_obj) |eo| usage.asString(eo.get("projectId")) else null; const cred_account_id = usage.asString(cred_root.get("accountId")); const account_id = cred_account_id orelse project_id; const email = usage.asString(cred_root.get("email"));
const resp_parsed = std.json.parseFromSlice(std.json.Value, alloc, responses_json, .{}) catch { return .{ .report = .{ .error_message = "invalid responses JSON" } }; }; const resp_array = usage.asArray(resp_parsed.value) orelse { return .{ .report = .{ .error_message = "invalid responses JSON" } }; };
if (resp_array.items.len == 0) { var headers = std.ArrayList([2][]const u8).empty; const auth_hdr = try std.fmt.allocPrint(alloc, "Bearer {s}", .{access}); try headers.append(alloc, .{ "Authorization", auth_hdr }); try headers.append(alloc, .{ "Content-Type", "application/json" }); try headers.append(alloc, .{ "User-Agent", ANTIGRAVITY_USER_AGENT });
const requests = try alloc.alloc(usage.UsageRequest, 1); requests[0] = .{ .method = "POST", .url = ANTIGRAVITY_USAGE_URL, .headers = headers.items, .body = "{}", }; return .{ .requests = requests }; }
const first = switch (try usage.parseFirstResponseEx(alloc, resp_array, "usage body is not JSON", true)) { .ok => |f| f, .err => |msg| return .{ .report = .{ .error_message = msg } }, }; const body_parsed = first.parsed;
const limits = try parseAntigravityUsage(alloc, body_parsed.value, now_ms);
const notes: []const []const u8 = if (project_id) |pid| blk: { const arr = try alloc.alloc([]const u8, 1); arr[0] = try std.fmt.allocPrint(alloc, "Project: {s}", .{pid}); break :blk arr; } else &[_][]const u8{};
return .{ .report = .{ .planType = "Cloud Code", .accountId = account_id, .email = email, .limits = limits, .notes = notes, .error_message = null, }, };}