UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

124 lines (123 loc) • 5.26 kB
import { readFileSync } from "node:fs"; import { join } from "node:path"; import { chargeUsageRecord, readUsageRecord, EMPTY_USAGE_WINDOW_RECORD, } from "../../core/usage-window-store.js"; import { atomicWriteFile, ensureDir, sanitizeScope } from "./fs-atomic.js"; import { assertAdoptionBootGate } from "./adoption/marker.js"; import { guardCorruptSink } from "./corrupt-read-sink.js"; export class FileUsageWindowStore { dir; onCorruptRead; constructor(root, opts) { assertAdoptionBootGate(root, "FileUsageWindowStore"); this.dir = join(root, "usage-windows"); ensureDir(this.dir); this.onCorruptRead = guardCorruptSink(opts?.onCorruptRead); } pathFor(key) { return join(this.dir, `${sanitizeScope(key)}.json`); } loadRecord(key) { const path = this.pathFor(key); try { return judgeRecord(path); } catch (err) { if (!(err instanceof LedgerFault)) throw err; this.onCorruptRead?.({ kind: "file", path, reason: err.message }); throw corrupt(key, err.message); } } async charge(key, tokens, at, windows, costMicroUsd) { const next = chargeUsageRecord(this.loadRecord(key), tokens, at, windows, costMicroUsd); atomicWriteFile(join(this.dir, "tmp"), this.pathFor(key), JSON.stringify(next)); } async read(key, windows, now) { return readUsageRecord(this.loadRecord(key), windows, now); } } class LedgerFault extends Error { } function judgeRecord(path) { let raw; try { raw = readFileSync(path, "utf8"); } catch (err) { if (err.code === "ENOENT") return EMPTY_USAGE_WINDOW_RECORD; throw new LedgerFault(`read failed (${err.code ?? err.name})`); } let parsed; try { parsed = JSON.parse(raw); } catch { throw new LedgerFault("unparseable JSON (torn/zero-byte write?)"); } if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new LedgerFault("not a JSON object"); const rec = parsed; return { slots: validateSlots(rec.slots), buckets: validateBuckets(rec.buckets) }; } function corrupt(key, reason) { const e = new Error(`usage-window ledger for key ${JSON.stringify(key)} is unreadable (${reason}). Refused rather than treated as empty: an unreadable ceiling would grant the key an unlimited, silent allowance. Repair it: fix the file, or delete it (deleting it = no usage recorded for the key). The file's location is reported to the store's corrupt-read disclosure (onCorruptRead), not in this message.`); e.code = "usage_window.store_corrupt"; return e; } function validateSlots(value) { if (value === undefined) return []; if (!Array.isArray(value)) throw new LedgerFault("`slots` is not an array"); return value.map((s) => { if (s === null || typeof s !== "object") throw new LedgerFault("`slots` entry is not an object"); const { at, tokens, costMicroUsd, costUnknown } = s; if (typeof at !== "number" || !Number.isFinite(at) || typeof tokens !== "number" || !Number.isFinite(tokens)) { throw new LedgerFault("`slots` entry has a non-numeric `at`/`tokens`"); } if (costMicroUsd !== undefined && (typeof costMicroUsd !== "number" || !Number.isFinite(costMicroUsd))) { throw new LedgerFault("`slots` entry has a non-numeric `costMicroUsd`"); } if (costUnknown !== undefined && costUnknown !== true) throw new LedgerFault("`slots` entry has a `costUnknown` that is not `true`"); return { at, tokens, ...(costMicroUsd === undefined ? {} : { costMicroUsd }), ...(costUnknown === true ? { costUnknown: true } : {}), }; }); } function validateBuckets(value) { if (value === undefined) return []; if (!Array.isArray(value)) throw new LedgerFault("`buckets` is not an array"); return value.map((b) => { if (b === null || typeof b !== "object") throw new LedgerFault("`buckets` entry is not an object"); const { windowMs, openedAt, tokens, costMicroUsd, costUnknown } = b; if (typeof windowMs !== "number" || !Number.isFinite(windowMs) || typeof openedAt !== "number" || !Number.isFinite(openedAt) || typeof tokens !== "number" || !Number.isFinite(tokens)) { throw new LedgerFault("`buckets` entry has a non-numeric `windowMs`/`openedAt`/`tokens`"); } if (costMicroUsd !== undefined && (typeof costMicroUsd !== "number" || !Number.isFinite(costMicroUsd))) { throw new LedgerFault("`buckets` entry has a non-numeric `costMicroUsd`"); } if (costUnknown !== undefined && costUnknown !== true) throw new LedgerFault("`buckets` entry has a `costUnknown` that is not `true`"); return { windowMs, openedAt, tokens, ...(costMicroUsd === undefined ? {} : { costMicroUsd }), ...(costUnknown === true ? { costUnknown: true } : {}), }; }); }