import type { AggregateUsage, AIProviderRegistryStatusEntry, AIProviderType, ChatInputDuringExecutionMode, ChatMessage } from '@genesislcap/foundation-ai';
import type { PayloadAction } from '@genesislcap/foundation-redux';
import type { AgentConfig } from '../config/config';
import type { AiAssistantAnimation, AiAssistantState, SuggestionsState } from '../main/main.types';
/**
 * A single in-flight per-call chat-input override pushed by a `requestSubAgent`
 * or `requestInteraction` call. Tracked as an array (not a counter) so the
 * slice can survive pop-in/pop-out and so a listener that connects
 * mid-execution can compute the effective mode without having seen the start
 * event.
 *
 * @internal
 */
export interface InputOverride {
    /** Unique per-invocation id, paired with the start/stop events. */
    id: string;
    /** The mode requested for this invocation. */
    mode: ChatInputDuringExecutionMode;
}
/**
 * One vendor's AI-spend figures, as fed by the host from the budget endpoint
 * (GENC-1464 — the settings-modal budget meter).
 *
 * Dollars, not a percentage: the meter's text shows the real figures (spend CAN
 * exceed the cap — the last allowed call may overshoot it), and only the bar
 * clamps. Storing a pre-computed percentage would destroy the overshoot.
 *
 * @beta
 */
export interface VendorBudgetFigures {
    /** The vendor's budget cap in USD. Finite and `>= 0`. */
    budgetUsd: number;
    /** Metered spend against that cap in USD. Finite and `>= 0`; may exceed the cap. */
    spentUsd: number;
}
/**
 * Shape of a single AI assistant session's serializable state.
 *
 * @internal
 */
export interface AiAssistantSessionState {
    messages: ChatMessage[];
    state: AiAssistantState;
    showToolCalls: boolean;
    showThinkingSteps: boolean;
    showNarration: boolean;
    showAgentSwitchIndicator: boolean;
    enabledAnimations: AiAssistantAnimation[];
    suggestionsState: SuggestionsState;
    contextTokens: number | undefined;
    contextLimit: number | undefined;
    /**
     * Cost and per-bucket token totals across every chat turn in this session,
     * including sub-agent turns and spend banked by a compaction.
     *
     * One field rather than a cost scalar plus a token scalar: they are derived from
     * the same walk of the transcript, so keeping them together makes it impossible
     * for the two to disagree about which turns they counted.
     */
    sessionUsage: AggregateUsage;
    /** Active model id (e.g. `claude-sonnet-4-6`), resolved on connect. */
    activeModel: string | undefined;
    /**
     * Name of the AI provider used on the most recent turn — drives "current"
     * marker in the settings panel. `undefined` until the first turn runs.
     */
    activeProviderName: string | undefined;
    /**
     * Snapshot of every registered provider's status, in registration order.
     * Populated on connect (or on settings-open if not yet loaded) and reused
     * for the lifetime of the session — provider registration is static.
     *
     * Describes the registry rather than the conversation, so it survives
     * {@link resetSession} ("Clear" / "New chat"); see the reasoning there. Only
     * a registry change (via the observable-registry subscription) rewrites it.
     */
    providerStatuses: AIProviderRegistryStatusEntry[];
    activeAgent: Omit<AgentConfig, 'toolHandlers'> | undefined;
    /**
     * Name of the agent the user has pinned via the picker. `null` means the
     * orchestrator chooses each turn (Auto). Survives lifecycle events but
     * resets on page refresh. Only changed by explicit user picker action (or
     * the host's `setAgent` API) — never by the orchestrator's auto-pin on
     * stateful activation. See {@link flowOwnerAgentName} for that.
     */
    pinnedAgentName: string | null;
    /**
     * Name of the agent that currently owns a guided flow — set by the
     * orchestrator when a stateful agent's `onActivate` fires, cleared by
     * `releaseAgent` or session teardown. While non-null, routing precedence
     * goes to this agent and the picker is locked. Independent of
     * {@link pinnedAgentName}: a flow can wrap up and release without
     * disturbing the user's sticky picker choice.
     */
    flowOwnerAgentName: string | null;
    /**
     * The user prompt that started the current guided flow (GENC-1351 §5.9) —
     * captured when a flow-owner locks in, cleared when it releases. Persisted only
     * into a *non-resumable tombstone* (not the normal snapshot), where it pre-fills
     * the composer on return so the user can re-run the task. Slice-backed (like
     * {@link flowOwnerAgentName}) so both element instances agree and it survives
     * pop-in/out.
     */
    flowActivationPrompt: string | null;
    /**
     * Whether the agent picker slide-out panel is currently open. Lives in the
     * slice so the bubble-dialog and popout-panel instances stay in sync as the
     * user pops in and out.
     */
    agentPickerOpen: boolean;
    /**
     * Whether the lifecycle (session) menu slide-out panel is open. Mirrors
     * `agentPickerOpen` — kept in the slice so panel state survives pop-in/out.
     */
    sessionMenuOpen: boolean;
    /**
     * Whether a manual compaction's async summary is in flight (GENC-1351 §5.7).
     * In the slice (not element-local) so the "Compacting…" transcript view and
     * input-disable stay in sync across pop-in/out, like the panel flags above.
     */
    compacting: boolean;
    /**
     * Whether a page-reload restore is in flight — `provider.load()` hasn't
     * resolved yet (GENC-1351 §6). In the slice (like `compacting`) so the
     * "Restoring…" transcript view + input-disable survive pop-in/out. Invisible
     * for synchronous providers (resolves before paint); matters for server-backed
     * ones (S3 latency), where the disable also **prevents a mid-load send from
     * being clobbered** when the restored history loads.
     */
    restoring: boolean;
    /**
     * Whether the assistant is **blocked by a backend condition** and cannot send
     * at all — today, an exhausted AI-spend budget (GENC-1464). Unlike `compacting`
     * and `restoring`, which are short-lived in-flight flags that clear
     * themselves, this is a **latched** state: nothing the user or the element
     * does clears it, because nothing they can do fixes it. It stays set until the
     * host writes `blocked = false` (typically after a fresh pre-flight shows
     * budget available again).
     *
     * Also unlike those two, it does **not** replace the transcript — the user
     * keeps their conversation and gets a banner above a disabled composer.
     *
     * In the slice so the banner + input-disable stay in sync across pop-in/out.
     * Preserved across `resetSession` ("Clear" / "New chat"): starting a new chat
     * does not refill the budget.
     *
     * **Vendor-agnostic.** Under per-vendor budgets this means "blocked whatever
     * vendor you use" — a host-set block, or a wall that could not be attributed
     * to a vendor. A wall that WAS attributed lands in
     * {@link AiAssistantSessionState.blockedVendors} instead, and the element
     * derives its public `blocked` from the two together.
     */
    blocked: boolean;
    /**
     * Host- or driver-supplied explanation shown in the blocked banner. `null`
     * falls back to the element's default copy. Meaningless while
     * {@link AiAssistantSessionState.blocked} is false.
     */
    blockedReason: string | null;
    /**
     * Vendors walled by the AI-spend budget (GENC-1464 per-vendor), in the order they
     * were walled.
     *
     * An **array**, not a `Set` and not one `Record<AIProviderType, {...}>`:
     * a `Set` is neither plain JSON for the redux store nor structured-cloneable
     * across the tab channel; the order is what the multi-vendor banner copy
     * ("Anthropic and Gemini") reads; and a combined record would conflate "not
     * walled" with "walled, no reason".
     *
     * Empty for every host that never adopts the per-vendor API — which is what
     * makes the element's derived `blocked` byte-identical to the old stored one.
     */
    blockedVendors: AIProviderType[];
    /**
     * The subset of {@link AiAssistantSessionState.blockedVendors} walled by the
     * `otherVendorAvailable: false` SWEEP rather than by a refusal of their own.
     * Provenance, not a second wall list: for locking, `blockedVendors` remains
     * the whole truth. The distinction exists for the banner's unknown-reachability
     * fallback — a refusal is a fact about a vendor the user just used and is
     * nameable anywhere; a sweep entry is a headroom fact about the PROXY, and
     * naming it before the statuses reveal whether the host even ships that vendor
     * put a vendor in front of the user that may not be theirs.
     */
    sweptVendors: AIProviderType[];
    /**
     * Per-vendor banner copy, keyed by a member of
     * {@link AiAssistantSessionState.blockedVendors}. A missing entry falls back to
     * copy composed from the vendor's display name.
     */
    blockedVendorReasons: Partial<Record<AIProviderType, string>>;
    /**
     * Per-vendor AI-spend figures for the settings-modal budget meter (GENC-1464),
     * fed by the host via the element's `setVendorBudget`. A vendor with no entry
     * renders no meter row — which is also how "unlimited" is expressed: the host
     * simply never feeds a vendor the server marks unlimited.
     *
     * NOT persisted to the session snapshot, deliberately. The server is
     * authoritative for spend and the host re-feeds on boot (and after each turn),
     * so a persisted copy could only ever show stale dollars across a reload —
     * wrong money is worse than a beat of no meter. The snapshot assembler
     * (`session-snapshot.ts`) builds from an explicit field list, so absence is
     * structural, not an accident of serialization.
     */
    vendorBudgets: Partial<Record<AIProviderType, VendorBudgetFigures>>;
    /** Draft text in the input box — preserved across pop-in/pop-out cycles. */
    inputValue: string;
    /** Live trace from a currently-executing sub-agent. Cleared on sub-agent-stop. */
    liveSubAgentTrace: ChatMessage[];
    /** Name of the currently-executing sub-agent, or null when idle. */
    liveSubAgentName: string | null;
    /**
     * In-flight per-call chat-input overrides pushed by `requestSubAgent` or
     * `requestInteraction` calls. The most restrictive entry (`'hidden'` >
     * `'disabled'`) wins; an empty list means the agent-level
     * `chatInputDuringExecution` applies.
     */
    inputOverrides: InputOverride[];
}
/**
 * The subset of session state restored verbatim from a persisted session
 * (page-reload restore). Agent *identity* (`activeAgent`) and per-agent machine
 * snapshots are applied separately by the host — resolve the name against the
 * live agents array, then hand the snapshot to the agent's `init({ restored })`.
 *
 * @internal
 */
export interface LoadSessionPayload {
    messages: ChatMessage[];
    pinnedAgentName: string | null;
    flowOwnerAgentName: string | null;
    contextTokens: number | undefined;
    contextLimit: number | undefined;
    activeModel: string | undefined;
    activeProviderName: string | undefined;
}
/**
 * Build a fresh default session state. A factory (not just a shared const) so
 * each call returns independent collections — `resetSession` can hand back a
 * clean slate without aliasing the initial state's arrays.
 */
export declare function createDefaultSessionState(): AiAssistantSessionState;
export declare const defaultSessionState: AiAssistantSessionState;
export declare const aiAssistantSlice: import("@reduxjs/toolkit").Slice<AiAssistantSessionState, {
    setMessages(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<ChatMessage[]>): void;
    setState(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<AiAssistantState>): void;
    setShowToolCalls(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<boolean>): void;
    setShowThinkingSteps(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<boolean>): void;
    setShowNarration(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<boolean>): void;
    setShowAgentSwitchIndicator(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<boolean>): void;
    setEnabledAnimations(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<AiAssistantAnimation[]>): void;
    setSuggestionsState(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<SuggestionsState>): void;
    setContextTokens(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<number | undefined>): void;
    setContextLimit(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<number | undefined>): void;
    setSessionUsage(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<AggregateUsage>): void;
    setActiveModel(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | undefined>): void;
    setActiveProviderName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | undefined>): void;
    setProviderStatuses(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<AIProviderRegistryStatusEntry[]>): void;
    setActiveAgent(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<Omit<AgentConfig, "toolHandlers"> | undefined>): void;
    setPinnedAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
    setFlowOwnerAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
    setFlowActivationPrompt(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
    setAgentPickerOpen(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<boolean>): void;
    setSessionMenuOpen(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<boolean>): void;
    setCompacting(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<boolean>): void;
    setRestoring(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<boolean>): void;
    /**
     * Latch (or release) the backend-blocked state.
     *
     * `reason` is only meaningful while blocking, and it distinguishes three
     * cases rather than two:
     *
     * - **omitted** — a re-latch that leaves the existing explanation intact.
     *   Load-bearing: the block has two writers (the host, and the driver's
     *   automatic latch), and the driver's write lands AFTER a bus-subscribed
     *   host has already set its own detailed copy. Treating "no reason" as
     *   "clear the reason" made that sequence replace the host's figures with
     *   generic copy every time, deterministically.
     * - **a string** — replaces the explanation.
     * - **`null`** — explicitly clears the explanation while staying blocked;
     *   the banner falls back to the element's default copy.
     *
     * Unblocking always clears it, so a stale explanation can never outlive the
     * condition it described.
     *
     * Unblocking **also clears every per-vendor latch**, and that is the single
     * rule that keeps per-vendor budgets non-breaking. A host asserting "the wall
     * is gone" (the documented pre-flight, migration-GENC-1464 Option B) must not be
     * silently overruled by a driver latch it cannot see; without this, the same
     * `setBlocked(false)` that used to unblock would leave the composer locked.
     */
    setBlocked(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<{
        blocked: boolean;
        reason?: string | null;
    }>): void;
    /**
     * Write the banner explanation without touching the flag.
     *
     * Separate from {@link setBlocked} rather than expressed as
     * `setBlocked({ blocked: current, reason })`, because the element's public
     * `blocked` is now DERIVED: re-latching with the derived value would pass
     * `false` for a partially-walled session and so clear every per-vendor latch
     * as a side effect of setting a string.
     */
    setBlockedReason(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
    /**
     * Wall (or release) a single vendor's budget.
     *
     * The `reason` trichotomy mirrors {@link setBlocked}'s exactly, and for the
     * same reason: the same two-writer race (the driver's latch landing after a
     * bus-subscribed host has written its own copy) applies per vendor.
     *
     * Adding an already-walled vendor is idempotent — the array is a set with an
     * order, so the vendor keeps the position it was first walled at.
     */
    setVendorBlocked(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<{
        vendor: AIProviderType;
        blocked: boolean;
        reason?: string | null;
        /**
         * Marks the wall as SWEEP-derived (see `sweptVendors`). Only the
         * element's `otherVendorAvailable: false` sweep passes `true`; a host
         * call or a refusal latch leaves it unset, which also UPGRADES a
         * previously swept vendor — a refusal (or an explicit host statement)
         * is nameable evidence, and first-hand evidence beats the verdict.
         */
        swept?: boolean;
    }>): void;
    /**
     * Store (or clear, with `null`) one vendor's budget figures for the meter.
     *
     * Validation lives at the element boundary (`setVendorBudget` on the
     * element), not here — the reducer is the storage layer and trusts its one
     * caller, exactly as `setVendorBlocked` above trusts its.
     *
     * Idempotent by VALUE, not just by effect: re-feeding the figures a vendor
     * already holds leaves the stored object untouched. The documented feed
     * re-runs after every turn, so without this every turn would publish a new
     * store reference for the same dollars and re-render a meter that has not
     * changed.
     */
    setVendorBudget(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<{
        vendor: AIProviderType;
        figures: VendorBudgetFigures | null;
    }>): void;
    setInputValue(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string>): void;
    setLiveSubAgentTrace(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<ChatMessage[]>): void;
    setLiveSubAgentName(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<string | null>): void;
    addInputOverride(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<InputOverride>): void;
    removeInputOverride(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<{
        id: string;
    }>): void;
    /**
     * Bulk-hydrate the restorable fields of a persisted session (page-reload
     * restore) and set `state` back to `idle`. Agent identity and the per-agent
     * machine snapshot are applied separately by the host — see
     * `LoadSessionPayload`.
     */
    loadSession(state: import("immer").WritableDraft<AiAssistantSessionState>, action: PayloadAction<LoadSessionPayload>): void;
    /**
     * Wipe the session back to a fresh default — the "Clear" / "New chat" action.
     *
     * Three categories survive, for three different reasons — keep them distinct:
     *
     * 1. The user's **display preferences** (tool-call / thinking / narration /
     *    agent-switch visibility + animations). They are settings, not
     *    conversation state, and are persisted independently of the snapshot
     *    (GENC-1351). Without this they'd flip to their all-off defaults in the
     *    live store on Clear, since `clearSession` re-applies no baseline and the
     *    idempotent setters suppress a re-apply.
     * 2. The **backend block** (`blocked` / `blockedReason`, and the per-vendor
     *    latches beside them). Not a preference at
     *    all — a standing backend condition that Clear cannot change. It is here
     *    because starting a new chat does not refill the AI budget, so clearing
     *    it would hand the user a working-looking composer that fails on the very
     *    next send.
     * 3. The **provider statuses**. They describe the REGISTRY, not the
     *    conversation — provider registration is static for the store's lifetime
     *    and this snapshot is refreshed from the registry, never from a turn. They
     *    are session state only because that is where the element's store lives.
     *
     *    Dropping them on Clear is not cosmetic: the element derives `blocked`
     *    from "every REACHABLE vendor is walled", and an empty reachable set fails
     *    safe to "blocked". So wiping the statuses while keeping the per-vendor
     *    latches (category 2) escalated one walled vendor out of two into a fully
     *    locked composer, and nothing reloads them — `loadProviderStatuses()` runs
     *    only on connect/session-switch and from the observable-registry
     *    subscription, so a host with a static registry stayed locked until a
     *    remount. Preserving is the fix rather than re-loading after reset,
     *    because the reload is async: it would still flash a fully locked composer
     *    for a turn of the event loop, and would break a host whose registry is
     *    immutable (nothing to re-listen to) or whose `listStatuses()` rejects.
     *
     * Stated separately so a future contributor deciding what belongs in this
     * list does not "tidy" the block out on the grounds that it is not a display
     * preference.
     */
    resetSession(state: import("immer").WritableDraft<AiAssistantSessionState>): void;
}, "aiAssistant", "aiAssistant", {}>;
//# sourceMappingURL=ai-assistant-slice.d.ts.map