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, ChatAction, AnnotationsAction, ClientAnnotationsAction, 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 { AnnotationsState, ChangesetState, ChatState, 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 when the subscription enters an error state. */ readonly onDidError?: 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; } /** * Read-only snapshot describing a single active resource subscription. Used by * inspection/debug surfaces that enumerate everything a connection is currently * subscribed to. Does not include the always-live root state. */ export interface IActiveSubscriptionInfo { /** The protocol resource URI subscribed to. */ readonly resource: URI; /** Which state component this subscription tracks. */ readonly kind: StateComponents; /** Number of outstanding {@link IReference} holders. */ readonly refCount: number; /** * The named owners currently holding a reference to this subscription, * with how many references each holds. Names come from the `owner` * argument passed to {@link AgentSubscriptionManager.getSubscription}. */ readonly holders: readonly IActiveSubscriptionHolder[]; /** * Lifecycle status derived from the subscription's value: * `pending` before the first snapshot, `error` if it failed, otherwise * `snapshot`. */ readonly status: "pending" | "snapshot" | "error"; } /** A named owner holding one or more references to a subscription. */ export interface IActiveSubscriptionHolder { readonly owner: string; readonly count: number; } /** * 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 _onDidError: Emitter; readonly onDidError: 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 _isRelevantEnvelope}. */ receiveEnvelope(envelope: ActionEnvelope): void; /** Apply the reducer to confirmed state. Subclasses must implement. */ protected abstract _applyReducer(state: T, action: StateAction): T; /** Whether the given envelope targets this subscription. */ protected abstract _isRelevantEnvelope(envelope: ActionEnvelope): 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 _isRelevantEnvelope(envelope: ActionEnvelope): boolean; } /** * A pending optimistic action awaiting server confirmation, paired with the * channel it was dispatched to so it can be replayed across a reconnect. The * channel is a session channel for {@link SessionStateSubscription} actions and * a chat channel for {@link ChatStateSubscription} actions. */ export interface IPendingDispatchAction { readonly clientSeq: number; /** The optimistic action awaiting confirmation. */ readonly action: SessionAction | ChatAction; /** URI of the channel this action targets, as stored on the subscription. */ readonly channel: 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 _isRelevantEnvelope(envelope: ActionEnvelope): 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(): IPendingDispatchAction[]; /** * 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 chat channel (e.g. a session's default chat URI). Turns, * tool calls and pending/input state moved off the session onto the chat * channel in the multi-chat protocol, so this subscription carries the * conversation contents. Supports write-ahead reconciliation for * client-dispatchable chat actions (turn starts, confirmations, etc.). */ export declare class ChatStateSubscription extends BaseAgentSubscription { private readonly _pendingActions; private _optimisticState; private readonly _chatUri; private readonly _seqAllocator; constructor(chatUri: string, clientId: string, seqAllocator: () => number, log: (msg: string) => void); /** * Optimistically apply a chat action. Returns the clientSeq to send to * the server so it can echo back for reconciliation. */ applyOptimistic(action: ChatAction): number; protected _getOptimisticState(): ChatState | undefined; protected _applyReducer(state: ChatState, action: StateAction): ChatState; protected _isRelevantEnvelope(envelope: ActionEnvelope): boolean; protected _onSnapshotApplied(fromSeq: number): void; protected _reconcile(envelope: ActionEnvelope, isOwnAction: boolean): void; private _promotePendingTurnStartIfTerminal; private _confirmedApply; private _recomputeOptimistic; clearPending(): void; getPendingActions(): IPendingDispatchAction[]; 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 _isRelevantEnvelope(envelope: ActionEnvelope): boolean; } /** * Subscription to a changeset at an expanded changeset URI (e.g. * `/changeset/session`). * * Server-only mutations — no write-ahead. The subscription itself does NOT * self-tear-down on lifecycle events; cleanup is driven externally: * - Workbench-side: `BaseAgentHostSessionsProvider._handleSessionRemoved` * disposes the per-session subscription map, which releases this * subscription's `IReference` and triggers `_releaseSubscription` on * the manager. * - Wire layer: {@link IAgentConnection} refcounts the underlying server * subscription so multiple consumers can share one wire-level subscribe. */ export declare class ChangesetStateSubscription extends BaseAgentSubscription { private readonly _changesetUri; constructor(changesetUri: string, clientId: string, log: (msg: string) => void); protected _applyReducer(state: ChangesetState, action: StateAction): ChangesetState; protected _isRelevantEnvelope(envelope: ActionEnvelope): boolean; } /** * Subscription to a session's annotations channel (e.g. * `/annotations`). * * Annotations actions are client-dispatchable, so this subscription supports * write-ahead reconciliation: optimistic state is layered on top of confirmed * state and reconciled as the server echoes the client's own actions back. * * Like {@link ChangesetStateSubscription}, the subscription does NOT * self-tear-down on lifecycle events; cleanup is driven externally by the * holder releasing its `IReference`. */ export declare class AnnotationsStateSubscription extends BaseAgentSubscription { private readonly _pendingActions; private _optimisticState; private readonly _annotationsUri; private readonly _seqAllocator; constructor(annotationsUri: string, clientId: string, seqAllocator: () => number, log: (msg: string) => void); /** * Optimistically apply an annotations action. Returns the clientSeq to * send to the server so it can echo back for reconciliation. */ applyOptimistic(action: AnnotationsAction): number; protected _getOptimisticState(): AnnotationsState | undefined; protected _applyReducer(state: AnnotationsState, action: StateAction): AnnotationsState; protected _isRelevantEnvelope(envelope: ActionEnvelope): boolean; protected _onSnapshotApplied(fromSeq: number): void; protected _reconcile(envelope: ActionEnvelope, isOwnAction: boolean): void; private _confirmedApply; private _recomputeOptimistic; } /** * 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 _inflightCreates; private _referenceOwnerIds; 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; /** * Returns the in-flight `createSession` Promise for this URI, or `undefined` if no create is pending. Used by * callers that need to gate their own work on a still-running eager `createSession` (e.g. the chat handler awaits * this before deciding whether the sessions provider's eager-create raced first send). */ getInflightSessionCreate(resource: URI): Promise | undefined; /** * Register an in-flight `createSession` Promise for a session URI. Any * subscribe issued for this resource while the create is pending waits * for the Promise before issuing the wire-level subscribe. */ trackSessionCreate(resource: URI, promise: Promise): void; /** * 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. * * `owner` names the caller holding the reference so inspection surfaces * (see {@link getActiveSubscriptions}) can attribute who is retaining a * subscription. Use a stable, human-readable identifier such as the * acquiring class name. */ getSubscription(kind: StateComponents, resource: URI, owner: string): IReference>; /** * Register `owner` as a holder of `entry` and return a reference whose * disposal removes that holder and releases the subscription. The * caller is responsible for the matching refcount increment (a fresh * entry starts at 1; an existing entry is bumped before calling this). */ private _acquireReference; private _disposeSubscriptionEntry; private _tryUnsubscribe; /** * 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. * * `channel` is the protocol URI string identifying the channel the * action targets (a session URI for session actions, etc.). */ dispatchOptimistic(channel: string, action: SessionAction | ChatAction | TerminalAction | ClientAnnotationsAction | 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[]; /** * Read-only descriptors of every active resource subscription, for * inspection/debug surfaces. Does NOT include the always-live root * state, which the connection exposes separately via {@link rootState}. */ getActiveSubscriptions(): readonly IActiveSubscriptionInfo[]; /** Group an entry's holders by owner name, sorted by descending count. */ private _summarizeHolders; /** * 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(): IPendingDispatchAction[]; /** * 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: string, 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; } /** Returns whether an action envelope targets one of the subscribed channel URIs. */ export declare function isActionEnvelopeRelevantToSubscriptionUris(envelope: ActionEnvelope, subscribedUris: Iterable): boolean; /** * 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 {};