-
Notifications
You must be signed in to change notification settings - Fork 39.1k
WIP #308904
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
WIP #308904
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| /*--------------------------------------------------------------------------------------------- | ||
| * Copyright (c) Microsoft Corporation. All rights reserved. | ||
| * Licensed under the MIT License. See License.txt in the project root for license information. | ||
| *--------------------------------------------------------------------------------------------*/ | ||
|
|
||
| import { Event } from '../../../base/common/event.js'; | ||
| import type { ISSHRemoteAgentHostService, ISSHAgentHostConnection, ISSHAgentHostConfig, ISSHConnectProgress, ISSHResolvedConfig } from '../common/sshRemoteAgentHost.js'; | ||
|
|
||
| /** | ||
| * Null implementation of {@link ISSHRemoteAgentHostService} for browser contexts | ||
| * where SSH is not available. | ||
| */ | ||
| export class NullSSHRemoteAgentHostService implements ISSHRemoteAgentHostService { | ||
| declare readonly _serviceBrand: undefined; | ||
| readonly onDidChangeConnections = Event.None; | ||
| readonly onDidReportConnectProgress: Event<ISSHConnectProgress> = Event.None; | ||
| readonly connections: readonly ISSHAgentHostConnection[] = []; | ||
|
|
||
| async connect(_config: ISSHAgentHostConfig): Promise<ISSHAgentHostConnection> { | ||
| throw new Error('SSH connections are not supported in the browser.'); | ||
| } | ||
|
|
||
| async disconnect(_host: string): Promise<void> { } | ||
|
|
||
| async listSSHConfigHosts(): Promise<string[]> { | ||
| return []; | ||
| } | ||
|
|
||
| async resolveSSHConfig(_host: string): Promise<ISSHResolvedConfig> { | ||
| throw new Error('SSH is not supported in the browser.'); | ||
| } | ||
|
|
||
| async reconnect(_sshConfigHost: string, _name: string): Promise<ISSHAgentHostConnection> { | ||
| throw new Error('SSH connections are not supported in the browser.'); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,286 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| /*--------------------------------------------------------------------------------------------- | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Copyright (c) Microsoft Corporation. All rights reserved. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * Licensed under the MIT License. See License.txt in the project root for license information. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| *--------------------------------------------------------------------------------------------*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Protocol client for communicating with a remote agent host process. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Wraps WebSocketClientTransport and SessionClientState to provide a | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // higher-level API matching IAgentService. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import { DeferredPromise } from '../../../base/common/async.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Emitter } from '../../../base/common/event.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Disposable } from '../../../base/common/lifecycle.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { hasKey } from '../../../base/common/types.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { URI } from '../../../base/common/uri.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { generateUuid } from '../../../base/common/uuid.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ILogService } from '../../log/common/log.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { AgentSession, IAgentConnection, IAgentCreateSessionConfig, IAgentDescriptor, IAgentSessionMetadata, IAuthenticateParams, IAuthenticateResult } from '../common/agentService.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { IClientNotificationMap, ICommandMap } from '../common/state/protocol/messages.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { IActionEnvelope, INotification, ISessionAction } from '../common/state/sessionActions.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { PROTOCOL_VERSION } from '../common/state/sessionCapabilities.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { isJsonRpcNotification, isJsonRpcResponse, type IJsonRpcResponse, type IProtocolMessage, type IResourceCopyParams, type IResourceCopyResult, type IResourceDeleteParams, type IResourceDeleteResult, type IResourceListResult, type IResourceMoveParams, type IResourceMoveResult, type IResourceReadResult, type IResourceWriteParams, type IResourceWriteResult, type IStateSnapshot } from '../common/state/sessionProtocol.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { ISessionSummary } from '../common/state/sessionState.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { WebSocketClientTransport } from './webSocketClientTransport.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||
| * 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 class RemoteAgentHostProtocolClient extends Disposable implements IAgentConnection { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| declare readonly _serviceBrand: undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly _clientId = generateUuid(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly _transport: WebSocketClientTransport; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| private _serverSeq = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| private _nextClientSeq = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| private _defaultDirectory: string | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly _onDidAction = this._register(new Emitter<IActionEnvelope>()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly onDidAction = this._onDidAction.event; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly _onDidNotification = this._register(new Emitter<INotification>()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly onDidNotification = this._onDidNotification.event; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly _onDidClose = this._register(new Emitter<void>()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| readonly onDidClose = this._onDidClose.event; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /** Pending JSON-RPC requests keyed by request id. */ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| private readonly _pendingRequests = new Map<number, DeferredPromise<unknown>>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| private _nextRequestId = 1; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| get clientId(): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return this._clientId; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| get address(): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return this._transport['_address']; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| get defaultDirectory(): string | undefined { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return this._defaultDirectory; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| address: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| connectionToken: string | undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| @ILogService private readonly _logService: ILogService, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| super(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| this._transport = this._register(new WebSocketClientTransport(address, connectionToken)); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| this._register(this._transport.onMessage(msg => this._handleMessage(msg))); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| this._register(this._transport.onClose(() => this._onDidClose.fire())); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| this._register(this._transport.onClose(() => this._onDidClose.fire())); | |
| } | |
| this._register(this._transport.onClose(() => { | |
| this._rejectAllPendingRequests('Remote agent host connection closed.'); | |
| this._onDidClose.fire(); | |
| })); | |
| } | |
| override dispose(): void { | |
| this._rejectAllPendingRequests('Remote agent host connection disposed.'); | |
| super.dispose(); | |
| } | |
| private _rejectAllPendingRequests(reason: string): void { | |
| if (this._pendingRequests.size === 0) { | |
| return; | |
| } | |
| const error = new Error(reason); | |
| for (const pendingRequest of this._pendingRequests.values()) { | |
| pendingRequest.error(error); | |
| } | |
| this._pendingRequests.clear(); | |
| } |
Copilot
AI
Apr 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_sendExtensionRequest builds a JSON-RPC request ({ jsonrpc, id, method, params }) but type-asserts it to IJsonRpcResponse, which is a response shape. This defeats compile-time checking and is likely to confuse future refactors. Please introduce/consume an explicit request type (or reuse IProtocolMessage) instead of asserting to a response type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressis implemented by reaching into a private field (this._transport['_address']). This is brittle and bypasses type-safety. Please add a realget address()accessor onWebSocketClientTransport(or store the address on the protocol client) instead of relying on private property indexing.