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