From 47020ee08de70386db3696b33e1f8d92b7e062ed Mon Sep 17 00:00:00 2001 From: ekko Date: Wed, 29 Apr 2026 10:55:29 +0800 Subject: [PATCH] feat(usage): filter usage stats by active profile Usage stats now automatically filter by the current active profile. Changes: - getLocalUsageStats() accepts optional profile parameter - Add WHERE profile = ? clause to all SQL queries when profile is provided - usageStats controller uses getActiveProfileName() to get current profile - Local session_usage data is now filtered by current profile - Hermes state.db sessions remain unfiltered (no profile field) This allows users to see usage stats specific to their current profile, making multi-profile usage tracking more useful. Co-Authored-By: Claude Sonnet 4.6 --- .../server/src/controllers/hermes/sessions.ts | 10 ++++-- packages/server/src/db/hermes/usage-store.ts | 31 +++++++++++++------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/server/src/controllers/hermes/sessions.ts b/packages/server/src/controllers/hermes/sessions.ts index 56c11073..2aa2884e 100644 --- a/packages/server/src/controllers/hermes/sessions.ts +++ b/packages/server/src/controllers/hermes/sessions.ts @@ -289,8 +289,11 @@ export async function contextLength(ctx: any) { } export async function usageStats(ctx: any) { - // 1. Local session_usage (web UI chat runs) - const local = getLocalUsageStats() + // Get current active profile + const currentProfile = getActiveProfileName() + + // 1. Local session_usage (web UI chat runs) - filtered by current profile + const local = getLocalUsageStats(currentProfile) // 2. Hermes state.db sessions (exclude api_server source) let hermesSessions: Array<{ @@ -307,6 +310,9 @@ export async function usageStats(ctx: any) { try { const allSessions = await listSessionSummaries(undefined, 100000) + // Only include sessions from current profile + // Note: Hermes sessions don't have profile field, so we include all + // This could be improved in the future by filtering by some criteria hermesSessions = allSessions.filter(s => s.source !== 'api_server') } catch (err) { logger.warn(err, 'usageStats: failed to load Hermes sessions') diff --git a/packages/server/src/db/hermes/usage-store.ts b/packages/server/src/db/hermes/usage-store.ts index 99e7631e..319bec61 100644 --- a/packages/server/src/db/hermes/usage-store.ts +++ b/packages/server/src/db/hermes/usage-store.ts @@ -199,7 +199,7 @@ export interface LocalUsageStats { by_day: UsageStatsDailyRow[] } -export function getLocalUsageStats(): LocalUsageStats { +export function getLocalUsageStats(profile?: string): LocalUsageStats { const empty: LocalUsageStats = { input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_write_tokens: 0, reasoning_tokens: 0, sessions: 0, @@ -208,6 +208,7 @@ export function getLocalUsageStats(): LocalUsageStats { if (!isSqliteAvailable()) return empty const db = getDb()! + const profileFilter = profile ? `WHERE profile = ?` : '' const totals = db.prepare(` SELECT COALESCE(SUM(input_tokens),0) as input_tokens, @@ -217,7 +218,8 @@ export function getLocalUsageStats(): LocalUsageStats { COALESCE(SUM(reasoning_tokens),0) as reasoning_tokens, COUNT(DISTINCT session_id) as sessions FROM ${TABLE} - `).get() as any + ${profileFilter} + `).get(...(profile ? [profile] : [])) as any const byModel = db.prepare(` SELECT model, @@ -228,21 +230,30 @@ export function getLocalUsageStats(): LocalUsageStats { SUM(reasoning_tokens) as reasoning_tokens, COUNT(DISTINCT session_id) as sessions FROM ${TABLE} + ${profileFilter} GROUP BY model ORDER BY sessions DESC - `).all() as unknown as UsageStatsModelRow[] + `).all(...(profile ? [profile] : [])) as unknown as UsageStatsModelRow[] const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000 - const byDay = db.prepare(` - SELECT DATE(created_at / 1000, 'unixepoch') as date, + const byDayStmt = profile + ? `SELECT DATE(created_at / 1000, 'unixepoch') as date, SUM(input_tokens + output_tokens) as tokens, SUM(cache_read_tokens) as cache, COUNT(DISTINCT session_id) as sessions - FROM ${TABLE} - WHERE created_at > ? - GROUP BY date - ORDER BY date - `).all(thirtyDaysAgo) as Array<{ date: string; tokens: number; cache: number; sessions: number }> + FROM ${TABLE} + WHERE profile = ? AND created_at > ? + GROUP BY date + ORDER BY date` + : `SELECT DATE(created_at / 1000, 'unixepoch') as date, + SUM(input_tokens + output_tokens) as tokens, + SUM(cache_read_tokens) as cache, + COUNT(DISTINCT session_id) as sessions + FROM ${TABLE} + WHERE created_at > ? + GROUP BY date + ORDER BY date` + const byDay = db.prepare(byDayStmt).all(...(profile ? [profile, thirtyDaysAgo] : [thirtyDaysAgo])) as Array<{ date: string; tokens: number; cache: number; sessions: number }> return { input_tokens: totals.input_tokens,