copilotAgent.ts ×22

Frontier kind: Code frontier

unlabeled · c_1f2c84d63a61

85 tests · 51426 LOC · 300 files · introduces 0 tests · 160 LOC · 1 file

Introduces — evidence that enters the hierarchy at this concept

Code
22 ranges160 lines · 1 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
4906 ranges51426 lines · 300 files · Browse complete extent
All tests (intent)
85 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

1 file ranked by introduced lines: 160 introduced LOC across 22 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/agentHost/node/copilot/copilotAgent.ts 160 introduced LOC · 22 ranges

Open complete file

99 const COPILOT_PROXY_SET_ENV_KEYS = ['HTTP_PROXY', 'HTTPS_PROXY'] as const;
100
101 > async function fileExists(filePath: string): Promise<boolean> { copilotAgent.ts
102 > try {
103 > await fs.access(filePath);
104 > return true;
105 > } catch {
106 return false;
107 }
108 > } copilotAgent.ts
109
110 > function isLinuxMuslRuntime(): boolean { copilotAgent.ts
111 > if (process.platform !== 'linux') {
112 return false;
113 }
115 > const report = process.report?.getReport() as { header?: { glibcVersionRuntime?: string } } | undefined;
116 > return !report?.header?.glibcVersionRuntime;
117 > }
118
119 > function getCopilotPlatformPackageCandidates(): string[] { copilotAgent.ts
120 > const platformArch = `${process.platform}-${process.arch}`;
121 > if (process.platform !== 'linux') {
122 return [platformArch];
123 }
125 > const linuxCandidates = [`linux-${process.arch}`, `linuxmusl-${process.arch}`];
126 > return isLinuxMuslRuntime() ? linuxCandidates.reverse() : linuxCandidates;
127 > }
128
129 > async function resolveCopilotCliPath(nodeModulesUri: URI): Promise<string> { copilotAgent.ts
130 > const tried: string[] = [];
131 > for (const platformPackage of getCopilotPlatformPackageCandidates()) {
132 > const cliPath = URI.joinPath(nodeModulesUri, '@github', `copilot-${platformPackage}`, 'index.js').fsPath;
133 > tried.push(cliPath);
134 > if (await fileExists(cliPath)) {
135 > return cliPath;
136 > }
137 > }
138
139 const oldTopLevelPath = URI.joinPath(nodeModulesUri, '@github', 'copilot', 'index.js').fsPath;
526
527 private _resolveCopilotSdkLogLevel(configured: CopilotSdkLogLevelSetting): NonNullable<CopilotClientOptions['logLevel']> {
528 > return configured === 'trace' || this._logService.getLevel() === LogLevel.Trace ? 'all' : 'info'; copilotAgent.ts
529 > }
530
531 private _getEnterpriseHost(): string | undefined {
1044
1045 private async _ensureClient(): Promise<CopilotClient> {
1046 > if (this._shutdownPromise) { copilotAgent.ts
1047 throw new CancellationError();
1048 }
1049 > while (this._clientStopping) { copilotAgent.ts
1050 await this._clientStopping;
1051 if (this._shutdownPromise) {
1053 }
1054 }
1055 > if (this._client) { copilotAgent.ts
1056 return this._client;
1057 }
1058 > if (this._clientStarting) { copilotAgent.ts
1059 return this._clientStarting;
1060 }
1061 > // Snapshot the startup config so we can detect a change that lands while the copilotAgent.ts
1062 > // client is still starting and abort the stale start (the values are baked
1063 > // into the client options / subprocess env below).
1064 > const sessionSyncAtStartup = this._isSessionSyncEnabled();
1065 > const rubberDuckAtStartup = this._isRubberDuckEnabled();
1066 > const copilotSdkLogLevelSettingAtStartup = this._getCopilotSdkLogLevelSetting();
1067 > const enterpriseHostAtStartup = this._getEnterpriseHost();
1068 > const systemProxyEnabledAtStartup = this._isSystemProxyEnabled();
1069 > const clientStarting = (async () => {
1070 > this._logService.info('[Copilot] Starting CopilotClient...');
1071 >
1072 > // Build a clean env for the CLI subprocess, stripping Electron/VS Code vars
1073 > // that can interfere with the Node.js process the SDK spawns.
1074 > const env: Record<string, string | undefined> = Object.assign({}, process.env, { ELECTRON_RUN_AS_NODE: '1' });
1075 > delete env['NODE_OPTIONS'];
1076 > delete env['VSCODE_INSPECTOR_OPTIONS'];
1077 > delete env['VSCODE_ESM_ENTRYPOINT'];
1078 > delete env['VSCODE_HANDLES_UNCAUGHT_ERRORS'];
1079 > for (const key of Object.keys(env)) {
1080 > if (key === 'ELECTRON_RUN_AS_NODE') {
1081 > continue;
1082 > }
1083 > if (key === 'VSCODE_AGENT_HOST_CAPI_URL_OVERRIDE') {
1084 // used for running the CLI in a test harness against a mock CAPI server
1085 continue;
1086 }
1087 > if (key.startsWith('VSCODE_') || key.startsWith('ELECTRON_')) { copilotAgent.ts
1088 delete env[key];
1089 }
1090 > } copilotAgent.ts
1091 > env['COPILOT_CLI_RUN_AS_NODE'] = '1';
1092 > env['USE_BUILTIN_RIPGREP'] = 'false';
1093 > env['COPILOT_MCP_APPS'] = 'true';
1094 > // Attribute the CLI and everything it spawns (`gh`, …) back to
1095 > // VS Code. Already inherited via `process.env` (the strip loop only
1096 > // removes `VSCODE_*`/`ELECTRON_*`); set here as defense in depth.
1097 > env[AiAgentEnvVar] = AiAgentEnvValue;
1098 > // Required by the currently bundled SDK to enable its experimental auto-approval judge.
1099 > env['AUTO_APPROVAL'] = 'true';
1100 > await this._configureProxyEnv(env);
1101 >
1102 > // On Linux the MXC bubblewrap sandbox backend does not forward a PTY into
1103 > // the container, so the CLI's default PTY-backed interactive shell can
1104 > // never start bash under the sandbox: the inner shell sees a non-tty
1105 > // stdin, runs non-interactively, reads EOF and exits immediately, which
1106 > // surfaces as "Failed to start bash process". Force the CLI's pipe-based
1107 > // spawn shell backend (`SHELL_SPAWN_BACKEND`), which runs each command as
1108 > // a one-shot child process and works correctly under bubblewrap. The CLI
1109 > // already force-enables this on Alpine/musl; glibc Linux needs it too for
1110 > // sandboxed shells. This becomes a no-op once the bundled CLI defaults the
1111 > // spawn backend on for all of Linux.
1112 > if (process.platform === 'linux') {
1113 > const enabledFlags = env['COPILOT_CLI_ENABLED_FEATURE_FLAGS'];
1114 > const flags = new Set((enabledFlags ?? '').split(',').map(f => f.trim()).filter(Boolean));
1115 > flags.add('SHELL_SPAWN_BACKEND');
1116 > env['COPILOT_CLI_ENABLED_FEATURE_FLAGS'] = [...flags].join(',');
1117 > }
1118 >
1119 > // Identify VS Code's agent host traffic in CAPI
1120 > env['GITHUB_COPILOT_INTEGRATION_ID'] = COPILOT_INTEGRATION_ID;
1121 > this._logService.info(`[Copilot] Set CLI env: GITHUB_COPILOT_INTEGRATION_ID=${COPILOT_INTEGRATION_ID}`);
1122 >
1123 > // Point the Copilot CLI at a configured GitHub Enterprise host for its
1124 > // authentication and CAPI endpoint discovery. `COPILOT_GH_HOST` is
1125 > // Copilot-CLI-specific (it does not affect the `gh` CLI). Unset for
1126 > // github.com so the CLI uses its default host.
1127 > const enterpriseHost = this._getEnterpriseHost();
1128 > if (enterpriseHost) {
1129 env['COPILOT_GH_HOST'] = enterpriseHost;
1130 this._logService.info(`[Copilot] Set CLI env: COPILOT_GH_HOST=${enterpriseHost}`);
1131 }
1133 > // Enable the rubber duck critic subagent in the CLI when the agent host
1134 > // config opts in. `RUBBER_DUCK_AGENT` is the SDK's required interface for
1135 > // gating this experimental feature
1136 > if (this._isRubberDuckEnabled()) {
1137 env['RUBBER_DUCK_AGENT'] = 'true';
1138 > } else { copilotAgent.ts
1139 > delete env['RUBBER_DUCK_AGENT'];
1140 > }
1141 >
1142 > // Resolve the CLI entry point and native SDK binaries from node_modules.
1143 > // In the desktop app these live next to the ASAR archive in
1144 > // `node_modules.asar.unpacked` (the `@github/copilot-<platform>` CLI and
1145 > // the `@microsoft/mxc-sdk/bin` executables are unpacked so they can be
1146 > // spawned), while in dev and on the server (which has no ASAR) they live
1147 > // in a plain `node_modules`.
1148 > // We can't use require.resolve() because @github/copilot's exports map
1149 > // blocks direct subpath access.
1150 > const nodeModulesUri = FileAccess.asFileUri(getAppNodeModulesPath());
1151 > const cliPath = await resolveCopilotCliPath(nodeModulesUri);
1152 >
1153 > // The SDK's sandbox auto-detection looks for `<MXC_BIN_DIR>/<arch>/wxc-exec.exe`
1154 > // (and the Linux/macOS equivalents). VS Code core ships the MXC sandbox binaries
1155 > // at `<nodeModules>/@microsoft/mxc-sdk/bin/<arch>/`, so point `MXC_BIN_DIR` there.
1156 > // The @github/copilot package's own `mxc-bin/` is excluded from the product build
1157 > // (see build/.moduleignore), mirroring `CopilotCLISDK.getPackage` in the extension.
1158 > env['MXC_BIN_DIR'] = URI.joinPath(nodeModulesUri, '@microsoft', 'mxc-sdk', 'bin').fsPath;
1159 >
1160 > // Add VS Code's built-in ripgrep to PATH so the CLI subprocess can find it.
1161 > const resolvedRgDiskPath = await rgDiskPath();
1162 > const rgDir = dirname(resolvedRgDiskPath);
1163 > // On Windows the env key is typically "Path" (not "PATH"). Since we copied
1164 > // process.env into a plain (case-sensitive) object, we must find the actual key.
1165 > const pathKey = Object.keys(env).find(k => k.toUpperCase() === 'PATH') ?? 'PATH';
1166 > const currentPath = env[pathKey];
1167 > env[pathKey] = currentPath ? `${currentPath}${delimiter}${rgDir}` : rgDir;
1168 > this._logService.info(`[Copilot] Resolved CLI path: ${cliPath}`);
1169 >
1170 > const telemetry = await this._otelService.getSdkTelemetryConfig();
1171 > const copilotSdkLogLevelAtStartup = this._resolveCopilotSdkLogLevel(copilotSdkLogLevelSettingAtStartup);
1172 >
1173 > const clientOptions: CopilotClientOptions = {
1174 > useLoggedInUser: false,
1175 > connection: RuntimeConnection.forStdio({ path: cliPath }),
1176 > env,
1177 > telemetry,
1178 > logLevel: copilotSdkLogLevelAtStartup,
1179 > enableRemoteSessions: sessionSyncAtStartup,
1180 > onGitHubTelemetry: notification => this._routeGitHubTelemetry(notification),
1181 > };
1182 > const client = this._createCopilotClient(clientOptions);
1183 > await client.start();
1184 > if (this._shutdownPromise) {
1185 await client.stop();
1186 throw new CancellationError();
1187 }
1188 > if (this._isSessionSyncEnabled() !== sessionSyncAtStartup || this._isRubberDuckEnabled() !== rubberDuckAtStartup || this._getCopilotSdkLogLevelSetting() !== copilotSdkLogLevelSettingAtStartup || this._getEnterpriseHost() !== enterpriseHostAtStartup || this._isSystemProxyEnabled() !== systemProxyEnabledAtStartup) { copilotAgent.ts
1189 await client.stop();
1190 throw new Error('Copilot startup config changed while the client was starting');
1195 this._clientStarting = undefined;
1196 return client;
1197 > })(); copilotAgent.ts
1198 > this._clientStarting = clientStarting;
1199 > void clientStarting.catch(() => {
1200 this._clientStarting = undefined;
1201 > }); copilotAgent.ts
1202 > return clientStarting;
1203 > }
1204
1205 // ---- session management -------------------------------------------------
2869
2870 private async _configureProxyEnv(env: Record<string, string | undefined>): Promise<void> {
2871 > const proxy = await this._resolveProxyForSdk(env); copilotAgent.ts
2872 > this._appliedProxy = proxy;
2873 > if (proxy) {
2874 for (const key of COPILOT_PROXY_SET_ENV_KEYS) {
2875 env[key] = proxy;
2877 this._logService.info('[Copilot] Resolved CAPI proxy and forwarded HTTP_PROXY/HTTPS_PROXY to Copilot SDK');
2878 }
2879 > } copilotAgent.ts
2880
2881 private async _resolveProxyForSdk(env: Record<string, string | undefined> = process.env): Promise<string | undefined> {