import type { Database as BetterSqliteDatabase } from 'better-sqlite3';
import { type ParsedCodexSession } from './CodexRolloutParser.js';
export interface IngestLineResult {
    inserted: boolean;
    reason?: string;
}
export interface IngestFileResult {
    inserted: number;
    skipped: number;
}
export interface ScanAllResult {
    filesScanned: number;
    inserted: number;
}
export interface SummaryRow {
    totalTokens: number;
    totalInput: number;
    totalOutput: number;
    totalCacheRead: number;
    totalCacheCreate: number;
    eventCount: number;
    sessionsActive: number;
    oldestEventTs: number | null;
    newestEventTs: number | null;
}
export interface TopSessionRow {
    sessionId: string;
    projectPath: string | null;
    totalTokens: number;
    eventCount: number;
    firstTs: number;
    lastTs: number;
}
export interface ProjectRow {
    projectPath: string | null;
    totalTokens: number;
    eventCount: number;
    sessionCount: number;
}
export interface AttributionKeyRow {
    attributionKey: string;
    totalTokens: number;
    eventCount: number;
    firstTs: number;
    lastTs: number;
}
export interface OrphanRow {
    sessionId: string;
    projectPath: string | null;
    lastTs: number;
    totalTokens: number;
    idleMs: number;
}
export interface CodexSessionRow {
    sessionId: string;
    projectPath: string | null;
    model: string | null;
    planType: string | null;
    inputTokens: number;
    cachedInputTokens: number;
    outputTokens: number;
    reasoningOutputTokens: number;
    totalTokens: number;
    primaryUsedPercent: number | null;
    secondaryUsedPercent: number | null;
    tokenCountEvents: number;
    firstTs: number;
    lastTs: number;
}
export interface CodexSummaryRow {
    totalTokens: number;
    totalInput: number;
    totalCachedInput: number;
    totalOutput: number;
    totalReasoning: number;
    sessionCount: number;
    oldestTs: number | null;
    newestTs: number | null;
    /** Highest short-window subscription usage % across sessions (latest reading). */
    maxPrimaryUsedPercent: number | null;
    /** Highest long-window subscription usage % across sessions (latest reading). */
    maxSecondaryUsedPercent: number | null;
}
export interface CodexIngestResult {
    ingested: boolean;
    sessionId?: string;
    reason?: string;
}
export interface TokenLedgerOptions {
    /** SQLite DB path. Use `:memory:` for tests. */
    dbPath: string;
    /** Root directory of Claude Code project transcripts (e.g. `~/.claude/projects`). */
    claudeProjectsDir: string;
    /**
     * Skip JSONL files whose mtime is older than this many ms when scanning.
     * Bounds the work the ledger does on agents with deep history (Echo had
     * 119k files / 12GB which blocked the event loop). Pass 0 / undefined to
     * scan everything. Default: scan everything (caller decides).
     */
    maxFileAgeMs?: number;
    /**
     * Per-tick scan cap. After this many files have been processed in a single
     * scanAll call, the scan returns and resumes on the next poll. Prevents a
     * single tick from monopolising the event loop. Default 500.
     */
    maxFilesPerScan?: number;
    /**
     * Yield to the event loop every N files within a scan. Default 25.
     * Even with maxFilesPerScan, a 500-file batch on slow disks can block for
     * seconds without yielding.
     */
    yieldEveryNFiles?: number;
    /**
     * Test seam for scanAllAsync's event-loop yield. Production uses setImmediate.
     */
    asyncYieldFn?: () => Promise<void>;
    /**
     * Test seam for constructor-time native open recovery. Production uses
     * better-sqlite3's Database constructor directly.
     */
    databaseFactory?: (dbPath: string) => BetterSqliteDatabase;
    /**
     * How the one-time attribution backfill (legacy PRE_ATTRIBUTION_KEY sentinel
     * rows → resolved keys) runs at construction:
     *  - 'async' (default): scheduled off the boot path in bounded chunks that
     *    yield the event loop between batches, so construction NEVER blocks. This
     *    is the fix for large ledgers bricking server boot — the old synchronous
     *    full-scan-in-one-transaction could exceed the supervisor health-check
     *    timeout, getting the boot killed mid-scan so the completion marker was
     *    never written and the backfill re-ran forever (permanent crash-loop).
     *  - 'sync': drain fully during construction (deterministic; for tests).
     *  - 'off': don't auto-run; the caller drives backfillAttributionOnce()/Chunk().
     * Default: 'async'.
     */
    attributionBackfill?: 'async' | 'sync' | 'off';
}
export declare class TokenLedger {
    private db;
    private claudeProjectsDir;
    private maxFileAgeMs;
    private maxFilesPerScan;
    private yieldEveryNFiles;
    private asyncYieldFn;
    /** Background attribution-backfill timer (async strategy); cleared on close(). */
    private attributionBackfillTimer;
    /** Consecutive failed backfill chunks; bounded by ATTRIBUTION_BACKFILL_MAX_FAILURES. */
    private attributionBackfillFailures;
    /** Set in close() so a pending background chunk doesn't touch a closed DB. */
    private closed;
    private scanCursor;
    private stmts;
    constructor(opts: TokenLedgerOptions);
    /** Read a value from the ledger_meta key/value table (null if absent). */
    private getMeta;
    /** Upsert a value into the ledger_meta key/value table. */
    private setMeta;
    /**
     * Idempotent backfill of the attribution_key column for legacy rows that
     * still carry the PRE_ATTRIBUTION_KEY sentinel (ingested before Phase 2
     * attribution was wired into ingestLine).
     *
     * Why it's needed: before this fix, every JSONL-sourced event was hardcoded
     * to `unknown::pre-attribution`, so 100% of spend sat in one bucket and the
     * BurnDetector's absolute-share trigger fired forever. Backfilling re-resolves
     * those rows from the signals we DO have on each row (session_id, project_path,
     * model).
     *
     * This is the FULL-DRAIN form (loops bounded chunks until done) — used by the
     * 'sync' boot strategy, explicit callers, and tests. Production boot uses the
     * async scheduler (scheduleAttributionBackfill) so a large ledger can't block
     * construction. Marker-guarded via ledger_meta so it runs at most once per DB.
     *
     * Termination: each chunk either makes progress (rows leave the sentinel) or
     * sets the completion marker (no further progress possible), so this always
     * terminates even if some triples resolve back to the sentinel.
     */
    backfillAttributionOnce(): {
        backfilled: number;
        alreadyDone: boolean;
    };
    /**
     * Process up to `limit` still-sentinel ROWS, resolving each to its attribution
     * key and updating it by rowid (an O(1) integer-primary-key update). Committed
     * per chunk so progress survives a restart. Returns done=true once the
     * completion marker is set — either no sentinel rows remain, or this batch
     * could move none off the sentinel (the documented acceptable worst case;
     * remaining rows stay unattributed and are exempt from burn alerts).
     *
     * Row-scoped (not distinct-triple-scoped) on purpose. The prior design ran one
     * `UPDATE ... WHERE attribution_key = sentinel AND session/project/model = ...`
     * per distinct triple, and EACH such UPDATE re-scanned the whole sentinel
     * partition — O(triples × sentinel_size). On a 202MB / ~390k-sentinel-row
     * ledger that was ~23s of synchronous work per 100-triple chunk, blocking the
     * event loop in bursts even though it ran off the boot path. Selecting N rows
     * and updating each by rowid is O(N) per chunk regardless of ledger size
     * (~5ms for 1000 rows on that same DB), so no chunk blocks the event loop.
     * Two rows of the same (session, project, model) triple resolve to the same
     * key, so the end state is identical to the old triple-batched approach.
     */
    backfillAttributionChunk(limit?: number): {
        backfilled: number;
        done: boolean;
    };
    /**
     * Drive the attribution backfill in bounded chunks off the event loop so a
     * large ledger never blocks construction or server boot. Each tick processes
     * one chunk then yields (setTimeout); reschedules until the marker is set.
     * Unref'd so it never keeps the process alive on its own. Cancelled by close().
     */
    private scheduleAttributionBackfill;
    private prepareStatements;
    /**
     * Parse a single JSONL line and INSERT OR IGNORE into token_events.
     * Returns inserted=true only when a new row was added.
     */
    ingestLine(line: string): IngestLineResult;
    /**
     * Record a token event from a direct-API provider (Phase 1 of the
     * burn-detection-and-self-heal spec). The Claude CLI path already writes
     * JSONL that the ledger ingests; direct-API providers (e.g. the
     * AnthropicIntelligenceProvider) have no JSONL trail and must call this
     * method explicitly when an LLM call completes.
     *
     * Attribution-key shape (per spec): `<componentName>::<promptFingerprint>`.
     * The caller is the chokepoint that knows both pieces. Events with an
     * empty/missing attribution_key fall back to 'unknown::direct-api'.
     *
     * Idempotent on request_id (INSERT OR IGNORE).
     */
    recordEvent(event: {
        requestId: string;
        sessionId: string;
        projectPath?: string | null;
        ts: number;
        model?: string | null;
        inputTokens: number;
        outputTokens: number;
        cacheCreationTokens?: number;
        cacheReadTokens?: number;
        serviceTier?: string | null;
        attributionKey?: string;
    }): IngestLineResult;
    /**
     * Read a JSONL file from its persisted offset and ingest each new line.
     * If the inode changed (rotation/replacement), starts from offset 0.
     */
    ingestFile(filePath: string): IngestFileResult;
    /**
     * Walk every `*.jsonl` under `claudeProjectsDir/<encoded-dir>/` and
     * incrementally ingest each one.
     *
     * Sync version retained for callers (and tests) that don't care about
     * event-loop yielding. Honors per-scan and age caps but does NOT yield.
     * Production callers should use {@link scanAllAsync} to keep the server
     * responsive on agents with large JSONL histories.
     */
    scanAll(): ScanAllResult;
    /**
     * Async variant that yields to the event loop every N files. Use this
     * from the poller so a multi-thousand-file backfill cannot monopolise
     * the event loop and stall HTTP / health-check traffic.
     */
    scanAllAsync(): Promise<ScanAllResult>;
    /**
     * Scan Codex (OpenAI) rollout files under `$CODEX_HOME/sessions` and upsert
     * the sessions belonging to this agent into codex_token_sessions. Read-only.
     *
     * Codex's session store is per-machine and GLOBAL (every Codex agent on the
     * box writes there), so we attribute by cwd: only rollouts whose working
     * directory is `projectDir` (or a subdirectory) are counted as ours. Without
     * a projectDir filter, all rollouts are ingested (project_path preserved for
     * downstream filtering). Cumulative-total semantics make this idempotent.
     */
    scanCodexRolloutsAsync(opts?: {
        projectDir?: string;
        codexHome?: string;
        limit?: number;
        maxFileAgeMs?: number;
    }): Promise<{
        filesScanned: number;
        ingested: number;
    }>;
    private scanInternal;
    private scanLoopSync;
    private scanLoopAsync;
    private listJsonlFiles;
    private shouldScanFile;
    /** Aggregate totals (optionally restricted to events ≥ sinceMs). */
    summary({ sinceMs }?: {
        sinceMs?: number;
    }): SummaryRow;
    /** Top sessions by total tokens, descending. */
    topSessions({ limit, sinceMs }?: {
        limit?: number;
        sinceMs?: number;
    }): TopSessionRow[];
    /**
     * Token activity for ONE session since a timestamp — gate F of the
     * SessionReaper (SESSION-REAPER-SPEC §3.1(3)). A lagging corroborator only:
     * the poller scans flushed JSONL on a ~60s cadence, so a positive result is
     * reliable but absence does NOT prove idleness. Keyed on the Claude Code
     * session_id; callers pass `claudeSessionId` (absent ⇒ they must KEEP).
     */
    sessionActivitySince(sessionId: string, sinceMs: number): {
        eventCount: number;
        tokens: number;
        lastTs: number;
    };
    /**
     * Total tokens since `sinceMs` across events whose model is in `models`.
     * Model-Tier Escalation §8 (FABLE-MODEL-ESCALATION-SPEC): backs the
     * `dailyUltraTokenCap` admission check — "how much ultra-model spend has
     * landed today (UTC)?". Read-only, like every TokenLedger surface.
     */
    tokensByModelSince(models: readonly string[], sinceMs: number): number;
    /** Aggregate by project (cwd). */
    byProject({ sinceMs }?: {
        sinceMs?: number;
    }): ProjectRow[];
    /**
     * Aggregate by attribution key (Phase 3 of burn-detection-and-self-heal
     * spec). Returns one row per key with totals, event count, and the time
     * window the key has been seen across the requested period.
     */
    byAttributionKey({ sinceMs, limit }?: {
        sinceMs?: number;
        limit?: number;
    }): AttributionKeyRow[];
    /** Sessions whose newest event is older than `idleMs` ago. */
    orphans({ idleMs }: {
        idleMs: number;
    }): OrphanRow[];
    /**
     * Ingest one Codex rollout file. Reads the whole file (rollouts are small
     * relative to Claude transcripts), parses the cumulative session total, and
     * upserts a single row keyed on the Codex session UUID. Idempotent: re-ingest
     * of a grown rollout replaces the totals with the latest reading.
     *
     * `last_ts` is taken from the file mtime because Codex's token_count events
     * carry no per-event timestamp. Returns ingested=false (with a reason) for
     * unreadable/empty/usage-less rollouts — never throws.
     */
    ingestCodexRollout(filePath: string): CodexIngestResult;
    /**
     * Upsert an already-parsed Codex session. Exposed separately so callers (and
     * tests) can ingest without touching the filesystem. `lastTs` is the
     * last-activity time (file mtime); `firstTs` falls back to it when the
     * rollout lacked a session_meta timestamp, so first_ts is never 0.
     */
    ingestCodexSession(parsed: ParsedCodexSession, lastTs: number): CodexIngestResult;
    /** Aggregate Codex usage. Optionally filter to sessions active since `sinceMs`. */
    codexSummary({ sinceMs }?: {
        sinceMs?: number;
    }): CodexSummaryRow;
    /** Per-session Codex rows, biggest first. Optionally filter by recency. */
    codexSessions({ limit, sinceMs }?: {
        limit?: number;
        sinceMs?: number;
    }): CodexSessionRow[];
    close(): void;
}
//# sourceMappingURL=TokenLedger.d.ts.map