import { Emitter, Event } from "@codingame/monaco-vscode-api/vscode/vs/base/common/event"; import { Disposable, IReference } from "@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle"; import { IObservable } from "@codingame/monaco-vscode-api/vscode/vs/base/common/observable"; import { URI } from "@codingame/monaco-vscode-api/vscode/vs/base/common/uri"; import { ActionEnvelope, IRootConfigChangedAction, SessionAction, StateAction } from "./sessionActions.js"; import type { TerminalAction } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/action-origin.generated"; import type { RootState, SessionState, TerminalState } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/state"; import type { IStateSnapshot } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/sessionProtocol"; import { StateComponents } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/sessionState"; /** * A read-only subscription to an agent host resource (root, session, or terminal). * * Subscriptions are hydrated from an initial server snapshot and kept in sync * via action envelopes. Session subscriptions support write-ahead * reconciliation — optimistic state is layered on top of confirmed state. */ export interface IAgentSubscription { /** * The current state value. For write-ahead subscriptions (sessions) this * reflects the optimistic state (confirmed + pending replayed). For * server-only subscriptions (root, terminal) this equals `verifiedValue`. * * `undefined` until the first snapshot arrives. An `Error` if subscription * failed. */ readonly value: T | Error | undefined; /** * The server-confirmed state with no pending optimistic actions applied. * `undefined` until the first snapshot arrives. */ readonly verifiedValue: T | undefined; /** Fires when {@link value} changes (optimistic or confirmed). */ readonly onDidChange: Event; /** Fires before a server-originated action is applied to this subscription's state. */ readonly onWillApplyAction: Event; /** Fires after a server-originated action is applied to this subscription's state. */ readonly onDidApplyAction: Event; } /** * Base class for agent subscriptions. Handles envelope reception, confirmed * state management, and action event emission. * * Subclasses provide the reducer and optionally override reconciliation * behavior. */ declare abstract class BaseAgentSubscription extends Disposable implements IAgentSubscription { protected _confirmedState: T | undefined; private _error; private _bufferedEnvelopes; protected readonly _onDidChange: Emitter; readonly onDidChange: Event; protected readonly _onWillApplyAction: Emitter; readonly onWillApplyAction: Event; protected readonly _onDidApplyAction: Emitter; readonly onDidApplyAction: Event; protected readonly _clientId: string; protected readonly _log: (msg: string) => void; constructor(clientId: string, log: (msg: string) => void); get value(): T | Error | undefined; get verifiedValue(): T | undefined; /** * Apply an initial snapshot from the server. */ handleSnapshot(state: T, fromSeq: number): void; /** * Mark this subscription as failed. */ setError(error: Error): void; /** * Process an incoming action envelope. The subscription determines * whether the action is relevant via {@link _isRelevantAction}. */ receiveEnvelope(envelope: ActionEnvelope): void; /** Apply the reducer to confirmed state. Subclasses must implement. */ protected abstract _applyReducer(state: T, action: StateAction): T; /** Whether the given action targets this subscription. */ protected abstract _isRelevantAction(action: StateAction): boolean; /** Return optimistic state if write-ahead is active, otherwise `undefined`. */ protected _getOptimisticState(): T | undefined; /** Hook called after a snapshot is applied. Replays buffered actions. */ protected _onSnapshotApplied(_fromSeq: number): void; /** * Default reconciliation: apply to confirmed, fire change event. * Session subscriptions override this for write-ahead. */ protected _reconcile(envelope: ActionEnvelope, _isOwnAction: boolean): void; } /** * Subscription to the root state at `agenthost:/root`. * Server-only mutations — no write-ahead. */ export declare class RootStateSubscription extends BaseAgentSubscription { protected _applyReducer(state: RootState, action: StateAction): RootState; protected _isRelevantAction(action: StateAction): boolean; } interface IPendingAction { readonly clientSeq: number; readonly action: SessionAction; } export interface IPendingSessionAction extends IPendingAction { /** URI of the session this action targets, as stored on the subscription. */ readonly sessionUri: string; } /** * Subscription to a session at `copilot:/`. * Supports write-ahead reconciliation for client-dispatchable actions. */ export declare class SessionStateSubscription extends BaseAgentSubscription { private readonly _pendingActions; private _optimisticState; private readonly _sessionUri; private readonly _seqAllocator; constructor(sessionUri: string, clientId: string, seqAllocator: () => number, log: (msg: string) => void); /** * Optimistically apply a session action. Returns the clientSeq to send * to the server so it can echo back for reconciliation. */ applyOptimistic(action: SessionAction): number; protected _getOptimisticState(): SessionState | undefined; protected _applyReducer(state: SessionState, action: StateAction): SessionState; protected _isRelevantAction(action: StateAction): boolean; protected _onSnapshotApplied(fromSeq: number): void; protected _reconcile(envelope: ActionEnvelope, isOwnAction: boolean): void; private _confirmedApply; private _recomputeOptimistic; /** * Clear pending actions for this session (e.g., on unsubscribe). */ clearPending(): void; /** * Snapshot of the currently-pending optimistic actions, with the session * URI included so callers can re-issue them across a reconnect. The * actions remain in the subscription so the optimistic state continues * to reflect them — the client must explicitly drop entries echoed back * by the server. */ getPendingActions(): IPendingSessionAction[]; /** * Drop the pending entry whose `clientSeq` matches the supplied value. * Used during reconnect to evict actions the server already echoed back * in the replay buffer so they're not resent. */ dropPendingByClientSeq(clientSeq: number): boolean; } /** * Subscription to a terminal at an agent-host terminal URI. * Server-only mutations — no write-ahead (terminal I/O is side-effect-only). */ export declare class TerminalStateSubscription extends BaseAgentSubscription { private readonly _terminalUri; constructor(terminalUri: string, clientId: string, log: (msg: string) => void); protected _applyReducer(state: TerminalState, action: StateAction): TerminalState; protected _isRelevantAction(action: StateAction): boolean; } /** * Manages the lifecycle of resource subscriptions for an agent connection. * * Provides refcounted access via {@link getSubscription} — the subscription * is created on first acquire, subscribes to the server, and stays alive * until the last reference is disposed. * * The connection feeds action envelopes to all active subscriptions via * {@link receiveEnvelope}. */ export declare class AgentSubscriptionManager extends Disposable { private readonly _subscriptions; private readonly _rootState; private readonly _clientId; private readonly _seqAllocator; private readonly _log; private readonly _subscribe; private readonly _unsubscribe; constructor(clientId: string, seqAllocator: () => number, log: (msg: string) => void, subscribe: (resource: URI) => Promise, unsubscribe: (resource: URI) => void); /** The always-live root state subscription. */ get rootState(): IAgentSubscription; /** * Initialize the root state from a snapshot received during the * connection handshake. */ handleRootSnapshot(state: RootState, fromSeq: number): void; /** * Returns an existing subscription without affecting its refcount. * Returns `undefined` if no subscription is active for the given resource. */ getSubscriptionUnmanaged(resource: URI): IAgentSubscription | undefined; /** * Get or create a refcounted subscription to any resource. Disposing * the returned reference decrements the refcount; when it reaches zero * the subscription is torn down and the server is notified. */ getSubscription(kind: StateComponents, resource: URI): IReference>; /** * Route an incoming action envelope to all active subscriptions. */ receiveEnvelope(envelope: ActionEnvelope): void; /** * Dispatch a client action. Applies optimistically to the relevant * subscription if applicable, then returns the clientSeq. */ dispatchOptimistic(action: SessionAction | TerminalAction | IRootConfigChangedAction): number; /** * URIs currently subscribed to via {@link getSubscription}. Used to * build the `subscriptions` payload for a `reconnect` RPC so the * server can restore them in one round-trip. * * Does NOT include the always-live root state, which the protocol * client manages separately. */ currentSubscriptionUris(): URI[]; /** * Snapshot of every pending optimistic action across all session * subscriptions. Callers use this to replay actions after a transport * reconnect; entries are kept on their subscriptions until they're * either echoed back by the server or explicitly dropped via * {@link dropPendingSessionAction}. */ getPendingSessionActions(): IPendingSessionAction[]; /** * Remove a single pending optimistic action for a session by its * `clientSeq`. Used during reconnect to evict actions the server * already processed (and replayed back to us) so they're not resent. */ dropPendingSessionAction(sessionUri: string, clientSeq: number): void; /** * Apply a fresh snapshot to a subscribed resource — used when the server * responds to a `reconnect` request with `type: 'snapshot'` because the * replay buffer no longer covers the client's gap. Routes to the root * subscription when {@link ROOT_STATE_URI} matches, otherwise reseats the * matching entry in {@link _subscriptions}. Unknown resources are ignored. */ applyReconnectSnapshot(resource: URI, state: unknown, fromSeq: number): void; /** * Mark a set of subscriptions as no longer resumable on the server * (reported via `ReconnectReplayResult.missing`). The subscriptions * themselves stay alive so consumers continue to hold valid references, * but their value transitions to an `Error` until they're recreated. */ markSubscriptionsMissing(missing: readonly URI[]): void; private _createSubscription; private _releaseSubscription; dispose(): void; } /** * Adapts an {@link IAgentSubscription} into an {@link IObservable} of the * subscription's value. Errors and the pre-snapshot phase are surfaced as * `undefined`; consumers that need the error itself should read * {@link IAgentSubscription.value} directly. */ export declare function observableFromSubscription(owner: object | undefined, sub: IAgentSubscription): IObservable; export {};