Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/agentHost/node/agentHostMain.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 { ProxyChannel } from '../../../base/parts/ipc/common/ipc.js';
7
import { Server as ChildProcessServer } from '../../../base/parts/ipc/node/ipc.cp.js';
8
import { Server as UtilityProcessServer } from '../../../base/parts/ipc/node/ipc.mp.js';
9
import { isUtilityProcess } from '../../../base/parts/sandbox/node/electronTypes.js';
10
import { Emitter } from '../../../base/common/event.js';
11
import { DisposableStore, IDisposable } from '../../../base/common/lifecycle.js';
12
import { joinPath } from '../../../base/common/resources.js';
13
import { isWindows } from '../../../base/common/platform.js';
14
import { URI } from '../../../base/common/uri.js';
15
import { generateUuid } from '../../../base/common/uuid.js';
16
import * as os from 'os';
17
import * as inspector from 'inspector';
18
import { AgentHostIpcChannels, IAgentHostInspectInfo, IAgentHostSocketInfo, IConnectionTrackerService } from '../common/agentService.js';
19
import { AgentService } from './agentService.js';
20
import { IAgentConfigurationService } from './agentConfigurationService.js';
21
import { IAgentHostTerminalManager } from './agentHostTerminalManager.js';
22
import { CopilotAgent } from './copilot/copilotAgent.js';
23
import { CopilotApiService, ICopilotApiService } from './shared/copilotApiService.js';
24
import { ProtocolServerHandler } from './protocolServerHandler.js';
25
import { WebSocketProtocolServer } from './webSocketTransport.js';
26
import { INativeEnvironmentService } from '../../environment/common/environment.js';
27
import { NativeEnvironmentService } from '../../environment/node/environmentService.js';
28
import { parseArgs, OPTIONS } from '../../environment/node/argv.js';
29
import { getLogLevel, ILogService } from '../../log/common/log.js';
30
import { LogService } from '../../log/common/logService.js';
31
import { LoggerService } from '../../log/node/loggerService.js';
32
import { LoggerChannel } from '../../log/common/logIpc.js';
33
import { DefaultURITransformer } from '../../../base/common/uriIpc.js';
34
import product from '../../product/common/product.js';
35
import { IProductService } from '../../product/common/productService.js';
36
import { localize } from '../../../nls.js';
37
import { FileService } from '../../files/common/fileService.js';
38
import { IFileService } from '../../files/common/files.js';
39
import { DiskFileSystemProvider } from '../../files/node/diskFileSystemProvider.js';
40
import { Schemas } from '../../../base/common/network.js';
41
import { InstantiationService } from '../../instantiation/common/instantiationService.js';
42
import { ServiceCollection } from '../../instantiation/common/serviceCollection.js';
43
import { SessionDataService } from './sessionDataService.js';
44
import { ISessionDataService } from '../common/sessionDataService.js';
45
import { IDiffComputeService } from '../common/diffComputeService.js';
46
import { NodeWorkerDiffComputeService } from './diffComputeService.js';
47
import { AgentHostClientFileSystemProvider } from '../common/agentHostClientFileSystemProvider.js';
48
import { AGENT_CLIENT_SCHEME } from '../common/agentClientUri.js';
49
import { AGENT_HOST_CLIENT_RESOURCE_CHANNEL, createAgentHostClientResourceConnection } from '../common/agentHostClientResourceChannel.js';
50
import { IAgentPluginManager } from '../common/agentPluginManager.js';
51
import { AgentPluginManager } from './agentPluginManager.js';
52
import { AgentHostGitService, IAgentHostGitService } from './agentHostGitService.js';
53
import { registerPendingEditContentProvider } from './copilot/pendingEditContentStore.js';
54
import { join } from '../../../base/common/path.js';
55
56
// Entry point for the agent host utility process.
57
// Sets up IPC, logging, and registers agent providers (Copilot).
58
// When VSCODE_AGENT_HOST_PORT or VSCODE_AGENT_HOST_SOCKET_PATH env vars
59
// are set, also starts a WebSocket server for external clients.
60
61
startAgentHost();
62
63
function startAgentHost(): void {
64
// Setup RPC - supports both Electron utility process and Node child process
65
let server: ChildProcessServer<string> | UtilityProcessServer;
66
if (isUtilityProcess(process)) {
67
server = new UtilityProcessServer();
68
} else {
69
server = new ChildProcessServer(AgentHostIpcChannels.AgentHost);
70
}
71
72
const disposables = new DisposableStore();
73
74
// Services
75
const productService: IProductService = { _serviceBrand: undefined, ...product };
76
const environmentService = new NativeEnvironmentService(parseArgs(process.argv, OPTIONS), productService);
77
const loggerService = new LoggerService(getLogLevel(environmentService), environmentService.logsHome);
78
server.registerChannel(AgentHostIpcChannels.Logger, new LoggerChannel(loggerService, () => DefaultURITransformer));
79
const logger = loggerService.createLogger('agenthost', { name: localize('agentHost', "Agent Host") });
80
const logService = new LogService(logger);
81
logService.info('Agent Host process started successfully');
82
83
// File service
84
const fileService = disposables.add(new FileService(logService));
85
disposables.add(fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))));
86
// In-memory filesystem backing transient file-edit previews shown during
87
// tool-call confirmations.
88
disposables.add(registerPendingEditContentProvider(fileService));
89
90
// Session data service
91
const sessionDataService = new SessionDataService(URI.file(environmentService.userDataPath), fileService, logService);
92
const rootConfigResource = joinPath(environmentService.appSettingsHome, 'globalStorage', 'agent-host-config.json');
93
94
// Create the real service implementation that lives in this process
95
let agentService: AgentService;
96
try {
97
// Build the DI container early so the git service can be created via
98
// `createInstance` (it needs IFileService + INativeEnvironmentService).
99
const diServices = new ServiceCollection();
100
diServices.set(INativeEnvironmentService, environmentService);
101
diServices.set(ILogService, logService);
102
diServices.set(IFileService, fileService);
103
diServices.set(ISessionDataService, sessionDataService);
104
diServices.set(IProductService, productService);
105
const instantiationService = new InstantiationService(diServices);
106
const gitService = instantiationService.createInstance(AgentHostGitService);
107
diServices.set(IAgentHostGitService, gitService);
108
const copilotApiService = instantiationService.createInstance(CopilotApiService, undefined);
109
diServices.set(ICopilotApiService, copilotApiService);
110
agentService = new AgentService(logService, fileService, sessionDataService, productService, gitService, rootConfigResource);
111
const pluginManager = new AgentPluginManager(URI.file(environmentService.userDataPath), fileService, logService);
112
diServices.set(IAgentPluginManager, pluginManager);
113
const diffComputeService = disposables.add(new NodeWorkerDiffComputeService(logService));
114
diServices.set(IDiffComputeService, diffComputeService);
115
116
diServices.set(IAgentHostTerminalManager, agentService.terminalManager);
117
diServices.set(IAgentConfigurationService, agentService.configurationService);
118
agentService.registerProvider(instantiationService.createInstance(CopilotAgent));
119
} catch (err) {
120
logService.error('Failed to create AgentService', err);
121
throw err;
122
}
123
const agentChannel = ProxyChannel.fromService(agentService, disposables);
124
server.registerChannel(AgentHostIpcChannels.AgentHost, agentChannel);
125
126
// Single shared `vscode-agent-client` filesystem provider. Per-client
127
// authorities are added either by ProtocolServerHandler (for WebSocket
128
// transports) or by the IPC connection lifecycle below (for the local
129
// in-process renderer-to-utility-process MessagePort transport).
130
const clientFileSystemProvider = disposables.add(new AgentHostClientFileSystemProvider());
131
disposables.add(fileService.registerProvider(AGENT_CLIENT_SCHEME, clientFileSystemProvider));
132
133
// Wire reverse-RPC for in-process renderer connections. The renderer's
134
// `MessagePortClient` ctx is its `clientId`, and it exposes
135
// `AGENT_HOST_CLIENT_RESOURCE_CHANNEL` for filesystem reads.
136
if (server instanceof UtilityProcessServer) {
137
const authorityRegistrations = new Map<unknown, IDisposable>();
138
disposables.add(server.onDidAddConnection(connection => {
139
const clientId = connection.ctx;
140
if (typeof clientId !== 'string' || !clientId) {
141
return;
142
}
143
const channel = server.getChannel(AGENT_HOST_CLIENT_RESOURCE_CHANNEL, c => c.ctx === clientId);
144
const fsConnection = createAgentHostClientResourceConnection(channel);
145
authorityRegistrations.set(connection, clientFileSystemProvider.registerAuthority(clientId, fsConnection));
146
}));
147
disposables.add(server.onDidRemoveConnection(connection => {
148
const reg = authorityRegistrations.get(connection);
149
if (reg) {
150
reg.dispose();
151
authorityRegistrations.delete(connection);
152
}
153
}));
154
}
155
156
// Expose the WebSocket client connection count to the parent process via IPC.
157
// This is NOT part of the agent host protocol -- it is only used by the
158
// server process to manage the agent host process lifetime.
159
const connectionCountEmitter = disposables.add(new Emitter<number>());
160
let dynamicSocketInfo: IAgentHostSocketInfo | undefined;
161
const connectionTrackerService: IConnectionTrackerService = {
162
onDidChangeConnectionCount: connectionCountEmitter.event,
163
async startWebSocketServer(): Promise<IAgentHostSocketInfo> {
164
if (dynamicSocketInfo) {
165
return dynamicSocketInfo;
166
}
167
168
const socketPath = isWindows
169
? `\\\\.\\pipe\\vscode-agent-host-${generateUuid().replace(/-/g, '')}`
170
: join(os.tmpdir(), `vscode-agent-host-${generateUuid().replace(/-/g, '')}.sock`);
171
172
const wsServer = disposables.add(await WebSocketProtocolServer.create(
173
{ socketPath },
174
logService,
175
));
176
177
const protocolHandler = disposables.add(new ProtocolServerHandler(
178
agentService,
179
agentService.stateManager,
180
wsServer,
181
{ defaultDirectory: URI.file(os.homedir()).toString() },
182
clientFileSystemProvider,
183
logService,
184
));
185
disposables.add(protocolHandler.onDidChangeConnectionCount(count => connectionCountEmitter.fire(count)));
186
187
logService.info(`[AgentHost] Dynamic WebSocket server listening on ${socketPath}`);
188
dynamicSocketInfo = { socketPath };
189
return dynamicSocketInfo;
190
},
191
async getInspectInfo(tryEnable: boolean): Promise<IAgentHostInspectInfo | undefined> {
192
let url = inspector.url();
193
if (!url && tryEnable) {
194
try {
195
inspector.open(0, '127.0.0.1', false);
196
} catch (err) {
197
logService.error('[AgentHost] Failed to open inspector', err);
198
return undefined;
199
}
200
url = inspector.url();
201
}
202
if (!url) {
203
return undefined;
204
}
205
// Inspector URL looks like: ws://host:port/uuid (host may be IPv6 in brackets)
206
try {
207
const parsedUrl = new URL(url);
208
if (parsedUrl.protocol !== 'ws:') {
209
logService.warn(`[AgentHost] Unexpected inspector URL: ${url}`);
210
return undefined;
211
}
212
213
const port = Number(parsedUrl.port);
214
const auth = parsedUrl.pathname.replace(/^\/+/, '');
215
if (!Number.isInteger(port) || !auth) {
216
logService.warn(`[AgentHost] Unexpected inspector URL: ${url}`);
217
return undefined;
218
}
219
220
const host = parsedUrl.hostname === '0.0.0.0'
221
? '127.0.0.1'
222
: parsedUrl.hostname === '::'
223
? '::1'
224
: parsedUrl.hostname;
225
const devtoolsHost = host.includes(':') ? `[${host}]` : host;
226
227
return {
228
host,
229
port,
230
devtoolsUrl: `devtools://devtools/bundled/js_app.html?v8only=true&ws=${devtoolsHost}:${parsedUrl.port}/${auth}`,
231
};
232
} catch {
233
logService.warn(`[AgentHost] Unexpected inspector URL: ${url}`);
234
return undefined;
235
}
236
},
237
};
238
const connectionTrackerChannel = ProxyChannel.fromService(connectionTrackerService, disposables);
239
server.registerChannel(AgentHostIpcChannels.ConnectionTracker, connectionTrackerChannel);
240
241
// Start WebSocket server for external clients if configured (env-var flow for CLI/server)
242
startWebSocketServer(agentService, clientFileSystemProvider, logService, disposables, count => connectionCountEmitter.fire(count)).catch(err => {
243
logService.error('Failed to start WebSocket server', err);
244
});
245
246
process.once('exit', () => {
247
agentService.dispose();
248
logService.dispose();
249
disposables.dispose();
250
});
251
}
252
253
/**
254
* When the parent process passes WebSocket configuration via environment
255
* variables, start a protocol server that external clients can connect to.
256
* This reuses the same {@link AgentService} and {@link AgentHostStateManager}
257
* that the IPC channel uses, so both IPC and WebSocket clients share state.
258
*/
259
async function startWebSocketServer(agentService: AgentService, clientFileSystemProvider: AgentHostClientFileSystemProvider, logService: ILogService, disposables: DisposableStore, onConnectionCountChanged: (count: number) => void): Promise<void> {
260
const port = process.env['VSCODE_AGENT_HOST_PORT'];
261
const socketPath = process.env['VSCODE_AGENT_HOST_SOCKET_PATH'];
262
263
if (!port && !socketPath) {
264
return;
265
}
266
267
const connectionToken = process.env['VSCODE_AGENT_HOST_CONNECTION_TOKEN'];
268
const host = process.env['VSCODE_AGENT_HOST_HOST'] || 'localhost';
269
270
const wsServer = disposables.add(await WebSocketProtocolServer.create(
271
socketPath
272
? {
273
socketPath,
274
connectionTokenValidate: connectionToken
275
? (token) => token === connectionToken
276
: undefined,
277
}
278
: {
279
port: parseInt(port!, 10),
280
host,
281
connectionTokenValidate: connectionToken
282
? (token) => token === connectionToken
283
: undefined,
284
},
285
logService,
286
));
287
288
const protocolHandler = disposables.add(new ProtocolServerHandler(
289
agentService,
290
agentService.stateManager,
291
wsServer,
292
{ defaultDirectory: URI.file(os.homedir()).toString() },
293
clientFileSystemProvider,
294
logService,
295
));
296
disposables.add(protocolHandler.onDidChangeConnectionCount(onConnectionCountChanged));
297
298
const listenTarget = socketPath ?? `${host}:${port}`;
299
logService.info(`[AgentHost] WebSocket server listening on ${listenTarget}`);
300
// Do not change this line. The CLI looks for this in the output.
301
console.log(`Agent host server listening on ${listenTarget}`);
302
}
303
304