singularity-forge/src/headless-usage.ts
Mikael Hugo 365c6bbc3b
Some checks are pending
CI / detect-changes (push) Waiting to run
CI / docs-check (push) Blocked by required conditions
CI / lint (push) Blocked by required conditions
CI / build (push) Blocked by required conditions
CI / integration-tests (push) Blocked by required conditions
CI / windows-portability (push) Blocked by required conditions
CI / rtk-portability (linux, blacksmith-4vcpu-ubuntu-2404) (push) Blocked by required conditions
CI / rtk-portability (macos, macos-15) (push) Blocked by required conditions
CI / rtk-portability (windows, blacksmith-4vcpu-windows-2025) (push) Blocked by required conditions
chore: formatter / linter touch-up (230 files)
Pure formatting / lint-fix pass that ran during `npm run build:core`
in the session that landed the agent-runner / quota / coverage /
phase-2 routing work. No logic changes — indentation, trailing
commas, import sort, etc. Captured separately so the actual feature
commits stay scoped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 21:19:53 +02:00

131 lines
4 KiB
TypeScript

/**
* headless-usage.ts — `sf headless usage`
*
* Live LLM-provider subscription quota state for every provider with a
* documented introspection endpoint:
*
* - kimi-coding GET https://api.kimi.com/coding/v1/usages
* - openrouter GET https://openrouter.ai/api/v1/credits
* - minimax GET https://api.minimax.io/v1/token_plan/remains
* - zai GET https://api.z.ai/api/monitor/usage/quota/limit
* - google-gemini-cli via snapshotGeminiCliAccount (OAuth Code Assist)
*
* Each call goes through `provider-quota-cache.js` which writes a unified
* representation to ~/.sf/provider-quota.json (15-minute TTL). This command
* forces a refresh, then prints either a compact human table or JSON.
*
* Providers without a documented quota endpoint (mistral, ollama-cloud,
* opencode, opencode-go, xiaomi) are listed as "unavailable" with a short
* note so users see exactly which subs SF can introspect today.
*
* Consumer: headless.ts when command === "usage".
*/
export interface HandleUsageOptions {
json?: boolean;
}
export interface HandleUsageResult {
exitCode: number;
}
const NO_API_PROVIDERS: ReadonlyArray<{ id: string; reason: string }> = [
{ id: "mistral", reason: "no public quota endpoint — console.mistral.ai" },
{ id: "ollama-cloud", reason: "WorkOS dashboard only — ollama.com/settings" },
{ id: "opencode", reason: "no public quota endpoint" },
{ id: "opencode-go", reason: "no public quota endpoint" },
{
id: "xiaomi",
reason: "no public quota endpoint — platform.xiaomimimo.com",
},
];
/**
* Render the unified provider-quota snapshot as a compact text table (default)
* or as JSON for machine consumers. Always writes to stdout; never throws.
*/
export async function handleUsage(
cwd: string,
options: HandleUsageOptions = {},
): Promise<HandleUsageResult> {
const { runProviderQuotaRefreshIfStale, getAllProviderQuotaEntries } =
await import("./resources/extensions/sf/provider-quota-cache.js");
const { getKeyManagerAuthStorage } = await import(
"./resources/extensions/sf/key-manager.js"
);
const auth = getKeyManagerAuthStorage();
try {
await runProviderQuotaRefreshIfStale(cwd, auth);
} catch (err) {
// Fall through to display whatever's cached, even on refresh failure.
if (!options.json) {
process.stderr.write(
`warning: quota refresh failed: ${err instanceof Error ? err.message : String(err)}\n`,
);
}
}
const entries = getAllProviderQuotaEntries();
if (options.json) {
const payload = {
ok: true,
providers: entries,
unavailable: NO_API_PROVIDERS,
};
process.stdout.write(`${JSON.stringify(payload)}\n`);
return { exitCode: 0 };
}
const lines: string[] = [];
lines.push("Provider quota state");
lines.push("");
const providerIds = Object.keys(entries).sort();
if (providerIds.length === 0) {
lines.push(
" (no providers have a quota snapshot yet — check API keys are configured)",
);
}
for (const providerId of providerIds) {
const entry = entries[providerId];
if (!entry?.ok) {
lines.push(
` ${providerId.padEnd(20)} — error: ${entry?.error ?? "unknown"}`,
);
continue;
}
lines.push(` ${providerId} (fetched ${entry.fetchedAt})`);
const windows = entry.windows ?? [];
if (windows.length === 0) {
lines.push(" (no windows reported)");
continue;
}
const labelW = Math.max(16, ...windows.map((w) => (w.label ?? "").length));
for (const w of windows as Array<{
label?: string;
usedFraction?: number;
resetHint?: string;
}>) {
const pct =
typeof w.usedFraction === "number"
? `${(w.usedFraction * 100).toFixed(1).padStart(5)}%`
: " ? ";
const reset = w.resetHint ? ` reset=${w.resetHint}` : "";
lines.push(
` ${String(w.label ?? "").padEnd(labelW)} used=${pct}${reset}`,
);
}
}
lines.push("");
lines.push("No public quota API for:");
for (const p of NO_API_PROVIDERS) {
lines.push(` ${p.id.padEnd(20)} ${p.reason}`);
}
process.stdout.write(`${lines.join("\n")}\n`);
return { exitCode: 0 };
}