@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.
package/dist/client.js CHANGED
@@ -22,11 +22,6 @@ import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js";
22
22
  import { createSessionFsAdapter } from "./sessionFsProvider.js";
23
23
  import { getTraceContext } from "./telemetry.js";
24
24
  import { defaultJoinSessionPermissionHandler } from "./types.js";
25
- function toWireProviderConfig(provider) {
26
- const { maxInputTokens, ...rest } = provider;
27
- if (maxInputTokens === void 0) return rest;
28
- return { ...rest, maxPromptTokens: maxInputTokens };
29
- }
30
25
  const MIN_PROTOCOL_VERSION = 2;
31
26
  function isZodSchema(value) {
32
27
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
@@ -38,6 +33,26 @@ function toJsonSchema(parameters) {
38
33
  }
39
34
  return parameters;
40
35
  }
36
+ function toWireMcpServers(mcpServers) {
37
+ if (!mcpServers) return void 0;
38
+ return Object.fromEntries(
39
+ Object.entries(mcpServers).map(([name, server]) => {
40
+ if ("workingDirectory" in server) {
41
+ const { workingDirectory, ...rest } = server;
42
+ return [name, { ...rest, cwd: workingDirectory }];
43
+ }
44
+ return [name, server];
45
+ })
46
+ );
47
+ }
48
+ function toWireCustomAgents(agents) {
49
+ if (!agents) return void 0;
50
+ return agents.map((agent) => {
51
+ if (!agent.mcpServers) return agent;
52
+ const { mcpServers, ...rest } = agent;
53
+ return { ...rest, mcpServers: toWireMcpServers(mcpServers) };
54
+ });
55
+ }
41
56
  function extractTransformCallbacks(systemMessage) {
42
57
  if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
43
58
  return { wirePayload: systemMessage, transformCallbacks: void 0 };
@@ -91,12 +106,18 @@ class CopilotClient {
91
106
  cliProcess = null;
92
107
  connection = null;
93
108
  socket = null;
94
- actualPort = null;
109
+ runtimePort = null;
95
110
  actualHost = "localhost";
96
111
  state = "disconnected";
97
112
  sessions = /* @__PURE__ */ new Map();
98
113
  stderrBuffer = "";
99
114
  // Captures CLI stderr for error messages
115
+ /** Resolved connection mode chosen in the constructor. */
116
+ connectionConfig;
117
+ /** Resolved path to the runtime executable (only used for child-process kinds). */
118
+ resolvedCliPath;
119
+ /** Resolved environment passed to the spawned runtime. */
120
+ resolvedEnv;
100
121
  options;
101
122
  isExternalServer = false;
102
123
  forceStopping = false;
@@ -145,86 +166,79 @@ class CopilotClient {
145
166
  * Creates a new CopilotClient instance.
146
167
  *
147
168
  * @param options - Configuration options for the client
148
- * @throws Error if mutually exclusive options are provided (e.g., cliUrl with useStdio or cliPath)
149
169
  *
150
170
  * @example
151
171
  * ```typescript
152
- * // Default options - spawns CLI server using stdio
172
+ * // Default: spawns the bundled runtime over stdio
153
173
  * const client = new CopilotClient();
154
174
  *
155
- * // Connect to an existing server
156
- * const client = new CopilotClient({ cliUrl: "localhost:3000" });
175
+ * // Connect to an existing runtime
176
+ * const client = new CopilotClient({
177
+ * connection: RuntimeConnection.forUri("localhost:3000"),
178
+ * });
179
+ *
180
+ * // Spawn the runtime over TCP on a chosen port
181
+ * const client = new CopilotClient({
182
+ * connection: RuntimeConnection.forTcp({ port: 9001 }),
183
+ * });
157
184
  *
158
- * // Custom CLI path with specific log level
185
+ * // Use a custom runtime binary
159
186
  * const client = new CopilotClient({
160
- * cliPath: "/usr/local/bin/copilot",
161
- * logLevel: "debug"
187
+ * connection: RuntimeConnection.forStdio({ path: "/usr/local/bin/copilot" }),
188
+ * logLevel: "debug",
162
189
  * });
163
190
  * ```
164
191
  */
165
192
  constructor(options = {}) {
166
- if (options.cliUrl && (options.useStdio === true || options.cliPath)) {
167
- throw new Error("cliUrl is mutually exclusive with useStdio and cliPath");
168
- }
169
- if (options.isChildProcess && (options.cliUrl || options.useStdio === false)) {
193
+ const conn = options._internalConnection ?? options.connection ?? { kind: "stdio" };
194
+ if (conn.kind === "uri" && (options.gitHubToken !== void 0 || options.useLoggedInUser !== void 0)) {
170
195
  throw new Error(
171
- "isChildProcess must be used in conjunction with useStdio and not with cliUrl"
196
+ "gitHubToken and useLoggedInUser cannot be used with RuntimeConnection.forUri (external server manages its own auth)"
172
197
  );
173
198
  }
174
- if (options.cliUrl && (options.gitHubToken || options.useLoggedInUser !== void 0)) {
175
- throw new Error(
176
- "gitHubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
177
- );
178
- }
179
- if (options.tcpConnectionToken !== void 0) {
180
- if (typeof options.tcpConnectionToken !== "string" || options.tcpConnectionToken.length === 0) {
181
- throw new Error("tcpConnectionToken must be a non-empty string");
182
- }
183
- if (options.useStdio === true) {
184
- throw new Error("tcpConnectionToken cannot be used with useStdio: true");
199
+ if (conn.kind === "tcp" && conn.connectionToken !== void 0) {
200
+ if (typeof conn.connectionToken !== "string" || conn.connectionToken.length === 0) {
201
+ throw new Error("connectionToken must be a non-empty string");
185
202
  }
186
203
  }
187
- const willUseStdio = options.cliUrl ? false : options.useStdio ?? true;
188
- const sdkSpawnsCli = !willUseStdio && !options.cliUrl && !options.isChildProcess;
189
- this.effectiveConnectionToken = options.tcpConnectionToken ?? (sdkSpawnsCli ? randomUUID() : void 0);
204
+ this.connectionConfig = conn;
190
205
  if (options.sessionFs) {
191
206
  this.validateSessionFsConfig(options.sessionFs);
192
207
  }
193
- if (options.cliUrl) {
194
- const { host, port } = this.parseCliUrl(options.cliUrl);
208
+ if (conn.kind === "uri") {
209
+ const { host, port } = this.parseCliUrl(conn.url);
195
210
  this.actualHost = host;
196
- this.actualPort = port;
211
+ this.runtimePort = port;
197
212
  this.isExternalServer = true;
198
- }
199
- if (options.isChildProcess) {
213
+ } else if (conn.kind === "parent-process") {
200
214
  this.isExternalServer = true;
201
215
  }
216
+ if (conn.kind === "tcp") {
217
+ this.effectiveConnectionToken = conn.connectionToken ?? randomUUID();
218
+ } else if (conn.kind === "uri") {
219
+ this.effectiveConnectionToken = conn.connectionToken;
220
+ }
202
221
  this.onListModels = options.onListModels;
203
222
  this.onGetTraceContext = options.onGetTraceContext;
204
223
  this.sessionFsConfig = options.sessionFs ?? null;
205
224
  const effectiveEnv = options.env ?? process.env;
225
+ this.resolvedEnv = effectiveEnv;
226
+ this.resolvedCliPath = conn.kind === "stdio" || conn.kind === "tcp" ? conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath() : void 0;
227
+ const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
228
+ this.connectionExtraArgs = [...connArgs];
206
229
  this.options = {
207
- cliPath: options.cliUrl ? void 0 : options.cliPath || effectiveEnv.COPILOT_CLI_PATH || getBundledCliPath(),
208
- cliArgs: options.cliArgs ?? [],
209
- cwd: options.cwd ?? process.cwd(),
210
- port: options.port || 0,
211
- useStdio: options.cliUrl ? false : options.useStdio ?? true,
212
- // Default to stdio unless cliUrl is provided
213
- isChildProcess: options.isChildProcess ?? false,
214
- cliUrl: options.cliUrl,
215
- logLevel: options.logLevel || "debug",
216
- autoStart: options.autoStart ?? true,
217
- autoRestart: false,
218
- env: effectiveEnv,
230
+ workingDirectory: options.workingDirectory ?? process.cwd(),
231
+ logLevel: options.logLevel,
219
232
  gitHubToken: options.gitHubToken,
220
- // Default useLoggedInUser to false when gitHubToken is provided, otherwise true
233
+ // Default useLoggedInUser to false when gitHubToken is provided, otherwise true.
221
234
  useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true),
222
235
  telemetry: options.telemetry,
223
- copilotHome: options.copilotHome,
236
+ baseDirectory: options.baseDirectory,
224
237
  sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
225
- remote: options.remote ?? false
238
+ enableRemoteSessions: options.enableRemoteSessions ?? false
226
239
  };
227
240
  }
241
+ connectionExtraArgs = [];
228
242
  /**
229
243
  * Parse CLI URL into host and port
230
244
  * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
@@ -258,20 +272,37 @@ class CopilotClient {
258
272
  throw new Error("sessionFs.conventions must be either 'windows' or 'posix'");
259
273
  }
260
274
  }
275
+ setupSessionFs(session, config) {
276
+ if (!this.sessionFsConfig) {
277
+ return;
278
+ }
279
+ if (!config.createSessionFsProvider) {
280
+ throw new Error(
281
+ "createSessionFsProvider is required in session config when sessionFs is enabled in client options."
282
+ );
283
+ }
284
+ const provider = config.createSessionFsProvider(session);
285
+ if (this.sessionFsConfig.capabilities?.sqlite && !provider.sqlite) {
286
+ throw new Error(
287
+ "SessionFsConfig declares capabilities.sqlite but the provider does not implement sqlite."
288
+ );
289
+ }
290
+ session.clientSessionApis.sessionFs = createSessionFsAdapter(provider);
291
+ }
261
292
  /**
262
293
  * Starts the CLI server and establishes a connection.
263
294
  *
264
295
  * If connecting to an external server (via cliUrl), only establishes the connection.
265
296
  * Otherwise, spawns the CLI server process and then connects.
266
297
  *
267
- * This method is called automatically when creating a session if `autoStart` is true (default).
298
+ * This method is called automatically the first time you create or resume a session.
268
299
  *
269
300
  * @returns A promise that resolves when the connection is established
270
301
  * @throws Error if the server fails to start or the connection fails
271
302
  *
272
303
  * @example
273
304
  * ```typescript
274
- * const client = new CopilotClient({ autoStart: false });
305
+ * const client = new CopilotClient();
275
306
  * await client.start();
276
307
  * // Now ready to create sessions
277
308
  * ```
@@ -291,7 +322,8 @@ class CopilotClient {
291
322
  await this.connection.sendRequest("sessionFs.setProvider", {
292
323
  initialCwd: this.sessionFsConfig.initialCwd,
293
324
  sessionStatePath: this.sessionFsConfig.sessionStatePath,
294
- conventions: this.sessionFsConfig.conventions
325
+ conventions: this.sessionFsConfig.conventions,
326
+ capabilities: this.sessionFsConfig.capabilities
295
327
  });
296
328
  }
297
329
  this.state = "connected";
@@ -365,8 +397,15 @@ class CopilotClient {
365
397
  }
366
398
  this.modelsCache = null;
367
399
  if (this.socket) {
400
+ const socket = this.socket;
401
+ this.socket = null;
368
402
  try {
369
- this.socket.end();
403
+ if (!socket.destroyed) {
404
+ await new Promise((resolve) => {
405
+ socket.once("close", () => resolve());
406
+ socket.end();
407
+ });
408
+ }
370
409
  } catch (error) {
371
410
  errors.push(
372
411
  new Error(
@@ -374,11 +413,18 @@ class CopilotClient {
374
413
  )
375
414
  );
376
415
  }
377
- this.socket = null;
378
416
  }
379
417
  if (this.cliProcess && !this.isExternalServer) {
418
+ const child = this.cliProcess;
419
+ this.cliProcess = null;
380
420
  try {
381
- this.cliProcess.kill();
421
+ if (child.exitCode === null && child.signalCode === null) {
422
+ const exited = new Promise((resolve) => {
423
+ child.once("exit", () => resolve());
424
+ });
425
+ child.kill();
426
+ await exited;
427
+ }
382
428
  } catch (error) {
383
429
  errors.push(
384
430
  new Error(
@@ -386,18 +432,32 @@ class CopilotClient {
386
432
  )
387
433
  );
388
434
  }
389
- this.cliProcess = null;
390
435
  }
391
436
  if (this.cliStartTimeout) {
392
437
  clearTimeout(this.cliStartTimeout);
393
438
  this.cliStartTimeout = null;
394
439
  }
395
440
  this.state = "disconnected";
396
- this.actualPort = null;
441
+ this.runtimePort = null;
397
442
  this.stderrBuffer = "";
398
443
  this.processExitPromise = null;
399
444
  return errors;
400
445
  }
446
+ /**
447
+ * Alias for {@link stop} that lets `CopilotClient` participate in `await using`
448
+ * blocks for automatic cleanup.
449
+ *
450
+ * @example
451
+ * ```typescript
452
+ * await using client = new CopilotClient();
453
+ * const session = await client.createSession({ onPermissionRequest: approveAll });
454
+ * await session.sendAndWait("Hello");
455
+ * // client.stop() is called automatically when the block exits.
456
+ * ```
457
+ */
458
+ async [Symbol.asyncDispose]() {
459
+ await this.stop();
460
+ }
401
461
  /**
402
462
  * Forcefully stops the CLI server without graceful cleanup.
403
463
  *
@@ -454,7 +514,7 @@ class CopilotClient {
454
514
  this.cliStartTimeout = null;
455
515
  }
456
516
  this.state = "disconnected";
457
- this.actualPort = null;
517
+ this.runtimePort = null;
458
518
  this.stderrBuffer = "";
459
519
  this.processExitPromise = null;
460
520
  }
@@ -462,12 +522,11 @@ class CopilotClient {
462
522
  * Creates a new conversation session with the Copilot CLI.
463
523
  *
464
524
  * Sessions maintain conversation state, handle events, and manage tool execution.
465
- * If the client is not connected and `autoStart` is enabled, this will automatically
466
- * start the connection.
525
+ * If the client is not connected, this method automatically starts the connection.
467
526
  *
468
527
  * @param config - Optional configuration for the session
469
528
  * @returns A promise that resolves with the created session
470
- * @throws Error if the client is not connected and autoStart is disabled
529
+ * @throws Error if the client fails to start
471
530
  *
472
531
  * @example
473
532
  * ```typescript
@@ -488,17 +547,8 @@ class CopilotClient {
488
547
  * ```
489
548
  */
490
549
  async createSession(config) {
491
- if (!config?.onPermissionRequest) {
492
- throw new Error(
493
- "An onPermissionRequest handler is required when creating a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
494
- );
495
- }
496
550
  if (!this.connection) {
497
- if (this.options.autoStart) {
498
- await this.start();
499
- } else {
500
- throw new Error("Client not connected. Call start() first.");
501
- }
551
+ await this.start();
502
552
  }
503
553
  const sessionId = config.sessionId ?? randomUUID();
504
554
  const session = new CopilotSession(
@@ -516,11 +566,11 @@ class CopilotClient {
516
566
  if (config.onElicitationRequest) {
517
567
  session.registerElicitationHandler(config.onElicitationRequest);
518
568
  }
519
- if (config.onExitPlanMode) {
520
- session.registerExitPlanModeHandler(config.onExitPlanMode);
569
+ if (config.onExitPlanModeRequest) {
570
+ session.registerExitPlanModeHandler(config.onExitPlanModeRequest);
521
571
  }
522
- if (config.onAutoModeSwitch) {
523
- session.registerAutoModeSwitchHandler(config.onAutoModeSwitch);
572
+ if (config.onAutoModeSwitchRequest) {
573
+ session.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
524
574
  }
525
575
  if (config.hooks) {
526
576
  session.registerHooks(config.hooks);
@@ -535,17 +585,7 @@ class CopilotClient {
535
585
  session.on(config.onEvent);
536
586
  }
537
587
  this.sessions.set(sessionId, session);
538
- if (this.sessionFsConfig) {
539
- if (config.createSessionFsHandler) {
540
- session.clientSessionApis.sessionFs = createSessionFsAdapter(
541
- config.createSessionFsHandler(session)
542
- );
543
- } else {
544
- throw new Error(
545
- "createSessionFsHandler is required in session config when sessionFs is enabled in client options."
546
- );
547
- }
548
- }
588
+ this.setupSessionFs(session, config);
549
589
  try {
550
590
  const response = await this.connection.sendRequest("session.create", {
551
591
  ...await getTraceContext(this.onGetTraceContext),
@@ -567,21 +607,21 @@ class CopilotClient {
567
607
  systemMessage: wireSystemMessage,
568
608
  availableTools: config.availableTools,
569
609
  excludedTools: config.excludedTools,
570
- provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
610
+ provider: config.provider,
571
611
  enableSessionTelemetry: config.enableSessionTelemetry,
572
612
  modelCapabilities: config.modelCapabilities,
573
613
  requestPermission: true,
574
614
  requestUserInput: !!config.onUserInputRequest,
575
615
  requestElicitation: !!config.onElicitationRequest,
576
- requestExitPlanMode: !!config.onExitPlanMode,
577
- requestAutoModeSwitch: !!config.onAutoModeSwitch,
616
+ requestExitPlanMode: !!config.onExitPlanModeRequest,
617
+ requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
578
618
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
579
619
  workingDirectory: config.workingDirectory,
580
620
  streaming: config.streaming,
581
621
  includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
582
- mcpServers: config.mcpServers,
622
+ mcpServers: toWireMcpServers(config.mcpServers),
583
623
  envValueMode: "direct",
584
- customAgents: config.customAgents,
624
+ customAgents: toWireCustomAgents(config.customAgents),
585
625
  defaultAgent: config.defaultAgent,
586
626
  agent: config.agent,
587
627
  configDir: config.configDir,
@@ -590,7 +630,9 @@ class CopilotClient {
590
630
  instructionDirectories: config.instructionDirectories,
591
631
  disabledSkills: config.disabledSkills,
592
632
  infiniteSessions: config.infiniteSessions,
593
- gitHubToken: config.gitHubToken
633
+ gitHubToken: config.gitHubToken,
634
+ remoteSession: config.remoteSession,
635
+ cloud: config.cloud
594
636
  });
595
637
  const { workspacePath, capabilities } = response;
596
638
  session["_workspacePath"] = workspacePath;
@@ -626,17 +668,8 @@ class CopilotClient {
626
668
  * ```
627
669
  */
628
670
  async resumeSession(sessionId, config) {
629
- if (!config?.onPermissionRequest) {
630
- throw new Error(
631
- "An onPermissionRequest handler is required when resuming a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }."
632
- );
633
- }
634
671
  if (!this.connection) {
635
- if (this.options.autoStart) {
636
- await this.start();
637
- } else {
638
- throw new Error("Client not connected. Call start() first.");
639
- }
672
+ await this.start();
640
673
  }
641
674
  const session = new CopilotSession(
642
675
  sessionId,
@@ -653,11 +686,11 @@ class CopilotClient {
653
686
  if (config.onElicitationRequest) {
654
687
  session.registerElicitationHandler(config.onElicitationRequest);
655
688
  }
656
- if (config.onExitPlanMode) {
657
- session.registerExitPlanModeHandler(config.onExitPlanMode);
689
+ if (config.onExitPlanModeRequest) {
690
+ session.registerExitPlanModeHandler(config.onExitPlanModeRequest);
658
691
  }
659
- if (config.onAutoModeSwitch) {
660
- session.registerAutoModeSwitchHandler(config.onAutoModeSwitch);
692
+ if (config.onAutoModeSwitchRequest) {
693
+ session.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
661
694
  }
662
695
  if (config.hooks) {
663
696
  session.registerHooks(config.hooks);
@@ -672,17 +705,7 @@ class CopilotClient {
672
705
  session.on(config.onEvent);
673
706
  }
674
707
  this.sessions.set(sessionId, session);
675
- if (this.sessionFsConfig) {
676
- if (config.createSessionFsHandler) {
677
- session.clientSessionApis.sessionFs = createSessionFsAdapter(
678
- config.createSessionFsHandler(session)
679
- );
680
- } else {
681
- throw new Error(
682
- "createSessionFsHandler is required in session config when sessionFs is enabled in client options."
683
- );
684
- }
685
- }
708
+ this.setupSessionFs(session, config);
686
709
  try {
687
710
  const response = await this.connection.sendRequest("session.resume", {
688
711
  ...await getTraceContext(this.onGetTraceContext),
@@ -705,31 +728,32 @@ class CopilotClient {
705
728
  name: cmd.name,
706
729
  description: cmd.description
707
730
  })),
708
- provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
731
+ provider: config.provider,
709
732
  modelCapabilities: config.modelCapabilities,
710
733
  requestPermission: config.onPermissionRequest !== defaultJoinSessionPermissionHandler,
711
734
  requestUserInput: !!config.onUserInputRequest,
712
735
  requestElicitation: !!config.onElicitationRequest,
713
- requestExitPlanMode: !!config.onExitPlanMode,
714
- requestAutoModeSwitch: !!config.onAutoModeSwitch,
736
+ requestExitPlanMode: !!config.onExitPlanModeRequest,
737
+ requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
715
738
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
716
739
  workingDirectory: config.workingDirectory,
717
740
  configDir: config.configDir,
718
741
  enableConfigDiscovery: config.enableConfigDiscovery,
719
742
  streaming: config.streaming,
720
743
  includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
721
- mcpServers: config.mcpServers,
744
+ mcpServers: toWireMcpServers(config.mcpServers),
722
745
  envValueMode: "direct",
723
- customAgents: config.customAgents,
746
+ customAgents: toWireCustomAgents(config.customAgents),
724
747
  defaultAgent: config.defaultAgent,
725
748
  agent: config.agent,
726
749
  skillDirectories: config.skillDirectories,
727
750
  instructionDirectories: config.instructionDirectories,
728
751
  disabledSkills: config.disabledSkills,
729
752
  infiniteSessions: config.infiniteSessions,
730
- disableResume: config.disableResume,
753
+ suppressResumeEvent: config.suppressResumeEvent,
731
754
  continuePendingWork: config.continuePendingWork,
732
- gitHubToken: config.gitHubToken
755
+ gitHubToken: config.gitHubToken,
756
+ remoteSession: config.remoteSession
733
757
  });
734
758
  const { workspacePath, capabilities } = response;
735
759
  session["_workspacePath"] = workspacePath;
@@ -956,8 +980,13 @@ class CopilotClient {
956
980
  if (!this.connection) {
957
981
  throw new Error("Client not connected");
958
982
  }
983
+ let wireFilter;
984
+ if (filter) {
985
+ const { workingDirectory, ...rest } = filter;
986
+ wireFilter = { ...rest, cwd: workingDirectory };
987
+ }
959
988
  const response = await this.connection.sendRequest("session.list", {
960
- filter
989
+ filter: wireFilter
961
990
  });
962
991
  const { sessions } = response;
963
992
  return sessions.map(CopilotClient.toSessionMetadata);
@@ -992,13 +1021,19 @@ class CopilotClient {
992
1021
  return CopilotClient.toSessionMetadata(session);
993
1022
  }
994
1023
  static toSessionMetadata(raw) {
1024
+ const { context } = raw;
995
1025
  return {
996
1026
  sessionId: raw.sessionId,
997
1027
  startTime: new Date(raw.startTime),
998
1028
  modifiedTime: new Date(raw.modifiedTime),
999
1029
  summary: raw.summary,
1000
1030
  isRemote: raw.isRemote,
1001
- context: raw.context
1031
+ context: context ? {
1032
+ workingDirectory: context.cwd,
1033
+ gitRoot: context.gitRoot,
1034
+ repository: context.repository,
1035
+ branch: context.branch
1036
+ } : void 0
1002
1037
  };
1003
1038
  }
1004
1039
  /**
@@ -1051,7 +1086,7 @@ class CopilotClient {
1051
1086
  throw new Error(result.error || "Failed to set foreground session");
1052
1087
  }
1053
1088
  }
1054
- on(eventTypeOrHandler, handler) {
1089
+ onLifecycle(eventTypeOrHandler, handler) {
1055
1090
  if (typeof eventTypeOrHandler === "string" && handler) {
1056
1091
  const eventType = eventTypeOrHandler;
1057
1092
  if (!this.typedLifecycleHandlers.has(eventType)) {
@@ -1078,17 +1113,17 @@ class CopilotClient {
1078
1113
  async startCLIServer() {
1079
1114
  return new Promise((resolve, reject) => {
1080
1115
  this.stderrBuffer = "";
1081
- const args = [
1082
- ...this.options.cliArgs,
1083
- "--headless",
1084
- "--no-auto-update",
1085
- "--log-level",
1086
- this.options.logLevel
1087
- ];
1088
- if (this.options.useStdio) {
1116
+ const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
1117
+ if (this.options.logLevel) {
1118
+ args.push("--log-level", this.options.logLevel);
1119
+ }
1120
+ if (this.connectionConfig.kind === "stdio") {
1089
1121
  args.push("--stdio");
1090
- } else if (this.options.port > 0) {
1091
- args.push("--port", this.options.port.toString());
1122
+ } else if (this.connectionConfig.kind === "tcp") {
1123
+ const requestedPort = this.connectionConfig.port ?? 0;
1124
+ if (requestedPort > 0) {
1125
+ args.push("--port", requestedPort.toString());
1126
+ }
1092
1127
  }
1093
1128
  if (this.options.gitHubToken) {
1094
1129
  args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN");
@@ -1102,10 +1137,10 @@ class CopilotClient {
1102
1137
  this.options.sessionIdleTimeoutSeconds.toString()
1103
1138
  );
1104
1139
  }
1105
- if (this.options.remote) {
1140
+ if (this.options.enableRemoteSessions) {
1106
1141
  args.push("--remote");
1107
1142
  }
1108
- const envWithoutNodeDebug = { ...this.options.env };
1143
+ const envWithoutNodeDebug = { ...this.resolvedEnv };
1109
1144
  delete envWithoutNodeDebug.NODE_DEBUG;
1110
1145
  if (this.options.gitHubToken) {
1111
1146
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
@@ -1113,12 +1148,12 @@ class CopilotClient {
1113
1148
  if (this.effectiveConnectionToken) {
1114
1149
  envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
1115
1150
  }
1116
- if (this.options.copilotHome) {
1117
- envWithoutNodeDebug.COPILOT_HOME = this.options.copilotHome;
1151
+ if (this.options.baseDirectory) {
1152
+ envWithoutNodeDebug.COPILOT_HOME = this.options.baseDirectory;
1118
1153
  }
1119
- if (!this.options.cliPath) {
1154
+ if (!this.resolvedCliPath) {
1120
1155
  throw new Error(
1121
- "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
1156
+ "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."
1122
1157
  );
1123
1158
  }
1124
1159
  if (this.options.telemetry) {
@@ -1137,31 +1172,31 @@ class CopilotClient {
1137
1172
  t.captureContent
1138
1173
  );
1139
1174
  }
1140
- if (!existsSync(this.options.cliPath)) {
1175
+ if (!existsSync(this.resolvedCliPath)) {
1141
1176
  throw new Error(
1142
- `Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
1177
+ `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
1143
1178
  );
1144
1179
  }
1145
- const stdioConfig = this.options.useStdio ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
1146
- const isJsFile = this.options.cliPath.endsWith(".js");
1180
+ const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
1181
+ const isJsFile = this.resolvedCliPath.endsWith(".js");
1147
1182
  if (isJsFile) {
1148
- this.cliProcess = spawn(getNodeExecPath(), [this.options.cliPath, ...args], {
1183
+ this.cliProcess = spawn(getNodeExecPath(), [this.resolvedCliPath, ...args], {
1149
1184
  stdio: stdioConfig,
1150
- cwd: this.options.cwd,
1185
+ cwd: this.options.workingDirectory,
1151
1186
  env: envWithoutNodeDebug,
1152
1187
  windowsHide: true
1153
1188
  });
1154
1189
  } else {
1155
- this.cliProcess = spawn(this.options.cliPath, args, {
1190
+ this.cliProcess = spawn(this.resolvedCliPath, args, {
1156
1191
  stdio: stdioConfig,
1157
- cwd: this.options.cwd,
1192
+ cwd: this.options.workingDirectory,
1158
1193
  env: envWithoutNodeDebug,
1159
1194
  windowsHide: true
1160
1195
  });
1161
1196
  }
1162
1197
  let stdout = "";
1163
1198
  let resolved = false;
1164
- if (this.options.useStdio) {
1199
+ if (this.connectionConfig.kind === "stdio") {
1165
1200
  resolved = true;
1166
1201
  resolve();
1167
1202
  } else {
@@ -1169,7 +1204,7 @@ class CopilotClient {
1169
1204
  stdout += data.toString();
1170
1205
  const match = stdout.match(/listening on port (\d+)/i);
1171
1206
  if (match && !resolved) {
1172
- this.actualPort = parseInt(match[1], 10);
1207
+ this.runtimePort = parseInt(match[1], 10);
1173
1208
  resolved = true;
1174
1209
  resolve();
1175
1210
  }
@@ -1243,19 +1278,21 @@ stderr: ${stderrOutput}`
1243
1278
  resolved = true;
1244
1279
  reject(new Error("Timeout waiting for CLI server to start"));
1245
1280
  }
1246
- }, 1e4);
1281
+ }, 3e4);
1247
1282
  });
1248
1283
  }
1249
1284
  /**
1250
1285
  * Connect to the CLI server (via socket or stdio)
1251
1286
  */
1252
1287
  async connectToServer() {
1253
- if (this.options.isChildProcess) {
1254
- return this.connectToParentProcessViaStdio();
1255
- } else if (this.options.useStdio) {
1256
- return this.connectToChildProcessViaStdio();
1257
- } else {
1258
- return this.connectViaTcp();
1288
+ switch (this.connectionConfig.kind) {
1289
+ case "parent-process":
1290
+ return this.connectToParentProcessViaStdio();
1291
+ case "stdio":
1292
+ return this.connectToChildProcessViaStdio();
1293
+ case "tcp":
1294
+ case "uri":
1295
+ return this.connectViaTcp();
1259
1296
  }
1260
1297
  }
1261
1298
  /**
@@ -1295,12 +1332,17 @@ stderr: ${stderrOutput}`
1295
1332
  * Connect to the CLI server via TCP socket
1296
1333
  */
1297
1334
  async connectViaTcp() {
1298
- if (!this.actualPort) {
1335
+ if (!this.runtimePort) {
1299
1336
  throw new Error("Server port not available");
1300
1337
  }
1301
1338
  return new Promise((resolve, reject) => {
1302
1339
  this.socket = new Socket();
1303
- this.socket.connect(this.actualPort, this.actualHost, () => {
1340
+ const connectionTimeout = setTimeout(() => {
1341
+ this.socket?.destroy();
1342
+ reject(new Error("Timeout connecting to CLI server"));
1343
+ }, 1e4);
1344
+ this.socket.connect(this.runtimePort, this.actualHost, () => {
1345
+ clearTimeout(connectionTimeout);
1304
1346
  this.connection = createMessageConnection(
1305
1347
  new StreamMessageReader(this.socket),
1306
1348
  new StreamMessageWriter(this.socket)
@@ -1310,6 +1352,7 @@ stderr: ${stderrOutput}`
1310
1352
  resolve();
1311
1353
  });
1312
1354
  this.socket.on("error", (error) => {
1355
+ clearTimeout(connectionTimeout);
1313
1356
  reject(new Error(`Failed to connect to CLI server: ${error.message}`));
1314
1357
  });
1315
1358
  });
@@ -1378,7 +1421,20 @@ stderr: ${stderrOutput}`
1378
1421
  if (typeof notification !== "object" || !notification || !("type" in notification) || typeof notification.type !== "string" || !("sessionId" in notification) || typeof notification.sessionId !== "string") {
1379
1422
  return;
1380
1423
  }
1381
- const event = notification;
1424
+ const raw = notification;
1425
+ let metadata;
1426
+ if (raw.metadata && raw.metadata.startTime && raw.metadata.modifiedTime) {
1427
+ metadata = {
1428
+ startTime: new Date(raw.metadata.startTime),
1429
+ modifiedTime: new Date(raw.metadata.modifiedTime),
1430
+ summary: raw.metadata.summary
1431
+ };
1432
+ }
1433
+ const event = {
1434
+ type: raw.type,
1435
+ sessionId: raw.sessionId,
1436
+ metadata
1437
+ };
1382
1438
  const typedHandlers = this.typedLifecycleHandlers.get(event.type);
1383
1439
  if (typedHandlers) {
1384
1440
  for (const handler of typedHandlers) {