@github/copilot-sdk 1.0.7-preview.0 → 1.0.7-preview.2

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/types.js CHANGED
@@ -59,6 +59,18 @@ const RuntimeConnection = {
59
59
  */
60
60
  forUri(url, opts = {}) {
61
61
  return { kind: "uri", url, connectionToken: opts.connectionToken };
62
+ },
63
+ /**
64
+ * Host the runtime in-process over the native runtime library's C ABI (FFI).
65
+ *
66
+ * @experimental Per-client options lowered to environment variables (`env`,
67
+ * `telemetry`, `gitHubToken`, `baseDirectory`) are **not** honored in-process;
68
+ * the worker inherits the host process's ambient environment. Set the
69
+ * corresponding environment variables on the host process instead. See
70
+ * https://github.com/github/copilot-sdk/issues/1934.
71
+ */
72
+ forInProcess() {
73
+ return { kind: "inprocess" };
62
74
  }
63
75
  };
64
76
  function convertMcpCallToolResult(callResult) {
package/dist/client.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { CopilotClientOptions, GetAuthStatusResponse, GetStatusResponse, Mo
4
4
  export declare class CopilotClient {
5
5
  private cliStartTimeout;
6
6
  private cliProcess;
7
+ private ffiHost;
7
8
  private connection;
8
9
  private messageWriter;
9
10
  private socket;
@@ -45,6 +46,18 @@ export declare class CopilotClient {
45
46
  get rpc(): ReturnType<typeof createServerRpc>;
46
47
  private logDebugTiming;
47
48
  private logDebug;
49
+ /**
50
+ * Environment variable that overrides the transport when the caller does not set
51
+ * {@link CopilotClientOptions.connection}. Accepts `"inprocess"` or `"stdio"`
52
+ * (case-insensitive); unset preserves the default stdio transport. Any other value
53
+ * is an error.
54
+ */
55
+ private static readonly DEFAULT_CONNECTION_ENV_VAR;
56
+ /**
57
+ * Resolves the default {@link RuntimeConnection} for the no-connection case,
58
+ * honoring {@link CopilotClient.DEFAULT_CONNECTION_ENV_VAR}.
59
+ */
60
+ private static resolveDefaultConnection;
48
61
  /**
49
62
  * Creates a new CopilotClient instance.
50
63
  *
@@ -406,6 +419,14 @@ export declare class CopilotClient {
406
419
  * ```
407
420
  */
408
421
  onLifecycle(handler: SessionLifecycleHandler): () => void;
422
+ /**
423
+ * Builds the environment for the spawned runtime child process (stdio/TCP): applies
424
+ * the auth token, connection token, `COPILOT_HOME`, keychain setting, and telemetry
425
+ * variables on top of the effective env. Not used by the in-process (FFI) transport,
426
+ * whose worker inherits the host process's ambient environment
427
+ * (see {@link CopilotClient.startInProcessFfi}).
428
+ */
429
+ private buildRuntimeEnv;
409
430
  /**
410
431
  * Start the CLI server process
411
432
  */
@@ -414,6 +435,34 @@ export declare class CopilotClient {
414
435
  * Connect to the CLI server (via socket or stdio)
415
436
  */
416
437
  private connectToServer;
438
+ /**
439
+ * Start the in-process FFI runtime host: resolve the CLI entrypoint and native
440
+ * runtime library, then let the native host spawn the CLI worker.
441
+ *
442
+ * The worker inherits this host process's ambient environment; per-client options
443
+ * that lower to environment variables (`env`, `telemetry`, `gitHubToken`,
444
+ * `baseDirectory`) are intentionally not applied here, because the native runtime
445
+ * loads into the shared host process and a single env block cannot carry per-client
446
+ * values. Configure the in-process runtime via the host process environment instead.
447
+ * See https://github.com/github/copilot-sdk/issues/1934.
448
+ */
449
+ private startInProcessFfi;
450
+ /**
451
+ * Connect to the in-process FFI runtime host over its receive/send streams,
452
+ * reusing the same `vscode-jsonrpc` framing as the stdio transport.
453
+ */
454
+ 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`) under which the runtime ships `prebuilds/<folder>/runtime.node`.
464
+ */
465
+ private static getNapiPrebuildsFolder;
417
466
  /**
418
467
  * Connect to child via stdio pipes
419
468
  */
package/dist/client.js CHANGED
@@ -233,6 +233,7 @@ class TeardownResilientStreamMessageWriter extends StreamMessageWriter {
233
233
  class CopilotClient {
234
234
  cliStartTimeout = null;
235
235
  cliProcess = null;
236
+ ffiHost = null;
236
237
  connection = null;
237
238
  messageWriter = null;
238
239
  socket = null;
@@ -309,6 +310,29 @@ class CopilotClient {
309
310
  `);
310
311
  }
311
312
  }
313
+ /**
314
+ * Environment variable that overrides the transport when the caller does not set
315
+ * {@link CopilotClientOptions.connection}. Accepts `"inprocess"` or `"stdio"`
316
+ * (case-insensitive); unset preserves the default stdio transport. Any other value
317
+ * is an error.
318
+ */
319
+ static DEFAULT_CONNECTION_ENV_VAR = "COPILOT_SDK_DEFAULT_CONNECTION";
320
+ /**
321
+ * Resolves the default {@link RuntimeConnection} for the no-connection case,
322
+ * honoring {@link CopilotClient.DEFAULT_CONNECTION_ENV_VAR}.
323
+ */
324
+ static resolveDefaultConnection() {
325
+ const value = process.env[CopilotClient.DEFAULT_CONNECTION_ENV_VAR];
326
+ if (!value || value.toLowerCase() === "stdio") {
327
+ return { kind: "stdio" };
328
+ }
329
+ if (value.toLowerCase() === "inprocess") {
330
+ return { kind: "inprocess" };
331
+ }
332
+ throw new Error(
333
+ `Invalid ${CopilotClient.DEFAULT_CONNECTION_ENV_VAR} value '${value}'. Expected 'inprocess', 'stdio', or unset.`
334
+ );
335
+ }
312
336
  /**
313
337
  * Creates a new CopilotClient instance.
314
338
  *
@@ -337,12 +361,17 @@ class CopilotClient {
337
361
  * ```
338
362
  */
339
363
  constructor(options = {}) {
340
- const conn = options._internalConnection ?? options.connection ?? { kind: "stdio" };
364
+ const conn = options._internalConnection ?? options.connection ?? CopilotClient.resolveDefaultConnection();
341
365
  if (conn.kind === "uri" && (options.gitHubToken !== void 0 || options.useLoggedInUser !== void 0)) {
342
366
  throw new Error(
343
367
  "gitHubToken and useLoggedInUser cannot be used with RuntimeConnection.forUri (external server manages its own auth)"
344
368
  );
345
369
  }
370
+ if (conn.kind === "inprocess" && options.workingDirectory !== void 0) {
371
+ throw new Error(
372
+ "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."
373
+ );
374
+ }
346
375
  if (conn.kind === "tcp" && conn.connectionToken !== void 0) {
347
376
  if (typeof conn.connectionToken !== "string" || conn.connectionToken.length === 0) {
348
377
  throw new Error("connectionToken must be a non-empty string");
@@ -498,7 +527,9 @@ class CopilotClient {
498
527
  }
499
528
  this.state = "connecting";
500
529
  try {
501
- if (!this.isExternalServer) {
530
+ if (this.connectionConfig.kind === "inprocess") {
531
+ await this.startInProcessFfi();
532
+ } else if (!this.isExternalServer) {
502
533
  await this.startCLIServer();
503
534
  }
504
535
  await this.connectToServer();
@@ -547,6 +578,9 @@ class CopilotClient {
547
578
  async stop() {
548
579
  const errors = [];
549
580
  const activeSessions = [...this.sessions.values()];
581
+ if (this.connectionConfig.kind === "inprocess") {
582
+ await Promise.allSettled(activeSessions.map((session) => session.abort()));
583
+ }
550
584
  for (const session of activeSessions) {
551
585
  const sessionId = session.sessionId;
552
586
  let lastError = null;
@@ -575,7 +609,7 @@ class CopilotClient {
575
609
  session._markDisconnected();
576
610
  }
577
611
  this.sessions.clear();
578
- if (this.connection && this.cliProcess && !this.isExternalServer) {
612
+ if (this.connection && (this.cliProcess || this.ffiHost) && !this.isExternalServer) {
579
613
  const runtimeShutdownStart = Date.now();
580
614
  const shutdownPromise = this.rpc.runtime.shutdown();
581
615
  void shutdownPromise.catch(() => void 0);
@@ -660,6 +694,19 @@ class CopilotClient {
660
694
  );
661
695
  }
662
696
  }
697
+ if (this.ffiHost) {
698
+ const host = this.ffiHost;
699
+ this.ffiHost = null;
700
+ try {
701
+ host.dispose();
702
+ } catch (error) {
703
+ errors.push(
704
+ new Error(
705
+ `Failed to dispose in-process runtime host: ${error instanceof Error ? error.message : String(error)}`
706
+ )
707
+ );
708
+ }
709
+ }
663
710
  if (this.cliStartTimeout) {
664
711
  clearTimeout(this.cliStartTimeout);
665
712
  this.cliStartTimeout = null;
@@ -744,6 +791,13 @@ class CopilotClient {
744
791
  }
745
792
  this.cliProcess = null;
746
793
  }
794
+ if (this.ffiHost) {
795
+ try {
796
+ this.ffiHost.dispose();
797
+ } catch {
798
+ }
799
+ this.ffiHost = null;
800
+ }
747
801
  if (this.cliStartTimeout) {
748
802
  clearTimeout(this.cliStartTimeout);
749
803
  this.cliStartTimeout = null;
@@ -988,6 +1042,7 @@ class CopilotClient {
988
1042
  requestExtensions: config.requestExtensions,
989
1043
  extensionSdkPath: config.extensionSdkPath,
990
1044
  extensionInfo: config.extensionInfo,
1045
+ canvasProvider: config.canvasProvider,
991
1046
  commands: config.commands?.map((cmd) => ({
992
1047
  name: cmd.name,
993
1048
  description: cmd.description
@@ -1185,6 +1240,7 @@ class CopilotClient {
1185
1240
  requestExtensions: config.requestExtensions,
1186
1241
  extensionSdkPath: config.extensionSdkPath,
1187
1242
  extensionInfo: config.extensionInfo,
1243
+ canvasProvider: config.canvasProvider,
1188
1244
  commands: config.commands?.map((cmd) => ({
1189
1245
  name: cmd.name,
1190
1246
  description: cmd.description
@@ -1583,6 +1639,41 @@ class CopilotClient {
1583
1639
  this.sessionLifecycleHandlers.delete(wildcardHandler);
1584
1640
  };
1585
1641
  }
1642
+ /**
1643
+ * Builds the environment for the spawned runtime child process (stdio/TCP): applies
1644
+ * the auth token, connection token, `COPILOT_HOME`, keychain setting, and telemetry
1645
+ * variables on top of the effective env. Not used by the in-process (FFI) transport,
1646
+ * whose worker inherits the host process's ambient environment
1647
+ * (see {@link CopilotClient.startInProcessFfi}).
1648
+ */
1649
+ buildRuntimeEnv() {
1650
+ const env = { ...this.resolvedEnv };
1651
+ delete env.NODE_DEBUG;
1652
+ if (this.options.gitHubToken) {
1653
+ env.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
1654
+ }
1655
+ if (this.effectiveConnectionToken) {
1656
+ env.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
1657
+ }
1658
+ if (this.options.baseDirectory) {
1659
+ env.COPILOT_HOME = this.options.baseDirectory;
1660
+ }
1661
+ if (this.options.mode === "empty") {
1662
+ env.COPILOT_DISABLE_KEYTAR = "1";
1663
+ }
1664
+ if (this.options.telemetry) {
1665
+ const t = this.options.telemetry;
1666
+ env.COPILOT_OTEL_ENABLED = "true";
1667
+ if (t.otlpEndpoint !== void 0) env.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
1668
+ if (t.otlpProtocol !== void 0) env.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol;
1669
+ if (t.filePath !== void 0) env.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
1670
+ if (t.exporterType !== void 0) env.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
1671
+ if (t.sourceName !== void 0) env.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
1672
+ if (t.captureContent !== void 0)
1673
+ env.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(t.captureContent);
1674
+ }
1675
+ return env;
1676
+ }
1586
1677
  /**
1587
1678
  * Start the CLI server process
1588
1679
  */
@@ -1616,43 +1707,12 @@ class CopilotClient {
1616
1707
  if (this.options.enableRemoteSessions) {
1617
1708
  args.push("--remote");
1618
1709
  }
1619
- const envWithoutNodeDebug = { ...this.resolvedEnv };
1620
- delete envWithoutNodeDebug.NODE_DEBUG;
1621
- if (this.options.gitHubToken) {
1622
- envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
1623
- }
1624
- if (this.effectiveConnectionToken) {
1625
- envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
1626
- }
1627
- if (this.options.baseDirectory) {
1628
- envWithoutNodeDebug.COPILOT_HOME = this.options.baseDirectory;
1629
- }
1630
- if (this.options.mode === "empty") {
1631
- envWithoutNodeDebug.COPILOT_DISABLE_KEYTAR = "1";
1632
- }
1710
+ const envWithoutNodeDebug = this.buildRuntimeEnv();
1633
1711
  if (!this.resolvedCliPath) {
1634
1712
  throw new Error(
1635
1713
  "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."
1636
1714
  );
1637
1715
  }
1638
- if (this.options.telemetry) {
1639
- const t = this.options.telemetry;
1640
- envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
1641
- if (t.otlpEndpoint !== void 0)
1642
- envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
1643
- if (t.otlpProtocol !== void 0)
1644
- envWithoutNodeDebug.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol;
1645
- if (t.filePath !== void 0)
1646
- envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
1647
- if (t.exporterType !== void 0)
1648
- envWithoutNodeDebug.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
1649
- if (t.sourceName !== void 0)
1650
- envWithoutNodeDebug.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
1651
- if (t.captureContent !== void 0)
1652
- envWithoutNodeDebug.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(
1653
- t.captureContent
1654
- );
1655
- }
1656
1716
  if (!existsSync(this.resolvedCliPath)) {
1657
1717
  throw new Error(
1658
1718
  `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
@@ -1771,11 +1831,66 @@ stderr: ${stderrOutput}`
1771
1831
  return this.connectToParentProcessViaStdio();
1772
1832
  case "stdio":
1773
1833
  return this.connectToChildProcessViaStdio();
1834
+ case "inprocess":
1835
+ return this.connectViaFfi();
1774
1836
  case "tcp":
1775
1837
  case "uri":
1776
1838
  return this.connectViaTcp();
1777
1839
  }
1778
1840
  }
1841
+ /**
1842
+ * Start the in-process FFI runtime host: resolve the CLI entrypoint and native
1843
+ * runtime library, then let the native host spawn the CLI worker.
1844
+ *
1845
+ * The worker inherits this host process's ambient environment; per-client options
1846
+ * that lower to environment variables (`env`, `telemetry`, `gitHubToken`,
1847
+ * `baseDirectory`) are intentionally not applied here, because the native runtime
1848
+ * loads into the shared host process and a single env block cannot carry per-client
1849
+ * values. Configure the in-process runtime via the host process environment instead.
1850
+ * See https://github.com/github/copilot-sdk/issues/1934.
1851
+ */
1852
+ async startInProcessFfi() {
1853
+ const entrypoint = this.resolveCliPathForFfi();
1854
+ const { FfiRuntimeHost } = await import("./ffiRuntimeHost.js");
1855
+ const host = FfiRuntimeHost.create(entrypoint, CopilotClient.getNapiPrebuildsFolder());
1856
+ this.ffiHost = host;
1857
+ await host.start();
1858
+ }
1859
+ /**
1860
+ * Connect to the in-process FFI runtime host over its receive/send streams,
1861
+ * reusing the same `vscode-jsonrpc` framing as the stdio transport.
1862
+ */
1863
+ async connectViaFfi() {
1864
+ if (!this.ffiHost) {
1865
+ throw new Error("In-process FFI runtime host not started");
1866
+ }
1867
+ this.messageWriter = new TeardownResilientStreamMessageWriter(this.ffiHost.sendStream);
1868
+ this.connection = createMessageConnection(
1869
+ new StreamMessageReader(this.ffiHost.receiveStream),
1870
+ this.messageWriter
1871
+ );
1872
+ this.attachConnectionHandlers();
1873
+ this.connection.listen();
1874
+ }
1875
+ /**
1876
+ * Resolves the CLI entrypoint used for in-process FFI hosting: `COPILOT_CLI_PATH`
1877
+ * when set, otherwise the bundled platform-package entrypoint.
1878
+ */
1879
+ resolveCliPathForFfi() {
1880
+ return this.resolvedEnv.COPILOT_CLI_PATH ?? getBundledCliPath();
1881
+ }
1882
+ /**
1883
+ * Returns the napi prebuilds folder name for the current host — the
1884
+ * `<node-platform>-<arch>` convention (e.g. `win32-x64`, `darwin-arm64`,
1885
+ * `linux-x64`) under which the runtime ships `prebuilds/<folder>/runtime.node`.
1886
+ */
1887
+ static getNapiPrebuildsFolder() {
1888
+ const arch = process.arch;
1889
+ if (arch !== "x64" && arch !== "arm64") {
1890
+ throw new Error(`Unsupported architecture '${arch}' for in-process FFI hosting.`);
1891
+ }
1892
+ return `${process.platform}-${arch}`;
1893
+ }
1779
1894
  /**
1780
1895
  * Connect to child via stdio pipes
1781
1896
  */
@@ -1897,8 +2012,9 @@ stderr: ${stderrOutput}`
1897
2012
  return;
1898
2013
  }
1899
2014
  const session = this.sessions.get(notification.sessionId);
2015
+ const event = notification.event;
1900
2016
  if (session) {
1901
- session._dispatchEvent(notification.event);
2017
+ session._dispatchEvent(event);
1902
2018
  }
1903
2019
  }
1904
2020
  handleSessionLifecycleNotification(notification) {
@@ -8,6 +8,9 @@ declare const kSuppressCloseOnDispose: unique symbol;
8
8
  export interface CopilotRequestContext {
9
9
  readonly requestId: string;
10
10
  readonly sessionId?: string;
11
+ readonly agentId?: string;
12
+ readonly parentAgentId?: string;
13
+ readonly interactionType?: string;
11
14
  readonly transport: "http" | "websocket";
12
15
  url: string;
13
16
  headers: LlmInferenceHeaders;
@@ -153,6 +153,9 @@ class CopilotRequestHandler {
153
153
  const ctx = {
154
154
  requestId: exchange.requestId,
155
155
  sessionId: exchange.sessionId,
156
+ agentId: exchange.agentId,
157
+ parentAgentId: exchange.parentAgentId,
158
+ interactionType: exchange.interactionType,
156
159
  transport: exchange.transport,
157
160
  url: exchange.url,
158
161
  headers: exchange.headers,
@@ -278,6 +281,9 @@ function routeChunk(exchange, params) {
278
281
  class CopilotRequestExchange {
279
282
  requestId;
280
283
  sessionId;
284
+ agentId;
285
+ parentAgentId;
286
+ interactionType;
281
287
  method = "GET";
282
288
  url = "";
283
289
  headers = {};
@@ -297,6 +303,9 @@ class CopilotRequestExchange {
297
303
  /** Fill in the request context once the matching start frame arrives. */
298
304
  setContext(params) {
299
305
  this.sessionId = params.sessionId;
306
+ this.agentId = params.agentId;
307
+ this.parentAgentId = params.parentAgentId;
308
+ this.interactionType = params.interactionType;
300
309
  this.method = params.method;
301
310
  this.url = params.url;
302
311
  this.headers = params.headers;
@@ -0,0 +1,40 @@
1
+ import { PassThrough, Writable } from "node:stream";
2
+ export declare class FfiRuntimeHost {
3
+ private readonly libraryPath;
4
+ private readonly cliEntrypoint;
5
+ private readonly environment?;
6
+ private readonly lib;
7
+ private serverId;
8
+ private connectionId;
9
+ private disposed;
10
+ private outboundCallback;
11
+ private keepAliveTimer;
12
+ /** The stream JSON-RPC reads server→client frames from. */
13
+ readonly receiveStream: PassThrough;
14
+ /** The stream JSON-RPC writes client→server frames to. */
15
+ readonly sendStream: Writable;
16
+ private constructor();
17
+ /**
18
+ * Resolves the cdylib next to the given CLI entrypoint and prepares the FFI host.
19
+ * The cdylib is resolved as `prebuilds/<prebuildsFolder>/runtime.node` relative to
20
+ * the entrypoint directory (the napi-rs `<node-platform>-<arch>` layout, e.g.
21
+ * `linux-x64`). Throws if it cannot be found.
22
+ */
23
+ static create(cliEntrypoint: string, prebuildsFolder: string, environment?: Record<string, string | undefined>): FfiRuntimeHost;
24
+ /**
25
+ * Starts the in-process runtime: spawns the CLI worker via the native host,
26
+ * waits for readiness, and opens the FFI JSON-RPC connection.
27
+ */
28
+ start(): Promise<void>;
29
+ private writeFrame;
30
+ /**
31
+ * Native outbound (server→client) callback. koffi delivers it on the JS event loop
32
+ * via a threadsafe function, so the frame is decoded and written straight to
33
+ * {@link receiveStream}. The native pointer is only valid for this call, so the
34
+ * bytes are copied out before returning.
35
+ */
36
+ private feedInbound;
37
+ private unregisterCallback;
38
+ /** Closes the FFI connection, shuts down the native host, and releases resources. */
39
+ dispose(): void;
40
+ }