Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246import { epochToWindowLabel, isoToEpochMs, jsonBody, makeLimit, unknownReport } from "../driver.js";import { defineUsageFeed } from "../registry.js";import type { UsageCredential, UsageLimit, UsageReportBody, UsageRequest, UsageResponse, UsageState, UsageStep,} from "../types.js";
/** Teams endpoint for Charm Hyper account and quota resolution */export const HYPER_TEAMS_URL = "https://hyper.charm.land/v1/teams";
function asRecord(value: unknown): Record<string, unknown> | undefined { return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;}
function asNumber(value: unknown): number | undefined { if (typeof value === "number" && Number.isFinite(value)) return value; if (typeof value === "string" && value.trim()) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : undefined; } return undefined;}
/** * Parses Charm Hyper team billing payload. * `plan.hypercredits` is the total recurring allocation. * `balances.plan.hypercredits` is the remaining quota. * `hypercredit_refresh.next_at` is the ISO reset timestamp. * `balances.total.usd` is the dollar balance. */export function parseHyperBilling( payload: unknown, nowMs: number,): { planType?: string; limits: UsageLimit[]; balance?: string } { const root = asRecord(payload); const plan = asRecord(root?.plan); const balances = asRecord(root?.balances); const planBalances = asRecord(balances?.plan); const totalBalances = asRecord(balances?.total); const refresh = asRecord(root?.hypercredit_refresh);
const planType = typeof plan?.name === "string" && plan.name.trim() ? plan.name.toLowerCase() : typeof plan?.type === "string" && plan.type.trim() ? plan.type.toLowerCase() : undefined;
// Unit converted FROM: hypercredits remaining count (balances.plan.hypercredits) and plan allocation (plan.hypercredits) const limit = asNumber(plan?.hypercredits); const remaining = asNumber(planBalances?.hypercredits);
let usedPercent: number | undefined; if (limit !== undefined && limit > 0 && remaining !== undefined) { const used = Math.max(0, limit - remaining); // Stored as percent (0..100) rounded to 1 decimal place usedPercent = Math.max(0, Math.min(100, Math.round((used / limit) * 1000) / 10)); } else if (remaining !== undefined && remaining <= 0) { usedPercent = 100; }
// Unit converted FROM: ISO-8601 string (hypercredit_refresh.next_at) to epoch milliseconds const resetsAt = isoToEpochMs(refresh?.next_at); // Unit converted FROM: ISO-8601 string (hypercredit_refresh.last_at) to epoch milliseconds const lastAt = isoToEpochMs(refresh?.last_at);
let windowLabel: string | undefined; if (resetsAt !== undefined && lastAt !== undefined) { // Unit converted FROM: difference in milliseconds between next_at and last_at to seconds const diffSec = Math.round((resetsAt - lastAt) / 1000); if (diffSec >= 86_400) { const days = Math.round(diffSec / 86_400); windowLabel = days === 1 ? "24h window" : `${days}d window`; } else if (diffSec > 0) { windowLabel = `${Math.max(1, Math.round(diffSec / 3600))}h window`; } } else { windowLabel = epochToWindowLabel(resetsAt, nowMs); }
const status = root?.status !== "active" ? "unknown" : usedPercent === undefined ? "unknown" : usedPercent >= 100 ? "exhausted" : usedPercent >= 90 ? "warning" : "ok";
const detail = remaining !== undefined && limit !== undefined ? `${remaining} of ${limit} credits` : undefined;
const limits: UsageLimit[] = [ makeLimit("Primary", usedPercent, { windowLabel, resetsAt, status, detail, }), ];
// Unit converted FROM: USD dollar amount string (balances.total.usd) const balance = typeof totalBalances?.usd === "string" && totalBalances.usd.trim() ? totalBalances.usd.trim() : typeof totalBalances?.usd === "number" ? totalBalances.usd.toFixed(2) : undefined;
return { planType, limits, balance };}
function httpStatusError(response: UsageResponse | undefined): string { if (!response) return "HTTP no response"; const payload = jsonBody<Record<string, unknown>>(response); const error = payload?.error; if (typeof error === "string" && error.trim()) { return `HTTP ${response.status}: ${error.trim()}`; } if (error && typeof error === "object" && !Array.isArray(error)) { const message = (error as Record<string, unknown>).message; if (typeof message === "string" && message.trim()) { return `HTTP ${response.status}: ${message.trim()}`; } } return `HTTP ${response.status}`;}
function resolveToken(credential: UsageCredential): string | undefined { if (credential.kind === "oauth") return credential.access; if (credential.kind === "apiKey") return credential.apiKey; return undefined;}
/** Per-credential session state across rounds in a runUsageFeed execution */const sessionCache = new WeakMap<UsageCredential, { teamId?: string; teamName?: string }>();
export const hyperUsageFeed = defineUsageFeed({ provider: "hyper", providerName: "Charm Hyper", credentialKinds: ["oauth", "apiKey"], advance(state: UsageState, responses): UsageStep { const credential = state.credential; const token = resolveToken(credential); if (!token) { return { report: unknownReport("hyper needs an OAuth or apiKey credential") }; }
const headers: Record<string, string> = { Authorization: `Bearer ${token}`, };
// Round 0: Ask for teams if (responses.length === 0) { const request: UsageRequest = { kind: "http", method: "GET", url: HYPER_TEAMS_URL, headers }; return { requests: [request] }; }
// Round 1 response: check if it's the teams response const firstResponse = responses[0]; if (!firstResponse) { return { report: unknownReport("HTTP no response") }; } if (firstResponse.status !== 200) { return { report: unknownReport(httpStatusError(firstResponse)) }; }
const firstPayload = jsonBody<Record<string, unknown>>(firstResponse); if (!firstPayload) { return { report: unknownReport("usage body is not JSON") }; }
// Detect if responses are already billing (e.g. direct test call or 2nd round) const isBillingResponse = Boolean(firstPayload.plan || firstPayload.balances);
if (!isBillingResponse && responses.length === 1) { // This is the teams response const items = Array.isArray(firstPayload.items) ? firstPayload.items : []; if (items.length === 0) { return { report: unknownReport("no teams found for hyper account") }; }
// Match credential.accountId if provided, else use first team const team = (credential.accountId ? items.find((item) => asRecord(item)?.id === credential.accountId) : undefined) ?? items[0]; const teamRec = asRecord(team); const teamId = typeof teamRec?.id === "string" ? teamRec.id : undefined; const teamName = typeof teamRec?.name === "string" ? teamRec.name : undefined;
if (!teamId) { return { report: unknownReport("invalid team in hyper response") }; }
sessionCache.set(credential, { teamId, teamName });
// Return 2 requests: billing (required) and members (optional email discovery) return { requests: [ { kind: "http", method: "GET", url: `${HYPER_TEAMS_URL}/${teamId}/billing`, headers }, { kind: "http", method: "GET", url: `${HYPER_TEAMS_URL}/${teamId}/members`, headers }, ], }; }
// Billing response handling (Round 2 or direct test invocation) const { planType, limits, balance } = parseHyperBilling(firstPayload, state.context.nowMs);
// Second response, if present, is team members let memberEmail: string | undefined; if (responses.length >= 2) { const membersResponse = responses[1]; if (membersResponse && membersResponse.status === 200) { const membersPayload = jsonBody<Record<string, unknown>>(membersResponse); const memberItems = Array.isArray(membersPayload?.items) ? membersPayload.items : []; const firstMember = asRecord(memberItems[0]); if (typeof firstMember?.email === "string" && firstMember.email.trim()) { memberEmail = firstMember.email.trim(); } } }
const cached = sessionCache.get(credential); const accountId = credential.accountId ?? cached?.teamId; const email = memberEmail ?? credential.email; const notes = cached?.teamName ? [`Team: ${cached.teamName}`] : undefined;
const body: UsageReportBody = { planType, accountId, email, limits, ...(balance ? { balance } : {}), ...(notes ? { notes } : {}), error: null, }; return { report: body }; },});