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 ?? [];
|
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 {
|
function modelButtonLabel(m: ORModelListItem, isCurrent: boolean): string {
|
||||||
const free = isFreeModel(m);
|
const free = isFreeModel(m);
|
||||||
const thinking = m.supported_parameters?.includes("reasoning") ?? false;
|
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.
|
// is resolved at every callOpenRouterOnce so changes apply immediately.
|
||||||
bot.command("model", admin(async (ctx) => { await renderModelPicker(ctx); }));
|
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
|
// History — last N messages in this chat
|
||||||
bot.command("history", admin(async (ctx) => {
|
bot.command("history", admin(async (ctx) => {
|
||||||
const n = Math.min(50, Math.max(5, Number.parseInt((ctx.match as string).trim(), 10) || 20));
|
const n = Math.min(50, Math.max(5, Number.parseInt((ctx.match as string).trim(), 10) || 20));
|
||||||
|
|||||||
Reference in New Issue
Block a user