@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 +1 -0
- package/dist/cjs/client.js +75 -54
- package/dist/cjs/ffiRuntimeHost.js +14 -19
- package/dist/cjs/runtimeArtifacts.js +159 -0
- package/dist/client.d.ts +1 -11
- package/dist/client.js +76 -55
- package/dist/ffiRuntimeHost.d.ts +3 -9
- package/dist/ffiRuntimeHost.js +15 -20
- package/dist/runtimeArtifacts.d.ts +6 -0
- package/dist/runtimeArtifacts.js +145 -0
- package/package.json +1 -1
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`.
|
package/dist/cjs/client.js
CHANGED
|
@@ -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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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((
|
|
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((
|
|
770
|
-
socket.once("close", () =>
|
|
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((
|
|
1577
|
-
resolveLock =
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
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
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
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("
|
|
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
|
|
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((
|
|
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
|
-
|
|
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
|
-
*
|
|
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(
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
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(
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
215
|
+
const root = join(base, ...packageName.split("/"));
|
|
216
|
+
const candidate = join(root, "index.js");
|
|
212
217
|
if (existsSync(candidate)) {
|
|
213
|
-
return
|
|
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
|
-
|
|
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
|
|
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((
|
|
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((
|
|
747
|
-
socket.once("close", () =>
|
|
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((
|
|
1554
|
-
resolveLock =
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
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("
|
|
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
|
|
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((
|
|
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
|
-
|
|
2205
|
+
resolve2();
|
|
2185
2206
|
});
|
|
2186
2207
|
this.socket.on("error", (error) => {
|
|
2187
2208
|
clearTimeout(connectionTimeout);
|
package/dist/ffiRuntimeHost.d.ts
CHANGED
|
@@ -16,16 +16,10 @@ export declare class FfiRuntimeHost {
|
|
|
16
16
|
readonly sendStream: Writable;
|
|
17
17
|
private constructor();
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
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
|
/**
|
package/dist/ffiRuntimeHost.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import koffi from "koffi";
|
|
3
|
-
import {
|
|
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
|
-
*
|
|
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(
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
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(
|
|
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.
|
|
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",
|