@github/copilot-language-server 1.542.0 → 1.544.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,20 +34,19 @@ module.exports = __toCommonJS(client_exports);
34
34
  var import_node_child_process = require("node:child_process");
35
35
  var import_node_crypto = require("node:crypto");
36
36
  var import_node_fs = require("node:fs");
37
- var import_node_module = require("node:module");
38
37
  var import_node_net = require("node:net");
39
38
  var import_node_path = require("node:path");
40
- var import_node_url = require("node:url");
41
39
  var import_node = require("vscode-jsonrpc/node.js");
42
40
  var import_rpc = require("./generated/rpc.js");
43
41
  var import_sdkProtocolVersion = require("./sdkProtocolVersion.js");
44
42
  var import_session = require("./session.js");
43
+ var import_runtimeArtifacts = require("./runtimeArtifacts.js");
44
+ var import_cliVersion = require("./cliVersion.js");
45
45
  var import_sessionFsProvider = require("./sessionFsProvider.js");
46
46
  var import_copilotRequestHandler = require("./copilotRequestHandler.js");
47
47
  var import_telemetry = require("./telemetry.js");
48
48
  var import_toolSet = require("./toolSet.js");
49
49
  var import_types = require("./types.js");
50
- const import_meta = {};
51
50
  const MIN_PROTOCOL_VERSION = 3;
52
51
  const RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
53
52
  function isZodSchema(value) {
@@ -72,7 +71,7 @@ async function waitForChildExit(child, timeoutMs) {
72
71
  if (child.exitCode != null || child.signalCode != null) {
73
72
  return true;
74
73
  }
75
- return new Promise((resolve) => {
74
+ return new Promise((resolve2) => {
76
75
  let timeout;
77
76
  let settled = false;
78
77
  const onExit = () => {
@@ -81,7 +80,7 @@ async function waitForChildExit(child, timeoutMs) {
81
80
  }
82
81
  settled = true;
83
82
  clearTimeout(timeout);
84
- resolve(true);
83
+ resolve2(true);
85
84
  };
86
85
  timeout = setTimeout(() => {
87
86
  if (settled) {
@@ -89,7 +88,7 @@ async function waitForChildExit(child, timeoutMs) {
89
88
  }
90
89
  settled = true;
91
90
  child.off("exit", onExit);
92
- resolve(false);
91
+ resolve2(false);
93
92
  }, timeoutMs);
94
93
  child.once("exit", onExit);
95
94
  if (child.exitCode != null || child.signalCode != null) {
@@ -150,6 +149,15 @@ function toWireCustomAgents(agents) {
150
149
  return { ...rest, mcpServers: toWireMcpServers(mcpServers) };
151
150
  });
152
151
  }
152
+ function clientInfoToWire(info) {
153
+ if (info == null) return void 0;
154
+ const wire = {};
155
+ if (info.applicationName) wire.editorName = info.applicationName;
156
+ if (info.applicationVersion) wire.editorVersion = info.applicationVersion;
157
+ if (info.integrationName) wire.extensionName = info.integrationName;
158
+ if (info.integrationVersion) wire.extensionVersion = info.integrationVersion;
159
+ return Object.keys(wire).length > 0 ? wire : void 0;
160
+ }
153
161
  function toWireLargeOutput(config) {
154
162
  if (!config) return void 0;
155
163
  const { outputDirectory, ...rest } = config;
@@ -207,39 +215,8 @@ function getNodeExecPath() {
207
215
  }
208
216
  return process.execPath;
209
217
  }
210
- function getCliPlatformPackageNames() {
211
- const arch = process.arch;
212
- const variants = process.platform === "linux" ? ["linux", "linuxmusl"] : [process.platform];
213
- return variants.map((variant) => `@github/copilot-${variant}-${arch}`);
214
- }
215
- function getBundledCliPath() {
216
- const packageNames = getCliPlatformPackageNames();
217
- if (typeof import_meta.resolve === "function") {
218
- for (const packageName of packageNames) {
219
- try {
220
- const sdkUrl = import_meta.resolve(`${packageName}/sdk`);
221
- const sdkPath = (0, import_node_url.fileURLToPath)(sdkUrl);
222
- return (0, import_node_path.join)((0, import_node_path.dirname)((0, import_node_path.dirname)(sdkPath)), "index.js");
223
- } catch {
224
- }
225
- }
226
- throw new Error(
227
- `Could not resolve a @github/copilot platform package (tried ${packageNames.join(", ")}). Ensure @github/copilot is installed, or pass cliPath/cliUrl to CopilotClient.`
228
- );
229
- }
230
- const req = (0, import_node_module.createRequire)(__filename);
231
- const searchPaths = req.resolve.paths("@github/copilot") ?? [];
232
- for (const base of searchPaths) {
233
- for (const packageName of packageNames) {
234
- const candidate = (0, import_node_path.join)(base, ...packageName.split("/"), "index.js");
235
- if ((0, import_node_fs.existsSync)(candidate)) {
236
- return candidate;
237
- }
238
- }
239
- }
240
- throw new Error(
241
- `Could not find a @github/copilot platform package (tried ${packageNames.join(", ")}). Searched ${searchPaths.length} paths. Ensure @github/copilot is installed, or pass cliPath/cliUrl to CopilotClient.`
242
- );
218
+ function getBundledRuntimePath() {
219
+ return (0, import_runtimeArtifacts.ensureRuntimeBundle)(import_cliVersion.COPILOT_CLI_VERSION);
243
220
  }
244
221
  class TeardownResilientStreamMessageWriter extends import_node.StreamMessageWriter {
245
222
  suppressWriteErrors = false;
@@ -259,6 +236,7 @@ class CopilotClient {
259
236
  ffiHost = null;
260
237
  connection = null;
261
238
  messageWriter = null;
239
+ connectionClosed = false;
262
240
  socket = null;
263
241
  runtimePort = null;
264
242
  actualHost = "localhost";
@@ -287,6 +265,7 @@ class CopilotClient {
287
265
  _internalRpc = null;
288
266
  processExitPromise = null;
289
267
  // Rejects when CLI process exits
268
+ processTransportError = null;
290
269
  negotiatedProtocolVersion = null;
291
270
  /** Connection-level session filesystem config, set via constructor option. */
292
271
  sessionFsConfig = null;
@@ -294,6 +273,7 @@ class CopilotClient {
294
273
  builtinPluginDirectories = [];
295
274
  onGitHubTelemetry;
296
275
  clientGlobalHandlers = {};
276
+ githubTokenProviders = /* @__PURE__ */ new Map();
297
277
  /**
298
278
  * Typed server-scoped RPC methods.
299
279
  * @throws Error if the client is not connected
@@ -452,7 +432,12 @@ class CopilotClient {
452
432
  const connEnv = conn.kind === "stdio" || conn.kind === "tcp" ? conn.env : void 0;
453
433
  const effectiveEnv = connEnv ?? options.env ?? process.env;
454
434
  this.resolvedEnv = effectiveEnv;
455
- this.resolvedCliPath = conn.kind === "stdio" || conn.kind === "tcp" ? conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath() : void 0;
435
+ if (conn.kind === "stdio" || conn.kind === "tcp") {
436
+ const explicitCliPath = conn.path ?? effectiveEnv.COPILOT_CLI_PATH;
437
+ if (explicitCliPath) {
438
+ this.resolvedCliPath = explicitCliPath;
439
+ }
440
+ }
456
441
  const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
457
442
  this.connectionExtraArgs = [...connArgs];
458
443
  this.options = {
@@ -465,7 +450,8 @@ class CopilotClient {
465
450
  baseDirectory: options.baseDirectory,
466
451
  sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
467
452
  enableRemoteSessions: options.enableRemoteSessions ?? false,
468
- mode: options.mode ?? "copilot-cli"
453
+ mode: options.mode ?? "copilot-cli",
454
+ clientInfo: options.clientInfo
469
455
  };
470
456
  if (this.options.mode === "empty") {
471
457
  const hasPersistence = this.options.baseDirectory !== void 0 || this.sessionFsConfig !== null || // External runtimes manage their own persistence layer; the SDK
@@ -481,17 +467,29 @@ class CopilotClient {
481
467
  connectionExtraArgs = [];
482
468
  /**
483
469
  * Parse CLI URL into host and port
484
- * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
470
+ * Supports formats: "host:port", "[ipv6]:port", "http://host:port", "https://host:port", or just "port"
485
471
  */
486
472
  parseCliUrl(url) {
487
- let cleanUrl = url.replace(/^https?:\/\//, "");
473
+ const cleanUrl = url.replace(/^https?:\/\//, "");
488
474
  if (/^\d+$/.test(cleanUrl)) {
489
475
  return { host: "localhost", port: parseInt(cleanUrl, 10) };
490
476
  }
477
+ const ipv6Match = cleanUrl.match(/^\[([^\]]+)\]:(\d+)$/);
478
+ if (ipv6Match) {
479
+ const host2 = ipv6Match[1];
480
+ if (!(0, import_node_net.isIPv6)(host2)) {
481
+ throw new Error(`Invalid cliUrl format: ${url}`);
482
+ }
483
+ const port2 = parseInt(ipv6Match[2], 10);
484
+ if (isNaN(port2) || port2 <= 0 || port2 > 65535) {
485
+ throw new Error(`Invalid port in cliUrl: ${url}`);
486
+ }
487
+ return { host: host2, port: port2 };
488
+ }
491
489
  const parts = cleanUrl.split(":");
492
490
  if (parts.length !== 2) {
493
491
  throw new Error(
494
- `Invalid cliUrl format: ${url}. Expected "host:port", "http://host:port", or "port"`
492
+ `Invalid cliUrl format: ${url}. Expected "host:port", "[ipv6]:port", "http://host:port", or "port"`
495
493
  );
496
494
  }
497
495
  const host = parts[0] || "localhost";
@@ -551,8 +549,53 @@ class CopilotClient {
551
549
  }
552
550
  };
553
551
  }
552
+ handlers.gitHubToken = {
553
+ getToken: (params) => this.acquireGitHubToken(params)
554
+ };
554
555
  this.clientGlobalHandlers = handlers;
555
556
  }
557
+ async acquireGitHubToken(params) {
558
+ const registration = this.githubTokenProviders.get(params.registrationId);
559
+ if (!registration) {
560
+ throw new Error(
561
+ `No GitHub token provider registered for registration ID "${params.registrationId}"`
562
+ );
563
+ }
564
+ return await registration.provider({
565
+ host: params.host,
566
+ sessionId: params.sessionId ?? registration.sessionId,
567
+ reason: params.reason
568
+ });
569
+ }
570
+ registerGitHubTokenProvider(provider, sessionId) {
571
+ if (!provider) {
572
+ return void 0;
573
+ }
574
+ const registrationId = (0, import_node_crypto.randomUUID)();
575
+ this.githubTokenProviders.set(registrationId, { provider, sessionId, committed: false });
576
+ return registrationId;
577
+ }
578
+ assignGitHubTokenProvider(registrationId, sessionId) {
579
+ if (!registrationId) {
580
+ return;
581
+ }
582
+ const registration = this.githubTokenProviders.get(registrationId);
583
+ if (registration) {
584
+ registration.sessionId = sessionId;
585
+ }
586
+ }
587
+ commitGitHubTokenProvider(sessionId, registrationId) {
588
+ for (const [candidateId, registration2] of this.githubTokenProviders) {
589
+ if (registration2.sessionId === sessionId && registration2.committed) {
590
+ this.githubTokenProviders.delete(candidateId);
591
+ }
592
+ }
593
+ const registration = registrationId ? this.githubTokenProviders.get(registrationId) : void 0;
594
+ if (registration) {
595
+ registration.sessionId = sessionId;
596
+ registration.committed = true;
597
+ }
598
+ }
556
599
  /**
557
600
  * Starts the CLI server and establishes a connection.
558
601
  *
@@ -575,6 +618,9 @@ class CopilotClient {
575
618
  if (this.state === "connected") {
576
619
  return;
577
620
  }
621
+ this.forceStopping = false;
622
+ this.connectionClosed = false;
623
+ this.processTransportError = null;
578
624
  this.state = "connecting";
579
625
  try {
580
626
  if (this.connectionConfig.kind === "inprocess") {
@@ -607,8 +653,10 @@ class CopilotClient {
607
653
  }
608
654
  this.state = "connected";
609
655
  } catch (error) {
656
+ const startupError = this.processTransportError ?? error;
657
+ await this.forceStop();
610
658
  this.state = "error";
611
- throw error;
659
+ throw startupError;
612
660
  }
613
661
  }
614
662
  /**
@@ -653,7 +701,7 @@ class CopilotClient {
653
701
  lastError = error instanceof Error ? error : new Error(String(error));
654
702
  if (attempt < 3) {
655
703
  const delay = 100 * Math.pow(2, attempt - 1);
656
- await new Promise((resolve) => setTimeout(resolve, delay));
704
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
657
705
  }
658
706
  }
659
707
  }
@@ -669,7 +717,8 @@ class CopilotClient {
669
717
  session._markDisconnected();
670
718
  }
671
719
  this.sessions.clear();
672
- if (this.connection && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
720
+ this.githubTokenProviders.clear();
721
+ if (this.connection && !this.connectionClosed && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
673
722
  const runtimeShutdownStart = Date.now();
674
723
  const shutdownPromise = this.rpc.runtime.shutdown();
675
724
  void shutdownPromise.catch(() => void 0);
@@ -719,8 +768,8 @@ class CopilotClient {
719
768
  this.socket = null;
720
769
  try {
721
770
  if (!socket.destroyed) {
722
- await new Promise((resolve) => {
723
- socket.once("close", () => resolve());
771
+ await new Promise((resolve2) => {
772
+ socket.once("close", () => resolve2());
724
773
  socket.end();
725
774
  });
726
775
  }
@@ -823,6 +872,7 @@ class CopilotClient {
823
872
  session._markDisconnected();
824
873
  }
825
874
  this.sessions.clear();
875
+ this.githubTokenProviders.clear();
826
876
  if (this.messageWriter) {
827
877
  this.messageWriter.suppressWriteErrors = true;
828
878
  }
@@ -988,7 +1038,8 @@ class CopilotClient {
988
1038
  *
989
1039
  * In empty mode, defaults the four overridable feature flags to safe values
990
1040
  * (caller values from `config` win). `installedPlugins=[]` is unconditional
991
- * in empty mode — apps that need custom plugins should switch modes.
1041
+ * in empty mode. `includedBuiltinSkills` defaults to `[]`, but callers can
1042
+ * explicitly allow selected runtime-bundled skills.
992
1043
  */
993
1044
  async updateSessionOptionsForMode(session, config) {
994
1045
  const patch = {};
@@ -998,6 +1049,7 @@ class CopilotClient {
998
1049
  patch.coauthorEnabled = config.coauthorEnabled ?? false;
999
1050
  patch.manageScheduleEnabled = config.manageScheduleEnabled ?? false;
1000
1051
  patch.installedPlugins = [];
1052
+ patch.includedBuiltinSkills = config.includedBuiltinSkills ?? [];
1001
1053
  } else {
1002
1054
  if (config.skipCustomInstructions !== void 0)
1003
1055
  patch.skipCustomInstructions = config.skipCustomInstructions;
@@ -1007,6 +1059,8 @@ class CopilotClient {
1007
1059
  patch.coauthorEnabled = config.coauthorEnabled;
1008
1060
  if (config.manageScheduleEnabled !== void 0)
1009
1061
  patch.manageScheduleEnabled = config.manageScheduleEnabled;
1062
+ if (config.includedBuiltinSkills !== void 0)
1063
+ patch.includedBuiltinSkills = config.includedBuiltinSkills;
1010
1064
  }
1011
1065
  if (Object.keys(patch).length === 0) {
1012
1066
  return;
@@ -1022,6 +1076,9 @@ class CopilotClient {
1022
1076
  }
1023
1077
  }
1024
1078
  async createSession(config) {
1079
+ if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
1080
+ throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
1081
+ }
1025
1082
  if (!this.connection) {
1026
1083
  await this.start();
1027
1084
  }
@@ -1032,6 +1089,11 @@ class CopilotClient {
1032
1089
  const callerSessionId = config.sessionId;
1033
1090
  const useServerGeneratedId = config.cloud != null && callerSessionId == null;
1034
1091
  const localSessionId = useServerGeneratedId ? void 0 : callerSessionId ?? (0, import_node_crypto.randomUUID)();
1092
+ const toolFilterOptions = this.resolveToolFilterOptions(config);
1093
+ const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider(
1094
+ config.gitHubTokenProvider,
1095
+ localSessionId
1096
+ );
1035
1097
  const {
1036
1098
  wireProvider: bearerWireProvider,
1037
1099
  wireProviders: bearerWireProviders,
@@ -1048,7 +1110,10 @@ class CopilotClient {
1048
1110
  this.onGetTraceContext,
1049
1111
  {
1050
1112
  mcpAuthHandler: config.onMcpAuthRequest,
1051
- managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0
1113
+ managedSettingsEnabled: config.enableManagedSettings === true || config.managedSettings !== void 0,
1114
+ onDisconnected: gitHubTokenProviderRegistrationId === void 0 ? void 0 : () => this.githubTokenProviders.delete(
1115
+ gitHubTokenProviderRegistrationId
1116
+ )
1052
1117
  }
1053
1118
  );
1054
1119
  s.registerTools(config.tools);
@@ -1086,10 +1151,16 @@ class CopilotClient {
1086
1151
  let session;
1087
1152
  let registeredId;
1088
1153
  if (localSessionId !== void 0) {
1089
- session = initializeSession(localSessionId);
1090
- registeredId = localSessionId;
1154
+ try {
1155
+ session = initializeSession(localSessionId);
1156
+ registeredId = localSessionId;
1157
+ } catch (error) {
1158
+ if (gitHubTokenProviderRegistrationId !== void 0) {
1159
+ this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
1160
+ }
1161
+ throw error;
1162
+ }
1091
1163
  }
1092
- const toolFilterOptions = this.resolveToolFilterOptions(config);
1093
1164
  try {
1094
1165
  const response = await this.connection.sendRequest("session.create", {
1095
1166
  ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
@@ -1119,7 +1190,7 @@ class CopilotClient {
1119
1190
  canvasProvider: config.canvasProvider,
1120
1191
  commands: config.commands?.map((cmd) => ({
1121
1192
  name: cmd.name,
1122
- description: cmd.description
1193
+ description: cmd.description ?? ""
1123
1194
  })),
1124
1195
  systemMessage: wireSystemMessage,
1125
1196
  availableTools: toolFilterOptions.availableTools,
@@ -1139,6 +1210,7 @@ class CopilotClient {
1139
1210
  requestPermission: !!config.onPermissionRequest,
1140
1211
  requestUserInput: !!config.onUserInputRequest,
1141
1212
  requestElicitation: !!config.onElicitationRequest,
1213
+ askUserVariant: config.askUserVariant,
1142
1214
  ...config.enableMcpApps ? { requestMcpApps: true } : {},
1143
1215
  ...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
1144
1216
  requestExitPlanMode: !!config.onExitPlanModeRequest,
@@ -1174,8 +1246,10 @@ class CopilotClient {
1174
1246
  infiniteSessions: config.infiniteSessions,
1175
1247
  memory: config.memory,
1176
1248
  gitHubToken: config.gitHubToken,
1249
+ gitHubTokenProviderRegistrationId,
1177
1250
  remoteSession: config.remoteSession,
1178
1251
  cloud: config.cloud,
1252
+ featureFlags: config.featureFlags,
1179
1253
  expAssignments: config.expAssignments,
1180
1254
  enableManagedSettings: config.enableManagedSettings,
1181
1255
  managedSettings: config.managedSettings
@@ -1197,6 +1271,7 @@ class CopilotClient {
1197
1271
  session = initializeSession(returnedSessionId);
1198
1272
  registeredId = returnedSessionId;
1199
1273
  }
1274
+ this.assignGitHubTokenProvider(gitHubTokenProviderRegistrationId, returnedSessionId);
1200
1275
  if (config.onMcpAuthRequest) {
1201
1276
  await this.connection.sendRequest("session.eventLog.registerInterest", {
1202
1277
  sessionId: returnedSessionId,
@@ -1206,10 +1281,15 @@ class CopilotClient {
1206
1281
  session["_workspacePath"] = workspacePath;
1207
1282
  session.setCapabilities(capabilities);
1208
1283
  await this.updateSessionOptionsForMode(session, config);
1284
+ this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId);
1209
1285
  } catch (e) {
1286
+ session?._markDisconnected();
1210
1287
  if (registeredId !== void 0) {
1211
1288
  this.sessions.delete(registeredId);
1212
1289
  }
1290
+ if (gitHubTokenProviderRegistrationId !== void 0) {
1291
+ this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
1292
+ }
1213
1293
  throw e;
1214
1294
  }
1215
1295
  return session;
@@ -1242,10 +1322,13 @@ class CopilotClient {
1242
1322
  return this.resumeSessionInternal(sessionId, config);
1243
1323
  }
1244
1324
  /** @internal */
1245
- async resumeSessionForExtension(sessionId, config, factories) {
1246
- return this.resumeSessionInternal(sessionId, config, factories);
1325
+ async resumeSessionForExtension(sessionId, config, factories, extensionOptions) {
1326
+ return this.resumeSessionInternal(sessionId, config, factories, extensionOptions);
1247
1327
  }
1248
- async resumeSessionInternal(sessionId, config, factories) {
1328
+ async resumeSessionInternal(sessionId, config, factories, extensionOptions) {
1329
+ if (config.gitHubToken !== void 0 && config.gitHubTokenProvider !== void 0) {
1330
+ throw new Error("gitHubToken and gitHubTokenProvider are mutually exclusive");
1331
+ }
1249
1332
  if (!this.connection) {
1250
1333
  await this.start();
1251
1334
  }
@@ -1303,6 +1386,15 @@ class CopilotClient {
1303
1386
  this.sessions.set(sessionId, session);
1304
1387
  this.setupSessionFs(session, config);
1305
1388
  const toolFilterOptions = this.resolveToolFilterOptions(config);
1389
+ const gitHubTokenProviderRegistrationId = this.registerGitHubTokenProvider(
1390
+ config.gitHubTokenProvider,
1391
+ sessionId
1392
+ );
1393
+ if (gitHubTokenProviderRegistrationId !== void 0) {
1394
+ session._setOnDisconnected(
1395
+ () => this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId)
1396
+ );
1397
+ }
1306
1398
  try {
1307
1399
  const response = await this.connection.sendRequest("session.resume", {
1308
1400
  ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
@@ -1342,7 +1434,7 @@ class CopilotClient {
1342
1434
  canvasProvider: config.canvasProvider,
1343
1435
  commands: config.commands?.map((cmd) => ({
1344
1436
  name: cmd.name,
1345
- description: cmd.description
1437
+ description: cmd.description ?? ""
1346
1438
  })),
1347
1439
  provider: bearerWireProvider,
1348
1440
  capi: config.capi,
@@ -1353,6 +1445,7 @@ class CopilotClient {
1353
1445
  requestPermission: config.onPermissionRequest !== import_types.defaultJoinSessionPermissionHandler,
1354
1446
  requestUserInput: !!config.onUserInputRequest,
1355
1447
  requestElicitation: !!config.onElicitationRequest,
1448
+ askUserVariant: config.askUserVariant,
1356
1449
  ...config.enableMcpApps ? { requestMcpApps: true } : {},
1357
1450
  ...config.githubMcpToolConfig != null ? { githubMcpToolConfig: config.githubMcpToolConfig } : {},
1358
1451
  requestExitPlanMode: !!config.onExitPlanModeRequest,
@@ -1390,12 +1483,26 @@ class CopilotClient {
1390
1483
  disableResume: config.suppressResumeEvent,
1391
1484
  continuePendingWork: config.continuePendingWork,
1392
1485
  gitHubToken: config.gitHubToken,
1486
+ gitHubTokenProviderRegistrationId,
1393
1487
  remoteSession: config.remoteSession,
1394
1488
  openCanvases: config.openCanvases,
1489
+ featureFlags: config.featureFlags,
1395
1490
  expAssignments: config.expAssignments,
1396
1491
  enableManagedSettings: config.enableManagedSettings,
1397
- managedSettings: config.managedSettings
1492
+ managedSettings: config.managedSettings,
1493
+ ...extensionOptions?.requestedEnvironmentVariables ? {
1494
+ requestedEnvironmentVariables: extensionOptions.requestedEnvironmentVariables
1495
+ } : {}
1398
1496
  });
1497
+ if (extensionOptions?.requestedEnvironmentVariables) {
1498
+ const requested = new Set(extensionOptions.requestedEnvironmentVariables);
1499
+ const { grantedEnvironmentVariables } = response;
1500
+ for (const [name, value] of Object.entries(grantedEnvironmentVariables ?? {})) {
1501
+ if (requested.has(name)) {
1502
+ process.env[name] = value;
1503
+ }
1504
+ }
1505
+ }
1399
1506
  const { workspacePath, capabilities, openCanvases } = response;
1400
1507
  session["_workspacePath"] = workspacePath;
1401
1508
  session.setCapabilities(capabilities);
@@ -1407,8 +1514,13 @@ class CopilotClient {
1407
1514
  });
1408
1515
  }
1409
1516
  await this.updateSessionOptionsForMode(session, config);
1517
+ this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
1410
1518
  } catch (e) {
1519
+ session._markDisconnected();
1411
1520
  this.sessions.delete(sessionId);
1521
+ if (gitHubTokenProviderRegistrationId !== void 0) {
1522
+ this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
1523
+ }
1412
1524
  throw e;
1413
1525
  }
1414
1526
  return session;
@@ -1467,8 +1579,8 @@ class CopilotClient {
1467
1579
  async listModels() {
1468
1580
  await this.modelsCacheLock;
1469
1581
  let resolveLock;
1470
- this.modelsCacheLock = new Promise((resolve) => {
1471
- resolveLock = resolve;
1582
+ this.modelsCacheLock = new Promise((resolve2) => {
1583
+ resolveLock = resolve2;
1472
1584
  });
1473
1585
  try {
1474
1586
  if (this.modelsCache !== null) {
@@ -1520,10 +1632,17 @@ class CopilotClient {
1520
1632
  const raceAgainstExit = (p) => this.processExitPromise ? Promise.race([p, this.processExitPromise]) : p;
1521
1633
  let serverVersion;
1522
1634
  try {
1523
- const connectParams = { token: this.effectiveConnectionToken };
1635
+ const connectParams = {
1636
+ token: this.effectiveConnectionToken,
1637
+ supportedTaskKinds: ["agent", "client", "shell"]
1638
+ };
1524
1639
  if (this.onGitHubTelemetry != null) {
1525
1640
  connectParams.enableGitHubTelemetryForwarding = true;
1526
1641
  }
1642
+ const clientInfo = clientInfoToWire(this.options.clientInfo);
1643
+ if (clientInfo != null) {
1644
+ connectParams.clientInfo = clientInfo;
1645
+ }
1527
1646
  const result = await raceAgainstExit(this.internalRpc.connect(connectParams));
1528
1647
  serverVersion = result.protocolVersion;
1529
1648
  } catch (err) {
@@ -1597,7 +1716,9 @@ class CopilotClient {
1597
1716
  if (!success) {
1598
1717
  throw new Error(`Failed to delete session ${sessionId}: ${error || "Unknown error"}`);
1599
1718
  }
1719
+ const session = this.sessions.get(sessionId);
1600
1720
  this.sessions.delete(sessionId);
1721
+ session?._runOnDisconnected();
1601
1722
  }
1602
1723
  /**
1603
1724
  * List all available sessions.
@@ -1782,7 +1903,8 @@ class CopilotClient {
1782
1903
  * Start the CLI server process
1783
1904
  */
1784
1905
  async startCLIServer() {
1785
- return new Promise((resolve, reject) => {
1906
+ this.resolvedCliPath ??= await getBundledRuntimePath();
1907
+ return new Promise((resolve2, reject) => {
1786
1908
  this.stderrBuffer = "";
1787
1909
  const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
1788
1910
  if (this.options.logLevel) {
@@ -1819,7 +1941,7 @@ class CopilotClient {
1819
1941
  }
1820
1942
  if (!(0, import_node_fs.existsSync)(this.resolvedCliPath)) {
1821
1943
  throw new Error(
1822
- `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
1944
+ `Copilot CLI not found at ${this.resolvedCliPath}. Set COPILOT_CLI_PATH to use a custom installation.`
1823
1945
  );
1824
1946
  }
1825
1947
  const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
@@ -1843,7 +1965,7 @@ class CopilotClient {
1843
1965
  let resolved = false;
1844
1966
  if (this.connectionConfig.kind === "stdio") {
1845
1967
  resolved = true;
1846
- resolve();
1968
+ resolve2();
1847
1969
  } else {
1848
1970
  this.cliProcess.stdout?.on("data", (data) => {
1849
1971
  stdout += data.toString();
@@ -1851,7 +1973,7 @@ class CopilotClient {
1851
1973
  if (match && !resolved) {
1852
1974
  this.runtimePort = parseInt(match[1], 10);
1853
1975
  resolved = true;
1854
- resolve();
1976
+ resolve2();
1855
1977
  }
1856
1978
  });
1857
1979
  }
@@ -1883,26 +2005,27 @@ stderr: ${stderrOutput}`
1883
2005
  });
1884
2006
  this.processExitPromise = new Promise((_, rejectProcessExit) => {
1885
2007
  this.cliProcess.on("exit", (code) => {
1886
- setTimeout(() => {
1887
- const stderrOutput = this.stderrBuffer.trim();
1888
- if (stderrOutput) {
1889
- rejectProcessExit(
1890
- new Error(
1891
- `CLI server exited with code ${code}
2008
+ if (this.messageWriter) {
2009
+ this.messageWriter.suppressWriteErrors = true;
2010
+ }
2011
+ const stderrOutput = this.stderrBuffer.trim();
2012
+ if (stderrOutput) {
2013
+ rejectProcessExit(
2014
+ new Error(
2015
+ `CLI server exited with code ${code}
1892
2016
  stderr: ${stderrOutput}`
1893
- )
1894
- );
1895
- } else {
1896
- rejectProcessExit(
1897
- new Error(`CLI server exited unexpectedly with code ${code}`)
1898
- );
1899
- }
1900
- }, 50);
2017
+ )
2018
+ );
2019
+ } else {
2020
+ rejectProcessExit(
2021
+ new Error(`CLI server exited unexpectedly with code ${code}`)
2022
+ );
2023
+ }
1901
2024
  });
1902
2025
  });
1903
2026
  this.processExitPromise.catch(() => {
1904
2027
  });
1905
- this.cliProcess.on("exit", (code) => {
2028
+ this.cliProcess.on("close", (code) => {
1906
2029
  if (!resolved) {
1907
2030
  resolved = true;
1908
2031
  const stderrOutput = this.stderrBuffer.trim();
@@ -1944,7 +2067,20 @@ stderr: ${stderrOutput}`
1944
2067
  }
1945
2068
  /** Starts the in-process FFI runtime with SDK-managed typed options. */
1946
2069
  async startInProcessFfi() {
1947
- const entrypoint = this.resolveCliPathForFfi();
2070
+ const explicitEntrypoint = this.resolvedEnv.COPILOT_CLI_PATH;
2071
+ let runtimeLibrary;
2072
+ if (explicitEntrypoint) {
2073
+ const entrypointDirectory = (0, import_node_path.dirname)((0, import_node_path.resolve)(explicitEntrypoint));
2074
+ const adjacentRuntime = (0, import_node_path.join)(entrypointDirectory, "runtime.node");
2075
+ runtimeLibrary = (0, import_node_fs.existsSync)(adjacentRuntime) ? adjacentRuntime : (0, import_node_path.join)(
2076
+ entrypointDirectory,
2077
+ "prebuilds",
2078
+ CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint),
2079
+ "runtime.node"
2080
+ );
2081
+ } else {
2082
+ runtimeLibrary = (0, import_node_path.join)((0, import_node_path.dirname)(await getBundledRuntimePath()), "runtime.node");
2083
+ }
1948
2084
  const { FfiRuntimeHost } = await import("./ffiRuntimeHost.js");
1949
2085
  const environment = {};
1950
2086
  if (this.options.gitHubToken) {
@@ -1972,12 +2108,7 @@ stderr: ${stderrOutput}`
1972
2108
  if (this.options.enableRemoteSessions) {
1973
2109
  args.push("--remote");
1974
2110
  }
1975
- const host = FfiRuntimeHost.create(
1976
- entrypoint,
1977
- CopilotClient.getNapiPrebuildsFolder(entrypoint),
1978
- environment,
1979
- args
1980
- );
2111
+ const host = FfiRuntimeHost.create(runtimeLibrary, explicitEntrypoint, environment, args);
1981
2112
  this.ffiHost = host;
1982
2113
  await host.start();
1983
2114
  }
@@ -1997,19 +2128,6 @@ stderr: ${stderrOutput}`
1997
2128
  this.attachConnectionHandlers();
1998
2129
  this.connection.listen();
1999
2130
  }
2000
- /**
2001
- * Resolves the CLI entrypoint used for in-process FFI hosting: `COPILOT_CLI_PATH`
2002
- * when set, otherwise the bundled platform-package entrypoint.
2003
- */
2004
- resolveCliPathForFfi() {
2005
- return this.resolvedEnv.COPILOT_CLI_PATH ?? getBundledCliPath();
2006
- }
2007
- /**
2008
- * Returns the napi prebuilds folder name for the current host — the
2009
- * `<node-platform>-<arch>` convention (e.g. `win32-x64`, `darwin-arm64`,
2010
- * `linux-x64`, `linuxmusl-x64`) under which the runtime ships
2011
- * `prebuilds/<folder>/runtime.node`.
2012
- */
2013
2131
  static getNapiPrebuildsFolder(entrypoint) {
2014
2132
  const arch = process.arch;
2015
2133
  if (arch !== "x64" && arch !== "arm64") {
@@ -2045,6 +2163,11 @@ stderr: ${stderrOutput}`
2045
2163
  }
2046
2164
  this.state = "error";
2047
2165
  const reason = err instanceof Error ? err.stack ?? err.message : String(err);
2166
+ const stderrOutput = this.stderrBuffer.trim();
2167
+ this.processTransportError = new Error(
2168
+ `CLI server connection failed: ${reason}${stderrOutput ? `
2169
+ stderr: ${stderrOutput}` : ""}`
2170
+ );
2048
2171
  this.logDebug(`stdin pipe error: ${reason}`);
2049
2172
  try {
2050
2173
  this.connection?.dispose();
@@ -2081,7 +2204,7 @@ stderr: ${stderrOutput}`
2081
2204
  if (!this.runtimePort) {
2082
2205
  throw new Error("Server port not available");
2083
2206
  }
2084
- return new Promise((resolve, reject) => {
2207
+ return new Promise((resolve2, reject) => {
2085
2208
  this.socket = new import_node_net.Socket();
2086
2209
  const connectionTimeout = setTimeout(() => {
2087
2210
  this.socket?.destroy();
@@ -2096,7 +2219,7 @@ stderr: ${stderrOutput}`
2096
2219
  );
2097
2220
  this.attachConnectionHandlers();
2098
2221
  this.connection.listen();
2099
- resolve();
2222
+ resolve2();
2100
2223
  });
2101
2224
  this.socket.on("error", (error) => {
2102
2225
  clearTimeout(connectionTimeout);
@@ -2143,11 +2266,24 @@ stderr: ${stderrOutput}`
2143
2266
  return await this.handleHooksInvoke(params);
2144
2267
  }
2145
2268
  );
2146
- this.connection.onClose(() => {
2147
- this.state = "disconnected";
2148
- });
2149
- this.connection.onError((_error) => {
2269
+ const connection = this.connection;
2270
+ const markDisconnected = () => {
2271
+ if (this.connection !== connection) {
2272
+ return;
2273
+ }
2274
+ this.connectionClosed = true;
2150
2275
  this.state = "disconnected";
2276
+ for (const session of this.sessions.values()) {
2277
+ session._markDisconnected();
2278
+ }
2279
+ this.sessions.clear();
2280
+ this.githubTokenProviders.clear();
2281
+ };
2282
+ this.connection.onClose(markDisconnected);
2283
+ this.connection.onError(() => {
2284
+ if (this.connection === connection) {
2285
+ this.state = "disconnected";
2286
+ }
2151
2287
  });
2152
2288
  }
2153
2289
  handleSessionEventNotification(notification) {