const fs = require("fs"); const { resolveData } = require("./paths"); const historyFile = () => resolveData("metrics", "history.jsonl"); const stateFile = () => resolveData("metrics", "summary.json"); function getSummary() { try { return JSON.parse(fs.readFileSync(stateFile(), "utf8")); } catch { return { total_requests:0, successful:0, failed:0, refusals:0, tool_suggestions:0, tool_executions:0, tool_denials:0, confirmation_cancellations:0, timeout_count:0, runtime_crash_count:0, runtime_self_test_total:0, runtime_self_test_failed_total:0, runtime_start_attempt_total:0, runtime_start_failed_total:0, verified_downloads:0, failed_downloads:0, requests_by_role:{}, requests_by_scope:{}, runtime_exit_code_counts:{}, durations:[], queue_wait_total_ms:0 }; } } function record(entry) { const summary = getSummary(); summary.requests_by_role ||= {}; summary.requests_by_scope ||= {}; if (entry.kind === "request") { summary.total_requests += 1; if (entry.status === "success") summary.successful += 1; if (entry.status === "failed") summary.failed += 1; if (entry.status === "refused") summary.refusals += 1; if (entry.role) summary.requests_by_role[entry.role] = (summary.requests_by_role[entry.role] || 0) + 1; if (entry.scope) summary.requests_by_scope[entry.scope] = (summary.requests_by_scope[entry.scope] || 0) + 1; } if (entry.tool_requested) summary.tool_suggestions += 1; if (entry.tool_executed) summary.tool_executions += 1; if (entry.kind === "tool" && entry.status === "failed") summary.tool_denials += 1; if (entry.kind === "tool" && entry.status === "cancelled") summary.confirmation_cancellations += 1; if (entry.timeout) summary.timeout_count += 1; if (entry.runtime_crash) summary.runtime_crash_count += 1; if (entry.kind === "runtime_self_test") { summary.runtime_self_test_total += 1; if (entry.status === "failed") summary.runtime_self_test_failed_total += 1; } if (entry.kind === "runtime_start") { if (entry.status === "attempt") summary.runtime_start_attempt_total += 1; if (entry.status === "failed") summary.runtime_start_failed_total += 1; } if (entry.code) { summary.runtime_exit_code_counts ||= {}; summary.runtime_exit_code_counts[entry.code] = (summary.runtime_exit_code_counts[entry.code] || 0) + 1; } if (entry.kind === "download" && entry.status === "success") summary.verified_downloads += 1; if (entry.kind === "download" && entry.status === "failed") summary.failed_downloads += 1; if (entry.duration_ms != null) summary.durations.push(entry.duration_ms); summary.durations = summary.durations.slice(-500); if (entry.queue_wait_ms) summary.queue_wait_total_ms += entry.queue_wait_ms; fs.writeFileSync(stateFile(), JSON.stringify(summary, null, 2)); fs.appendFileSync(historyFile(), `${JSON.stringify({ timestamp:new Date().toISOString(), ...entry })}\n`); } function report() { const s = getSummary(); const sorted=[...s.durations].sort((a,b)=>a-b); return { ...s, average_response_ms: sorted.length ? Math.round(sorted.reduce((a,b)=>a+b,0)/sorted.length) : 0, median_response_ms: sorted.length ? sorted[Math.floor(sorted.length/2)] : 0 }; } function history(limit=100) { try { return fs.readFileSync(historyFile(),"utf8").trim().split(/\r?\n/).filter(Boolean).slice(-limit).reverse().map(JSON.parse); } catch { return []; } } module.exports = { record, report, history };