@github/copilot-sdk 1.0.13-preview.4 → 1.0.13

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.
package/dist/client.js CHANGED
@@ -1,10 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { existsSync } from "node:fs";
4
- import { createRequire } from "node:module";
5
- import { Socket } from "node:net";
4
+ import { isIPv6, Socket } from "node:net";
6
5
  import { dirname, isAbsolute, join, resolve } from "node:path";
7
- import { fileURLToPath } from "node:url";
8
6
  import {
9
7
  createMessageConnection,
10
8
  ErrorCodes,
@@ -20,7 +18,8 @@ import {
20
18
  } from "./generated/rpc.js";
21
19
  import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
22
20
  import { CopilotSession } from "./session.js";
23
- import { materializeRuntimeBundle } from "./runtimeArtifacts.js";
21
+ import { ensureRuntimeBundle } from "./runtimeArtifacts.js";
22
+ import { COPILOT_CLI_VERSION } from "./cliVersion.js";
24
23
  import { createSessionFsAdapter } from "./sessionFsProvider.js";
25
24
  import { createCopilotRequestAdapter } from "./copilotRequestHandler.js";
26
25
  import { getTraceContext } from "./telemetry.js";
@@ -128,6 +127,15 @@ function toWireCustomAgents(agents) {
128
127
  return { ...rest, mcpServers: toWireMcpServers(mcpServers) };
129
128
  });
130
129
  }
130
+ function clientInfoToWire(info) {
131
+ if (info == null) return void 0;
132
+ const wire = {};
133
+ if (info.applicationName) wire.editorName = info.applicationName;
134
+ if (info.applicationVersion) wire.editorVersion = info.applicationVersion;
135
+ if (info.integrationName) wire.extensionName = info.integrationName;
136
+ if (info.integrationVersion) wire.extensionVersion = info.integrationVersion;
137
+ return Object.keys(wire).length > 0 ? wire : void 0;
138
+ }
131
139
  function toWireLargeOutput(config) {
132
140
  if (!config) return void 0;
133
141
  const { outputDirectory, ...rest } = config;
@@ -185,53 +193,8 @@ function getNodeExecPath() {
185
193
  }
186
194
  return process.execPath;
187
195
  }
188
- function getCliPlatformPackageNames() {
189
- const arch = process.arch;
190
- const variants = process.platform === "linux" ? ["linux", "linuxmusl"] : [process.platform];
191
- return variants.map((variant) => `@github/copilot-${variant}-${arch}`);
192
- }
193
- function getBundledCliPackage() {
194
- const packageNames = getCliPlatformPackageNames();
195
- if (typeof import.meta.resolve === "function") {
196
- for (const packageName of packageNames) {
197
- try {
198
- const packageEntryUrl = import.meta.resolve(packageName);
199
- const packageEntryPath = fileURLToPath(packageEntryUrl);
200
- return {
201
- root: dirname(packageEntryPath),
202
- platform: packageName.slice("@github/copilot-".length)
203
- };
204
- } catch {
205
- }
206
- }
207
- throw new Error(
208
- `Could not resolve a @github/copilot platform package (tried ${packageNames.join(", ")}). Ensure @github/copilot is installed, or pass cliPath/cliUrl to CopilotClient.`
209
- );
210
- }
211
- const req = createRequire(__filename);
212
- const searchPaths = req.resolve.paths("@github/copilot") ?? [];
213
- for (const base of searchPaths) {
214
- for (const packageName of packageNames) {
215
- const root = join(base, ...packageName.split("/"));
216
- const candidate = join(root, "index.js");
217
- if (existsSync(candidate)) {
218
- return {
219
- root,
220
- platform: packageName.slice("@github/copilot-".length)
221
- };
222
- }
223
- }
224
- }
225
- throw new Error(
226
- `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.`
227
- );
228
- }
229
196
  function getBundledRuntimePath() {
230
- const bundled = getBundledCliPackage();
231
- return materializeRuntimeBundle({
232
- packageRoot: bundled.root,
233
- platform: bundled.platform
234
- });
197
+ return ensureRuntimeBundle(COPILOT_CLI_VERSION);
235
198
  }
236
199
  class TeardownResilientStreamMessageWriter extends StreamMessageWriter {
237
200
  suppressWriteErrors = false;
@@ -251,6 +214,7 @@ class CopilotClient {
251
214
  ffiHost = null;
252
215
  connection = null;
253
216
  messageWriter = null;
217
+ connectionClosed = false;
254
218
  socket = null;
255
219
  runtimePort = null;
256
220
  actualHost = "localhost";
@@ -450,8 +414,6 @@ class CopilotClient {
450
414
  const explicitCliPath = conn.path ?? effectiveEnv.COPILOT_CLI_PATH;
451
415
  if (explicitCliPath) {
452
416
  this.resolvedCliPath = explicitCliPath;
453
- } else {
454
- this.resolvedCliPath = getBundledRuntimePath();
455
417
  }
456
418
  }
457
419
  const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
@@ -466,7 +428,8 @@ class CopilotClient {
466
428
  baseDirectory: options.baseDirectory,
467
429
  sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
468
430
  enableRemoteSessions: options.enableRemoteSessions ?? false,
469
- mode: options.mode ?? "copilot-cli"
431
+ mode: options.mode ?? "copilot-cli",
432
+ clientInfo: options.clientInfo
470
433
  };
471
434
  if (this.options.mode === "empty") {
472
435
  const hasPersistence = this.options.baseDirectory !== void 0 || this.sessionFsConfig !== null || // External runtimes manage their own persistence layer; the SDK
@@ -482,17 +445,29 @@ class CopilotClient {
482
445
  connectionExtraArgs = [];
483
446
  /**
484
447
  * Parse CLI URL into host and port
485
- * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
448
+ * Supports formats: "host:port", "[ipv6]:port", "http://host:port", "https://host:port", or just "port"
486
449
  */
487
450
  parseCliUrl(url) {
488
- let cleanUrl = url.replace(/^https?:\/\//, "");
451
+ const cleanUrl = url.replace(/^https?:\/\//, "");
489
452
  if (/^\d+$/.test(cleanUrl)) {
490
453
  return { host: "localhost", port: parseInt(cleanUrl, 10) };
491
454
  }
455
+ const ipv6Match = cleanUrl.match(/^\[([^\]]+)\]:(\d+)$/);
456
+ if (ipv6Match) {
457
+ const host2 = ipv6Match[1];
458
+ if (!isIPv6(host2)) {
459
+ throw new Error(`Invalid cliUrl format: ${url}`);
460
+ }
461
+ const port2 = parseInt(ipv6Match[2], 10);
462
+ if (isNaN(port2) || port2 <= 0 || port2 > 65535) {
463
+ throw new Error(`Invalid port in cliUrl: ${url}`);
464
+ }
465
+ return { host: host2, port: port2 };
466
+ }
492
467
  const parts = cleanUrl.split(":");
493
468
  if (parts.length !== 2) {
494
469
  throw new Error(
495
- `Invalid cliUrl format: ${url}. Expected "host:port", "http://host:port", or "port"`
470
+ `Invalid cliUrl format: ${url}. Expected "host:port", "[ipv6]:port", "http://host:port", or "port"`
496
471
  );
497
472
  }
498
473
  const host = parts[0] || "localhost";
@@ -622,6 +597,7 @@ class CopilotClient {
622
597
  return;
623
598
  }
624
599
  this.forceStopping = false;
600
+ this.connectionClosed = false;
625
601
  this.processTransportError = null;
626
602
  this.state = "connecting";
627
603
  try {
@@ -720,7 +696,7 @@ class CopilotClient {
720
696
  }
721
697
  this.sessions.clear();
722
698
  this.githubTokenProviders.clear();
723
- if (this.connection && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
699
+ if (this.connection && !this.connectionClosed && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
724
700
  const runtimeShutdownStart = Date.now();
725
701
  const shutdownPromise = this.rpc.runtime.shutdown();
726
702
  void shutdownPromise.catch(() => void 0);
@@ -1251,6 +1227,7 @@ class CopilotClient {
1251
1227
  gitHubTokenProviderRegistrationId,
1252
1228
  remoteSession: config.remoteSession,
1253
1229
  cloud: config.cloud,
1230
+ featureFlags: config.featureFlags,
1254
1231
  expAssignments: config.expAssignments,
1255
1232
  enableManagedSettings: config.enableManagedSettings,
1256
1233
  managedSettings: config.managedSettings
@@ -1284,6 +1261,7 @@ class CopilotClient {
1284
1261
  await this.updateSessionOptionsForMode(session, config);
1285
1262
  this.commitGitHubTokenProvider(returnedSessionId, gitHubTokenProviderRegistrationId);
1286
1263
  } catch (e) {
1264
+ session?._markDisconnected();
1287
1265
  if (registeredId !== void 0) {
1288
1266
  this.sessions.delete(registeredId);
1289
1267
  }
@@ -1486,6 +1464,7 @@ class CopilotClient {
1486
1464
  gitHubTokenProviderRegistrationId,
1487
1465
  remoteSession: config.remoteSession,
1488
1466
  openCanvases: config.openCanvases,
1467
+ featureFlags: config.featureFlags,
1489
1468
  expAssignments: config.expAssignments,
1490
1469
  enableManagedSettings: config.enableManagedSettings,
1491
1470
  managedSettings: config.managedSettings,
@@ -1515,6 +1494,7 @@ class CopilotClient {
1515
1494
  await this.updateSessionOptionsForMode(session, config);
1516
1495
  this.commitGitHubTokenProvider(sessionId, gitHubTokenProviderRegistrationId);
1517
1496
  } catch (e) {
1497
+ session._markDisconnected();
1518
1498
  this.sessions.delete(sessionId);
1519
1499
  if (gitHubTokenProviderRegistrationId !== void 0) {
1520
1500
  this.githubTokenProviders.delete(gitHubTokenProviderRegistrationId);
@@ -1630,10 +1610,17 @@ class CopilotClient {
1630
1610
  const raceAgainstExit = (p) => this.processExitPromise ? Promise.race([p, this.processExitPromise]) : p;
1631
1611
  let serverVersion;
1632
1612
  try {
1633
- const connectParams = { token: this.effectiveConnectionToken };
1613
+ const connectParams = {
1614
+ token: this.effectiveConnectionToken,
1615
+ supportedTaskKinds: ["agent", "client", "shell"]
1616
+ };
1634
1617
  if (this.onGitHubTelemetry != null) {
1635
1618
  connectParams.enableGitHubTelemetryForwarding = true;
1636
1619
  }
1620
+ const clientInfo = clientInfoToWire(this.options.clientInfo);
1621
+ if (clientInfo != null) {
1622
+ connectParams.clientInfo = clientInfo;
1623
+ }
1637
1624
  const result = await raceAgainstExit(this.internalRpc.connect(connectParams));
1638
1625
  serverVersion = result.protocolVersion;
1639
1626
  } catch (err) {
@@ -1894,6 +1881,7 @@ class CopilotClient {
1894
1881
  * Start the CLI server process
1895
1882
  */
1896
1883
  async startCLIServer() {
1884
+ this.resolvedCliPath ??= await getBundledRuntimePath();
1897
1885
  return new Promise((resolve2, reject) => {
1898
1886
  this.stderrBuffer = "";
1899
1887
  const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
@@ -1931,7 +1919,7 @@ class CopilotClient {
1931
1919
  }
1932
1920
  if (!existsSync(this.resolvedCliPath)) {
1933
1921
  throw new Error(
1934
- `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
1922
+ `Copilot CLI not found at ${this.resolvedCliPath}. Set COPILOT_CLI_PATH to use a custom installation.`
1935
1923
  );
1936
1924
  }
1937
1925
  const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
@@ -2058,12 +2046,19 @@ stderr: ${stderrOutput}`
2058
2046
  /** Starts the in-process FFI runtime with SDK-managed typed options. */
2059
2047
  async startInProcessFfi() {
2060
2048
  const explicitEntrypoint = this.resolvedEnv.COPILOT_CLI_PATH;
2061
- const runtimeLibrary = explicitEntrypoint ? join(
2062
- dirname(resolve(explicitEntrypoint)),
2063
- "prebuilds",
2064
- CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint),
2065
- "runtime.node"
2066
- ) : join(dirname(getBundledRuntimePath()), "runtime.node");
2049
+ let runtimeLibrary;
2050
+ if (explicitEntrypoint) {
2051
+ const entrypointDirectory = dirname(resolve(explicitEntrypoint));
2052
+ const adjacentRuntime = join(entrypointDirectory, "runtime.node");
2053
+ runtimeLibrary = existsSync(adjacentRuntime) ? adjacentRuntime : join(
2054
+ entrypointDirectory,
2055
+ "prebuilds",
2056
+ CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint),
2057
+ "runtime.node"
2058
+ );
2059
+ } else {
2060
+ runtimeLibrary = join(dirname(await getBundledRuntimePath()), "runtime.node");
2061
+ }
2067
2062
  const { FfiRuntimeHost } = await import("./ffiRuntimeHost.js");
2068
2063
  const environment = {};
2069
2064
  if (this.options.gitHubToken) {
@@ -2249,12 +2244,24 @@ stderr: ${stderrOutput}` : ""}`
2249
2244
  return await this.handleHooksInvoke(params);
2250
2245
  }
2251
2246
  );
2252
- this.connection.onClose(() => {
2247
+ const connection = this.connection;
2248
+ const markDisconnected = () => {
2249
+ if (this.connection !== connection) {
2250
+ return;
2251
+ }
2252
+ this.connectionClosed = true;
2253
2253
  this.state = "disconnected";
2254
+ for (const session of this.sessions.values()) {
2255
+ session._markDisconnected();
2256
+ }
2257
+ this.sessions.clear();
2254
2258
  this.githubTokenProviders.clear();
2255
- });
2256
- this.connection.onError((_error) => {
2257
- this.state = "disconnected";
2259
+ };
2260
+ this.connection.onClose(markDisconnected);
2261
+ this.connection.onError(() => {
2262
+ if (this.connection === connection) {
2263
+ this.state = "disconnected";
2264
+ }
2258
2265
  });
2259
2266
  }
2260
2267
  handleSessionEventNotification(notification) {