import { Disposable, IReference } from "@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle"; import { URI } from "@codingame/monaco-vscode-api/vscode/vs/base/common/uri"; import { ILogService } from "@codingame/monaco-vscode-api/vscode/vs/platform/log/common/log.service"; import { IFileService } from "@codingame/monaco-vscode-api/vscode/vs/platform/files/common/files.service"; import { IConfigurationService } from "@codingame/monaco-vscode-api/vscode/vs/platform/configuration/common/configuration.service"; import { IAgentConnection, IAgentCreateSessionConfig, IAgentResolveSessionConfigParams, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, AuthenticateParams, AuthenticateResult } from "../common/agentService.js"; import { type IAgentSubscription } from "../common/state/agentSubscription.js"; import { IAgentHostPermissionService } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/agentHostPermissionService.service"; import type { CommandMap } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/messages"; import { type ActionEnvelope, type IRootConfigChangedAction, type SessionAction, type TerminalAction } from "../common/state/sessionActions.js"; import { StateComponents, type RootState } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/sessionState"; import { type IStateSnapshot } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/sessionProtocol"; import { type IVscodeUpgradeResult } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocolUpgrade"; import { type IProtocolTransport } from "../common/state/sessionTransport.js"; import { type CompletionsParams, type CompletionsResult, type CreateTerminalParams, type ResolveSessionConfigResult, type SessionConfigCompletionsResult } from "@codingame/monaco-vscode-api/vscode/vs/platform/agentHost/common/state/protocol/commands"; import { ILoadEstimator } from "@codingame/monaco-vscode-api/vscode/vs/base/parts/ipc/common/ipc.net"; /** * High-level connection state of a {@link RemoteAgentHostProtocolClient}. * Exposed via {@link RemoteAgentHostProtocolClient.onDidChangeConnectionState} * so consumers can surface transient reconnect activity in the UI. */ export declare enum AgentHostClientState { /** Initial handshake in progress. */ Connecting = "connecting", /** Transport is open and handshake/reconnect has completed. */ Connected = "connected", /** Transport closed unexpectedly; an automatic reconnect is in flight or scheduled. */ Reconnecting = "reconnecting", /** Client has been disposed or has given up reconnecting. Terminal state. */ Closed = "closed" } /** * A protocol-level client for a single remote agent host connection. * Manages the WebSocket transport, handshake, subscriptions, action dispatch, * and command/response correlation. * * Implements {@link IAgentConnection} so consumers can program against * a single interface regardless of whether the agent host is local or remote. */ export declare class RemoteAgentHostProtocolClient extends Disposable implements IAgentConnection { private readonly _logService; private readonly _fileService; private readonly _permissionService; private readonly _configurationService; readonly _serviceBrand: undefined; private readonly _clientId; private readonly _address; private readonly _transportFactory; private _transport; /** Disposable holding the listeners attached to the current transport. */ private readonly _transportListeners; private readonly _connectionAuthority; private _serverSeq; private _nextClientSeq; private _defaultDirectory; private _completionTriggerCharacters; private readonly _subscriptionManager; private readonly _onDidAction; readonly onDidAction: import("@codingame/monaco-vscode-api/vscode/vs/base/common/event").Event; private readonly _onDidNotification; readonly onDidNotification: import("@codingame/monaco-vscode-api/vscode/vs/base/common/event").Event; private readonly _onDidClose; readonly onDidClose: import("@codingame/monaco-vscode-api/vscode/vs/base/common/event").Event; private readonly _onDidChangeConnectionState; readonly onDidChangeConnectionState: import("@codingame/monaco-vscode-api/vscode/vs/base/common/event").Event; /** * Discriminated state union. Read via narrowing (`_state.kind === ...`); * reconnect-only fields like the gate/outbox/attempt counter are only * accessible while {@link _state.kind} is {@link AgentHostClientState.Reconnecting}, * and the close error is only accessible while it's {@link AgentHostClientState.Closed}. */ private _state; /** Pending JSON-RPC requests keyed by request id. */ private readonly _pendingRequests; private _nextRequestId; /** * Timestamp of the most recent message of any kind received from the * server. Used only for diagnostic logging when the close timer fires. */ private _lastReadTime; /** * Liveness watchdog — see {@link _resetLivenessTimers}. * * {@link _pingTimer} fires after {@link PING_INTERVAL_MS} of inbound * silence and sends an application-level `ping` so we have something * to time out on. {@link _closeTimer} fires after another * {@link LIVENESS_TIMEOUT_MS} of continued silence and force-closes * the transport so the renderer's reconnect logic kicks in. Both are * reset on every received message, so busy connections generate no * ping traffic at all. * * Detects silently-dead transports (e.g. SSH/tunnel after laptop * sleep + network change) that don't produce a socket close event of * their own. */ private readonly _pingTimer; private readonly _closeTimer; /** * Used to suppress watchdog-triggered closes when our own JS event loop * has been pegged — in that case the silence is on our side, not the * remote's, and tearing down the transport would just generate a useless * reconnect cycle that aborts in-flight requests. */ private readonly _loadEstimator; /** * Comparison keys of customization URIs we have already granted implicit * read access for on this connection. Dedupes repeat sends so we don't * pile up grants per dispatch. Cleared with the connection. */ private readonly _grantedCustomizationUris; get clientId(): string; get address(): string; get defaultDirectory(): string | undefined; get connectionState(): AgentHostClientState; constructor(address: string, transportOrFactory: IProtocolTransport | (() => IProtocolTransport), loadEstimator: ILoadEstimator | undefined, _logService: ILogService, _fileService: IFileService, _permissionService: IAgentHostPermissionService, _configurationService: IConfigurationService); /** * Install a transport and wire listeners. Used both for the initial * transport and for replacements created by the factory during a * transport-level reconnect. */ private _installTransport; /** * Transition to a new {@link ClientState}. Fires {@link onDidChangeConnectionState} * only when the variant kind actually changes; in-place mutation of * reconnect-state fields (e.g. swapping the gate on a failed retry) does * NOT count as a transition and produces no event. */ private _transitionTo; private _newReconnectGate; private _newReconnectState; dispose(): void; /** * Connect to the remote agent host and perform the protocol handshake. */ connect(): Promise; /** * Called from the transport's `onClose` event. When a {@link _transportFactory} * is configured we attempt to soft-reconnect rather than fire `onDidClose` — * the protocol-level `reconnect` request lets the server replay missed * actions and preserves the `clientId` so pending tool calls etc. are not * cancelled by the host-side disconnect timeout. Without a factory * (passive-transport SSH/relay path) we fall back to "close means closed" * and let the service decide whether to spin up a fresh client. */ private _handleTransportClose; private _scheduleReconnect; private _attemptReconnect; /** * Apply a `reconnect` RPC result to the subscription manager. On `replay` * we feed each missed envelope through the normal action path; on * `snapshot` we reseat each named subscription with the fresh state and * advance the server seq cursor accordingly. */ private _applyReconnectResult; /** * Drain queued outgoing wire traffic after a successful soft reconnect: * * 1. Resend pending optimistic session actions that the server did NOT * echo back in the replay buffer (i.e. anything still on * {@link AgentSubscriptionManager.getPendingSessionActions}). * 2. Flush every message that {@link _sendNotification} queued onto the * outbox while the gate was engaged. * * Replays are deduped against the outbox by `clientSeq` so a session * action that was both optimistic-tracked AND queued during the * reconnect window only goes out once. */ private _drainAfterReconnect; get rootState(): IAgentSubscription; getSubscription(kind: StateComponents, resource: URI): IReference>; getSubscriptionUnmanaged(_kind: StateComponents, resource: URI): IAgentSubscription | undefined; dispatch(action: SessionAction | TerminalAction | IRootConfigChangedAction): void; /** * Subscribe to state at a URI. Returns the current state snapshot. */ subscribe(resource: URI): Promise; /** * Unsubscribe from state at a URI. */ unsubscribe(resource: URI): void; /** * Dispatch a client action to the server. Returns the clientSeq used. */ private dispatchAction; /** * Create a new session on the remote agent host. */ createSession(config?: IAgentCreateSessionConfig): Promise; resolveSessionConfig(params: IAgentResolveSessionConfigParams): Promise; sessionConfigCompletions(params: IAgentSessionConfigCompletionsParams): Promise; completions(params: CompletionsParams): Promise; /** * Send an application-level ping and wait for the server's response. * Used by {@link _watchdogTick} to keep idle connections under * watchdog supervision; safe to call from external code as well. * * The returned promise rejects with a {@link ProtocolError} if the * connection closes before a response arrives. */ ping(): Promise; /** * Returns the trigger characters captured from the `initialize` handshake. * Empty when the remote host did not announce any. */ getCompletionTriggerCharacters(): Promise; /** * Authenticate with the remote agent host using a specific scheme. */ authenticate(params: AuthenticateParams): Promise; /** * Gracefully shut down all sessions on the remote host. */ shutdown(): Promise; /** * Dispose a session on the remote agent host. */ disposeSession(session: URI): Promise; /** * Create a new terminal on the remote agent host. */ createTerminal(params: CreateTerminalParams): Promise; /** * Dispose a terminal on the remote agent host. */ disposeTerminal(terminal: URI): Promise; /** * List all sessions from the remote agent host. */ listSessions(): Promise; private _toLocalProjectUri; /** * Inspect an outgoing client-dispatched action and grant implicit reads * for any customization URIs it carries. Today this covers * `SessionActiveClientChanged`, which is the only client-dispatched * action that ships customization URIs to the host. */ private _grantImplicitReadsForOutgoingAction; /** * Register implicit read grants for each customization URI that we are * about to send to the host. The host needs to read these to materialize * the customization, but should not need to write them. Grants are * deduped per connection and revoked when the connection closes. */ private _grantImplicitReadsForCustomizations; /** * List the contents of a directory on the remote host's filesystem. */ resourceList(uri: URI): Promise; /** * Read the content of a resource on the remote host. */ resourceRead(uri: URI): Promise; resourceWrite(params: CommandMap["resourceWrite"]["params"]): Promise; resourceCopy(params: CommandMap["resourceCopy"]["params"]): Promise; resourceDelete(params: CommandMap["resourceDelete"]["params"]): Promise; resourceMove(params: CommandMap["resourceMove"]["params"]): Promise; /** * Trigger the CLI-managed upgrade flow for this agent host using the * method name advertised by the server (typically * {@link VSCODE_UPGRADE_METHOD}). Callable before {@link connect} has * completed — typically used when the host has just rejected our * `initialize` with an `UnsupportedProtocolVersion` error. The * transport stays open after the rejection, so the extension request * rides over it without a special out-of-band path. * * The result mirrors the CLI's HTTP response: ok flag, whether the * upgrade is needed / started, running/latest commits. */ triggerVscodeUpgrade(method: string): Promise; private _handleMessage; private _handleClose; private _raceClose; /** * Handles reverse RPC requests from the server (e.g. resourceList, * resourceRead). Reads from the local file service and sends a response. * * Filesystem-mutating reverse requests are gated through * {@link IAgentHostPermissionService} — denied operations return a typed * `PermissionDenied` error advertising a `resourceRequest` payload that, * if granted, would unlock the operation. Hosts SHOULD then issue a * `resourceRequest` and retry. */ private _handleReverseRequest; /** Send a typed JSON-RPC notification for a protocol-defined method. */ private _sendNotification; /** Send a typed JSON-RPC request for a protocol-defined method. */ private _sendRequest; /** Send a JSON-RPC request for a VS Code extension method (not in the protocol spec). */ private _sendExtensionRequest; private _updateTelemetryLevel; /** * Common path for outgoing JSON-RPC requests: gate on any in-flight * reconnect (unless explicitly bypassed for the `reconnect` RPC itself), * assign an id, register the pending deferred, and write to the wire. * * The bypass option is the single special case that exists: the * `reconnect` request is sent from inside `_attemptReconnect` while the * gate is engaged, so it can't wait on its own resolution. */ private _dispatchRequest; private _toProtocolError; private _rejectPendingRequests; /** * Reset the liveness timers. Called once at construction (after the * initial transport is installed), once on every received message * (which is itself proof the remote is alive), and once after a * successful soft reconnect. * * Two timers cooperate: * * 1. {@link _pingTimer} fires after {@link PING_INTERVAL_MS} of silence * and sends an application-level `ping` so the close timer has * something to time out on. Tolerates servers that don't implement * `ping` — the error response still resets both timers. * * 2. {@link _closeTimer} fires after {@link PING_INTERVAL_MS}+ * {@link LIVENESS_TIMEOUT_MS} of continued silence and force-closes * the transport so the renderer's reconnect logic kicks in. Catches * silently-dead transports (e.g. SSH/tunnel after laptop sleep + * network change) that don't emit a socket close event of their own. * * After laptop sleep + wake the JS event loop is paused, so a timer * armed before sleep fires immediately after wake. That's fine — * any inbound message processed during the wake catch-up resets it * before the close handler runs. * * No-op while {@link _state.kind} is {@link AgentHostClientState.Reconnecting} * or {@link AgentHostClientState.Closed}: the reconnect machinery * owns scheduling in those states, and we don't want a stray inbound * message during reconnect to re-arm the timers behind its back. */ private _resetLivenessTimers; private _cancelLivenessTimers; private _onPingTimer; private _onCloseTimer; /** * Get the next client sequence number for optimistic dispatch. */ nextClientSeq(): number; }