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

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
@@ -95,6 +95,7 @@ new CopilotClient(options?: CopilotClientOptions)
95
95
  - `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
96
  - `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
97
  - 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).
98
+ - 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
99
  - `mode?: "empty" | "copilot-cli"` - Defaulting strategy. Use `"empty"` for multi-user server mode; defaults to `"copilot-cli"`.
99
100
  - `workingDirectory?: string` - Working directory for the runtime process (default: current process cwd).
100
101
  - `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`.
@@ -42,6 +42,7 @@ var import_node = require("vscode-jsonrpc/node.js");
42
42
  var import_rpc = require("./generated/rpc.js");
43
43
  var import_sdkProtocolVersion = require("./sdkProtocolVersion.js");
44
44
  var import_session = require("./session.js");
45
+ var import_runtimeArtifacts = require("./runtimeArtifacts.js");
45
46
  var import_sessionFsProvider = require("./sessionFsProvider.js");
46
47
  var import_copilotRequestHandler = require("./copilotRequestHandler.js");
47
48
  var import_telemetry = require("./telemetry.js");
@@ -72,7 +73,7 @@ async function waitForChildExit(child, timeoutMs) {
72
73
  if (child.exitCode != null || child.signalCode != null) {
73
74
  return true;
74
75
  }
75
- return new Promise((resolve) => {
76
+ return new Promise((resolve2) => {
76
77
  let timeout;
77
78
  let settled = false;
78
79
  const onExit = () => {
@@ -81,7 +82,7 @@ async function waitForChildExit(child, timeoutMs) {
81
82
  }
82
83
  settled = true;
83
84
  clearTimeout(timeout);
84
- resolve(true);
85
+ resolve2(true);
85
86
  };
86
87
  timeout = setTimeout(() => {
87
88
  if (settled) {
@@ -89,7 +90,7 @@ async function waitForChildExit(child, timeoutMs) {
89
90
  }
90
91
  settled = true;
91
92
  child.off("exit", onExit);
92
- resolve(false);
93
+ resolve2(false);
93
94
  }, timeoutMs);
94
95
  child.once("exit", onExit);
95
96
  if (child.exitCode != null || child.signalCode != null) {
@@ -212,14 +213,17 @@ function getCliPlatformPackageNames() {
212
213
  const variants = process.platform === "linux" ? ["linux", "linuxmusl"] : [process.platform];
213
214
  return variants.map((variant) => `@github/copilot-${variant}-${arch}`);
214
215
  }
215
- function getBundledCliPath() {
216
+ function getBundledCliPackage() {
216
217
  const packageNames = getCliPlatformPackageNames();
217
218
  if (typeof import_meta.resolve === "function") {
218
219
  for (const packageName of packageNames) {
219
220
  try {
220
221
  const packageEntryUrl = import_meta.resolve(packageName);
221
222
  const packageEntryPath = (0, import_node_url.fileURLToPath)(packageEntryUrl);
222
- return (0, import_node_path.join)((0, import_node_path.dirname)(packageEntryPath), "index.js");
223
+ return {
224
+ root: (0, import_node_path.dirname)(packageEntryPath),
225
+ platform: packageName.slice("@github/copilot-".length)
226
+ };
223
227
  } catch {
224
228
  }
225
229
  }
@@ -231,9 +235,13 @@ function getBundledCliPath() {
231
235
  const searchPaths = req.resolve.paths("@github/copilot") ?? [];
232
236
  for (const base of searchPaths) {
233
237
  for (const packageName of packageNames) {
234
- const candidate = (0, import_node_path.join)(base, ...packageName.split("/"), "index.js");
238
+ const root = (0, import_node_path.join)(base, ...packageName.split("/"));
239
+ const candidate = (0, import_node_path.join)(root, "index.js");
235
240
  if ((0, import_node_fs.existsSync)(candidate)) {
236
- return candidate;
241
+ return {
242
+ root,
243
+ platform: packageName.slice("@github/copilot-".length)
244
+ };
237
245
  }
238
246
  }
239
247
  }
@@ -241,6 +249,13 @@ function getBundledCliPath() {
241
249
  `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
250
  );
243
251
  }
252
+ function getBundledRuntimePath() {
253
+ const bundled = getBundledCliPackage();
254
+ return (0, import_runtimeArtifacts.materializeRuntimeBundle)({
255
+ packageRoot: bundled.root,
256
+ platform: bundled.platform
257
+ });
258
+ }
244
259
  class TeardownResilientStreamMessageWriter extends import_node.StreamMessageWriter {
245
260
  suppressWriteErrors = false;
246
261
  async write(msg) {
@@ -287,6 +302,7 @@ class CopilotClient {
287
302
  _internalRpc = null;
288
303
  processExitPromise = null;
289
304
  // Rejects when CLI process exits
305
+ processTransportError = null;
290
306
  negotiatedProtocolVersion = null;
291
307
  /** Connection-level session filesystem config, set via constructor option. */
292
308
  sessionFsConfig = null;
@@ -453,7 +469,14 @@ class CopilotClient {
453
469
  const connEnv = conn.kind === "stdio" || conn.kind === "tcp" ? conn.env : void 0;
454
470
  const effectiveEnv = connEnv ?? options.env ?? process.env;
455
471
  this.resolvedEnv = effectiveEnv;
456
- this.resolvedCliPath = conn.kind === "stdio" || conn.kind === "tcp" ? conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath() : void 0;
472
+ if (conn.kind === "stdio" || conn.kind === "tcp") {
473
+ const explicitCliPath = conn.path ?? effectiveEnv.COPILOT_CLI_PATH;
474
+ if (explicitCliPath) {
475
+ this.resolvedCliPath = explicitCliPath;
476
+ } else {
477
+ this.resolvedCliPath = getBundledRuntimePath();
478
+ }
479
+ }
457
480
  const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
458
481
  this.connectionExtraArgs = [...connArgs];
459
482
  this.options = {
@@ -621,6 +644,8 @@ class CopilotClient {
621
644
  if (this.state === "connected") {
622
645
  return;
623
646
  }
647
+ this.forceStopping = false;
648
+ this.processTransportError = null;
624
649
  this.state = "connecting";
625
650
  try {
626
651
  if (this.connectionConfig.kind === "inprocess") {
@@ -653,8 +678,10 @@ class CopilotClient {
653
678
  }
654
679
  this.state = "connected";
655
680
  } catch (error) {
681
+ const startupError = this.processTransportError ?? error;
682
+ await this.forceStop();
656
683
  this.state = "error";
657
- throw error;
684
+ throw startupError;
658
685
  }
659
686
  }
660
687
  /**
@@ -699,7 +726,7 @@ class CopilotClient {
699
726
  lastError = error instanceof Error ? error : new Error(String(error));
700
727
  if (attempt < 3) {
701
728
  const delay = 100 * Math.pow(2, attempt - 1);
702
- await new Promise((resolve) => setTimeout(resolve, delay));
729
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
703
730
  }
704
731
  }
705
732
  }
@@ -766,8 +793,8 @@ class CopilotClient {
766
793
  this.socket = null;
767
794
  try {
768
795
  if (!socket.destroyed) {
769
- await new Promise((resolve) => {
770
- socket.once("close", () => resolve());
796
+ await new Promise((resolve2) => {
797
+ socket.once("close", () => resolve2());
771
798
  socket.end();
772
799
  });
773
800
  }
@@ -1573,8 +1600,8 @@ class CopilotClient {
1573
1600
  async listModels() {
1574
1601
  await this.modelsCacheLock;
1575
1602
  let resolveLock;
1576
- this.modelsCacheLock = new Promise((resolve) => {
1577
- resolveLock = resolve;
1603
+ this.modelsCacheLock = new Promise((resolve2) => {
1604
+ resolveLock = resolve2;
1578
1605
  });
1579
1606
  try {
1580
1607
  if (this.modelsCache !== null) {
@@ -1890,7 +1917,7 @@ class CopilotClient {
1890
1917
  * Start the CLI server process
1891
1918
  */
1892
1919
  async startCLIServer() {
1893
- return new Promise((resolve, reject) => {
1920
+ return new Promise((resolve2, reject) => {
1894
1921
  this.stderrBuffer = "";
1895
1922
  const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
1896
1923
  if (this.options.logLevel) {
@@ -1951,7 +1978,7 @@ class CopilotClient {
1951
1978
  let resolved = false;
1952
1979
  if (this.connectionConfig.kind === "stdio") {
1953
1980
  resolved = true;
1954
- resolve();
1981
+ resolve2();
1955
1982
  } else {
1956
1983
  this.cliProcess.stdout?.on("data", (data) => {
1957
1984
  stdout += data.toString();
@@ -1959,7 +1986,7 @@ class CopilotClient {
1959
1986
  if (match && !resolved) {
1960
1987
  this.runtimePort = parseInt(match[1], 10);
1961
1988
  resolved = true;
1962
- resolve();
1989
+ resolve2();
1963
1990
  }
1964
1991
  });
1965
1992
  }
@@ -1991,26 +2018,27 @@ stderr: ${stderrOutput}`
1991
2018
  });
1992
2019
  this.processExitPromise = new Promise((_, rejectProcessExit) => {
1993
2020
  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}
2021
+ if (this.messageWriter) {
2022
+ this.messageWriter.suppressWriteErrors = true;
2023
+ }
2024
+ const stderrOutput = this.stderrBuffer.trim();
2025
+ if (stderrOutput) {
2026
+ rejectProcessExit(
2027
+ new Error(
2028
+ `CLI server exited with code ${code}
2000
2029
  stderr: ${stderrOutput}`
2001
- )
2002
- );
2003
- } else {
2004
- rejectProcessExit(
2005
- new Error(`CLI server exited unexpectedly with code ${code}`)
2006
- );
2007
- }
2008
- }, 50);
2030
+ )
2031
+ );
2032
+ } else {
2033
+ rejectProcessExit(
2034
+ new Error(`CLI server exited unexpectedly with code ${code}`)
2035
+ );
2036
+ }
2009
2037
  });
2010
2038
  });
2011
2039
  this.processExitPromise.catch(() => {
2012
2040
  });
2013
- this.cliProcess.on("exit", (code) => {
2041
+ this.cliProcess.on("close", (code) => {
2014
2042
  if (!resolved) {
2015
2043
  resolved = true;
2016
2044
  const stderrOutput = this.stderrBuffer.trim();
@@ -2052,7 +2080,13 @@ stderr: ${stderrOutput}`
2052
2080
  }
2053
2081
  /** Starts the in-process FFI runtime with SDK-managed typed options. */
2054
2082
  async startInProcessFfi() {
2055
- const entrypoint = this.resolveCliPathForFfi();
2083
+ const explicitEntrypoint = this.resolvedEnv.COPILOT_CLI_PATH;
2084
+ const runtimeLibrary = explicitEntrypoint ? (0, import_node_path.join)(
2085
+ (0, import_node_path.dirname)((0, import_node_path.resolve)(explicitEntrypoint)),
2086
+ "prebuilds",
2087
+ CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint),
2088
+ "runtime.node"
2089
+ ) : (0, import_node_path.join)((0, import_node_path.dirname)(getBundledRuntimePath()), "runtime.node");
2056
2090
  const { FfiRuntimeHost } = await import("./ffiRuntimeHost.js");
2057
2091
  const environment = {};
2058
2092
  if (this.options.gitHubToken) {
@@ -2080,12 +2114,7 @@ stderr: ${stderrOutput}`
2080
2114
  if (this.options.enableRemoteSessions) {
2081
2115
  args.push("--remote");
2082
2116
  }
2083
- const host = FfiRuntimeHost.create(
2084
- entrypoint,
2085
- CopilotClient.getNapiPrebuildsFolder(entrypoint),
2086
- environment,
2087
- args
2088
- );
2117
+ const host = FfiRuntimeHost.create(runtimeLibrary, explicitEntrypoint, environment, args);
2089
2118
  this.ffiHost = host;
2090
2119
  await host.start();
2091
2120
  }
@@ -2105,19 +2134,6 @@ stderr: ${stderrOutput}`
2105
2134
  this.attachConnectionHandlers();
2106
2135
  this.connection.listen();
2107
2136
  }
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
2137
  static getNapiPrebuildsFolder(entrypoint) {
2122
2138
  const arch = process.arch;
2123
2139
  if (arch !== "x64" && arch !== "arm64") {
@@ -2153,6 +2169,11 @@ stderr: ${stderrOutput}`
2153
2169
  }
2154
2170
  this.state = "error";
2155
2171
  const reason = err instanceof Error ? err.stack ?? err.message : String(err);
2172
+ const stderrOutput = this.stderrBuffer.trim();
2173
+ this.processTransportError = new Error(
2174
+ `CLI server connection failed: ${reason}${stderrOutput ? `
2175
+ stderr: ${stderrOutput}` : ""}`
2176
+ );
2156
2177
  this.logDebug(`stdin pipe error: ${reason}`);
2157
2178
  try {
2158
2179
  this.connection?.dispose();
@@ -2189,7 +2210,7 @@ stderr: ${stderrOutput}`
2189
2210
  if (!this.runtimePort) {
2190
2211
  throw new Error("Server port not available");
2191
2212
  }
2192
- return new Promise((resolve, reject) => {
2213
+ return new Promise((resolve2, reject) => {
2193
2214
  this.socket = new import_node_net.Socket();
2194
2215
  const connectionTimeout = setTimeout(() => {
2195
2216
  this.socket?.destroy();
@@ -2204,7 +2225,7 @@ stderr: ${stderrOutput}`
2204
2225
  );
2205
2226
  this.attachConnectionHandlers();
2206
2227
  this.connection.listen();
2207
- resolve();
2228
+ resolve2();
2208
2229
  });
2209
2230
  this.socket.on("error", (error) => {
2210
2231
  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),
@@ -0,0 +1,159 @@
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 runtimeArtifacts_exports = {};
20
+ __export(runtimeArtifacts_exports, {
21
+ defaultRuntimeCacheRoot: () => defaultRuntimeCacheRoot,
22
+ materializeRuntimeBundle: () => materializeRuntimeBundle
23
+ });
24
+ module.exports = __toCommonJS(runtimeArtifacts_exports);
25
+ var import_node_crypto = require("node:crypto");
26
+ var import_node_fs = require("node:fs");
27
+ var import_node_os = require("node:os");
28
+ var import_node_path = require("node:path");
29
+ const EXCLUDED_TOP_LEVEL = /* @__PURE__ */ new Set([
30
+ "app.js",
31
+ "assets",
32
+ "changelog.json",
33
+ "copilot",
34
+ "copilot.exe",
35
+ "copilot-sdk",
36
+ "foundry-local-sdk",
37
+ "index.js",
38
+ "LICENSE.md",
39
+ "napi-oop-runtime",
40
+ "npm-loader.js",
41
+ "package.json",
42
+ "preloads",
43
+ "pvrecorder",
44
+ "queries",
45
+ "README.md",
46
+ "sdk",
47
+ "sea-loader.js",
48
+ "webview"
49
+ ]);
50
+ function validateFile(path, label) {
51
+ if (!(0, import_node_fs.existsSync)(path)) {
52
+ throw new Error(`${label} not found at ${path}.`);
53
+ }
54
+ if ((0, import_node_fs.statSync)(path).size === 0) {
55
+ throw new Error(`${label} at ${path} is empty.`);
56
+ }
57
+ }
58
+ function validateRuntimeBundle(wrapper, runtimeNode) {
59
+ validateFile(wrapper, "Copilot runtime wrapper");
60
+ validateFile(runtimeNode, "Copilot runtime.node");
61
+ }
62
+ function isExcluded(relativePath) {
63
+ const parts = relativePath.split(import_node_path.sep);
64
+ const topLevel = parts[0];
65
+ const fileName = parts.at(-1) ?? "";
66
+ return EXCLUDED_TOP_LEVEL.has(topLevel) || /^tree-sitter.*\.wasm$/.test(topLevel) || /^voice-.*\.js$/.test(topLevel) || fileName === "cli-native.node" || parts.includes("mediaremote-adapter") || fileName.startsWith("copilot-runtime-bin");
67
+ }
68
+ function collectRuntimeAssets(sources) {
69
+ const assets = [];
70
+ const visit = (directory) => {
71
+ for (const entry of (0, import_node_fs.readdirSync)(directory, { withFileTypes: true })) {
72
+ const source = (0, import_node_path.join)(directory, entry.name);
73
+ const sourceRelative = (0, import_node_path.relative)(sources.packageRoot, source);
74
+ if (isExcluded(sourceRelative)) {
75
+ continue;
76
+ }
77
+ if (entry.isDirectory()) {
78
+ visit(source);
79
+ continue;
80
+ }
81
+ if (!entry.isFile() && !entry.isSymbolicLink()) {
82
+ continue;
83
+ }
84
+ const parts = sourceRelative.split(import_node_path.sep);
85
+ let relativePath = sourceRelative;
86
+ if (parts[0] === "prebuilds") {
87
+ if (parts[1] !== sources.platform || parts.length < 3) {
88
+ continue;
89
+ }
90
+ relativePath = parts.slice(2).join(import_node_path.sep);
91
+ }
92
+ assets.push({ source, relativePath });
93
+ }
94
+ };
95
+ visit(sources.packageRoot);
96
+ return assets.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
97
+ }
98
+ function sourceFingerprint(assets) {
99
+ const hash = (0, import_node_crypto.createHash)("sha256");
100
+ for (const asset of assets) {
101
+ const stat = (0, import_node_fs.lstatSync)(asset.source);
102
+ hash.update(asset.relativePath).update("\0");
103
+ hash.update(`${stat.size}:${stat.mtimeMs}`).update("\0");
104
+ }
105
+ return hash.digest("hex").slice(0, 20);
106
+ }
107
+ function makeExecutable(path) {
108
+ if (process.platform === "win32") {
109
+ return;
110
+ }
111
+ const mode = (0, import_node_fs.statSync)(path).mode;
112
+ if ((mode & 73) === 0) {
113
+ (0, import_node_fs.chmodSync)(path, mode | 73);
114
+ }
115
+ }
116
+ function defaultRuntimeCacheRoot(platform = process.platform, home = (0, import_node_os.homedir)(), environment = process.env) {
117
+ const cacheDirectory = platform === "win32" ? environment.LOCALAPPDATA ?? (0, import_node_path.join)(home, "AppData", "Local") : platform === "darwin" ? (0, import_node_path.join)(home, "Library", "Caches") : environment.XDG_CACHE_HOME ?? (0, import_node_path.join)(home, ".cache");
118
+ return (0, import_node_path.join)(cacheDirectory, "github-copilot-sdk", "runtime");
119
+ }
120
+ function materializeRuntimeBundle(sources, cacheRoot = defaultRuntimeCacheRoot()) {
121
+ const assets = collectRuntimeAssets(sources);
122
+ const wrapperName = process.platform === "win32" ? "copilot-runtime.exe" : "copilot-runtime";
123
+ const sourceWrapper = assets.find((asset) => asset.relativePath === wrapperName)?.source;
124
+ const sourceRuntimeNode = assets.find((asset) => asset.relativePath === "runtime.node")?.source;
125
+ validateRuntimeBundle(sourceWrapper ?? "", sourceRuntimeNode ?? "");
126
+ const installDir = (0, import_node_path.join)(cacheRoot, `${sources.platform}-${sourceFingerprint(assets)}`);
127
+ const installedWrapper = (0, import_node_path.join)(installDir, wrapperName);
128
+ const installedRuntimeNode = (0, import_node_path.join)(installDir, "runtime.node");
129
+ if ((0, import_node_fs.existsSync)(installDir)) {
130
+ validateRuntimeBundle(installedWrapper, installedRuntimeNode);
131
+ makeExecutable(installedWrapper);
132
+ return installedWrapper;
133
+ }
134
+ (0, import_node_fs.mkdirSync)(cacheRoot, { recursive: true });
135
+ const stagingDir = (0, import_node_fs.mkdtempSync)((0, import_node_path.join)(cacheRoot, ".runtime-"));
136
+ try {
137
+ for (const asset of assets) {
138
+ const destination = (0, import_node_path.join)(stagingDir, asset.relativePath);
139
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(destination), { recursive: true });
140
+ (0, import_node_fs.copyFileSync)(asset.source, destination);
141
+ }
142
+ const stagedWrapper = (0, import_node_path.join)(stagingDir, wrapperName);
143
+ makeExecutable(stagedWrapper);
144
+ (0, import_node_fs.renameSync)(stagingDir, installDir);
145
+ } catch (error) {
146
+ if (!(0, import_node_fs.existsSync)(installDir)) {
147
+ throw error;
148
+ }
149
+ validateRuntimeBundle(installedWrapper, installedRuntimeNode);
150
+ } finally {
151
+ (0, import_node_fs.rmSync)(stagingDir, { recursive: true, force: true });
152
+ }
153
+ return installedWrapper;
154
+ }
155
+ // Annotate the CommonJS export names for ESM import in node:
156
+ 0 && (module.exports = {
157
+ defaultRuntimeCacheRoot,
158
+ materializeRuntimeBundle
159
+ });
package/dist/client.d.ts CHANGED
@@ -33,6 +33,7 @@ export declare class CopilotClient {
33
33
  private _rpc;
34
34
  private _internalRpc;
35
35
  private processExitPromise;
36
+ private processTransportError;
36
37
  private negotiatedProtocolVersion;
37
38
  /** Connection-level session filesystem config, set via constructor option. */
38
39
  private sessionFsConfig;
@@ -452,17 +453,6 @@ export declare class CopilotClient {
452
453
  * reusing the same `vscode-jsonrpc` framing as the stdio transport.
453
454
  */
454
455
  private connectViaFfi;
455
- /**
456
- * Resolves the CLI entrypoint used for in-process FFI hosting: `COPILOT_CLI_PATH`
457
- * when set, otherwise the bundled platform-package entrypoint.
458
- */
459
- private resolveCliPathForFfi;
460
- /**
461
- * Returns the napi prebuilds folder name for the current host — the
462
- * `<node-platform>-<arch>` convention (e.g. `win32-x64`, `darwin-arm64`,
463
- * `linux-x64`, `linuxmusl-x64`) under which the runtime ships
464
- * `prebuilds/<folder>/runtime.node`.
465
- */
466
456
  private static getNapiPrebuildsFolder;
467
457
  private static isMusl;
468
458
  /**
package/dist/client.js CHANGED
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { existsSync } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
5
  import { Socket } from "node:net";
6
- import { dirname, isAbsolute, join } from "node:path";
6
+ import { dirname, isAbsolute, join, resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import {
9
9
  createMessageConnection,
@@ -20,6 +20,7 @@ import {
20
20
  } from "./generated/rpc.js";
21
21
  import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
22
22
  import { CopilotSession } from "./session.js";
23
+ import { materializeRuntimeBundle } from "./runtimeArtifacts.js";
23
24
  import { createSessionFsAdapter } from "./sessionFsProvider.js";
24
25
  import { createCopilotRequestAdapter } from "./copilotRequestHandler.js";
25
26
  import { getTraceContext } from "./telemetry.js";
@@ -49,7 +50,7 @@ async function waitForChildExit(child, timeoutMs) {
49
50
  if (child.exitCode != null || child.signalCode != null) {
50
51
  return true;
51
52
  }
52
- return new Promise((resolve) => {
53
+ return new Promise((resolve2) => {
53
54
  let timeout;
54
55
  let settled = false;
55
56
  const onExit = () => {
@@ -58,7 +59,7 @@ async function waitForChildExit(child, timeoutMs) {
58
59
  }
59
60
  settled = true;
60
61
  clearTimeout(timeout);
61
- resolve(true);
62
+ resolve2(true);
62
63
  };
63
64
  timeout = setTimeout(() => {
64
65
  if (settled) {
@@ -66,7 +67,7 @@ async function waitForChildExit(child, timeoutMs) {
66
67
  }
67
68
  settled = true;
68
69
  child.off("exit", onExit);
69
- resolve(false);
70
+ resolve2(false);
70
71
  }, timeoutMs);
71
72
  child.once("exit", onExit);
72
73
  if (child.exitCode != null || child.signalCode != null) {
@@ -189,14 +190,17 @@ function getCliPlatformPackageNames() {
189
190
  const variants = process.platform === "linux" ? ["linux", "linuxmusl"] : [process.platform];
190
191
  return variants.map((variant) => `@github/copilot-${variant}-${arch}`);
191
192
  }
192
- function getBundledCliPath() {
193
+ function getBundledCliPackage() {
193
194
  const packageNames = getCliPlatformPackageNames();
194
195
  if (typeof import.meta.resolve === "function") {
195
196
  for (const packageName of packageNames) {
196
197
  try {
197
198
  const packageEntryUrl = import.meta.resolve(packageName);
198
199
  const packageEntryPath = fileURLToPath(packageEntryUrl);
199
- return join(dirname(packageEntryPath), "index.js");
200
+ return {
201
+ root: dirname(packageEntryPath),
202
+ platform: packageName.slice("@github/copilot-".length)
203
+ };
200
204
  } catch {
201
205
  }
202
206
  }
@@ -208,9 +212,13 @@ function getBundledCliPath() {
208
212
  const searchPaths = req.resolve.paths("@github/copilot") ?? [];
209
213
  for (const base of searchPaths) {
210
214
  for (const packageName of packageNames) {
211
- const candidate = join(base, ...packageName.split("/"), "index.js");
215
+ const root = join(base, ...packageName.split("/"));
216
+ const candidate = join(root, "index.js");
212
217
  if (existsSync(candidate)) {
213
- return candidate;
218
+ return {
219
+ root,
220
+ platform: packageName.slice("@github/copilot-".length)
221
+ };
214
222
  }
215
223
  }
216
224
  }
@@ -218,6 +226,13 @@ function getBundledCliPath() {
218
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.`
219
227
  );
220
228
  }
229
+ function getBundledRuntimePath() {
230
+ const bundled = getBundledCliPackage();
231
+ return materializeRuntimeBundle({
232
+ packageRoot: bundled.root,
233
+ platform: bundled.platform
234
+ });
235
+ }
221
236
  class TeardownResilientStreamMessageWriter extends StreamMessageWriter {
222
237
  suppressWriteErrors = false;
223
238
  async write(msg) {
@@ -264,6 +279,7 @@ class CopilotClient {
264
279
  _internalRpc = null;
265
280
  processExitPromise = null;
266
281
  // Rejects when CLI process exits
282
+ processTransportError = null;
267
283
  negotiatedProtocolVersion = null;
268
284
  /** Connection-level session filesystem config, set via constructor option. */
269
285
  sessionFsConfig = null;
@@ -430,7 +446,14 @@ class CopilotClient {
430
446
  const connEnv = conn.kind === "stdio" || conn.kind === "tcp" ? conn.env : void 0;
431
447
  const effectiveEnv = connEnv ?? options.env ?? process.env;
432
448
  this.resolvedEnv = effectiveEnv;
433
- this.resolvedCliPath = conn.kind === "stdio" || conn.kind === "tcp" ? conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath() : void 0;
449
+ if (conn.kind === "stdio" || conn.kind === "tcp") {
450
+ const explicitCliPath = conn.path ?? effectiveEnv.COPILOT_CLI_PATH;
451
+ if (explicitCliPath) {
452
+ this.resolvedCliPath = explicitCliPath;
453
+ } else {
454
+ this.resolvedCliPath = getBundledRuntimePath();
455
+ }
456
+ }
434
457
  const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
435
458
  this.connectionExtraArgs = [...connArgs];
436
459
  this.options = {
@@ -598,6 +621,8 @@ class CopilotClient {
598
621
  if (this.state === "connected") {
599
622
  return;
600
623
  }
624
+ this.forceStopping = false;
625
+ this.processTransportError = null;
601
626
  this.state = "connecting";
602
627
  try {
603
628
  if (this.connectionConfig.kind === "inprocess") {
@@ -630,8 +655,10 @@ class CopilotClient {
630
655
  }
631
656
  this.state = "connected";
632
657
  } catch (error) {
658
+ const startupError = this.processTransportError ?? error;
659
+ await this.forceStop();
633
660
  this.state = "error";
634
- throw error;
661
+ throw startupError;
635
662
  }
636
663
  }
637
664
  /**
@@ -676,7 +703,7 @@ class CopilotClient {
676
703
  lastError = error instanceof Error ? error : new Error(String(error));
677
704
  if (attempt < 3) {
678
705
  const delay = 100 * Math.pow(2, attempt - 1);
679
- await new Promise((resolve) => setTimeout(resolve, delay));
706
+ await new Promise((resolve2) => setTimeout(resolve2, delay));
680
707
  }
681
708
  }
682
709
  }
@@ -743,8 +770,8 @@ class CopilotClient {
743
770
  this.socket = null;
744
771
  try {
745
772
  if (!socket.destroyed) {
746
- await new Promise((resolve) => {
747
- socket.once("close", () => resolve());
773
+ await new Promise((resolve2) => {
774
+ socket.once("close", () => resolve2());
748
775
  socket.end();
749
776
  });
750
777
  }
@@ -1550,8 +1577,8 @@ class CopilotClient {
1550
1577
  async listModels() {
1551
1578
  await this.modelsCacheLock;
1552
1579
  let resolveLock;
1553
- this.modelsCacheLock = new Promise((resolve) => {
1554
- resolveLock = resolve;
1580
+ this.modelsCacheLock = new Promise((resolve2) => {
1581
+ resolveLock = resolve2;
1555
1582
  });
1556
1583
  try {
1557
1584
  if (this.modelsCache !== null) {
@@ -1867,7 +1894,7 @@ class CopilotClient {
1867
1894
  * Start the CLI server process
1868
1895
  */
1869
1896
  async startCLIServer() {
1870
- return new Promise((resolve, reject) => {
1897
+ return new Promise((resolve2, reject) => {
1871
1898
  this.stderrBuffer = "";
1872
1899
  const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
1873
1900
  if (this.options.logLevel) {
@@ -1928,7 +1955,7 @@ class CopilotClient {
1928
1955
  let resolved = false;
1929
1956
  if (this.connectionConfig.kind === "stdio") {
1930
1957
  resolved = true;
1931
- resolve();
1958
+ resolve2();
1932
1959
  } else {
1933
1960
  this.cliProcess.stdout?.on("data", (data) => {
1934
1961
  stdout += data.toString();
@@ -1936,7 +1963,7 @@ class CopilotClient {
1936
1963
  if (match && !resolved) {
1937
1964
  this.runtimePort = parseInt(match[1], 10);
1938
1965
  resolved = true;
1939
- resolve();
1966
+ resolve2();
1940
1967
  }
1941
1968
  });
1942
1969
  }
@@ -1968,26 +1995,27 @@ stderr: ${stderrOutput}`
1968
1995
  });
1969
1996
  this.processExitPromise = new Promise((_, rejectProcessExit) => {
1970
1997
  this.cliProcess.on("exit", (code) => {
1971
- setTimeout(() => {
1972
- const stderrOutput = this.stderrBuffer.trim();
1973
- if (stderrOutput) {
1974
- rejectProcessExit(
1975
- new Error(
1976
- `CLI server exited with code ${code}
1998
+ if (this.messageWriter) {
1999
+ this.messageWriter.suppressWriteErrors = true;
2000
+ }
2001
+ const stderrOutput = this.stderrBuffer.trim();
2002
+ if (stderrOutput) {
2003
+ rejectProcessExit(
2004
+ new Error(
2005
+ `CLI server exited with code ${code}
1977
2006
  stderr: ${stderrOutput}`
1978
- )
1979
- );
1980
- } else {
1981
- rejectProcessExit(
1982
- new Error(`CLI server exited unexpectedly with code ${code}`)
1983
- );
1984
- }
1985
- }, 50);
2007
+ )
2008
+ );
2009
+ } else {
2010
+ rejectProcessExit(
2011
+ new Error(`CLI server exited unexpectedly with code ${code}`)
2012
+ );
2013
+ }
1986
2014
  });
1987
2015
  });
1988
2016
  this.processExitPromise.catch(() => {
1989
2017
  });
1990
- this.cliProcess.on("exit", (code) => {
2018
+ this.cliProcess.on("close", (code) => {
1991
2019
  if (!resolved) {
1992
2020
  resolved = true;
1993
2021
  const stderrOutput = this.stderrBuffer.trim();
@@ -2029,7 +2057,13 @@ stderr: ${stderrOutput}`
2029
2057
  }
2030
2058
  /** Starts the in-process FFI runtime with SDK-managed typed options. */
2031
2059
  async startInProcessFfi() {
2032
- const entrypoint = this.resolveCliPathForFfi();
2060
+ 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");
2033
2067
  const { FfiRuntimeHost } = await import("./ffiRuntimeHost.js");
2034
2068
  const environment = {};
2035
2069
  if (this.options.gitHubToken) {
@@ -2057,12 +2091,7 @@ stderr: ${stderrOutput}`
2057
2091
  if (this.options.enableRemoteSessions) {
2058
2092
  args.push("--remote");
2059
2093
  }
2060
- const host = FfiRuntimeHost.create(
2061
- entrypoint,
2062
- CopilotClient.getNapiPrebuildsFolder(entrypoint),
2063
- environment,
2064
- args
2065
- );
2094
+ const host = FfiRuntimeHost.create(runtimeLibrary, explicitEntrypoint, environment, args);
2066
2095
  this.ffiHost = host;
2067
2096
  await host.start();
2068
2097
  }
@@ -2082,19 +2111,6 @@ stderr: ${stderrOutput}`
2082
2111
  this.attachConnectionHandlers();
2083
2112
  this.connection.listen();
2084
2113
  }
2085
- /**
2086
- * Resolves the CLI entrypoint used for in-process FFI hosting: `COPILOT_CLI_PATH`
2087
- * when set, otherwise the bundled platform-package entrypoint.
2088
- */
2089
- resolveCliPathForFfi() {
2090
- return this.resolvedEnv.COPILOT_CLI_PATH ?? getBundledCliPath();
2091
- }
2092
- /**
2093
- * Returns the napi prebuilds folder name for the current host — the
2094
- * `<node-platform>-<arch>` convention (e.g. `win32-x64`, `darwin-arm64`,
2095
- * `linux-x64`, `linuxmusl-x64`) under which the runtime ships
2096
- * `prebuilds/<folder>/runtime.node`.
2097
- */
2098
2114
  static getNapiPrebuildsFolder(entrypoint) {
2099
2115
  const arch = process.arch;
2100
2116
  if (arch !== "x64" && arch !== "arm64") {
@@ -2130,6 +2146,11 @@ stderr: ${stderrOutput}`
2130
2146
  }
2131
2147
  this.state = "error";
2132
2148
  const reason = err instanceof Error ? err.stack ?? err.message : String(err);
2149
+ const stderrOutput = this.stderrBuffer.trim();
2150
+ this.processTransportError = new Error(
2151
+ `CLI server connection failed: ${reason}${stderrOutput ? `
2152
+ stderr: ${stderrOutput}` : ""}`
2153
+ );
2133
2154
  this.logDebug(`stdin pipe error: ${reason}`);
2134
2155
  try {
2135
2156
  this.connection?.dispose();
@@ -2166,7 +2187,7 @@ stderr: ${stderrOutput}`
2166
2187
  if (!this.runtimePort) {
2167
2188
  throw new Error("Server port not available");
2168
2189
  }
2169
- return new Promise((resolve, reject) => {
2190
+ return new Promise((resolve2, reject) => {
2170
2191
  this.socket = new Socket();
2171
2192
  const connectionTimeout = setTimeout(() => {
2172
2193
  this.socket?.destroy();
@@ -2181,7 +2202,7 @@ stderr: ${stderrOutput}`
2181
2202
  );
2182
2203
  this.attachConnectionHandlers();
2183
2204
  this.connection.listen();
2184
- resolve();
2205
+ resolve2();
2185
2206
  });
2186
2207
  this.socket.on("error", (error) => {
2187
2208
  clearTimeout(connectionTimeout);
@@ -16,16 +16,10 @@ export declare class FfiRuntimeHost {
16
16
  readonly sendStream: Writable;
17
17
  private constructor();
18
18
  /**
19
- * Resolves the cdylib next to the given CLI entrypoint and prepares the FFI host.
20
- * The cdylib is resolved as `prebuilds/<prebuildsFolder>/runtime.node` relative to
21
- * the entrypoint directory (the napi-rs `<node-platform>-<arch>` layout, e.g.
22
- * `linux-x64`). Throws if it cannot be found.
23
- */
24
- static create(cliEntrypoint: string, prebuildsFolder: string, environment: Record<string, string | undefined> | undefined, args: readonly string[]): FfiRuntimeHost;
25
- /**
26
- * Starts the in-process runtime: spawns the CLI worker via the native host,
27
- * waits for readiness, and opens the FFI JSON-RPC connection.
19
+ * Loads the runtime cdylib at the given path and prepares the FFI host.
28
20
  */
21
+ static create(libraryPath: string, cliEntrypoint: string | undefined, environment: Record<string, string | undefined> | undefined, args: readonly string[]): FfiRuntimeHost;
22
+ /** Starts the in-process Rust runtime and opens the FFI JSON-RPC connection. */
29
23
  start(): Promise<void>;
30
24
  private writeFrame;
31
25
  /**
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import koffi from "koffi";
3
- import { dirname, join, resolve } from "node:path";
3
+ import { resolve } from "node:path";
4
4
  import { PassThrough, Writable } from "node:stream";
5
5
  const SYMBOL_PREFIX = "copilot_runtime_";
6
6
  const KEEP_ALIVE_INTERVAL_MS = 1 << 30;
@@ -52,7 +52,7 @@ function loadLibrary(libraryPath) {
52
52
  return loadedLibrary;
53
53
  }
54
54
  function buildArgvJson(cliEntrypoint, args) {
55
- const argv = cliEntrypoint.toLowerCase().endsWith(".js") ? ["node", cliEntrypoint, "--embedded-host", "--no-auto-update"] : [cliEntrypoint, "--embedded-host", "--no-auto-update"];
55
+ const argv = cliEntrypoint ? cliEntrypoint.toLowerCase().endsWith(".js") ? ["node", cliEntrypoint, "--embedded-host", "--no-auto-update"] : [cliEntrypoint, "--embedded-host", "--no-auto-update"] : [];
56
56
  argv.push(...args);
57
57
  return Buffer.from(JSON.stringify(argv), "utf8");
58
58
  }
@@ -107,24 +107,21 @@ class FfiRuntimeHost {
107
107
  /** The stream JSON-RPC writes client→server frames to. */
108
108
  sendStream;
109
109
  /**
110
- * Resolves the cdylib next to the given CLI entrypoint and prepares the FFI host.
111
- * The cdylib is resolved as `prebuilds/<prebuildsFolder>/runtime.node` relative to
112
- * the entrypoint directory (the napi-rs `<node-platform>-<arch>` layout, e.g.
113
- * `linux-x64`). Throws if it cannot be found.
110
+ * Loads the runtime cdylib at the given path and prepares the FFI host.
114
111
  */
115
- static create(cliEntrypoint, prebuildsFolder, environment, args) {
116
- const fullEntrypoint = resolve(cliEntrypoint);
117
- const distDir = dirname(fullEntrypoint);
118
- const libraryPath = join(distDir, "prebuilds", prebuildsFolder, "runtime.node");
119
- if (!existsSync(libraryPath)) {
120
- throw new Error(`FFI runtime library not found. Looked for '${libraryPath}'.`);
112
+ static create(libraryPath, cliEntrypoint, environment, args) {
113
+ const fullLibraryPath = resolve(libraryPath);
114
+ if (!existsSync(fullLibraryPath)) {
115
+ throw new Error(`FFI runtime library not found at '${fullLibraryPath}'.`);
121
116
  }
122
- return new FfiRuntimeHost(libraryPath, fullEntrypoint, environment, args);
117
+ return new FfiRuntimeHost(
118
+ fullLibraryPath,
119
+ cliEntrypoint ? resolve(cliEntrypoint) : void 0,
120
+ environment,
121
+ args
122
+ );
123
123
  }
124
- /**
125
- * Starts the in-process runtime: spawns the CLI worker via the native host,
126
- * waits for readiness, and opens the FFI JSON-RPC connection.
127
- */
124
+ /** Starts the in-process Rust runtime and opens the FFI JSON-RPC connection. */
128
125
  async start() {
129
126
  const argvJson = buildArgvJson(this.cliEntrypoint, this.args);
130
127
  const envJson = buildEnvJson(this.environment);
@@ -144,9 +141,7 @@ class FfiRuntimeHost {
144
141
  );
145
142
  });
146
143
  if (!this.serverId) {
147
- throw new Error(
148
- `copilot_runtime_host_start failed (library '${this.libraryPath}', entrypoint '${this.cliEntrypoint}').`
149
- );
144
+ throw new Error(`copilot_runtime_host_start failed (library '${this.libraryPath}').`);
150
145
  }
151
146
  this.outboundCallback = koffi.register(
152
147
  (_userData, bytesPtr, bytesLen) => this.feedInbound(bytesPtr, bytesLen),
@@ -0,0 +1,6 @@
1
+ export interface RuntimeArtifactSources {
2
+ packageRoot: string;
3
+ platform: string;
4
+ }
5
+ export declare function defaultRuntimeCacheRoot(platform?: NodeJS.Platform, home?: string, environment?: NodeJS.ProcessEnv): string;
6
+ export declare function materializeRuntimeBundle(sources: RuntimeArtifactSources, cacheRoot?: string): string;
@@ -0,0 +1,145 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ chmodSync,
4
+ copyFileSync,
5
+ existsSync,
6
+ lstatSync,
7
+ mkdirSync,
8
+ mkdtempSync,
9
+ readdirSync,
10
+ renameSync,
11
+ rmSync,
12
+ statSync
13
+ } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import { dirname, join, relative, sep } from "node:path";
16
+ const EXCLUDED_TOP_LEVEL = /* @__PURE__ */ new Set([
17
+ "app.js",
18
+ "assets",
19
+ "changelog.json",
20
+ "copilot",
21
+ "copilot.exe",
22
+ "copilot-sdk",
23
+ "foundry-local-sdk",
24
+ "index.js",
25
+ "LICENSE.md",
26
+ "napi-oop-runtime",
27
+ "npm-loader.js",
28
+ "package.json",
29
+ "preloads",
30
+ "pvrecorder",
31
+ "queries",
32
+ "README.md",
33
+ "sdk",
34
+ "sea-loader.js",
35
+ "webview"
36
+ ]);
37
+ function validateFile(path, label) {
38
+ if (!existsSync(path)) {
39
+ throw new Error(`${label} not found at ${path}.`);
40
+ }
41
+ if (statSync(path).size === 0) {
42
+ throw new Error(`${label} at ${path} is empty.`);
43
+ }
44
+ }
45
+ function validateRuntimeBundle(wrapper, runtimeNode) {
46
+ validateFile(wrapper, "Copilot runtime wrapper");
47
+ validateFile(runtimeNode, "Copilot runtime.node");
48
+ }
49
+ function isExcluded(relativePath) {
50
+ const parts = relativePath.split(sep);
51
+ const topLevel = parts[0];
52
+ const fileName = parts.at(-1) ?? "";
53
+ return EXCLUDED_TOP_LEVEL.has(topLevel) || /^tree-sitter.*\.wasm$/.test(topLevel) || /^voice-.*\.js$/.test(topLevel) || fileName === "cli-native.node" || parts.includes("mediaremote-adapter") || fileName.startsWith("copilot-runtime-bin");
54
+ }
55
+ function collectRuntimeAssets(sources) {
56
+ const assets = [];
57
+ const visit = (directory) => {
58
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
59
+ const source = join(directory, entry.name);
60
+ const sourceRelative = relative(sources.packageRoot, source);
61
+ if (isExcluded(sourceRelative)) {
62
+ continue;
63
+ }
64
+ if (entry.isDirectory()) {
65
+ visit(source);
66
+ continue;
67
+ }
68
+ if (!entry.isFile() && !entry.isSymbolicLink()) {
69
+ continue;
70
+ }
71
+ const parts = sourceRelative.split(sep);
72
+ let relativePath = sourceRelative;
73
+ if (parts[0] === "prebuilds") {
74
+ if (parts[1] !== sources.platform || parts.length < 3) {
75
+ continue;
76
+ }
77
+ relativePath = parts.slice(2).join(sep);
78
+ }
79
+ assets.push({ source, relativePath });
80
+ }
81
+ };
82
+ visit(sources.packageRoot);
83
+ return assets.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
84
+ }
85
+ function sourceFingerprint(assets) {
86
+ const hash = createHash("sha256");
87
+ for (const asset of assets) {
88
+ const stat = lstatSync(asset.source);
89
+ hash.update(asset.relativePath).update("\0");
90
+ hash.update(`${stat.size}:${stat.mtimeMs}`).update("\0");
91
+ }
92
+ return hash.digest("hex").slice(0, 20);
93
+ }
94
+ function makeExecutable(path) {
95
+ if (process.platform === "win32") {
96
+ return;
97
+ }
98
+ const mode = statSync(path).mode;
99
+ if ((mode & 73) === 0) {
100
+ chmodSync(path, mode | 73);
101
+ }
102
+ }
103
+ function defaultRuntimeCacheRoot(platform = process.platform, home = homedir(), environment = process.env) {
104
+ const cacheDirectory = platform === "win32" ? environment.LOCALAPPDATA ?? join(home, "AppData", "Local") : platform === "darwin" ? join(home, "Library", "Caches") : environment.XDG_CACHE_HOME ?? join(home, ".cache");
105
+ return join(cacheDirectory, "github-copilot-sdk", "runtime");
106
+ }
107
+ function materializeRuntimeBundle(sources, cacheRoot = defaultRuntimeCacheRoot()) {
108
+ const assets = collectRuntimeAssets(sources);
109
+ const wrapperName = process.platform === "win32" ? "copilot-runtime.exe" : "copilot-runtime";
110
+ const sourceWrapper = assets.find((asset) => asset.relativePath === wrapperName)?.source;
111
+ const sourceRuntimeNode = assets.find((asset) => asset.relativePath === "runtime.node")?.source;
112
+ validateRuntimeBundle(sourceWrapper ?? "", sourceRuntimeNode ?? "");
113
+ const installDir = join(cacheRoot, `${sources.platform}-${sourceFingerprint(assets)}`);
114
+ const installedWrapper = join(installDir, wrapperName);
115
+ const installedRuntimeNode = join(installDir, "runtime.node");
116
+ if (existsSync(installDir)) {
117
+ validateRuntimeBundle(installedWrapper, installedRuntimeNode);
118
+ makeExecutable(installedWrapper);
119
+ return installedWrapper;
120
+ }
121
+ mkdirSync(cacheRoot, { recursive: true });
122
+ const stagingDir = mkdtempSync(join(cacheRoot, ".runtime-"));
123
+ try {
124
+ for (const asset of assets) {
125
+ const destination = join(stagingDir, asset.relativePath);
126
+ mkdirSync(dirname(destination), { recursive: true });
127
+ copyFileSync(asset.source, destination);
128
+ }
129
+ const stagedWrapper = join(stagingDir, wrapperName);
130
+ makeExecutable(stagedWrapper);
131
+ renameSync(stagingDir, installDir);
132
+ } catch (error) {
133
+ if (!existsSync(installDir)) {
134
+ throw error;
135
+ }
136
+ validateRuntimeBundle(installedWrapper, installedRuntimeNode);
137
+ } finally {
138
+ rmSync(stagingDir, { recursive: true, force: true });
139
+ }
140
+ return installedWrapper;
141
+ }
142
+ export {
143
+ defaultRuntimeCacheRoot,
144
+ materializeRuntimeBundle
145
+ };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/github/copilot-sdk.git"
6
6
  },
7
- "version": "1.0.13-preview.3",
7
+ "version": "1.0.13-preview.4",
8
8
  "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC",
9
9
  "main": "./dist/cjs/index.js",
10
10
  "types": "./dist/index.d.ts",