/** * Transcript accumulator * * Turns an `SDKMessage` stream into stable, render-ready rows so consumers stop * hand-rolling stream reconciliation. It owns the four reconciliation rules the * wire protocol requires: * * 1. Typed-by-family accumulation. Text slices are keyed on * `message family + otid`, falling back to `uuid` *within the same family*. * A bare `otid`/`uuid` key would collapse an assistant slice into a * reasoning slice whenever a provider reuses an identifier across kinds. * 2. Per-`runId` `seqId` replay suppression. Each run keeps its own high-water * mark, so a resumed stream that replays positions is dropped while a new * run starts from a clean threshold. * 3. `toolCallId`-keyed merging. Tool argument fragments and the eventual tool * result merge into one row keyed on the payload identity (`toolCallId`), * while the envelope identities (the `uuid` of the `tool_call` message and * of the `tool_result` message) stay separately visible. * 4. `rebase()` for mid-run backfill. A history page is merged in place with * replace semantics, reordered ahead of live-only rows, and raises the * replay thresholds it proves. * * The accumulator is pure and portable: no I/O, no timers, no Node built-ins, * so it is exported from both the package root and `/client`. */ import type { Message as LettaMessage } from "@letta-ai/letta-client/resources/agents/messages"; import type { SDKMessage } from "./types.js"; /** Message families the accumulator projects into rows. */ export type TranscriptRowKind = "user" | "assistant" | "reasoning" | "tool_call"; /** Text families. Rows in different families never share a key. */ export type TranscriptTextKind = "user" | "assistant" | "reasoning"; export interface TranscriptRowIdentity { /** * Stable render key. Namespaced by message family, so a provider that reuses * an `otid` or a message id across kinds still produces separate rows. */ key: string; /** Envelope id of the message that opened this row, when known. */ uuid?: string; /** Lineage key for this typed slice, when the stream supplied one. */ otid?: string; /** Run that most recently contributed to this row. */ runId?: string; /** Highest replay cursor observed for this row. */ seqId?: number; } export interface TranscriptTextRow extends TranscriptRowIdentity { kind: TranscriptTextKind; /** Accumulated text for this slice. */ text: string; } export interface TranscriptToolResult { content: string; isError: boolean; /** * Envelope id of the `tool_result` message. Deliberately distinct from the * row's `uuid`, which identifies the `tool_call` envelope. */ uuid?: string; } /** * Lifecycle of a tool row. * * - `streaming`: argument fragments are still arriving and have not parsed. * - `ready`: arguments parsed; the result has not arrived. * - `complete`: a tool result merged into the row. */ export type TranscriptToolCallStatus = "streaming" | "ready" | "complete"; export interface TranscriptToolCallRow extends TranscriptRowIdentity { kind: "tool_call"; /** Payload identity. This is what the row is keyed on. */ toolCallId: string; toolName: string; /** * Best known parsed arguments. Never the transitional `{ raw }` wrapper the * protocol layer emits for an argument fragment that does not parse. */ toolInput: Record; /** Argument fragments concatenated in arrival order, when the wire sent any. */ rawArguments?: string; /** Whether {@link toolInput} reflects fully parsed arguments. */ argumentsComplete: boolean; result?: TranscriptToolResult; status: TranscriptToolCallStatus; } export type TranscriptRow = TranscriptTextRow | TranscriptToolCallRow; /** * A history page accepted by {@link TranscriptAccumulator.rebase}. Covers * `session.listMessages()`, `session.bootstrapState()`, and a bare array of * Letta API messages. */ export type TranscriptHistoryPage = { messages: readonly LettaMessage[]; } | readonly LettaMessage[]; export interface TranscriptRebaseOptions { /** * Order of the supplied page. Omitted means auto-detect from `seq_id`/`date`; * `listMessages()` defaults to `"desc"` (newest first). */ order?: "asc" | "desc"; } export interface TranscriptAccumulator { /** * Fold one streamed message into the transcript and return the current rows. * * The returned array is referentially stable when the message changed * nothing (a replayed position, or a message family the accumulator ignores), * so it can be handed straight to a memoizing renderer. */ apply(message: SDKMessage): readonly TranscriptRow[]; /** Merge a history page into the transcript. Safe to call mid-run. */ rebase(page: TranscriptHistoryPage, options?: TranscriptRebaseOptions): readonly TranscriptRow[]; /** Current rows in transcript order. */ rows(): readonly TranscriptRow[]; /** Drop all rows and replay state. */ reset(): void; } /** * Create a transcript accumulator. * * @example * ```typescript * const acc = createTranscriptAccumulator(); * for await (const message of session.stream()) { * render(acc.apply(message)); * } * * // Safe mid-run: merges older history without duplicating live rows. * acc.rebase(await session.listMessages({ limit: 50 })); * ``` */ export declare function createTranscriptAccumulator(): TranscriptAccumulator; //# sourceMappingURL=transcript-accumulator.d.ts.map