/** * Pure mappers between the AgJSON fold (`AgReduceResult` components) and * `ThreadMessage` rows / `ThreadSnapshot`. No AWS, no I/O — the byte-identity * round-trip lives here and is unit-tested table-free. See * docs/superpowers/specs/2026-06-23-agjson-persistence-fold-design.md §6–§9. */ import type { AgMessage, AgArtifact, AgTurnRecord, AgReduceResult, AgEvent, AgMemoryRecord, JsonValue } from "@silverprotocol/core"; import type { ThreadMessageRow, ThreadSnapshotRow } from "./rows.js"; export interface RowCtx { threadId: string; userId: string; seq: number; at: string; clientMessageId: string; /** * guuey#524: set by the pod for a page-aware turn's writes — every row * of that turn (message and card alike) carries the stamp. See * `ThreadMessageRow.untrustedOrigin`. */ untrustedOrigin?: boolean; } /** * The ANSWER as plain text: the message's text blocks WITHOUT interim * narration. Distinct text blocks are distinct paragraphs, so they join with * a paragraph break, the same block-boundary contract as `@guuey/agent-client`'s * live flat-text fold (guuey#98); raw concatenation jammed "…instead.Here's * your packing…". Empty blocks contribute nothing (no stacked separators). * This is the row's `text`: what a reloaded thread, a preview and a hook read. */ export declare function messageText(msg: AgMessage): string; /** The message's interim narration, one entry per non-empty narration block, in order. */ export declare function messageNarration(msg: AgMessage): string[]; /** * EVERY text block, narration included, joined as the answer text was joined * before narration split out of it: the MODEL's view of a stored message. * `loadHistory` replays this, so the model's conversation history is the same * bytes whether a row was written before the split or after it. */ export declare function messageFullText(msg: AgMessage): string; /** A stored message's text, split: the answer, the narration, and everything (the model's view). */ export interface StoredTextParts { answer: string; narration: string[]; all: string; } /** * Split a stored row's `content` into answer, narration and the model's full * text, when `content` is a stored `AgMessage` (an object, or the JSON string * an AppSync writer stores). Anything else is null, and a reader falls back to * the row's `text`. It is read through core's lenient stored-record reader, so * an unknown block is omitted rather than failing the row. */ export declare function storedTextParts(content: unknown): StoredTextParts | null; export declare function agMessageToRow(msg: AgMessage, ctx: RowCtx & { turnRecord?: AgTurnRecord; }): ThreadMessageRow; export declare function uiCardArtifactsFromMessages(messages: AgMessage[]): AgArtifact[]; /** * toolCallId → tool name, read off every `tool-call` block in the fold's * messages (guuey#402). The name rides ONLY the tool-call block; results * carry just the id, so this map is how a card row learns which tool * produced it. First writer wins on a duplicate id (ids are unique per * provider contract; a collision would be the provider's bug). */ export declare function toolNamesByCallId(messages: AgMessage[]): Map; /** * The producing tool's name for a card artifact: the first tool-result part * whose `toolCallId` the fold's tool-call blocks named (guuey#402). * `undefined` when unresolvable — a first-class `artifact.*` event with no * tool-result part, or a call outside this fold. Callers persist absence, * never a guess (humanization and fallbacks are the kit's, one owner). */ export declare function producingToolName(art: AgArtifact, toolNames: ReadonlyMap): string | undefined; export declare function agArtifactToCardRow(art: AgArtifact, ctx: RowCtx, toolName?: string): ThreadMessageRow; /** * Reconstruct an AgMessage from a row. Agent-fold rows store the verbatim * AgMessage in `content`; user/system rows (persisted up-front) store plain * `{ kind, text }` — synthesize a single-text-block AgMessage for those so * the reassembled transcript is uniform. * * The stored AgMessage is read with the core's lenient stored-record reader * (AgJSON draft.4 §0.2): a content element of an undefined type, or with an * undefined value in a closed set, is omitted whole and the rest restores in * order; unknown fields pass through untouched. Only a record the reader * cannot materialize (no string `id`, an undefined `role`, no `content` * array) falls to the text projection. */ export declare function rowToAgMessage(row: ThreadMessageRow): AgMessage; /** Extract the AgArtifact from a kind='card' row, or undefined if malformed. */ export declare function cardRowToAgArtifact(row: ThreadMessageRow): AgArtifact | undefined; /** * Reassemble an AgReduceResult from persisted rows + the thread snapshot. * Inverse of the per-turn write (design §9). Append components come from * rows in seq order; latest-replace components from the snapshot. Byte- * identical to reduce() over the agent-folded portion — EXCEPT that * `artifacts` additionally carries the UI-card projections * ({@link uiCardArtifactsFromMessages}) the live reduce() never produced. * Re-persisting a reassembled fold through `appendFold` stays single-write: * the projection is deduped by its deterministic `#ui#` * artifactIds there. */ export declare function reassembleFold(rows: ThreadMessageRow[], snapshot: ThreadSnapshotRow | undefined): AgReduceResult; /** * Split a snapshot's STORED thread-memory array (what a binding reads back) * into the view this runtime reads and the elements it carries (AgJSON * draft.4 §0.2's stored-record reader). Stored data is a wire across a rolling * release, so it is never taken as `AgMemoryRecord[]`: * - `threadMemory`: the records the reader materializes with scope `thread`; * the view a fold is seeded with and a prompt recalls; * - `carriedThreadMemory`: every other stored element, verbatim and in stored * order: a record the reader cannot materialize (an undefined `scope`, a * missing `value`) and a readable record of another scope. * The reader either materializes an element or reports it at `[i]`. If a core * version ever did both for one element (a partial report), pairing the view * back to the stored elements would desync, so then EVERY element is carried * and nothing is seeded: safe at rest, and visible as a full carry. */ export declare function splitStoredThreadMemory(stored: readonly JsonValue[]): Pick & { carriedThreadMemory: JsonValue[]; }; /** * The one stored thread-memory array a binding writes: the carried elements * FIRST, then this runtime's thread records. Stored order is precedence for a * reader that seeds every record (the reducer keys memory by scope and key, and * the last write wins), and the carried elements were written before this * turn, so write order stays time order. */ export declare function joinStoredThreadMemory(row: Pick): JsonValue[]; /** * The thread records a turn persists: each record of the fold, except that a * record the turn did NOT change is persisted as the PRIOR stored record * instead of the fold's rebuild of it (guuey#1669). * * The prior view comes from the stored-record reader, whose elements are raw * copies that keep members this runtime does not know. The fold cannot * carry those: {@link seedEventsForReducer} rebuilds each record as a * `memory.write` from its known fields, and the reducer keeps only those. So * persisting the fold alone would drop a record's unknown members on the first * turn that re-persists the thread, even though the turn never touched it. A * record keeps what it was stored with until a turn changes it. * * - **Identity** is the reducer's: scope plus `key ?? ""`. A fold record meets * the LAST prior record of that identity, the one the seed left standing, * because the last write wins. Several key-less records of one scope were * already collapsed to that one by the reducer before this existed; that * limit predates the overlay and is not papered over here. * - **Unchanged** means the known fields a turn can write are deep-equal: * `scope`, `key`, `value`, `reason`, `durable`, `turnId` and `_meta` * (draft.8: a `memory.write` sets it, and a patch can replace it without * touching the value or the turnId, so a `_meta`-only change must count as * a change or the prior `_meta` would be persisted over it). A live write * stamps the current turn's `turnId`, so a turn that rewrites the SAME value * still changes the record, and the fold's record wins (the unknown members * described the previous write). **`threadId` is deliberately left out:** a * `memory.write` has no `threadId` on its event arm, so no turn can set it and * the seed cannot carry it. Comparing it would mark every stored record that * has one as changed, which is exactly the loss this prevents. * - Records the turn removed are absent from the fold and stay absent; a * record the turn added has no prior and is the fold's own. */ export declare function keepUnchangedThreadMemory(priorView: readonly AgMemoryRecord[], folded: readonly AgMemoryRecord[]): AgMemoryRecord[]; /** * Synthetic events that seed a reducer with prior latest-replace state before * folding a turn (design §8.1). Seeds state + thread-memory ONLY — never * messages/artifacts/turns — so result() yields this turn's append-delta while * carrying cumulative state/memory. Contiguous seqs 0..K (no internal gap); the * live stream that follows uses its own per-message 0-based seqs (backward * jumps, which the reducer tolerates). Push these into the reducer but never * emit them to the client. * * Each synthetic `memory.write` carries `turnId` so the reducer's SET handler * lands it back onto the AgMemoryRecord — without it, a re-seeded thread-memory * record loses its turnId and memory byte-identity breaks from turn 2 on. * It carries the record's `_meta` the same way: AgJSON draft.8 declares * `_meta` on both the record and `memory.write`, and the reducer folds it * onto the record (set on create, replaced on update), so a record's host * side-channel (e.g. `_meta.provenance`) survives every re-seeded turn. * (`threadId` is NOT carried: `memory.write` has no threadId on its event arm, * so it cannot round-trip through a live write — that is expected.) */ export declare function seedEventsForReducer(priorState: JsonValue | undefined, priorThreadMemory: AgMemoryRecord[]): AgEvent[]; //# sourceMappingURL=fold-rows.d.ts.map