import * as fs from "fs" import * as path from "path" import * as os from "os" import { getConfigDir } from "../auth/config" export type TelemetryEvent = | { event: "tool_call"; tool: string; latency_ms: number; input_preview?: string } | { event: "llm_response"; tokens_in: number; tokens_out: number; cost: number; model: string; latency_ms: number } | { event: "compaction"; tokens_saved: number; messages_before: number; messages_after: number; trigger: string } | { event: "error"; source: string; error: string; tool?: string } | { event: "session_start"; model: string; tools: string[]; cwd: string } | { event: "session_end"; duration_ms: number; total_tool_calls: number; total_tokens: number } interface SessionLog { sessionId: string startedAt: string events: TelemetryEvent[] } let currentSession: SessionLog | null = null let sessionStartTime = 0 function getLogsDir(): string { const base = process.env.LLMTUNE_LOGS_DIR || path.join(getConfigDir(), "logs") if (!fs.existsSync(base)) { fs.mkdirSync(base, { recursive: true }) } return base } function getSessionLogPath(sessionId: string): string { return path.join(getLogsDir(), `${sessionId}.jsonl`) } export function startSessionLog(sessionId: string, meta: { model: string; tools: string[]; cwd: string }): void { currentSession = { sessionId, startedAt: new Date().toISOString(), events: [], } sessionStartTime = Date.now() logEvent({ event: "session_start", model: meta.model, tools: meta.tools, cwd: meta.cwd }) } export function logEvent(event: TelemetryEvent): void { if (!currentSession) return currentSession.events.push(event) const entry = { ts: new Date().toISOString(), session_id: currentSession.sessionId, ...event, } try { const logPath = getSessionLogPath(currentSession.sessionId) fs.appendFileSync(logPath, JSON.stringify(entry) + "\n", "utf-8") } catch { // Telemetry write failure is non-critical } } export function endSessionLog(stats: { totalToolCalls: number; totalTokens: number }): void { if (!currentSession) return const durationMs = Date.now() - sessionStartTime logEvent({ event: "session_end", duration_ms: durationMs, total_tool_calls: stats.totalToolCalls, total_tokens: stats.totalTokens, }) currentSession = null } export function getSessionLogs(sessionId: string): TelemetryEvent[] { const logPath = getSessionLogPath(sessionId) try { if (!fs.existsSync(logPath)) return [] const lines = fs.readFileSync(logPath, "utf-8").split("\n").filter(Boolean) return lines.map((line) => { try { return JSON.parse(line) as TelemetryEvent } catch { return null } }).filter((e): e is TelemetryEvent => e !== null) } catch { return [] } } export function listSessionLogs(): Array<{ sessionId: string; size: number; modified: string }> { const dir = getLogsDir() try { if (!fs.existsSync(dir)) return [] return fs.readdirSync(dir) .filter((f) => f.endsWith(".jsonl")) .map((f) => { const fullPath = path.join(dir, f) const stat = fs.statSync(fullPath) return { sessionId: f.replace(".jsonl", ""), size: stat.size, modified: stat.mtime.toISOString(), } }) .sort((a, b) => b.modified.localeCompare(a.modified)) } catch { return [] } }