import { type Interrupt } from "./interrupts.js";
import type { RuntimeContext } from "./state/context.js";
import type { BranchState, State, StateStack } from "./state/stateStack.js";
export type BatchChild<T> = {
    /** Stable per-child key. Used for `getOrCreateBranch`. Caller is
     * responsible for uniqueness within `parentFrame.branches`. */
    key: string;
    /** Invoked with the child's own `StateStack` (already seeded with abort
     * signal composed with parent) and that stack's abort signal. Must
     * return either a value `T` (success) or an `Interrupt[]` (halted with
     * interrupts). MUST NOT throw `Interrupt[]`. May throw other errors. */
    invoke: (childStack: StateStack, abortSignal: AbortSignal) => Promise<T | Interrupt[]>;
};
export type BatchHooks = {
    seedBranchCost?: (childStack: StateStack, parentStack: StateStack) => void;
    /** Used by mode "all" / "sequential" only. The race adapter uses the
     * asymmetric pair below instead. */
    propagateBranchCost?: (branches: BranchState[], parentStack: StateStack) => void;
    /** Race mode only: propagate loser-branch cost at race-time (before the
     * losers are deleted). The winner's cost propagates separately on resume
     * via `propagateWinnerCost`. */
    propagateLoserCost?: (loserBranches: BranchState[], parentStack: StateStack) => void;
    /** Race mode only: propagate the winner's cost when the winner finally
     * completes (no-interrupt resume). */
    propagateWinnerCost?: (winnerBranch: BranchState, parentStack: StateStack) => void;
    /** Called once per branch start (statelog). */
    onBranchStart?: (key: string, index: number) => void;
    /** Called once per branch end with its outcome and elapsed time in ms. */
    onBranchEnd?: (key: string, index: number, outcome: "success" | "interrupted" | "failure" | "aborted", timeMs: number) => void;
    /** Called once immediately before `ctx.checkpoints.create` deep-clones
     * the parent frame. Use this to flush state that was mutated by
     * sibling branches during the batch into frame-backed locals that need
     * to survive the checkpoint. Concretely: runPrompt's tool loop pushes
     * `tool` messages onto the shared `MessageThread` from inside each
     * branch's body, but `self.messagesJSON` (the snapshot the checkpoint
     * captures) isn't refreshed by those pushes — the adapter uses this
     * hook to do `self.messagesJSON = snapshotMessages()` so the
     * deep-clone sees the up-to-date thread. Without this hook the
     * checkpoint would carry the pre-batch messages and successful
     * sibling tool responses would be silently lost on resume. */
    beforeCheckpoint?: () => void;
    /** Called once when the batch stamps its shared checkpoint. */
    onCheckpoint?: (checkpointId: number) => void;
};
export type RunBatchOpts<T> = {
    ctx: RuntimeContext<any>;
    /** The parent's local state stack — used as the capture stack for the
     * shared batch-level checkpoint. MUST be the local slice (e.g. the
     * branch stack if `runBatch` is itself called inside a child of an
     * outer `runBatch`), NOT `ctx.stateStack`. This is the one discipline
     * the caller of `runBatch` must observe. */
    parentStack: StateStack;
    /** The frame where branch state lives. Usually `parentStack.lastFrame()`. */
    parentFrame: State;
    /** Where the shared checkpoint records its location. Same fields the
     * existing call sites pass to `ctx.checkpoints.create`. */
    checkpointLocation: {
        moduleId: string;
        scopeName: string;
        stepPath: string;
    };
    /** "all" → Promise.allSettled, concurrent; "sequential" → for...of,
     * each child after the previous (today's callHook semantics); "race"
     * → first to settle wins, others are aborted.
     *
     * IMPORTANT: do not use "all" for hook-callback batching — that would
     * change today's strictly-sequential `callHook` ordering. Use
     * "sequential". */
    mode: "all" | "sequential" | "race";
    children: BatchChild<T>[];
    /** Mode "race" only: the `parentFrame.locals` key under which the winner
     * index is persisted. The caller (race adapter) computes
     * `__race_winner_${id}` and passes it. */
    raceWinnerLocalKey?: string;
    /** When `true` (default), runBatch records the per-branch outcome on
     * `parentFrame.branches` via `setResultOnBranch` (success) or
     * `setInterruptOnBranch` (interrupt). When `false`, the caller's
     * `invoke` is responsible for managing branch state itself — runBatch
     * still stamps the shared checkpoint and overwrites
     * `intr.checkpoint`/`checkpointId`, but does NOT touch BranchState
     * fields. Used by runPrompt's tool loop where the body manages the
     * real tool result on the branch. */
    recordBranchOutcomes?: boolean;
    hooks?: BatchHooks;
};
export type RunBatchResult<T> = {
    kind: "values";
    values: T[];
} | {
    kind: "interrupts";
    interrupts: Interrupt[];
};
export declare function runBatch<T>(opts: RunBatchOpts<T>): Promise<RunBatchResult<T>>;
