2026-05-09 06:48:20 -04:00
import { EventEmitter } from "events" ;
import { getAdapter } from "../driver.js" ;
import { parseJson , stringifyJson } from "../helpers/jsonCol.js" ;
import { getMeta , setMeta } from "../helpers/metaStore.js" ;
const PENDING _TIMEOUT _MS = 60 * 1000 ;
const RING _CAP = 50 ;
const CONN _CACHE _TTL _MS = 30 * 1000 ;
const PERIOD _MS = { "24h" : 86400000 , "7d" : 604800000 , "30d" : 2592000000 , "60d" : 5184000000 } ;
// In-memory state shared across Next.js modules
if ( ! global . _pendingRequests ) global . _pendingRequests = { byModel : { } , byAccount : { } } ;
if ( ! global . _lastErrorProvider ) global . _lastErrorProvider = { provider : "" , ts : 0 } ;
if ( ! global . _statsEmitter ) {
global . _statsEmitter = new EventEmitter ( ) ;
global . _statsEmitter . setMaxListeners ( 50 ) ;
}
if ( ! global . _pendingTimers ) global . _pendingTimers = { } ;
if ( ! global . _recentRing ) global . _recentRing = { items : [ ] , initialized : false } ;
if ( ! global . _connectionMapCache ) global . _connectionMapCache = { map : { } , ts : 0 } ;
2026-06-25 23:21:28 -04:00
if ( ! global . _statsEmitTimers ) global . _statsEmitTimers = { pending : null , update : null } ;
2026-05-09 06:48:20 -04:00
const pendingRequests = global . _pendingRequests ;
const lastErrorProvider = global . _lastErrorProvider ;
const pendingTimers = global . _pendingTimers ;
const recentRing = global . _recentRing ;
const connCache = global . _connectionMapCache ;
2026-06-25 23:21:28 -04:00
const statsEmitTimers = global . _statsEmitTimers ;
2026-05-09 06:48:20 -04:00
export const statsEmitter = global . _statsEmitter ;
2026-06-25 23:21:28 -04:00
function scheduleStatsEvent ( event , delayMs = 150 ) {
const key = event === "update" ? "update" : "pending" ;
if ( statsEmitTimers [ key ] ) return ;
statsEmitTimers [ key ] = setTimeout ( ( ) => {
statsEmitTimers [ key ] = null ;
statsEmitter . emit ( event ) ;
} , delayMs ) ;
statsEmitTimers [ key ] ? . unref ? . ( ) ;
}
2026-05-09 06:48:20 -04:00
function getLocalDateKey ( timestamp ) {
const d = timestamp ? new Date ( timestamp ) : new Date ( ) ;
return ` ${ d . getFullYear ( ) } - ${ String ( d . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) } - ${ String ( d . getDate ( ) ) . padStart ( 2 , "0" ) } ` ;
}
function addToCounter ( target , key , values ) {
if ( ! target [ key ] ) target [ key ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 } ;
target [ key ] . requests += values . requests || 1 ;
target [ key ] . promptTokens += values . promptTokens || 0 ;
target [ key ] . completionTokens += values . completionTokens || 0 ;
target [ key ] . cost += values . cost || 0 ;
if ( values . meta ) Object . assign ( target [ key ] , values . meta ) ;
}
function aggregateEntryToDay ( day , entry ) {
const promptTokens = entry . tokens ? . prompt _tokens || entry . tokens ? . input _tokens || 0 ;
const completionTokens = entry . tokens ? . completion _tokens || entry . tokens ? . output _tokens || 0 ;
const cost = entry . cost || 0 ;
const vals = { promptTokens , completionTokens , cost } ;
day . requests = ( day . requests || 0 ) + 1 ;
day . promptTokens = ( day . promptTokens || 0 ) + promptTokens ;
day . completionTokens = ( day . completionTokens || 0 ) + completionTokens ;
day . cost = ( day . cost || 0 ) + cost ;
day . byProvider || = { } ;
day . byModel || = { } ;
day . byAccount || = { } ;
day . byApiKey || = { } ;
day . byEndpoint || = { } ;
if ( entry . provider ) addToCounter ( day . byProvider , entry . provider , vals ) ;
const modelKey = entry . provider ? ` ${ entry . model } | ${ entry . provider } ` : entry . model ;
addToCounter ( day . byModel , modelKey , { ... vals , meta : { rawModel : entry . model , provider : entry . provider } } ) ;
if ( entry . connectionId ) {
addToCounter ( day . byAccount , entry . connectionId , { ... vals , meta : { rawModel : entry . model , provider : entry . provider } } ) ;
}
const apiKeyVal = entry . apiKey && typeof entry . apiKey === "string" ? entry . apiKey : "local-no-key" ;
const akModelKey = ` ${ apiKeyVal } | ${ entry . model } | ${ entry . provider || "unknown" } ` ;
addToCounter ( day . byApiKey , akModelKey , { ... vals , meta : { rawModel : entry . model , provider : entry . provider , apiKey : entry . apiKey || null } } ) ;
const endpoint = entry . endpoint || "Unknown" ;
const epKey = ` ${ endpoint } | ${ entry . model } | ${ entry . provider || "unknown" } ` ;
addToCounter ( day . byEndpoint , epKey , { ... vals , meta : { endpoint , rawModel : entry . model , provider : entry . provider } } ) ;
}
function pushToRing ( entry ) {
recentRing . items . push ( entry ) ;
if ( recentRing . items . length > RING _CAP ) {
recentRing . items = recentRing . items . slice ( - RING _CAP ) ;
}
}
async function getConnectionMapCached ( ) {
if ( Date . now ( ) - connCache . ts < CONN _CACHE _TTL _MS ) return connCache . map ;
try {
const { getProviderConnections } = await import ( "./connectionsRepo.js" ) ;
const all = await getProviderConnections ( ) ;
const map = { } ;
for ( const c of all ) map [ c . id ] = c . name || c . email || c . id ;
connCache . map = map ;
connCache . ts = Date . now ( ) ;
} catch { }
return connCache . map ;
}
async function ensureRingInitialized ( ) {
if ( recentRing . initialized ) return ;
recentRing . initialized = true ;
try {
const db = await getAdapter ( ) ;
const rows = db . all ( ` SELECT timestamp, provider, model, connectionId, apiKey, endpoint, cost, status, tokens FROM usageHistory ORDER BY id DESC LIMIT ? ` , [ RING _CAP ] ) ;
recentRing . items = rows . reverse ( ) . map ( ( r ) => ( {
timestamp : r . timestamp , provider : r . provider , model : r . model , connectionId : r . connectionId ,
apiKey : r . apiKey , endpoint : r . endpoint , cost : r . cost , status : r . status ,
tokens : parseJson ( r . tokens , { } ) ,
} ) ) ;
} catch { }
}
async function calculateCost ( provider , model , tokens ) {
if ( ! tokens || ! provider || ! model ) return 0 ;
try {
const { getPricingForModel } = await import ( "./pricingRepo.js" ) ;
const pricing = await getPricingForModel ( provider , model ) ;
if ( ! pricing ) return 0 ;
let cost = 0 ;
const inputTokens = tokens . prompt _tokens || tokens . input _tokens || 0 ;
const cachedTokens = tokens . cached _tokens || tokens . cache _read _input _tokens || 0 ;
const nonCachedInput = Math . max ( 0 , inputTokens - cachedTokens ) ;
cost += nonCachedInput * ( pricing . input / 1000000 ) ;
if ( cachedTokens > 0 ) {
const cachedRate = pricing . cached || pricing . input ;
cost += cachedTokens * ( cachedRate / 1000000 ) ;
}
const outputTokens = tokens . completion _tokens || tokens . output _tokens || 0 ;
cost += outputTokens * ( pricing . output / 1000000 ) ;
const reasoningTokens = tokens . reasoning _tokens || 0 ;
if ( reasoningTokens > 0 ) {
const rate = pricing . reasoning || pricing . output ;
cost += reasoningTokens * ( rate / 1000000 ) ;
}
const cacheCreationTokens = tokens . cache _creation _input _tokens || 0 ;
if ( cacheCreationTokens > 0 ) {
const rate = pricing . cache _creation || pricing . input ;
cost += cacheCreationTokens * ( rate / 1000000 ) ;
}
return cost ;
} catch ( e ) {
console . error ( "Error calculating cost:" , e ) ;
return 0 ;
}
}
export function trackPendingRequest ( model , provider , connectionId , started , error = false ) {
const modelKey = provider ? ` ${ model } ( ${ provider } ) ` : model ;
const timerKey = ` ${ connectionId } | ${ modelKey } ` ;
if ( ! pendingRequests . byModel [ modelKey ] ) pendingRequests . byModel [ modelKey ] = 0 ;
pendingRequests . byModel [ modelKey ] = Math . max ( 0 , pendingRequests . byModel [ modelKey ] + ( started ? 1 : - 1 ) ) ;
if ( pendingRequests . byModel [ modelKey ] === 0 ) delete pendingRequests . byModel [ modelKey ] ;
if ( connectionId ) {
if ( ! pendingRequests . byAccount [ connectionId ] ) pendingRequests . byAccount [ connectionId ] = { } ;
if ( ! pendingRequests . byAccount [ connectionId ] [ modelKey ] ) pendingRequests . byAccount [ connectionId ] [ modelKey ] = 0 ;
pendingRequests . byAccount [ connectionId ] [ modelKey ] = Math . max ( 0 , pendingRequests . byAccount [ connectionId ] [ modelKey ] + ( started ? 1 : - 1 ) ) ;
if ( pendingRequests . byAccount [ connectionId ] [ modelKey ] === 0 ) {
delete pendingRequests . byAccount [ connectionId ] [ modelKey ] ;
if ( Object . keys ( pendingRequests . byAccount [ connectionId ] ) . length === 0 ) {
delete pendingRequests . byAccount [ connectionId ] ;
}
}
}
if ( started ) {
clearTimeout ( pendingTimers [ timerKey ] ) ;
pendingTimers [ timerKey ] = setTimeout ( ( ) => {
delete pendingTimers [ timerKey ] ;
if ( pendingRequests . byModel [ modelKey ] > 0 ) pendingRequests . byModel [ modelKey ] = 0 ;
if ( connectionId && pendingRequests . byAccount [ connectionId ] ? . [ modelKey ] > 0 ) {
pendingRequests . byAccount [ connectionId ] [ modelKey ] = 0 ;
}
2026-06-25 23:21:28 -04:00
scheduleStatsEvent ( "pending" ) ;
2026-05-09 06:48:20 -04:00
} , PENDING _TIMEOUT _MS ) ;
} else {
clearTimeout ( pendingTimers [ timerKey ] ) ;
delete pendingTimers [ timerKey ] ;
}
if ( ! started && error && provider ) {
lastErrorProvider . provider = provider . toLowerCase ( ) ;
lastErrorProvider . ts = Date . now ( ) ;
}
const t = new Date ( ) . toLocaleTimeString ( "en-US" , { hour12 : false , hour : "2-digit" , minute : "2-digit" , second : "2-digit" } ) ;
console . log ( ` [ ${ t } ] [PENDING] ${ started ? "START" : "END" } ${ error ? " (ERROR)" : "" } | provider= ${ provider } | model= ${ model } ` ) ;
2026-06-25 23:21:28 -04:00
scheduleStatsEvent ( "pending" ) ;
2026-05-09 06:48:20 -04:00
}
export async function getActiveRequests ( ) {
const activeRequests = [ ] ;
const connectionMap = await getConnectionMapCached ( ) ;
for ( const [ connectionId , models ] of Object . entries ( pendingRequests . byAccount ) ) {
for ( const [ modelKey , count ] of Object . entries ( models ) ) {
if ( count > 0 ) {
const accountName = connectionMap [ connectionId ] || ` Account ${ connectionId . slice ( 0 , 8 ) } ... ` ;
const match = modelKey . match ( /^(.*) \((.*)\)$/ ) ;
activeRequests . push ( {
model : match ? match [ 1 ] : modelKey ,
provider : match ? match [ 2 ] : "unknown" ,
account : accountName , count ,
} ) ;
}
}
}
await ensureRingInitialized ( ) ;
const seen = new Set ( ) ;
const recentRequests = [ ... recentRing . items ]
. sort ( ( a , b ) => new Date ( b . timestamp ) - new Date ( a . timestamp ) )
. map ( ( e ) => {
const t = e . tokens || { } ;
return {
timestamp : e . timestamp , model : e . model , provider : e . provider || "" ,
promptTokens : t . prompt _tokens || t . input _tokens || 0 ,
completionTokens : t . completion _tokens || t . output _tokens || 0 ,
status : e . status || "ok" ,
} ;
} )
. filter ( ( e ) => {
if ( e . promptTokens === 0 && e . completionTokens === 0 ) return false ;
const minute = e . timestamp ? e . timestamp . slice ( 0 , 16 ) : "" ;
const key = ` ${ e . model } | ${ e . provider } | ${ e . promptTokens } | ${ e . completionTokens } | ${ minute } ` ;
if ( seen . has ( key ) ) return false ;
seen . add ( key ) ;
return true ;
} )
. slice ( 0 , 20 ) ;
const errorProvider = ( Date . now ( ) - lastErrorProvider . ts < 10000 ) ? lastErrorProvider . provider : "" ;
return { activeRequests , recentRequests , errorProvider } ;
}
export async function saveRequestUsage ( entry ) {
try {
const db = await getAdapter ( ) ;
if ( ! entry . timestamp ) entry . timestamp = new Date ( ) . toISOString ( ) ;
entry . cost = await calculateCost ( entry . provider , entry . model , entry . tokens ) ;
const tokens = entry . tokens || { } ;
const promptTokens = tokens . prompt _tokens || tokens . input _tokens || 0 ;
const completionTokens = tokens . completion _tokens || tokens . output _tokens || 0 ;
2026-06-25 23:21:28 -04:00
let inserted = false ;
2026-05-09 06:48:20 -04:00
// All 3 writes (history insert, daily upsert, lifetime counter) in ONE transaction.
// better-sqlite3 is sync → no JS yield mid-transaction → no race in same process.
db . transaction ( ( ) => {
2026-06-25 23:21:28 -04:00
const existing = db . get (
` SELECT id, endpoint FROM usageHistory
WHERE timestamp = ?
AND COALESCE ( provider , '' ) = COALESCE ( ? , '' )
AND COALESCE ( model , '' ) = COALESCE ( ? , '' )
AND COALESCE ( connectionId , '' ) = COALESCE ( ? , '' )
AND COALESCE ( apiKey , '' ) = COALESCE ( ? , '' )
AND promptTokens = ?
AND completionTokens = ?
ORDER BY id DESC LIMIT 1 ` ,
[
entry . timestamp , entry . provider || null , entry . model || null ,
entry . connectionId || null , entry . apiKey || null ,
promptTokens , completionTokens ,
]
) ;
if ( existing ) {
if ( ! existing . endpoint && entry . endpoint ) {
db . run ( ` UPDATE usageHistory SET endpoint = ? WHERE id = ? ` , [ entry . endpoint , existing . id ] ) ;
}
return ;
}
2026-05-09 06:48:20 -04:00
db . run (
` INSERT INTO usageHistory(timestamp, provider, model, connectionId, apiKey, endpoint, promptTokens, completionTokens, cost, status, tokens, meta) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ` ,
[
entry . timestamp , entry . provider || null , entry . model || null ,
entry . connectionId || null , entry . apiKey || null , entry . endpoint || null ,
promptTokens , completionTokens , entry . cost || 0 , entry . status || "ok" ,
stringifyJson ( tokens ) , stringifyJson ( { } ) ,
]
) ;
const dateKey = getLocalDateKey ( entry . timestamp ) ;
const row = db . get ( ` SELECT data FROM usageDaily WHERE dateKey = ? ` , [ dateKey ] ) ;
const day = row ? parseJson ( row . data , { } ) : {
requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 ,
byProvider : { } , byModel : { } , byAccount : { } , byApiKey : { } , byEndpoint : { } ,
} ;
aggregateEntryToDay ( day , entry ) ;
db . run ( ` INSERT INTO usageDaily(dateKey, data) VALUES(?, ?) ON CONFLICT(dateKey) DO UPDATE SET data = excluded.data ` , [ dateKey , stringifyJson ( day ) ] ) ;
// Atomic counter increment in same transaction
const cur = db . get ( ` SELECT value FROM _meta WHERE key = 'totalRequestsLifetime' ` ) ;
const next = ( cur ? parseInt ( cur . value , 10 ) : 0 ) + 1 ;
db . run ( ` INSERT INTO _meta(key, value) VALUES('totalRequestsLifetime', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value ` , [ String ( next ) ] ) ;
2026-06-25 23:21:28 -04:00
inserted = true ;
2026-05-09 06:48:20 -04:00
} ) ;
2026-06-25 23:21:28 -04:00
if ( inserted ) {
pushToRing ( entry ) ;
scheduleStatsEvent ( "update" , 250 ) ;
}
2026-05-09 06:48:20 -04:00
} catch ( e ) {
console . error ( "Failed to save usage stats:" , e ) ;
}
}
export async function getUsageHistory ( filter = { } ) {
const db = await getAdapter ( ) ;
const conds = [ ] ;
const params = [ ] ;
if ( filter . provider ) { conds . push ( "provider = ?" ) ; params . push ( filter . provider ) ; }
if ( filter . model ) { conds . push ( "model = ?" ) ; params . push ( filter . model ) ; }
if ( filter . startDate ) { conds . push ( "timestamp >= ?" ) ; params . push ( new Date ( filter . startDate ) . toISOString ( ) ) ; }
if ( filter . endDate ) { conds . push ( "timestamp <= ?" ) ; params . push ( new Date ( filter . endDate ) . toISOString ( ) ) ; }
const where = conds . length ? ` WHERE ${ conds . join ( " AND " ) } ` : "" ;
const rows = db . all ( ` SELECT timestamp, provider, model, connectionId, apiKey, endpoint, cost, status, tokens FROM usageHistory ${ where } ORDER BY id ASC ` , params ) ;
return rows . map ( ( r ) => ( {
timestamp : r . timestamp , provider : r . provider , model : r . model ,
connectionId : r . connectionId , apiKey : r . apiKey , endpoint : r . endpoint ,
cost : r . cost , status : r . status , tokens : parseJson ( r . tokens , { } ) ,
} ) ) ;
}
function loadDaysInRange ( adapter , maxDays ) {
if ( maxDays == null ) {
return adapter . all ( ` SELECT dateKey, data FROM usageDaily ` ) ;
}
const today = new Date ( ) ;
const cutoff = new Date ( today . getFullYear ( ) , today . getMonth ( ) , today . getDate ( ) - maxDays + 1 ) ;
const cutoffKey = ` ${ cutoff . getFullYear ( ) } - ${ String ( cutoff . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) } - ${ String ( cutoff . getDate ( ) ) . padStart ( 2 , "0" ) } ` ;
return adapter . all ( ` SELECT dateKey, data FROM usageDaily WHERE dateKey >= ? ` , [ cutoffKey ] ) ;
}
export async function getUsageStats ( period = "all" ) {
const db = await getAdapter ( ) ;
const [ { getProviderConnections } , { getApiKeys } , { getProviderNodes } ] = await Promise . all ( [
import ( "./connectionsRepo.js" ) ,
import ( "./apiKeysRepo.js" ) ,
import ( "./nodesRepo.js" ) ,
] ) ;
let allConnections = [ ] ;
try { allConnections = await getProviderConnections ( ) ; } catch { }
const connectionMap = { } ;
for ( const c of allConnections ) connectionMap [ c . id ] = c . name || c . email || c . id ;
const providerNodeNameMap = { } ;
try {
const nodes = await getProviderNodes ( ) ;
for ( const n of nodes ) if ( n . id && n . name ) providerNodeNameMap [ n . id ] = n . name ;
} catch { }
let allApiKeys = [ ] ;
try { allApiKeys = await getApiKeys ( ) ; } catch { }
const apiKeyMap = { } ;
for ( const k of allApiKeys ) apiKeyMap [ k . key ] = { name : k . name , id : k . id , createdAt : k . createdAt } ;
// recentRequests from live history (last 100 entries enough for 20 deduped)
const recentRows = db . all ( ` SELECT timestamp, provider, model, tokens, status FROM usageHistory ORDER BY id DESC LIMIT 100 ` ) ;
const seen = new Set ( ) ;
const recentRequests = recentRows
. map ( ( r ) => {
const t = parseJson ( r . tokens , { } ) || { } ;
return {
timestamp : r . timestamp , model : r . model , provider : r . provider || "" ,
promptTokens : t . prompt _tokens || t . input _tokens || 0 ,
completionTokens : t . completion _tokens || t . output _tokens || 0 ,
status : r . status || "ok" ,
} ;
} )
. filter ( ( e ) => {
if ( e . promptTokens === 0 && e . completionTokens === 0 ) return false ;
const minute = e . timestamp ? e . timestamp . slice ( 0 , 16 ) : "" ;
const key = ` ${ e . model } | ${ e . provider } | ${ e . promptTokens } | ${ e . completionTokens } | ${ minute } ` ;
if ( seen . has ( key ) ) return false ;
seen . add ( key ) ;
return true ;
} )
. slice ( 0 , 20 ) ;
const stats = {
totalRequests : 0 ,
totalPromptTokens : 0 , totalCompletionTokens : 0 , totalCost : 0 ,
byProvider : { } , byModel : { } , byAccount : { } , byApiKey : { } , byEndpoint : { } ,
last10Minutes : [ ] ,
pending : pendingRequests ,
activeRequests : [ ] ,
recentRequests ,
errorProvider : ( Date . now ( ) - lastErrorProvider . ts < 10000 ) ? lastErrorProvider . provider : "" ,
} ;
// Active requests
for ( const [ connectionId , models ] of Object . entries ( pendingRequests . byAccount ) ) {
for ( const [ modelKey , count ] of Object . entries ( models ) ) {
if ( count > 0 ) {
const accountName = connectionMap [ connectionId ] || ` Account ${ connectionId . slice ( 0 , 8 ) } ... ` ;
const match = modelKey . match ( /^(.*) \((.*)\)$/ ) ;
stats . activeRequests . push ( {
model : match ? match [ 1 ] : modelKey ,
provider : match ? match [ 2 ] : "unknown" ,
account : accountName , count ,
} ) ;
}
}
}
// last10Minutes — query 10min window
const now = new Date ( ) ;
const currentMinuteStart = new Date ( Math . floor ( now . getTime ( ) / 60000 ) * 60000 ) ;
const tenMinutesAgo = new Date ( currentMinuteStart . getTime ( ) - 9 * 60 * 1000 ) ;
const bucketMap = { } ;
for ( let i = 0 ; i < 10 ; i ++ ) {
const ts = currentMinuteStart . getTime ( ) - ( 9 - i ) * 60 * 1000 ;
bucketMap [ ts ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 } ;
stats . last10Minutes . push ( bucketMap [ ts ] ) ;
}
const recent10 = db . all (
` SELECT timestamp, promptTokens, completionTokens, cost FROM usageHistory WHERE timestamp >= ? AND timestamp <= ? ` ,
[ tenMinutesAgo . toISOString ( ) , now . toISOString ( ) ]
) ;
for ( const r of recent10 ) {
const tt = new Date ( r . timestamp ) . getTime ( ) ;
const minuteStart = Math . floor ( tt / 60000 ) * 60000 ;
if ( bucketMap [ minuteStart ] ) {
bucketMap [ minuteStart ] . requests ++ ;
bucketMap [ minuteStart ] . promptTokens += r . promptTokens || 0 ;
bucketMap [ minuteStart ] . completionTokens += r . completionTokens || 0 ;
bucketMap [ minuteStart ] . cost += r . cost || 0 ;
}
}
2026-05-13 23:03:05 -04:00
const useDailySummary = period !== "24h" && period !== "today" ;
2026-05-09 06:48:20 -04:00
if ( useDailySummary ) {
const periodDays = { "7d" : 7 , "30d" : 30 , "60d" : 60 } ;
const maxDays = periodDays [ period ] || null ;
const dayRows = loadDaysInRange ( db , maxDays ) ;
for ( const dr of dayRows ) {
const dateKey = dr . dateKey ;
const day = parseJson ( dr . data , { } ) ;
stats . totalPromptTokens += day . promptTokens || 0 ;
stats . totalCompletionTokens += day . completionTokens || 0 ;
stats . totalCost += day . cost || 0 ;
for ( const [ prov , p ] of Object . entries ( day . byProvider || { } ) ) {
if ( ! stats . byProvider [ prov ] ) stats . byProvider [ prov ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 } ;
stats . byProvider [ prov ] . requests += p . requests || 0 ;
stats . byProvider [ prov ] . promptTokens += p . promptTokens || 0 ;
stats . byProvider [ prov ] . completionTokens += p . completionTokens || 0 ;
stats . byProvider [ prov ] . cost += p . cost || 0 ;
}
for ( const [ mk , m ] of Object . entries ( day . byModel || { } ) ) {
const rawModel = m . rawModel || mk . split ( "|" ) [ 0 ] ;
const provider = m . provider || mk . split ( "|" ) [ 1 ] || "" ;
const statsKey = provider ? ` ${ rawModel } ( ${ provider } ) ` : rawModel ;
const providerDisplayName = providerNodeNameMap [ provider ] || provider ;
if ( ! stats . byModel [ statsKey ] ) {
stats . byModel [ statsKey ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 , rawModel , provider : providerDisplayName , lastUsed : dateKey } ;
}
stats . byModel [ statsKey ] . requests += m . requests || 0 ;
stats . byModel [ statsKey ] . promptTokens += m . promptTokens || 0 ;
stats . byModel [ statsKey ] . completionTokens += m . completionTokens || 0 ;
stats . byModel [ statsKey ] . cost += m . cost || 0 ;
if ( dateKey > ( stats . byModel [ statsKey ] . lastUsed || "" ) ) stats . byModel [ statsKey ] . lastUsed = dateKey ;
}
for ( const [ connId , a ] of Object . entries ( day . byAccount || { } ) ) {
const accountName = connectionMap [ connId ] || ` Account ${ connId . slice ( 0 , 8 ) } ... ` ;
const rawModel = a . rawModel || "" ;
const provider = a . provider || "" ;
const providerDisplayName = providerNodeNameMap [ provider ] || provider ;
const accountKey = ` ${ rawModel } ( ${ provider } - ${ accountName } ) ` ;
if ( ! stats . byAccount [ accountKey ] ) {
stats . byAccount [ accountKey ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 , rawModel , provider : providerDisplayName , connectionId : connId , accountName , lastUsed : dateKey } ;
}
stats . byAccount [ accountKey ] . requests += a . requests || 0 ;
stats . byAccount [ accountKey ] . promptTokens += a . promptTokens || 0 ;
stats . byAccount [ accountKey ] . completionTokens += a . completionTokens || 0 ;
stats . byAccount [ accountKey ] . cost += a . cost || 0 ;
if ( dateKey > ( stats . byAccount [ accountKey ] . lastUsed || "" ) ) stats . byAccount [ accountKey ] . lastUsed = dateKey ;
}
for ( const [ akKey , ak ] of Object . entries ( day . byApiKey || { } ) ) {
const rawModel = ak . rawModel || "" ;
const provider = ak . provider || "" ;
const providerDisplayName = providerNodeNameMap [ provider ] || provider ;
const apiKeyVal = ak . apiKey ;
const keyInfo = apiKeyVal ? apiKeyMap [ apiKeyVal ] : null ;
const keyName = keyInfo ? . name || ( apiKeyVal ? apiKeyVal . slice ( 0 , 8 ) + "..." : "Local (No API Key)" ) ;
const apiKeyKey = apiKeyVal || "local-no-key" ;
if ( ! stats . byApiKey [ akKey ] ) {
stats . byApiKey [ akKey ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 , rawModel , provider : providerDisplayName , apiKey : apiKeyVal , keyName , apiKeyKey , lastUsed : dateKey } ;
}
stats . byApiKey [ akKey ] . requests += ak . requests || 0 ;
stats . byApiKey [ akKey ] . promptTokens += ak . promptTokens || 0 ;
stats . byApiKey [ akKey ] . completionTokens += ak . completionTokens || 0 ;
stats . byApiKey [ akKey ] . cost += ak . cost || 0 ;
if ( dateKey > ( stats . byApiKey [ akKey ] . lastUsed || "" ) ) stats . byApiKey [ akKey ] . lastUsed = dateKey ;
}
for ( const [ epKey , ep ] of Object . entries ( day . byEndpoint || { } ) ) {
const endpoint = ep . endpoint || epKey . split ( "|" ) [ 0 ] || "Unknown" ;
const rawModel = ep . rawModel || "" ;
const provider = ep . provider || "" ;
const providerDisplayName = providerNodeNameMap [ provider ] || provider ;
if ( ! stats . byEndpoint [ epKey ] ) {
stats . byEndpoint [ epKey ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 , endpoint , rawModel , provider : providerDisplayName , lastUsed : dateKey } ;
}
stats . byEndpoint [ epKey ] . requests += ep . requests || 0 ;
stats . byEndpoint [ epKey ] . promptTokens += ep . promptTokens || 0 ;
stats . byEndpoint [ epKey ] . completionTokens += ep . completionTokens || 0 ;
stats . byEndpoint [ epKey ] . cost += ep . cost || 0 ;
if ( dateKey > ( stats . byEndpoint [ epKey ] . lastUsed || "" ) ) stats . byEndpoint [ epKey ] . lastUsed = dateKey ;
}
}
// Overlay precise lastUsed timestamps from history
const overlayCutoff = maxDays ? Date . now ( ) - maxDays * 86400000 : 0 ;
const histRows = db . all (
` SELECT timestamp, provider, model, connectionId, apiKey, endpoint FROM usageHistory WHERE timestamp >= ? ` ,
[ new Date ( overlayCutoff ) . toISOString ( ) ]
) ;
for ( const e of histRows ) {
const ts = e . timestamp ;
const modelKey = e . provider ? ` ${ e . model } ( ${ e . provider } ) ` : e . model ;
if ( stats . byModel [ modelKey ] && new Date ( ts ) > new Date ( stats . byModel [ modelKey ] . lastUsed ) ) stats . byModel [ modelKey ] . lastUsed = ts ;
if ( e . connectionId ) {
const accountName = connectionMap [ e . connectionId ] || ` Account ${ e . connectionId . slice ( 0 , 8 ) } ... ` ;
const accountKey = ` ${ e . model } ( ${ e . provider } - ${ accountName } ) ` ;
if ( stats . byAccount [ accountKey ] && new Date ( ts ) > new Date ( stats . byAccount [ accountKey ] . lastUsed ) ) stats . byAccount [ accountKey ] . lastUsed = ts ;
}
const apiKeyKey = ( e . apiKey && typeof e . apiKey === "string" )
? ` ${ e . apiKey } | ${ e . model } | ${ e . provider || "unknown" } `
: "local-no-key" ;
if ( stats . byApiKey [ apiKeyKey ] && new Date ( ts ) > new Date ( stats . byApiKey [ apiKeyKey ] . lastUsed ) ) stats . byApiKey [ apiKeyKey ] . lastUsed = ts ;
const endpoint = e . endpoint || "Unknown" ;
const endpointKey = ` ${ endpoint } | ${ e . model } | ${ e . provider || "unknown" } ` ;
if ( stats . byEndpoint [ endpointKey ] && new Date ( ts ) > new Date ( stats . byEndpoint [ endpointKey ] . lastUsed ) ) stats . byEndpoint [ endpointKey ] . lastUsed = ts ;
}
} else {
2026-05-13 23:03:05 -04:00
// 24h / today: live history
let cutoff ;
if ( period === "today" ) {
const startOfDay = new Date ( ) ;
startOfDay . setHours ( 0 , 0 , 0 , 0 ) ;
cutoff = startOfDay . toISOString ( ) ;
} else {
cutoff = new Date ( Date . now ( ) - PERIOD _MS [ "24h" ] ) . toISOString ( ) ;
}
2026-05-09 06:48:20 -04:00
const filtered = db . all (
` SELECT timestamp, provider, model, connectionId, apiKey, endpoint, promptTokens, completionTokens, cost, tokens FROM usageHistory WHERE timestamp >= ? ` ,
[ cutoff ]
) ;
for ( const r of filtered ) {
const tokens = parseJson ( r . tokens , { } ) || { } ;
const promptTokens = tokens . prompt _tokens || 0 ;
const completionTokens = tokens . completion _tokens || 0 ;
const entryCost = r . cost || 0 ;
const providerDisplayName = providerNodeNameMap [ r . provider ] || r . provider ;
stats . totalPromptTokens += promptTokens ;
stats . totalCompletionTokens += completionTokens ;
stats . totalCost += entryCost ;
if ( ! stats . byProvider [ r . provider ] ) stats . byProvider [ r . provider ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 } ;
stats . byProvider [ r . provider ] . requests ++ ;
stats . byProvider [ r . provider ] . promptTokens += promptTokens ;
stats . byProvider [ r . provider ] . completionTokens += completionTokens ;
stats . byProvider [ r . provider ] . cost += entryCost ;
const modelKey = r . provider ? ` ${ r . model } ( ${ r . provider } ) ` : r . model ;
if ( ! stats . byModel [ modelKey ] ) {
stats . byModel [ modelKey ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 , rawModel : r . model , provider : providerDisplayName , lastUsed : r . timestamp } ;
}
stats . byModel [ modelKey ] . requests ++ ;
stats . byModel [ modelKey ] . promptTokens += promptTokens ;
stats . byModel [ modelKey ] . completionTokens += completionTokens ;
stats . byModel [ modelKey ] . cost += entryCost ;
if ( new Date ( r . timestamp ) > new Date ( stats . byModel [ modelKey ] . lastUsed ) ) stats . byModel [ modelKey ] . lastUsed = r . timestamp ;
if ( r . connectionId ) {
const accountName = connectionMap [ r . connectionId ] || ` Account ${ r . connectionId . slice ( 0 , 8 ) } ... ` ;
const accountKey = ` ${ r . model } ( ${ r . provider } - ${ accountName } ) ` ;
if ( ! stats . byAccount [ accountKey ] ) {
stats . byAccount [ accountKey ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 , rawModel : r . model , provider : providerDisplayName , connectionId : r . connectionId , accountName , lastUsed : r . timestamp } ;
}
stats . byAccount [ accountKey ] . requests ++ ;
stats . byAccount [ accountKey ] . promptTokens += promptTokens ;
stats . byAccount [ accountKey ] . completionTokens += completionTokens ;
stats . byAccount [ accountKey ] . cost += entryCost ;
if ( new Date ( r . timestamp ) > new Date ( stats . byAccount [ accountKey ] . lastUsed ) ) stats . byAccount [ accountKey ] . lastUsed = r . timestamp ;
}
if ( r . apiKey && typeof r . apiKey === "string" ) {
const keyInfo = apiKeyMap [ r . apiKey ] ;
const keyName = keyInfo ? . name || r . apiKey . slice ( 0 , 8 ) + "..." ;
const akKey = ` ${ r . apiKey } | ${ r . model } | ${ r . provider || "unknown" } ` ;
if ( ! stats . byApiKey [ akKey ] ) {
stats . byApiKey [ akKey ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 , rawModel : r . model , provider : providerDisplayName , apiKey : r . apiKey , keyName , apiKeyKey : r . apiKey , lastUsed : r . timestamp } ;
}
const ake = stats . byApiKey [ akKey ] ;
ake . requests ++ ; ake . promptTokens += promptTokens ; ake . completionTokens += completionTokens ; ake . cost += entryCost ;
if ( new Date ( r . timestamp ) > new Date ( ake . lastUsed ) ) ake . lastUsed = r . timestamp ;
} else {
if ( ! stats . byApiKey [ "local-no-key" ] ) {
stats . byApiKey [ "local-no-key" ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 , rawModel : r . model , provider : providerDisplayName , apiKey : null , keyName : "Local (No API Key)" , apiKeyKey : "local-no-key" , lastUsed : r . timestamp } ;
}
const ake = stats . byApiKey [ "local-no-key" ] ;
ake . requests ++ ; ake . promptTokens += promptTokens ; ake . completionTokens += completionTokens ; ake . cost += entryCost ;
if ( new Date ( r . timestamp ) > new Date ( ake . lastUsed ) ) ake . lastUsed = r . timestamp ;
}
const endpoint = r . endpoint || "Unknown" ;
const epKey = ` ${ endpoint } | ${ r . model } | ${ r . provider || "unknown" } ` ;
if ( ! stats . byEndpoint [ epKey ] ) {
stats . byEndpoint [ epKey ] = { requests : 0 , promptTokens : 0 , completionTokens : 0 , cost : 0 , endpoint , rawModel : r . model , provider : providerDisplayName , lastUsed : r . timestamp } ;
}
const epe = stats . byEndpoint [ epKey ] ;
epe . requests ++ ; epe . promptTokens += promptTokens ; epe . completionTokens += completionTokens ; epe . cost += entryCost ;
if ( new Date ( r . timestamp ) > new Date ( epe . lastUsed ) ) epe . lastUsed = r . timestamp ;
}
}
stats . totalRequests = Object . values ( stats . byProvider ) . reduce ( ( sum , p ) => sum + ( p . requests || 0 ) , 0 ) ;
return stats ;
}
export async function getChartData ( period = "7d" ) {
const db = await getAdapter ( ) ;
const now = Date . now ( ) ;
2026-05-13 23:03:05 -04:00
if ( period === "today" ) {
const bucketCount = 24 ;
const bucketMs = 3600000 ;
const startOfDay = new Date ( ) ;
startOfDay . setHours ( 0 , 0 , 0 , 0 ) ;
const startTime = startOfDay . getTime ( ) ;
const endTime = startTime + bucketCount * bucketMs ;
const labelFn = ( ts ) => new Date ( ts ) . toLocaleTimeString ( "en-US" , { hour : "2-digit" , minute : "2-digit" , hour12 : false } ) ;
const buckets = Array . from ( { length : bucketCount } , ( _ , i ) => ( { label : labelFn ( startTime + i * bucketMs ) , tokens : 0 , cost : 0 } ) ) ;
const rows = db . all (
` SELECT timestamp, promptTokens, completionTokens, cost FROM usageHistory WHERE timestamp >= ? ` ,
[ new Date ( startTime ) . toISOString ( ) ]
) ;
for ( const r of rows ) {
const t = new Date ( r . timestamp ) . getTime ( ) ;
if ( t < startTime || t >= endTime ) continue ;
const idx = Math . floor ( ( t - startTime ) / bucketMs ) ;
if ( idx >= 0 && idx < bucketCount ) {
buckets [ idx ] . tokens += ( r . promptTokens || 0 ) + ( r . completionTokens || 0 ) ;
buckets [ idx ] . cost += r . cost || 0 ;
}
}
return buckets ;
}
2026-05-09 06:48:20 -04:00
if ( period === "24h" ) {
const bucketCount = 24 ;
const bucketMs = 3600000 ;
const labelFn = ( ts ) => new Date ( ts ) . toLocaleTimeString ( "en-US" , { hour : "2-digit" , minute : "2-digit" , hour12 : false } ) ;
const startTime = now - bucketCount * bucketMs ;
const buckets = Array . from ( { length : bucketCount } , ( _ , i ) => ( { label : labelFn ( startTime + i * bucketMs ) , tokens : 0 , cost : 0 } ) ) ;
const rows = db . all (
` SELECT timestamp, promptTokens, completionTokens, cost FROM usageHistory WHERE timestamp >= ? ` ,
[ new Date ( startTime ) . toISOString ( ) ]
) ;
for ( const r of rows ) {
const t = new Date ( r . timestamp ) . getTime ( ) ;
if ( t < startTime || t > now ) continue ;
const idx = Math . min ( Math . floor ( ( t - startTime ) / bucketMs ) , bucketCount - 1 ) ;
buckets [ idx ] . tokens += ( r . promptTokens || 0 ) + ( r . completionTokens || 0 ) ;
buckets [ idx ] . cost += r . cost || 0 ;
}
return buckets ;
}
const bucketCount = period === "7d" ? 7 : period === "30d" ? 30 : 60 ;
const today = new Date ( ) ;
const labelFn = ( d ) => d . toLocaleDateString ( "en-US" , { month : "short" , day : "numeric" } ) ;
// Build map of dateKey → day data
const dayRows = loadDaysInRange ( db , bucketCount ) ;
const dayMap = { } ;
for ( const r of dayRows ) dayMap [ r . dateKey ] = parseJson ( r . data , { } ) ;
return Array . from ( { length : bucketCount } , ( _ , i ) => {
const d = new Date ( today ) ;
d . setDate ( d . getDate ( ) - ( bucketCount - 1 - i ) ) ;
const dateKey = ` ${ d . getFullYear ( ) } - ${ String ( d . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) } - ${ String ( d . getDate ( ) ) . padStart ( 2 , "0" ) } ` ;
const dayData = dayMap [ dateKey ] ;
return {
label : labelFn ( d ) ,
tokens : dayData ? ( dayData . promptTokens || 0 ) + ( dayData . completionTokens || 0 ) : 0 ,
cost : dayData ? ( dayData . cost || 0 ) : 0 ,
} ;
} ) ;
}
function formatLogDate ( date = new Date ( ) ) {
const pad = ( n ) => String ( n ) . padStart ( 2 , "0" ) ;
return ` ${ pad ( date . getDate ( ) ) } - ${ pad ( date . getMonth ( ) + 1 ) } - ${ date . getFullYear ( ) } ${ pad ( date . getHours ( ) ) } : ${ pad ( date . getMinutes ( ) ) } : ${ pad ( date . getSeconds ( ) ) } ` ;
}
// No-op: request log is now derived from usageHistory table on read.
export async function appendRequestLog ( ) { }
export async function getRecentLogs ( limit = 200 ) {
try {
2026-06-16 23:08:36 -04:00
const db = await getAdapter ( ) ;
2026-05-09 06:48:20 -04:00
const rows = db . all (
` SELECT timestamp, provider, model, connectionId, promptTokens, completionTokens, status, tokens FROM usageHistory ORDER BY id DESC LIMIT ? ` ,
[ limit ] ,
) ;
if ( ! rows . length ) return [ ] ;
const connMap = { } ;
try {
const { getProviderConnections } = await import ( "./connectionsRepo.js" ) ;
const connections = await getProviderConnections ( ) ;
for ( const c of connections ) connMap [ c . id ] = c . name || c . email || "" ;
} catch { }
return rows . map ( ( r ) => {
const ts = formatLogDate ( new Date ( r . timestamp ) ) ;
const p = r . provider ? . toUpperCase ( ) || "-" ;
const m = r . model || "-" ;
const account = connMap [ r . connectionId ] || ( r . connectionId ? r . connectionId . slice ( 0 , 8 ) : "-" ) ;
const tk = r . tokens ? parseJson ( r . tokens , { } ) : { } ;
const sent = r . promptTokens ? ? tk . prompt _tokens ? ? "-" ;
const received = r . completionTokens ? ? tk . completion _tokens ? ? "-" ;
return ` ${ ts } | ${ m } | ${ p } | ${ account } | ${ sent } | ${ received } | ${ r . status || "-" } ` ;
} ) ;
} catch ( e ) {
console . error ( "[usageRepo] getRecentLogs failed:" , e . message ) ;
return [ ] ;
}
}