import type { SinkWriter, StreamFrame, TokenStreamSink } from './spi/token-stream-sink.js';
import type { LucidDatabaseLike } from './stores/lucid.js';
export interface LucidTokenStreamSinkOptions {
    /** The frame table. Defaults to `agent_stream_frame` (`AGENT_TABLES.streamFrames`). */
    tableName?: string;
    /**
     * Gap (ms) between two reads of a run a subscriber is following — the most a frame waits in the
     * table before a browser sees it. Defaults to 250.
     */
    pollIntervalMs?: number;
    /**
     * Gap (ms) between two reads once the run has written nothing for five seconds (parked on a
     * human, a slow tool), so an open SSE connection on a quiet run costs one query a second rather
     * than four. The first frame to arrive puts the subscriber back on `pollIntervalMs`. Defaults to
     * 1000; never below `pollIntervalMs`.
     */
    idlePollIntervalMs?: number;
    /**
     * Window (ms) consecutive `text` frames are gathered over and written as ONE row — a model
     * streams token by token, and a row per token is more writes than a database should take for a
     * chat answer. Any other frame, and `end()`, write what is gathered first, so order is kept.
     * Defaults to 50. `0` writes every frame as its own row.
     */
    flushMs?: number;
    /**
     * TTL (seconds) of a run's rows, counted from its LAST write — so a long run stays, and a run
     * that crashed without ending still lapses. {@link LucidTokenStreamSink.purgeExpired} is what
     * deletes them. Defaults to 3600 (1h). `0` keeps rows until `close()`/manual cleanup.
     */
    ttlSeconds?: number;
    /**
     * Purge lapsed runs from this instance as a side effect of ending a run, at most once a minute,
     * so the table stays bounded in an app that never schedules {@link LucidTokenStreamSink.purgeExpired}.
     * Default `true`. Set `false` to purge only from your own schedule.
     */
    autoPurge?: boolean;
    /**
     * Create the frame table on first use (and at startup, under the agent provider). Default `true`.
     * Set `false` when the published migration (`createAgentTables`) owns the schema.
     */
    autoCreateTables?: boolean;
}
/**
 * A multi-replica {@link TokenStreamSink} over the app's SQL database (through `@adonisjs/lucid`) —
 * for a deployment with several replicas and NO Redis. The replica running the model turn appends
 * each frame as a row of `agent_stream_frame`; any replica serves the run's SSE by reading the rows
 * past its cursor, in order, until it meets the end marker. A subscriber that attaches late, or
 * after the run ended, replays from the first row — the same guarantee the in-process and Redis
 * sinks give.
 *
 * Two things differ from the Redis sink, and both are the price of having no broker:
 *
 *  - **Delivery is polled.** There is no publish to wake a subscriber, so a frame waits up to
 *    `pollIntervalMs` (250 ms) before a browser sees it.
 *  - **Text is coalesced on the way in.** Consecutive `text` frames written within `flushMs`
 *    (50 ms) are stored as ONE `text` frame holding their concatenation. The stored rows are the
 *    run's stream: every subscriber, on every replica, reads the same rows in the same order, so
 *    the SSE event ids `AgentSseEncoder` counts from them — and an `after` cursor — stay exact. They
 *    are NOT the ids the in-process sink would have produced for the same run (it numbers every
 *    token), which only matters to a cursor carried across a change of sink.
 *
 * Sequence numbers are per run, start at 1 and have no gaps: each append takes `MAX(seq) + 1` for
 * its run in the same statement that inserts it, and the `(run_id, seq)` primary key turns a
 * collision into a retry. A run normally has ONE writer, and then no append ever collides; the
 * retry exists for a parent whose delegated children (each forwarding into its stream, possibly
 * from another replica) write at once. Nothing depends on ordering across runs.
 *
 * Text a writer is still gathering lives in the process for at most `flushMs`; a process that dies
 * in that window loses it from the LIVE stream (the persisted message is unaffected).
 *
 * Wire it with `defineConfig({ sink: tokenSinks.lucid() })`.
 */
export declare class LucidTokenStreamSink implements TokenStreamSink {
    private readonly db;
    private readonly table;
    private readonly pollIntervalMs;
    private readonly idlePollIntervalMs;
    private readonly flushMs;
    private readonly ttlSeconds;
    private readonly autoPurge;
    private readonly autoCreateTables;
    private readonly writes;
    private provisioned;
    private lastAutoPurge;
    constructor(db: LucidDatabaseLike, options?: LucidTokenStreamSinkOptions);
    /**
     * Create the frame table if it is missing. The agent provider calls this once as the app starts;
     * a sink built by hand falls back to it on first use. A no-op under `autoCreateTables: false`.
     */
    ensureSchema(): Promise<void>;
    private runWrites;
    /** Forget a run's write state once nothing is gathered, queued or left to report. */
    private release;
    /** Queue `work` behind every write already queued for the run, so rows land in call order. */
    private enqueue;
    /** Queue the gathered text (if any) as one `text` row. */
    private flushText;
    /** Raise, once, a gathered write that failed with nobody awaiting it. */
    private raiseFailure;
    /** Append one row to the run — a frame, or the end marker (`null`). */
    private append;
    /**
     * The run's writer. Writers opened for the same run in one process share what they are gathering,
     * so a delegated run's text (forwarded through a {@link childSinkWriter}) and its parent's next
     * frame keep the order they were written in.
     */
    open(runId: string): SinkWriter;
    subscribe(runId: string): AsyncIterable<StreamFrame>;
    has(runId: string): Promise<boolean>;
    close(runId: string): Promise<void>;
    /**
     * Delete every run whose last write is older than `ttlSeconds` at `now` (epoch ms) — ended runs
     * past their replay window and runs that crashed without ending alike. Returns how many runs went.
     * Safe to call from any replica, and from several at once. A no-op under `ttlSeconds: 0`.
     *
     * An instance already calls it after a run ends (at most once a minute; `autoPurge`), which keeps
     * the table bounded on a replica that streams. Call it from a schedule as well if a quiet
     * deployment must not keep the last hour's streams around indefinitely.
     */
    purgeExpired(now?: number): Promise<number>;
    /** {@link purgeExpired}, unawaited and at most once a minute. Its failure is not the run's. */
    private purgeInBackground;
}
//# sourceMappingURL=lucid-token-stream-sink.d.ts.map