import type { ToolCallOutcome } from '../dangling-tool-calls.js';
import type { RecordGenuiCallInput } from '../genui-telemetry.js';
import type { ActionProposalStoreOptions } from '../spi/action-proposal-store.js';
import { type AgentStore, type AppendMessageInput, type CreateThreadInput, type RecordRunEndInput, type RecordRunStartInput, type RecordToolCallInput, type RecordUsageInput, type ThreadTurnPage, type ThreadTurnQuery, type ThreadTurnReader, type UpdateThreadInput, type UpdateToolCallInput, type UsageTotals } from '../spi/agent-store.js';
import { type ToolCallApprovalState } from '../spi/approval-policy.js';
import type { ChatQueueStore, EnqueueMessageInput, QueuedMessage, QueuedMessagePatch, QueuePause } from '../spi/chat-queue.js';
import type { AgentUiComponent } from '../stream-events.js';
import type { MessageFeedback, StoredMessage, ThreadDetail, ThreadSummary, ToolResult } from '../types.js';
import { LucidActionProposalStore } from './lucid-action-proposal-store.js';
/** The chainable query-builder surface the store leans on (Knex-shaped). */
export interface LucidQueryBuilderLike {
    where(column: string, value: unknown): this;
    where(column: string, operator: string, value: unknown): this;
    whereNull(column: string): this;
    whereIn(column: string, values: readonly unknown[]): this;
    orderBy(column: string, direction: 'asc' | 'desc'): this;
    limit(value: number): this;
    offset(value: number): this;
    first(): Promise<Record<string, unknown> | null>;
    select(...columns: string[]): Promise<Record<string, unknown>[]>;
    update(row: Record<string, unknown>): Promise<unknown>;
    delete(): Promise<unknown>;
}
export interface LucidInsertBuilderLike {
    insert(row: Record<string, unknown>): Promise<unknown>;
}
/** A query client — the base connection or a transaction client (both expose `from`/`table`). */
export interface LucidClientLike {
    from(table: string): LucidQueryBuilderLike;
    table(table: string): LucidInsertBuilderLike;
}
/**
 * Bindings a raw query accepts: positional (an array) or named (an object). Deliberately a SUPERTYPE
 * of Lucid's own `RawQueryBindings` (`StrictValues[] | { [key: string]: StrictValues }`) rather than a
 * narrower guess.
 *
 * It used to be `unknown[]`, and that silently excluded the very client the migration stub passes in.
 * A method parameter is checked bivariantly, so a candidate satisfies this interface if EITHER
 * direction assigns — but `unknown[]` assigned in neither: not to `StrictValues[]` (an `unknown`
 * element is not a `StrictValue`), and `RawQueryBindings` not to `unknown[]` (the named-bindings
 * object is not an array). So `QueryClientContract` — what `db.connection(name)` returns — failed to
 * match, only the `Database` manager did (its `bindings?: any` matches anything), and the published
 * migration did not compile in a consumer app.
 *
 * Widening to `readonly unknown[] | Record<string, unknown>` makes `RawQueryBindings` assignable in
 * the outward direction, so every real Lucid client matches. It stays a structural mirror: nothing
 * here imports `@adonisjs/lucid`, which is what keeps it an optional peer.
 */
export type LucidRawBindings = readonly unknown[] | Record<string, unknown>;
/**
 * A raw SQL runner — the ONLY capability the schema helpers need. Kept separate from
 * {@link LucidDatabaseLike} so `createAgentTables` and friends ask for exactly what they use: both the
 * `Database` manager and a per-connection `QueryClientContract` satisfy it, which is what lets the
 * published migration scope its DDL with `db.connection(this.db.connectionName)` and honour
 * `migration:run --connection=x`.
 */
export interface LucidRawRunner {
    rawQuery(sql: string, bindings?: LucidRawBindings): Promise<unknown>;
}
/** The `Database` facade slice: a client plus raw SQL (DDL) and transactions. */
export interface LucidDatabaseLike extends LucidClientLike, LucidRawRunner {
    transaction<T>(callback: (trx: LucidClientLike) => Promise<T>): Promise<T>;
}
/**
 * `query` in the order its messages were appended: `seq`, which append assigns, then `created_at`
 * and `id` for the rows from before `seq` existed (they all hold 0, and so sort first). `created_at`
 * alone could not order a transcript: two messages of one turn routinely share a millisecond, and
 * the database then returned them in either order.
 */
export declare function inAppendOrder<Q extends LucidQueryBuilderLike>(query: Q, direction: 'asc' | 'desc'): Q;
export interface LucidAgentStoreOptions extends ActionProposalStoreOptions {
    /**
     * Provision the agent tables (via {@link ensureAgentTables}) — as the app starts when the agent
     * provider built the store, else on first use — so the lib manages its own schema, the ecosystem
     * convention (mirrors `@adonis-agora/durable` and `@adonis-agora/authz`).
     * Default `true`. Set `false` to opt out and run the published migration (`createAgentTables` /
     * `node ace configure @adonis-agora/agent`) instead — e.g. when you want the schema versioned.
     * The same flag governs the pricing store and the governance read-model, which share these tables.
     */
    autoCreateTables?: boolean;
}
/**
 * The PK of a tool call's row: the model's id scoped to the assistant message that carries it. A
 * provider only promises a call id is unique within one response — one assistant message, since the
 * loop writes one per step — and some repeat one across runs (the short `call_<digits>` ids of some
 * providers behind OpenRouter), so the bare id as PK let one insert fail on another's row. Scoping
 * by message rather than run also covers a provider that numbers calls per response (`call_0` again
 * on the next step of the same run), and a call recorded without a run. Deterministic, so a durable
 * replay derives the same key. The model's id itself is kept in `call_id`, and is still what every
 * caller addresses the call by. Hashed in the rare case the pair outgrows the column.
 */
export declare function toolCallRowId(toolCallId: string, messageId: string): string;
/** The id the model gave the call on `row`: `call_id`, or the PK on a row from before it existed. */
export declare function toolCallIdOfRow(row: Record<string, unknown>): string;
/**
 * A production-grade, persistent {@link AgentStore} backed by AdonisJS **Lucid** (Knex) over the five
 * agent tables (threads, messages, tool calls, token usage, model pricing). JSON payloads are stored
 * as TEXT and timestamps as epoch-ms integers, so it is portable across SQLite / Postgres / MySQL and
 * is the behavioral twin of the in-memory store.
 *
 * `forkThread` / `truncateFrom` run in a transaction (cheap safety); `truncateFrom` deletes the
 * doomed messages' tool calls explicitly (not only via FK cascade) so it works on SQLite even without
 * `PRAGMA foreign_keys=ON`. The tool-call PK is always the model-supplied `toolCallId`.
 *
 * Usually you don't construct this directly: `config/agent.ts` selects it via `stores.lucid({ ... })`
 * and the provider builds it, lazily importing `@adonisjs/lucid` only when the `lucid` store is chosen.
 */
export declare class LucidAgentStore extends LucidActionProposalStore implements AgentStore, ThreadTurnReader, ChatQueueStore {
    private readonly db;
    private readonly autoCreateTables;
    private ready;
    /**
     * Whether `agent_tool_call` has `call_id`, i.e. rows are keyed per message (#183). Always so when
     * the store provisions the schema itself; see {@link checkToolCallSchema} for when it is not.
     */
    private toolCallsScoped;
    /**
     * Whether `agent_message` / `agent_queued_message` have `sent_at`. Always so when the store
     * provisions the schema; see {@link checkToolCallSchema} for when it is not.
     */
    private sentAtColumns;
    constructor(db: LucidDatabaseLike, options?: LucidAgentStoreOptions);
    /** The database this store writes to — what other Lucid-backed state (a channel store) shares. */
    get database(): LucidDatabaseLike;
    private init;
    /**
     * Under `autoCreateTables: false` the app owns the schema, so it can boot this version before its
     * migration has added `agent_tool_call.call_id`. Rather than fail every tool call insert until then,
     * the store keeps the old layout — PK = the model's id, so the #183 collision is still possible —
     * and says so once, at startup.
     */
    private checkToolCallSchema;
    /**
     * Provision (or repair) the agent tables now. The agent provider calls this once as the app starts,
     * so no request — and no transaction a caller happens to have open — ever pays for DDL. Idempotent;
     * a no-op under `autoCreateTables: false`. Every other method still awaits it, which is what keeps
     * a store built by hand (no provider) working.
     */
    ensureSchema(): Promise<void>;
    createThread(input: CreateThreadInput): Promise<ThreadSummary>;
    getThread(threadId: string): Promise<ThreadDetail | null>;
    loadThreadForTurn(query: ThreadTurnQuery): Promise<ThreadTurnPage | null>;
    /**
     * The newest `limit` messages, oldest-first, projected to the columns a model turn reads. The
     * bound is the database's: a `limit` on rows ordered newest-first, so a long thread's older rows
     * are never materialized at all.
     */
    private loadTurnWindow;
    getThreadActorRef(threadId: string): Promise<string | null>;
    listThreads(actorRef: string, limit?: number): Promise<ThreadSummary[]>;
    promoteThread(threadId: string): Promise<void>;
    softDeleteThread(threadId: string): Promise<void>;
    forkThread(threadId: string, fromMessageId: string): Promise<ThreadSummary>;
    setTitle(threadId: string, title: string): Promise<void>;
    updateThread(threadId: string, patch: UpdateThreadInput): Promise<void>;
    defaultAgentForThread(threadId: string): Promise<string | null>;
    personaForThread(threadId: string): Promise<string | null>;
    clearActiveStream(threadId: string, runId: string): Promise<void>;
    setActiveStream(threadId: string, runId: string | null): Promise<void>;
    activeRunForThread(threadId: string): Promise<string | null>;
    threadHeldByRun(runId: string): Promise<string | null>;
    /**
     * Compare-and-set, as two conditional updates rather than one `OR` (the structural query builder
     * has no `orWhere`): free → `runId`; else held by `runId` or `replacing` → `runId`. Each statement
     * is atomic and re-checks its own predicate under the row lock, so of two racing claims exactly
     * one matches.
     */
    claimActiveStream(threadId: string, runId: string, options?: {
        replacing?: string;
    }): Promise<boolean>;
    releaseActiveStream(threadId: string, runId: string): Promise<boolean>;
    enqueueMessage(input: EnqueueMessageInput): Promise<QueuedMessage>;
    listQueue(threadId: string): Promise<QueuedMessage[]>;
    getQueuedMessage(id: string): Promise<QueuedMessage | null>;
    updateQueuedMessage(id: string, patch: QueuedMessagePatch): Promise<QueuedMessage | null>;
    moveQueuedMessage(id: string, index: number): Promise<boolean>;
    /** A conditional delete: `false` when the row was already gone (a drain lost its head). */
    removeQueuedMessage(id: string): Promise<boolean>;
    clearQueue(threadId: string): Promise<number>;
    queuePause(threadId: string): Promise<QueuePause | null>;
    setQueuePause(threadId: string, pause: QueuePause | null): Promise<void>;
    appendMessage(input: AppendMessageInput): Promise<StoredMessage>;
    /**
     * `{ sent_at }` for an insert, only when there is one to write and the column exists — a database
     * whose `sent_at` has not been added yet (`autoCreateTables: false`) keeps every write working.
     */
    private sentAtColumn;
    /**
     * The next `seq` in a thread: one past its highest. Two appends racing on ONE thread can draw the
     * same number — the turn loop never does that, and the tie then falls back to `created_at`, `id`.
     */
    private nextMessageSeq;
    setMessageUi(messageId: string, ui: AgentUiComponent[]): Promise<void>;
    setMessageToolResults(messageId: string, results: ToolResult[]): Promise<void>;
    truncateFrom(threadId: string, messageId: string): Promise<void>;
    /**
     * Of `mediaIds`, the ones a surviving message — or one waiting in a thread's queue — in one of this
     * actor's threads still carries.
     *
     * The match on the attachment's `mediaId` runs here rather than in SQL: the column is JSON text,
     * and every dialect this store targets spells "an array element with this field" differently
     * while none of them could use an index for it. The scan is bounded by ONE actor's
     * attachment-bearing messages.
     * Soft-deleted threads are included: their messages survive, so what they point at is still
     * reachable from stored state.
     */
    referencedMediaIds(actorRef: string, mediaIds: readonly string[]): Promise<string[]>;
    recordToolCall(input: RecordToolCallInput): Promise<void>;
    updateToolCall(input: UpdateToolCallInput): Promise<void>;
    /**
     * The row of the call the model named `toolCallId`: the newest one `runId` recorded, or without a
     * run the newest one overall. Newest is the live one: a run's steps are sequential, so when a step
     * settles its call, a repeat of the id in an earlier step has already settled. Falls back to a row
     * from before `call_id` existed, whose PK is the bare model id, unless it belongs to another run.
     */
    private findToolCall;
    /** Attach, per message, the approval record of every call on it a policy put to a person. */
    private withApprovals;
    rememberedApprovals(threadId: string): Promise<string[]>;
    toolCallApproval(toolCallId: string, runId?: string): Promise<ToolCallApprovalState | null>;
    threadOfMessage(messageId: string): Promise<string | null>;
    setMessageFeedback(messageId: string, feedback: MessageFeedback | null): Promise<void>;
    toolCallInput(toolCallId: string, runId?: string): Promise<unknown>;
    getToolCallRunId(toolCallId: string): Promise<string | null>;
    toolCallOutcomes(toolCallIds: string[]): Promise<ToolCallOutcome[]>;
    failUnsettledToolCalls(runId: string, error: string): Promise<number>;
    recordUsage(input: RecordUsageInput): Promise<void>;
    recordGenuiCalls(rows: readonly RecordGenuiCallInput[]): Promise<void>;
    recordRunStart(input: RecordRunStartInput): Promise<void>;
    getRunActorRef(runId: string): Promise<string | null>;
    recordRunEnd(input: RecordRunEndInput): Promise<void>;
    usageBetween(actorRef: string, fromDay: string, toDay: string): Promise<UsageTotals>;
    quotaToday(actorRef: string, day: string): Promise<{
        usedTokens: number;
    }>;
}
//# sourceMappingURL=lucid.d.ts.map