/**
 * streamHandler — one run per request, streamed in a wire protocol, as a
 * framework-agnostic Fetch-API handler: `(request: Request) => Promise<Response>`.
 *
 * Pattern: Template method over a {@link ProtocolAdapter} (the protocol is a
 *          strategy; AG-UI is one). Everything HTTP happens here once — the
 *          door guard, the body ceiling, the JSON parse, binding the
 *          conversation to its caller, keeping it between requests, the client
 *          hanging up, a failure that is never the process's — so an adapter
 *          only reads its request, plans the turn and translates.
 * Role:    the host for a protocol whose response is a STREAM OF EVENTS rather
 *          than text pieces (`httpHost`'s port). It mounts in anything that
 *          speaks `Request`/`Response` — a framework's route handler, an edge
 *          or serverless runtime, or Node through any fetch adapter.
 *          Deliberately names no vendor, like every file in this folder.
 *
 * THE REQUEST IS UNTRUSTED. A body is what anybody could send. It decides
 * nothing the server owns:
 *   - WHO is calling and WHICH conversation is theirs come from the app's
 *     {@link ThreadBinder} (`threads`), from the request's headers — the id a
 *     request names is format-checked caller data and never a key;
 *   - WHAT WAS SAID BEFORE comes from the server: the session store
 *     (`sessions`), or the agent's own memory keyed by the bound session;
 *   - WHAT A PAUSED RUN WAITS ON is the stored run's own record — an answer is
 *     matched to it by the adapter, never taken on the request's word;
 *   - identity and redaction settings never come from a request. A credential
 *     the request CARRIES (a gateway's per-request token) reaches its own run
 *     only through the app's `sealRequest`, sealed: never a frame, a stored
 *     session or a checkpoint.
 *
 * NOTHING IS SHARED BETWEEN REQUESTS. Every request builds a FRESH runner
 * (`agent`, a factory): footprintjs runs one executor per run and a runner
 * holds one run's context, so a request can neither see another's events nor
 * leave anything behind for one — no shared listener, recorder, buffer, tool
 * session or frame count (each stream numbers its own frames, and hands the
 * number to the framing: `SseFramePosition`). A factory that hands back a
 * runner it handed back before is refused; so is a `sealRequest` that hands
 * back a sealed request twice. The handler's own state is KEYS, never content:
 * the bound conversations with a turn in flight and who holds each (a second
 * turn on one conversation is refused rather than racing the first for the
 * stored state), and the runners and sealed requests already used (by
 * identity, held weakly).
 *
 * ONE TURN PER THREAD IS A PER-PROCESS GUARD. Two instances of the handler
 * (several serverless instances, several workers) do not see each other's
 * turns: route a thread to one instance (sticky sessions), or a double-sent
 * answer can resume one paused run twice and a later persist can overwrite an
 * earlier one. The session store port has no compare-and-set yet. A consent
 * decision itself is used once per LEDGER: the agents every instance builds
 * share one only if they are given one (`decisionLedger`).
 *
 * The laws it keeps from `httpHost`:
 *   - The door guard runs first, on the headers alone (`doorGuard.ts`): a Host
 *     not configured (421), a browser origin not allowed (403), a POST that
 *     does not say it is JSON (415). A refused body is never read.
 *   - A body over `maxBodyBytes` is refused at the byte that crosses it (413).
 *   - A refusal answers with its own status and code (`refusalStatus.ts`);
 *     anything else is a 500 that names nothing the caller sent and no thrown
 *     message.
 *   - Nothing in a request's lifecycle is the process's failure: every step is
 *     caught, and a stream always ends.
 */
import type { AgentRunCheckpoint } from '../core/runCheckpoint.js';
import type { Runner } from '../core/runner.js';
import { type CrossSiteOptions } from './doorGuard.js';
import type { ProtocolAdapter, StreamRunContext, ThreadBinder } from './protocolAdapter.js';
import { type RequestSealer } from './requestSealing.js';
import type { SessionLifecycle } from './types.js';
/** The default body ceiling — 1 MiB, the same as `nodeHost`. */
export declare const DEFAULT_STREAM_MAX_BODY_BYTES = 1048576;
/** The default keep-alive interval: an SSE comment every 15 s of silence. */
export declare const DEFAULT_STREAM_HEARTBEAT_MS = 15000;
/**
 * The runner one request runs — an `Agent`, an `LLMCall`, a composition. An
 * Agent's `checkpoint()` is the conversation a session store keeps; a runner
 * without one answers each request's message on its own.
 */
export type StreamRunner = Pick<Runner, 'on' | 'run' | 'resume'> & {
    checkpoint?(): AgentRunCheckpoint | undefined;
    shutdown?(options?: {
        readonly stop?: boolean;
    }): Promise<void>;
};
/** Options for {@link streamHandler}. */
export interface StreamHandlerOptions<TRequest, TEvent> extends CrossSiteOptions {
    /**
     * Builds a FRESH runner for every request — called once per request, with
     * what that request's runner is built with ({@link StreamRunContext}: the
     * tools the caller runs, the values the app's validators accepted, the bound
     * conversation). A runner is never shared between requests; one handed back
     * twice is refused.
     */
    readonly agent: (run: StreamRunContext) => StreamRunner;
    /** The wire protocol — `agUi()` from `agentfootprint/ag-ui`. */
    readonly adapter: ProtocolAdapter<TRequest, TEvent>;
    /**
     * The app's port that binds the conversation a request names to the caller
     * the app authenticates ({@link ThreadBinder}). Without it every request is
     * a conversation of one message: no session, no identity, nothing kept.
     */
    readonly threads?: ThreadBinder;
    /**
     * Where a bound conversation is kept between requests — what was said (an
     * Agent's `checkpoint()`) and a run paused on an answer — under the key
     * `threads` binds (`memorySessions()`, `sqliteSessions()`, or yours). Needs
     * `threads`: nothing is ever kept under the id a request names.
     */
    readonly sessions?: SessionLifecycle;
    /**
     * Seal the credential a request carries for its run ({@link RequestSealer}) —
     * a gateway's per-request token in a header, read by a `requestCredentials`
     * provider for the tools that declare a `needs` for it. Called once per
     * request that runs, after the thread is bound and the turn is planned, with
     * the request's headers; its handle goes to THAT request's
     * `run()` / `resume()` and nowhere else — never a frame, a stored session or
     * a checkpoint. A handle returned twice is refused; a sealer that throws
     * refuses the request in fixed words (`ERR_REQUEST_NOT_SEALED`, 400).
     * Without it, no run carries a request.
     */
    readonly sealRequest?: RequestSealer;
    /** The largest request body read, in bytes. Default {@link DEFAULT_STREAM_MAX_BODY_BYTES}. */
    readonly maxBodyBytes?: number;
    /**
     * An SSE comment (`: ping`) every this many milliseconds, so a proxy does not
     * cut a stream that is quiet while the model thinks. Default
     * {@link DEFAULT_STREAM_HEARTBEAT_MS}; `0` turns it off.
     */
    readonly heartbeatMs?: number;
    /** Names this handler in refusals. Default `'<adapter name> stream'`. */
    readonly name?: string;
}
/** A Fetch-API handler: one request in, one streamed response out. */
export type StreamHandler = (request: Request) => Promise<Response>;
/**
 * Serve runs in `adapter`'s protocol: POST a protocol request, read a stream
 * of the protocol's events.
 *
 * @throws TypeError at construction for options that cannot be served, and
 *   when the adapter's framing cannot be built (an optional peer missing) —
 *   refused at boot, never mid-request.
 *
 * @example A route handler that speaks `Request`/`Response`
 * ```ts
 * import { memorySessions, streamHandler } from 'agentfootprint/hosting';
 * import { agUi } from 'agentfootprint/ag-ui';
 *
 * export const POST = streamHandler({
 *   agent: ({ tools }) => buildAgent(tools), // a fresh agent per request
 *   adapter: agUi(),
 *   threads: { bind: ({ conversation, headers }) => bindToUser(conversation, headers) },
 *   sessions: memorySessions(),
 * });
 * ```
 */
export declare function streamHandler<TRequest, TEvent>(options: StreamHandlerOptions<TRequest, TEvent>): StreamHandler;
