@github/copilot-sdk 0.3.0 → 1.0.0-beta.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/README.md CHANGED
@@ -88,6 +88,7 @@ new CopilotClient(options?: CopilotClientOptions)
88
88
  - `autoStart?: boolean` - Auto-start server (default: true)
89
89
  - `gitHubToken?: string` - GitHub token for authentication. When provided, takes priority over other auth methods.
90
90
  - `useLoggedInUser?: boolean` - Whether to use logged-in user for authentication (default: true, but false when `gitHubToken` is provided). Cannot be used with `cliUrl`.
91
+ - `copilotHome?: string` - Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned CLI process. When not set, the CLI defaults to `~/.copilot`. Useful in restricted environments where only specific directories are writable. Ignored when using `cliUrl`.
91
92
  - `telemetry?: TelemetryConfig` - OpenTelemetry configuration for the CLI process. Providing this object enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below.
92
93
  - `onGetTraceContext?: TraceContextProvider` - Advanced: callback for linking your application's own OpenTelemetry spans into the same distributed trace as the CLI's spans. Not needed for normal telemetry collection. See [Telemetry](#telemetry) below.
93
94
 
@@ -109,6 +109,8 @@ class CopilotClient {
109
109
  options;
110
110
  isExternalServer = false;
111
111
  forceStopping = false;
112
+ /** Token sent in `connect`; auto-generated when the SDK spawns its own CLI in TCP mode. */
113
+ effectiveConnectionToken;
112
114
  onListModels;
113
115
  onGetTraceContext;
114
116
  modelsCache = null;
@@ -116,6 +118,7 @@ class CopilotClient {
116
118
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
117
119
  typedLifecycleHandlers = /* @__PURE__ */ new Map();
118
120
  _rpc = null;
121
+ _internalRpc = null;
119
122
  processExitPromise = null;
120
123
  // Rejects when CLI process exits
121
124
  negotiatedProtocolVersion = null;
@@ -134,6 +137,19 @@ class CopilotClient {
134
137
  }
135
138
  return this._rpc;
136
139
  }
140
+ /**
141
+ * Internal RPC surface (e.g. handshake helpers). Not part of the public API.
142
+ * @internal
143
+ */
144
+ get internalRpc() {
145
+ if (!this.connection) {
146
+ throw new Error("Client is not connected. Call start() first.");
147
+ }
148
+ if (!this._internalRpc) {
149
+ this._internalRpc = (0, import_rpc.createInternalServerRpc)(this.connection);
150
+ }
151
+ return this._internalRpc;
152
+ }
137
153
  /**
138
154
  * Creates a new CopilotClient instance.
139
155
  *
@@ -169,6 +185,17 @@ class CopilotClient {
169
185
  "gitHubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
170
186
  );
171
187
  }
188
+ if (options.tcpConnectionToken !== void 0) {
189
+ if (typeof options.tcpConnectionToken !== "string" || options.tcpConnectionToken.length === 0) {
190
+ throw new Error("tcpConnectionToken must be a non-empty string");
191
+ }
192
+ if (options.useStdio === true) {
193
+ throw new Error("tcpConnectionToken cannot be used with useStdio: true");
194
+ }
195
+ }
196
+ const willUseStdio = options.cliUrl ? false : options.useStdio ?? true;
197
+ const sdkSpawnsCli = !willUseStdio && !options.cliUrl && !options.isChildProcess;
198
+ this.effectiveConnectionToken = options.tcpConnectionToken ?? (sdkSpawnsCli ? (0, import_node_crypto.randomUUID)() : void 0);
172
199
  if (options.sessionFs) {
173
200
  this.validateSessionFsConfig(options.sessionFs);
174
201
  }
@@ -202,6 +229,7 @@ class CopilotClient {
202
229
  // Default useLoggedInUser to false when gitHubToken is provided, otherwise true
203
230
  useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true),
204
231
  telemetry: options.telemetry,
232
+ copilotHome: options.copilotHome,
205
233
  sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0
206
234
  };
207
235
  }
@@ -558,6 +586,7 @@ class CopilotClient {
558
586
  configDir: config.configDir,
559
587
  enableConfigDiscovery: config.enableConfigDiscovery,
560
588
  skillDirectories: config.skillDirectories,
589
+ instructionDirectories: config.instructionDirectories,
561
590
  disabledSkills: config.disabledSkills,
562
591
  infiniteSessions: config.infiniteSessions,
563
592
  gitHubToken: config.gitHubToken
@@ -685,9 +714,11 @@ class CopilotClient {
685
714
  defaultAgent: config.defaultAgent,
686
715
  agent: config.agent,
687
716
  skillDirectories: config.skillDirectories,
717
+ instructionDirectories: config.instructionDirectories,
688
718
  disabledSkills: config.disabledSkills,
689
719
  infiniteSessions: config.infiniteSessions,
690
720
  disableResume: config.disableResume,
721
+ continuePendingWork: config.continuePendingWork,
691
722
  gitHubToken: config.gitHubToken
692
723
  });
693
724
  const { workspacePath, capabilities } = response;
@@ -809,18 +840,29 @@ class CopilotClient {
809
840
  }
810
841
  }
811
842
  /**
812
- * Verify that the server's protocol version is within the supported range
813
- * and store the negotiated version.
843
+ * Send the `connect` handshake (carrying the optional token) and verify the
844
+ * server's protocol version. Falls back to `ping` against legacy servers
845
+ * that don't implement `connect`.
814
846
  */
815
847
  async verifyProtocolVersion() {
848
+ if (!this.connection) {
849
+ throw new Error("Client not connected");
850
+ }
816
851
  const maxVersion = (0, import_sdkProtocolVersion.getSdkProtocolVersion)();
817
- let pingResult;
818
- if (this.processExitPromise) {
819
- pingResult = await Promise.race([this.ping(), this.processExitPromise]);
820
- } else {
821
- pingResult = await this.ping();
852
+ const raceAgainstExit = (p) => this.processExitPromise ? Promise.race([p, this.processExitPromise]) : p;
853
+ let serverVersion;
854
+ try {
855
+ const result = await raceAgainstExit(
856
+ this.internalRpc.connect({ token: this.effectiveConnectionToken })
857
+ );
858
+ serverVersion = result.protocolVersion;
859
+ } catch (err) {
860
+ if (err instanceof import_node.ResponseError && err.code === import_node.ErrorCodes.MethodNotFound) {
861
+ serverVersion = (await raceAgainstExit(this.ping())).protocolVersion;
862
+ } else {
863
+ throw err;
864
+ }
822
865
  }
823
- const serverVersion = pingResult.protocolVersion;
824
866
  if (serverVersion === void 0) {
825
867
  throw new Error(
826
868
  `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`
@@ -1055,6 +1097,12 @@ class CopilotClient {
1055
1097
  if (this.options.gitHubToken) {
1056
1098
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
1057
1099
  }
1100
+ if (this.effectiveConnectionToken) {
1101
+ envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
1102
+ }
1103
+ if (this.options.copilotHome) {
1104
+ envWithoutNodeDebug.COPILOT_HOME = this.options.copilotHome;
1105
+ }
1058
1106
  if (!this.options.cliPath) {
1059
1107
  throw new Error(
1060
1108
  "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var rpc_exports = {};
20
20
  __export(rpc_exports, {
21
+ createInternalServerRpc: () => createInternalServerRpc,
21
22
  createServerRpc: () => createServerRpc,
22
23
  createSessionRpc: () => createSessionRpc,
23
24
  registerClientSessionApiHandlers: () => registerClientSessionApiHandlers
@@ -61,8 +62,14 @@ function createServerRpc(connection) {
61
62
  }
62
63
  };
63
64
  }
65
+ function createInternalServerRpc(connection) {
66
+ return {
67
+ connect: async (params) => connection.sendRequest("connect", params)
68
+ };
69
+ }
64
70
  function createSessionRpc(connection, sessionId) {
65
71
  return {
72
+ suspend: async () => connection.sendRequest("session.suspend", { sessionId }),
66
73
  auth: {
67
74
  getStatus: async () => connection.sendRequest("session.auth.getStatus", { sessionId })
68
75
  },
@@ -105,6 +112,14 @@ function createSessionRpc(connection, sessionId) {
105
112
  reload: async () => connection.sendRequest("session.agent.reload", { sessionId })
106
113
  },
107
114
  /** @experimental */
115
+ tasks: {
116
+ startAgent: async (params) => connection.sendRequest("session.tasks.startAgent", { sessionId, ...params }),
117
+ list: async () => connection.sendRequest("session.tasks.list", { sessionId }),
118
+ promoteToBackground: async (params) => connection.sendRequest("session.tasks.promoteToBackground", { sessionId, ...params }),
119
+ cancel: async (params) => connection.sendRequest("session.tasks.cancel", { sessionId, ...params }),
120
+ remove: async (params) => connection.sendRequest("session.tasks.remove", { sessionId, ...params })
121
+ },
122
+ /** @experimental */
108
123
  skills: {
109
124
  list: async () => connection.sendRequest("session.skills.list", { sessionId }),
110
125
  enable: async (params) => connection.sendRequest("session.skills.enable", { sessionId, ...params }),
@@ -218,6 +233,7 @@ function registerClientSessionApiHandlers(connection, getHandlers) {
218
233
  }
219
234
  // Annotate the CommonJS export names for ESM import in node:
220
235
  0 && (module.exports = {
236
+ createInternalServerRpc,
221
237
  createServerRpc,
222
238
  createSessionRpc,
223
239
  registerClientSessionApiHandlers
package/dist/client.d.ts CHANGED
@@ -47,6 +47,8 @@ export declare class CopilotClient {
47
47
  private options;
48
48
  private isExternalServer;
49
49
  private forceStopping;
50
+ /** Token sent in `connect`; auto-generated when the SDK spawns its own CLI in TCP mode. */
51
+ private effectiveConnectionToken?;
50
52
  private onListModels?;
51
53
  private onGetTraceContext?;
52
54
  private modelsCache;
@@ -54,6 +56,7 @@ export declare class CopilotClient {
54
56
  private sessionLifecycleHandlers;
55
57
  private typedLifecycleHandlers;
56
58
  private _rpc;
59
+ private _internalRpc;
57
60
  private processExitPromise;
58
61
  private negotiatedProtocolVersion;
59
62
  /** Connection-level session filesystem config, set via constructor option. */
@@ -63,6 +66,11 @@ export declare class CopilotClient {
63
66
  * @throws Error if the client is not connected
64
67
  */
65
68
  get rpc(): ReturnType<typeof createServerRpc>;
69
+ /**
70
+ * Internal RPC surface (e.g. handshake helpers). Not part of the public API.
71
+ * @internal
72
+ */
73
+ private get internalRpc();
66
74
  /**
67
75
  * Creates a new CopilotClient instance.
68
76
  *
@@ -267,8 +275,9 @@ export declare class CopilotClient {
267
275
  */
268
276
  listModels(): Promise<ModelInfo[]>;
269
277
  /**
270
- * Verify that the server's protocol version is within the supported range
271
- * and store the negotiated version.
278
+ * Send the `connect` handshake (carrying the optional token) and verify the
279
+ * server's protocol version. Falls back to `ping` against legacy servers
280
+ * that don't implement `connect`.
272
281
  */
273
282
  private verifyProtocolVersion;
274
283
  /**
package/dist/client.js CHANGED
@@ -7,10 +7,16 @@ import { dirname, join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import {
9
9
  createMessageConnection,
10
+ ErrorCodes,
11
+ ResponseError,
10
12
  StreamMessageReader,
11
13
  StreamMessageWriter
12
14
  } from "vscode-jsonrpc/node.js";
13
- import { createServerRpc, registerClientSessionApiHandlers } from "./generated/rpc.js";
15
+ import {
16
+ createServerRpc,
17
+ createInternalServerRpc,
18
+ registerClientSessionApiHandlers
19
+ } from "./generated/rpc.js";
14
20
  import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
15
21
  import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js";
16
22
  import { createSessionFsAdapter } from "./sessionFsProvider.js";
@@ -89,6 +95,8 @@ class CopilotClient {
89
95
  options;
90
96
  isExternalServer = false;
91
97
  forceStopping = false;
98
+ /** Token sent in `connect`; auto-generated when the SDK spawns its own CLI in TCP mode. */
99
+ effectiveConnectionToken;
92
100
  onListModels;
93
101
  onGetTraceContext;
94
102
  modelsCache = null;
@@ -96,6 +104,7 @@ class CopilotClient {
96
104
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
97
105
  typedLifecycleHandlers = /* @__PURE__ */ new Map();
98
106
  _rpc = null;
107
+ _internalRpc = null;
99
108
  processExitPromise = null;
100
109
  // Rejects when CLI process exits
101
110
  negotiatedProtocolVersion = null;
@@ -114,6 +123,19 @@ class CopilotClient {
114
123
  }
115
124
  return this._rpc;
116
125
  }
126
+ /**
127
+ * Internal RPC surface (e.g. handshake helpers). Not part of the public API.
128
+ * @internal
129
+ */
130
+ get internalRpc() {
131
+ if (!this.connection) {
132
+ throw new Error("Client is not connected. Call start() first.");
133
+ }
134
+ if (!this._internalRpc) {
135
+ this._internalRpc = createInternalServerRpc(this.connection);
136
+ }
137
+ return this._internalRpc;
138
+ }
117
139
  /**
118
140
  * Creates a new CopilotClient instance.
119
141
  *
@@ -149,6 +171,17 @@ class CopilotClient {
149
171
  "gitHubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
150
172
  );
151
173
  }
174
+ if (options.tcpConnectionToken !== void 0) {
175
+ if (typeof options.tcpConnectionToken !== "string" || options.tcpConnectionToken.length === 0) {
176
+ throw new Error("tcpConnectionToken must be a non-empty string");
177
+ }
178
+ if (options.useStdio === true) {
179
+ throw new Error("tcpConnectionToken cannot be used with useStdio: true");
180
+ }
181
+ }
182
+ const willUseStdio = options.cliUrl ? false : options.useStdio ?? true;
183
+ const sdkSpawnsCli = !willUseStdio && !options.cliUrl && !options.isChildProcess;
184
+ this.effectiveConnectionToken = options.tcpConnectionToken ?? (sdkSpawnsCli ? randomUUID() : void 0);
152
185
  if (options.sessionFs) {
153
186
  this.validateSessionFsConfig(options.sessionFs);
154
187
  }
@@ -182,6 +215,7 @@ class CopilotClient {
182
215
  // Default useLoggedInUser to false when gitHubToken is provided, otherwise true
183
216
  useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true),
184
217
  telemetry: options.telemetry,
218
+ copilotHome: options.copilotHome,
185
219
  sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0
186
220
  };
187
221
  }
@@ -538,6 +572,7 @@ class CopilotClient {
538
572
  configDir: config.configDir,
539
573
  enableConfigDiscovery: config.enableConfigDiscovery,
540
574
  skillDirectories: config.skillDirectories,
575
+ instructionDirectories: config.instructionDirectories,
541
576
  disabledSkills: config.disabledSkills,
542
577
  infiniteSessions: config.infiniteSessions,
543
578
  gitHubToken: config.gitHubToken
@@ -665,9 +700,11 @@ class CopilotClient {
665
700
  defaultAgent: config.defaultAgent,
666
701
  agent: config.agent,
667
702
  skillDirectories: config.skillDirectories,
703
+ instructionDirectories: config.instructionDirectories,
668
704
  disabledSkills: config.disabledSkills,
669
705
  infiniteSessions: config.infiniteSessions,
670
706
  disableResume: config.disableResume,
707
+ continuePendingWork: config.continuePendingWork,
671
708
  gitHubToken: config.gitHubToken
672
709
  });
673
710
  const { workspacePath, capabilities } = response;
@@ -789,18 +826,29 @@ class CopilotClient {
789
826
  }
790
827
  }
791
828
  /**
792
- * Verify that the server's protocol version is within the supported range
793
- * and store the negotiated version.
829
+ * Send the `connect` handshake (carrying the optional token) and verify the
830
+ * server's protocol version. Falls back to `ping` against legacy servers
831
+ * that don't implement `connect`.
794
832
  */
795
833
  async verifyProtocolVersion() {
834
+ if (!this.connection) {
835
+ throw new Error("Client not connected");
836
+ }
796
837
  const maxVersion = getSdkProtocolVersion();
797
- let pingResult;
798
- if (this.processExitPromise) {
799
- pingResult = await Promise.race([this.ping(), this.processExitPromise]);
800
- } else {
801
- pingResult = await this.ping();
838
+ const raceAgainstExit = (p) => this.processExitPromise ? Promise.race([p, this.processExitPromise]) : p;
839
+ let serverVersion;
840
+ try {
841
+ const result = await raceAgainstExit(
842
+ this.internalRpc.connect({ token: this.effectiveConnectionToken })
843
+ );
844
+ serverVersion = result.protocolVersion;
845
+ } catch (err) {
846
+ if (err instanceof ResponseError && err.code === ErrorCodes.MethodNotFound) {
847
+ serverVersion = (await raceAgainstExit(this.ping())).protocolVersion;
848
+ } else {
849
+ throw err;
850
+ }
802
851
  }
803
- const serverVersion = pingResult.protocolVersion;
804
852
  if (serverVersion === void 0) {
805
853
  throw new Error(
806
854
  `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`
@@ -1035,6 +1083,12 @@ class CopilotClient {
1035
1083
  if (this.options.gitHubToken) {
1036
1084
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
1037
1085
  }
1086
+ if (this.effectiveConnectionToken) {
1087
+ envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
1088
+ }
1089
+ if (this.options.copilotHome) {
1090
+ envWithoutNodeDebug.COPILOT_HOME = this.options.copilotHome;
1091
+ }
1038
1092
  if (!this.options.cliPath) {
1039
1093
  throw new Error(
1040
1094
  "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."