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
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>
131 lines
4 KiB
TypeScript
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 };
|
|
}
|