feat(redstone): /ai_use — OpenRouter key usage report
Admin-only command. Calls GET https://openrouter.ai/api/v1/key (the REST equivalent of the @openrouter/sdk's apiKeys.getCurrent()) and formats the result: - active model (from the settings table) - tier (🟢 free / 💰 paid) - credit limit + remaining (or "none") - credits spent today / week / month / all-time Plus a note that free-model requests don't draw credits — they're capped separately at 50/day (UTC-midnight reset), liftable to 1000/day with $10 credit. Kept fetch-based like the rest of the OpenRouter integration rather than pulling in the SDK. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
59
bot/bot.ts
59
bot/bot.ts
@@ -626,6 +626,62 @@ async function fetchOpenRouterModels(): Promise<ORModelListItem[]> {
|
||||
return data.data ?? [];
|
||||
}
|
||||
|
||||
// OpenRouter key/usage info — GET /api/v1/key (equivalent to the SDK's
|
||||
// openRouter.apiKeys.getCurrent()). Surfaces credit usage + tier.
|
||||
type ORKeyInfo = {
|
||||
label?: string;
|
||||
is_free_tier?: boolean;
|
||||
limit?: number | null;
|
||||
limit_remaining?: number | null;
|
||||
usage?: number;
|
||||
usage_daily?: number;
|
||||
usage_weekly?: number;
|
||||
usage_monthly?: number;
|
||||
};
|
||||
|
||||
async function fetchOpenRouterKeyInfo(): Promise<ORKeyInfo> {
|
||||
if (!OPENROUTER_API_KEY) throw new Error("OPENROUTER_API_KEY not configured");
|
||||
const res = await fetch("https://openrouter.ai/api/v1/key", {
|
||||
headers: { Authorization: `Bearer ${OPENROUTER_API_KEY}`, Accept: "application/json" },
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
});
|
||||
if (!res.ok) throw new Error(`openrouter /key -> ${res.status}`);
|
||||
const data = (await res.json()) as { data?: ORKeyInfo };
|
||||
return data.data ?? {};
|
||||
}
|
||||
|
||||
async function renderAiUsage(ctx: Context) {
|
||||
if (!OPENROUTER_API_KEY) {
|
||||
await sendText(ctx, "OpenRouter is not configured — set OPENROUTER_API_KEY to see usage.");
|
||||
return;
|
||||
}
|
||||
let info: ORKeyInfo;
|
||||
try {
|
||||
info = await fetchOpenRouterKeyInfo();
|
||||
} catch (e) {
|
||||
await sendText(ctx, `Couldn't load OpenRouter usage: ${escape((e as Error).message)}`);
|
||||
return;
|
||||
}
|
||||
const usd = (n: number | null | undefined) =>
|
||||
n == null ? "—" : `$${n.toFixed(n < 1 ? 4 : 2)}`;
|
||||
const tier = info.is_free_tier ? "🟢 free tier" : "💰 paid";
|
||||
const limitLine = info.limit == null
|
||||
? "Credit limit: <i>none</i>"
|
||||
: `Credit limit: ${usd(info.limit)} (remaining ${usd(info.limit_remaining)})`;
|
||||
const text =
|
||||
`<b>🤖 OpenRouter usage</b>\n\n` +
|
||||
`Active model: <code>${escape(currentOpenRouterModel())}</code>\n` +
|
||||
`Tier: ${tier}\n` +
|
||||
`${limitLine}\n\n` +
|
||||
`<b>Credits spent</b>\n` +
|
||||
`• today: ${usd(info.usage_daily)}\n` +
|
||||
`• this week: ${usd(info.usage_weekly)}\n` +
|
||||
`• this month: ${usd(info.usage_monthly)}\n` +
|
||||
`• all time: ${usd(info.usage)}\n\n` +
|
||||
`<i>Free-model requests don't consume credits — they're capped separately at 50/day, resetting at UTC midnight. Add $10 of credit to lift that to 1000/day.</i>`;
|
||||
await sendText(ctx, text);
|
||||
}
|
||||
|
||||
function modelButtonLabel(m: ORModelListItem, isCurrent: boolean): string {
|
||||
const free = isFreeModel(m);
|
||||
const thinking = m.supported_parameters?.includes("reasoning") ?? false;
|
||||
@@ -1104,6 +1160,9 @@ bot.command("teststicker", admin(async (ctx) => {
|
||||
// is resolved at every callOpenRouterOnce so changes apply immediately.
|
||||
bot.command("model", admin(async (ctx) => { await renderModelPicker(ctx); }));
|
||||
|
||||
// OpenRouter usage — admin views credit spend + tier for the configured key.
|
||||
bot.command("ai_use", admin(async (ctx) => { await renderAiUsage(ctx); }));
|
||||
|
||||
// History — last N messages in this chat
|
||||
bot.command("history", admin(async (ctx) => {
|
||||
const n = Math.min(50, Math.max(5, Number.parseInt((ctx.match as string).trim(), 10) || 20));
|
||||
|
||||
Reference in New Issue
Block a user