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[]>;
    setStatus(turnId: string, status: TurnStatus): Promise<void>;
    getStatus(turnId: string): Promise<TurnStatus | null>;
}
/** 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[];
interface PumpBufferedTurnOptions {
    source: AsyncIterable<unknown>;
    store: TurnEventStore;
    turnId: string;
    /** Deliver one serialized line (with seq) to the live client. Throwing here
     *  (client disconnected) does NOT stop the turn — events keep buffering. */
    write?: (line: string) => Promise<void> | void;
    /** Flush buffered events to the store at most this often. Default 400ms. */
    flushIntervalMs?: number;
}
/**
 * Drive a turn to completion regardless of the live client: every source
 * event is sequence-numbered, delivered to `write` (best-effort), and flushed
 * to the store in coalesced batches. 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
 * status '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  updatedAt TEXT NOT NULL\n);\n";
declare function createD1TurnEventStore(db: D1LikeForTurns): TurnEventStore;
/** In-memory store for tests and keyless local dev. */
declare function createMemoryTurnEventStore(): TurnEventStore;

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