Something went wrong. Try again.
zig library for building requests to query inference providers usage limits
Something went wrong. Try again.
26 kB · 656 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657//! Alibaba Model Studio usage feed implementation.//!//! Mirrors prime-agent:packages/ai/src/utils/usage/providers/alibaba.ts field for field.const std = @import("std");const usage = @import("../usage.zig");
pub const AlibabaRegion = enum { intl, cn, @"intl-personal", @"cn-personal",
pub fn toString(self: AlibabaRegion) []const u8 { return switch (self) { .intl => "intl", .cn => "cn", .@"intl-personal" => "intl-personal", .@"cn-personal" => "cn-personal", }; }
pub fn fromString(str: []const u8) ?AlibabaRegion { if (std.mem.eql(u8, str, "intl")) return .intl; if (std.mem.eql(u8, str, "cn")) return .cn; if (std.mem.eql(u8, str, "intl-personal")) return .@"intl-personal"; if (std.mem.eql(u8, str, "cn-personal")) return .@"cn-personal"; return null; }};
pub const RegionInfo = struct { currentRegionId: []const u8, cliConsoleSite: []const u8, quotaHost: []const u8, productCode: []const u8,};
pub fn getRegionInfo(region: AlibabaRegion) RegionInfo { return switch (region) { .intl => .{ .currentRegionId = "ap-southeast-1", .cliConsoleSite = "international", .quotaHost = "https://modelstudio.console.alibabacloud.com", .productCode = "sfm_tokenplanteams_dp_intl", }, .cn => .{ .currentRegionId = "cn-beijing", .cliConsoleSite = "domestic", .quotaHost = "https://bailian.console.aliyun.com", .productCode = "sfm_tokenplanteams_dp_cn", }, .@"intl-personal" => .{ .currentRegionId = "ap-southeast-1", .cliConsoleSite = "international", .quotaHost = "https://bailian-singapore-cs.alibabacloud.com", .productCode = "sfm_tokenplansolo_public_intl", }, .@"cn-personal" => .{ .currentRegionId = "cn-beijing", .cliConsoleSite = "domestic", .quotaHost = "https://bailian-cs.console.aliyun.com", .productCode = "sfm_tokenplansolo_public_cn", }, };}
pub const ALIBABA_CLI_TIMEOUT_MS: i64 = 15_000;
pub fn alibabaCliArguments(alloc: std.mem.Allocator, region: AlibabaRegion) ![]const []const u8 { const info = getRegionInfo(region); const args = try alloc.alloc([]const u8, 8); args[0] = "usage"; args[1] = "token-plan"; args[2] = "--console-region"; args[3] = info.currentRegionId; args[4] = "--console-site"; args[5] = info.cliConsoleSite; args[6] = "--output"; args[7] = "json"; return args;}
pub const ALIBABA_USAGE_API = "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage";pub const ALIBABA_SUBSCRIPTION_API = "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/subscription";pub const ALIBABA_QUOTA_CONFIG_API = "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/quota-config";pub const ALIBABA_RESET_CARD_API = "zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/reset-card/list";
/// Unwrap the console's envelope (`data.DataV2.data.data`) when present. The CLI answers with the/// inner payload directly, so one parser covers both.pub fn unwrapEnvelope(value: ?std.json.Value) ?std.json.Value { const root = usage.asObject(value) orelse return value; const data = usage.asObject(root.get("data")); if (data) |d| { const data_v2 = usage.asObject(d.get("DataV2")); if (data_v2) |v2| { const v2_data = usage.asObject(v2.get("data")); if (v2_data) |v2d| { if (v2d.get("data")) |inner| return inner; } } if (d.get("data")) |inner| return inner; } return value;}
pub const ParseUsageResult = struct { limits: []const usage.UsageLimit = &[_]usage.UsageLimit{}, notes: []const []const u8 = &[_][]const u8{},};
pub fn parseAlibabaUsage(alloc: std.mem.Allocator, payload: ?std.json.Value) !ParseUsageResult { const inner_val = unwrapEnvelope(payload); const inner = usage.asObject(inner_val); var limits = std.ArrayList(usage.UsageLimit).empty;
if (inner) |ud| { // Session: per5HourPercentage (0..1 fraction) -> percent if (usage.asNumber(ud.get("per5HourPercentage"))) |sf| { 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), }); }
// Weekly: per1WeekPercentage (0..1 fraction) -> percent if (usage.asNumber(ud.get("per1WeekPercentage"))) |wf| { 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), }); } }
return .{ .limits = limits.items, .notes = &[_][]const u8{}, };}
pub fn parseAlibabaQuotaConfig( alloc: std.mem.Allocator, payload: ?std.json.Value, plan_type: ?[]const u8,) ![]const []const u8 { const inner_val = unwrapEnvelope(payload); const tiers = usage.asObject(inner_val) orelse return &[_][]const u8{}; const pt = plan_type orelse return &[_][]const u8{};
const lowered = try alloc.alloc(u8, pt.len); _ = std.ascii.lowerString(lowered, pt);
const tier = usage.asObject(tiers.get(lowered)) orelse return &[_][]const u8{}; const weekly = usage.asNumber(tier.get("weekly")); const five_hour = usage.asNumber(tier.get("five_hour"));
if (weekly == null and five_hour == null) return &[_][]const u8{};
var note_buf = std.ArrayList(u8).empty; try note_buf.appendSlice(alloc, "Token plan "); try note_buf.appendSlice(alloc, pt); try note_buf.appendSlice(alloc, ": ");
var parts_wrote = false; if (usage.toInt(i64, weekly)) |w| { const w_str = try std.fmt.allocPrint(alloc, "weekly {d}", .{w}); try note_buf.appendSlice(alloc, w_str); parts_wrote = true; } if (usage.toInt(i64, five_hour)) |fh| { if (parts_wrote) try note_buf.appendSlice(alloc, ", "); const fh_str = try std.fmt.allocPrint(alloc, "5h {d}", .{fh}); try note_buf.appendSlice(alloc, fh_str); } try note_buf.appendSlice(alloc, " credits");
const notes = try alloc.alloc([]const u8, 1); notes[0] = note_buf.items; return notes;}
pub const ConsoleSession = struct { host: []const u8, region: []const u8, secToken: ?[]const u8 = null, headers: ?std.json.ObjectMap = null,};
pub fn parseConsoleSession(session_val: ?std.json.Value, default_host: []const u8) ?ConsoleSession { const session = usage.asObject(session_val) orelse return null; const region = usage.stringOf(session.get("region")) orelse return null; const host = usage.stringOf(session.get("url")) orelse default_host; const secToken = usage.stringOf(session.get("secToken")); const headers = usage.asObject(session.get("headers")); return .{ .host = host, .region = region, .secToken = secToken, .headers = headers, };}
pub fn regionFor(cred_root: ?std.json.ObjectMap, session: ?ConsoleSession) AlibabaRegion { const root = cred_root orelse return .@"intl-personal"; const kind = usage.asString(root.get("kind")) orelse ""; if (std.mem.eql(u8, kind, "cli")) { const config = usage.asObject(root.get("config")); if (config) |c| { if (usage.stringOf(c.get("region"))) |r_str| { if (AlibabaRegion.fromString(r_str)) |r| return r; } } } if (std.mem.eql(u8, kind, "session") and session != null) { if (std.mem.startsWith(u8, session.?.region, "cn")) { return .@"cn-personal"; } else { return .@"intl-personal"; } } return .@"intl-personal";}
pub fn buildGatewayUrl(alloc: std.mem.Allocator, host: []const u8, api: []const u8) ![]const u8 { const base = if (std.mem.indexOf(u8, host, "/data/api.json") != null) host else try std.fmt.allocPrint(alloc, "{s}/data/api.json", .{host});
const clean_base = if (std.mem.indexOfScalar(u8, base, '?')) |idx| base[0..idx] else base; const enc_api = try usage.urlEncode(alloc, api); return try std.fmt.allocPrint(alloc, "{s}?action=IntlBroadScopeAspnGateway&product=sfm_bailian&api={s}&_v=", .{ clean_base, enc_api });}
pub fn buildFormBody( alloc: std.mem.Allocator, api: []const u8, region: AlibabaRegion, session: ConsoleSession,) ![]const u8 { const info = getRegionInfo(region); var pw = usage.Writer.init(alloc); try pw.raw("{\"Api\":"); try pw.string(api); try pw.raw(",\"V\":\"1.0\",\"Data\":{\"cornerstoneParam\":{\"protocol\":\"V2\",\"console\":\"ONE_CONSOLE\",\"productCode\":\"p_efm\",\"domain\":\"modelstudio.console.alibabacloud.com\",\"consoleSite\":\"MODELSTUDIO_ALBABACLOUD\",\"xsp_lang\":\"en-US\",\"feUrl\":"); const fe_url = try std.fmt.allocPrint(alloc, "https://modelstudio.console.alibabacloud.com/{s}/subscription/token-plan/personal", .{info.currentRegionId}); try pw.string(fe_url); try pw.raw("}");
if (std.mem.eql(u8, api, ALIBABA_SUBSCRIPTION_API)) { try pw.raw(",\"queryInstanceInfoRequest\":{\"commodityCode\":"); try pw.string(info.productCode); try pw.raw("}"); } try pw.raw("}}");
const enc_params = try usage.urlEncode(alloc, pw.items()); const enc_region = try usage.urlEncode(alloc, session.region);
if (session.secToken) |tok| { if (tok.len > 0) { const enc_tok = try usage.urlEncode(alloc, tok); return try std.fmt.allocPrint(alloc, "params={s}®ion={s}&sec_token={s}", .{ enc_params, enc_region, enc_tok }); } } return try std.fmt.allocPrint(alloc, "params={s}®ion={s}", .{ enc_params, enc_region });}
pub fn consoleRequests( alloc: std.mem.Allocator, session: ConsoleSession, region: AlibabaRegion,) ![]const usage.UsageRequest { const apis = [_][]const u8{ ALIBABA_USAGE_API, ALIBABA_SUBSCRIPTION_API, ALIBABA_QUOTA_CONFIG_API, ALIBABA_RESET_CARD_API, };
var base_headers = std.ArrayList([2][]const u8).empty; if (session.headers) |sh| { var it = sh.iterator(); while (it.next()) |entry| { if (usage.asString(entry.value_ptr.*)) |val| { const key_lower = try alloc.alloc(u8, entry.key_ptr.*.len); _ = std.ascii.lowerString(key_lower, entry.key_ptr.*); if (!std.mem.eql(u8, key_lower, "content-type") and !std.mem.eql(u8, key_lower, "accept") and !std.mem.eql(u8, key_lower, "origin") and !std.mem.eql(u8, key_lower, "referer")) { try base_headers.append(alloc, .{ key_lower, val }); } } } }
try base_headers.append(alloc, .{ "content-type", "application/x-www-form-urlencoded" }); try base_headers.append(alloc, .{ "accept", "*/*" }); try base_headers.append(alloc, .{ "origin", "https://modelstudio.console.alibabacloud.com" }); const referer = try std.fmt.allocPrint(alloc, "https://modelstudio.console.alibabacloud.com/{s}/subscription/token-plan/personal", .{session.region}); try base_headers.append(alloc, .{ "referer", referer });
const requests = try alloc.alloc(usage.UsageRequest, apis.len); for (apis, 0..) |api, i| { const url = try buildGatewayUrl(alloc, session.host, api); const body = try buildFormBody(alloc, api, region, session); requests[i] = .{ .kind = .http, .method = "POST", .url = url, .headers = base_headers.items, .body = body, }; } return requests;}
fn getResetCardsCount(body_str: ?[]const u8, alloc: std.mem.Allocator) ?usize { const bs = body_str orelse return null; const parsed = std.json.parseFromSlice(std.json.Value, alloc, bs, .{}) catch return null; if (usage.asArray(parsed.value)) |arr| { return arr.items.len; } const unwrapped = unwrapEnvelope(parsed.value); if (usage.asArray(unwrapped)) |arr| { return arr.items.len; } return null;}
pub fn reportFromConsole( alloc: std.mem.Allocator, cred_root: ?std.json.ObjectMap, resp_array: std.json.Array,) !usage.Outcome { const cred_email = if (cred_root) |cr| usage.stringOf(cr.get("email")) else null;
if (resp_array.items.len == 0) { return .{ .report = .{ .error_message = "console gateway HTTP no response" } }; }
// Match responses: first try by api in envelope, otherwise positional var usage_resp: ?std.json.ObjectMap = null; var sub_resp: ?std.json.ObjectMap = null; var quota_resp: ?std.json.ObjectMap = null; var card_resp: ?std.json.ObjectMap = null;
for (resp_array.items) |r_val| { const r_obj = usage.asObject(r_val) orelse continue; const b_str = usage.asString(r_obj.get("body")) orelse continue; if (std.json.parseFromSlice(std.json.Value, alloc, b_str, .{})) |parsed| { if (usage.asObject(parsed.value)) |root| { const data_obj = usage.asObject(root.get("data")); const api = if (data_obj) |d| usage.asString(d.get("api")) else usage.asString(root.get("api")); if (api) |a| { if (std.mem.eql(u8, a, ALIBABA_USAGE_API)) { usage_resp = r_obj; } else if (std.mem.eql(u8, a, ALIBABA_SUBSCRIPTION_API)) { sub_resp = r_obj; } else if (std.mem.eql(u8, a, ALIBABA_QUOTA_CONFIG_API)) { quota_resp = r_obj; } else if (std.mem.eql(u8, a, ALIBABA_RESET_CARD_API)) { card_resp = r_obj; } } } } else |_| {} }
if (usage_resp == null and resp_array.items.len > 0) usage_resp = usage.asObject(resp_array.items[0]); if (sub_resp == null and resp_array.items.len > 1) sub_resp = usage.asObject(resp_array.items[1]); if (quota_resp == null and resp_array.items.len > 2) quota_resp = usage.asObject(resp_array.items[2]); if (card_resp == null and resp_array.items.len > 3) card_resp = usage.asObject(resp_array.items[3]);
const status_num = if (usage_resp) |ro| usage.asNumber(ro.get("status")) else null; const status = usage.toInt(i64, status_num); if (status == null or status.? != 200) { const err_text = if (status) |st| try std.fmt.allocPrint(alloc, "console gateway HTTP {d}", .{st}) else "console gateway HTTP no response"; return .{ .report = .{ .error_message = err_text } }; }
const usage_b_str = if (usage_resp) |ro| usage.asString(ro.get("body")) else null; if (usage_b_str == null) { return .{ .report = .{ .error_message = "console gateway HTTP no response" } }; } const usage_parsed = std.json.parseFromSlice(std.json.Value, alloc, usage_b_str.?, .{}) catch { return .{ .report = .{ .error_message = "console gateway HTTP no response" } }; };
const parsed = try parseAlibabaUsage(alloc, usage_parsed.value);
// Subscription response var sub_val: ?std.json.Value = null; if (sub_resp) |ro| { if (usage.asString(ro.get("body"))) |bs| { if (std.json.parseFromSlice(std.json.Value, alloc, bs, .{})) |p| { sub_val = p.value; } else |_| {} } }
const sub_unwrapped = unwrapEnvelope(sub_val); const sub_obj = usage.asObject(sub_unwrapped); const plan_type = if (sub_obj) |so| usage.stringOf(so.get("specCode")) else null; const instance_code = if (sub_obj) |so| usage.stringOf(so.get("instanceCode")) else null; const remaining_days = if (sub_obj) |so| usage.asNumber(so.get("remainingDays")) else null;
// Quota config response var quota_val: ?std.json.Value = null; if (quota_resp) |ro| { if (usage.asString(ro.get("body"))) |bs| { if (std.json.parseFromSlice(std.json.Value, alloc, bs, .{})) |p| { quota_val = p.value; } else |_| {} } } const quota_notes = try parseAlibabaQuotaConfig(alloc, quota_val, plan_type);
var notes = std.ArrayList([]const u8).empty; for (parsed.notes) |n| try notes.append(alloc, n); for (quota_notes) |n| try notes.append(alloc, n);
if (usage.toInt(i64, remaining_days)) |rd| { const note = try std.fmt.allocPrint(alloc, "{d} days remaining in the plan", .{rd}); try notes.append(alloc, note); }
if (card_resp) |ro| { const card_b_str = usage.asString(ro.get("body")); if (getResetCardsCount(card_b_str, alloc)) |count| { if (count > 0) { const note = try std.fmt.allocPrint(alloc, "{d} reset cards available", .{count}); try notes.append(alloc, note); } } }
const err_msg: ?[]const u8 = if (parsed.limits.len == 0) "the token plan reported no windows" else null;
return .{ .report = .{ .limits = parsed.limits, .planType = plan_type, .accountId = instance_code, .email = cred_email, .notes = notes.items, .error_message = err_msg, }, };}
pub const CliReportOutcome = struct { report: ?usage.ReportOutcome = null, reason: ?[]const u8 = null,};
/// The human part of a CLI failure. A JSON error body carries its own message and usually a hint —/// "No console access token found. Run `bl auth login --console`." — which is the action the user/// needs; anything else explains itself in its first non-empty line. JSON without a message says/// nothing useful, so the reason stays the bare exit code rather than quoting a brace.fn cliFailureDetail(alloc: std.mem.Allocator, text: []const u8) !?[]const u8 { if (std.json.parseFromSlice(std.json.Value, alloc, text, .{})) |parsed_val| { const root = usage.asObject(parsed_val.value); const err_obj = if (root) |r| usage.asObject(r.get("error")) else null; const message = if (err_obj) |e| usage.stringOf(e.get("message")) else null; if (message) |msg| { if (msg.len == 0) return null; if (err_obj) |e| { if (usage.stringOf(e.get("hint"))) |hint| { if (hint.len > 0) { const join: []const u8 = if (msg[msg.len - 1] == '.') " " else ". "; const joined: ?[]const u8 = try std.fmt.allocPrint(alloc, "{s}{s}{s}", .{ msg, join, hint }); return joined; } } } const bare: ?[]const u8 = msg; return bare; } return null; } else |_| {} var lines = std.mem.splitScalar(u8, text, '\n'); while (lines.next()) |raw| { const line = std.mem.trim(u8, raw, " \t\r"); if (line.len > 0) { const found: ?[]const u8 = line[0..@min(line.len, 120)]; return found; } } return null;}
/// A CLI that wrote to stderr before failing has already explained itself, and that explanation is/// more useful than the exit code: "bl is not on PATH" beats "exit 126". The body is provider-visible/// text, so it is trimmed to one bounded line before it goes into a report.fn cliFailureReason(alloc: std.mem.Allocator, status: i64, resp0: std.json.ObjectMap) ![]const u8 { if (usage.stringOf(resp0.get("body"))) |text| { if (try cliFailureDetail(alloc, text)) |detail| { const capped = detail[0..@min(detail.len, 160)]; return std.fmt.allocPrint(alloc, "bailian CLI failed (exit {d}): {s}", .{ status, capped }); } } return std.fmt.allocPrint(alloc, "bailian CLI failed (exit {d})", .{status});}
fn reportFromCli( alloc: std.mem.Allocator, cred_root: ?std.json.ObjectMap, resp0: std.json.ObjectMap, region: AlibabaRegion,) !CliReportOutcome { const cred_account_id = if (cred_root) |cr| usage.stringOf(cr.get("accountId")) else null; const cred_email = if (cred_root) |cr| usage.stringOf(cr.get("email")) else null;
const status = usage.toInt(i64, usage.asNumber(resp0.get("status"))) orelse -1;
if (status != 0 and status != 200) { const reason = try cliFailureReason(alloc, status, resp0); return .{ .reason = reason }; }
const body_str = usage.asString(resp0.get("body")); if (body_str) |bs| { if (std.json.parseFromSlice(std.json.Value, alloc, bs, .{})) |parsed_val| { const parsed = try parseAlibabaUsage(alloc, parsed_val.value); if (parsed.limits.len > 0) { const note = try std.fmt.allocPrint(alloc, "read through the bailian CLI ({s})", .{region.toString()}); const notes = try alloc.alloc([]const u8, 1); notes[0] = note; return .{ .report = .{ .limits = parsed.limits, .accountId = cred_account_id, .email = cred_email, .notes = notes, .error_message = null, }, }; } } else |_| {} }
return .{ .reason = "the bailian CLI output carried no token-plan windows" };}
pub fn advance( alloc: std.mem.Allocator, credential_json: []const u8, responses_json: []const u8, _now_ms: i64,) !usage.Outcome { _ = _now_ms; 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);
const default_host = "https://bailian-singapore-cs.alibabacloud.com"; const session_val = if (cred_root) |cr| cr.get("session") else null; const session = parseConsoleSession(session_val, default_host); const region = regionFor(cred_root, session);
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) { // Round 1: Official CLI first const cli_args = try alibabaCliArguments(alloc, region); const requests = try alloc.alloc(usage.UsageRequest, 1); requests[0] = .{ .kind = .command, .command = "bl", .args = cli_args, .timeoutMs = ALIBABA_CLI_TIMEOUT_MS, }; return .{ .requests = requests }; }
if (resp_array.items.len == 1) { const resp0_obj = usage.asObject(resp_array.items[0]); const status = if (resp0_obj) |ro| (usage.toInt(i64, usage.asNumber(ro.get("status"))) orelse -1) else -1; const unavailable = (status == 127);
if (!unavailable) { if (resp0_obj) |ro| { const cli_outcome = try reportFromCli(alloc, cred_root, ro, region); if (cli_outcome.report) |rep| { return .{ .report = rep }; } if (session == null) { const notes = try alloc.alloc([]const u8, 1); notes[0] = "a captured console session would be the fallback"; return .{ .report = .{ .limits = &[_]usage.UsageLimit{}, .error_message = cli_outcome.reason orelse "the bailian CLI gave no usable output", .notes = notes, }, }; } } else if (session == null) { const notes = try alloc.alloc([]const u8, 1); notes[0] = "a captured console session would be the fallback"; return .{ .report = .{ .limits = &[_]usage.UsageLimit{}, .error_message = "the bailian CLI gave no usable output", .notes = notes, }, }; } } else if (session == null) { return .{ .report = .{ .limits = &[_]usage.UsageLimit{}, .error_message = "needs a session credential when the host runs no commands; capture a console HAR with prime-agent's scripts/capture-alibaba-session.mjs", }, }; }
return .{ .requests = try consoleRequests(alloc, session.?, region) }; }
if (session == null) { return .{ .report = .{ .limits = &[_]usage.UsageLimit{}, .error_message = "alibaba needs a cli or session credential", }, }; }
return try reportFromConsole(alloc, cred_root, resp_array);}