/** * hosting/envelope — pack a session for storage, and refuse to unpack one you * cannot read. * * One rule, and everything here is a consequence of it: **an envelope this * runtime cannot read is refused BY NAME, never guessed at.** A store outlives * the code that wrote to it. Somebody will deploy a newer runtime, it will write * a newer format, and an older instance still running will read it. The only * honest thing that older instance can do is say which format it found, which * ones it knows, and stop — because "restore what I can and hope" means an agent * answering from a session that is missing whatever the older reader did not * understand. * * ── Two formats, two readers, and why they refuse each other ───────────────── * `readEnvelope` unpacks a CONVERSATION; `readPausedRun` unpacks a PAUSED RUN. * Each refuses the other's format by name and points at its sibling. That looks * fussy until you notice the alternative: one reader that quietly returned the * conversation inside a paused run would hand back a session that LOOKS finished * while a person is still waiting on a question nobody mentioned. That is a * half-restore wearing a happy path, which is the exact failure the format field * exists to prevent. * * ── The same rule one step earlier: unreadable ≠ absent ────────────────────── * A format nobody knows is one way a store can hand back something unusable. * The other is bytes that are not an envelope at all — a store that kept its own * encoding and gave back its host language's `toString()` of the object rather * than the object, say. **An unreadable stored conversation and an absent one * are different facts, and only one of them is safe to answer with a fresh * start.** Absent is ordinary. Unreadable means a conversation EXISTS and this * runtime cannot see it, and answering that with a fresh start is invisible from * the outside — it looks exactly like a new user — until a deployment boundary * hands somebody a blank slate where their conversation was. * * So {@link readFormat} — the ONE place a stored value is inspected, and * therefore the place every reader and every store adapter inherits from — * raises {@link UnreadableEnvelopeError} by name, with the session and a short * prefix of what it found. Decoding is an adapter's business (only the adapter * knows its transport's encoding); "these bytes are nobody's readable session" * is everybody's, so it is refused here, once. */ import { type AgentRunCheckpoint } from '../core/runCheckpoint.js'; import type { CheckpointEnvelope, ConversationEnvelope, PausedRun, PausedRunEnvelope } from './types.js'; import type { TranscriptMessage } from './sessionWire.js'; /** * Pack a conversation checkpoint for storage. * * @example * const conversation = agent.checkpoint(); * if (conversation) await sessions.persist(sessionId, toEnvelope(conversation)); */ export declare function toEnvelope(checkpoint: AgentRunCheckpoint): ConversationEnvelope; /** * Pack a paused run for storage — the engine checkpoint, the conversation as of * the pause, and the question it is waiting on. * * Store it anywhere that speaks JSON. Note what JSON does and does not preserve * here: `agent.resume()` reads `checkpoint.sharedState`, which round-trips * unchanged; the engine's diagnostic halves lose their explicitly-`undefined` * properties, because that is what `JSON.stringify` does to them. See * {@link PausedRun}. * * @example * const outcome = await agent.run({ message }); * if (isPaused(outcome)) { * await sessions.persist(sessionId, toPausedEnvelope({ * checkpoint: outcome.checkpoint, * conversation: agent.checkpoint()!, * pending: { pauseData: outcome.pauseData }, * })); * } */ export declare function toPausedEnvelope(paused: PausedRun): PausedRunEnvelope; /** * Unpack a stored envelope back into a conversation checkpoint. * * Takes `unknown` on purpose: what comes back from a store is bytes somebody * else wrote, in a format this runtime may not know, and typing the parameter * as the happy shape would be assuming the very thing that needs checking. * * @throws UnreadableEnvelopeError when what came back is present but is not an * envelope at all — bytes a store kept in its own encoding, say. Never * `undefined`: a conversation that exists and cannot be read is not the same * fact as one that was never there. * @throws TypeError naming the format when it is one this runtime cannot read; * naming the missing field when the conversation inside is malformed; and * pointing at {@link readPausedRun} when the envelope holds a paused run, * which is a session with a question outstanding rather than a conversation. */ export declare function readEnvelope(envelope: unknown): AgentRunCheckpoint; /** * Unpack a stored envelope back into a paused run. * * @throws TypeError naming the format when it is one this runtime cannot read; * pointing at {@link readEnvelope} when the envelope holds a plain * conversation; and naming the missing field when the paused run inside is * malformed. */ export declare function readPausedRun(envelope: unknown): PausedRun; /** * Check that an envelope is one this runtime can read, and hand it back * unchanged — without committing to which half you wanted. * * This is what a STORE wants. A store's job is to notice that the bytes it is * about to hand over are unreadable, so the refusal names the store that * produced them rather than whoever read them next; it has no business caring * whether the session inside is mid-conversation or mid-question. * * Pass `sessionId` and the refusal names the conversation, which is what turns * a log line into an incident somebody can act on. Pass it whenever you have it * — a store always does. * * @throws UnreadableEnvelopeError when the stored value is present but is not an * envelope at all. That is the case a store must NOT answer with `undefined`: * see the module header. * @throws TypeError naming the format when this runtime cannot read it, or the * missing field when the payload is malformed. * * @example * async hydrate(sessionId) { * const stored = await myStore.get(sessionId); * return stored === undefined ? undefined : checkEnvelope(stored, sessionId); * } */ export declare function checkEnvelope(envelope: unknown, sessionId?: string): CheckpointEnvelope; /** * WHO a stored session belongs to — the `principal` on the conversation the * envelope carries, or `undefined` when it names nobody (9.26.0). * * The ONE derivation, so a store's owner index and the composer's ownership * check can never disagree about what "belongs to" means. It is read from the * conversation's own identity — the tuple `standingAgent` composed from the * request's verified user — and NOT from anything a caller supplied at persist * time: a store that indexed on a claimed owner would be a store where owning * a session is a matter of asking. * * A conversation that ran anonymously, or under a derived session-namespace * rung with no principal, has no owner. That is a FACT, not a gap to fill: a * session nobody signed for is a session nobody can list, and inventing an * owner would be inventing an entitlement. * * **Status: the CONTRACT is field-validated — an independent field trial, * 2026-08-13.** A live multi-user run against a real Firestore: an owner * written once and never moved, a cross-user transcript answering the same * not-found a missing session gets, one caller's own two sessions listed over * two pages, `listByUser` / `ownerOf` feature detection, and `mayOpenSession` * refusing an ordinary turn on somebody else's conversation. The store * underneath was **the trial's own Firestore `SessionLifecycle`**, not one this * package ships — so the rung belongs to this derivation and the rules above * it, and no shipped Google adapter may borrow the credit. * * Reads both formats, so a session that is mid-question is owned exactly as * firmly as one that is finished. Returns `undefined` rather than throwing on * a shape it cannot read — an index is a convenience, and refusing to WRITE a * conversation because its owner could not be derived would be the tail * wagging the dog. (Every reader of the index still checks ownership itself.) */ export declare function envelopeOwner(envelope: unknown): string | undefined; /** * The stored conversation's messages, projected to what a person may read back * (9.26.0): user and assistant TEXT, in stored order. * * Everything else is deliberately dropped — see {@link TranscriptMessage} for * the full list and the reason. Non-string content (a provider's structured * block) is dropped rather than stringified: a transcript is what was said, and * `[object Object]` is not something anybody said. */ export declare function envelopeTranscript(envelope: unknown): readonly TranscriptMessage[];