import { type SessionEvent, type SessionStatus, type StreamConnectionState } from "@opengeni/sdk"; import { type EmbeddedSessionClientOverride } from "../session-context.js"; import { type OlderHistoryLoadReceipt } from "../older-history.js"; import type { TimelineItem } from "../timeline/types.js"; import type { EmbeddedSessionClientLike } from "../client.js"; import type { LatestQuestionOptions } from "./latest-question.js"; export type SessionEventsConnectionState = StreamConnectionState | "idle" | "ended" | "error"; export type UseSessionEventsOptions = EmbeddedSessionClientOverride & { /** Resume after this sequence (exclusive). Nonzero keeps full replay/resume semantics. */ after?: number | undefined; /** Load a bounded tail by default, or opt back into full replay from `after`. */ replay?: "windowed" | "full" | undefined; /** Pause the stream without unmounting (e.g. hidden tab). Defaults to true. */ enabled?: boolean | undefined; }; export type UseSessionEventsResult = { /** * Loaded events, ordered by sequence, no duplicates. Windowed history holds a * session's conversation; a turn's steps (tool calls and outputs, streamed * reasoning and command output) are included while live and once a reader * opens that turn. Full replay holds every event. */ events: SessionEvent[]; /** Renderable timeline, including the bounded navigation witness for a distant queued prompt. */ timeline: TimelineItem[]; /** Latest session status observed in the event log, if any. */ sessionStatus: SessionStatus | null; /** Sequence of the retained status projection, including events evicted from the window. */ sessionStatusSequence?: number; connectionState: SessionEventsConnectionState; /** Highest sequence seen so far (0 before the first event). */ lastSequence: number; /** Exact serialized bytes retained in the current browser event window. */ windowBytes: number; /** Whether older delivered events were evicted from the browser window. */ windowTruncated: boolean; /** True until the initial tail window has been applied (windowed mode). */ initialLoading: boolean; /** A history window (including an empty tail) succeeded for this session/replay * identity. Stays true through later stream errors and navigation/reloads. * Full replay has no snapshot-completion watermark and does not set this by itself. */ initialHistoryReady: boolean; /** Whether older durable events are available before the current window. */ hasOlder: boolean; /** True while an older window is being fetched. */ loadingOlder: boolean; /** Prepend an older density-bounded window; resolves true when more remain. */ loadOlder: () => OlderHistoryLoadReceipt; /** * Durable events exist after the current window (history view jumped away * from the live tip, or a forward page is incomplete). */ hasNewer: boolean; /** True while a newer history page is being fetched. */ loadingNewer: boolean; /** * Append one density-bounded newer page without pulling the whole gap. * Resolves true when more newer history remains. */ loadNewer: () => Promise; /** True while replacing the window with the session start. */ loadingOldest: boolean; /** * Jump to the durable session start: one bounded oldest window, no middle. * Leaves live streaming until {@link jumpToLatest} (or loadNewer catches up). */ loadOldest: () => Promise; /** True while reloading the live tip window. */ loadingLatest: boolean; /** * Reload the newest bounded tip and resume live streaming. Use for * "Jump to latest" when the tip is not in memory (history view). */ jumpToLatest: () => Promise; /** Resolve the newest eligible durable human question. Pending prompts use the * optional queue destination and return null; started prompts load their actual * turn context (pass `timeline` to MessageTimeline as `items`). */ jumpToLatestQuestion: (options?: LatestQuestionOptions) => Promise; /** Replace history with a bounded window containing this exact durable event. */ jumpToSequence: (sequence: number, options?: { signal?: AbortSignal; }) => Promise; loadingTarget: boolean; error: Error | null; }; export declare const REJOIN_STALL_TIMEOUT_MS = 30000; export declare const SESSION_EVENT_BROWSER_MAX_BYTES: number; export declare const SESSION_EVENT_BROWSER_MAX_COUNT = 20000; export declare const SESSION_EVENT_BROWSER_PENDING_MAX_BYTES: number; export declare const SESSION_EVENT_BROWSER_PENDING_MAX_COUNT = 256; export type BrowserSessionEventWindow = { events: SessionEvent[]; bytes: number; truncated: boolean; }; /** * Live-stream a session's event log with replay-by-sequence, reconnect, and * batched React updates. Fresh loads default to a bounded tail window; pass * `replay: "full"` or a nonzero `after` for the previous full replay path. */ export declare function useSessionEvents(sessionId: string | null | undefined, options?: UseSessionEventsOptions): UseSessionEventsResult; /** * Keep one direction-aware count+byte-bounded browser window. Live/default * accumulation retains the newest suffix; backward paging retains the oldest * prefix so newly fetched history cannot be immediately evicted. This is * deliberately separate from durable history and transport paging: when a * backward page evicts the live tail, the hook enters bounded history mode and * preserves the highest-ever-observed sequence separately for forward paging. * The source event remains durable in PostgreSQL throughout. An event larger * than the byte target is retained alone, never replaced by a lossy preview. */ export declare function boundBrowserSessionEventWindow(events: readonly SessionEvent[], options?: { maxBytes?: number; maxCount?: number; direction?: "newest" | "oldest"; }): BrowserSessionEventWindow; /** @internal Append immutable events without serializing the retained history again. */ export declare function appendBrowserSessionEventWindow(current: BrowserSessionEventWindow, batch: readonly SessionEvent[], options?: { maxBytes?: number; maxCount?: number; }): BrowserSessionEventWindow; export declare const SESSION_TURN_STEP_PAGE_SIZE = 200; export declare const SESSION_TURN_LIVE_STEP_TAIL = 200; export declare const SESSION_STEP_BROWSER_MAX_BYTES: number; /** * @internal Whether an event belongs in the conversation window. With * `startupTurns` (the turns whose model has not responded yet), a turn's * bookkeeping is a step except its model request rows up to that response. */ export declare function isConversationHistoryEvent(event: SessionEvent, presentedCallIds: ReadonlySet, startupTurns?: ReadonlySet): boolean; type ConversationFilterMode = "v2" | "compat"; /** * @internal How the next conversation read of `client` filters. A client * without `listEventPage` cannot see the server's announcement. */ export declare function conversationFilterMode(client: EmbeddedSessionClientLike): ConversationFilterMode; type TurnBounds = { /** The turn's first window event (normally `turn.started`). */ first: number; /** The turn's `turn.started`, when the window holds it. */ started: number | null; /** * The last sequence of another turn before this turn's first window event * (0 when none). Turnless rows (queue changes, PTY output) do not count: * the turn's steps can sit below them. */ previous: number; /** The turn's settlement, when the window holds it. */ terminal: number | null; /** Steps hidden between the turn's window events. */ hidden: boolean; }; /** * Each turn's extent in the conversation window and whether sequences inside * it are absent: those are steps (or other unread rows) a reader can load. * Compacted events cover `[sequence, coveredThrough]`. */ export declare function conversationTurnBounds(events: readonly SessionEvent[]): Map; /** The newest turn in the window that has started but not settled. */ export declare function activeConversationTurn(events: readonly SessionEvent[]): string | null; export {};