@github/copilot-sdk 1.0.13-preview.3 → 1.0.13-preview.6

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/README.md CHANGED
@@ -8,6 +8,20 @@ To use the SDK, you'll need:
8
8
 
9
9
  - Node.js ^20.19.0 or >=22.12.0
10
10
 
11
+ The SDK uses an optional `@github/copilot-sdk-<platform>` package containing the
12
+ Copilot CLI runtime for the host platform. These packages are built from
13
+ verified `github/copilot-cli` release assets when the SDK is published, so
14
+ starting the SDK performs no runtime download. Set `COPILOT_CLI_PATH` to use an
15
+ existing installation instead.
16
+
17
+ The checked-in release pin is `copilotCliVersion` in `package.json`. Run
18
+ `npm run set:cli-version -- <version>` to update it and regenerate the compiled
19
+ metadata in `src/cliVersion.ts`. Packaging verifies release assets against the
20
+ release's `SHA256SUMS.txt`.
21
+
22
+ `npm run pack:release` builds the main package and all platform packages. Set
23
+ `COPILOT_CLI_DOWNLOAD_BASE_URL` to use a release mirror while packaging.
24
+
11
25
  ## Installation
12
26
 
13
27
  ```bash
@@ -22,6 +36,7 @@ Try the interactive chat sample (from the repo root):
22
36
  cd nodejs
23
37
  npm ci
24
38
  npm run build
39
+ export COPILOT_CLI_PATH="$(npm run --silent prepare:runtime -- --print-path)"
25
40
  cd samples
26
41
  npm install
27
42
  npm start
@@ -95,6 +110,7 @@ new CopilotClient(options?: CopilotClientOptions)
95
110
  - `RuntimeConnection.forUri(url, { connectionToken? })` — connect to an already-running runtime (mutually exclusive with `gitHubToken`/`useLoggedInUser`). There is no top-level `cliUrl` shortcut; use this factory for URL-based connections.
96
111
  - `RuntimeConnection.forInProcess()` — host the runtime in-process over its native C ABI (FFI). **Experimental.** Because the runtime shares this process, `env`, `telemetry`, and `workingDirectory` are rejected with this transport; set them on the host process instead.
97
112
  - The child-process transports (`forStdio`/`forTcp`) also accept a per-connection `env`. Set it there or via the top-level `env` option — not both (setting both throws).
113
+ - Managed child-process connections materialize the bundled `copilot-runtime` and adjacent `runtime.node`, then launch the wrapper by default. An explicit connection `path` or `COPILOT_CLI_PATH` overrides the bundled runtime.
98
114
  - `mode?: "empty" | "copilot-cli"` - Defaulting strategy. Use `"empty"` for multi-user server mode; defaults to `"copilot-cli"`.
99
115
  - `workingDirectory?: string` - Working directory for the runtime process (default: current process cwd).
100
116
  - `baseDirectory?: string` - Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned runtime. When not set, the runtime defaults to `~/.copilot`. Ignored when connecting via `RuntimeConnection.forUri`.
@@ -131,6 +147,7 @@ Create a new conversation session.
131
147
 
132
148
  - `sessionId?: string` - Custom session ID.
133
149
  - `model?: string` - Model to use ("gpt-5", "claude-sonnet-4.5", etc.). **Required when using custom provider.**
150
+ - `capi?: CapiSessionOptions` - Copilot API options. With `model: "auto"`, set `autoTier` to `"efficiency"`, `"balance"`, or `"intelligence"` to choose a routing preference. Requires a runtime with Auto tier support and V2 Auto routing. Omission preserves default behavior. See [Auto tier persistence](../docs/features/session-persistence.md#auto-tier-persistence) for resume semantics.
134
151
  - `reasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max"` - Reasoning effort level for models that support it. Use `listModels()` to check which models support this option.
135
152
  - `tools?: Tool[]` - Custom tools exposed to the CLI. Tools without `handler` are declaration-only and must be resolved via pending tool-call RPCs.
136
153
  - `systemMessage?: SystemMessageConfig` - System message customization (see below)
@@ -934,15 +951,15 @@ const session = await client.createSession({
934
951
 
935
952
  The handler must return one of the `PermissionDecision` shapes (or `{ kind: "no-result" }`). Approval scopes are present-tense — they describe the decision to apply, not the outcome reported back on session events:
936
953
 
937
- | Kind | Meaning | Extra fields |
938
- | ------------------------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
939
- | `"approve-once"` | Allow this single request | — |
940
- | `"approve-for-session"` | Allow this request and remember the approval for the rest of the session | `approval?` (rule to remember), `domain?` (for URL approvals) |
941
- | `"approve-for-location"` | Allow this request and persist the approval for this project location (git root or cwd) | `approval` (rule to persist), `locationKey` (location to persist under) |
942
- | `"approve-permanently"` | Allow this request and persist the approval across sessions (currently used for URL domains) | `domain` (URL domain to approve) |
943
- | `"reject"` | Deny the request | `feedback?` (optional string surfaced to the agent) |
944
- | `"user-not-available"` | Deny the request because no user is available to confirm it | — |
945
- | `"no-result"` | Suppress this SDK client's response so another connected client can answer the pending request | — |
954
+ | Kind | Meaning | Extra fields |
955
+ | ------------------------ | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
956
+ | `"approve-once"` | Allow this single request | — |
957
+ | `"approve-for-session"` | Allow this request and remember the approval for the rest of the session | `approval?` (rule to remember), `domain?` (for URL approvals) |
958
+ | `"approve-for-location"` | Allow this request and persist the approval for this project location (git root or cwd) | `approval` (rule to persist), `locationKey` (location to persist under) |
959
+ | `"approve-permanently"` | Allow this request and persist the approval across sessions (currently used for URL domains) | `domain` (URL domain to approve) |
960
+ | `"reject"` | Deny the request | `feedback?` (optional string surfaced to the agent) |
961
+ | `"user-not-available"` | Deny the request because no user is available to confirm it | — |
962
+ | `"no-result"` | Suppress this SDK client's response so another connected client can answer the pending request | — |
946
963
 
947
964
  ### Resuming Sessions
948
965
 
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var cliVersion_exports = {};
20
+ __export(cliVersion_exports, {
21
+ COPILOT_CLI_USE_NPM_PACKAGE: () => COPILOT_CLI_USE_NPM_PACKAGE,
22
+ COPILOT_CLI_VERSION: () => COPILOT_CLI_VERSION
23
+ });
24
+ module.exports = __toCommonJS(cliVersion_exports);
25
+ const COPILOT_CLI_VERSION = "1.0.83-3";
26
+ const COPILOT_CLI_USE_NPM_PACKAGE = false;
27
+ // Annotate the CommonJS export names for ESM import in node:
28
+ 0 && (module.exports = {
29
+ COPILOT_CLI_USE_NPM_PACKAGE,
30
+ COPILOT_CLI_VERSION
31
+ });
@@ -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 packageEntryUrl = import_meta.resolve(packageName);
221
- const packageEntryPath = (0, import_node_url.fileURLToPath)(packageEntryUrl);
222
- return (0, import_node_path.join)((0, import_node_path.dirname)(packageEntryPath), "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;
@@ -287,6 +264,7 @@ class CopilotClient {
287
264
  _internalRpc = null;
288
265
  processExitPromise = null;
289
266
  // Rejects when CLI process exits
267
+ processTransportError = null;
290
268
  negotiatedProtocolVersion = null;
291
269
  /** Connection-level session filesystem config, set via constructor option. */
292
270
  sessionFsConfig = null;
@@ -453,7 +431,12 @@ class CopilotClient {
453
431
  const connEnv = conn.kind === "stdio" || conn.kind === "tcp" ? conn.env : void 0;
454
432
  const effectiveEnv = connEnv ?? options.env ?? process.env;
455
433
  this.resolvedEnv = effectiveEnv;
456
- this.resolvedCliPath = conn.kind === "stdio" || conn.kind === "tcp" ? conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath() : void 0;
434
+ if (conn.kind === "stdio" || conn.kind === "tcp") {
435
+ const explicitCliPath = conn.path ?? effectiveEnv.COPILOT_CLI_PATH;
436
+ if (explicitCliPath) {
437
+ this.resolvedCliPath = explicitCliPath;
438
+ }
439
+ }
457
440
  const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
458
441
  this.connectionExtraArgs = [...connArgs];
459
442
  this.options = {
@@ -466,7 +449,8 @@ class CopilotClient {
466
449
  baseDirectory: options.baseDirectory,
467
450
  sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
468
451
  enableRemoteSessions: options.enableRemoteSessions ?? false,
469
- mode: options.mode ?? "copilot-cli"
452
+ mode: options.mode ?? "copilot-cli",
453
+ clientInfo: options.clientInfo
470
454
  };
471
455
  if (this.options.mode === "empty") {
472
456
  const hasPersistence = this.options.baseDirectory !== void 0 || this.sessionFsConfig !== null || // External runtimes manage their own persistence layer; the SDK
@@ -482,17 +466,29 @@ class CopilotClient {
482
466
  connectionExtraArgs = [];
483
467
  /**
484
468
  * Parse CLI URL into host and port
485
- * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
469
+ * Supports formats: "host:port", "[ipv6]:port", "http://host:port", "https://host:port", or just "port"
486
470
  */
487
471
  parseCliUrl(url) {
488
- let cleanUrl = url.replace(/^https?:\/\//, "");
472
+ const cleanUrl = url.replace(/^https?:\/\//, "");
489
473
  if (/^\d+$/.test(cleanUrl)) {
490
474
  return { host: "localhost", port: parseInt(cleanUrl, 10) };
491
475
  }
476
+ const ipv6Match = cleanUrl.match(/^\[([^\]]+)\]:(\d+)$/);
477
+ if (ipv6Match) {
478
+ const host2 = ipv6Match[1];
479
+ if (!(0, import_node_net.isIPv6)(host2)) {
480
+ throw new Error(`Invalid cliUrl format: ${url}`);
481
+ }
482
+ const port2 = parseInt(ipv6Match[2], 10);
483
+ if (isNaN(port2) || port2 <= 0 || port2 > 65535) {
484
+ throw new Error(`Invalid port in cliUrl: ${url}`);
485
+ }
486
+ return { host: host2, port: port2 };
487
+ }
492
488
  const parts = cleanUrl.split(":");
493
489
  if (parts.length !== 2) {
494
490
  throw new Error(
495
- `Invalid cliUrl format: ${url}. Expected "host:port", "http://host:port", or "port"`
491
+ `Invalid cliUrl format: ${url}. Expected "host:port", "[ipv6]:port", "http://host:port", or "port"`
496
492
  );
497
493
  }
498
494
  const host = parts[0] || "localhost";
@@ -621,6 +617,8 @@ class CopilotClient {
621
617
  if (this.state === "connected") {
622
618
  return;
623
619
  }
620
+ this.forceStopping = false;
621
+ this.processTransportError = null;
624
622
  this.state = "connecting";
625
623
  try {
626
624
  if (this.connectionConfig.kind === "inprocess") {
@@ -653,8 +651,10 @@ class CopilotClient {
653
651
  }
654
652
  this.state = "connected";
655
653
  } catch (error) {
654
+ const startupError = this.processTransportError ?? error;
655
+ await this.forceStop();
656
656
  this.state = "error";
657
- throw error;
657
+ throw startupError;
658
658
  }
659
659
  }
660
660
  /**
@@ -699,7 +699,7 @@ class CopilotClient {
699
699
  lastError = error instanceof Error ? error : new Error(String(error));
700
700
  if (attempt < 3) {
701
701
  const delay = 100 * Math.pow(2, attempt - 1);
702
- await new Promise((resolve) => setTimeout(resolve, delay));
702
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
703
703
  }
704
704
  }
705
705
  }
@@ -766,8 +766,8 @@ class CopilotClient {
766
766
  this.socket = null;
767
767
  try {
768
768
  if (!socket.destroyed) {
769
- await new Promise((resolve) => {
770
- socket.once("close", () => resolve());
769
+ await new Promise((resolve2) => {
770
+ socket.once("close", () => resolve2());
771
771
  socket.end();
772
772
  });
773
773
  }
@@ -1247,6 +1247,7 @@ class CopilotClient {
1247
1247
  gitHubTokenProviderRegistrationId,
1248
1248
  remoteSession: config.remoteSession,
1249
1249
  cloud: config.cloud,
1250
+ featureFlags: config.featureFlags,
1250
1251
  expAssignments: config.expAssignments,
1251
1252
  enableManagedSettings: config.enableManagedSettings,
1252
1253
  managedSettings: config.managedSettings
@@ -1482,6 +1483,7 @@ class CopilotClient {
1482
1483
  gitHubTokenProviderRegistrationId,
1483
1484
  remoteSession: config.remoteSession,
1484
1485
  openCanvases: config.openCanvases,
1486
+ featureFlags: config.featureFlags,
1485
1487
  expAssignments: config.expAssignments,
1486
1488
  enableManagedSettings: config.enableManagedSettings,
1487
1489
  managedSettings: config.managedSettings,
@@ -1573,8 +1575,8 @@ class CopilotClient {
1573
1575
  async listModels() {
1574
1576
  await this.modelsCacheLock;
1575
1577
  let resolveLock;
1576
- this.modelsCacheLock = new Promise((resolve) => {
1577
- resolveLock = resolve;
1578
+ this.modelsCacheLock = new Promise((resolve2) => {
1579
+ resolveLock = resolve2;
1578
1580
  });
1579
1581
  try {
1580
1582
  if (this.modelsCache !== null) {
@@ -1630,6 +1632,10 @@ class CopilotClient {
1630
1632
  if (this.onGitHubTelemetry != null) {
1631
1633
  connectParams.enableGitHubTelemetryForwarding = true;
1632
1634
  }
1635
+ const clientInfo = clientInfoToWire(this.options.clientInfo);
1636
+ if (clientInfo != null) {
1637
+ connectParams.clientInfo = clientInfo;
1638
+ }
1633
1639
  const result = await raceAgainstExit(this.internalRpc.connect(connectParams));
1634
1640
  serverVersion = result.protocolVersion;
1635
1641
  } catch (err) {
@@ -1890,7 +1896,8 @@ class CopilotClient {
1890
1896
  * Start the CLI server process
1891
1897
  */
1892
1898
  async startCLIServer() {
1893
- return new Promise((resolve, reject) => {
1899
+ this.resolvedCliPath ??= await getBundledRuntimePath();
1900
+ return new Promise((resolve2, reject) => {
1894
1901
  this.stderrBuffer = "";
1895
1902
  const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
1896
1903
  if (this.options.logLevel) {
@@ -1927,7 +1934,7 @@ class CopilotClient {
1927
1934
  }
1928
1935
  if (!(0, import_node_fs.existsSync)(this.resolvedCliPath)) {
1929
1936
  throw new Error(
1930
- `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
1937
+ `Copilot CLI not found at ${this.resolvedCliPath}. Set COPILOT_CLI_PATH to use a custom installation.`
1931
1938
  );
1932
1939
  }
1933
1940
  const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
@@ -1951,7 +1958,7 @@ class CopilotClient {
1951
1958
  let resolved = false;
1952
1959
  if (this.connectionConfig.kind === "stdio") {
1953
1960
  resolved = true;
1954
- resolve();
1961
+ resolve2();
1955
1962
  } else {
1956
1963
  this.cliProcess.stdout?.on("data", (data) => {
1957
1964
  stdout += data.toString();
@@ -1959,7 +1966,7 @@ class CopilotClient {
1959
1966
  if (match && !resolved) {
1960
1967
  this.runtimePort = parseInt(match[1], 10);
1961
1968
  resolved = true;
1962
- resolve();
1969
+ resolve2();
1963
1970
  }
1964
1971
  });
1965
1972
  }
@@ -1991,26 +1998,27 @@ stderr: ${stderrOutput}`
1991
1998
  });
1992
1999
  this.processExitPromise = new Promise((_, rejectProcessExit) => {
1993
2000
  this.cliProcess.on("exit", (code) => {
1994
- setTimeout(() => {
1995
- const stderrOutput = this.stderrBuffer.trim();
1996
- if (stderrOutput) {
1997
- rejectProcessExit(
1998
- new Error(
1999
- `CLI server exited with code ${code}
2001
+ if (this.messageWriter) {
2002
+ this.messageWriter.suppressWriteErrors = true;
2003
+ }
2004
+ const stderrOutput = this.stderrBuffer.trim();
2005
+ if (stderrOutput) {
2006
+ rejectProcessExit(
2007
+ new Error(
2008
+ `CLI server exited with code ${code}
2000
2009
  stderr: ${stderrOutput}`
2001
- )
2002
- );
2003
- } else {
2004
- rejectProcessExit(
2005
- new Error(`CLI server exited unexpectedly with code ${code}`)
2006
- );
2007
- }
2008
- }, 50);
2010
+ )
2011
+ );
2012
+ } else {
2013
+ rejectProcessExit(
2014
+ new Error(`CLI server exited unexpectedly with code ${code}`)
2015
+ );
2016
+ }
2009
2017
  });
2010
2018
  });
2011
2019
  this.processExitPromise.catch(() => {
2012
2020
  });
2013
- this.cliProcess.on("exit", (code) => {
2021
+ this.cliProcess.on("close", (code) => {
2014
2022
  if (!resolved) {
2015
2023
  resolved = true;
2016
2024
  const stderrOutput = this.stderrBuffer.trim();
@@ -2052,7 +2060,20 @@ stderr: ${stderrOutput}`
2052
2060
  }
2053
2061
  /** Starts the in-process FFI runtime with SDK-managed typed options. */
2054
2062
  async startInProcessFfi() {
2055
- const entrypoint = this.resolveCliPathForFfi();
2063
+ const explicitEntrypoint = this.resolvedEnv.COPILOT_CLI_PATH;
2064
+ let runtimeLibrary;
2065
+ if (explicitEntrypoint) {
2066
+ const entrypointDirectory = (0, import_node_path.dirname)((0, import_node_path.resolve)(explicitEntrypoint));
2067
+ const adjacentRuntime = (0, import_node_path.join)(entrypointDirectory, "runtime.node");
2068
+ runtimeLibrary = (0, import_node_fs.existsSync)(adjacentRuntime) ? adjacentRuntime : (0, import_node_path.join)(
2069
+ entrypointDirectory,
2070
+ "prebuilds",
2071
+ CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint),
2072
+ "runtime.node"
2073
+ );
2074
+ } else {
2075
+ runtimeLibrary = (0, import_node_path.join)((0, import_node_path.dirname)(await getBundledRuntimePath()), "runtime.node");
2076
+ }
2056
2077
  const { FfiRuntimeHost } = await import("./ffiRuntimeHost.js");
2057
2078
  const environment = {};
2058
2079
  if (this.options.gitHubToken) {
@@ -2080,12 +2101,7 @@ stderr: ${stderrOutput}`
2080
2101
  if (this.options.enableRemoteSessions) {
2081
2102
  args.push("--remote");
2082
2103
  }
2083
- const host = FfiRuntimeHost.create(
2084
- entrypoint,
2085
- CopilotClient.getNapiPrebuildsFolder(entrypoint),
2086
- environment,
2087
- args
2088
- );
2104
+ const host = FfiRuntimeHost.create(runtimeLibrary, explicitEntrypoint, environment, args);
2089
2105
  this.ffiHost = host;
2090
2106
  await host.start();
2091
2107
  }
@@ -2105,19 +2121,6 @@ stderr: ${stderrOutput}`
2105
2121
  this.attachConnectionHandlers();
2106
2122
  this.connection.listen();
2107
2123
  }
2108
- /**
2109
- * Resolves the CLI entrypoint used for in-process FFI hosting: `COPILOT_CLI_PATH`
2110
- * when set, otherwise the bundled platform-package entrypoint.
2111
- */
2112
- resolveCliPathForFfi() {
2113
- return this.resolvedEnv.COPILOT_CLI_PATH ?? getBundledCliPath();
2114
- }
2115
- /**
2116
- * Returns the napi prebuilds folder name for the current host — the
2117
- * `<node-platform>-<arch>` convention (e.g. `win32-x64`, `darwin-arm64`,
2118
- * `linux-x64`, `linuxmusl-x64`) under which the runtime ships
2119
- * `prebuilds/<folder>/runtime.node`.
2120
- */
2121
2124
  static getNapiPrebuildsFolder(entrypoint) {
2122
2125
  const arch = process.arch;
2123
2126
  if (arch !== "x64" && arch !== "arm64") {
@@ -2153,6 +2156,11 @@ stderr: ${stderrOutput}`
2153
2156
  }
2154
2157
  this.state = "error";
2155
2158
  const reason = err instanceof Error ? err.stack ?? err.message : String(err);
2159
+ const stderrOutput = this.stderrBuffer.trim();
2160
+ this.processTransportError = new Error(
2161
+ `CLI server connection failed: ${reason}${stderrOutput ? `
2162
+ stderr: ${stderrOutput}` : ""}`
2163
+ );
2156
2164
  this.logDebug(`stdin pipe error: ${reason}`);
2157
2165
  try {
2158
2166
  this.connection?.dispose();
@@ -2189,7 +2197,7 @@ stderr: ${stderrOutput}`
2189
2197
  if (!this.runtimePort) {
2190
2198
  throw new Error("Server port not available");
2191
2199
  }
2192
- return new Promise((resolve, reject) => {
2200
+ return new Promise((resolve2, reject) => {
2193
2201
  this.socket = new import_node_net.Socket();
2194
2202
  const connectionTimeout = setTimeout(() => {
2195
2203
  this.socket?.destroy();
@@ -2204,7 +2212,7 @@ stderr: ${stderrOutput}`
2204
2212
  );
2205
2213
  this.attachConnectionHandlers();
2206
2214
  this.connection.listen();
2207
- resolve();
2215
+ resolve2();
2208
2216
  });
2209
2217
  this.socket.on("error", (error) => {
2210
2218
  clearTimeout(connectionTimeout);
@@ -85,7 +85,7 @@ function loadLibrary(libraryPath) {
85
85
  return loadedLibrary;
86
86
  }
87
87
  function buildArgvJson(cliEntrypoint, args) {
88
- const argv = cliEntrypoint.toLowerCase().endsWith(".js") ? ["node", cliEntrypoint, "--embedded-host", "--no-auto-update"] : [cliEntrypoint, "--embedded-host", "--no-auto-update"];
88
+ const argv = cliEntrypoint ? cliEntrypoint.toLowerCase().endsWith(".js") ? ["node", cliEntrypoint, "--embedded-host", "--no-auto-update"] : [cliEntrypoint, "--embedded-host", "--no-auto-update"] : [];
89
89
  argv.push(...args);
90
90
  return Buffer.from(JSON.stringify(argv), "utf8");
91
91
  }
@@ -140,24 +140,21 @@ class FfiRuntimeHost {
140
140
  /** The stream JSON-RPC writes client→server frames to. */
141
141
  sendStream;
142
142
  /**
143
- * Resolves the cdylib next to the given CLI entrypoint and prepares the FFI host.
144
- * The cdylib is resolved as `prebuilds/<prebuildsFolder>/runtime.node` relative to
145
- * the entrypoint directory (the napi-rs `<node-platform>-<arch>` layout, e.g.
146
- * `linux-x64`). Throws if it cannot be found.
143
+ * Loads the runtime cdylib at the given path and prepares the FFI host.
147
144
  */
148
- static create(cliEntrypoint, prebuildsFolder, environment, args) {
149
- const fullEntrypoint = (0, import_node_path.resolve)(cliEntrypoint);
150
- const distDir = (0, import_node_path.dirname)(fullEntrypoint);
151
- const libraryPath = (0, import_node_path.join)(distDir, "prebuilds", prebuildsFolder, "runtime.node");
152
- if (!(0, import_node_fs.existsSync)(libraryPath)) {
153
- throw new Error(`FFI runtime library not found. Looked for '${libraryPath}'.`);
145
+ static create(libraryPath, cliEntrypoint, environment, args) {
146
+ const fullLibraryPath = (0, import_node_path.resolve)(libraryPath);
147
+ if (!(0, import_node_fs.existsSync)(fullLibraryPath)) {
148
+ throw new Error(`FFI runtime library not found at '${fullLibraryPath}'.`);
154
149
  }
155
- return new FfiRuntimeHost(libraryPath, fullEntrypoint, environment, args);
150
+ return new FfiRuntimeHost(
151
+ fullLibraryPath,
152
+ cliEntrypoint ? (0, import_node_path.resolve)(cliEntrypoint) : void 0,
153
+ environment,
154
+ args
155
+ );
156
156
  }
157
- /**
158
- * Starts the in-process runtime: spawns the CLI worker via the native host,
159
- * waits for readiness, and opens the FFI JSON-RPC connection.
160
- */
157
+ /** Starts the in-process Rust runtime and opens the FFI JSON-RPC connection. */
161
158
  async start() {
162
159
  const argvJson = buildArgvJson(this.cliEntrypoint, this.args);
163
160
  const envJson = buildEnvJson(this.environment);
@@ -177,9 +174,7 @@ class FfiRuntimeHost {
177
174
  );
178
175
  });
179
176
  if (!this.serverId) {
180
- throw new Error(
181
- `copilot_runtime_host_start failed (library '${this.libraryPath}', entrypoint '${this.cliEntrypoint}').`
182
- );
177
+ throw new Error(`copilot_runtime_host_start failed (library '${this.libraryPath}').`);
183
178
  }
184
179
  this.outboundCallback = import_koffi.default.register(
185
180
  (_userData, bytesPtr, bytesLen) => this.feedInbound(bytesPtr, bytesLen),
@@ -39,6 +39,17 @@ function createServerRpc(connection) {
39
39
  */
40
40
  ping: async (params) => connection.sendRequest("ping", params),
41
41
  /** @experimental */
42
+ hooks: {
43
+ /**
44
+ * Discovers hook actions enabled under server-side discovery settings from user, repository, plugin, and managed-policy sources.
45
+ *
46
+ * @param params Optional project paths and host-exclusion behavior for server-scoped hook discovery.
47
+ *
48
+ * @returns Server-discovered hook actions and partial-load diagnostics from user, repository, plugin, and managed-policy sources. Concrete sessions may include additional session-specific hook sources.
49
+ */
50
+ discover: async (params) => connection.sendRequest("hooks.discover", params)
51
+ },
52
+ /** @experimental */
42
53
  models: {
43
54
  /**
44
55
  * Lists Copilot models available to the authenticated user.
@@ -506,6 +517,14 @@ function createServerRpc(connection) {
506
517
  * @returns Sessions matching the filter, ordered most-recently-modified first.
507
518
  */
508
519
  list: async (params) => connection.sendRequest("sessions.list", params),
520
+ /**
521
+ * Reads a page of durable events directly from a local session's persisted journal without creating, resuming, or activating the session. The initial backward read uses a bounded tail scan for fast first paint; cursor continuations preserve the session event-log paging semantics. Persisted events may omit payloads that are reconstructed only for an active session.
522
+ *
523
+ * @param params Pagination options for reading an inactive or active local session's persisted event journal.
524
+ *
525
+ * @returns Batch of session events returned by a read, with cursor and continuation metadata.
526
+ */
527
+ readPersistedEvents: async (params) => connection.sendRequest("sessions.readPersistedEvents", params),
509
528
  /**
510
529
  * Finds the local session bound to a GitHub task ID, if any.
511
530
  *