@github/copilot-sdk 1.0.0-beta.5 → 1.0.0-beta.7

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.
@@ -30,18 +30,14 @@ var import_node_path = require("node:path");
30
30
  var import_node_url = require("node:url");
31
31
  var import_node = require("vscode-jsonrpc/node.js");
32
32
  var import_rpc = require("./generated/rpc.js");
33
+ var import_canvas = require("./canvas.js");
33
34
  var import_sdkProtocolVersion = require("./sdkProtocolVersion.js");
34
35
  var import_session = require("./session.js");
35
36
  var import_sessionFsProvider = require("./sessionFsProvider.js");
36
37
  var import_telemetry = require("./telemetry.js");
37
38
  var import_types = require("./types.js");
38
39
  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
- const MIN_PROTOCOL_VERSION = 2;
40
+ const MIN_PROTOCOL_VERSION = 3;
45
41
  function isZodSchema(value) {
46
42
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
47
43
  }
@@ -52,6 +48,36 @@ function toJsonSchema(parameters) {
52
48
  }
53
49
  return parameters;
54
50
  }
51
+ function toWireMcpServers(mcpServers) {
52
+ if (!mcpServers) return void 0;
53
+ return Object.fromEntries(
54
+ Object.entries(mcpServers).map(([name, server]) => {
55
+ if ("workingDirectory" in server) {
56
+ const { workingDirectory, ...rest } = server;
57
+ return [name, { ...rest, cwd: workingDirectory }];
58
+ }
59
+ return [name, server];
60
+ })
61
+ );
62
+ }
63
+ function toWireCustomAgents(agents) {
64
+ if (!agents) return void 0;
65
+ return agents.map((agent) => {
66
+ if (!agent.mcpServers) return agent;
67
+ const { mcpServers, ...rest } = agent;
68
+ return { ...rest, mcpServers: toWireMcpServers(mcpServers) };
69
+ });
70
+ }
71
+ function isCanvasProviderRequestParams(params) {
72
+ if (!params || typeof params !== "object") {
73
+ return false;
74
+ }
75
+ const request = params;
76
+ return typeof request.sessionId === "string" && typeof request.extensionId === "string" && typeof request.canvasId === "string" && typeof request.instanceId === "string";
77
+ }
78
+ function isCanvasActionInvokeParams(params) {
79
+ return isCanvasProviderRequestParams(params) && typeof params.actionName === "string";
80
+ }
55
81
  function extractTransformCallbacks(systemMessage) {
56
82
  if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
57
83
  return { wirePayload: systemMessage, transformCallbacks: void 0 };
@@ -105,12 +131,18 @@ class CopilotClient {
105
131
  cliProcess = null;
106
132
  connection = null;
107
133
  socket = null;
108
- actualPort = null;
134
+ runtimePort = null;
109
135
  actualHost = "localhost";
110
136
  state = "disconnected";
111
137
  sessions = /* @__PURE__ */ new Map();
112
138
  stderrBuffer = "";
113
139
  // Captures CLI stderr for error messages
140
+ /** Resolved connection mode chosen in the constructor. */
141
+ connectionConfig;
142
+ /** Resolved path to the runtime executable (only used for child-process kinds). */
143
+ resolvedCliPath;
144
+ /** Resolved environment passed to the spawned runtime. */
145
+ resolvedEnv;
114
146
  options;
115
147
  isExternalServer = false;
116
148
  forceStopping = false;
@@ -159,86 +191,79 @@ class CopilotClient {
159
191
  * Creates a new CopilotClient instance.
160
192
  *
161
193
  * @param options - Configuration options for the client
162
- * @throws Error if mutually exclusive options are provided (e.g., cliUrl with useStdio or cliPath)
163
194
  *
164
195
  * @example
165
196
  * ```typescript
166
- * // Default options - spawns CLI server using stdio
197
+ * // Default: spawns the bundled runtime over stdio
167
198
  * const client = new CopilotClient();
168
199
  *
169
- * // Connect to an existing server
170
- * const client = new CopilotClient({ cliUrl: "localhost:3000" });
200
+ * // Connect to an existing runtime
201
+ * const client = new CopilotClient({
202
+ * connection: RuntimeConnection.forUri("localhost:3000"),
203
+ * });
204
+ *
205
+ * // Spawn the runtime over TCP on a chosen port
206
+ * const client = new CopilotClient({
207
+ * connection: RuntimeConnection.forTcp({ port: 9001 }),
208
+ * });
171
209
  *
172
- * // Custom CLI path with specific log level
210
+ * // Use a custom runtime binary
173
211
  * const client = new CopilotClient({
174
- * cliPath: "/usr/local/bin/copilot",
175
- * logLevel: "debug"
212
+ * connection: RuntimeConnection.forStdio({ path: "/usr/local/bin/copilot" }),
213
+ * logLevel: "debug",
176
214
  * });
177
215
  * ```
178
216
  */
179
217
  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)) {
184
- throw new Error(
185
- "isChildProcess must be used in conjunction with useStdio and not with cliUrl"
186
- );
187
- }
188
- if (options.cliUrl && (options.gitHubToken || options.useLoggedInUser !== void 0)) {
218
+ const conn = options._internalConnection ?? options.connection ?? { kind: "stdio" };
219
+ if (conn.kind === "uri" && (options.gitHubToken !== void 0 || options.useLoggedInUser !== void 0)) {
189
220
  throw new Error(
190
- "gitHubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
221
+ "gitHubToken and useLoggedInUser cannot be used with RuntimeConnection.forUri (external server manages its own auth)"
191
222
  );
192
223
  }
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");
224
+ if (conn.kind === "tcp" && conn.connectionToken !== void 0) {
225
+ if (typeof conn.connectionToken !== "string" || conn.connectionToken.length === 0) {
226
+ throw new Error("connectionToken must be a non-empty string");
199
227
  }
200
228
  }
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);
229
+ this.connectionConfig = conn;
204
230
  if (options.sessionFs) {
205
231
  this.validateSessionFsConfig(options.sessionFs);
206
232
  }
207
- if (options.cliUrl) {
208
- const { host, port } = this.parseCliUrl(options.cliUrl);
233
+ if (conn.kind === "uri") {
234
+ const { host, port } = this.parseCliUrl(conn.url);
209
235
  this.actualHost = host;
210
- this.actualPort = port;
236
+ this.runtimePort = port;
211
237
  this.isExternalServer = true;
212
- }
213
- if (options.isChildProcess) {
238
+ } else if (conn.kind === "parent-process") {
214
239
  this.isExternalServer = true;
215
240
  }
241
+ if (conn.kind === "tcp") {
242
+ this.effectiveConnectionToken = conn.connectionToken ?? (0, import_node_crypto.randomUUID)();
243
+ } else if (conn.kind === "uri") {
244
+ this.effectiveConnectionToken = conn.connectionToken;
245
+ }
216
246
  this.onListModels = options.onListModels;
217
247
  this.onGetTraceContext = options.onGetTraceContext;
218
248
  this.sessionFsConfig = options.sessionFs ?? null;
219
249
  const effectiveEnv = options.env ?? process.env;
250
+ this.resolvedEnv = effectiveEnv;
251
+ this.resolvedCliPath = conn.kind === "stdio" || conn.kind === "tcp" ? conn.path ?? effectiveEnv.COPILOT_CLI_PATH ?? getBundledCliPath() : void 0;
252
+ const connArgs = conn.kind === "stdio" || conn.kind === "tcp" ? conn.args ?? [] : [];
253
+ this.connectionExtraArgs = [...connArgs];
220
254
  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,
255
+ workingDirectory: options.workingDirectory ?? process.cwd(),
256
+ logLevel: options.logLevel,
233
257
  gitHubToken: options.gitHubToken,
234
- // Default useLoggedInUser to false when gitHubToken is provided, otherwise true
258
+ // Default useLoggedInUser to false when gitHubToken is provided, otherwise true.
235
259
  useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true),
236
260
  telemetry: options.telemetry,
237
- copilotHome: options.copilotHome,
261
+ baseDirectory: options.baseDirectory,
238
262
  sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
239
- remote: options.remote ?? false
263
+ enableRemoteSessions: options.enableRemoteSessions ?? false
240
264
  };
241
265
  }
266
+ connectionExtraArgs = [];
242
267
  /**
243
268
  * Parse CLI URL into host and port
244
269
  * Supports formats: "host:port", "http://host:port", "https://host:port", or just "port"
@@ -276,12 +301,12 @@ class CopilotClient {
276
301
  if (!this.sessionFsConfig) {
277
302
  return;
278
303
  }
279
- if (!config.createSessionFsHandler) {
304
+ if (!config.createSessionFsProvider) {
280
305
  throw new Error(
281
- "createSessionFsHandler is required in session config when sessionFs is enabled in client options."
306
+ "createSessionFsProvider is required in session config when sessionFs is enabled in client options."
282
307
  );
283
308
  }
284
- const provider = config.createSessionFsHandler(session);
309
+ const provider = config.createSessionFsProvider(session);
285
310
  if (this.sessionFsConfig.capabilities?.sqlite && !provider.sqlite) {
286
311
  throw new Error(
287
312
  "SessionFsConfig declares capabilities.sqlite but the provider does not implement sqlite."
@@ -295,14 +320,14 @@ class CopilotClient {
295
320
  * If connecting to an external server (via cliUrl), only establishes the connection.
296
321
  * Otherwise, spawns the CLI server process and then connects.
297
322
  *
298
- * This method is called automatically when creating a session if `autoStart` is true (default).
323
+ * This method is called automatically the first time you create or resume a session.
299
324
  *
300
325
  * @returns A promise that resolves when the connection is established
301
326
  * @throws Error if the server fails to start or the connection fails
302
327
  *
303
328
  * @example
304
329
  * ```typescript
305
- * const client = new CopilotClient({ autoStart: false });
330
+ * const client = new CopilotClient();
306
331
  * await client.start();
307
332
  * // Now ready to create sessions
308
333
  * ```
@@ -397,8 +422,15 @@ class CopilotClient {
397
422
  }
398
423
  this.modelsCache = null;
399
424
  if (this.socket) {
425
+ const socket = this.socket;
426
+ this.socket = null;
400
427
  try {
401
- this.socket.end();
428
+ if (!socket.destroyed) {
429
+ await new Promise((resolve) => {
430
+ socket.once("close", () => resolve());
431
+ socket.end();
432
+ });
433
+ }
402
434
  } catch (error) {
403
435
  errors.push(
404
436
  new Error(
@@ -406,11 +438,18 @@ class CopilotClient {
406
438
  )
407
439
  );
408
440
  }
409
- this.socket = null;
410
441
  }
411
442
  if (this.cliProcess && !this.isExternalServer) {
443
+ const child = this.cliProcess;
444
+ this.cliProcess = null;
412
445
  try {
413
- this.cliProcess.kill();
446
+ if (child.exitCode === null && child.signalCode === null) {
447
+ const exited = new Promise((resolve) => {
448
+ child.once("exit", () => resolve());
449
+ });
450
+ child.kill();
451
+ await exited;
452
+ }
414
453
  } catch (error) {
415
454
  errors.push(
416
455
  new Error(
@@ -418,18 +457,32 @@ class CopilotClient {
418
457
  )
419
458
  );
420
459
  }
421
- this.cliProcess = null;
422
460
  }
423
461
  if (this.cliStartTimeout) {
424
462
  clearTimeout(this.cliStartTimeout);
425
463
  this.cliStartTimeout = null;
426
464
  }
427
465
  this.state = "disconnected";
428
- this.actualPort = null;
466
+ this.runtimePort = null;
429
467
  this.stderrBuffer = "";
430
468
  this.processExitPromise = null;
431
469
  return errors;
432
470
  }
471
+ /**
472
+ * Alias for {@link stop} that lets `CopilotClient` participate in `await using`
473
+ * blocks for automatic cleanup.
474
+ *
475
+ * @example
476
+ * ```typescript
477
+ * await using client = new CopilotClient();
478
+ * const session = await client.createSession({ onPermissionRequest: approveAll });
479
+ * await session.sendAndWait("Hello");
480
+ * // client.stop() is called automatically when the block exits.
481
+ * ```
482
+ */
483
+ async [Symbol.asyncDispose]() {
484
+ await this.stop();
485
+ }
433
486
  /**
434
487
  * Forcefully stops the CLI server without graceful cleanup.
435
488
  *
@@ -486,7 +539,7 @@ class CopilotClient {
486
539
  this.cliStartTimeout = null;
487
540
  }
488
541
  this.state = "disconnected";
489
- this.actualPort = null;
542
+ this.runtimePort = null;
490
543
  this.stderrBuffer = "";
491
544
  this.processExitPromise = null;
492
545
  }
@@ -494,12 +547,11 @@ class CopilotClient {
494
547
  * Creates a new conversation session with the Copilot CLI.
495
548
  *
496
549
  * Sessions maintain conversation state, handle events, and manage tool execution.
497
- * If the client is not connected and `autoStart` is enabled, this will automatically
498
- * start the connection.
550
+ * If the client is not connected, this method automatically starts the connection.
499
551
  *
500
552
  * @param config - Optional configuration for the session
501
553
  * @returns A promise that resolves with the created session
502
- * @throws Error if the client is not connected and autoStart is disabled
554
+ * @throws Error if the client fails to start
503
555
  *
504
556
  * @example
505
557
  * ```typescript
@@ -521,11 +573,7 @@ class CopilotClient {
521
573
  */
522
574
  async createSession(config) {
523
575
  if (!this.connection) {
524
- if (this.options.autoStart) {
525
- await this.start();
526
- } else {
527
- throw new Error("Client not connected. Call start() first.");
528
- }
576
+ await this.start();
529
577
  }
530
578
  const sessionId = config.sessionId ?? (0, import_node_crypto.randomUUID)();
531
579
  const session = new import_session.CopilotSession(
@@ -535,6 +583,7 @@ class CopilotClient {
535
583
  this.onGetTraceContext
536
584
  );
537
585
  session.registerTools(config.tools);
586
+ session.registerCanvases(config.canvases);
538
587
  session.registerCommands(config.commands);
539
588
  session.registerPermissionHandler(config.onPermissionRequest);
540
589
  if (config.onUserInputRequest) {
@@ -543,11 +592,11 @@ class CopilotClient {
543
592
  if (config.onElicitationRequest) {
544
593
  session.registerElicitationHandler(config.onElicitationRequest);
545
594
  }
546
- if (config.onExitPlanMode) {
547
- session.registerExitPlanModeHandler(config.onExitPlanMode);
595
+ if (config.onExitPlanModeRequest) {
596
+ session.registerExitPlanModeHandler(config.onExitPlanModeRequest);
548
597
  }
549
- if (config.onAutoModeSwitch) {
550
- session.registerAutoModeSwitchHandler(config.onAutoModeSwitch);
598
+ if (config.onAutoModeSwitchRequest) {
599
+ session.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
551
600
  }
552
601
  if (config.hooks) {
553
602
  session.registerHooks(config.hooks);
@@ -577,6 +626,10 @@ class CopilotClient {
577
626
  overridesBuiltInTool: tool.overridesBuiltInTool,
578
627
  skipPermission: tool.skipPermission
579
628
  })),
629
+ canvases: config.canvases?.map((canvas) => canvas.declaration),
630
+ requestCanvasRenderer: config.requestCanvasRenderer,
631
+ requestExtensions: config.requestExtensions,
632
+ extensionInfo: config.extensionInfo,
580
633
  commands: config.commands?.map((cmd) => ({
581
634
  name: cmd.name,
582
635
  description: cmd.description
@@ -584,21 +637,21 @@ class CopilotClient {
584
637
  systemMessage: wireSystemMessage,
585
638
  availableTools: config.availableTools,
586
639
  excludedTools: config.excludedTools,
587
- provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
640
+ provider: config.provider,
588
641
  enableSessionTelemetry: config.enableSessionTelemetry,
589
642
  modelCapabilities: config.modelCapabilities,
590
- requestPermission: true,
643
+ requestPermission: !!config.onPermissionRequest,
591
644
  requestUserInput: !!config.onUserInputRequest,
592
645
  requestElicitation: !!config.onElicitationRequest,
593
- requestExitPlanMode: !!config.onExitPlanMode,
594
- requestAutoModeSwitch: !!config.onAutoModeSwitch,
646
+ requestExitPlanMode: !!config.onExitPlanModeRequest,
647
+ requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
595
648
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
596
649
  workingDirectory: config.workingDirectory,
597
650
  streaming: config.streaming,
598
651
  includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
599
- mcpServers: config.mcpServers,
652
+ mcpServers: toWireMcpServers(config.mcpServers),
600
653
  envValueMode: "direct",
601
- customAgents: config.customAgents,
654
+ customAgents: toWireCustomAgents(config.customAgents),
602
655
  defaultAgent: config.defaultAgent,
603
656
  agent: config.agent,
604
657
  configDir: config.configDir,
@@ -646,11 +699,7 @@ class CopilotClient {
646
699
  */
647
700
  async resumeSession(sessionId, config) {
648
701
  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
- }
702
+ await this.start();
654
703
  }
655
704
  const session = new import_session.CopilotSession(
656
705
  sessionId,
@@ -659,6 +708,7 @@ class CopilotClient {
659
708
  this.onGetTraceContext
660
709
  );
661
710
  session.registerTools(config.tools);
711
+ session.registerCanvases(config.canvases);
662
712
  session.registerCommands(config.commands);
663
713
  session.registerPermissionHandler(config.onPermissionRequest);
664
714
  if (config.onUserInputRequest) {
@@ -667,11 +717,11 @@ class CopilotClient {
667
717
  if (config.onElicitationRequest) {
668
718
  session.registerElicitationHandler(config.onElicitationRequest);
669
719
  }
670
- if (config.onExitPlanMode) {
671
- session.registerExitPlanModeHandler(config.onExitPlanMode);
720
+ if (config.onExitPlanModeRequest) {
721
+ session.registerExitPlanModeHandler(config.onExitPlanModeRequest);
672
722
  }
673
- if (config.onAutoModeSwitch) {
674
- session.registerAutoModeSwitchHandler(config.onAutoModeSwitch);
723
+ if (config.onAutoModeSwitchRequest) {
724
+ session.registerAutoModeSwitchHandler(config.onAutoModeSwitchRequest);
675
725
  }
676
726
  if (config.hooks) {
677
727
  session.registerHooks(config.hooks);
@@ -705,61 +755,52 @@ class CopilotClient {
705
755
  overridesBuiltInTool: tool.overridesBuiltInTool,
706
756
  skipPermission: tool.skipPermission
707
757
  })),
758
+ canvases: config.canvases?.map((canvas) => canvas.declaration),
759
+ requestCanvasRenderer: config.requestCanvasRenderer,
760
+ requestExtensions: config.requestExtensions,
761
+ extensionInfo: config.extensionInfo,
708
762
  commands: config.commands?.map((cmd) => ({
709
763
  name: cmd.name,
710
764
  description: cmd.description
711
765
  })),
712
- provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
766
+ provider: config.provider,
713
767
  modelCapabilities: config.modelCapabilities,
714
768
  requestPermission: config.onPermissionRequest !== import_types.defaultJoinSessionPermissionHandler,
715
769
  requestUserInput: !!config.onUserInputRequest,
716
770
  requestElicitation: !!config.onElicitationRequest,
717
- requestExitPlanMode: !!config.onExitPlanMode,
718
- requestAutoModeSwitch: !!config.onAutoModeSwitch,
771
+ requestExitPlanMode: !!config.onExitPlanModeRequest,
772
+ requestAutoModeSwitch: !!config.onAutoModeSwitchRequest,
719
773
  hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
720
774
  workingDirectory: config.workingDirectory,
721
775
  configDir: config.configDir,
722
776
  enableConfigDiscovery: config.enableConfigDiscovery,
723
777
  streaming: config.streaming,
724
778
  includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true,
725
- mcpServers: config.mcpServers,
779
+ mcpServers: toWireMcpServers(config.mcpServers),
726
780
  envValueMode: "direct",
727
- customAgents: config.customAgents,
781
+ customAgents: toWireCustomAgents(config.customAgents),
728
782
  defaultAgent: config.defaultAgent,
729
783
  agent: config.agent,
730
784
  skillDirectories: config.skillDirectories,
731
785
  instructionDirectories: config.instructionDirectories,
732
786
  disabledSkills: config.disabledSkills,
733
787
  infiniteSessions: config.infiniteSessions,
734
- disableResume: config.disableResume,
788
+ suppressResumeEvent: config.suppressResumeEvent,
735
789
  continuePendingWork: config.continuePendingWork,
736
790
  gitHubToken: config.gitHubToken,
737
- remoteSession: config.remoteSession
791
+ remoteSession: config.remoteSession,
792
+ openCanvases: config.openCanvases
738
793
  });
739
- const { workspacePath, capabilities } = response;
794
+ const { workspacePath, capabilities, openCanvases } = response;
740
795
  session["_workspacePath"] = workspacePath;
741
796
  session.setCapabilities(capabilities);
797
+ session.setOpenCanvases(openCanvases ?? []);
742
798
  } catch (e) {
743
799
  this.sessions.delete(sessionId);
744
800
  throw e;
745
801
  }
746
802
  return session;
747
803
  }
748
- /**
749
- * Gets the current connection state of the client.
750
- *
751
- * @returns The current connection state: "disconnected", "connecting", "connected", or "error"
752
- *
753
- * @example
754
- * ```typescript
755
- * if (client.getState() === "connected") {
756
- * const session = await client.createSession({ onPermissionRequest: approveAll });
757
- * }
758
- * ```
759
- */
760
- getState() {
761
- return this.state;
762
- }
763
804
  /**
764
805
  * Sends a ping request to the server to verify connectivity.
765
806
  *
@@ -961,8 +1002,13 @@ class CopilotClient {
961
1002
  if (!this.connection) {
962
1003
  throw new Error("Client not connected");
963
1004
  }
1005
+ let wireFilter;
1006
+ if (filter) {
1007
+ const { workingDirectory, ...rest } = filter;
1008
+ wireFilter = { ...rest, cwd: workingDirectory };
1009
+ }
964
1010
  const response = await this.connection.sendRequest("session.list", {
965
- filter
1011
+ filter: wireFilter
966
1012
  });
967
1013
  const { sessions } = response;
968
1014
  return sessions.map(CopilotClient.toSessionMetadata);
@@ -997,13 +1043,19 @@ class CopilotClient {
997
1043
  return CopilotClient.toSessionMetadata(session);
998
1044
  }
999
1045
  static toSessionMetadata(raw) {
1046
+ const { context } = raw;
1000
1047
  return {
1001
1048
  sessionId: raw.sessionId,
1002
1049
  startTime: new Date(raw.startTime),
1003
1050
  modifiedTime: new Date(raw.modifiedTime),
1004
1051
  summary: raw.summary,
1005
1052
  isRemote: raw.isRemote,
1006
- context: raw.context
1053
+ context: context ? {
1054
+ workingDirectory: context.cwd,
1055
+ gitRoot: context.gitRoot,
1056
+ repository: context.repository,
1057
+ branch: context.branch
1058
+ } : void 0
1007
1059
  };
1008
1060
  }
1009
1061
  /**
@@ -1056,7 +1108,7 @@ class CopilotClient {
1056
1108
  throw new Error(result.error || "Failed to set foreground session");
1057
1109
  }
1058
1110
  }
1059
- on(eventTypeOrHandler, handler) {
1111
+ onLifecycle(eventTypeOrHandler, handler) {
1060
1112
  if (typeof eventTypeOrHandler === "string" && handler) {
1061
1113
  const eventType = eventTypeOrHandler;
1062
1114
  if (!this.typedLifecycleHandlers.has(eventType)) {
@@ -1083,17 +1135,17 @@ class CopilotClient {
1083
1135
  async startCLIServer() {
1084
1136
  return new Promise((resolve, reject) => {
1085
1137
  this.stderrBuffer = "";
1086
- const args = [
1087
- ...this.options.cliArgs,
1088
- "--headless",
1089
- "--no-auto-update",
1090
- "--log-level",
1091
- this.options.logLevel
1092
- ];
1093
- if (this.options.useStdio) {
1138
+ const args = [...this.connectionExtraArgs, "--headless", "--no-auto-update"];
1139
+ if (this.options.logLevel) {
1140
+ args.push("--log-level", this.options.logLevel);
1141
+ }
1142
+ if (this.connectionConfig.kind === "stdio") {
1094
1143
  args.push("--stdio");
1095
- } else if (this.options.port > 0) {
1096
- args.push("--port", this.options.port.toString());
1144
+ } else if (this.connectionConfig.kind === "tcp") {
1145
+ const requestedPort = this.connectionConfig.port ?? 0;
1146
+ if (requestedPort > 0) {
1147
+ args.push("--port", requestedPort.toString());
1148
+ }
1097
1149
  }
1098
1150
  if (this.options.gitHubToken) {
1099
1151
  args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN");
@@ -1107,10 +1159,10 @@ class CopilotClient {
1107
1159
  this.options.sessionIdleTimeoutSeconds.toString()
1108
1160
  );
1109
1161
  }
1110
- if (this.options.remote) {
1162
+ if (this.options.enableRemoteSessions) {
1111
1163
  args.push("--remote");
1112
1164
  }
1113
- const envWithoutNodeDebug = { ...this.options.env };
1165
+ const envWithoutNodeDebug = { ...this.resolvedEnv };
1114
1166
  delete envWithoutNodeDebug.NODE_DEBUG;
1115
1167
  if (this.options.gitHubToken) {
1116
1168
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
@@ -1118,12 +1170,12 @@ class CopilotClient {
1118
1170
  if (this.effectiveConnectionToken) {
1119
1171
  envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
1120
1172
  }
1121
- if (this.options.copilotHome) {
1122
- envWithoutNodeDebug.COPILOT_HOME = this.options.copilotHome;
1173
+ if (this.options.baseDirectory) {
1174
+ envWithoutNodeDebug.COPILOT_HOME = this.options.baseDirectory;
1123
1175
  }
1124
- if (!this.options.cliPath) {
1176
+ if (!this.resolvedCliPath) {
1125
1177
  throw new Error(
1126
- "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
1178
+ "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."
1127
1179
  );
1128
1180
  }
1129
1181
  if (this.options.telemetry) {
@@ -1142,31 +1194,31 @@ class CopilotClient {
1142
1194
  t.captureContent
1143
1195
  );
1144
1196
  }
1145
- if (!(0, import_node_fs.existsSync)(this.options.cliPath)) {
1197
+ if (!(0, import_node_fs.existsSync)(this.resolvedCliPath)) {
1146
1198
  throw new Error(
1147
- `Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
1199
+ `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.`
1148
1200
  );
1149
1201
  }
1150
- const stdioConfig = this.options.useStdio ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
1151
- const isJsFile = this.options.cliPath.endsWith(".js");
1202
+ const stdioConfig = this.connectionConfig.kind === "stdio" ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
1203
+ const isJsFile = this.resolvedCliPath.endsWith(".js");
1152
1204
  if (isJsFile) {
1153
- this.cliProcess = (0, import_node_child_process.spawn)(getNodeExecPath(), [this.options.cliPath, ...args], {
1205
+ this.cliProcess = (0, import_node_child_process.spawn)(getNodeExecPath(), [this.resolvedCliPath, ...args], {
1154
1206
  stdio: stdioConfig,
1155
- cwd: this.options.cwd,
1207
+ cwd: this.options.workingDirectory,
1156
1208
  env: envWithoutNodeDebug,
1157
1209
  windowsHide: true
1158
1210
  });
1159
1211
  } else {
1160
- this.cliProcess = (0, import_node_child_process.spawn)(this.options.cliPath, args, {
1212
+ this.cliProcess = (0, import_node_child_process.spawn)(this.resolvedCliPath, args, {
1161
1213
  stdio: stdioConfig,
1162
- cwd: this.options.cwd,
1214
+ cwd: this.options.workingDirectory,
1163
1215
  env: envWithoutNodeDebug,
1164
1216
  windowsHide: true
1165
1217
  });
1166
1218
  }
1167
1219
  let stdout = "";
1168
1220
  let resolved = false;
1169
- if (this.options.useStdio) {
1221
+ if (this.connectionConfig.kind === "stdio") {
1170
1222
  resolved = true;
1171
1223
  resolve();
1172
1224
  } else {
@@ -1174,7 +1226,7 @@ class CopilotClient {
1174
1226
  stdout += data.toString();
1175
1227
  const match = stdout.match(/listening on port (\d+)/i);
1176
1228
  if (match && !resolved) {
1177
- this.actualPort = parseInt(match[1], 10);
1229
+ this.runtimePort = parseInt(match[1], 10);
1178
1230
  resolved = true;
1179
1231
  resolve();
1180
1232
  }
@@ -1248,19 +1300,21 @@ stderr: ${stderrOutput}`
1248
1300
  resolved = true;
1249
1301
  reject(new Error("Timeout waiting for CLI server to start"));
1250
1302
  }
1251
- }, 1e4);
1303
+ }, 3e4);
1252
1304
  });
1253
1305
  }
1254
1306
  /**
1255
1307
  * Connect to the CLI server (via socket or stdio)
1256
1308
  */
1257
1309
  async connectToServer() {
1258
- if (this.options.isChildProcess) {
1259
- return this.connectToParentProcessViaStdio();
1260
- } else if (this.options.useStdio) {
1261
- return this.connectToChildProcessViaStdio();
1262
- } else {
1263
- return this.connectViaTcp();
1310
+ switch (this.connectionConfig.kind) {
1311
+ case "parent-process":
1312
+ return this.connectToParentProcessViaStdio();
1313
+ case "stdio":
1314
+ return this.connectToChildProcessViaStdio();
1315
+ case "tcp":
1316
+ case "uri":
1317
+ return this.connectViaTcp();
1264
1318
  }
1265
1319
  }
1266
1320
  /**
@@ -1300,7 +1354,7 @@ stderr: ${stderrOutput}`
1300
1354
  * Connect to the CLI server via TCP socket
1301
1355
  */
1302
1356
  async connectViaTcp() {
1303
- if (!this.actualPort) {
1357
+ if (!this.runtimePort) {
1304
1358
  throw new Error("Server port not available");
1305
1359
  }
1306
1360
  return new Promise((resolve, reject) => {
@@ -1309,7 +1363,7 @@ stderr: ${stderrOutput}`
1309
1363
  this.socket?.destroy();
1310
1364
  reject(new Error("Timeout connecting to CLI server"));
1311
1365
  }, 1e4);
1312
- this.socket.connect(this.actualPort, this.actualHost, () => {
1366
+ this.socket.connect(this.runtimePort, this.actualHost, () => {
1313
1367
  clearTimeout(connectionTimeout);
1314
1368
  this.connection = (0, import_node.createMessageConnection)(
1315
1369
  new import_node.StreamMessageReader(this.socket),
@@ -1335,14 +1389,6 @@ stderr: ${stderrOutput}`
1335
1389
  this.connection.onNotification("session.lifecycle", (notification) => {
1336
1390
  this.handleSessionLifecycleNotification(notification);
1337
1391
  });
1338
- this.connection.onRequest(
1339
- "tool.call",
1340
- async (params) => await this.handleToolCallRequestV2(params)
1341
- );
1342
- this.connection.onRequest(
1343
- "permission.request",
1344
- async (params) => await this.handlePermissionRequestV2(params)
1345
- );
1346
1392
  this.connection.onRequest(
1347
1393
  "userInput.request",
1348
1394
  async (params) => await this.handleUserInputRequest(params)
@@ -1363,6 +1409,18 @@ stderr: ${stderrOutput}`
1363
1409
  "systemMessage.transform",
1364
1410
  async (params) => await this.handleSystemMessageTransform(params)
1365
1411
  );
1412
+ this.connection.onRequest(
1413
+ "canvas.open",
1414
+ async (params) => this.handleCanvasProviderRequest("canvas.open", params)
1415
+ );
1416
+ this.connection.onRequest(
1417
+ "canvas.close",
1418
+ async (params) => this.handleCanvasProviderRequest("canvas.close", params)
1419
+ );
1420
+ this.connection.onRequest(
1421
+ "canvas.action.invoke",
1422
+ async (params) => this.handleCanvasActionInvokeRequest(params)
1423
+ );
1366
1424
  const sessions = this.sessions;
1367
1425
  (0, import_rpc.registerClientSessionApiHandlers)(this.connection, (sessionId) => {
1368
1426
  const session = sessions.get(sessionId);
@@ -1389,7 +1447,20 @@ stderr: ${stderrOutput}`
1389
1447
  if (typeof notification !== "object" || !notification || !("type" in notification) || typeof notification.type !== "string" || !("sessionId" in notification) || typeof notification.sessionId !== "string") {
1390
1448
  return;
1391
1449
  }
1392
- const event = notification;
1450
+ const raw = notification;
1451
+ let metadata;
1452
+ if (raw.metadata && raw.metadata.startTime && raw.metadata.modifiedTime) {
1453
+ metadata = {
1454
+ startTime: new Date(raw.metadata.startTime),
1455
+ modifiedTime: new Date(raw.metadata.modifiedTime),
1456
+ summary: raw.metadata.summary
1457
+ };
1458
+ }
1459
+ const event = {
1460
+ type: raw.type,
1461
+ sessionId: raw.sessionId,
1462
+ metadata
1463
+ };
1393
1464
  const typedHandlers = this.typedLifecycleHandlers.get(event.type);
1394
1465
  if (typedHandlers) {
1395
1466
  for (const handler of typedHandlers) {
@@ -1471,104 +1542,25 @@ stderr: ${stderrOutput}`
1471
1542
  }
1472
1543
  return await session._handleSystemMessageTransform(params.sections);
1473
1544
  }
1474
- // ========================================================================
1475
- // Protocol v2 backward-compatibility adapters
1476
- // ========================================================================
1477
- /**
1478
- * Handles a v2-style tool.call RPC request from the server.
1479
- * Looks up the session and tool handler, executes it, and returns the result
1480
- * in the v2 response format.
1481
- */
1482
- async handleToolCallRequestV2(params) {
1483
- if (!params || typeof params.sessionId !== "string" || typeof params.toolCallId !== "string" || typeof params.toolName !== "string") {
1484
- throw new Error("Invalid tool call payload");
1485
- }
1486
- const session = this.sessions.get(params.sessionId);
1487
- if (!session) {
1488
- throw new Error(`Unknown session ${params.sessionId}`);
1489
- }
1490
- const handler = session.getToolHandler(params.toolName);
1491
- if (!handler) {
1492
- return {
1493
- result: {
1494
- textResultForLlm: `Tool '${params.toolName}' is not supported by this client instance.`,
1495
- resultType: "failure",
1496
- error: `tool '${params.toolName}' not supported`,
1497
- toolTelemetry: {}
1498
- }
1499
- };
1500
- }
1501
- try {
1502
- const traceparent = params.traceparent;
1503
- const tracestate = params.tracestate;
1504
- const invocation = {
1505
- sessionId: params.sessionId,
1506
- toolCallId: params.toolCallId,
1507
- toolName: params.toolName,
1508
- arguments: params.arguments,
1509
- traceparent,
1510
- tracestate
1511
- };
1512
- const result = await handler(params.arguments, invocation);
1513
- return { result: this.normalizeToolResultV2(result) };
1514
- } catch (error) {
1515
- const message = error instanceof Error ? error.message : String(error);
1516
- return {
1517
- result: {
1518
- textResultForLlm: "Invoking this tool produced an error. Detailed information is not available.",
1519
- resultType: "failure",
1520
- error: message,
1521
- toolTelemetry: {}
1522
- }
1523
- };
1524
- }
1525
- }
1526
- /**
1527
- * Handles a v2-style permission.request RPC request from the server.
1528
- */
1529
- async handlePermissionRequestV2(params) {
1530
- if (!params || typeof params.sessionId !== "string" || !params.permissionRequest) {
1531
- throw new Error("Invalid permission request payload");
1545
+ async handleCanvasProviderRequest(actionName, params) {
1546
+ if (!isCanvasProviderRequestParams(params)) {
1547
+ throw new Error("Invalid canvas provider request payload");
1532
1548
  }
1533
1549
  const session = this.sessions.get(params.sessionId);
1534
1550
  if (!session) {
1535
1551
  throw new Error(`Session not found: ${params.sessionId}`);
1536
1552
  }
1537
- try {
1538
- const result = await session._handlePermissionRequestV2(params.permissionRequest);
1539
- return { result };
1540
- } catch (error) {
1541
- if (error instanceof Error && error.message === import_session.NO_RESULT_PERMISSION_V2_ERROR) {
1542
- throw error;
1543
- }
1544
- return {
1545
- result: {
1546
- kind: "user-not-available"
1547
- }
1548
- };
1553
+ const canvas = session.getCanvas(params.canvasId);
1554
+ if (!canvas) {
1555
+ throw new Error(`No canvas registered with id "${params.canvasId}"`);
1549
1556
  }
1557
+ return (0, import_canvas.dispatchCanvasProviderRequest)(canvas, actionName, params);
1550
1558
  }
1551
- normalizeToolResultV2(result) {
1552
- if (result === void 0 || result === null) {
1553
- return {
1554
- textResultForLlm: "Tool returned no result",
1555
- resultType: "failure",
1556
- error: "tool returned no result",
1557
- toolTelemetry: {}
1558
- };
1559
- }
1560
- if (this.isToolResultObject(result)) {
1561
- return result;
1559
+ async handleCanvasActionInvokeRequest(params) {
1560
+ if (!isCanvasActionInvokeParams(params)) {
1561
+ throw new Error("Invalid canvas provider request payload");
1562
1562
  }
1563
- const textResult = typeof result === "string" ? result : JSON.stringify(result);
1564
- return {
1565
- textResultForLlm: textResult,
1566
- resultType: "success",
1567
- toolTelemetry: {}
1568
- };
1569
- }
1570
- isToolResultObject(value) {
1571
- return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
1563
+ return this.handleCanvasProviderRequest(params.actionName, params);
1572
1564
  }
1573
1565
  }
1574
1566
  // Annotate the CommonJS export names for ESM import in node: