/** * A {@link DurableSource} over an OpenCode session, read through the SDK's own * `session.messages()` surface rather than the SQLite file behind it. * * **Why not the database.** `~/.local/share/opencode/opencode.db` is OpenCode's private store: its * schema migrates, and reading it would couple this plane to internals no contract covers. The * measurements in this file's comments were taken from a copy of that store because it is the only * corpus large enough to answer the questions honestly; the SHIPPED read path is the API. * * **The cursor is the PAIR `messageId:partId`, and the pair is not decoration.** Part ids are * monotonic within a message (measured: 0 inversions over 14 915 parts). They are NOT monotonic * across a session: ordering every part by (message creation, message id, part id) produces 16 * inversions over 14 759 ordered pairs, every one of them the first part of a USER message whose id * is lower than the last part of the assistant message before it, because the prompt's text part is * created while the assistant's final part is still being written. Restricted to the assistant-only * stream that ยง3.2's authorship ruling leaves us with, the count is 0 over 14 084. So a bare * `part.id` cursor would be sound only for as long as an unrelated safety ruling keeps user parts * out, which is a dependency nobody would remember. The pair is sound either way and costs nothing. * * **Removal is tolerated by construction.** The cursor is compared as an ORDER, never looked up as * an identity, so a session whose parts were reverted away resumes from the same position without * the record it names having to still exist. That is what makes the ruling implementable: publish * on finality, and treat a later removal as a logged divergence rather than a halt. */ import type { DurableSource, SourceRead } from "@cotal-ai/connector-core"; /** The message fields the mapper needs. Deliberately not the whole `Message`: the source hands on * identity, authorship and the turn-level completion mark, and nothing it does not read. */ export interface OpenCodeMessageInfo { id: string; role: string; time?: { created?: number; completed?: number; }; } /** One record: a part, plus the message that owns it. The mapper needs both, because authorship and * the turn-level finality backstop live on the message, not on the part. */ export interface OpenCodeRecord { part: OpenCodePart; message: OpenCodeMessageInfo; } /** * The part shape this source reads. It is a STRUCTURAL subset of the SDK's `Part` union, declared * here rather than imported, for the same reason the Claude mapper declares its own entry type: the * SDK's type describes what OpenCode may write, and this describes what we actually read. Importing * the union would make an SDK bump a compile error in a file that does not care about the fields * that changed. */ export interface OpenCodePart { id: string; messageID: string; type: string; text?: string; synthetic?: boolean; ignored?: boolean; callID?: string; tool?: string; state?: { status?: string; input?: unknown; output?: string; error?: string; time?: { start?: number; end?: number; }; }; time?: { start?: number; end?: number; }; cost?: number; tokens?: unknown; } /** One entry of `session.messages()`: the message and its parts. */ export interface OpenCodeMessageWithParts { info: OpenCodeMessageInfo; parts: OpenCodePart[]; } /** Reads the whole session. Injected so the source can be exercised without a live server. */ export type ReadSessionMessages = () => Promise; /** * Is this part settled, so that emitting from it now cannot be contradicted later? * * The question matters because parts are mutated in place long after they appear: 11 518 of 14 915 * carry `time_updated > time_created`, and 1 657 of the 11 298 that have a successor were updated * AFTER their successor already existed, by up to 29.5 minutes. "A later part exists" therefore * does NOT mean an earlier one is done, which is the design this measurement killed. * * The turn-level backstop is what stops a stream from wedging on a part that never gets its own end * mark. It is sound in normal operation: the only parts in the corpus updated after their message * reported completed are 830 rows rewritten inside a single 150 ms window, ~16 to 20 per * millisecond, which is a bulk write rather than session behaviour. It matches no timestamp in the * store's migration tables, so it is provably bulk and NOT attributable to a named process. */ export declare function isSettled(record: OpenCodeRecord): boolean; /** Serialise the ordering key. Ids carry no `:`, so the first one separates the halves. */ export declare function cursorOf(record: OpenCodeRecord): string; export interface OpenCodeSessionSourceOptions { /** Reads the session. */ read: ReadSessionMessages; /** * Called ONCE per read in which the cursor's own record is no longer present, with the cursor * that vanished. This is the divergence a revert produces, and it is reported rather than thrown: * a user pressing revert is a legitimate session action, and an emitter that died on it would * fail the deliverable. Ordering makes the read itself correct without this; the callback exists * so the divergence is visible instead of silent. */ onVanished?: (cursor: string) => void; } export declare class OpenCodeSessionSource implements DurableSource { readonly kind = "opencode-session"; private readonly read0; private readonly onVanished?; /** The last cursor already reported as vanished. A revert leaves the cursor absent on EVERY read * until it advances past the removed region, so reporting per read would turn one divergence * into a log flood and bury the next, different one. */ private reportedVanished?; constructor(opts: OpenCodeSessionSourceOptions); read(cursor: string | undefined): Promise>; /** * The contiguous run of settled records from the front, and the cursor of the last of them. * * **STOPPING AT THE FIRST UNSETTLED RECORD IS THE WHOLE POINT**, and it is the same rule * `JsonlFileSource` applies to a half-written line: consume up to the last complete unit, leave * the rest for the next read. Emitting AROUND a part that is still filling would publish frames * out of the order the brackets require, and advancing past it would drop it for good, because * nothing ever revisits a cursor. The cost is head-of-line blocking within a session, which is * the honest price of ordered brackets rather than an oversight. */ private settledPrefix; } //# sourceMappingURL=agui-source.d.ts.map