/** * Maps OpenCode session records to AG-UI events. * * **The mapper is a transition machine over per-part state, not a pure function of a record**, for * the reason §3.2 gives: the bus re-emits `message.part.updated` for one part id as it streams. It * is fed from {@link OpenCodeSessionSource}, which hands over only SETTLED parts, in order, so the * transitions here are the ones that survive: a part arrives once, finished. * * **NO USER-AUTHORED TEXT IS EMITTED, EVER (§3.2, orchestrator ruling).** OpenCode injects a peer * batch by prepending it into the human's own text part, so a single part holds peer-authored and * human-authored content with no boundary in the record to filter on. The unit of authorship is * smaller than the smallest unit the store keeps. Content-parsing the injected header back out is * ruled out and is not to be built, prototyped, or left in a comment as an option: it would fail * OPEN the moment either formatter changed a character, and a boundary that depends on two * formatters agreeing forever is not a boundary. So user messages map to nothing at all. * * **REMOVAL NEEDS NO HOOK HERE, AND THAT IS A CONSEQUENCE RATHER THAN AN OVERSIGHT.** The ruling on * a reverted session is: publish only on finality, drop the per-part state for the vanished ids, log * the divergence once, keep going. Because the source hands over only settled parts, this mapper * holds no per-part state to drop: a part arrives once, complete, and is mapped in one shot. So the * divergence log is the source's, and there is nothing here for a revert to invalidate. */ import { type AguiEvent, type RecordMapper } from "@cotal-ai/connector-core"; import type { OpenCodeRecord } from "./agui-source.js"; export interface OpenCodeMapperOptions { /** The native session id. It is the `threadId` on every event and nothing else may claim it. */ threadId: string; /** Mints a `runId`. Connector-minted, so every `RUN_STARTED` carries `runIdSource: "connector"`. */ mintRunId: () => string; /** Emit `REASONING_*` for reasoning parts. Off by default (§7 Q1). */ reasoning?: boolean; /** Arrival clock, for the records carrying no usable source stamp. Injectable for determinism. */ now?: () => number; } export interface OpenCodeMapper { map: RecordMapper; /** * Close the open run at a boundary the record stream cannot see: `session.idle`, which §3.2 makes * the flush boundary. Returns `null` when nothing is open, so calling it twice cannot manufacture * a second `RUN_FINISHED` for the bracket machine to refuse. */ closeOpenRun: (timestamp: number, stopReason?: string) => { runId: string; events: AguiEvent[]; } | null; /** The run currently open, or `null`. */ openRun: () => string | null; /** * Forget a run the emitter closed out of band. KEYED ON THE ID: the report can arrive after this * mapper has already opened a newer run, and clearing unconditionally would orphan that one, whose * events would then emit under no run at all and halt a session that had done nothing wrong. */ forgetOpenRun: (runId: string) => void; /** Why this session opened no runs, or `null` once one has. A silent refusal is the defect. */ diagnose: () => string | null; } export declare function createOpenCodeMapper(opts: OpenCodeMapperOptions): OpenCodeMapper; //# sourceMappingURL=agui-map.d.ts.map