Path: blob/main/src/vs/platform/agentHost/electron-browser/agentHostService.ts
13394 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { DeferredPromise } from '../../../base/common/async.js';6import { Emitter } from '../../../base/common/event.js';7import { Disposable, DisposableStore, IReference } from '../../../base/common/lifecycle.js';8import { IObservable, ISettableObservable, observableValue } from '../../../base/common/observable.js';9import { generateUuid } from '../../../base/common/uuid.js';10import { getDelayedChannel, ProxyChannel } from '../../../base/parts/ipc/common/ipc.js';11import { Client as MessagePortClient } from '../../../base/parts/ipc/common/ipc.mp.js';12import { acquirePort } from '../../../base/parts/ipc/electron-browser/ipc.mp.js';13import { InstantiationType, registerSingleton } from '../../instantiation/common/extensions.js';14import { IConfigurationService } from '../../configuration/common/configuration.js';15import { ILogService } from '../../log/common/log.js';16import { AgentHostEnabledSettingId, AgentHostIpcChannels, IAgentCreateSessionConfig, IAgentHostInspectInfo, IAgentHostService, IAgentResolveSessionConfigParams, IAgentService, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, AuthenticateParams, AuthenticateResult, IAgentHostSocketInfo, IConnectionTrackerService } from '../common/agentService.js';17import { AgentSubscriptionManager, type IAgentSubscription } from '../common/state/agentSubscription.js';18import type { CreateTerminalParams, ResolveSessionConfigResult, SessionConfigCompletionsResult } from '../common/state/protocol/commands.js';19import type { ActionEnvelope, INotification, IRootConfigChangedAction, SessionAction, TerminalAction } from '../common/state/sessionActions.js';20import type { ResourceCopyParams, ResourceCopyResult, ResourceDeleteParams, ResourceDeleteResult, ResourceListResult, ResourceMoveParams, ResourceMoveResult, ResourceReadResult, ResourceWriteParams, ResourceWriteResult, IStateSnapshot } from '../common/state/sessionProtocol.js';21import { StateComponents, ROOT_STATE_URI, type RootState } from '../common/state/sessionState.js';22import { revive } from '../../../base/common/marshalling.js';23import { URI } from '../../../base/common/uri.js';24import { IFileService } from '../../files/common/files.js';25import { AGENT_HOST_CLIENT_RESOURCE_CHANNEL, AgentHostClientResourceChannel } from '../common/agentHostClientResourceChannel.js';2627/**28* Renderer-side implementation of {@link IAgentHostService} that connects29* directly to the agent host utility process via MessagePort, bypassing30* the main process relay. Uses the same `getDelayedChannel` pattern as31* the pty host so the proxy is usable immediately while the port is acquired.32*/33class AgentHostServiceClient extends Disposable implements IAgentHostService {34declare readonly _serviceBrand: undefined;3536/** Unique identifier for this window, used in action envelope origin tracking. */37readonly clientId = generateUuid();3839private readonly _clientEventually = new DeferredPromise<MessagePortClient>();40private readonly _proxy: IAgentService;41private readonly _connectionTracker: IConnectionTrackerService;42private readonly _subscriptionManager: AgentSubscriptionManager;4344private readonly _onAgentHostExit = this._register(new Emitter<number>());45readonly onAgentHostExit = this._onAgentHostExit.event;46private readonly _onAgentHostStart = this._register(new Emitter<void>());47readonly onAgentHostStart = this._onAgentHostStart.event;4849private readonly _onDidAction = this._register(new Emitter<ActionEnvelope>());50readonly onDidAction = this._onDidAction.event;5152private readonly _onDidNotification = this._register(new Emitter<INotification>());53readonly onDidNotification = this._onDidNotification.event;5455private readonly _authenticationPending: ISettableObservable<boolean> = observableValue('authenticationPending', true);56readonly authenticationPending: IObservable<boolean> = this._authenticationPending;57private _authenticationSettled = false;5859setAuthenticationPending(pending: boolean): void {60// Sticky: once the first authentication pass settles, never surface61// pending again. Subsequent re-auths (account/session changes, reconnect)62// happen silently in the background and should not flicker the UI.63if (this._authenticationSettled) {64return;65}66if (!pending) {67this._authenticationSettled = true;68}69this._authenticationPending.set(pending, undefined);70}7172constructor(73@ILogService private readonly _logService: ILogService,74@IConfigurationService configurationService: IConfigurationService,75@IFileService private readonly _fileService: IFileService,76) {77super();7879// Create a proxy backed by a delayed channel - usable immediately,80// calls queue until the MessagePort connection is established.81this._proxy = ProxyChannel.toService<IAgentService>(82getDelayedChannel(this._clientEventually.p.then(client => client.getChannel(AgentHostIpcChannels.AgentHost)))83);8485this._connectionTracker = ProxyChannel.toService<IConnectionTrackerService>(86getDelayedChannel(this._clientEventually.p.then(client => client.getChannel(AgentHostIpcChannels.ConnectionTracker)))87);8889this._subscriptionManager = this._register(new AgentSubscriptionManager(90this.clientId,91() => this.nextClientSeq(),92msg => this._logService.warn(`[AgentHost:renderer] ${msg}`),93resource => this.subscribe(resource),94resource => this.unsubscribe(resource),95));9697if (configurationService.getValue<boolean>(AgentHostEnabledSettingId)) {98this._connect();99}100}101102private async _connect(): Promise<void> {103this._logService.info('[AgentHost:renderer] Acquiring MessagePort to agent host...');104const port = await acquirePort('vscode:createAgentHostMessageChannel', 'vscode:createAgentHostMessageChannelResult');105this._logService.info('[AgentHost:renderer] MessagePort acquired, creating client...');106107const store = this._register(new DisposableStore());108// Use clientId as the IPC ctx so the agent host can route reverse-RPC109// calls (vscode-agent-client filesystem reads) back to this renderer110// via `IPCServer.getChannel(name, c => c.ctx === clientId)`.111const client = store.add(new MessagePortClient(port, this.clientId));112// Serve filesystem reverse-RPCs from the local file service. The113// agent host registers an authority on its114// AgentHostClientFileSystemProvider that calls back through this channel.115client.registerChannel(AGENT_HOST_CLIENT_RESOURCE_CHANNEL, new AgentHostClientResourceChannel(this._fileService));116this._clientEventually.complete(client);117118store.add(this._proxy.onDidAction(e => {119const revived = revive(e) as ActionEnvelope;120this._subscriptionManager.receiveEnvelope(revived);121this._onDidAction.fire(revived);122}));123store.add(this._proxy.onDidNotification(e => {124this._onDidNotification.fire(revive(e));125}));126this._logService.info('[AgentHost:renderer] Direct MessagePort connection established');127this._onAgentHostStart.fire();128129// Subscribe to root state130this.subscribe(URI.parse(ROOT_STATE_URI)).then(snapshot => {131this._subscriptionManager.handleRootSnapshot(snapshot.state as RootState, snapshot.fromSeq);132}).catch(err => {133this._logService.error('[AgentHost:renderer] Failed to subscribe to root state', err);134});135}136137// ---- IAgentService forwarding (no await needed, delayed channel handles queuing) ----138139authenticate(params: AuthenticateParams): Promise<AuthenticateResult> {140return this._proxy.authenticate(params);141}142listSessions(): Promise<IAgentSessionMetadata[]> {143return this._proxy.listSessions();144}145createSession(config?: IAgentCreateSessionConfig): Promise<URI> {146return this._proxy.createSession(config);147}148resolveSessionConfig(params: IAgentResolveSessionConfigParams): Promise<ResolveSessionConfigResult> {149return this._proxy.resolveSessionConfig(params);150}151sessionConfigCompletions(params: IAgentSessionConfigCompletionsParams): Promise<SessionConfigCompletionsResult> {152return this._proxy.sessionConfigCompletions(params);153}154disposeSession(session: URI): Promise<void> {155return this._proxy.disposeSession(session);156}157createTerminal(params: CreateTerminalParams): Promise<void> {158return this._proxy.createTerminal(params);159}160disposeTerminal(terminal: URI): Promise<void> {161return this._proxy.disposeTerminal(terminal);162}163shutdown(): Promise<void> {164return this._proxy.shutdown();165}166subscribe(resource: URI): Promise<IStateSnapshot> {167return this._proxy.subscribe(resource);168}169unsubscribe(resource: URI): void {170this._proxy.unsubscribe(resource);171}172dispatchAction(action: SessionAction | TerminalAction | IRootConfigChangedAction, clientId: string, clientSeq: number): void {173this._proxy.dispatchAction(action, clientId, clientSeq);174}175private _nextSeq = 1;176nextClientSeq(): number {177return this._nextSeq++;178}179180get rootState(): IAgentSubscription<RootState> {181return this._subscriptionManager.rootState;182}183184getSubscription<T>(kind: StateComponents, resource: URI): IReference<IAgentSubscription<T>> {185return this._subscriptionManager.getSubscription<T>(kind, resource);186}187188getSubscriptionUnmanaged<T>(_kind: StateComponents, resource: URI): IAgentSubscription<T> | undefined {189return this._subscriptionManager.getSubscriptionUnmanaged<T>(resource);190}191192dispatch(action: SessionAction | TerminalAction | IRootConfigChangedAction): void {193const seq = this._subscriptionManager.dispatchOptimistic(action);194this.dispatchAction(action, this.clientId, seq);195}196197resourceList(uri: URI): Promise<ResourceListResult> {198return this._proxy.resourceList(uri);199}200resourceRead(uri: URI): Promise<ResourceReadResult> {201return this._proxy.resourceRead(uri);202}203resourceWrite(params: ResourceWriteParams): Promise<ResourceWriteResult> {204return this._proxy.resourceWrite(params);205}206resourceCopy(params: ResourceCopyParams): Promise<ResourceCopyResult> {207return this._proxy.resourceCopy(params);208}209resourceDelete(params: ResourceDeleteParams): Promise<ResourceDeleteResult> {210return this._proxy.resourceDelete(params);211}212resourceMove(params: ResourceMoveParams): Promise<ResourceMoveResult> {213return this._proxy.resourceMove(params);214}215async restartAgentHost(): Promise<void> {216// Restart is handled by the main process side217}218219startWebSocketServer(): Promise<IAgentHostSocketInfo> {220return this._connectionTracker.startWebSocketServer();221}222223getInspectInfo(tryEnable: boolean): Promise<IAgentHostInspectInfo | undefined> {224return this._connectionTracker.getInspectInfo(tryEnable);225}226}227228registerSingleton(IAgentHostService, AgentHostServiceClient, InstantiationType.Delayed);229230231