@github/copilot-sdk 1.0.6 → 1.0.7-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/client.js +166 -38
- package/dist/cjs/copilotRequestHandler.js +9 -0
- package/dist/cjs/ffiRuntimeHost.js +282 -0
- package/dist/cjs/generated/rpc.js +63 -15
- package/dist/cjs/types.js +12 -0
- package/dist/client.d.ts +49 -0
- package/dist/client.js +156 -38
- package/dist/copilotRequestHandler.d.ts +3 -0
- package/dist/copilotRequestHandler.js +9 -0
- package/dist/ffiRuntimeHost.d.ts +40 -0
- package/dist/ffiRuntimeHost.js +248 -0
- package/dist/generated/rpc.d.ts +459 -9
- package/dist/generated/rpc.js +63 -15
- package/dist/generated/session-events.d.ts +174 -1
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +60 -1
- package/dist/types.js +12 -0
- package/package.json +3 -2
package/dist/cjs/client.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var client_exports = {};
|
|
20
30
|
__export(client_exports, {
|
|
@@ -246,6 +256,7 @@ class TeardownResilientStreamMessageWriter extends import_node.StreamMessageWrit
|
|
|
246
256
|
class CopilotClient {
|
|
247
257
|
cliStartTimeout = null;
|
|
248
258
|
cliProcess = null;
|
|
259
|
+
ffiHost = null;
|
|
249
260
|
connection = null;
|
|
250
261
|
messageWriter = null;
|
|
251
262
|
socket = null;
|
|
@@ -322,6 +333,29 @@ class CopilotClient {
|
|
|
322
333
|
`);
|
|
323
334
|
}
|
|
324
335
|
}
|
|
336
|
+
/**
|
|
337
|
+
* Environment variable that overrides the transport when the caller does not set
|
|
338
|
+
* {@link CopilotClientOptions.connection}. Accepts `"inprocess"` or `"stdio"`
|
|
339
|
+
* (case-insensitive); unset preserves the default stdio transport. Any other value
|
|
340
|
+
* is an error.
|
|
341
|
+
*/
|
|
342
|
+
static DEFAULT_CONNECTION_ENV_VAR = "COPILOT_SDK_DEFAULT_CONNECTION";
|
|
343
|
+
/**
|
|
344
|
+
* Resolves the default {@link RuntimeConnection} for the no-connection case,
|
|
345
|
+
* honoring {@link CopilotClient.DEFAULT_CONNECTION_ENV_VAR}.
|
|
346
|
+
*/
|
|
347
|
+
static resolveDefaultConnection() {
|
|
348
|
+
const value = process.env[CopilotClient.DEFAULT_CONNECTION_ENV_VAR];
|
|
349
|
+
if (!value || value.toLowerCase() === "stdio") {
|
|
350
|
+
return { kind: "stdio" };
|
|
351
|
+
}
|
|
352
|
+
if (value.toLowerCase() === "inprocess") {
|
|
353
|
+
return { kind: "inprocess" };
|
|
354
|
+
}
|
|
355
|
+
throw new Error(
|
|
356
|
+
`Invalid ${CopilotClient.DEFAULT_CONNECTION_ENV_VAR} value '${value}'. Expected 'inprocess', 'stdio', or unset.`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
325
359
|
/**
|
|
326
360
|
* Creates a new CopilotClient instance.
|
|
327
361
|
*
|
|
@@ -350,12 +384,17 @@ class CopilotClient {
|
|
|
350
384
|
* ```
|
|
351
385
|
*/
|
|
352
386
|
constructor(options = {}) {
|
|
353
|
-
const conn = options._internalConnection ?? options.connection ??
|
|
387
|
+
const conn = options._internalConnection ?? options.connection ?? CopilotClient.resolveDefaultConnection();
|
|
354
388
|
if (conn.kind === "uri" && (options.gitHubToken !== void 0 || options.useLoggedInUser !== void 0)) {
|
|
355
389
|
throw new Error(
|
|
356
390
|
"gitHubToken and useLoggedInUser cannot be used with RuntimeConnection.forUri (external server manages its own auth)"
|
|
357
391
|
);
|
|
358
392
|
}
|
|
393
|
+
if (conn.kind === "inprocess" && options.workingDirectory !== void 0) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
"workingDirectory is not supported with RuntimeConnection.forInProcess(): the in-process transport hosts the runtime in this process, so honoring it would require mutating the shared process-global cwd. Change the host process's working directory before constructing the client instead."
|
|
396
|
+
);
|
|
397
|
+
}
|
|
359
398
|
if (conn.kind === "tcp" && conn.connectionToken !== void 0) {
|
|
360
399
|
if (typeof conn.connectionToken !== "string" || conn.connectionToken.length === 0) {
|
|
361
400
|
throw new Error("connectionToken must be a non-empty string");
|
|
@@ -511,7 +550,9 @@ class CopilotClient {
|
|
|
511
550
|
}
|
|
512
551
|
this.state = "connecting";
|
|
513
552
|
try {
|
|
514
|
-
if (
|
|
553
|
+
if (this.connectionConfig.kind === "inprocess") {
|
|
554
|
+
await this.startInProcessFfi();
|
|
555
|
+
} else if (!this.isExternalServer) {
|
|
515
556
|
await this.startCLIServer();
|
|
516
557
|
}
|
|
517
558
|
await this.connectToServer();
|
|
@@ -560,6 +601,9 @@ class CopilotClient {
|
|
|
560
601
|
async stop() {
|
|
561
602
|
const errors = [];
|
|
562
603
|
const activeSessions = [...this.sessions.values()];
|
|
604
|
+
if (this.connectionConfig.kind === "inprocess") {
|
|
605
|
+
await Promise.allSettled(activeSessions.map((session) => session.abort()));
|
|
606
|
+
}
|
|
563
607
|
for (const session of activeSessions) {
|
|
564
608
|
const sessionId = session.sessionId;
|
|
565
609
|
let lastError = null;
|
|
@@ -588,7 +632,7 @@ class CopilotClient {
|
|
|
588
632
|
session._markDisconnected();
|
|
589
633
|
}
|
|
590
634
|
this.sessions.clear();
|
|
591
|
-
if (this.connection && this.cliProcess && !this.isExternalServer) {
|
|
635
|
+
if (this.connection && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
|
|
592
636
|
const runtimeShutdownStart = Date.now();
|
|
593
637
|
const shutdownPromise = this.rpc.runtime.shutdown();
|
|
594
638
|
void shutdownPromise.catch(() => void 0);
|
|
@@ -673,6 +717,19 @@ class CopilotClient {
|
|
|
673
717
|
);
|
|
674
718
|
}
|
|
675
719
|
}
|
|
720
|
+
if (this.ffiHost) {
|
|
721
|
+
const host = this.ffiHost;
|
|
722
|
+
this.ffiHost = null;
|
|
723
|
+
try {
|
|
724
|
+
host.dispose();
|
|
725
|
+
} catch (error) {
|
|
726
|
+
errors.push(
|
|
727
|
+
new Error(
|
|
728
|
+
`Failed to dispose in-process runtime host: ${error instanceof Error ? error.message : String(error)}`
|
|
729
|
+
)
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
676
733
|
if (this.cliStartTimeout) {
|
|
677
734
|
clearTimeout(this.cliStartTimeout);
|
|
678
735
|
this.cliStartTimeout = null;
|
|
@@ -757,6 +814,13 @@ class CopilotClient {
|
|
|
757
814
|
}
|
|
758
815
|
this.cliProcess = null;
|
|
759
816
|
}
|
|
817
|
+
if (this.ffiHost) {
|
|
818
|
+
try {
|
|
819
|
+
this.ffiHost.dispose();
|
|
820
|
+
} catch {
|
|
821
|
+
}
|
|
822
|
+
this.ffiHost = null;
|
|
823
|
+
}
|
|
760
824
|
if (this.cliStartTimeout) {
|
|
761
825
|
clearTimeout(this.cliStartTimeout);
|
|
762
826
|
this.cliStartTimeout = null;
|
|
@@ -1001,6 +1065,7 @@ class CopilotClient {
|
|
|
1001
1065
|
requestExtensions: config.requestExtensions,
|
|
1002
1066
|
extensionSdkPath: config.extensionSdkPath,
|
|
1003
1067
|
extensionInfo: config.extensionInfo,
|
|
1068
|
+
canvasProvider: config.canvasProvider,
|
|
1004
1069
|
commands: config.commands?.map((cmd) => ({
|
|
1005
1070
|
name: cmd.name,
|
|
1006
1071
|
description: cmd.description
|
|
@@ -1055,7 +1120,8 @@ class CopilotClient {
|
|
|
1055
1120
|
gitHubToken: config.gitHubToken,
|
|
1056
1121
|
remoteSession: config.remoteSession,
|
|
1057
1122
|
cloud: config.cloud,
|
|
1058
|
-
expAssignments: config.expAssignments
|
|
1123
|
+
expAssignments: config.expAssignments,
|
|
1124
|
+
enableManagedSettings: config.enableManagedSettings
|
|
1059
1125
|
});
|
|
1060
1126
|
const {
|
|
1061
1127
|
sessionId: returnedSessionId,
|
|
@@ -1197,6 +1263,7 @@ class CopilotClient {
|
|
|
1197
1263
|
requestExtensions: config.requestExtensions,
|
|
1198
1264
|
extensionSdkPath: config.extensionSdkPath,
|
|
1199
1265
|
extensionInfo: config.extensionInfo,
|
|
1266
|
+
canvasProvider: config.canvasProvider,
|
|
1200
1267
|
commands: config.commands?.map((cmd) => ({
|
|
1201
1268
|
name: cmd.name,
|
|
1202
1269
|
description: cmd.description
|
|
@@ -1245,7 +1312,8 @@ class CopilotClient {
|
|
|
1245
1312
|
gitHubToken: config.gitHubToken,
|
|
1246
1313
|
remoteSession: config.remoteSession,
|
|
1247
1314
|
openCanvases: config.openCanvases,
|
|
1248
|
-
expAssignments: config.expAssignments
|
|
1315
|
+
expAssignments: config.expAssignments,
|
|
1316
|
+
enableManagedSettings: config.enableManagedSettings
|
|
1249
1317
|
});
|
|
1250
1318
|
const { workspacePath, capabilities, openCanvases } = response;
|
|
1251
1319
|
session["_workspacePath"] = workspacePath;
|
|
@@ -1594,6 +1662,41 @@ class CopilotClient {
|
|
|
1594
1662
|
this.sessionLifecycleHandlers.delete(wildcardHandler);
|
|
1595
1663
|
};
|
|
1596
1664
|
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Builds the environment for the spawned runtime child process (stdio/TCP): applies
|
|
1667
|
+
* the auth token, connection token, `COPILOT_HOME`, keychain setting, and telemetry
|
|
1668
|
+
* variables on top of the effective env. Not used by the in-process (FFI) transport,
|
|
1669
|
+
* whose worker inherits the host process's ambient environment
|
|
1670
|
+
* (see {@link CopilotClient.startInProcessFfi}).
|
|
1671
|
+
*/
|
|
1672
|
+
buildRuntimeEnv() {
|
|
1673
|
+
const env = { ...this.resolvedEnv };
|
|
1674
|
+
delete env.NODE_DEBUG;
|
|
1675
|
+
if (this.options.gitHubToken) {
|
|
1676
|
+
env.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
|
|
1677
|
+
}
|
|
1678
|
+
if (this.effectiveConnectionToken) {
|
|
1679
|
+
env.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
|
|
1680
|
+
}
|
|
1681
|
+
if (this.options.baseDirectory) {
|
|
1682
|
+
env.COPILOT_HOME = this.options.baseDirectory;
|
|
1683
|
+
}
|
|
1684
|
+
if (this.options.mode === "empty") {
|
|
1685
|
+
env.COPILOT_DISABLE_KEYTAR = "1";
|
|
1686
|
+
}
|
|
1687
|
+
if (this.options.telemetry) {
|
|
1688
|
+
const t = this.options.telemetry;
|
|
1689
|
+
env.COPILOT_OTEL_ENABLED = "true";
|
|
1690
|
+
if (t.otlpEndpoint !== void 0) env.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
|
|
1691
|
+
if (t.otlpProtocol !== void 0) env.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol;
|
|
1692
|
+
if (t.filePath !== void 0) env.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
|
|
1693
|
+
if (t.exporterType !== void 0) env.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
|
|
1694
|
+
if (t.sourceName !== void 0) env.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
|
|
1695
|
+
if (t.captureContent !== void 0)
|
|
1696
|
+
env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(t.captureContent);
|
|
1697
|
+
}
|
|
1698
|
+
return env;
|
|
1699
|
+
}
|
|
1597
1700
|
/**
|
|
1598
1701
|
* Start the CLI server process
|
|
1599
1702
|
*/
|
|
@@ -1627,43 +1730,12 @@ class CopilotClient {
|
|
|
1627
1730
|
if (this.options.enableRemoteSessions) {
|
|
1628
1731
|
args.push("--remote");
|
|
1629
1732
|
}
|
|
1630
|
-
const envWithoutNodeDebug =
|
|
1631
|
-
delete envWithoutNodeDebug.NODE_DEBUG;
|
|
1632
|
-
if (this.options.gitHubToken) {
|
|
1633
|
-
envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
|
|
1634
|
-
}
|
|
1635
|
-
if (this.effectiveConnectionToken) {
|
|
1636
|
-
envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
|
|
1637
|
-
}
|
|
1638
|
-
if (this.options.baseDirectory) {
|
|
1639
|
-
envWithoutNodeDebug.COPILOT_HOME = this.options.baseDirectory;
|
|
1640
|
-
}
|
|
1641
|
-
if (this.options.mode === "empty") {
|
|
1642
|
-
envWithoutNodeDebug.COPILOT_DISABLE_KEYTAR = "1";
|
|
1643
|
-
}
|
|
1733
|
+
const envWithoutNodeDebug = this.buildRuntimeEnv();
|
|
1644
1734
|
if (!this.resolvedCliPath) {
|
|
1645
1735
|
throw new Error(
|
|
1646
1736
|
"Path to Copilot CLI is required. Please supply it via `RuntimeConnection.forStdio({ path })` or `RuntimeConnection.forTcp({ path })`, set the COPILOT_CLI_PATH environment variable, or use `RuntimeConnection.forUri(...)` to connect to an already-running runtime."
|
|
1647
1737
|
);
|
|
1648
1738
|
}
|
|
1649
|
-
if (this.options.telemetry) {
|
|
1650
|
-
const t = this.options.telemetry;
|
|
1651
|
-
envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
|
|
1652
|
-
if (t.otlpEndpoint !== void 0)
|
|
1653
|
-
envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
|
|
1654
|
-
if (t.otlpProtocol !== void 0)
|
|
1655
|
-
envWithoutNodeDebug.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol;
|
|
1656
|
-
if (t.filePath !== void 0)
|
|
1657
|
-
envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
|
|
1658
|
-
if (t.exporterType !== void 0)
|
|
1659
|
-
envWithoutNodeDebug.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
|
|
1660
|
-
if (t.sourceName !== void 0)
|
|
1661
|
-
envWithoutNodeDebug.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
|
|
1662
|
-
if (t.captureContent !== void 0)
|
|
1663
|
-
envWithoutNodeDebug.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(
|
|
1664
|
-
t.captureContent
|
|
1665
|
-
);
|
|
1666
|
-
}
|
|
1667
1739
|
if (!(0, import_node_fs.existsSync)(this.resolvedCliPath)) {
|
|
1668
1740
|
throw new Error(
|
|
1669
1741
|
`Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
|
|
@@ -1782,11 +1854,66 @@ stderr: ${stderrOutput}`
|
|
|
1782
1854
|
return this.connectToParentProcessViaStdio();
|
|
1783
1855
|
case "stdio":
|
|
1784
1856
|
return this.connectToChildProcessViaStdio();
|
|
1857
|
+
case "inprocess":
|
|
1858
|
+
return this.connectViaFfi();
|
|
1785
1859
|
case "tcp":
|
|
1786
1860
|
case "uri":
|
|
1787
1861
|
return this.connectViaTcp();
|
|
1788
1862
|
}
|
|
1789
1863
|
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Start the in-process FFI runtime host: resolve the CLI entrypoint and native
|
|
1866
|
+
* runtime library, then let the native host spawn the CLI worker.
|
|
1867
|
+
*
|
|
1868
|
+
* The worker inherits this host process's ambient environment; per-client options
|
|
1869
|
+
* that lower to environment variables (`env`, `telemetry`, `gitHubToken`,
|
|
1870
|
+
* `baseDirectory`) are intentionally not applied here, because the native runtime
|
|
1871
|
+
* loads into the shared host process and a single env block cannot carry per-client
|
|
1872
|
+
* values. Configure the in-process runtime via the host process environment instead.
|
|
1873
|
+
* See https://github.com/github/copilot-sdk/issues/1934.
|
|
1874
|
+
*/
|
|
1875
|
+
async startInProcessFfi() {
|
|
1876
|
+
const entrypoint = this.resolveCliPathForFfi();
|
|
1877
|
+
const { FfiRuntimeHost } = await import("./ffiRuntimeHost.js");
|
|
1878
|
+
const host = FfiRuntimeHost.create(entrypoint, CopilotClient.getNapiPrebuildsFolder());
|
|
1879
|
+
this.ffiHost = host;
|
|
1880
|
+
await host.start();
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Connect to the in-process FFI runtime host over its receive/send streams,
|
|
1884
|
+
* reusing the same `vscode-jsonrpc` framing as the stdio transport.
|
|
1885
|
+
*/
|
|
1886
|
+
async connectViaFfi() {
|
|
1887
|
+
if (!this.ffiHost) {
|
|
1888
|
+
throw new Error("In-process FFI runtime host not started");
|
|
1889
|
+
}
|
|
1890
|
+
this.messageWriter = new TeardownResilientStreamMessageWriter(this.ffiHost.sendStream);
|
|
1891
|
+
this.connection = (0, import_node.createMessageConnection)(
|
|
1892
|
+
new import_node.StreamMessageReader(this.ffiHost.receiveStream),
|
|
1893
|
+
this.messageWriter
|
|
1894
|
+
);
|
|
1895
|
+
this.attachConnectionHandlers();
|
|
1896
|
+
this.connection.listen();
|
|
1897
|
+
}
|
|
1898
|
+
/**
|
|
1899
|
+
* Resolves the CLI entrypoint used for in-process FFI hosting: `COPILOT_CLI_PATH`
|
|
1900
|
+
* when set, otherwise the bundled platform-package entrypoint.
|
|
1901
|
+
*/
|
|
1902
|
+
resolveCliPathForFfi() {
|
|
1903
|
+
return this.resolvedEnv.COPILOT_CLI_PATH ?? getBundledCliPath();
|
|
1904
|
+
}
|
|
1905
|
+
/**
|
|
1906
|
+
* Returns the napi prebuilds folder name for the current host — the
|
|
1907
|
+
* `<node-platform>-<arch>` convention (e.g. `win32-x64`, `darwin-arm64`,
|
|
1908
|
+
* `linux-x64`) under which the runtime ships `prebuilds/<folder>/runtime.node`.
|
|
1909
|
+
*/
|
|
1910
|
+
static getNapiPrebuildsFolder() {
|
|
1911
|
+
const arch = process.arch;
|
|
1912
|
+
if (arch !== "x64" && arch !== "arm64") {
|
|
1913
|
+
throw new Error(`Unsupported architecture '${arch}' for in-process FFI hosting.`);
|
|
1914
|
+
}
|
|
1915
|
+
return `${process.platform}-${arch}`;
|
|
1916
|
+
}
|
|
1790
1917
|
/**
|
|
1791
1918
|
* Connect to child via stdio pipes
|
|
1792
1919
|
*/
|
|
@@ -1908,8 +2035,9 @@ stderr: ${stderrOutput}`
|
|
|
1908
2035
|
return;
|
|
1909
2036
|
}
|
|
1910
2037
|
const session = this.sessions.get(notification.sessionId);
|
|
2038
|
+
const event = notification.event;
|
|
1911
2039
|
if (session) {
|
|
1912
|
-
session._dispatchEvent(
|
|
2040
|
+
session._dispatchEvent(event);
|
|
1913
2041
|
}
|
|
1914
2042
|
}
|
|
1915
2043
|
handleSessionLifecycleNotification(notification) {
|
|
@@ -180,6 +180,9 @@ class CopilotRequestHandler {
|
|
|
180
180
|
const ctx = {
|
|
181
181
|
requestId: exchange.requestId,
|
|
182
182
|
sessionId: exchange.sessionId,
|
|
183
|
+
agentId: exchange.agentId,
|
|
184
|
+
parentAgentId: exchange.parentAgentId,
|
|
185
|
+
interactionType: exchange.interactionType,
|
|
183
186
|
transport: exchange.transport,
|
|
184
187
|
url: exchange.url,
|
|
185
188
|
headers: exchange.headers,
|
|
@@ -305,6 +308,9 @@ function routeChunk(exchange, params) {
|
|
|
305
308
|
class CopilotRequestExchange {
|
|
306
309
|
requestId;
|
|
307
310
|
sessionId;
|
|
311
|
+
agentId;
|
|
312
|
+
parentAgentId;
|
|
313
|
+
interactionType;
|
|
308
314
|
method = "GET";
|
|
309
315
|
url = "";
|
|
310
316
|
headers = {};
|
|
@@ -324,6 +330,9 @@ class CopilotRequestExchange {
|
|
|
324
330
|
/** Fill in the request context once the matching start frame arrives. */
|
|
325
331
|
setContext(params) {
|
|
326
332
|
this.sessionId = params.sessionId;
|
|
333
|
+
this.agentId = params.agentId;
|
|
334
|
+
this.parentAgentId = params.parentAgentId;
|
|
335
|
+
this.interactionType = params.interactionType;
|
|
327
336
|
this.method = params.method;
|
|
328
337
|
this.url = params.url;
|
|
329
338
|
this.headers = params.headers;
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var ffiRuntimeHost_exports = {};
|
|
30
|
+
__export(ffiRuntimeHost_exports, {
|
|
31
|
+
FfiRuntimeHost: () => FfiRuntimeHost
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(ffiRuntimeHost_exports);
|
|
34
|
+
var import_node_fs = require("node:fs");
|
|
35
|
+
var import_koffi = __toESM(require("koffi"), 1);
|
|
36
|
+
var import_node_path = require("node:path");
|
|
37
|
+
var import_node_stream = require("node:stream");
|
|
38
|
+
const SYMBOL_PREFIX = "copilot_runtime_";
|
|
39
|
+
const KEEP_ALIVE_INTERVAL_MS = 1 << 30;
|
|
40
|
+
let loadedLibraryPath;
|
|
41
|
+
let loadedLibrary;
|
|
42
|
+
function loadLibrary(libraryPath) {
|
|
43
|
+
if (loadedLibrary) {
|
|
44
|
+
if (loadedLibraryPath !== libraryPath) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`An in-process FFI runtime library is already loaded from '${loadedLibraryPath}'; loading a different library from '${libraryPath}' in the same process is not supported.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return loadedLibrary;
|
|
50
|
+
}
|
|
51
|
+
const lib = import_koffi.default.load(libraryPath);
|
|
52
|
+
const outboundCallbackType = import_koffi.default.pointer(
|
|
53
|
+
import_koffi.default.proto(
|
|
54
|
+
`void ${SYMBOL_PREFIX}outbound(void *userData, uint8 *bytesPtr, size_t bytesLen)`
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
loadedLibrary = {
|
|
58
|
+
hostStart: lib.func(`${SYMBOL_PREFIX}host_start`, "uint32", [
|
|
59
|
+
"uint8*",
|
|
60
|
+
"size_t",
|
|
61
|
+
"uint8*",
|
|
62
|
+
"size_t"
|
|
63
|
+
]),
|
|
64
|
+
hostShutdown: lib.func(`${SYMBOL_PREFIX}host_shutdown`, "bool", ["uint32"]),
|
|
65
|
+
connectionOpen: lib.func(`${SYMBOL_PREFIX}connection_open`, "uint32", [
|
|
66
|
+
"uint32",
|
|
67
|
+
outboundCallbackType,
|
|
68
|
+
"void*",
|
|
69
|
+
"uint8*",
|
|
70
|
+
"size_t",
|
|
71
|
+
"uint8*",
|
|
72
|
+
"size_t",
|
|
73
|
+
"uint8*",
|
|
74
|
+
"size_t"
|
|
75
|
+
]),
|
|
76
|
+
connectionWrite: lib.func(`${SYMBOL_PREFIX}connection_write`, "bool", [
|
|
77
|
+
"uint32",
|
|
78
|
+
"uint8*",
|
|
79
|
+
"size_t"
|
|
80
|
+
]),
|
|
81
|
+
connectionClose: lib.func(`${SYMBOL_PREFIX}connection_close`, "bool", ["uint32"]),
|
|
82
|
+
outboundCallbackType
|
|
83
|
+
};
|
|
84
|
+
loadedLibraryPath = libraryPath;
|
|
85
|
+
return loadedLibrary;
|
|
86
|
+
}
|
|
87
|
+
function buildArgvJson(cliEntrypoint) {
|
|
88
|
+
const argv = cliEntrypoint.toLowerCase().endsWith(".js") ? ["node", cliEntrypoint, "--embedded-host", "--no-auto-update"] : [cliEntrypoint, "--embedded-host", "--no-auto-update"];
|
|
89
|
+
return Buffer.from(JSON.stringify(argv), "utf8");
|
|
90
|
+
}
|
|
91
|
+
function buildEnvJson(environment) {
|
|
92
|
+
if (!environment) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const obj = {};
|
|
96
|
+
for (const [key, value] of Object.entries(environment)) {
|
|
97
|
+
if (value !== void 0) {
|
|
98
|
+
obj[key] = value;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (Object.keys(obj).length === 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
return Buffer.from(JSON.stringify(obj), "utf8");
|
|
105
|
+
}
|
|
106
|
+
class FfiRuntimeHost {
|
|
107
|
+
constructor(libraryPath, cliEntrypoint, environment) {
|
|
108
|
+
this.libraryPath = libraryPath;
|
|
109
|
+
this.cliEntrypoint = cliEntrypoint;
|
|
110
|
+
this.environment = environment;
|
|
111
|
+
this.lib = loadLibrary(libraryPath);
|
|
112
|
+
this.receiveStream = new import_node_stream.PassThrough();
|
|
113
|
+
this.sendStream = new import_node_stream.Writable({
|
|
114
|
+
// connection_write enqueues the frame into the runtime's inbound channel and
|
|
115
|
+
// returns immediately, so a synchronous FFI call is sufficient here.
|
|
116
|
+
write: (chunk, _encoding, callback) => {
|
|
117
|
+
try {
|
|
118
|
+
this.writeFrame(chunk);
|
|
119
|
+
callback();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
callback(error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
libraryPath;
|
|
127
|
+
cliEntrypoint;
|
|
128
|
+
environment;
|
|
129
|
+
lib;
|
|
130
|
+
serverId = 0;
|
|
131
|
+
connectionId = 0;
|
|
132
|
+
disposed = false;
|
|
133
|
+
outboundCallback;
|
|
134
|
+
keepAliveTimer;
|
|
135
|
+
/** The stream JSON-RPC reads server→client frames from. */
|
|
136
|
+
receiveStream;
|
|
137
|
+
/** The stream JSON-RPC writes client→server frames to. */
|
|
138
|
+
sendStream;
|
|
139
|
+
/**
|
|
140
|
+
* Resolves the cdylib next to the given CLI entrypoint and prepares the FFI host.
|
|
141
|
+
* The cdylib is resolved as `prebuilds/<prebuildsFolder>/runtime.node` relative to
|
|
142
|
+
* the entrypoint directory (the napi-rs `<node-platform>-<arch>` layout, e.g.
|
|
143
|
+
* `linux-x64`). Throws if it cannot be found.
|
|
144
|
+
*/
|
|
145
|
+
static create(cliEntrypoint, prebuildsFolder, environment) {
|
|
146
|
+
const fullEntrypoint = (0, import_node_path.resolve)(cliEntrypoint);
|
|
147
|
+
const distDir = (0, import_node_path.dirname)(fullEntrypoint);
|
|
148
|
+
const libraryPath = (0, import_node_path.join)(distDir, "prebuilds", prebuildsFolder, "runtime.node");
|
|
149
|
+
if (!(0, import_node_fs.existsSync)(libraryPath)) {
|
|
150
|
+
throw new Error(`FFI runtime library not found. Looked for '${libraryPath}'.`);
|
|
151
|
+
}
|
|
152
|
+
return new FfiRuntimeHost(libraryPath, fullEntrypoint, environment);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Starts the in-process runtime: spawns the CLI worker via the native host,
|
|
156
|
+
* waits for readiness, and opens the FFI JSON-RPC connection.
|
|
157
|
+
*/
|
|
158
|
+
async start() {
|
|
159
|
+
const argvJson = buildArgvJson(this.cliEntrypoint);
|
|
160
|
+
const envJson = buildEnvJson(this.environment);
|
|
161
|
+
this.serverId = await new Promise((resolvePromise, rejectPromise) => {
|
|
162
|
+
this.lib.hostStart.async(
|
|
163
|
+
argvJson,
|
|
164
|
+
argvJson.length,
|
|
165
|
+
envJson,
|
|
166
|
+
envJson ? envJson.length : 0,
|
|
167
|
+
(error, result) => {
|
|
168
|
+
if (error) {
|
|
169
|
+
rejectPromise(error);
|
|
170
|
+
} else {
|
|
171
|
+
resolvePromise(result);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
if (!this.serverId) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`copilot_runtime_host_start failed (library '${this.libraryPath}', entrypoint '${this.cliEntrypoint}').`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
this.outboundCallback = import_koffi.default.register(
|
|
182
|
+
(_userData, bytesPtr, bytesLen) => this.feedInbound(bytesPtr, bytesLen),
|
|
183
|
+
this.lib.outboundCallbackType
|
|
184
|
+
);
|
|
185
|
+
this.connectionId = this.lib.connectionOpen(
|
|
186
|
+
this.serverId,
|
|
187
|
+
this.outboundCallback,
|
|
188
|
+
null,
|
|
189
|
+
null,
|
|
190
|
+
0,
|
|
191
|
+
null,
|
|
192
|
+
0,
|
|
193
|
+
null,
|
|
194
|
+
0
|
|
195
|
+
);
|
|
196
|
+
if (!this.connectionId) {
|
|
197
|
+
this.unregisterCallback();
|
|
198
|
+
this.lib.hostShutdown(this.serverId);
|
|
199
|
+
this.serverId = 0;
|
|
200
|
+
throw new Error("copilot_runtime_connection_open failed.");
|
|
201
|
+
}
|
|
202
|
+
this.keepAliveTimer = setInterval(() => {
|
|
203
|
+
}, KEEP_ALIVE_INTERVAL_MS);
|
|
204
|
+
}
|
|
205
|
+
writeFrame(frame) {
|
|
206
|
+
if (this.disposed || !this.connectionId) {
|
|
207
|
+
throw new Error("The in-process runtime connection is closed.");
|
|
208
|
+
}
|
|
209
|
+
const ok = this.lib.connectionWrite(this.connectionId, frame, frame.length);
|
|
210
|
+
if (!ok) {
|
|
211
|
+
throw new Error("Failed to write a frame to the in-process runtime connection.");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Native outbound (server→client) callback. koffi delivers it on the JS event loop
|
|
216
|
+
* via a threadsafe function, so the frame is decoded and written straight to
|
|
217
|
+
* {@link receiveStream}. The native pointer is only valid for this call, so the
|
|
218
|
+
* bytes are copied out before returning.
|
|
219
|
+
*/
|
|
220
|
+
feedInbound(bytesPtr, bytesLen) {
|
|
221
|
+
try {
|
|
222
|
+
if (this.disposed || this.receiveStream.writableEnded) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const length = Number(bytesLen);
|
|
226
|
+
if (!bytesPtr || length <= 0) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const bytes = import_koffi.default.decode(
|
|
230
|
+
bytesPtr,
|
|
231
|
+
import_koffi.default.array("uint8", length, "Typed")
|
|
232
|
+
);
|
|
233
|
+
this.receiveStream.write(Buffer.from(bytes));
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error(
|
|
236
|
+
`In-process FFI inbound callback failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
unregisterCallback() {
|
|
241
|
+
if (this.outboundCallback === void 0) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const callback = this.outboundCallback;
|
|
245
|
+
this.outboundCallback = void 0;
|
|
246
|
+
try {
|
|
247
|
+
import_koffi.default.unregister(callback);
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** Closes the FFI connection, shuts down the native host, and releases resources. */
|
|
252
|
+
dispose() {
|
|
253
|
+
if (this.disposed) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
this.disposed = true;
|
|
257
|
+
if (this.keepAliveTimer !== void 0) {
|
|
258
|
+
clearInterval(this.keepAliveTimer);
|
|
259
|
+
this.keepAliveTimer = void 0;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
if (this.connectionId) {
|
|
263
|
+
this.lib.connectionClose(this.connectionId);
|
|
264
|
+
this.connectionId = 0;
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
if (this.serverId) {
|
|
270
|
+
this.lib.hostShutdown(this.serverId);
|
|
271
|
+
this.serverId = 0;
|
|
272
|
+
}
|
|
273
|
+
} catch {
|
|
274
|
+
}
|
|
275
|
+
this.receiveStream.end();
|
|
276
|
+
this.unregisterCallback();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
280
|
+
0 && (module.exports = {
|
|
281
|
+
FfiRuntimeHost
|
|
282
|
+
});
|