@github/copilot-sdk 1.0.13-preview.3 → 1.0.13-preview.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -9
- package/dist/cjs/cliVersion.js +31 -0
- package/dist/cjs/client.js +99 -91
- package/dist/cjs/ffiRuntimeHost.js +14 -19
- package/dist/cjs/generated/rpc.js +19 -0
- package/dist/cjs/runtimeArtifacts.js +254 -0
- package/dist/cliVersion.d.ts +2 -0
- package/dist/cliVersion.js +6 -0
- package/dist/client.d.ts +2 -12
- package/dist/client.js +101 -92
- package/dist/ffiRuntimeHost.d.ts +3 -9
- package/dist/ffiRuntimeHost.js +15 -20
- package/dist/generated/rpc.d.ts +267 -7
- package/dist/generated/rpc.js +19 -0
- package/dist/generated/session-events.d.ts +322 -35
- package/dist/index.d.ts +1 -1
- package/dist/runtimeArtifacts.d.ts +18 -0
- package/dist/runtimeArtifacts.js +232 -0
- package/dist/types.d.ts +53 -2
- package/package.json +103 -88
package/dist/client.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { dirname, isAbsolute, join } from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { isIPv6, Socket } from "node:net";
|
|
5
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
8
6
|
import {
|
|
9
7
|
createMessageConnection,
|
|
10
8
|
ErrorCodes,
|
|
@@ -20,6 +18,8 @@ import {
|
|
|
20
18
|
} from "./generated/rpc.js";
|
|
21
19
|
import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
|
|
22
20
|
import { CopilotSession } from "./session.js";
|
|
21
|
+
import { ensureRuntimeBundle } from "./runtimeArtifacts.js";
|
|
22
|
+
import { COPILOT_CLI_VERSION } from "./cliVersion.js";
|
|
23
23
|
import { createSessionFsAdapter } from "./sessionFsProvider.js";
|
|
24
24
|
import { createCopilotRequestAdapter } from "./copilotRequestHandler.js";
|
|
25
25
|
import { getTraceContext } from "./telemetry.js";
|
|
@@ -49,7 +49,7 @@ async function waitForChildExit(child, timeoutMs) {
|
|
|
49
49
|
if (child.exitCode != null || child.signalCode != null) {
|
|
50
50
|
return true;
|
|
51
51
|
}
|
|
52
|
-
return new Promise((
|
|
52
|
+
return new Promise((resolve2) => {
|
|
53
53
|
let timeout;
|
|
54
54
|
let settled = false;
|
|
55
55
|
const onExit = () => {
|
|
@@ -58,7 +58,7 @@ async function waitForChildExit(child, timeoutMs) {
|
|
|
58
58
|
}
|
|
59
59
|
settled = true;
|
|
60
60
|
clearTimeout(timeout);
|
|
61
|
-
|
|
61
|
+
resolve2(true);
|
|
62
62
|
};
|
|
63
63
|
timeout = setTimeout(() => {
|
|
64
64
|
if (settled) {
|
|
@@ -66,7 +66,7 @@ async function waitForChildExit(child, timeoutMs) {
|
|
|
66
66
|
}
|
|
67
67
|
settled = true;
|
|
68
68
|
child.off("exit", onExit);
|
|
69
|
-
|
|
69
|
+
resolve2(false);
|
|
70
70
|
}, timeoutMs);
|
|
71
71
|
child.once("exit", onExit);
|
|
72
72
|
if (child.exitCode != null || child.signalCode != null) {
|
|
@@ -127,6 +127,15 @@ function toWireCustomAgents(agents) {
|
|
|
127
127
|
return { ...rest, mcpServers: toWireMcpServers(mcpServers) };
|
|
128
128
|
});
|
|
129
129
|
}
|
|
130
|
+
function clientInfoToWire(info) {
|
|
131
|
+
if (info == null) return void 0;
|
|
132
|
+
const wire = {};
|
|
133
|
+
if (info.applicationName) wire.editorName = info.applicationName;
|
|
134
|
+
if (info.applicationVersion) wire.editorVersion = info.applicationVersion;
|
|
135
|
+
if (info.integrationName) wire.extensionName = info.integrationName;
|
|
136
|
+
if (info.integrationVersion) wire.extensionVersion = info.integrationVersion;
|
|
137
|
+
return Object.keys(wire).length > 0 ? wire : void 0;
|
|
138
|
+
}
|
|
130
139
|
function toWireLargeOutput(config) {
|
|
131
140
|
if (!config) return void 0;
|
|
132
141
|
const { outputDirectory, ...rest } = config;
|
|
@@ -184,39 +193,8 @@ function getNodeExecPath() {
|
|
|
184
193
|
}
|
|
185
194
|
return process.execPath;
|
|
186
195
|
}
|
|
187
|
-
function
|
|
188
|
-
|
|
189
|
-
const variants = process.platform === "linux" ? ["linux", "linuxmusl"] : [process.platform];
|
|
190
|
-
return variants.map((variant) => `@github/copilot-${variant}-${arch}`);
|
|
191
|
-
}
|
|
192
|
-
function getBundledCliPath() {
|
|
193
|
-
const packageNames = getCliPlatformPackageNames();
|
|
194
|
-
if (typeof import.meta.resolve === "function") {
|
|
195
|
-
for (const packageName of packageNames) {
|
|
196
|
-
try {
|
|
197
|
-
const packageEntryUrl = import.meta.resolve(packageName);
|
|
198
|
-
const packageEntryPath = fileURLToPath(packageEntryUrl);
|
|
199
|
-
return join(dirname(packageEntryPath), "index.js");
|
|
200
|
-
} catch {
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
throw new Error(
|
|
204
|
-
`Could not resolve a @github/copilot platform package (tried ${packageNames.join(", ")}). Ensure @github/copilot is installed, or pass cliPath/cliUrl to CopilotClient.`
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
const req = createRequire(__filename);
|
|
208
|
-
const searchPaths = req.resolve.paths("@github/copilot") ?? [];
|
|
209
|
-
for (const base of searchPaths) {
|
|
210
|
-
for (const packageName of packageNames) {
|
|
211
|
-
const candidate = join(base, ...packageName.split("/"), "index.js");
|
|
212
|
-
if (existsSync(candidate)) {
|
|
213
|
-
return candidate;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
throw new Error(
|
|
218
|
-
`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
|
-
);
|
|
196
|
+
function getBundledRuntimePath() {
|
|
197
|
+
return ensureRuntimeBundle(COPILOT_CLI_VERSION);
|
|
220
198
|
}
|
|
221
199
|
class TeardownResilientStreamMessageWriter extends StreamMessageWriter {
|
|
222
200
|
suppressWriteErrors = false;
|
|
@@ -264,6 +242,7 @@ class CopilotClient {
|
|
|
264
242
|
_internalRpc = null;
|
|
265
243
|
processExitPromise = null;
|
|
266
244
|
// Rejects when CLI process exits
|
|
245
|
+
processTransportError = null;
|
|
267
246
|
negotiatedProtocolVersion = null;
|
|
268
247
|
/** Connection-level session filesystem config, set via constructor option. */
|
|
269
248
|
sessionFsConfig = null;
|
|
@@ -430,7 +409,12 @@ class CopilotClient {
|
|
|
430
409
|
const connEnv = conn.kind === "stdio" || conn.kind === "tcp" ? conn.env : void 0;
|
|
431
410
|
const effectiveEnv = connEnv ?? options.env ?? process.env;
|
|
432
411
|
this.resolvedEnv = effectiveEnv;
|
|
433
|
-
|
|
412
|
+
if (conn.kind === "stdio" || conn.kind === "tcp") {
|
|
413
|
+
const explicitCliPath = conn.path ?? effectiveEnv.COPILOT_CLI_PATH;
|
|
414
|
+
if (explicitCliPath) {
|
|
415
|
+
this.resolvedCliPath = explicitCliPath;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
434
418
|
const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
|
|
435
419
|
this.connectionExtraArgs = [...connArgs];
|
|
436
420
|
this.options = {
|
|
@@ -443,7 +427,8 @@ class CopilotClient {
|
|
|
443
427
|
baseDirectory: options.baseDirectory,
|
|
444
428
|
sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
|
|
445
429
|
enableRemoteSessions: options.enableRemoteSessions ?? false,
|
|
446
|
-
mode: options.mode ?? "copilot-cli"
|
|
430
|
+
mode: options.mode ?? "copilot-cli",
|
|
431
|
+
clientInfo: options.clientInfo
|
|
447
432
|
};
|
|
448
433
|
if (this.options.mode === "empty") {
|
|
449
434
|
const hasPersistence = this.options.baseDirectory !== void 0 || this.sessionFsConfig !== null || // External runtimes manage their own persistence layer; the SDK
|
|
@@ -459,17 +444,29 @@ class CopilotClient {
|
|
|
459
444
|
connectionExtraArgs = [];
|
|
460
445
|
/**
|
|
461
446
|
* Parse CLI URL into host and port
|
|
462
|
-
* Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
|
|
447
|
+
* Supports formats: "host:port", "[ipv6]:port", "http://host:port", "https://host:port", or just "port"
|
|
463
448
|
*/
|
|
464
449
|
parseCliUrl(url) {
|
|
465
|
-
|
|
450
|
+
const cleanUrl = url.replace(/^https?:\/\//, "");
|
|
466
451
|
if (/^\d+$/.test(cleanUrl)) {
|
|
467
452
|
return { host: "localhost", port: parseInt(cleanUrl, 10) };
|
|
468
453
|
}
|
|
454
|
+
const ipv6Match = cleanUrl.match(/^\[([^\]]+)\]:(\d+)$/);
|
|
455
|
+
if (ipv6Match) {
|
|
456
|
+
const host2 = ipv6Match[1];
|
|
457
|
+
if (!isIPv6(host2)) {
|
|
458
|
+
throw new Error(`Invalid cliUrl format: ${url}`);
|
|
459
|
+
}
|
|
460
|
+
const port2 = parseInt(ipv6Match[2], 10);
|
|
461
|
+
if (isNaN(port2) || port2 <= 0 || port2 > 65535) {
|
|
462
|
+
throw new Error(`Invalid port in cliUrl: ${url}`);
|
|
463
|
+
}
|
|
464
|
+
return { host: host2, port: port2 };
|
|
465
|
+
}
|
|
469
466
|
const parts = cleanUrl.split(":");
|
|
470
467
|
if (parts.length !== 2) {
|
|
471
468
|
throw new Error(
|
|
472
|
-
`Invalid cliUrl format: ${url}. Expected "host:port", "http://host:port", or "port"`
|
|
469
|
+
`Invalid cliUrl format: ${url}. Expected "host:port", "[ipv6]:port", "http://host:port", or "port"`
|
|
473
470
|
);
|
|
474
471
|
}
|
|
475
472
|
const host = parts[0] || "localhost";
|
|
@@ -598,6 +595,8 @@ class CopilotClient {
|
|
|
598
595
|
if (this.state === "connected") {
|
|
599
596
|
return;
|
|
600
597
|
}
|
|
598
|
+
this.forceStopping = false;
|
|
599
|
+
this.processTransportError = null;
|
|
601
600
|
this.state = "connecting";
|
|
602
601
|
try {
|
|
603
602
|
if (this.connectionConfig.kind === "inprocess") {
|
|
@@ -630,8 +629,10 @@ class CopilotClient {
|
|
|
630
629
|
}
|
|
631
630
|
this.state = "connected";
|
|
632
631
|
} catch (error) {
|
|
632
|
+
const startupError = this.processTransportError ?? error;
|
|
633
|
+
await this.forceStop();
|
|
633
634
|
this.state = "error";
|
|
634
|
-
throw
|
|
635
|
+
throw startupError;
|
|
635
636
|
}
|
|
636
637
|
}
|
|
637
638
|
/**
|
|
@@ -676,7 +677,7 @@ class CopilotClient {
|
|
|
676
677
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
677
678
|
if (attempt < 3) {
|
|
678
679
|
const delay = 100 * Math.pow(2, attempt - 1);
|
|
679
|
-
await new Promise((
|
|
680
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
680
681
|
}
|
|
681
682
|
}
|
|
682
683
|
}
|
|
@@ -743,8 +744,8 @@ class CopilotClient {
|
|
|
743
744
|
this.socket = null;
|
|
744
745
|
try {
|
|
745
746
|
if (!socket.destroyed) {
|
|
746
|
-
await new Promise((
|
|
747
|
-
socket.once("close", () =>
|
|
747
|
+
await new Promise((resolve2) => {
|
|
748
|
+
socket.once("close", () => resolve2());
|
|
748
749
|
socket.end();
|
|
749
750
|
});
|
|
750
751
|
}
|
|
@@ -1224,6 +1225,7 @@ class CopilotClient {
|
|
|
1224
1225
|
gitHubTokenProviderRegistrationId,
|
|
1225
1226
|
remoteSession: config.remoteSession,
|
|
1226
1227
|
cloud: config.cloud,
|
|
1228
|
+
featureFlags: config.featureFlags,
|
|
1227
1229
|
expAssignments: config.expAssignments,
|
|
1228
1230
|
enableManagedSettings: config.enableManagedSettings,
|
|
1229
1231
|
managedSettings: config.managedSettings
|
|
@@ -1459,6 +1461,7 @@ class CopilotClient {
|
|
|
1459
1461
|
gitHubTokenProviderRegistrationId,
|
|
1460
1462
|
remoteSession: config.remoteSession,
|
|
1461
1463
|
openCanvases: config.openCanvases,
|
|
1464
|
+
featureFlags: config.featureFlags,
|
|
1462
1465
|
expAssignments: config.expAssignments,
|
|
1463
1466
|
enableManagedSettings: config.enableManagedSettings,
|
|
1464
1467
|
managedSettings: config.managedSettings,
|
|
@@ -1550,8 +1553,8 @@ class CopilotClient {
|
|
|
1550
1553
|
async listModels() {
|
|
1551
1554
|
await this.modelsCacheLock;
|
|
1552
1555
|
let resolveLock;
|
|
1553
|
-
this.modelsCacheLock = new Promise((
|
|
1554
|
-
resolveLock =
|
|
1556
|
+
this.modelsCacheLock = new Promise((resolve2) => {
|
|
1557
|
+
resolveLock = resolve2;
|
|
1555
1558
|
});
|
|
1556
1559
|
try {
|
|
1557
1560
|
if (this.modelsCache !== null) {
|
|
@@ -1607,6 +1610,10 @@ class CopilotClient {
|
|
|
1607
1610
|
if (this.onGitHubTelemetry != null) {
|
|
1608
1611
|
connectParams.enableGitHubTelemetryForwarding = true;
|
|
1609
1612
|
}
|
|
1613
|
+
const clientInfo = clientInfoToWire(this.options.clientInfo);
|
|
1614
|
+
if (clientInfo != null) {
|
|
1615
|
+
connectParams.clientInfo = clientInfo;
|
|
1616
|
+
}
|
|
1610
1617
|
const result = await raceAgainstExit(this.internalRpc.connect(connectParams));
|
|
1611
1618
|
serverVersion = result.protocolVersion;
|
|
1612
1619
|
} catch (err) {
|
|
@@ -1867,7 +1874,8 @@ class CopilotClient {
|
|
|
1867
1874
|
* Start the CLI server process
|
|
1868
1875
|
*/
|
|
1869
1876
|
async startCLIServer() {
|
|
1870
|
-
|
|
1877
|
+
this.resolvedCliPath ??= await getBundledRuntimePath();
|
|
1878
|
+
return new Promise((resolve2, reject) => {
|
|
1871
1879
|
this.stderrBuffer = "";
|
|
1872
1880
|
const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
|
|
1873
1881
|
if (this.options.logLevel) {
|
|
@@ -1904,7 +1912,7 @@ class CopilotClient {
|
|
|
1904
1912
|
}
|
|
1905
1913
|
if (!existsSync(this.resolvedCliPath)) {
|
|
1906
1914
|
throw new Error(
|
|
1907
|
-
`Copilot CLI not found at ${this.resolvedCliPath}.
|
|
1915
|
+
`Copilot CLI not found at ${this.resolvedCliPath}. Set COPILOT_CLI_PATH to use a custom installation.`
|
|
1908
1916
|
);
|
|
1909
1917
|
}
|
|
1910
1918
|
const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
|
|
@@ -1928,7 +1936,7 @@ class CopilotClient {
|
|
|
1928
1936
|
let resolved = false;
|
|
1929
1937
|
if (this.connectionConfig.kind === "stdio") {
|
|
1930
1938
|
resolved = true;
|
|
1931
|
-
|
|
1939
|
+
resolve2();
|
|
1932
1940
|
} else {
|
|
1933
1941
|
this.cliProcess.stdout?.on("data", (data) => {
|
|
1934
1942
|
stdout += data.toString();
|
|
@@ -1936,7 +1944,7 @@ class CopilotClient {
|
|
|
1936
1944
|
if (match && !resolved) {
|
|
1937
1945
|
this.runtimePort = parseInt(match[1], 10);
|
|
1938
1946
|
resolved = true;
|
|
1939
|
-
|
|
1947
|
+
resolve2();
|
|
1940
1948
|
}
|
|
1941
1949
|
});
|
|
1942
1950
|
}
|
|
@@ -1968,26 +1976,27 @@ stderr: ${stderrOutput}`
|
|
|
1968
1976
|
});
|
|
1969
1977
|
this.processExitPromise = new Promise((_, rejectProcessExit) => {
|
|
1970
1978
|
this.cliProcess.on("exit", (code) => {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1979
|
+
if (this.messageWriter) {
|
|
1980
|
+
this.messageWriter.suppressWriteErrors = true;
|
|
1981
|
+
}
|
|
1982
|
+
const stderrOutput = this.stderrBuffer.trim();
|
|
1983
|
+
if (stderrOutput) {
|
|
1984
|
+
rejectProcessExit(
|
|
1985
|
+
new Error(
|
|
1986
|
+
`CLI server exited with code ${code}
|
|
1977
1987
|
stderr: ${stderrOutput}`
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
}, 50);
|
|
1988
|
+
)
|
|
1989
|
+
);
|
|
1990
|
+
} else {
|
|
1991
|
+
rejectProcessExit(
|
|
1992
|
+
new Error(`CLI server exited unexpectedly with code ${code}`)
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1986
1995
|
});
|
|
1987
1996
|
});
|
|
1988
1997
|
this.processExitPromise.catch(() => {
|
|
1989
1998
|
});
|
|
1990
|
-
this.cliProcess.on("
|
|
1999
|
+
this.cliProcess.on("close", (code) => {
|
|
1991
2000
|
if (!resolved) {
|
|
1992
2001
|
resolved = true;
|
|
1993
2002
|
const stderrOutput = this.stderrBuffer.trim();
|
|
@@ -2029,7 +2038,20 @@ stderr: ${stderrOutput}`
|
|
|
2029
2038
|
}
|
|
2030
2039
|
/** Starts the in-process FFI runtime with SDK-managed typed options. */
|
|
2031
2040
|
async startInProcessFfi() {
|
|
2032
|
-
const
|
|
2041
|
+
const explicitEntrypoint = this.resolvedEnv.COPILOT_CLI_PATH;
|
|
2042
|
+
let runtimeLibrary;
|
|
2043
|
+
if (explicitEntrypoint) {
|
|
2044
|
+
const entrypointDirectory = dirname(resolve(explicitEntrypoint));
|
|
2045
|
+
const adjacentRuntime = join(entrypointDirectory, "runtime.node");
|
|
2046
|
+
runtimeLibrary = existsSync(adjacentRuntime) ? adjacentRuntime : join(
|
|
2047
|
+
entrypointDirectory,
|
|
2048
|
+
"prebuilds",
|
|
2049
|
+
CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint),
|
|
2050
|
+
"runtime.node"
|
|
2051
|
+
);
|
|
2052
|
+
} else {
|
|
2053
|
+
runtimeLibrary = join(dirname(await getBundledRuntimePath()), "runtime.node");
|
|
2054
|
+
}
|
|
2033
2055
|
const { FfiRuntimeHost } = await import("./ffiRuntimeHost.js");
|
|
2034
2056
|
const environment = {};
|
|
2035
2057
|
if (this.options.gitHubToken) {
|
|
@@ -2057,12 +2079,7 @@ stderr: ${stderrOutput}`
|
|
|
2057
2079
|
if (this.options.enableRemoteSessions) {
|
|
2058
2080
|
args.push("--remote");
|
|
2059
2081
|
}
|
|
2060
|
-
const host = FfiRuntimeHost.create(
|
|
2061
|
-
entrypoint,
|
|
2062
|
-
CopilotClient.getNapiPrebuildsFolder(entrypoint),
|
|
2063
|
-
environment,
|
|
2064
|
-
args
|
|
2065
|
-
);
|
|
2082
|
+
const host = FfiRuntimeHost.create(runtimeLibrary, explicitEntrypoint, environment, args);
|
|
2066
2083
|
this.ffiHost = host;
|
|
2067
2084
|
await host.start();
|
|
2068
2085
|
}
|
|
@@ -2082,19 +2099,6 @@ stderr: ${stderrOutput}`
|
|
|
2082
2099
|
this.attachConnectionHandlers();
|
|
2083
2100
|
this.connection.listen();
|
|
2084
2101
|
}
|
|
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
2102
|
static getNapiPrebuildsFolder(entrypoint) {
|
|
2099
2103
|
const arch = process.arch;
|
|
2100
2104
|
if (arch !== "x64" && arch !== "arm64") {
|
|
@@ -2130,6 +2134,11 @@ stderr: ${stderrOutput}`
|
|
|
2130
2134
|
}
|
|
2131
2135
|
this.state = "error";
|
|
2132
2136
|
const reason = err instanceof Error ? err.stack ?? err.message : String(err);
|
|
2137
|
+
const stderrOutput = this.stderrBuffer.trim();
|
|
2138
|
+
this.processTransportError = new Error(
|
|
2139
|
+
`CLI server connection failed: ${reason}${stderrOutput ? `
|
|
2140
|
+
stderr: ${stderrOutput}` : ""}`
|
|
2141
|
+
);
|
|
2133
2142
|
this.logDebug(`stdin pipe error: ${reason}`);
|
|
2134
2143
|
try {
|
|
2135
2144
|
this.connection?.dispose();
|
|
@@ -2166,7 +2175,7 @@ stderr: ${stderrOutput}`
|
|
|
2166
2175
|
if (!this.runtimePort) {
|
|
2167
2176
|
throw new Error("Server port not available");
|
|
2168
2177
|
}
|
|
2169
|
-
return new Promise((
|
|
2178
|
+
return new Promise((resolve2, reject) => {
|
|
2170
2179
|
this.socket = new Socket();
|
|
2171
2180
|
const connectionTimeout = setTimeout(() => {
|
|
2172
2181
|
this.socket?.destroy();
|
|
@@ -2181,7 +2190,7 @@ stderr: ${stderrOutput}`
|
|
|
2181
2190
|
);
|
|
2182
2191
|
this.attachConnectionHandlers();
|
|
2183
2192
|
this.connection.listen();
|
|
2184
|
-
|
|
2193
|
+
resolve2();
|
|
2185
2194
|
});
|
|
2186
2195
|
this.socket.on("error", (error) => {
|
|
2187
2196
|
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),
|