import { readdir, readFile, stat } from 'fs/promises'; import { join } from 'path'; import { getOpenClawConfigDir } from './paths'; import { logger } from './logger'; import { extractSessionIdFromTranscriptFileName, parseUsageEntriesFromJsonl, type TokenUsageHistoryEntry, } from './token-usage-core'; import { listConfiguredAgentIds } from './agent-config'; export { extractSessionIdFromTranscriptFileName, parseUsageEntriesFromJsonl, type TokenUsageHistoryEntry, } from './token-usage-core'; async function listRecentSessionFiles(): Promise> { const openclawDir = getOpenClawConfigDir(); const agentsDir = join(openclawDir, 'agents'); try { const agentEntries = await listConfiguredAgentIds(); const files: Array<{ filePath: string; sessionId: string; agentId: string; mtimeMs: number }> = []; for (const agentId of agentEntries) { const sessionsDir = join(agentsDir, agentId, 'sessions'); try { const sessionEntries = await readdir(sessionsDir); for (const fileName of sessionEntries) { const sessionId = extractSessionIdFromTranscriptFileName(fileName); if (!sessionId) continue; const filePath = join(sessionsDir, fileName); try { const fileStat = await stat(filePath); files.push({ filePath, sessionId, agentId, mtimeMs: fileStat.mtimeMs, }); } catch { continue; } } } catch { continue; } } files.sort((a, b) => b.mtimeMs - a.mtimeMs); return files; } catch { return []; } } export async function getRecentTokenUsageHistory(limit?: number): Promise { const files = await listRecentSessionFiles(); const results: TokenUsageHistoryEntry[] = []; const maxEntries = typeof limit === 'number' && Number.isFinite(limit) ? Math.max(Math.floor(limit), 0) : Number.POSITIVE_INFINITY; for (const file of files) { if (results.length >= maxEntries) break; try { const content = await readFile(file.filePath, 'utf8'); const entries = parseUsageEntriesFromJsonl(content, { sessionId: file.sessionId, agentId: file.agentId, }, Number.isFinite(maxEntries) ? maxEntries - results.length : undefined); results.push(...entries); } catch (error) { logger.debug(`Failed to read token usage transcript ${file.filePath}:`, error); } } results.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)); return Number.isFinite(maxEntries) ? results.slice(0, maxEntries) : results; }