@github/copilot-sdk 1.0.29-preview.1 → 1.0.29-preview.10

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.
@@ -22,7 +22,7 @@ __export(cliVersion_exports, {
22
22
  COPILOT_CLI_VERSION: () => COPILOT_CLI_VERSION
23
23
  });
24
24
  module.exports = __toCommonJS(cliVersion_exports);
25
- const COPILOT_CLI_VERSION = "1.0.106-2";
25
+ const COPILOT_CLI_VERSION = "1.0.106-11";
26
26
  const COPILOT_CLI_USE_NPM_PACKAGE = false;
27
27
  // Annotate the CommonJS export names for ESM import in node:
28
28
  0 && (module.exports = {
@@ -54,6 +54,19 @@ var import_types = require("./types.js");
54
54
  const MIN_PROTOCOL_VERSION = 3;
55
55
  const RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
56
56
  const CLOUD_SESSION_CLEANUP_TIMEOUT_MS = 1e4;
57
+ const messageEncodeErrors = /* @__PURE__ */ new WeakSet();
58
+ const jsonMessageEncoder = {
59
+ name: "application/json",
60
+ encode: (msg, options) => {
61
+ try {
62
+ return Promise.resolve(Buffer.from(JSON.stringify(msg), options.charset));
63
+ } catch (thrown) {
64
+ const error = thrown instanceof Error ? thrown : new Error(String(thrown));
65
+ messageEncodeErrors.add(error);
66
+ return Promise.reject(error);
67
+ }
68
+ }
69
+ };
57
70
  function createMessageConnection(reader, writer) {
58
71
  let dispatch;
59
72
  let finishDrain;
@@ -288,8 +301,15 @@ function getNodeExecPath() {
288
301
  function getBundledRuntimePath() {
289
302
  return (0, import_runtimeArtifacts.ensureRuntimeBundle)(import_cliVersion.COPILOT_CLI_VERSION);
290
303
  }
304
+ async function getSpawnableBundledRuntimePath() {
305
+ const runtimePath = await getBundledRuntimePath();
306
+ return process.platform === "win32" ? import_node_path.win32.toNamespacedPath(runtimePath) : runtimePath;
307
+ }
291
308
  class TeardownResilientStreamMessageWriter extends import_node.StreamMessageWriter {
292
309
  suppressWriteErrors = false;
310
+ constructor(writable) {
311
+ super(writable, { contentTypeEncoder: jsonMessageEncoder });
312
+ }
293
313
  async write(msg) {
294
314
  try {
295
315
  await super.write(msg);
@@ -336,8 +356,10 @@ class CopilotClient {
336
356
  effectiveConnectionToken;
337
357
  onListModels;
338
358
  onGetTraceContext;
339
- modelsCache = null;
340
- modelsCacheLock = Promise.resolve();
359
+ modelsCache = {
360
+ models: null,
361
+ lock: Promise.resolve()
362
+ };
341
363
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
342
364
  typedLifecycleHandlers = /* @__PURE__ */ new Map();
343
365
  _rpc = null;
@@ -448,19 +470,22 @@ class CopilotClient {
448
470
  */
449
471
  constructor(options = {}) {
450
472
  const conn = options._internalConnection ?? options.connection ?? CopilotClient.resolveDefaultConnection();
473
+ if ((conn.kind === "uri" || conn.kind === "parent-process") && options.internalTestOptions !== void 0) {
474
+ throw new Error("internalTestOptions requires a runtime owned by this client");
475
+ }
451
476
  if (conn.kind === "uri" && (options.gitHubToken !== void 0 || options.useLoggedInUser !== void 0)) {
452
477
  throw new Error(
453
478
  "gitHubToken and useLoggedInUser cannot be used with RuntimeConnection.forUri (external server manages its own auth)"
454
479
  );
455
480
  }
456
- if (conn.kind === "inprocess" && options.workingDirectory !== void 0) {
481
+ if (conn.kind === "inprocess" && options.env !== void 0) {
457
482
  throw new Error(
458
- "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."
483
+ "env is not supported with RuntimeConnection.forInProcess(): environment variables belong to the shared host process. Use typed client/session options or a child-process transport."
459
484
  );
460
485
  }
461
- if (conn.kind === "inprocess" && options.env !== void 0) {
486
+ if (conn.kind === "inprocess" && options.workingDirectory !== void 0) {
462
487
  throw new Error(
463
- "env is not supported with RuntimeConnection.forInProcess(): the in-process transport loads the native runtime into the shared host process, whose single environment block cannot carry per-client values. Set the variables on the host process environment instead."
488
+ "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. Set SessionConfig.workingDirectory instead."
464
489
  );
465
490
  }
466
491
  if (conn.kind === "inprocess" && options.telemetry !== void 0) {
@@ -526,6 +551,7 @@ class CopilotClient {
526
551
  const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
527
552
  this.connectionExtraArgs = [...connArgs];
528
553
  this.options = {
554
+ internalTestOptions: options.internalTestOptions,
529
555
  entra: options.entra ? { ...options.entra, tokenProvider: options.entra.tokenProvider } : void 0,
530
556
  workingDirectory: options.workingDirectory ?? process.cwd(),
531
557
  logLevel: options.logLevel,
@@ -797,12 +823,17 @@ class CopilotClient {
797
823
  * Stops the CLI server and closes all active sessions.
798
824
  *
799
825
  * This method performs graceful cleanup:
800
- * 1. Closes all active sessions (releases in-memory resources)
826
+ * 1. Closes all active sessions (releases in-memory resources); detach
827
+ * requests share one shutdown timeout
801
828
  * 2. Requests runtime shutdown for SDK-owned CLI processes
802
829
  * 3. Closes the JSON-RPC connection
803
830
  * 4. Signals EOF to an owned stdio process and waits for host cleanup, then
804
831
  * terminates the process if it does not exit within the shutdown timeout
805
832
  *
833
+ * In-process connections first abort active turns without a timeout. That
834
+ * step is outside the detach deadline, so in-process stop() can still wait
835
+ * indefinitely if the runtime stops answering abort requests.
836
+ *
806
837
  * Note: session data on disk is preserved, so sessions can be resumed later.
807
838
  * To permanently remove session data before stopping, call
808
839
  * {@link deleteSession} for each session first.
@@ -824,16 +855,31 @@ class CopilotClient {
824
855
  if (this.connectionConfig.kind === "inprocess") {
825
856
  await Promise.allSettled(activeSessions.map((session) => session.abort()));
826
857
  }
858
+ let detachDeadlineTimer;
859
+ const detachDeadline = new Promise((resolve2) => {
860
+ detachDeadlineTimer = setTimeout(() => resolve2("timeout"), RUNTIME_SHUTDOWN_TIMEOUT_MS);
861
+ });
827
862
  for (const session of activeSessions) {
828
863
  const sessionId = session.sessionId;
829
864
  let lastError = null;
865
+ let attempts = 0;
830
866
  for (let attempt = 1; attempt <= 3; attempt++) {
831
867
  if (this.connectionClosed) {
832
868
  lastError = null;
833
869
  break;
834
870
  }
871
+ attempts = attempt;
835
872
  try {
836
- await session.disconnect();
873
+ const outcome = await Promise.race([
874
+ session.disconnect().then(() => "disconnected"),
875
+ detachDeadline
876
+ ]);
877
+ if (outcome === "timeout") {
878
+ lastError = new Error(
879
+ `session.detach did not complete within the ${RUNTIME_SHUTDOWN_TIMEOUT_MS}ms shutdown timeout`
880
+ );
881
+ break;
882
+ }
837
883
  lastError = null;
838
884
  break;
839
885
  } catch (error) {
@@ -851,11 +897,12 @@ class CopilotClient {
851
897
  if (lastError) {
852
898
  errors.push(
853
899
  new Error(
854
- `Failed to disconnect session ${sessionId} after 3 attempts: ${lastError.message}`
900
+ `Failed to disconnect session ${sessionId} after ${attempts} ${attempts === 1 ? "attempt" : "attempts"}: ${lastError.message}`
855
901
  )
856
902
  );
857
903
  }
858
904
  }
905
+ clearTimeout(detachDeadlineTimer);
859
906
  for (const session of activeSessions) {
860
907
  session._markDisconnected();
861
908
  }
@@ -906,7 +953,7 @@ class CopilotClient {
906
953
  this._rpc = null;
907
954
  this._internalRpc = null;
908
955
  }
909
- this.modelsCache = null;
956
+ this.modelsCache = { models: null, lock: Promise.resolve() };
910
957
  if (this.socket) {
911
958
  const socket = this.socket;
912
959
  this.socket = null;
@@ -1068,7 +1115,7 @@ class CopilotClient {
1068
1115
  this._rpc = null;
1069
1116
  this._internalRpc = null;
1070
1117
  }
1071
- this.modelsCache = null;
1118
+ this.modelsCache = { models: null, lock: Promise.resolve() };
1072
1119
  if (this.socket) {
1073
1120
  try {
1074
1121
  this.socket.destroy();
@@ -1125,7 +1172,7 @@ class CopilotClient {
1125
1172
  * // Session with model and tools
1126
1173
  * const session = await client.createSession({
1127
1174
  * onPermissionRequest: approveAll,
1128
- * model: "gpt-4",
1175
+ * model: "gpt-5.4",
1129
1176
  * tools: [{
1130
1177
  * name: "get_weather",
1131
1178
  * description: "Get weather for a location",
@@ -2087,14 +2134,16 @@ class CopilotClient {
2087
2134
  * @throws Error if not connected (when no custom handler is set)
2088
2135
  */
2089
2136
  async listModels() {
2090
- await this.modelsCacheLock;
2137
+ const cache = this.modelsCache;
2138
+ const previousLock = cache.lock;
2091
2139
  let resolveLock;
2092
- this.modelsCacheLock = new Promise((resolve2) => {
2140
+ cache.lock = new Promise((resolve2) => {
2093
2141
  resolveLock = resolve2;
2094
2142
  });
2143
+ await previousLock;
2095
2144
  try {
2096
- if (this.modelsCache !== null) {
2097
- return [...this.modelsCache];
2145
+ if (cache.models !== null) {
2146
+ return [...cache.models];
2098
2147
  }
2099
2148
  let models;
2100
2149
  if (this.onListModels) {
@@ -2123,7 +2172,7 @@ class CopilotClient {
2123
2172
  }
2124
2173
  }
2125
2174
  }
2126
- this.modelsCache = [...models];
2175
+ cache.models = [...models];
2127
2176
  return [...models];
2128
2177
  } finally {
2129
2178
  resolveLock();
@@ -2311,7 +2360,9 @@ class CopilotClient {
2311
2360
  gitRoot: context.gitRoot,
2312
2361
  repository: context.repository,
2313
2362
  branch: context.branch
2314
- } : void 0
2363
+ } : void 0,
2364
+ clientName: raw.clientName,
2365
+ lastClientName: raw.lastClientName
2315
2366
  };
2316
2367
  }
2317
2368
  /**
@@ -2386,15 +2437,40 @@ class CopilotClient {
2386
2437
  };
2387
2438
  }
2388
2439
  /**
2389
- * Builds the environment for the spawned runtime child process (stdio/TCP): applies
2390
- * the auth token, connection token, `COPILOT_HOME`, keychain setting, and telemetry
2391
- * variables on top of the effective env. Not used by the in-process (FFI) transport,
2392
- * whose worker inherits the host process's ambient environment
2393
- * (see {@link CopilotClient.startInProcessFfi}).
2440
+ * Builds runtime configuration for child processes and the per-host FFI snapshot.
2441
+ * Typed auth, home, keychain, and telemetry options override the effective env.
2394
2442
  */
2395
2443
  buildRuntimeEnv() {
2396
- const env = { ...this.resolvedEnv };
2444
+ const env = process.platform === "win32" ? Object.fromEntries(
2445
+ Object.entries(this.resolvedEnv).map(([key, value]) => [
2446
+ key.toUpperCase(),
2447
+ value
2448
+ ])
2449
+ ) : { ...this.resolvedEnv };
2397
2450
  delete env.NODE_DEBUG;
2451
+ const test = this.options.internalTestOptions;
2452
+ if (test) {
2453
+ const overrides = {
2454
+ COPILOT_API_URL: test.copilotApiUrl,
2455
+ COPILOT_DEBUG_GITHUB_API_URL: test.githubApiUrl,
2456
+ HTTP_PROXY: test.proxyUrl,
2457
+ HTTPS_PROXY: test.proxyUrl,
2458
+ http_proxy: test.proxyUrl,
2459
+ https_proxy: test.proxyUrl,
2460
+ NO_PROXY: test.noProxy,
2461
+ no_proxy: test.noProxy,
2462
+ NODE_EXTRA_CA_CERTS: test.caCertificatePath,
2463
+ SSL_CERT_FILE: test.caCertificatePath,
2464
+ REQUESTS_CA_BUNDLE: test.caCertificatePath,
2465
+ CURL_CA_BUNDLE: test.caCertificatePath,
2466
+ GIT_SSL_CAINFO: test.caCertificatePath
2467
+ };
2468
+ for (const [key, value] of Object.entries(overrides)) {
2469
+ if (value !== void 0) {
2470
+ env[process.platform === "win32" ? key.toUpperCase() : key] = value;
2471
+ }
2472
+ }
2473
+ }
2398
2474
  if (this.options.gitHubToken) {
2399
2475
  env.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
2400
2476
  }
@@ -2403,6 +2479,9 @@ class CopilotClient {
2403
2479
  }
2404
2480
  if (this.options.baseDirectory) {
2405
2481
  env.COPILOT_HOME = this.options.baseDirectory;
2482
+ if (this.connectionConfig.kind === "inprocess") {
2483
+ env.COPILOT_CACHE_HOME = (0, import_node_path.join)(this.options.baseDirectory, "cache");
2484
+ }
2406
2485
  }
2407
2486
  if (this.options.mode === "empty") {
2408
2487
  env.COPILOT_DISABLE_KEYTAR = "1";
@@ -2429,7 +2508,7 @@ class CopilotClient {
2429
2508
  * Start the CLI server process
2430
2509
  */
2431
2510
  async startCLIServer() {
2432
- this.resolvedCliPath ??= await getBundledRuntimePath();
2511
+ this.resolvedCliPath ??= await getSpawnableBundledRuntimePath();
2433
2512
  return new Promise((resolve2, reject) => {
2434
2513
  this.stderrBuffer = "";
2435
2514
  const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
@@ -2467,6 +2546,22 @@ class CopilotClient {
2467
2546
  `Copilot CLI not found at ${this.resolvedCliPath}. Set COPILOT_CLI_PATH to use a custom installation.`
2468
2547
  );
2469
2548
  }
2549
+ const workingDirectory = this.options.workingDirectory;
2550
+ let workingDirectoryProblem;
2551
+ try {
2552
+ workingDirectoryProblem = (0, import_node_fs.statSync)(workingDirectory).isDirectory() ? void 0 : "does not exist or is not a directory";
2553
+ if (!workingDirectoryProblem) {
2554
+ (0, import_node_fs.accessSync)(workingDirectory, import_node_fs.constants.X_OK);
2555
+ }
2556
+ } catch (error) {
2557
+ const code = error.code;
2558
+ workingDirectoryProblem = code === "ENOENT" || code === "ENOTDIR" ? "does not exist or is not a directory" : `cannot be entered: ${error instanceof Error ? error.message : String(error)}`;
2559
+ }
2560
+ if (workingDirectoryProblem) {
2561
+ throw new Error(
2562
+ `Working directory ${workingDirectory} ${workingDirectoryProblem}.`
2563
+ );
2564
+ }
2470
2565
  const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
2471
2566
  const isJsFile = this.resolvedCliPath.endsWith(".js");
2472
2567
  if (isJsFile) {
@@ -2618,16 +2713,7 @@ stderr: ${this.stderrBuffer.trim().slice(-8192) || "(empty)"}`
2618
2713
  runtimeLibrary = (0, import_node_path.join)((0, import_node_path.dirname)(bundledRuntimePath), "runtime.node");
2619
2714
  }
2620
2715
  const { FfiRuntimeHost } = await import("./ffiRuntimeHost.js");
2621
- const environment = {};
2622
- if (this.options.gitHubToken) {
2623
- environment.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
2624
- }
2625
- if (this.options.baseDirectory) {
2626
- environment.COPILOT_HOME = this.options.baseDirectory;
2627
- }
2628
- if (this.options.mode === "empty") {
2629
- environment.COPILOT_DISABLE_KEYTAR = "1";
2630
- }
2716
+ const environment = this.buildRuntimeEnv();
2631
2717
  const args = [];
2632
2718
  if (this.options.logLevel) {
2633
2719
  args.push("--log-level", this.options.logLevel);
@@ -2888,6 +2974,14 @@ stderr: ${stderrOutput}` : ""}`
2888
2974
  "skillProvider.list",
2889
2975
  async (params, token) => await this.resolveSkillProviderSession(params)._handleSkillProviderList(token)
2890
2976
  );
2977
+ this.connection.onRequest(
2978
+ "sessionManagement.create",
2979
+ async (params, token) => {
2980
+ const session = this.sessions.get(params.sessionId);
2981
+ if (!session) throw new Error("Session creation owner is unavailable");
2982
+ return await session._handleSessionManagementCreate(params, token);
2983
+ }
2984
+ );
2891
2985
  this.connection.onRequest(
2892
2986
  "skillProvider.read",
2893
2987
  async (params, token) => {
@@ -2925,7 +3019,7 @@ stderr: ${stderrOutput}` : ""}`
2925
3019
  this.messageWriter = null;
2926
3020
  this._rpc = null;
2927
3021
  this._internalRpc = null;
2928
- this.modelsCache = null;
3022
+ this.modelsCache = { models: null, lock: Promise.resolve() };
2929
3023
  };
2930
3024
  const disconnectAfterDrain = () => {
2931
3025
  if (exitFallback !== void 0) {
@@ -2960,7 +3054,10 @@ stderr: ${stderrOutput}` : ""}`
2960
3054
  exitFallback.unref?.();
2961
3055
  });
2962
3056
  }
2963
- this.connection.onError(() => {
3057
+ this.connection.onError(([error]) => {
3058
+ if (messageEncodeErrors.has(error)) {
3059
+ return;
3060
+ }
2964
3061
  if (connectionEpoch === this.connectionEpoch && this.connection === connection) {
2965
3062
  this.state = "disconnected";
2966
3063
  }
@@ -3021,7 +3118,8 @@ stderr: ${stderrOutput}` : ""}`
3021
3118
  const result = await session._handleUserInputRequest({
3022
3119
  question: params.question,
3023
3120
  choices: params.choices,
3024
- allowFreeform: params.allowFreeform
3121
+ allowFreeform: params.allowFreeform,
3122
+ requestId: params.requestId
3025
3123
  });
3026
3124
  return result;
3027
3125
  }
@@ -3037,7 +3135,8 @@ stderr: ${stderrOutput}` : ""}`
3037
3135
  summary: params.summary,
3038
3136
  planContent: params.planContent,
3039
3137
  actions: params.actions,
3040
- recommendedAction: params.recommendedAction
3138
+ recommendedAction: params.recommendedAction,
3139
+ requestId: params.requestId
3041
3140
  });
3042
3141
  }
3043
3142
  async handleAutoModeSwitchRequest(params) {
@@ -109,7 +109,9 @@ class CopilotWebSocketForwarder extends CopilotWebSocketHandler {
109
109
  if (this.#upstream) {
110
110
  return;
111
111
  }
112
- const upstream = new WebSocket(this.context.url);
112
+ const upstream = new WebSocket(this.context.url, {
113
+ headers: forwardedRequestHeaders(this.context.headers)
114
+ });
113
115
  upstream.binaryType = "arraybuffer";
114
116
  this.#upstream = upstream;
115
117
  upstream.addEventListener("message", (event) => {
@@ -149,13 +151,16 @@ class CopilotWebSocketForwarder extends CopilotWebSocketHandler {
149
151
  });
150
152
  }
151
153
  async close(status = CopilotWebSocketCloseStatus.normalClosure) {
154
+ const upstream = this.#upstream;
155
+ this.#upstream = null;
156
+ const completion = super.close(status);
152
157
  try {
153
- if (this.#upstream?.readyState === WebSocket.OPEN || this.#upstream?.readyState === WebSocket.CONNECTING) {
154
- this.#upstream?.close();
158
+ if (upstream?.readyState === WebSocket.OPEN || upstream?.readyState === WebSocket.CONNECTING) {
159
+ upstream.close();
155
160
  }
156
161
  } catch {
157
162
  }
158
- await super.close(status);
163
+ await completion;
159
164
  }
160
165
  async [Symbol.asyncDispose]() {
161
166
  try {
@@ -494,19 +499,24 @@ const FORBIDDEN_REQUEST_HEADERS = /* @__PURE__ */ new Set([
494
499
  "te",
495
500
  "trailer"
496
501
  ]);
497
- async function buildFetchRequest(exchange) {
502
+ function forwardedRequestHeaders(source) {
498
503
  const headers = new Headers();
499
- for (const [name, values] of Object.entries(exchange.headers)) {
504
+ for (const [name, values] of Object.entries(source)) {
500
505
  if (!values) {
501
506
  continue;
502
507
  }
503
- if (FORBIDDEN_REQUEST_HEADERS.has(name.toLowerCase())) {
508
+ const lower = name.toLowerCase();
509
+ if (FORBIDDEN_REQUEST_HEADERS.has(lower) || lower.startsWith("sec-websocket-")) {
504
510
  continue;
505
511
  }
506
512
  for (const value of values) {
507
513
  headers.append(name, value);
508
514
  }
509
515
  }
516
+ return headers;
517
+ }
518
+ async function buildFetchRequest(exchange) {
519
+ const headers = forwardedRequestHeaders(exchange.headers);
510
520
  const method = exchange.method.toUpperCase();
511
521
  const hasBody = method !== "GET" && method !== "HEAD";
512
522
  let body;