@github/copilot-sdk 1.0.0-beta.4 → 1.0.0-beta.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.
@@ -36,11 +36,6 @@ var import_sessionFsProvider = require("./sessionFsProvider.js");
36
36
  var import_telemetry = require("./telemetry.js");
37
37
  var import_types = require("./types.js");
38
38
  const import_meta = {};
39
- function toWireProviderConfig(provider) {
40
- const { maxInputTokens, ...rest } = provider;
41
- if (maxInputTokens === void 0) return rest;
42
- return { ...rest, maxPromptTokens: maxInputTokens };
43
- }
44
39
  const MIN_PROTOCOL_VERSION = 2;
45
40
  function isZodSchema(value) {
46
41
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
@@ -52,6 +47,26 @@ function toJsonSchema(parameters) {
52
47
  }
53
48
  return parameters;
54
49
  }
50
+ function toWireMcpServers(mcpServers) {
51
+ if (!mcpServers) return void 0;
52
+ return Object.fromEntries(
53
+ Object.entries(mcpServers).map(([name, server]) => {
54
+ if ("workingDirectory" in server) {
55
+ const { workingDirectory, ...rest } = server;
56
+ return [name, { ...rest, cwd: workingDirectory }];
57
+ }
58
+ return [name, server];
59
+ })
60
+ );
61
+ }
62
+ function toWireCustomAgents(agents) {
63
+ if (!agents) return void 0;
64
+ return agents.map((agent) => {
65
+ if (!agent.mcpServers) return agent;
66
+ const { mcpServers, ...rest } = agent;
67
+ return { ...rest, mcpServers: toWireMcpServers(mcpServers) };
68
+ });
69
+ }
55
70
  function extractTransformCallbacks(systemMessage) {
56
71
  if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
57
72
  return { wirePayload: systemMessage, transformCallbacks: void 0 };
@@ -105,12 +120,18 @@ class CopilotClient {
105
120
  cliProcess = null;
106
121
  connection = null;
107
122
  socket = null;
108
- actualPort = null;
123
+ runtimePort = null;
109
124
  actualHost = "localhost";
110
125
  state = "disconnected";
111
126
  sessions = /* @__PURE__ */ new Map();
112
127
  stderrBuffer = "";
113
128
  // Captures CLI stderr for error messages
129
+ /** Resolved connection mode chosen in the constructor. */
130
+ connectionConfig;
131
+ /** Resolved path to the runtime executable (only used for child-process kinds). */
132
+ resolvedCliPath;
133
+ /** Resolved environment passed to the spawned runtime. */
134
+ resolvedEnv;
114
135
  options;
115
136
  isExternalServer = false;
116
137
  forceStopping = false;
@@ -159,86 +180,79 @@ class CopilotClient {
159
180
  * Creates a new CopilotClient instance.
160
181
  *
161
182
  * @param options - Configuration options for the client
162
- * @throws Error if mutually exclusive options are provided (e.g., cliUrl with useStdio or cliPath)
163
183
  *
164
184
  * @example
165
185
  * ```typescript
166
- * // Default options - spawns CLI server using stdio
186
+ * // Default: spawns the bundled runtime over stdio
167
187
  * const client = new CopilotClient();
168
188
  *
169
- * // Connect to an existing server
170
- * const client = new CopilotClient({ cliUrl: "localhost:3000" });
189
+ * // Connect to an existing runtime
190
+ * const client = new CopilotClient({
191
+ * connection: RuntimeConnection.forUri("localhost:3000"),
192
+ * });
193
+ *
194
+ * // Spawn the runtime over TCP on a chosen port
195
+ * const client = new CopilotClient({
196
+ * connection: RuntimeConnection.forTcp({ port: 9001 }),
197
+ * });
171
198
  *
172
- * // Custom CLI path with specific log level
199
+ * // Use a custom runtime binary
173
200
  * const client = new CopilotClient({
174
- * cliPath: "/usr/local/bin/copilot",
175
- * logLevel: "debug"
201
+ * connection: RuntimeConnection.forStdio({ path: "/usr/local/bin/copilot" }),
202
+ * logLevel: "debug",
176
203
  * });
177
204
  * ```
178
205
  */
179
206
  constructor(options = {}) {
180
- if (options.cliUrl && (options.useStdio === true || options.cliPath)) {
181
- throw new Error("cliUrl is mutually exclusive with useStdio and cliPath");
182
- }
183
- if (options.isChildProcess && (options.cliUrl || options.useStdio === false)) {
207
+ const conn = options._internalConnection ?? options.connection ?? { kind: "stdio" };
208
+ if (conn.kind === "uri" && (options.gitHubToken !== void 0 || options.useLoggedInUser !== void 0)) {
184
209
  throw new Error(
185
- "isChildProcess must be used in conjunction with useStdio and not with cliUrl"
210
+ "gitHubToken and useLoggedInUser cannot be used with RuntimeConnection.forUri (external server manages its own auth)"
186
211
  );
187
212
  }
188
- if (options.cliUrl && (options.gitHubToken || options.useLoggedInUser !== void 0)) {
189
- throw new Error(
190
- "gitHubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
191
- );
192
- }
193
- if (options.tcpConnectionToken !== void 0) {
194
- if (typeof options.tcpConnectionToken !== "string" || options.tcpConnectionToken.length === 0) {
195
- throw new Error("tcpConnectionToken must be a non-empty string");
196
- }
197
- if (options.useStdio === true) {
198
- throw new Error("tcpConnectionToken cannot be used with useStdio: true");
213
+ if (conn.kind === "tcp" && conn.connectionToken !== void 0) {
214
+ if (typeof conn.connectionToken !== "string" || conn.connectionToken.length === 0) {
215
+ throw new Error("connectionToken must be a non-empty string");
199
216
  }
200
217
  }
201
- const willUseStdio = options.cliUrl ? false : options.useStdio ?? true;
202
- const sdkSpawnsCli = !willUseStdio && !options.cliUrl && !options.isChildProcess;
203
- this.effectiveConnectionToken = options.tcpConnectionToken ?? (sdkSpawnsCli ? (0, import_node_crypto.randomUUID)() : void 0);
218
+ this.connectionConfig = conn;
204
219
  if (options.sessionFs) {
205
220
  this.validateSessionFsConfig(options.sessionFs);
206
221
  }
207
- if (options.cliUrl) {
208
- const { host, port } = this.parseCliUrl(options.cliUrl);
222
+ if (conn.kind === "uri") {
223
+ const { host, port } = this.parseCliUrl(conn.url);
209
224
  this.actualHost = host;
210
- this.actualPort = port;
225
+ this.runtimePort = port;
211
226
  this.isExternalServer = true;
212
- }
213
- if (options.isChildProcess) {
227
+ } else if (conn.kind === "parent-process") {
214
228
  this.isExternalServer = true;
215
229
  }
230
+ if (conn.kind === "tcp") {
231
+ this.effectiveConnectionToken = conn.connectionToken ?? (0, import_node_crypto.randomUUID)();
232
+ } else if (conn.kind === "uri") {
233
+ this.effectiveConnectionToken = conn.connectionToken;
234
+ }
216
235
  this.onListModels = options.onListModels;
217
236
  this.onGetTraceContext = options.onGetTraceContext;
218
237
  this.sessionFsConfig = options.sessionFs ?? null;
219
238
  const effectiveEnv = options.env ?? process.env;
239
+ this.resolvedEnv = effectiveEnv;
240
+ this.resolvedCliPath = conn.kind === "stdio" || conn.kind === "tcp" ? conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath() : void 0;
241
+ const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
242
+ this.connectionExtraArgs = [...connArgs];
220
243
  this.options = {
221
- cliPath: options.cliUrl ? void 0 : options.cliPath || effectiveEnv.COPILOT_CLI_PATH || getBundledCliPath(),
222
- cliArgs: options.cliArgs ?? [],
223
- cwd: options.cwd ?? process.cwd(),
224
- port: options.port || 0,
225
- useStdio: options.cliUrl ? false : options.useStdio ?? true,
226
- // Default to stdio unless cliUrl is provided
227
- isChildProcess: options.isChildProcess ?? false,
228
- cliUrl: options.cliUrl,
229
- logLevel: options.logLevel || "debug",
230
- autoStart: options.autoStart ?? true,
231
- autoRestart: false,
232
- env: effectiveEnv,
244
+ workingDirectory: options.workingDirectory ?? process.cwd(),
245
+ logLevel: options.logLevel,
233
246
  gitHubToken: options.gitHubToken,
234
- // Default useLoggedInUser to false when gitHubToken is provided, otherwise true
247
+ // Default useLoggedInUser to false when gitHubToken is provided, otherwise true.
235
248
  useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true),
236
249
  telemetry: options.telemetry,
237
- copilotHome: options.copilotHome,
250
+ baseDirectory: options.baseDirectory,
238
251
  sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
239
- remote: options.remote ?? false
252
+ enableRemoteSessions: options.enableRemoteSessions ?? false
240
253
  };
241
254
  }
255
+ connectionExtraArgs = [];
242
256
  /**
243
257
  * Parse CLI URL into host and port
244
258
  * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
@@ -272,20 +286,37 @@ class CopilotClient {
272
286
  throw new Error("sessionFs.conventions must be either 'windows' or 'posix'");
273
287
  }
274
288
  }
289
+ setupSessionFs(session, config) {
290
+ if (!this.sessionFsConfig) {
291
+ return;
292
+ }
293
+ if (!config.createSessionFsProvider) {
294
+ throw new Error(
295
+ "createSessionFsProvider is required in session config when sessionFs is enabled in client options."
296
+ );
297
+ }
298
+ const provider = config.createSessionFsProvider(session);
299
+ if (this.sessionFsConfig.capabilities?.sqlite && !provider.sqlite) {
300
+ throw new Error(
301
+ "SessionFsConfig declares capabilities.sqlite but the provider does not implement sqlite."
302
+ );
303
+ }
304
+ session.clientSessionApis.sessionFs = (0, import_sessionFsProvider.createSessionFsAdapter)(provider);
305
+ }
275
306
  /**
276
307
  * Starts the CLI server and establishes a connection.
277
308
  *
278
309
  * If connecting to an external server (via cliUrl), only establishes the connection.
279
310
  * Otherwise, spawns the CLI server process and then connects.
280
311
  *
281
- * This method is called automatically when creating a session if `autoStart` is true (default).
312
+ * This method is called automatically the first time you create or resume a session.
282
313
  *
283
314
  * @returns A promise that resolves when the connection is established
284
315
  * @throws Error if the server fails to start or the connection fails
285
316
  *
286
317
  * @example
287
318
  * ```typescript
288
- * const client = new CopilotClient({ autoStart: false });
319
+ * const client = new CopilotClient();
289
320
  * await client.start();
290
321
  * // Now ready to create sessions
291
322
  * ```
@@ -305,7 +336,8 @@ class CopilotClient {
305
336
  await this.connection.sendRequest("sessionFs.setProvider", {
306
337
  initialCwd: this.sessionFsConfig.initialCwd,
307
338
  sessionStatePath: this.sessionFsConfig.sessionStatePath,
308
- conventions: this.sessionFsConfig.conventions
339
+ conventions: this.sessionFsConfig.conventions,
340
+ capabilities: this.sessionFsConfig.capabilities
309
341
  });
310
342
  }
311
343
  this.state = "connected";
@@ -379,8 +411,15 @@ class CopilotClient {
379
411
  }
380
412
  this.modelsCache = null;
381
413
  if (this.socket) {
414
+ const socket = this.socket;
415
+ this.socket = null;
382
416
  try {
383
- this.socket.end();
417
+ if (!socket.destroyed) {
418
+ await new Promise((resolve) => {
419
+ socket.once("close", () => resolve());
420
+ socket.end();
421
+ });
422
+ }
384
423
  } catch (error) {
385
424
  errors.push(
386
425
  new Error(
@@ -388,11 +427,18 @@ class CopilotClient {
388
427
  )
389
428
  );
390
429
  }
391
- this.socket = null;
392
430
  }
393
431
  if (this.cliProcess && !this.isExternalServer) {
432
+ const child = this.cliProcess;
433
+ this.cliProcess = null;
394
434
  try {
395
- this.cliProcess.kill();
435
+ if (child.exitCode === null && child.signalCode === null) {
436
+ const exited = new Promise((resolve) => {
437
+ child.once("exit", () => resolve());
438
+ });
439
+ child.kill();
440
+ await exited;
441
+ }
396
442
  } catch (error) {
397
443
  errors.push(
398
444
  new Error(
@@ -400,18 +446,32 @@ class CopilotClient {
400
446
  )
401
447
  );
402
448
  }
403
- this.cliProcess = null;
404
449
  }
405
450
  if (this.cliStartTimeout) {
406
451
  clearTimeout(this.cliStartTimeout);
407
452
  this.cliStartTimeout = null;
408
453
  }
409
454
  this.state = "disconnected";
410
- this.actualPort = null;
455
+ this.runtimePort = null;
411
456
  this.stderrBuffer = "";
412
457
  this.processExitPromise = null;
413
458
  return errors;
414
459
  }
460
+ /**
461
+ * Alias for {@link stop} that lets `CopilotClient` participate in `await using`
462
+ * blocks for automatic cleanup.
463
+ *
464
+ * @example
465
+ * ```typescript
466
+ * await using client = new CopilotClient();
467
+ * const session = await client.createSession({ onPermissionRequest: approveAll });
468
+ * await session.sendAndWait("Hello");
469
+ * // client.stop() is called automatically when the block exits.
470
+ * ```
471
+ */
472
+ async [Symbol.asyncDispose]() {
473
+ await this.stop();
474
+ }
415
475
  /**
416
476
  * Forcefully stops the CLI server without graceful cleanup.
417
477
  *
@@ -468,7 +528,7 @@ class CopilotClient {
468
528
  this.cliStartTimeout = null;
469
529
  }
470
530
  this.state = "disconnected";
471
- this.actualPort = null;
531
+ this.runtimePort = null;
472
532
  this.stderrBuffer = "";
473
533
  this.processExitPromise = null;
474
534
  }
@@ -476,12 +536,11 @@ class CopilotClient {
476
536
  * Creates a new conversation session with the Copilot CLI.
477
537
  *
478
538
  * Sessions maintain conversation state, handle events, and manage tool execution.
479
- * If the client is not connected and `autoStart` is enabled, this will automatically
480
- * start the connection.
539
+ * If the client is not connected, this method automatically starts the connection.
481
540
  *
482
541
  * @param config - Optional configuration for the session
483
542
  * @returns A promise that resolves with the created session
484
- * @throws Error if the client is not connected and autoStart is disabled
543
+ * @throws Error if the client fails to start
485
544
  *
486
545
  * @example
487
546
  * ```typescript
@@ -502,17 +561,8 @@ class CopilotClient {
502
561
  * ```
503
562
  */
504
563
  async createSession(config) {
505
- if (!config?.onPermissionRequest) {
506
- throw new Error(
507
- "An onPermissionRequest handler is required when creating a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
508
- );
509
- }
510
564
  if (!this.connection) {
511
- if (this.options.autoStart) {
512
- await this.start();
513
- } else {
514
- throw new Error("Client not connected. Call start() first.");
515
- }
565
+ await this.start();
516
566
  }
517
567
  const sessionId = config.sessionId ?? (0, import_node_crypto.randomUUID)();
518
568
  const session = new import_session.CopilotSession(
@@ -530,11 +580,11 @@ class CopilotClient {
530
580
  if (config.onElicitationRequest) {
531
581
  session.registerElicitationHandler(config.onElicitationRequest);
532
582
  }
533
- if (config.onExitPlanMode) {
534
- session.registerExitPlanModeHandler(config.onExitPlanMode);
583
+ if (config.onExitPlanModeRequest) {
584
+ session.registerExitPlanModeHandler(config.onExitPlanModeRequest);
535
585
  }
536
- if (config.onAutoModeSwitch) {
537
- session.registerAutoModeSwitchHandler(config.onAutoModeSwitch);
586
+ if (config.onAutoModeSwitchRequest) {
587
+ session.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
538
588
  }
539
589
  if (config.hooks) {
540
590
  session.registerHooks(config.hooks);
@@ -549,17 +599,7 @@ class CopilotClient {
549
599
  session.on(config.onEvent);
550
600
  }
551
601
  this.sessions.set(sessionId, session);
552
- if (this.sessionFsConfig) {
553
- if (config.createSessionFsHandler) {
554
- session.clientSessionApis.sessionFs = (0, import_sessionFsProvider.createSessionFsAdapter)(
555
- config.createSessionFsHandler(session)
556
- );
557
- } else {
558
- throw new Error(
559
- "createSessionFsHandler is required in session config when sessionFs is enabled in client options."
560
- );
561
- }
562
- }
602
+ this.setupSessionFs(session, config);
563
603
  try {
564
604
  const response = await this.connection.sendRequest("session.create", {
565
605
  ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
@@ -581,21 +621,21 @@ class CopilotClient {
581
621
  systemMessage: wireSystemMessage,
582
622
  availableTools: config.availableTools,
583
623
  excludedTools: config.excludedTools,
584
- provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
624
+ provider: config.provider,
585
625
  enableSessionTelemetry: config.enableSessionTelemetry,
586
626
  modelCapabilities: config.modelCapabilities,
587
627
  requestPermission: true,
588
628
  requestUserInput: !!config.onUserInputRequest,
589
629
  requestElicitation: !!config.onElicitationRequest,
590
- requestExitPlanMode: !!config.onExitPlanMode,
591
- requestAutoModeSwitch: !!config.onAutoModeSwitch,
630
+ requestExitPlanMode: !!config.onExitPlanModeRequest,
631
+ requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
592
632
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
593
633
  workingDirectory: config.workingDirectory,
594
634
  streaming: config.streaming,
595
635
  includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
596
- mcpServers: config.mcpServers,
636
+ mcpServers: toWireMcpServers(config.mcpServers),
597
637
  envValueMode: "direct",
598
- customAgents: config.customAgents,
638
+ customAgents: toWireCustomAgents(config.customAgents),
599
639
  defaultAgent: config.defaultAgent,
600
640
  agent: config.agent,
601
641
  configDir: config.configDir,
@@ -604,7 +644,9 @@ class CopilotClient {
604
644
  instructionDirectories: config.instructionDirectories,
605
645
  disabledSkills: config.disabledSkills,
606
646
  infiniteSessions: config.infiniteSessions,
607
- gitHubToken: config.gitHubToken
647
+ gitHubToken: config.gitHubToken,
648
+ remoteSession: config.remoteSession,
649
+ cloud: config.cloud
608
650
  });
609
651
  const { workspacePath, capabilities } = response;
610
652
  session["_workspacePath"] = workspacePath;
@@ -640,17 +682,8 @@ class CopilotClient {
640
682
  * ```
641
683
  */
642
684
  async resumeSession(sessionId, config) {
643
- if (!config?.onPermissionRequest) {
644
- throw new Error(
645
- "An onPermissionRequest handler is required when resuming a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
646
- );
647
- }
648
685
  if (!this.connection) {
649
- if (this.options.autoStart) {
650
- await this.start();
651
- } else {
652
- throw new Error("Client not connected. Call start() first.");
653
- }
686
+ await this.start();
654
687
  }
655
688
  const session = new import_session.CopilotSession(
656
689
  sessionId,
@@ -667,11 +700,11 @@ class CopilotClient {
667
700
  if (config.onElicitationRequest) {
668
701
  session.registerElicitationHandler(config.onElicitationRequest);
669
702
  }
670
- if (config.onExitPlanMode) {
671
- session.registerExitPlanModeHandler(config.onExitPlanMode);
703
+ if (config.onExitPlanModeRequest) {
704
+ session.registerExitPlanModeHandler(config.onExitPlanModeRequest);
672
705
  }
673
- if (config.onAutoModeSwitch) {
674
- session.registerAutoModeSwitchHandler(config.onAutoModeSwitch);
706
+ if (config.onAutoModeSwitchRequest) {
707
+ session.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
675
708
  }
676
709
  if (config.hooks) {
677
710
  session.registerHooks(config.hooks);
@@ -686,17 +719,7 @@ class CopilotClient {
686
719
  session.on(config.onEvent);
687
720
  }
688
721
  this.sessions.set(sessionId, session);
689
- if (this.sessionFsConfig) {
690
- if (config.createSessionFsHandler) {
691
- session.clientSessionApis.sessionFs = (0, import_sessionFsProvider.createSessionFsAdapter)(
692
- config.createSessionFsHandler(session)
693
- );
694
- } else {
695
- throw new Error(
696
- "createSessionFsHandler is required in session config when sessionFs is enabled in client options."
697
- );
698
- }
699
- }
722
+ this.setupSessionFs(session, config);
700
723
  try {
701
724
  const response = await this.connection.sendRequest("session.resume", {
702
725
  ...await (0, import_telemetry.getTraceContext)(this.onGetTraceContext),
@@ -719,31 +742,32 @@ class CopilotClient {
719
742
  name: cmd.name,
720
743
  description: cmd.description
721
744
  })),
722
- provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
745
+ provider: config.provider,
723
746
  modelCapabilities: config.modelCapabilities,
724
747
  requestPermission: config.onPermissionRequest !== import_types.defaultJoinSessionPermissionHandler,
725
748
  requestUserInput: !!config.onUserInputRequest,
726
749
  requestElicitation: !!config.onElicitationRequest,
727
- requestExitPlanMode: !!config.onExitPlanMode,
728
- requestAutoModeSwitch: !!config.onAutoModeSwitch,
750
+ requestExitPlanMode: !!config.onExitPlanModeRequest,
751
+ requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
729
752
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
730
753
  workingDirectory: config.workingDirectory,
731
754
  configDir: config.configDir,
732
755
  enableConfigDiscovery: config.enableConfigDiscovery,
733
756
  streaming: config.streaming,
734
757
  includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
735
- mcpServers: config.mcpServers,
758
+ mcpServers: toWireMcpServers(config.mcpServers),
736
759
  envValueMode: "direct",
737
- customAgents: config.customAgents,
760
+ customAgents: toWireCustomAgents(config.customAgents),
738
761
  defaultAgent: config.defaultAgent,
739
762
  agent: config.agent,
740
763
  skillDirectories: config.skillDirectories,
741
764
  instructionDirectories: config.instructionDirectories,
742
765
  disabledSkills: config.disabledSkills,
743
766
  infiniteSessions: config.infiniteSessions,
744
- disableResume: config.disableResume,
767
+ suppressResumeEvent: config.suppressResumeEvent,
745
768
  continuePendingWork: config.continuePendingWork,
746
- gitHubToken: config.gitHubToken
769
+ gitHubToken: config.gitHubToken,
770
+ remoteSession: config.remoteSession
747
771
  });
748
772
  const { workspacePath, capabilities } = response;
749
773
  session["_workspacePath"] = workspacePath;
@@ -970,8 +994,13 @@ class CopilotClient {
970
994
  if (!this.connection) {
971
995
  throw new Error("Client not connected");
972
996
  }
997
+ let wireFilter;
998
+ if (filter) {
999
+ const { workingDirectory, ...rest } = filter;
1000
+ wireFilter = { ...rest, cwd: workingDirectory };
1001
+ }
973
1002
  const response = await this.connection.sendRequest("session.list", {
974
- filter
1003
+ filter: wireFilter
975
1004
  });
976
1005
  const { sessions } = response;
977
1006
  return sessions.map(CopilotClient.toSessionMetadata);
@@ -1006,13 +1035,19 @@ class CopilotClient {
1006
1035
  return CopilotClient.toSessionMetadata(session);
1007
1036
  }
1008
1037
  static toSessionMetadata(raw) {
1038
+ const { context } = raw;
1009
1039
  return {
1010
1040
  sessionId: raw.sessionId,
1011
1041
  startTime: new Date(raw.startTime),
1012
1042
  modifiedTime: new Date(raw.modifiedTime),
1013
1043
  summary: raw.summary,
1014
1044
  isRemote: raw.isRemote,
1015
- context: raw.context
1045
+ context: context ? {
1046
+ workingDirectory: context.cwd,
1047
+ gitRoot: context.gitRoot,
1048
+ repository: context.repository,
1049
+ branch: context.branch
1050
+ } : void 0
1016
1051
  };
1017
1052
  }
1018
1053
  /**
@@ -1065,7 +1100,7 @@ class CopilotClient {
1065
1100
  throw new Error(result.error || "Failed to set foreground session");
1066
1101
  }
1067
1102
  }
1068
- on(eventTypeOrHandler, handler) {
1103
+ onLifecycle(eventTypeOrHandler, handler) {
1069
1104
  if (typeof eventTypeOrHandler === "string" && handler) {
1070
1105
  const eventType = eventTypeOrHandler;
1071
1106
  if (!this.typedLifecycleHandlers.has(eventType)) {
@@ -1092,17 +1127,17 @@ class CopilotClient {
1092
1127
  async startCLIServer() {
1093
1128
  return new Promise((resolve, reject) => {
1094
1129
  this.stderrBuffer = "";
1095
- const args = [
1096
- ...this.options.cliArgs,
1097
- "--headless",
1098
- "--no-auto-update",
1099
- "--log-level",
1100
- this.options.logLevel
1101
- ];
1102
- if (this.options.useStdio) {
1130
+ const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
1131
+ if (this.options.logLevel) {
1132
+ args.push("--log-level", this.options.logLevel);
1133
+ }
1134
+ if (this.connectionConfig.kind === "stdio") {
1103
1135
  args.push("--stdio");
1104
- } else if (this.options.port > 0) {
1105
- args.push("--port", this.options.port.toString());
1136
+ } else if (this.connectionConfig.kind === "tcp") {
1137
+ const requestedPort = this.connectionConfig.port ?? 0;
1138
+ if (requestedPort > 0) {
1139
+ args.push("--port", requestedPort.toString());
1140
+ }
1106
1141
  }
1107
1142
  if (this.options.gitHubToken) {
1108
1143
  args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN");
@@ -1116,10 +1151,10 @@ class CopilotClient {
1116
1151
  this.options.sessionIdleTimeoutSeconds.toString()
1117
1152
  );
1118
1153
  }
1119
- if (this.options.remote) {
1154
+ if (this.options.enableRemoteSessions) {
1120
1155
  args.push("--remote");
1121
1156
  }
1122
- const envWithoutNodeDebug = { ...this.options.env };
1157
+ const envWithoutNodeDebug = { ...this.resolvedEnv };
1123
1158
  delete envWithoutNodeDebug.NODE_DEBUG;
1124
1159
  if (this.options.gitHubToken) {
1125
1160
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
@@ -1127,12 +1162,12 @@ class CopilotClient {
1127
1162
  if (this.effectiveConnectionToken) {
1128
1163
  envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
1129
1164
  }
1130
- if (this.options.copilotHome) {
1131
- envWithoutNodeDebug.COPILOT_HOME = this.options.copilotHome;
1165
+ if (this.options.baseDirectory) {
1166
+ envWithoutNodeDebug.COPILOT_HOME = this.options.baseDirectory;
1132
1167
  }
1133
- if (!this.options.cliPath) {
1168
+ if (!this.resolvedCliPath) {
1134
1169
  throw new Error(
1135
- "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
1170
+ "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."
1136
1171
  );
1137
1172
  }
1138
1173
  if (this.options.telemetry) {
@@ -1151,31 +1186,31 @@ class CopilotClient {
1151
1186
  t.captureContent
1152
1187
  );
1153
1188
  }
1154
- if (!(0, import_node_fs.existsSync)(this.options.cliPath)) {
1189
+ if (!(0, import_node_fs.existsSync)(this.resolvedCliPath)) {
1155
1190
  throw new Error(
1156
- `Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
1191
+ `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
1157
1192
  );
1158
1193
  }
1159
- const stdioConfig = this.options.useStdio ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
1160
- const isJsFile = this.options.cliPath.endsWith(".js");
1194
+ const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
1195
+ const isJsFile = this.resolvedCliPath.endsWith(".js");
1161
1196
  if (isJsFile) {
1162
- this.cliProcess = (0, import_node_child_process.spawn)(getNodeExecPath(), [this.options.cliPath, ...args], {
1197
+ this.cliProcess = (0, import_node_child_process.spawn)(getNodeExecPath(), [this.resolvedCliPath, ...args], {
1163
1198
  stdio: stdioConfig,
1164
- cwd: this.options.cwd,
1199
+ cwd: this.options.workingDirectory,
1165
1200
  env: envWithoutNodeDebug,
1166
1201
  windowsHide: true
1167
1202
  });
1168
1203
  } else {
1169
- this.cliProcess = (0, import_node_child_process.spawn)(this.options.cliPath, args, {
1204
+ this.cliProcess = (0, import_node_child_process.spawn)(this.resolvedCliPath, args, {
1170
1205
  stdio: stdioConfig,
1171
- cwd: this.options.cwd,
1206
+ cwd: this.options.workingDirectory,
1172
1207
  env: envWithoutNodeDebug,
1173
1208
  windowsHide: true
1174
1209
  });
1175
1210
  }
1176
1211
  let stdout = "";
1177
1212
  let resolved = false;
1178
- if (this.options.useStdio) {
1213
+ if (this.connectionConfig.kind === "stdio") {
1179
1214
  resolved = true;
1180
1215
  resolve();
1181
1216
  } else {
@@ -1183,7 +1218,7 @@ class CopilotClient {
1183
1218
  stdout += data.toString();
1184
1219
  const match = stdout.match(/listening on port (\d+)/i);
1185
1220
  if (match && !resolved) {
1186
- this.actualPort = parseInt(match[1], 10);
1221
+ this.runtimePort = parseInt(match[1], 10);
1187
1222
  resolved = true;
1188
1223
  resolve();
1189
1224
  }
@@ -1257,19 +1292,21 @@ stderr: ${stderrOutput}`
1257
1292
  resolved = true;
1258
1293
  reject(new Error("Timeout waiting for CLI server to start"));
1259
1294
  }
1260
- }, 1e4);
1295
+ }, 3e4);
1261
1296
  });
1262
1297
  }
1263
1298
  /**
1264
1299
  * Connect to the CLI server (via socket or stdio)
1265
1300
  */
1266
1301
  async connectToServer() {
1267
- if (this.options.isChildProcess) {
1268
- return this.connectToParentProcessViaStdio();
1269
- } else if (this.options.useStdio) {
1270
- return this.connectToChildProcessViaStdio();
1271
- } else {
1272
- return this.connectViaTcp();
1302
+ switch (this.connectionConfig.kind) {
1303
+ case "parent-process":
1304
+ return this.connectToParentProcessViaStdio();
1305
+ case "stdio":
1306
+ return this.connectToChildProcessViaStdio();
1307
+ case "tcp":
1308
+ case "uri":
1309
+ return this.connectViaTcp();
1273
1310
  }
1274
1311
  }
1275
1312
  /**
@@ -1309,12 +1346,17 @@ stderr: ${stderrOutput}`
1309
1346
  * Connect to the CLI server via TCP socket
1310
1347
  */
1311
1348
  async connectViaTcp() {
1312
- if (!this.actualPort) {
1349
+ if (!this.runtimePort) {
1313
1350
  throw new Error("Server port not available");
1314
1351
  }
1315
1352
  return new Promise((resolve, reject) => {
1316
1353
  this.socket = new import_node_net.Socket();
1317
- this.socket.connect(this.actualPort, this.actualHost, () => {
1354
+ const connectionTimeout = setTimeout(() => {
1355
+ this.socket?.destroy();
1356
+ reject(new Error("Timeout connecting to CLI server"));
1357
+ }, 1e4);
1358
+ this.socket.connect(this.runtimePort, this.actualHost, () => {
1359
+ clearTimeout(connectionTimeout);
1318
1360
  this.connection = (0, import_node.createMessageConnection)(
1319
1361
  new import_node.StreamMessageReader(this.socket),
1320
1362
  new import_node.StreamMessageWriter(this.socket)
@@ -1324,6 +1366,7 @@ stderr: ${stderrOutput}`
1324
1366
  resolve();
1325
1367
  });
1326
1368
  this.socket.on("error", (error) => {
1369
+ clearTimeout(connectionTimeout);
1327
1370
  reject(new Error(`Failed to connect to CLI server: ${error.message}`));
1328
1371
  });
1329
1372
  });
@@ -1392,7 +1435,20 @@ stderr: ${stderrOutput}`
1392
1435
  if (typeof notification !== "object" || !notification || !("type" in notification) || typeof notification.type !== "string" || !("sessionId" in notification) || typeof notification.sessionId !== "string") {
1393
1436
  return;
1394
1437
  }
1395
- const event = notification;
1438
+ const raw = notification;
1439
+ let metadata;
1440
+ if (raw.metadata && raw.metadata.startTime && raw.metadata.modifiedTime) {
1441
+ metadata = {
1442
+ startTime: new Date(raw.metadata.startTime),
1443
+ modifiedTime: new Date(raw.metadata.modifiedTime),
1444
+ summary: raw.metadata.summary
1445
+ };
1446
+ }
1447
+ const event = {
1448
+ type: raw.type,
1449
+ sessionId: raw.sessionId,
1450
+ metadata
1451
+ };
1396
1452
  const typedHandlers = this.typedLifecycleHandlers.get(event.type);
1397
1453
  if (typedHandlers) {
1398
1454
  for (const handler of typedHandlers) {