import fs from "fs"; import path from "path"; import { EVAL_CASES_DIR, PRESETS_DIR, TRACES_DIR } from "../config"; import { ensureDir, writeJsonFile } from "../utils/files"; import type { PromptPreset } from "../types/preset"; export interface TraceRecord { trace_id: string; timestamp: string; model: string; prompt_version: string; schema_version: string; case_id?: string; user_question_raw: string; context: Record; request_payload_redacted: Record; raw_model_response: unknown; parsed_normalized_json: unknown; validation_result: { passed: boolean; errors: string[]; }; route_hint_summary?: unknown; route_hint: string | null; confidence: string | null; usage: { input_tokens: number; output_tokens: number; total_tokens: number; }; latency_ms: number; expected_route?: string; eval_label?: string; eval_mode?: string; request_count_for_case: number; } export interface HistoryListItem { trace_id: string; timestamp: string; model: string; question_short: string; confidence: string | null; validation_passed: boolean; route_hint: string | null; save_status: "saved"; } function redactSecrets(payload: Record): Record { const output = { ...payload }; delete output.apiKey; return output; } function isNoSpaceError(error: unknown): boolean { const code = (error as { code?: unknown } | null)?.code; return code === "ENOSPC"; } export function saveTrace(record: TraceRecord): void { try { ensureDir(TRACES_DIR); const target = path.resolve(TRACES_DIR, `${record.trace_id}.json`); writeJsonFile(target, record); } catch (error) { if (isNoSpaceError(error)) { return; } throw error; } } export function listTraces(limit = 100): HistoryListItem[] { ensureDir(TRACES_DIR); const files = fs .readdirSync(TRACES_DIR) .filter((item) => item.endsWith(".json")) .sort((a, b) => { const pa = path.resolve(TRACES_DIR, a); const pb = path.resolve(TRACES_DIR, b); return fs.statSync(pb).mtimeMs - fs.statSync(pa).mtimeMs; }) .slice(0, limit); return files.map((fileName) => { const raw = fs.readFileSync(path.resolve(TRACES_DIR, fileName), "utf-8"); const item = JSON.parse(raw) as TraceRecord; return { trace_id: item.trace_id, timestamp: item.timestamp, model: item.model, question_short: item.user_question_raw.slice(0, 110), confidence: item.confidence, validation_passed: item.validation_result.passed, route_hint: item.route_hint, save_status: "saved" }; }); } export function getTrace(traceId: string): TraceRecord | null { ensureDir(TRACES_DIR); const target = path.resolve(TRACES_DIR, `${traceId}.json`); if (!fs.existsSync(target)) { return null; } const raw = fs.readFileSync(target, "utf-8"); return JSON.parse(raw) as TraceRecord; } export function savePreset(preset: PromptPreset): void { try { ensureDir(PRESETS_DIR); writeJsonFile(path.resolve(PRESETS_DIR, `${preset.id}.json`), preset); } catch (error) { if (isNoSpaceError(error)) { return; } throw error; } } export function listPresets(): PromptPreset[] { ensureDir(PRESETS_DIR); return fs .readdirSync(PRESETS_DIR) .filter((item) => item.endsWith(".json")) .map((fileName) => { const raw = fs.readFileSync(path.resolve(PRESETS_DIR, fileName), "utf-8"); return JSON.parse(raw) as PromptPreset; }) .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); } export function saveEvalCase(casePayload: Record): string { const id = String(casePayload.case_id ?? `NQ-${Date.now()}`); try { ensureDir(EVAL_CASES_DIR); writeJsonFile(path.resolve(EVAL_CASES_DIR, `${id}.json`), casePayload); } catch (error) { if (!isNoSpaceError(error)) { throw error; } } return id; } export function redactRequestPayload(payload: Record): Record { return redactSecrets(payload); }