fix: restore om-usage HTTP endpoint (#5859)

Integrated into release/v3.8.43
This commit is contained in:
WITALO ROCHA
2026-07-01 22:03:17 -03:00
committed by GitHub
parent 9358eeada9
commit cfb2db8261
6 changed files with 296 additions and 3 deletions

View File

@@ -0,0 +1,16 @@
import { handleCorsOptions } from "@/shared/utils/cors";
import { handleInternalUsageCommandHttpRequest } from "@/lib/usage/internalUsageCommand";
export async function OPTIONS() {
return handleCorsOptions();
}
/**
* GET /api/usage/om-usage
*
* Terminal-friendly equivalent of @@om-usage. Authenticates with the same
* OmniRoute API key used by Claude Code/Codex and requires allowUsageCommand.
*/
export async function GET(request: Request) {
return handleInternalUsageCommandHttpRequest(request);
}

View File

@@ -3,11 +3,13 @@ import {
buildApiKeyUsageLimitText,
type ApiKeyUsageLimitStatus,
} from "@/lib/usage/apiKeyUsageLimits";
import { buildErrorBody } from "@omniroute/open-sse/utils/error";
export const INTERNAL_USAGE_COMMAND = "@@om-usage";
export const USAGE_COMMAND_DISABLED_MESSAGE = "Usage command is disabled for this API key.";
const USAGE_COMMAND_AUTH_REQUIRED_MESSAGE = "Usage command requires an authenticated API key.";
const LOCAL_USAGE_MODEL = "omniroute/local-usage";
const TEXT_PLAIN_HEADERS = { "Content-Type": "text/plain; charset=utf-8" } as const;
type JsonRecord = Record<string, unknown>;
@@ -34,6 +36,11 @@ interface UsageSnapshot {
quotas: JsonRecord;
}
interface UsageCommandSelection {
preferredProvider?: string | null;
preferredConnectionId?: string | null;
}
export interface InternalUsageCommandDeps {
now?: () => number;
isValidApiKey?: (apiKey: string) => Promise<boolean>;
@@ -350,7 +357,7 @@ function snapshotScore(snapshot: UsageSnapshot): number {
return score;
}
function selectUsageSnapshot(snapshots: UsageSnapshot[]): UsageSnapshot | null {
function selectBestUsageSnapshot(snapshots: UsageSnapshot[]): UsageSnapshot | null {
let selected: UsageSnapshot | null = null;
let bestScore = -1;
for (const snapshot of snapshots) {
@@ -363,6 +370,35 @@ function selectUsageSnapshot(snapshots: UsageSnapshot[]): UsageSnapshot | null {
return selected;
}
function normalizeProviderId(provider: string | null | undefined): string | null {
const normalized = provider?.trim().toLowerCase().replace(/_/g, "-");
if (!normalized) return null;
if (normalized === "cc" || normalized === "claude-code" || normalized === "claudecode") {
return "claude";
}
return normalized;
}
function selectUsageSnapshot(
snapshots: UsageSnapshot[],
selection: UsageCommandSelection = {}
): UsageSnapshot | null {
const preferredConnectionId = selection.preferredConnectionId?.trim();
if (preferredConnectionId) {
const snapshot = snapshots.find((entry) => entry.connectionId === preferredConnectionId);
if (snapshot) return snapshot;
}
const preferredProvider = normalizeProviderId(selection.preferredProvider);
if (preferredProvider) {
return selectBestUsageSnapshot(
snapshots.filter((entry) => normalizeProviderId(entry.provider) === preferredProvider)
);
}
return selectBestUsageSnapshot(snapshots);
}
function appendQuotaBlock(lines: string[], label: string, quota: JsonRecord | null, now: number) {
lines.push(label);
lines.push(formatPercent(getQuotaUsedPercent(quota)));
@@ -371,7 +407,8 @@ function appendQuotaBlock(lines: string[], label: string, quota: JsonRecord | nu
export async function buildUsageCommandText(
metadata: UsageCommandApiKeyMetadata,
deps: InternalUsageCommandDeps = {}
deps: InternalUsageCommandDeps = {},
selection: UsageCommandSelection = {}
): Promise<string> {
const resolvedDeps = await normalizeDeps(deps);
if (metadata.usageLimitEnabled === true) {
@@ -381,7 +418,10 @@ export async function buildUsageCommandText(
);
}
const snapshot = selectUsageSnapshot(await collectUsageSnapshots(metadata, resolvedDeps));
const snapshot = selectUsageSnapshot(
await collectUsageSnapshots(metadata, resolvedDeps),
selection
);
if (!snapshot) {
return ["Plan", "Unavailable", "", "Usage", "No cached usage data available."].join("\n");
@@ -403,6 +443,28 @@ function getResponseModel(body: unknown): string {
: LOCAL_USAGE_MODEL;
}
function inferHttpUsageCommandSelection(request: Request): UsageCommandSelection {
try {
const url = new URL(request.url, "http://localhost");
return {
preferredConnectionId:
url.searchParams.get("connectionId")?.trim() ||
readHeader(request, "x-omniroute-connection")?.trim() ||
null,
preferredProvider: url.searchParams.get("provider")?.trim() || null,
};
} catch {
return {
preferredConnectionId: readHeader(request, "x-omniroute-connection")?.trim() || null,
preferredProvider: null,
};
}
}
function createPlainUsageCommandResponse(text: string, status = 200): Response {
return new Response(text, { status, headers: TEXT_PLAIN_HEADERS });
}
function isAnthropicRequest(request: Request): boolean {
if (request.headers.has("anthropic-version")) return true;
try {
@@ -568,3 +630,32 @@ export async function handleInternalUsageCommand(
await buildUsageCommandText(metadata, resolvedDeps)
);
}
export async function handleInternalUsageCommandHttpRequest(
request: Request,
deps: InternalUsageCommandDeps = {}
): Promise<Response> {
try {
const resolvedDeps = await normalizeDeps(deps);
const apiKey = extractUsageCommandApiKey(request);
if (!apiKey || !(await resolvedDeps.isValidApiKey(apiKey))) {
return createPlainUsageCommandResponse(USAGE_COMMAND_AUTH_REQUIRED_MESSAGE, 401);
}
const metadata = await resolvedDeps.getApiKeyMetadata(apiKey);
if (!metadata?.id) {
return createPlainUsageCommandResponse(USAGE_COMMAND_AUTH_REQUIRED_MESSAGE, 401);
}
if (metadata.allowUsageCommand !== true) {
return createPlainUsageCommandResponse(USAGE_COMMAND_DISABLED_MESSAGE, 403);
}
return createPlainUsageCommandResponse(
await buildUsageCommandText(metadata, resolvedDeps, inferHttpUsageCommandSelection(request))
);
} catch (err) {
const body = buildErrorBody(500, err instanceof Error ? err.message : String(err));
return Response.json(body, { status: 500 });
}
}

View File

@@ -14,6 +14,10 @@ const PUBLIC_API_ROUTE_PREFIXES = [
// access token. The handler enforces its own password check + lockout — there
// is no token yet at this point, so it cannot require management auth.
"/api/cli/connect",
// Terminal-friendly @@om-usage equivalent for CLI clients (Claude Code/Codex).
// The handler enforces its own auth via extractUsageCommandApiKey/isValidApiKey
// and the allowUsageCommand flag — it must not be gated by management auth.
"/api/usage/om-usage",
];
const PUBLIC_READONLY_API_ROUTE_PREFIXES = [