import type { DataUIPart, UIDataTypes, UIMessage } from 'ai';
import { type UiAction } from '../../genui/actions.js';
import type { UiCapabilities } from '../../genui/index.js';
import type { QuotaBlock, ThreadSummary } from '../../index.js';
import { type ReconnectOptions, type StreamConnectionState } from './agent-chat-transport.js';
import { type AttachmentsState, type UseAttachmentsOptions } from './attachments/use-attachments.js';
import { type AgentBackend } from './backend.js';
import { type BackgroundRun } from './background-runs.js';
import { type ModelOption, type ModelsState } from './catalog/use-models.js';
import type { ChatQueue, SendWhileRunning, WhileRunning } from './queue/model.js';
import { type QuotaState } from './quota/use-quota.js';
import type { AgentRunFailure } from './run-errors.js';
import type { ChatStatus } from './transcript/model.js';
import { type AnswerInput, type ApproveInput, type ChatTranscript, type MessageActionInput, type RejectInput, type SkipInput, type UseChatTranscriptOptions } from './transcript/use-chat-transcript.js';
export interface UseAgentChatOptions<B extends AgentBackend = AgentBackend> {
    uiCapabilities?: UiCapabilities;
    proposalPollMs?: number;
    /** Enable proposal reads even when history and resume are both disabled. */
    proposals?: boolean;
    /**
     * What the chat talks to — see {@link AgentBackend}. Omitted → the enclosing `<AgentProvider>`'s,
     * else a same-origin client on `/agent`. Must be stable across renders.
     */
    backend?: B;
    /**
     * Retry a stream that dropped mid-run from its last frame (`GET <base>/chat/:runId/stream?after=`),
     * with exponential backoff. `status` reads `'reconnecting'` meanwhile. Default on; `false` turns
     * it off.
     */
    reconnect?: ReconnectOptions | false;
    /** Named agent to run each turn. */
    agent?: string;
    /**
     * One of the agent's personas (`GET <base>/agents` lists them) to answer as. Read per send, so a
     * picker can switch it between messages; the server pins it on the thread, and `threadPersona`
     * reports it back. Omitted → the thread's pinned persona, else the agent's default. A single send
     * can name its own: `sendMessage(message, { body: { persona } })`.
     */
    persona?: string;
    /**
     * Thread this chat is bound to. Omitted → the backend creates one on the first send
     * ({@link UseAgentChatOptions.onThreadCreated}). Changing it switches the chat to that thread:
     * its history loads and a turn still streaming on it is re-attached. Switching to the thread this
     * chat just created (syncing a URL) keeps the live conversation as it is.
     */
    threadId?: string;
    /**
     * Load a `threadId`'s persisted history into the chat (`GET <base>/threads/:id`). Default `true`;
     * skipped when {@link UseAgentChatOptions.initialMessages} is given. `false` opts out.
     */
    history?: boolean;
    /** Persisted history you already have — seeds the chat instead of loading it (read per thread). */
    initialMessages?: UIMessage[];
    /**
     * Re-attach to a turn that was still streaming on `threadId` when the page (re)loaded — read off
     * the thread's `activeRunId`, so a reload mid-answer keeps streaming it. Default `true`; `false`
     * opts out.
     */
    resume?: boolean;
    /**
     * A run id you already know is streaming — attached to on mount without reading the thread first.
     * Rarely needed: {@link UseAgentChatOptions.resume} finds it by itself.
     */
    resumeRunId?: string;
    /**
     * The model every turn runs on, controlled by you. Omitted → `chat.models.selected` (what
     * `chat.models.select(id)` picked), else the thread's pinned model, else the server default. A
     * single send can still override it with `sendMessage(msg, { body: { model } })`. Sent as the
     * turn's `model`, which the server applies to that turn only — pinning a model on the thread is
     * `chat.models.pinToThread(id)`.
     */
    model?: string;
    /**
     * Read the caller's budget (`GET <base>/quota`) and refuse sends while a window is exhausted.
     * Default `true` when the backend implements `getQuota`; `false` skips the request.
     */
    quota?: boolean;
    /**
     * Override the quota gate: `null` never blocks, a {@link QuotaBlock} always does. Omitted → the
     * gate follows {@link UseAgentChatOptions.quota}'s report. While blocked, `sendMessage` and
     * `regenerate` refuse with {@link QuotaBlockedError}.
     */
    blocked?: QuotaBlock | null;
    /** Validation and upload for `chat.composer.files` — see `useAttachments`. */
    composer?: Omit<UseAttachmentsOptions, 'backend'>;
    /**
     * What `composer.submit()` and `sendMessage` do while a turn is still running. Default `'queue'`:
     * the message waits in the thread's queue (server-side — it survives a reload) and runs when the
     * turn settles; `chat.queue` lists and edits what is waiting. `'interrupt'` cancels the running
     * turn and runs the message next. `'block'` refuses the send (`composer.blockedBy === 'busy'`).
     * A backend without `enqueueMessage` behaves as `'block'`. One send can answer differently:
     * `composer.submit({ mode })`, `sendMessage(message, { mode })`.
     */
    whileRunning?: WhileRunning;
    /**
     * Overrides for `chat.transcript` — anything `useChatTranscript` takes (`sources`, `editable` +
     * `onEditSubmit`, `followUps`, `getUsage`, a `toolCatalog` of your own, `onApprove: null`, …).
     * Yours win over the chat's own wiring.
     */
    transcript?: Partial<Omit<UseChatTranscriptOptions, 'messages' | 'status'>>;
    /** Read at every send to capture a page snapshot for the page-assistant. */
    getPageContext?: () => Record<string, unknown> | null;
    /** Fired after each streamed turn finishes (e.g. to refetch the sidebar). */
    onFinish?: () => void;
    /**
     * Fired when this chat moves to a thread it created: the first send of a threadless chat (the
     * backend created one), or `chat.fork(…)`. Carries the id so the host can sync its URL/router.
     * The hook already remembers a created thread, so every later send reuses it even before the
     * host navigates.
     */
    onThreadCreated?: (threadId: string) => void;
    /**
     * Fired exactly once per run when its stream settles — normally (`'completed'`) or via a thrown
     * failure / the backend's `event: error` frame (`'failed'`) — including a RESUMED stream's own
     * completion. By the time this fires, the server has already derived and persisted the thread's
     * title (and the run's terminal state).
     *
     * Skipped when the attempt never got far enough to learn a run id. A user-initiated `cancel()`
     * reports `'completed'` (the server-side cancel records its own terminal state).
     */
    onRunSettled?: (outcome: {
        runId: string;
        status: 'completed' | 'failed';
    }) => void;
    /**
     * Every data part the stream delivers, as it arrives — including transient ones that are never
     * stored on a message (`data-title`, `data-cancelled`). Pushed components arrive as `data-ui`,
     * approval metadata as `data-approval-requested`, and a frame kind this version does not map as
     * `data-<kind>`. See `AgentChatTransport` for the full mapping.
     */
    onData?: (part: DataUIPart<UIDataTypes>) => void;
    /** The server set or changed the thread's title while a turn streamed. */
    onTitle?: (title: string) => void;
    /**
     * Track DETACHED sub-agents this conversation started: which are still working, and their answers
     * as they land (see {@link ChatBackground}). Default `false`.
     */
    background?: boolean;
    /**
     * How often to re-read the thread while a detached sub-agent is still working, in ms. Default
     * 5000; `0` turns the interval off, leaving {@link ChatBackground.refresh} as the only trigger.
     */
    backgroundPollMs?: number;
}
/** Sub-agents this conversation started and did not wait for. See {@link BackgroundRun}. */
export interface ChatBackground {
    runs: BackgroundRun[];
    /** True while any of them is still working — what a "2 agents running" affordance reads. */
    isWorking: boolean;
    /** Re-read the thread now, instead of waiting for the next poll. */
    refresh: () => Promise<void>;
}
/**
 * The chat's model picker state. Loaded the first time `list`/`providers`/`defaultModel`/`locked`
 * is read.
 *
 * Two different choices: `select(id)` picks the model this chat's following sends name — each send
 * carries it as `model`, which the server applies to that turn only and never stores; the pick is
 * this conversation's, so switching threads drops it. `pinToThread(id)` stores a model on the
 * thread (`PATCH threads/:id { model }`), which every later turn without a `model` of its own runs
 * on, across reloads and devices.
 */
export interface ChatModels {
    /** Every model the caller may pick, flattened (`[]` until loaded). */
    readonly list: ModelOption[];
    /** The same, grouped by provider. */
    readonly providers: ModelsState['providers'];
    /**
     * What the next turn runs on: the lock, the `model` option, the pick, the thread's pin, else the
     * default.
     */
    selected: string | null;
    /** The model pinned on the thread (`null` when none, or before the thread is read). */
    pinned: string | null;
    /** The catalog's default — what a turn runs on when nothing is picked or pinned (`null` until loaded). */
    readonly defaultModel: string | null;
    /**
     * The agent always runs on one model (`{ model, reason? }`), or `null`. While locked, `select`
     * does nothing and `selected` is the locked model.
     */
    readonly locked: ModelsState['locked'];
    /** Run this chat's following sends on `id` — each send's `model`, that turn only. */
    select: (id: string) => void;
    /**
     * Pin `id` on the thread (`null` unpins) — every later turn without its own `model` runs on it,
     * across reloads. Replaces the pick, so the next send runs on the pin. On a chat with no thread
     * yet, the pin lands when the first send creates one.
     */
    pinToThread: (id: string | null) => Promise<void>;
    isLoading: boolean;
    error: Error | null;
}
/** Why the composer cannot send right now. */
export type ComposerBlock = 'empty' | 'busy' | 'uploading' | 'quota';
/** A headless composer: the draft, its files, and whether (and why not) it can send. */
export interface ChatComposer {
    text: string;
    setText: (text: string) => void;
    /** The staged attachments — `useAttachments` state on the chat's backend. */
    files: AttachmentsState;
    canSend: boolean;
    /** Why `canSend` is false, or `null`. */
    blockedBy: ComposerBlock | null;
    /**
     * Send the draft with the ready files attached, then clear both. A no-op while blocked.
     *
     * `submit({ mode })` says what to do if a turn is running, for this send only — `'interrupt'`
     * for a "send now" button next to a plain send that queues. It overrides `whileRunning`
     * (`'block'` included) and clears the draft like any other submit.
     */
    submit: (options?: SendWhileRunning) => Promise<void>;
}
interface AddToolResultArgs {
    tool: string;
    toolCallId: string;
    output: unknown;
}
/**
 * The agent chat, wired: the AI SDK v7 `useChat` over `AgentChatTransport`, a thread's history
 * loaded and a streaming turn re-attached on its own, a quota send gate, a model picker, a headless
 * composer with attachments, and a transcript whose approve/reject/answer/skip/stop/fork/regenerate
 * are already bound. Every piece is also a hook of its own (`useChatTranscript`, `useAttachments`,
 * `useModels`, `useQuota`, `useThreads`) for a host that wants to wire it differently.
 */
export declare function useAgentChat<B extends AgentBackend = AgentBackend>(options?: UseAgentChatOptions<B>): {
    messages: UIMessage<unknown, UIDataTypes, import("ai").UITools>[];
    resumeStream: (options?: import("ai").ChatRequestOptions) => Promise<void>;
    clearError: () => void;
    addToolApprovalResponse: import("ai").ChatAddToolApproveResponseFunction;
    addToolOutput: import("ai").ChatAddToolOutputFunction<UIMessage<unknown, UIDataTypes, import("ai").UITools>>;
    stop: () => Promise<void>;
    id: string;
    setMessages: (messages: UIMessage<unknown, UIDataTypes, import("ai").UITools>[] | ((messages: UIMessage<unknown, UIDataTypes, import("ai").UITools>[]) => UIMessage<unknown, UIDataTypes, import("ai").UITools>[])) => void;
    error: Error | undefined;
    status: ChatStatus;
    /** Where the live stream stands — see {@link StreamConnectionState}. */
    connection: StreamConnectionState;
    sendMessage: (message?: Parameters<(message?: {
        text: string;
        files?: FileList | import("ai").FileUIPart[];
        metadata?: unknown;
        parts?: never;
        messageId?: string;
    } | {
        files: FileList | import("ai").FileUIPart[];
        metadata?: unknown;
        parts?: never;
        messageId?: string;
    } | (Omit<UIMessage<unknown, UIDataTypes, import("ai").UITools>, "id" | "role"> & {
        id?: string;
        role?: "assistant" | "system" | "user";
    } & {
        text?: never;
        files?: never;
        messageId?: string;
    }) | undefined, options?: import("ai").ChatRequestOptions) => Promise<void>>[0], options?: Parameters<(message?: {
        text: string;
        files?: FileList | import("ai").FileUIPart[];
        metadata?: unknown;
        parts?: never;
        messageId?: string;
    } | {
        files: FileList | import("ai").FileUIPart[];
        metadata?: unknown;
        parts?: never;
        messageId?: string;
    } | (Omit<UIMessage<unknown, UIDataTypes, import("ai").UITools>, "id" | "role"> & {
        id?: string;
        role?: "assistant" | "system" | "user";
    } & {
        text?: never;
        files?: never;
        messageId?: string;
    }) | undefined, options?: import("ai").ChatRequestOptions) => Promise<void>>[1] & SendWhileRunning) => Promise<void>;
    /** Send a UI action as the next user message (see `GenuiActionProvider`). */
    sendUiAction: (action: UiAction) => Promise<void>;
    addToolResult: ({ tool, toolCallId, output }: AddToolResultArgs) => void | PromiseLike<void>;
    /** Sub-agents this conversation started and did not wait for. */
    background: ChatBackground;
    proposals: import("./approvals/use-action-proposals.js").ActionProposalsState;
    /**
     * Look for a turn the server started on this thread (after an action's outcome — a sign-in that
     * finished elsewhere continues the request) and stream it. Called on its own when this chat's
     * proposals see a new outcome; an app with its own proposals list calls it when it does.
     */
    followServerTurn: () => Promise<void>;
    runId: string | undefined;
    /** The loaded thread's running turn, or `null` once read with none. */
    activeRunId: string | null;
    /** The thread's history is being read. */
    isLoadingHistory: boolean;
    /** Reading the thread's history failed. */
    historyError: Error | null;
    /**
     * How the last run's stream said it failed — the `event: error` frame's `code` and `message` —
     * or `null`. Cleared when the next attempt starts and when the chat moves to another thread.
     * `chat.error` still carries the message; this is where the `code` is, so the app can word each
     * failure itself (`replay_diverged`, `model_no_output`, `run_failed`, `quota_exceeded`, …).
     */
    runError: AgentRunFailure | null;
    /**
     * The thread this chat is on right now: the `threadId` option, else the one the backend created
     * on the first send. A getter, because the created id is learned mid-stream.
     */
    getThreadId: () => string | undefined;
    /** The backend this chat talks to — the same one every other hook under the provider uses. */
    backend: B;
    /** The transcript model, with every action already bound to this chat. */
    transcript: ChatTranscript;
    /** A headless composer: draft, files, send gate, `submit()`. */
    composer: ChatComposer;
    /**
     * Messages sent while a turn was running, waiting their turn (server-side): list, edit, move,
     * remove, clear, and resume a paused queue.
     */
    queue: ChatQueue;
    /** The model picker: `list`, `selected`, `select(id)`, `pinToThread(id)`. */
    models: ChatModels;
    /**
     * The persona pinned on this thread (`ThreadSummary.persona`), or `null` when none is — what a
     * persona picker on a reopened thread starts from (`?? agent.defaultPersona`).
     */
    threadPersona: string | null;
    /** The caller's budget (`GET <base>/quota`); `quota.blocked` gates sends. */
    quota: QuotaState;
    /** The window blocking sends right now (the `blocked` option, else the quota report's). */
    blocked: QuotaBlock | null;
    cancel: () => Promise<void>;
    approve: ({ toolCallId, target, remember, via }: ApproveInput) => Promise<void>;
    reject: ({ toolCallId, target, reason, via }: RejectInput) => Promise<void>;
    answer: ({ toolCallId, answers, via }: AnswerInput) => Promise<void>;
    skip: ({ toolCallId, via }: SkipInput) => Promise<void>;
    regenerate: (_input?: Partial<MessageActionInput>) => void;
    fork: ({ messageId, threadId, }: MessageActionInput & {
        threadId?: string;
    }) => Promise<ThreadSummary>;
    truncateFrom: ({ messageId, threadId }: MessageActionInput & {
        threadId?: string;
    }) => Promise<void>;
    promote: ({ threadId }?: {
        threadId?: string;
    }) => Promise<void>;
};
/** A send refused on the client because a quota window is exhausted. */
export declare class QuotaBlockedError extends Error {
    readonly block: QuotaBlock;
    constructor(block: QuotaBlock);
}
export {};
//# sourceMappingURL=use-agent-chat.d.ts.map