import { type Principal, type ThreadId } from "@vendoai/core"; import type { VendoStore } from "../store.js"; /** The least a transcript row must have for this store to key it: an id. * * Build contract §6 writes the surface in terms of the ai-SDK's `UIMessage`, * and lane A's runtime instantiates it that way — `threadMessageStore(store)` * — so callers get the frozen signature exactly. The parameter stays generic * here because `@vendoai/store` (like `@vendoai/core`) deliberately does not * depend on `ai`: the store never interprets a message, only orders and * returns it. Importing the type would put `ai` + a `zod` floor into the * store's published peer set for one type annotation, which * `scripts/dependency-guard.mjs` rejects outright. See the lane report. */ export interface ThreadMessageLike { id: string; } /** Build contract §6 — the surface, whichever backend serves it. */ export interface ThreadMessageStore { /** One row per message. Pass `expectedRevision` for a per-row CAS edit * (contract §6): the write lands only if the row is still at that revision, * so an edit built on a stale read is refused instead of clobbering a * concurrent one. Omit it for last-write-wins, which is the default. */ upsert(principal: Principal, threadId: ThreadId, message: M, seq: number, opts?: { expectedRevision?: number; }): Promise; /** A whole turn's new-or-edited messages in ONE round trip, with `title` the * listing title the caller derived. What `upsert` does per message — and, * over the wire, per message plus a full thread download — this does once. * A message the thread already holds is updated in place and keeps its * position; genuinely new ones are appended after the tail, in the order * given. * * Unlike `upsert`, this takes NO `seq`: a caller computing a position * outside the write cannot avoid racing another turn on the same thread for * it (proven on PostgreSQL 17), so the position is the statement's to assign * while it holds the thread row. */ upsertMany(principal: Principal, threadId: ThreadId, messages: ReadonlyArray, opts?: { title?: string; }): Promise; /** Reassembled by seq, oldest → newest. */ list(principal: Principal, threadId: ThreadId): Promise; } /** Build contract §6 — one row per transcript message. * * Why this exists: rewriting a whole `messages` array on every turn is * O(messages²) over a conversation. One row per message makes a turn's write * O(new messages), which is the property E6 measures. * * Two invariants both backends enforce rather than trust: * - **`seq` is the only ordering authority.** Approval flips rewrite older * messages, so `updated_at` does not order a transcript and is never read * for ordering. * - **Threads never cross subjects** (03 §5). The thread row is the ownership * record; every read and write here joins it under `principal.subject`, so a * foreign thread id reads as empty and writes to it are refused. */ export declare function threadMessageStore(store: VendoStore): ThreadMessageStore; //# sourceMappingURL=thread-messages.d.ts.map