type JsonRecord = Record<string, unknown>;
interface StreamEvent {
    type: string;
    data?: JsonRecord;
}
declare function asRecord(value: unknown): JsonRecord | undefined;
declare function asString(value: unknown): string | undefined;
declare function resolveToolId(part: JsonRecord): string;
declare function resolveToolName(part: JsonRecord): string;
declare function normalizeTime(value: unknown): JsonRecord | undefined;
declare function normalizeToolEvent(event: StreamEvent): StreamEvent;
declare function normalizePersistedPart(rawPart: JsonRecord): JsonRecord | null;
declare function getPartKey(part: JsonRecord): string;
declare function mergePersistedPart(existing: JsonRecord | undefined, incoming: JsonRecord, delta?: string): JsonRecord;
declare function finalizeAssistantParts(partOrder: string[], partMap: Map<string, JsonRecord>, finalText: string): JsonRecord[];
declare function encodeEvent(encoder: TextEncoder, event: StreamEvent): Uint8Array;

interface PersistedChatMessageForTurn {
    id: string;
    role: 'user' | 'assistant' | 'system' | 'tool';
    content: string;
    parts: Array<Record<string, unknown>> | null;
}
interface ResolvedChatTurn {
    turnIndex: number;
    shouldInsertUserMessage: boolean;
    priorMessages: PersistedChatMessageForTurn[];
    userParts: JsonRecord[];
}
declare function normalizeClientTurnId(value: unknown): string | undefined;
declare function buildUserTextParts(text: string, turnId: string | undefined): JsonRecord[];
declare function messageHasTurnId(message: PersistedChatMessageForTurn, turnId: string): boolean;
declare function resolveChatTurn(input: {
    existingMessages: PersistedChatMessageForTurn[];
    userContent: string;
    turnId?: string;
}): ResolvedChatTurn;

/**
 * Resumable chat turns — the router-path answer to "streams resume on
 * disconnect" (issue #27). A turn's loop events are teed into a store as they
 * stream; the turn keeps running under `ctx.waitUntil` when the client drops;
 * a reconnecting client replays the buffered tail by sequence number and
 * keeps following until the turn completes.
 *
 *   POST /chat/stream            → pumpBufferedTurn(...) + live NDJSON
 *   GET  /chat/stream/:turnId    → replayTurnEvents({ fromSeq }) → NDJSON
 *
 * Storage is a structural seam ({@link TurnEventStore}); a D1 implementation
 * ships here because that's what Cloudflare products have (KV is unsuitable:
 * eventually consistent cross-isolate). Per-token deltas would mean hundreds
 * of rows per turn, so consecutive text/reasoning deltas are coalesced within
 * a flush window before they are persisted — replay yields slightly chunkier
 * deltas with identical concatenation.
 */
type TurnStatus = 'running' | 'complete' | 'error';
interface BufferedTurnEvent {
    seq: number;
    /** The serialized event line (JSON string, no trailing newline). */
    event: string;
}
interface TurnEventStore {
    append(turnId: string, events: BufferedTurnEvent[]): Promise<void>;
    read(turnId: string, fromSeq: number): Promise<BufferedTurnEvent[]>;
    /** Record turn lifecycle. `scopeId` (a thread/session id) is optional and lets
     *  {@link TurnEventStore.listRunning} rediscover this turn after a client reload
     *  loses the turnId; stores that don't track scope ignore it. */
    setStatus(turnId: string, status: TurnStatus, scopeId?: string): Promise<void>;
    getStatus(turnId: string): Promise<TurnStatus | null>;
    /** Running turnIds for a scope, newest first — so a reloaded client (clientRunId
     *  lost) can find and resume the in-flight turn. Optional: a store records it
     *  only if `setStatus` was given a `scopeId`. */
    listRunning?(scopeId: string): Promise<string[]>;
}
/** Merge consecutive text/reasoning deltas of the same type into one event.
 *  Concatenation-preserving: replaying the coalesced stream produces the same
 *  accumulated text as the original. */
declare function coalesceDeltas(events: unknown[]): unknown[];
/**
 * Coalesce consecutive `message.part.updated` deltas for the SAME part into one
 * event. agent-runtime products stream `ChatStreamEvent` NDJSON
 * (`{type:'message.part.updated', data:{part, delta}}`); pumped through the
 * buffer with the default tool-loop coalescer, every per-token delta persists as
 * its own row because that coalescer never recognizes the shape. Pass this as
 * {@link PumpBufferedTurnOptions.coalesce} instead.
 *
 * Concatenation-preserving for BOTH consumer styles: the merged event keeps the
 * LATEST event's `data.part` (already the cumulative accumulation) and sets
 * `data.delta` to the concatenation of the merged deltas, so a client that
 * appends `delta` and one that reads the cumulative `part` both reconstruct the
 * identical final text.
 */
declare function coalesceChatStreamEvents(events: unknown[]): unknown[];
interface BufferedTurnOptions {
    store: TurnEventStore;
    turnId: string;
    /** Deliver one serialized line to the live client. Throwing here (client
     *  disconnected) does NOT stop buffering — events keep persisting. */
    write?: (line: string) => Promise<void> | void;
    /** Flush buffered events to the store at most this often. Default 400ms. */
    flushIntervalMs?: number;
    /** Per-flush coalescer. Default {@link coalesceDeltas} (tool-loop text/reasoning
     *  deltas). agent-runtime products streaming `ChatStreamEvent` pass
     *  {@link coalesceChatStreamEvents} so per-token deltas don't each persist as a
     *  row. Must be concatenation-preserving. */
    coalesce?: (events: unknown[]) => unknown[];
    /** Optional scope (thread/session id) recorded with the turn status, so
     *  {@link TurnEventStore.listRunning} can find this turn after a reload. */
    scopeId?: string;
}
/** A push-driven buffer for a turn whose producer the caller does NOT own. */
interface BufferedTurnTap {
    /** Buffer one event: persist (coalesced, on the flush window) + best-effort
     *  live-deliver. Wire to a push source's per-event hook (e.g. agent-runtime
     *  `handleChatTurn`'s `hooks.onEvent`). Marks the turn 'running' on first call. */
    onEvent(raw: unknown): Promise<void>;
    /** Settle the turn: final flush + set status. Call after the producer resolves
     *  ('complete') or rejects ('error'). 'error' flushes what was produced first. */
    done(status?: Extract<TurnStatus, 'complete' | 'error'>): Promise<void>;
}
/**
 * The buffering core. Sequence-numbers every event, delivers it to `write`
 * (best-effort — a disconnected client never stops buffering), and flushes to
 * the store in coalesced batches. Drives both transports:
 *
 *   • {@link pumpBufferedTurn}     — when you OWN an `AsyncIterable` producer.
 *   • this tap (`onEvent`/`done`)  — when the producer owns iteration and only
 *     hands you a push callback (agent-runtime `handleChatTurn`'s `hooks.onEvent`
 *     + the finished body). Durability stays here in the shell; the engine needs
 *     no `TurnEventStore` seam.
 */
declare function createBufferedTurnTap(opts: BufferedTurnOptions): BufferedTurnTap;
interface PumpBufferedTurnOptions extends BufferedTurnOptions {
    source: AsyncIterable<unknown>;
}
/**
 * Drive a turn to completion regardless of the live client, when you OWN the
 * producer as an `AsyncIterable`. A thin driver over {@link createBufferedTurnTap}.
 * Returns a promise that resolves when the turn finishes — hand it to
 * `ctx.waitUntil` so a disconnect can't kill the turn. Never rejects on
 * client-write failure; a source error marks the turn 'error' (after flushing
 * what was produced) and rethrows.
 */
declare function pumpBufferedTurn(opts: PumpBufferedTurnOptions): Promise<void>;
interface ReplayTurnEventsOptions {
    store: TurnEventStore;
    turnId: string;
    /** Replay strictly after this sequence number (0 = from the beginning). */
    fromSeq?: number;
    /** Poll cadence while the turn is still running. Default 500ms. */
    pollMs?: number;
    /** Give up following a 'running' turn after this long. Default 120s. */
    timeoutMs?: number;
}
/**
 * Yield buffered events after `fromSeq`, then keep polling while the turn is
 * still 'running' until it completes, errors, or times out. Terminates with a
 * final `{seq: -1, event: '{"type":"turn_status",...}'}` marker so clients
 * know why the replay ended.
 */
declare function replayTurnEvents(opts: ReplayTurnEventsOptions): AsyncGenerator<BufferedTurnEvent>;
/** Minimal structural D1 contract (Cloudflare `D1Database` satisfies it). */
interface D1LikeForTurns {
    prepare(sql: string): {
        bind(...values: unknown[]): {
            run(): Promise<unknown>;
            all<T = Record<string, unknown>>(): Promise<{
                results: T[];
            }>;
            first<T = Record<string, unknown>>(): Promise<T | null>;
        };
    };
}
/** Schema for the D1 store — append to the product's migrations. */
declare const TURN_EVENTS_MIGRATION_SQL = "\nCREATE TABLE IF NOT EXISTS turn_events (\n  turnId TEXT NOT NULL,\n  seq INTEGER NOT NULL,\n  event TEXT NOT NULL,\n  PRIMARY KEY (turnId, seq)\n);\nCREATE TABLE IF NOT EXISTS turn_status (\n  turnId TEXT PRIMARY KEY,\n  status TEXT NOT NULL,\n  scopeId TEXT,\n  updatedAt TEXT NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_turn_status_scope ON turn_status (scopeId, status);\n";
/** For deployments whose `turn_status` table predates `scopeId`/`listRunning` —
 *  run once to add the column (the CREATE above already includes it for new
 *  deployments). SQLite ignores a duplicate-add error if already applied. */
declare const TURN_STATUS_SCOPE_MIGRATION_SQL = "ALTER TABLE turn_status ADD COLUMN scopeId TEXT;";
declare function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore;
/** In-memory store for tests and keyless local dev. */
declare function createMemoryTurnEventStore(): TurnEventStore;

export { type BufferedTurnEvent, type BufferedTurnOptions, type BufferedTurnTap, type D1LikeForTurns, type JsonRecord, type PersistedChatMessageForTurn, type PumpBufferedTurnOptions, type ReplayTurnEventsOptions, type ResolvedChatTurn, type StreamEvent, TURN_EVENTS_MIGRATION_SQL, TURN_STATUS_SCOPE_MIGRATION_SQL, type TurnEventStore, type TurnStatus, asRecord, asString, buildUserTextParts, coalesceChatStreamEvents, coalesceDeltas, createBufferedTurnTap, createD1TurnEventStore, createMemoryTurnEventStore, encodeEvent, finalizeAssistantParts, getPartKey, mergePersistedPart, messageHasTurnId, normalizeClientTurnId, normalizePersistedPart, normalizeTime, normalizeToolEvent, pumpBufferedTurn, replayTurnEvents, resolveChatTurn, resolveToolId, resolveToolName };
