import type { ActionApprovalMode } from './action-proposal-receipt.js';
import { ActionProposalService } from './action-proposal-service.js';
import { type TextActionProposalVocabulary } from './action-proposal-text.js';
import type { AgentDepsFactory } from './agent-deps-factory.js';
import { type AttachmentLimits } from './attachment-limits.js';
import type { ChatQueueService } from './chat-queue-service.js';
import type { ActionProposalMutationResult } from './spi/action-proposal-store.js';
import type { AgentRunner } from './spi/agent-runner.js';
import type { AgentStore } from './spi/agent-store.js';
import { type ApprovalDecisionRef, type ToolCallApprovalState } from './spi/approval-policy.js';
import type { AttachmentRef, AttachmentStagingStore, StagedAttachment } from './spi/attachment-staging.js';
import type { BackgroundActorResolver } from './spi/background-actor-resolver.js';
import type { ChatQueueState } from './spi/chat-queue.js';
import { type ModelCatalog, type ModelCatalogView } from './spi/model-catalog.js';
import { type QuotaProvider, type QuotaReport } from './spi/quota-provider.js';
import type { StreamFrame } from './spi/token-stream-sink.js';
import { type LucidDatabaseLike } from './stores/lucid.js';
import type { ToolCatalogEntry } from './tool-presentation.js';
import type { Actor, AgentCatalogEntry, AgentHostContext, MessageAttachment, MessageFeedback, PageContext, Persona, PersonaCatalogEntry, ThreadDetail, ThreadSummary } from './types.js';
import type { UiCapabilities } from './ui-capabilities.js';
export interface ChatParams {
    actor: Actor;
    message: string;
    threadId?: string;
    agentName?: string;
    /**
     * Run this turn under one of the agent's personas, and pin it on the thread so later sends that
     * name none keep it. Refused ({@link PersonaNotFoundError}, `400 persona_not_found`) unless the
     * agent declares it. Omitted → the thread's pinned persona, else the agent's `defaultPersona`, else
     * none.
     */
    personaId?: string;
    pageContext?: PageContext;
    uiCapabilities?: UiCapabilities;
    /**
     * The channel this turn runs on — `web`, `mobile`, `whatsapp`… — over whatever its page context
     * says. Stamped as `pageContext.channel`, which is what decides (`turnChannel`) which genui tools
     * the turn is offered (`genui({ channels })`). Omitted → the page context's, else `web`.
     */
    channel?: string;
    /**
     * Uploads to attach to this message, named by id (`POST <path>/attachments` answered it). The
     * configured attachment store resolves each — the url the model fetches is never taken from here.
     */
    attachments?: AttachmentRef[];
    /**
     * Run THIS turn on a catalog model instead of the thread's pinned one (or the provider default).
     * Never stored on the thread — only `setThreadModel` (`PATCH <path>/threads/:id { model }`) pins.
     * Refused ({@link ModelNotAllowedError}) unless the catalog lists it as available to this actor.
     */
    model?: string;
    /**
     * Answer the thread's last user message again (the retry under the last answer): requires
     * `threadId` ({@link RegenerateNeedsThreadError}), ignores `message`, stores no user message, drops
     * the answer(s) after that user message and starts a new run.
     */
    regenerate?: boolean;
    /**
     * When creating a thread (no `threadId`), start it transient — a scratch conversation left out of
     * the thread list until {@link AgentService.promoteThread} keeps it. Ignored with a `threadId`.
     */
    transient?: boolean;
    /** What to do when the thread already has a turn running — see {@link ChatSendMode}. */
    mode?: ChatSendMode;
    /**
     * When creating a thread (no `threadId`), ask the store to create it under this id — the id an
     * AG-UI consumer named the conversation with. A store may ignore it (`CreateThreadInput.id`); the
     * thread's real id is always the one on the result.
     */
    newThreadId?: string;
    /**
     * The host's own facts about this send — where it came from, where its answer goes (see
     * `AgentRunInput.hostContext`). Carried to the turn as sent, through the queue when it waits there;
     * never shown to the model or to clients. Set by in-process callers, never from a request body.
     */
    hostContext?: AgentHostContext;
    /**
     * When the person sent this message, when that is not "now": a channel's provider timestamp, a
     * client that sends what it buffered offline. A `Date`, an ISO 8601 string or epoch milliseconds;
     * stored with the message (`StoredMessage.sentAt`) and what `messageTimestamps` shows the model.
     * Unreadable → ignored; in the future → now. Omitted → the message's `createdAt` stands for it
     * (for a send that waits in the queue, the time it was queued).
     */
    sentAt?: Date | string | number;
}
/** How {@link AgentService.send} treats one send. */
export interface ChatSendOptions {
    /**
     * Read a message that is a text decision ("yes", "no #ID") as one — decide the thread's pending
     * proposal instead of starting a turn. Default `true`. `false` → always a turn: for a caller that
     * decides by text itself, scoped to the proposals it showed (the text channels).
     */
    textDecisions?: boolean;
}
/**
 * What a send does when its thread already has a turn running:
 *  - `'auto'` (default) — run now when the thread is idle, else wait in the thread's queue.
 *  - `'queue'` — always wait in the queue, behind whatever is already waiting (it still starts at
 *    once when nothing is running and nothing is ahead of it).
 *  - `'interrupt'` — cancel the running turn and run this one next, ahead of the queue.
 */
export type ChatSendMode = 'auto' | 'queue' | 'interrupt';
/** A send that is waiting in its thread's queue instead of running. */
export interface QueuedSend {
    threadId: string;
    queued: true;
    /** The queued message's id — also the run id it will start under. */
    messageId: string;
    /** 0-based place in the queue at the time it was queued (`0` → next). */
    position: number;
    queue: ChatQueueState;
    /** The turn it started under, when it started straight away (an idle thread). */
    runId?: string;
    /** The run an interrupt cancelled to make room for it. */
    interrupting?: string;
}
/** What {@link AgentService.send} did: started a turn, or queued the message. */
export interface ProposalDecisionSend {
    threadId: string;
    proposalDecision: ActionProposalMutationResult | {
        status: 'ambiguous';
        proposalIds: string[];
    };
    text: string;
    queued?: undefined;
    runId?: never;
}
export type ChatSendResult = ProposalDecisionSend | {
    runId: string;
    threadId: string;
    queued?: undefined;
} | QueuedSend;
/**
 * A queue request the service refuses — `status` and `code` are what the routes answer:
 * `409 run_active` (a start-or-refuse {@link AgentService.chat}, or a regenerate, on a busy thread),
 * `410` (a queued message that already started or was removed), `404` (no such queued message),
 * `400` (a bad patch), `501` (the store cannot hold a queue).
 */
export declare class ChatQueueError extends Error {
    readonly status: 400 | 404 | 409 | 410 | 501;
    readonly code?: string | undefined;
    constructor(status: 400 | 404 | 409 | 410 | 501, message: string, code?: string | undefined);
}
/** `regenerate: true` on a send without a `threadId` — there is no answer to regenerate. */
export declare class RegenerateNeedsThreadError extends Error {
    constructor();
}
/**
 * The framework-agnostic orchestration facade the `/agent` routes call: start a run, subscribe to
 * its live token stream, deliver HITL decisions, and read/mutate threads. It owns no HTTP — the
 * provider's route handlers resolve the actor and pipe the SSE, then delegate here.
 */
/** What the service needs beyond the runner, store and deps — all optional. */
export interface AgentServiceOptions {
    /** Which models a caller may pick. Omitted → an empty catalog; naming a model is refused. */
    models?: ModelCatalog;
    /**
     * The budget report behind `GET <path>/quota` and, when `gated`, the send gate. Omitted → a day
     * window from the ledger, never gated.
     */
    quota?: {
        provider: QuotaProvider;
        gated: boolean;
    };
    /** Where attachments live (`attachments` in `config/agent.ts`). Absent → a send naming one is refused. */
    attachments?: AttachmentStagingStore;
    /**
     * The thread message queue — the same instance the runner drains with. Absent (or over a store
     * that is not a `ChatQueueStore`) → a send on a busy thread starts a second, concurrent turn, as
     * before the queue existed.
     */
    queue?: ChatQueueService;
    actionProposals?: ActionProposalService;
    backgroundActorResolver?: BackgroundActorResolver;
}
/** A send's attachments were refused — `status` is what the chat route answers (`403`, `501`). */
/** A thread's `defaultAgent` named an agent this app does not register. */
export declare class UnknownAgentError extends Error {
    readonly agentName: string;
    constructor(agentName: string);
}
export declare class AttachmentRefusedError extends Error {
    readonly status: 400 | 403 | 413 | 415 | 501;
    constructor(status: 400 | 403 | 413 | 415 | 501, message: string);
}
/**
 * The staged-attachment inventory cannot be answered here — no attachment store, a store without
 * `list`, or an agent store without `referencedMediaIds`. `status` is what `GET <path>/attachments`
 * answers. Never answered with an empty list instead: "no inventory" and "no files" are opposite
 * facts, and a sweep that read the first as the second would delete files that are in use.
 */
export declare class AttachmentInventoryError extends Error {
    readonly status: 501;
    constructor(message: string);
}
export declare class AgentService {
    private readonly runner;
    private readonly store;
    private readonly deps;
    private readonly options;
    constructor(runner: AgentRunner, store: AgentStore, deps: AgentDepsFactory, options?: AgentServiceOptions);
    /**
     * The models `actor` may pick for `agent` (`GET <path>/models`). An empty catalog when none is
     * configured, so a picker simply has nothing to offer.
     */
    /** Is a model catalog configured (so `GET <path>/models` lists something)? */
    hasModelCatalog(): boolean;
    listModels(actor: Actor, agent?: string): Promise<ModelCatalogView>;
    /**
     * `model` if the catalog offers it to this actor and agent right now, else a
     * {@link ModelNotAllowedError} naming why. The one gate every model choice passes — a send's own,
     * and a thread's pinned one when it is pinned and again at every turn that runs on it.
     */
    assertModelAllowed(actor: Actor, agent: string, model: string): Promise<string>;
    /**
     * The model a turn runs on: the send's own (that turn only — never stored on the thread), else the
     * thread's pinned one, else none. An agent the catalog locks to one model runs on it whatever was
     * sent or pinned, and a send naming another model is refused.
     */
    private resolveModel;
    /**
     * Pin (or with `null`, unpin) a catalog model on a thread — checked against the catalog for the
     * thread's default agent. `false` when the store cannot persist it (no `updateThread`).
     */
    setThreadModel(actor: Actor, threadId: string, model: string | null): Promise<boolean>;
    /**
     * Set a thread's default agent and/or pin its model (`PATCH <path>/threads/:id`). `null` clears
     * either. The model is checked against the catalog for the agent the thread's next turn runs as —
     * the one this patch sets, else the thread's own default, else the configured one. A
     * `defaultAgent` that names no registered agent is refused ({@link UnknownAgentError}). `false`
     * when the store cannot persist it (no `updateThread`). Ownership is the caller's to check.
     */
    updateThreadSettings(actor: Actor, threadId: string, patch: {
        defaultAgent?: string | null;
        model?: string | null;
        persona?: string | null;
    }): Promise<boolean>;
    /** A registered agent, or a name one of their personas took over (`Persona.aliases`). */
    private isKnownAgent;
    /**
     * The persona this send runs under — see {@link AgentDepsFactory.resolvePersona}. The thread's
     * pinned persona is read only when the send names none AND the agent has personas to pick from.
     */
    private resolveSendPersona;
    /** Pin `persona` on the thread when it is not already — a no-op on a store that cannot. */
    private pinThreadPersona;
    /**
     * The agent a send runs as: the one it names, else the thread's own default agent (when the
     * thread exists and has one), else the configured default.
     */
    private resolveAgentName;
    /**
     * The registered agents, for a picker (`GET <path>/agents`). With a model catalog, an agent it
     * locks to one model for this actor carries `lockedModel` — the same lock `GET <path>/models`
     * reports as `locked`.
     */
    listAgents(actor: Actor): Promise<AgentCatalogEntry[]>;
    /** The actor's budget across windows (`GET <path>/quota`). */
    quotaReport(actor: Actor): Promise<QuotaReport>;
    /**
     * Refuse a turn the actor's budget no longer covers — only when a budget was configured
     * (`quota: { limits }` or a `QuotaProvider`): the default report is informational.
     */
    private assertWithinQuota;
    /** The queue service when the bound store can hold a queue, else `undefined`. */
    private queueing;
    /** The queue service, or a `501` naming what is missing. */
    private requireQueue;
    /**
     * Start a turn — for in-process callers that need a run id back. A send that would have to wait
     * (its thread already has a turn running) is refused with a `409 run_active`
     * {@link ChatQueueError} instead; {@link send} is the form that queues it.
     */
    chat(params: ChatParams): Promise<{
        runId: string;
        threadId: string;
    }>;
    /**
     * Send a message: start a turn, or — when its thread already has one running — queue the message
     * to run after it (see {@link ChatSendMode}). What `POST <path>/chat` calls.
     */
    send(params: ChatParams, options?: ChatSendOptions): Promise<ChatSendResult>;
    /** Start or queue the turn itself — what a send is once it is not a text decision. */
    private sendTurn;
    /**
     * Start a turn the thread is already claimed for, under the claimed id. A runner that minted an
     * id of its own anyway gets the thread re-pointed at it; one that fails to start frees the thread.
     */
    private startClaimed;
    /** Put a send in its thread's queue, then drain it if the thread turned out to be free. */
    private enqueue;
    /** Can this deployment queue messages (the store is a `ChatQueueStore` and a queue is wired)? */
    queueSupported(): boolean;
    /** `GET <path>/threads/:id/queue` — the thread's waiting messages and whether it drains. */
    getQueue(threadId: string): Promise<ChatQueueState>;
    /** The thread a queued message waits on — `null` when there is no such message. */
    queuedMessageThread(messageId: string): Promise<string | null>;
    /**
     * `PATCH <path>/queue/:messageId` — change a waiting message's text and/or attachments, and/or
     * move it to `position` in the queue. Answers the thread's queue.
     */
    updateQueuedMessage(actor: Actor, messageId: string, patch: {
        message?: string;
        attachments?: AttachmentRef[] | null;
        position?: number;
    }): Promise<ChatQueueState>;
    /**
     * `POST <path>/queue/:messageId/interrupt` — run a waiting message NOW: it moves to the head of
     * its queue as an interrupt, any pause is lifted, and the turn holding the thread is cancelled for
     * it (the cancel settles into the queue, which starts it). With nothing running it starts at once.
     *
     * One request rather than "remove it, then send it again with `mode: 'interrupt'`", because a
     * client cannot compose that safely: the message is lost if the second request fails, and runs
     * twice if another tab's drain starts it in between. Here it never leaves the queue. Answers the
     * queue, plus `interrupting` (the run that was cancelled) or `runId` (the run the message started
     * under — its own id).
     */
    interruptQueuedMessage(messageId: string): Promise<ChatQueueState & {
        runId?: string;
        interrupting?: string;
    }>;
    /** `DELETE <path>/queue/:messageId` — drop a waiting message. Answers the thread's queue. */
    removeQueuedMessage(messageId: string): Promise<ChatQueueState>;
    /** `DELETE <path>/threads/:id/queue` — drop every waiting message (and any pause). */
    clearQueue(threadId: string): Promise<ChatQueueState>;
    /**
     * `POST <path>/threads/:id/queue/resume` — lift a pause and start the head when nothing is
     * running. Answers the queue, and the run the head started under, if it did.
     */
    resumeQueue(threadId: string): Promise<ChatQueueState & {
        runId?: string;
    }>;
    subscribe(runId: string): AsyncIterable<StreamFrame>;
    /**
     * Rate a message: `'up'`/`'down'` with an optional comment, or `null` to clear the rating. Checks
     * nothing about WHO — the route resolves the message's thread (`threadOfMessage`) and its owner
     * first. Returns the stored rating, or an error the route maps to a status.
     */
    setMessageFeedback(messageId: string, input: {
        value: unknown;
        comment?: unknown;
    }): Promise<{
        ok: true;
        feedback: MessageFeedback | null;
    } | {
        ok: false;
        status: 400 | 501;
        error: string;
    }>;
    /** The thread a message belongs to (`AgentStore.threadOfMessage`); `undefined` when unsupported. */
    threadOfMessage(messageId: string): Promise<string | null | undefined>;
    /**
     * Is anything streaming (or buffered) under this run — or about to? `true` when the sink cannot
     * say.
     *
     * "About to": a run that holds its thread and has written nothing yet. It was admitted (a send, or
     * a queue drain handing the thread to the next message) and its body has not reached the sink — on
     * a durable runner, because no worker has picked it up. A client told that run started attaches
     * right then; answering "nothing to resume" would make it give up on a turn that is a moment from
     * streaming. The runner decides whether the holder is alive, as it does for admission: a claim
     * left by a process that died is not a stream.
     */
    hasStream(runId: string): Promise<boolean>;
    /**
     * The owning actor ref of a run (turn), or `null` if unknown — for per-actor route ownership
     * checks. A run that holds a thread but has not started executing has no row of its own yet (see
     * `AgentStore.threadHeldByRun`): its owner is the thread's.
     */
    runOwner(runId: string): Promise<string | null>;
    /** The owning actor ref of a thread, or `null` if unknown — for per-actor route ownership checks. */
    /** The run a tool call belongs to (see `AgentStore.getToolCallRunId`); `null` when unknown. */
    toolCallRun(toolCallId: string): Promise<string | null>;
    threadOwner(threadId: string): Promise<string | null>;
    /**
     * Refuse a decision addressed at a run that is over. The runtime would take the signal and buffer
     * it for a run that never comes back — the person's "yes" is accepted, the card says so, and
     * nothing runs. Settles what the dead run left behind on the way out. A runner that cannot say
     * whether a run is alive is taken at its word that it is.
     */
    private assertRunWaiting;
    /**
     * A thread's independent proposals. Without them (the default blocking approvals) there are none,
     * so the read answers an empty list rather than refusing: a client that lists by default (React's
     * `useAgentChat`) needs no flag. Deciding one still answers `501`.
     */
    listActionProposals(actor: Actor, threadId: string): Promise<import("./spi/action-proposal-store.js").ActionProposal[]>;
    /** One page of {@link listActionProposals}: an empty, final page without independent proposals. */
    listActionProposalsPage(actor: Actor, threadId: string, after?: import('./spi/action-proposal-store.js').ListActionProposals['after']): Promise<{
        items: import("./spi/action-proposal-store.js").ActionProposal[];
        next?: {
            createdAt: number;
            id: string;
        };
    }>;
    decideActionProposal(actor: Actor, threadId: string, proposalId: string, decision: 'approved' | 'rejected', body: unknown, via?: string): Promise<ActionProposalMutationResult>;
    /** `actionApprovalMode` in `config/agent.ts` — `'blocking'` unless it says `'independent'`. */
    actionApprovalMode(): ActionApprovalMode;
    /**
     * The words a text decision is made of (`actionProposalText.vocabulary` over the English
     * defaults) — what a surface tells a person to reply, or what it maps a button to.
     */
    actionProposalVocabulary(): TextActionProposalVocabulary;
    /** The configured chat reply to a proposal decision (see `actionProposalText.replies`). */
    actionProposalReply(result: import('./spi/action-proposal-store.js').ActionProposalMutationResult, decision: 'approved' | 'rejected'): string;
    handleTextDecision(actor: Actor, threadId: string, text: string): Promise<{
        status: 'unmatched';
    } | {
        threadId: string;
        proposalDecision: ActionProposalMutationResult;
        text: string;
    } | {
        threadId: string;
        proposalDecision: {
            status: 'ambiguous';
            proposalIds: string[];
        };
        text: string;
    }>;
    /**
     * Approve a parked action call. `executedByRef` is who decided (the routes stamp the caller);
     * `remember` approves later calls of the same tool in the same thread; `via` names the surface the
     * decision came through (`'web'`, `'slack'`, …) — all persisted with the call.
     */
    approve(runId: string, toolCallId: string, opts?: {
        executedByRef?: string;
        remember?: boolean;
        via?: string;
    }): Promise<void>;
    reject(runId: string, toolCallId: string, reason?: string, opts?: {
        executedByRef?: string;
        via?: string;
    }): Promise<void>;
    /**
     * A call's approval state (see `AgentStore.toolCallApproval`); `null` when unknown or unrecorded.
     * Pass the call's `runId` when known: another run may have been handed the same call id.
     */
    toolCallApproval(toolCallId: string, runId?: string): Promise<ToolCallApprovalState | null>;
    /**
     * May `actor` settle a call whose recorded approver is `approver` (never the requester — that is
     * the ownership check)? The configured policy's `canDecide`, else "holds that role".
     */
    mayDecide(actor: Actor, decision: ApprovalDecisionRef): Promise<boolean>;
    /**
     * Deliver a human's answers to a parked question set. An id the reply omits takes that question's
     * own pre-picked default, resolved by the loop against the request it already holds — so "just
     * pressed enter" and "picked exactly the defaults" persist identically, and a client that never
     * rendered the defaults cannot submit a blank.
     *
     * Addressed at a tool call that is in fact waiting for an approve/reject, this is refused: see
     * {@link import('./elicitation.js').HumanReplyMismatchError}. Answers say nothing about whether
     * proposed work should go ahead, and the alternative is a rejection nobody made.
     */
    /**
     * Check a reply against the questions it answers before it is signalled: every submitted value a
     * question's own rules accept (a typed input's type, bounds and pattern; a pick from the offered
     * options), and every `required` question answered — by the reply, or by the defaults an omitted
     * question falls back to. Returns the first problem as `answers["<id>"] <reason>`, or `null`.
     * Needs the store's `toolCallInput`; without it the loop's own filtering is the only check.
     */
    answerProblem(toolCallId: string, answers: Record<string, string[]>, runId?: string): Promise<string | null>;
    answer(args: {
        runId: string;
        toolCallId: string;
        answers: Record<string, string[]>;
        answeredByRef?: string;
        /** The surface the answer came through (`'web'`, `'slack'`, …) — an approval's `via`. */
        answeredVia?: string;
    }): Promise<void>;
    /**
     * The user declined to answer and told the agent to proceed on its own assumptions. It lands on
     * the same values a confirmation would and persists as `rejected` rather than `executed`, because
     * proceeding on an assumption someone declined to confirm is a different fact from proceeding on
     * one they chose.
     */
    skip(args: {
        runId: string;
        toolCallId: string;
        answeredByRef?: string;
        answeredVia?: string;
    }): Promise<void>;
    cancel(runId: string): Promise<void>;
    /**
     * Execute in this process what of `runId` still waits for a worker (see
     * {@link AgentRunner.drive}) — for a caller that waits on the run from inside that worker's own
     * work. A no-op when the runner runs turns where it starts them.
     */
    drive(runId: string): Promise<void>;
    /**
     * The tools THIS actor can reach through an agent, with how a chat surface talks about each —
     * the same list the model is offered (`ToolRegistry.visibleSpecs` behind `definitionsFor`, against
     * the agent's allow-list and the roles policy). Built-in tools the loop serves itself (`ask`,
     * `skill`, `remember`) are not registry tools and are not listed.
     */
    toolCatalog(actor: Actor, agentName?: string): Promise<ToolCatalogEntry[]>;
    /**
     * `GET <path>/tools?agent=*`: the union of {@link toolCatalog} across the default agent and every
     * registered one — each tool this actor reaches through ANY of them, once (first agent wins its
     * entry), under the same gates.
     */
    toolCatalogForAllAgents(actor: Actor): Promise<ToolCatalogEntry[]>;
    /**
     * The persona `id` of `agentName` (default: the default agent), else its default persona — the
     * definition, for a caller that wants to read it. Unlike a send, an unknown `id` is not refused.
     */
    resolvePersona(agentName?: string, id?: string): Persona | undefined;
    /**
     * The agent and persona a queued message that carries no persona starts as: an agent name a
     * persona took over, then the thread's pin, then the agent's default — never refused. What the
     * provider hands the queue as `resolveTarget`.
     */
    queuedTarget(message: {
        threadId: string;
        agentName?: string;
    }): Promise<{
        agentName?: string;
        persona?: string;
    }>;
    /** `agentName`'s personas (default: the default agent's) as a picker reads them. */
    personaCatalog(agentName?: string): PersonaCatalogEntry[];
    listThreads(actorRef: string): Promise<ThreadSummary[]>;
    /**
     * A thread as the routes report it: a persona pin that no registered agent declares — a thread
     * created before pins existed holds whatever it was created with (`'default'`, most often), or the
     * persona was removed since — reads as `null`, which is what it resolves to.
     */
    private withPinnedPersona;
    /**
     * A thread as `actor` reads it back. Each attachment's url is re-minted by `mediaId` through the
     * attachment store: the one persisted with the message was minted for THAT turn (a signed url that
     * expires), so an old turn would otherwise show a dead link. One the store will not resolve for
     * this actor keeps the url it was stored with.
     */
    getThread(threadId: string, actor?: Actor): Promise<ThreadDetail | null>;
    /**
     * The size cap and content types an attachment is held to — what the attachment store declares,
     * else the defaults (20 MiB; images, PDF, plain text, CSV). `null` when no attachment store is
     * configured, so nothing can be attached.
     */
    attachmentLimits(): AttachmentLimits | null;
    /**
     * Stage a file the server received itself (a channel's media message) for `actor`, held to the same
     * limits as `POST <path>/attachments` — then name it in a send as `{ mediaId }`. Refused
     * ({@link AttachmentRefusedError}) with `501` when no attachment store is configured, `415` for a
     * content type outside the allowlist, `413` past the size cap.
     */
    stageAttachment(actor: Actor, file: {
        data: Buffer;
        filename: string;
        contentType: string;
    }): Promise<MessageAttachment>;
    /**
     * The Lucid database the agent's store writes to, or `null` when the store is not Lucid — what
     * shared state that should live next to the threads (a text channel's store) is built on.
     */
    lucidDatabase(): LucidDatabaseLike | null;
    /**
     * Turn a send's `{ mediaId }` refs into attachments through the store, for this actor. Refused
     * ({@link AttachmentRefusedError}) when no store is configured or one ref is not the actor's to use.
     */
    private resolveAttachments;
    /**
     * Every file this actor has staged, newest first — across threads, including uploads that were
     * never sent, which nothing else on this surface can see (`GET <path>/attachments`). Metadata only:
     * a url is minted per request by `resolve`, so it can stay short-lived.
     *
     * Refused ({@link AttachmentInventoryError}) when the configured attachment store keeps no
     * inventory (`AttachmentStagingStore.list`).
     */
    listAttachments(actor: Actor, options?: {
        limit?: number;
    }): Promise<StagedAttachment[]>;
    /**
     * This actor's staged media that no live message — and no message waiting in a thread's queue —
     * references, and that is old enough not to be an upload in flight: the candidate set for a sweep.
     * Returns them; never deletes them. The bytes are the host's, and so is the decision:
     *
     * ```ts
     * const service = await app.container.make(AgentService)
     * for (const file of await service.collectableAttachments(actor, { olderThan: subDays(new Date(), 7) })) {
     *   await media.delete(file.mediaId)
     * }
     * ```
     *
     * `olderThan` is required and has no default: how long a composer may sit open with a file attached
     * is the host's knowledge. The staging store is asked to apply it, and the result is filtered again
     * here — a store that ignored the hint would otherwise hand back an upload someone is about to send.
     *
     * Both halves must be answerable or this refuses ({@link AttachmentInventoryError}): an
     * unanswerable reference query means "cannot tell", and treating it as "nothing is referenced"
     * would mark every attachment the actor ever sent as safe to delete.
     */
    collectableAttachments(actor: Actor, options: {
        olderThan: Date;
        limit?: number;
    }): Promise<StagedAttachment[]>;
    /** The configured store's `list`, or a refusal — never an empty inventory in its place. */
    private attachmentInventory;
    renameThread(threadId: string, title: string): Promise<void>;
    deleteThread(threadId: string): Promise<void>;
    /**
     * Make a transient thread a regular one (listed by {@link listThreads}). `false` when the store
     * cannot — it has no `promoteThread`.
     */
    promoteThread(threadId: string): Promise<boolean>;
    /**
     * Drop a message and everything after it — the "edit and resend" primitive: the client then sends
     * a fresh turn on the shortened thread.
     */
    truncateThreadFrom(threadId: string, messageId: string): Promise<void>;
    forkThread(threadId: string, fromMessageId: string): Promise<ThreadSummary>;
    quotaToday(actorRef: string): Promise<{
        usedTokens: number;
    }>;
}
//# sourceMappingURL=agent-service.d.ts.map