export declare class ChatSessionTooLargeError extends Error {
}
export declare class ChatSessionForbiddenError extends Error {
}
/** A single persisted message. `meta` is the opaque web `ChatMessage` extras. */
export interface StoredChatMessage {
    role: 'user' | 'agent';
    content: string;
    /** Parsed JSON of { segments?, suggestions?, error? } — opaque to the server. */
    meta?: Record<string, unknown> | null;
}
/** Session metadata, no messages. What the list endpoint returns. */
export interface ChatSessionMeta {
    id: string;
    title: string;
    titleGenerated: boolean;
    pinned: boolean;
    pinnedAt: string | null;
    createdAt: string;
    updatedAt: string;
    /** Number of stored messages (for the list count badge). */
    messageCount: number;
    /** First user message content, truncated — for the list preview line. */
    preview: string;
}
/** A session plus its assembled messages. What the detail endpoint returns. */
export interface ChatSessionWithMessages extends Omit<ChatSessionMeta, 'messageCount' | 'preview'> {
    messages: StoredChatMessage[];
}
/** Metadata create/upsert payload (POST /api/chat-sessions, PATCH /:id). */
export interface UpsertChatSessionMetaInput {
    id: string;
    title?: string;
    titleGenerated?: boolean;
    pinned?: boolean;
    pinnedAt?: string | null;
    createdAt?: string;
    updatedAt?: string;
}
/** Append payload — the new messages for a turn. */
export interface AppendMessageInput {
    role: 'user' | 'agent';
    content: string;
    meta?: Record<string, unknown> | null;
}
/** What an attached scratch file is, for resolution: an image the agent SEES
 *  (vision) or a doc whose extracted TEXT the agent reads. */
export type ScratchFileKind = 'image' | 'doc';
/** Doc text-extraction outcome. `none` = not a doc (images); `ready`/`failed`
 *  only meaningful for docs. A doc is never stored with `failed` (D13 rejects at
 *  upload), so this is effectively `none`|`ready` on disk — `failed` exists for
 *  defensive reads. */
export type ScratchExtractState = 'none' | 'ready' | 'failed';
/** Where the bytes came from. `upload` = we own the blob (delete on cleanup);
 *  `library` = the blob is a permanent curated asset we only REFERENCE — cleanup
 *  must NOT delete it (D9). */
export type ScratchFileSource = 'upload' | 'library';
/** A conversation-scratch file's metadata (composer-vision E2a, generalized to
 *  any readable file in PR3). No bytes — those live in the blob backend (uploads)
 *  or the curated asset store (library picks). */
export interface ScratchFileInput {
    sessionId: string;
    fileId: string;
    userId: number;
    filename: string;
    contentType: string;
    size: number | null;
    /** Full backend key the bytes live under (e.g. `chat-scratch/{s}/{f}/{name}`,
     *  or a curated asset's key when `source==='library'`). */
    blobKey: string;
    kind: ScratchFileKind;
    /** Extracted doc text (already capped, D8). Null for images / un-extracted. */
    extractedText?: string | null;
    extractState?: ScratchExtractState;
    source?: ScratchFileSource;
    /** For `source==='library'` rows: the source asset's repo + id, so access can
     *  be RE-checked on every serve/replay (access may be revoked after attach). */
    sourceRepo?: string | null;
    sourceAssetId?: string | null;
}
export type ScratchFileRow = Required<Omit<ScratchFileInput, 'extractedText' | 'sourceRepo' | 'sourceAssetId'>> & {
    extractedText: string | null;
    sourceRepo: string | null;
    sourceAssetId: string | null;
    /** ISO timestamp the row was inserted (for most-recent-first ordering). */
    createdAt?: string;
};
export declare class ChatSessionService {
    private db;
    /** Injected external cleanup for a deleted chat (composer-vision E2a): delete
     *  the OWNED scratch file BLOBS (by exact key — only `source='upload'` rows;
     *  library-sourced rows reference permanent assets and are excluded by the
     *  caller, D9) and the agent-pool session JSON (which holds base64 copies of
     *  vision images). This SQLite service deletes its own rows; the callback
     *  handles the blob store + agent pool it has no handle to.
     *  `sessionId` = single delete; undefined = delete-all-for-user. */
    private scratchCleanup?;
    /** Wire the scratch blob + agent-pool cleanup (see field doc). */
    setScratchCleanup(fn: (userId: number, sessionId: string | undefined, blobKeys: string[]) => void): void;
    constructor(workspacePath: string);
    /**
     * Idempotent: guarded by the recorded `schema_version`. The undeployed blob
     * model (`a2ba23b6`, schema 1) never shipped, so there's no data to migrate —
     * a local dev DB that touched it simply keeps its orphaned `messages` column
     * (the new chat_sessions inserts don't reference it; it defaults). Add a new
     * branch when bumping `SCHEMA_VERSION`; never edit existing branches.
     */
    private runMigrations;
    /** True if a table with this name exists in the DB. */
    private tableExists;
    /**
     * Bring the conversation-scratch table to the PR3 `chat_scratch_files` shape.
     * Self-contained + idempotent (runs from the constructor, independent of
     * `schema_version` ordering since the constructor's CREATEs run first):
     *  - PR2 `chat_scratch_images` present, new table absent → rename in place +
     *    `ADD COLUMN` (preserves rows/blobs/refs; images get kind='image',
     *    extract_state='none', source='upload'). This is D14 — additive, no drop.
     *  - Neither present (fresh DB) → create `chat_scratch_files` directly.
     *  - New table already present → nothing to do.
     */
    private migrateScratchTable;
    /** Add a column to chat_scratch_files only if it's not already present. */
    private ensureScratchColumn;
    private rowToMeta;
    /**
     * A user's sessions as metadata (no messages), most-recently-updated first.
     * `messageCount` + `preview` (first user message) come from correlated
     * subqueries so the list stays a single query and never ships message bodies.
     */
    listMeta(userId: number): ChatSessionMeta[];
    /** A single session WITH its messages (seq order), or null if not owned/found. */
    get(userId: number, id: string): ChatSessionWithMessages | null;
    /**
     * Raw attachment-ref objects persisted on a session's USER messages, for the
     * server-side doc replay (D7). Returns the loosely-typed ref objects (from
     * `meta.attachments` (PR3) or legacy `meta.imageRefs` (PR2)); the caller
     * normalizes + re-authorizes them. Returns [] if the session isn't owned.
     * Kept here (not in the route) so the meta shape lives in one place, and
     * returns `unknown[]` to avoid importing the ref type (would cycle with
     * scratch-attachment-service, which imports this module).
     */
    getSessionAttachmentRefs(userId: number, id: string): unknown[];
    private safeParse;
    private ownerOf;
    /**
     * Create a session, or update its metadata in place. A session id is owned by
     * the first user who writes it; a write from a different user is rejected so
     * one account can't overwrite another's history. Only the provided fields are
     * updated (PATCH semantics) — messages are NEVER touched here, so a metadata
     * write (rename/pin) can't clobber an in-flight stream's appended messages.
     * `createdAt`/`updatedAt` are honored on create; on update, `createdAt` is
     * preserved and `updatedAt` only advances when supplied.
     */
    upsertMeta(userId: number, input: UpsertChatSessionMetaInput): ChatSessionMeta;
    /**
     * Append messages to a session, in order, after the current max `seq`.
     * Create-on-demand: if the session row doesn't exist it's inserted (owned by
     * the caller) so a turn-completion append can't lose data to a create race.
     * Bumps the session's `updated_at` so active chats float to the top of the
     * list. Rejects another user's session id, an over-budget append, or one that
     * would push the user past the abuse ceiling.
     */
    appendMessages(userId: number, sessionId: string, messages: AppendMessageInput[], opts?: {
        title?: string;
    }): {
        appended: number;
    };
    /** Owner of a session, or undefined if it doesn't exist (public for the
     *  composer-vision upload route's reservation check). */
    getSessionOwner(id: string): number | undefined;
    /** Record a conversation-scratch file's metadata (no bytes). */
    insertScratchFile(input: ScratchFileInput): void;
    /** Map a raw chat_scratch_files row to a ScratchFileRow. */
    private static toScratchRow;
    /** Look up a scratch file's metadata row, or null. */
    getScratchFile(sessionId: string, fileId: string): ScratchFileRow | null;
    /** A session's scratch files, most-recently-added first. Used by the agent's
     *  `save_file_to_assets` tool to resolve "this file" scoped to the CURRENT
     *  conversation, and by the server's per-turn doc replay (D7). */
    getScratchFilesBySession(sessionId: string): ScratchFileRow[];
    /** Delete a session and cascade its messages + scratch metadata, scoped to its
     *  owner (no-op if not owned). External blob + agent-pool cleanup runs via the
     *  injected scratchCleanup with the session's blob keys. */
    delete(userId: number, id: string): void;
    /** Delete every session + message + scratch row for a user (reset path). */
    deleteAllForUser(userId: number): void;
    close(): void;
}
