import type { AgentRunner } from './spi/agent-runner.js';
import type { AgentStore } from './spi/agent-store.js';
import type { AttachmentStagingStore } from './spi/attachment-staging.js';
import { type ChatQueueState, type ChatQueueStore, type QueuedMessage, type QueuePauseReason } from './spi/chat-queue.js';
import type { QuotaProvider } from './spi/quota-provider.js';
import type { TokenStreamSink } from './spi/token-stream-sink.js';
import type { AgentStreamEvent } from './stream-events.js';
import type { Actor, AgentRunInput, Persona } from './types.js';
/** How the run that held a thread ended. */
export type QueueSettleOutcome = 'completed' | 'failed' | 'cancelled';
/** The next queued message a settling run handed the thread to — the caller starts it. */
export interface QueuedTurn {
    /** The run id the thread is already claimed for: the queued message's own id. */
    runId: string;
    input: AgentRunInput;
    messageId: string;
}
/**
 * What {@link ChatQueueService.plan} decided. Plain data, so a durable workflow can journal it in a
 * `localStep` and start `next` itself (`ctx.startChild`) — replay-safe, and never twice.
 */
export interface QueuePlan {
    next?: QueuedTurn;
    /** The `queue` frame to write into the settling run's stream, before its terminal. */
    frame?: AgentStreamEvent;
}
/** Starts a run under a pre-claimed id — a runner's own `start`, bound. */
export type QueuedTurnStarter = (input: AgentRunInput, runId: string) => Promise<unknown>;
/** What {@link ChatQueueService} needs beyond the store and the sink — all optional. */
export interface ChatQueueServiceOptions {
    /** The budget a queued message's actor is checked against as it starts. Omitted → not checked. */
    quota?: QuotaProvider;
    /** Re-mints a queued message's attachment urls as it starts (they may have expired waiting). */
    attachments?: AttachmentStagingStore;
    /**
     * The agent and persona a queued message that carries NO persona starts as — one queued before
     * personas were persisted, or by a host that set none: an agent name a persona took over, then the
     * thread's pin, then the agent's default, exactly as a send resolves them. Never refused: nobody is
     * waiting on a 400. A message that carries its persona starts under it as queued. Omitted → such a
     * message starts as it was queued.
     */
    resolveTarget?: (message: QueuedMessage) => Promise<{
        agentName?: string;
        persona?: string;
    }>;
    /**
     * @deprecated Use {@link resolveTarget}. The persona a turn of `agentName` runs with, consulted
     * only for a message that carries none when `resolveTarget` is not given.
     */
    personaFor?: (agentName: string | undefined) => Persona | undefined;
}
/**
 * The thread-level message queue: messages a person sends while a turn is running wait here, and
 * the next one starts as soon as the running turn settles. A port of `@dudousxd/nestjs-agent`'s
 * `ChatQueueService`; the wire is its `docs/stream-protocol.md`, *Message queue*.
 *
 * Everything that decides WHICH run holds a thread goes through the store's compare-and-set
 * admission (`claimActiveStream` / `releaseActiveStream`), so two processes that both try to start
 * the next turn — a settling run on a worker and a send on a web pod — cannot both win. The next
 * run's id is the queued message's own id, which makes every step here idempotent: a retried claim
 * for the same message succeeds again, and a durable start under the same id is a no-op.
 *
 * Policy, per outcome of the run that held the thread:
 *  - `completed` → the head starts.
 *  - `failed`    → the queue pauses (`run_failed`), keeping every message.
 *  - `cancelled` → the queue pauses (`cancelled`), unless the head was queued by an interrupt — the
 *                  cancel was made for it, so it starts.
 * A paused queue starts nothing until it is resumed. A head whose actor is over quota pauses it
 * (`quota_exceeded`) instead of starting.
 */
export declare class ChatQueueService {
    private readonly store;
    private readonly sink;
    private readonly options;
    /**
     * Runs this process claimed a thread for and has not handed to a runner yet. A holder in here is
     * alive whatever the runner says — it simply has not registered the run yet.
     */
    private readonly starting;
    constructor(store: AgentStore, sink: TokenStreamSink, options?: ChatQueueServiceOptions);
    /** Whether the bound store can hold a queue. Without one, a busy thread is not queued. */
    get supported(): boolean;
    /** The bound store as a queue store; throws when it is not one (check {@link supported} first). */
    queueStore(): AgentStore & ChatQueueStore;
    state(threadId: string): Promise<ChatQueueState>;
    /**
     * Whether `holder` is still running. A run this process is about to start is; otherwise the
     * runner decides, and a runner that cannot tell says yes.
     */
    isAlive(holder: string, runner: AgentRunner): Promise<boolean>;
    /**
     * The run holding `threadId`, or `null` when it is free — a holder the runner reports dead counts
     * as free, and is returned as `stale` so the caller can claim over it.
     */
    holder(threadId: string, runner: AgentRunner): Promise<{
        live: string | null;
        stale: string | null;
    }>;
    /**
     * Decide what follows a TOP-LEVEL run that is settling on `threadId`, and move the thread there:
     * hand it to the next queued message (claimed and popped here, started by the caller), or pause
     * the queue, or release the thread. Call it BEFORE the run's terminal frame, so a client reading
     * the stream learns what comes next from the `queue` frame, and so the thread is already free
     * when the client sees the run end.
     *
     * A no-op returning `{}` on a store without a queue.
     */
    plan(args: {
        threadId: string;
        /** The run that is settling; `null` when the thread is idle (a kick). */
        runId: string | null;
        outcome: QueueSettleOutcome;
        error?: string;
    }): Promise<QueuePlan>;
    /**
     * {@link plan}, then start what it picked with `start`. What an in-process settle (the inline
     * runner, a durable cancel issued outside a workflow) calls; answers the frame to write.
     */
    handoff(args: {
        threadId: string;
        runId: string;
        outcome: QueueSettleOutcome;
        error?: string;
    }, start: QueuedTurnStarter): Promise<AgentStreamEvent | undefined>;
    /**
     * Start the head of an IDLE thread's queue — after a send queued behind a run that turned out to
     * be gone, after a resume, after a stale holder. Does nothing while a live run holds the thread
     * (its settle drains) or while the queue is paused. Answers the started run's id, if any.
     */
    kick(threadId: string, runner: AgentRunner): Promise<string | undefined>;
    /**
     * Start a turn the thread is already claimed for. On failure the message goes back to the head of
     * the queue, the queue pauses (`start_failed`), and the claim is released — nothing is lost and
     * nothing is left holding the thread.
     */
    launch(threadId: string, next: QueuedTurn, start: QueuedTurnStarter): Promise<boolean>;
    /** Put a turn that never started back at the head of its queue, paused, and free the thread. */
    restore(threadId: string, next: QueuedTurn, reason: QueuePauseReason, message: string): Promise<void>;
    /** A `queue` frame with the thread's current state. */
    pausedFrame(threadId: string): Promise<AgentStreamEvent>;
    /**
     * Tell whoever is watching the thread that its queue changed: a `queue` frame into the stream of
     * the run holding it. Best-effort — a thread nobody is streaming has no one to tell, and the next
     * read of the thread carries the queue anyway.
     */
    publish(threadId: string): Promise<ChatQueueState>;
    /** Write one agent-protocol event into `runId`'s stream. */
    write(runId: string, frame: AgentStreamEvent): Promise<void>;
    /** Why `actor` may not start a turn now (the quota message), or `null`. */
    quotaBlock(actor: Actor): Promise<string | null>;
    /**
     * The turn a queued message starts: everything it was queued with, today's date, and its
     * attachments' urls minted afresh — the ones stored at enqueue time may have expired while it
     * waited. An attachment the store no longer resolves keeps its stored url.
     */
    private inputFor;
    /** The agent and persona a queued message starts as — see {@link ChatQueueServiceOptions.resolveTarget}. */
    private targetFor;
    private freshAttachments;
}
/**
 * A thread's own turn — not a sub-agent's run on a subthread, which neither holds its thread's
 * queue nor answers to it.
 */
export declare function isThreadTurn(input: {
    sinkRunId?: unknown;
    parentRunId?: unknown;
}): boolean;
//# sourceMappingURL=chat-queue-service.d.ts.map