@github/copilot-sdk 1.0.1 → 1.0.2-preview.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC.
4
4
 
5
+ ## Prerequisites
6
+
7
+ To use the SDK, you'll need:
8
+
9
+ - Node.js ^20.19.0 or >=22.12.0
10
+
5
11
  ## Installation
6
12
 
7
13
  ```bash
@@ -482,6 +488,21 @@ defineTool("safe_lookup", {
482
488
  });
483
489
  ```
484
490
 
491
+ #### Deferring Tools
492
+
493
+ Set `defer` to control whether a tool may be loaded lazily via tool search rather than always pre-loaded. Use `"auto"` to allow the tool to be deferred and surfaced through tool search, or `"never"` to force it to always be pre-loaded. Defaults to `"auto"`.
494
+
495
+ ```ts
496
+ defineTool("lookup_issue", {
497
+ description: "Fetch issue details",
498
+ parameters: z.object({ id: z.string() }),
499
+ defer: "auto",
500
+ handler: async ({ id }) => {
501
+ /* your logic */
502
+ },
503
+ });
504
+ ```
505
+
485
506
  ### Commands
486
507
 
487
508
  Register slash commands so that users of the CLI's TUI can invoke custom actions via `/commandName`. Each command has a `name`, optional `description`, and a `handler` called when the user executes it.
@@ -656,6 +677,28 @@ When enabled, sessions emit compaction events:
656
677
  - `session.compaction_start` - Background compaction started
657
678
  - `session.compaction_complete` - Compaction finished (includes token counts)
658
679
 
680
+ ### Memory
681
+
682
+ Sessions can opt in to the memory feature, which lets the agent persist and recall
683
+ information across turns. Provide a `memory` configuration on session create or resume;
684
+ when omitted, the runtime default applies. In the default `"copilot-cli"` client mode the
685
+ SDK leaves `memory` unset so the runtime applies its own default, while `"empty"` mode
686
+ defaults `memory` to disabled unless you set it explicitly.
687
+
688
+ ```typescript
689
+ // Enable memory for a session
690
+ const session = await client.createSession({
691
+ model: "gpt-5",
692
+ memory: { enabled: true },
693
+ });
694
+
695
+ // Disable memory for a session
696
+ const session = await client.createSession({
697
+ model: "gpt-5",
698
+ memory: { enabled: false },
699
+ });
700
+ ```
701
+
659
702
  ### Multiple Sessions
660
703
 
661
704
  ```typescript
@@ -771,6 +814,7 @@ With just this configuration, the CLI emits spans for every session, message, an
771
814
  **TelemetryConfig options:**
772
815
 
773
816
  - `otlpEndpoint?: string` - OTLP HTTP endpoint URL
817
+ - `otlpProtocol?: "http/json" | "http/protobuf"` - OTLP HTTP protocol for all signals
774
818
  - `filePath?: string` - File path for JSON-lines trace output
775
819
  - `exporterType?: string` - `"otlp-http"` or `"file"`
776
820
  - `sourceName?: string` - Instrumentation scope name
@@ -1033,11 +1077,6 @@ try {
1033
1077
  }
1034
1078
  ```
1035
1079
 
1036
- ## Requirements
1037
-
1038
- - Node.js ^20.19.0 or >=22.12.0
1039
- - GitHub Copilot CLI installed and in PATH (or provide a custom `connection`)
1040
-
1041
1080
  ## License
1042
1081
 
1043
1082
  MIT
package/dist/canvas.js CHANGED
@@ -4,6 +4,7 @@ class CanvasError extends Error {
4
4
  this.code = code;
5
5
  this.name = "CanvasError";
6
6
  }
7
+ code;
7
8
  /** Default error when an action is declared but no `handler` is wired. */
8
9
  static noHandler() {
9
10
  return new CanvasError(
@@ -29,6 +29,7 @@ class CanvasError extends Error {
29
29
  this.code = code;
30
30
  this.name = "CanvasError";
31
31
  }
32
+ code;
32
33
  /** Default error when an action is declared but no `handler` is wired. */
33
34
  static noHandler() {
34
35
  return new CanvasError(
@@ -38,9 +38,54 @@ var import_toolSet = require("./toolSet.js");
38
38
  var import_types = require("./types.js");
39
39
  const import_meta = {};
40
40
  const MIN_PROTOCOL_VERSION = 3;
41
+ const RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
41
42
  function isZodSchema(value) {
42
43
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
43
44
  }
45
+ async function withTimeout(promise, timeoutMs, message) {
46
+ let timeout;
47
+ try {
48
+ return await Promise.race([
49
+ promise,
50
+ new Promise((_, reject) => {
51
+ timeout = setTimeout(() => reject(new Error(message)), timeoutMs);
52
+ })
53
+ ]);
54
+ } finally {
55
+ if (timeout !== void 0) {
56
+ clearTimeout(timeout);
57
+ }
58
+ }
59
+ }
60
+ async function waitForChildExit(child, timeoutMs) {
61
+ if (child.exitCode != null || child.signalCode != null) {
62
+ return true;
63
+ }
64
+ return new Promise((resolve) => {
65
+ let timeout;
66
+ let settled = false;
67
+ const onExit = () => {
68
+ if (settled) {
69
+ return;
70
+ }
71
+ settled = true;
72
+ clearTimeout(timeout);
73
+ resolve(true);
74
+ };
75
+ timeout = setTimeout(() => {
76
+ if (settled) {
77
+ return;
78
+ }
79
+ settled = true;
80
+ child.off("exit", onExit);
81
+ resolve(false);
82
+ }, timeoutMs);
83
+ child.once("exit", onExit);
84
+ if (child.exitCode != null || child.signalCode != null) {
85
+ onExit();
86
+ }
87
+ });
88
+ }
44
89
  function toJsonSchema(parameters) {
45
90
  if (!parameters) return void 0;
46
91
  if (isZodSchema(parameters)) {
@@ -204,6 +249,13 @@ class CopilotClient {
204
249
  }
205
250
  return this._internalRpc;
206
251
  }
252
+ logDebugTiming(message, startMs) {
253
+ const level = this.options.logLevel?.toLowerCase();
254
+ if (level === "debug" || level === "all") {
255
+ process.stderr.write(`[copilot-sdk] ${message}. Elapsed=${Date.now() - startMs}ms
256
+ `);
257
+ }
258
+ }
207
259
  /**
208
260
  * Creates a new CopilotClient instance.
209
261
  *
@@ -390,8 +442,9 @@ class CopilotClient {
390
442
  *
391
443
  * This method performs graceful cleanup:
392
444
  * 1. Closes all active sessions (releases in-memory resources)
393
- * 2. Closes the JSON-RPC connection
394
- * 3. Terminates the CLI server process (if spawned by this client)
445
+ * 2. Requests runtime shutdown for SDK-owned CLI processes
446
+ * 3. Closes the JSON-RPC connection
447
+ * 4. Terminates the CLI server process (if spawned by this client)
395
448
  *
396
449
  * Note: session data on disk is preserved, so sessions can be resumed later.
397
450
  * To permanently remove session data before stopping, call
@@ -435,6 +488,34 @@ class CopilotClient {
435
488
  }
436
489
  }
437
490
  this.sessions.clear();
491
+ let runtimeShutdownCompleted = false;
492
+ if (this.connection && this.cliProcess && !this.isExternalServer) {
493
+ const runtimeShutdownStart = Date.now();
494
+ const shutdownPromise = this.rpc.runtime.shutdown();
495
+ void shutdownPromise.catch(() => void 0);
496
+ try {
497
+ await withTimeout(
498
+ shutdownPromise,
499
+ RUNTIME_SHUTDOWN_TIMEOUT_MS,
500
+ `runtime.shutdown timed out after ${RUNTIME_SHUTDOWN_TIMEOUT_MS}ms`
501
+ );
502
+ runtimeShutdownCompleted = true;
503
+ this.logDebugTiming(
504
+ "CopilotClient.stop runtime shutdown complete",
505
+ runtimeShutdownStart
506
+ );
507
+ } catch (error) {
508
+ this.logDebugTiming(
509
+ "CopilotClient.stop runtime shutdown failed",
510
+ runtimeShutdownStart
511
+ );
512
+ errors.push(
513
+ new Error(
514
+ `Failed to gracefully shut down runtime: ${error instanceof Error ? error.message : String(error)}`
515
+ )
516
+ );
517
+ }
518
+ }
438
519
  if (this.connection) {
439
520
  try {
440
521
  this.connection.dispose();
@@ -447,6 +528,7 @@ class CopilotClient {
447
528
  }
448
529
  this.connection = null;
449
530
  this._rpc = null;
531
+ this._internalRpc = null;
450
532
  }
451
533
  this.modelsCache = null;
452
534
  if (this.socket) {
@@ -471,12 +553,18 @@ class CopilotClient {
471
553
  const child = this.cliProcess;
472
554
  this.cliProcess = null;
473
555
  try {
474
- if (child.exitCode === null && child.signalCode === null) {
475
- const exited = new Promise((resolve) => {
476
- child.once("exit", () => resolve());
477
- });
478
- child.kill();
479
- await exited;
556
+ if (child.exitCode == null && child.signalCode == null) {
557
+ const exitedGracefully = runtimeShutdownCompleted ? await waitForChildExit(child, RUNTIME_SHUTDOWN_TIMEOUT_MS) : false;
558
+ if (!exitedGracefully) {
559
+ child.kill();
560
+ if (!await waitForChildExit(child, RUNTIME_SHUTDOWN_TIMEOUT_MS)) {
561
+ errors.push(
562
+ new Error(
563
+ `Timed out waiting for CLI process to exit after kill: ${RUNTIME_SHUTDOWN_TIMEOUT_MS}ms`
564
+ )
565
+ );
566
+ }
567
+ }
480
568
  }
481
569
  } catch (error) {
482
570
  errors.push(
@@ -546,6 +634,7 @@ class CopilotClient {
546
634
  }
547
635
  this.connection = null;
548
636
  this._rpc = null;
637
+ this._internalRpc = null;
549
638
  }
550
639
  this.modelsCache = null;
551
640
  if (this.socket) {
@@ -638,7 +727,8 @@ class CopilotClient {
638
727
  enableFileHooks: false,
639
728
  enableHostGitOperations: false,
640
729
  enableSessionStore: false,
641
- enableSkills: false
730
+ enableSkills: false,
731
+ memory: { enabled: false }
642
732
  };
643
733
  }
644
734
  return {};
@@ -788,7 +878,8 @@ class CopilotClient {
788
878
  description: tool.description,
789
879
  parameters: toJsonSchema(tool.parameters),
790
880
  overridesBuiltInTool: tool.overridesBuiltInTool,
791
- skipPermission: tool.skipPermission
881
+ skipPermission: tool.skipPermission,
882
+ defer: tool.defer
792
883
  })),
793
884
  canvases: config.canvases?.map((canvas) => canvas.declaration),
794
885
  requestCanvasRenderer: config.requestCanvasRenderer,
@@ -838,6 +929,7 @@ class CopilotClient {
838
929
  instructionDirectories: config.instructionDirectories,
839
930
  disabledSkills: config.disabledSkills,
840
931
  infiniteSessions: config.infiniteSessions,
932
+ memory: config.memory,
841
933
  gitHubToken: config.gitHubToken,
842
934
  remoteSession: config.remoteSession,
843
935
  cloud: config.cloud
@@ -956,7 +1048,8 @@ class CopilotClient {
956
1048
  description: tool.description,
957
1049
  parameters: toJsonSchema(tool.parameters),
958
1050
  overridesBuiltInTool: tool.overridesBuiltInTool,
959
- skipPermission: tool.skipPermission
1051
+ skipPermission: tool.skipPermission,
1052
+ defer: tool.defer
960
1053
  })),
961
1054
  canvases: config.canvases?.map((canvas) => canvas.declaration),
962
1055
  requestCanvasRenderer: config.requestCanvasRenderer,
@@ -1001,6 +1094,7 @@ class CopilotClient {
1001
1094
  instructionDirectories: config.instructionDirectories,
1002
1095
  disabledSkills: config.disabledSkills,
1003
1096
  infiniteSessions: config.infiniteSessions,
1097
+ memory: config.memory,
1004
1098
  disableResume: config.suppressResumeEvent,
1005
1099
  continuePendingWork: config.continuePendingWork,
1006
1100
  gitHubToken: config.gitHubToken,
@@ -1403,6 +1497,8 @@ class CopilotClient {
1403
1497
  envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
1404
1498
  if (t.otlpEndpoint !== void 0)
1405
1499
  envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
1500
+ if (t.otlpProtocol !== void 0)
1501
+ envWithoutNodeDebug.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol;
1406
1502
  if (t.filePath !== void 0)
1407
1503
  envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
1408
1504
  if (t.exporterType !== void 0)
@@ -233,7 +233,17 @@ function createServerRpc(connection) {
233
233
  *
234
234
  * @returns Skills discovered across global and project sources.
235
235
  */
236
- discover: async (params) => connection.sendRequest("skills.discover", params)
236
+ discover: async (params) => connection.sendRequest("skills.discover", params),
237
+ /**
238
+ * Returns the canonical directories where a client may create skills that the runtime will recognize, including ones that do not exist yet. Project directories become active once created.
239
+ *
240
+ * @param params Optional project paths to enumerate.
241
+ *
242
+ * @returns Canonical locations where skills can be created so the runtime will recognize them.
243
+ *
244
+ * @experimental
245
+ */
246
+ getDiscoveryPaths: async (params) => connection.sendRequest("skills.getDiscoveryPaths", params)
237
247
  },
238
248
  /** @experimental */
239
249
  agents: {
@@ -244,7 +254,15 @@ function createServerRpc(connection) {
244
254
  *
245
255
  * @returns Agents discovered across user, project, plugin, and remote sources.
246
256
  */
247
- discover: async (params) => connection.sendRequest("agents.discover", params)
257
+ discover: async (params) => connection.sendRequest("agents.discover", params),
258
+ /**
259
+ * Returns the canonical directories where a client may create custom agents that the runtime will recognize, including ones that do not exist yet. Project directories become active once created.
260
+ *
261
+ * @param params Optional project paths to include when enumerating agent discovery directories.
262
+ *
263
+ * @returns Canonical locations where custom agents can be created so the runtime will recognize them.
264
+ */
265
+ getDiscoveryPaths: async (params) => connection.sendRequest("agents.getDiscoveryPaths", params)
248
266
  },
249
267
  /** @experimental */
250
268
  instructions: {
@@ -255,7 +273,15 @@ function createServerRpc(connection) {
255
273
  *
256
274
  * @returns Instruction sources discovered across user, repository, and plugin sources.
257
275
  */
258
- discover: async (params) => connection.sendRequest("instructions.discover", params)
276
+ discover: async (params) => connection.sendRequest("instructions.discover", params),
277
+ /**
278
+ * Returns the canonical files and directories where a client may create custom instructions that the runtime will recognize, including ones that do not exist yet. Repository targets become active once created.
279
+ *
280
+ * @param params Optional project paths to include when enumerating instruction discovery targets.
281
+ *
282
+ * @returns Canonical files and directories where custom instructions can be created so the runtime will recognize them.
283
+ */
284
+ getDiscoveryPaths: async (params) => connection.sendRequest("instructions.getDiscoveryPaths", params)
259
285
  },
260
286
  user: {
261
287
  settings: {
@@ -725,7 +751,13 @@ function createSessionRpc(connection, sessionId) {
725
751
  *
726
752
  * @returns Todo rows read from the session SQL database. Empty when no session database is available.
727
753
  */
728
- readSqlTodos: async () => connection.sendRequest("session.plan.readSqlTodos", { sessionId })
754
+ readSqlTodos: async () => connection.sendRequest("session.plan.readSqlTodos", { sessionId }),
755
+ /**
756
+ * Reads todo rows AND dependency edges from the session SQL database for structured progress UI. Same defensive behavior as readSqlTodos — returns empty arrays when the database, tables, or columns aren't available. Clients should call this on session start and after every `session.todos_changed` event to refresh structured-UI rendering.
757
+ *
758
+ * @returns Todo rows + dependency edges read from the session SQL database.
759
+ */
760
+ readSqlTodosWithDependencies: async () => connection.sendRequest("session.plan.readSqlTodosWithDependencies", { sessionId })
729
761
  },
730
762
  /** @experimental */
731
763
  workspaces: {
@@ -1108,6 +1140,17 @@ function createSessionRpc(connection, sessionId) {
1108
1140
  reload: async (params) => connection.sendRequest("session.plugins.reload", { sessionId, ...params })
1109
1141
  },
1110
1142
  /** @experimental */
1143
+ provider: {
1144
+ /**
1145
+ * Returns the provider endpoint and credentials the session is currently configured to talk to, so the caller can make inference calls directly against the same backend the session uses.
1146
+ *
1147
+ * @param params Optional model identifier to scope the endpoint snapshot to.
1148
+ *
1149
+ * @returns A snapshot of the provider endpoint the session is currently configured to talk to.
1150
+ */
1151
+ getEndpoint: async (params) => connection.sendRequest("session.provider.getEndpoint", { sessionId, ...params })
1152
+ },
1153
+ /** @experimental */
1111
1154
  options: {
1112
1155
  /**
1113
1156
  * Patches the genuinely-mutable subset of session options.
@@ -1179,7 +1222,15 @@ function createSessionRpc(connection, sessionId) {
1179
1222
  *
1180
1223
  * @returns Current lightweight tool metadata snapshot for the session.
1181
1224
  */
1182
- getCurrentMetadata: async () => connection.sendRequest("session.tools.getCurrentMetadata", { sessionId })
1225
+ getCurrentMetadata: async () => connection.sendRequest("session.tools.getCurrentMetadata", { sessionId }),
1226
+ /**
1227
+ * Updates the current session's live subagent settings after user settings change. The persisted user settings remain the source of truth for future sessions.
1228
+ *
1229
+ * @param params Subagent settings to apply to the current session
1230
+ *
1231
+ * @returns Empty result after applying subagent settings
1232
+ */
1233
+ updateSubagentSettings: async (params) => connection.sendRequest("session.tools.updateSubagentSettings", { sessionId, ...params })
1183
1234
  },
1184
1235
  /** @experimental */
1185
1236
  commands: {
@@ -56,6 +56,9 @@ class CopilotSession {
56
56
  this._workspacePath = _workspacePath;
57
57
  this.traceContextProvider = traceContextProvider;
58
58
  }
59
+ sessionId;
60
+ connection;
61
+ _workspacePath;
59
62
  eventHandlers = /* @__PURE__ */ new Set();
60
63
  typedEventHandlers = /* @__PURE__ */ new Map();
61
64
  toolHandlers = /* @__PURE__ */ new Map();
package/dist/client.d.ts CHANGED
@@ -72,6 +72,7 @@ export declare class CopilotClient {
72
72
  * @throws Error if the client is not connected
73
73
  */
74
74
  get rpc(): ReturnType<typeof createServerRpc>;
75
+ private logDebugTiming;
75
76
  /**
76
77
  * Creates a new CopilotClient instance.
77
78
  *
@@ -132,8 +133,9 @@ export declare class CopilotClient {
132
133
  *
133
134
  * This method performs graceful cleanup:
134
135
  * 1. Closes all active sessions (releases in-memory resources)
135
- * 2. Closes the JSON-RPC connection
136
- * 3. Terminates the CLI server process (if spawned by this client)
136
+ * 2. Requests runtime shutdown for SDK-owned CLI processes
137
+ * 3. Closes the JSON-RPC connection
138
+ * 4. Terminates the CLI server process (if spawned by this client)
137
139
  *
138
140
  * Note: session data on disk is preserved, so sessions can be resumed later.
139
141
  * To permanently remove session data before stopping, call
package/dist/client.js CHANGED
@@ -24,9 +24,54 @@ import { getTraceContext } from "./telemetry.js";
24
24
  import { ToolSet } from "./toolSet.js";
25
25
  import { defaultJoinSessionPermissionHandler } from "./types.js";
26
26
  const MIN_PROTOCOL_VERSION = 3;
27
+ const RUNTIME_SHUTDOWN_TIMEOUT_MS = 1e4;
27
28
  function isZodSchema(value) {
28
29
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
29
30
  }
31
+ async function withTimeout(promise, timeoutMs, message) {
32
+ let timeout;
33
+ try {
34
+ return await Promise.race([
35
+ promise,
36
+ new Promise((_, reject) => {
37
+ timeout = setTimeout(() => reject(new Error(message)), timeoutMs);
38
+ })
39
+ ]);
40
+ } finally {
41
+ if (timeout !== void 0) {
42
+ clearTimeout(timeout);
43
+ }
44
+ }
45
+ }
46
+ async function waitForChildExit(child, timeoutMs) {
47
+ if (child.exitCode != null || child.signalCode != null) {
48
+ return true;
49
+ }
50
+ return new Promise((resolve) => {
51
+ let timeout;
52
+ let settled = false;
53
+ const onExit = () => {
54
+ if (settled) {
55
+ return;
56
+ }
57
+ settled = true;
58
+ clearTimeout(timeout);
59
+ resolve(true);
60
+ };
61
+ timeout = setTimeout(() => {
62
+ if (settled) {
63
+ return;
64
+ }
65
+ settled = true;
66
+ child.off("exit", onExit);
67
+ resolve(false);
68
+ }, timeoutMs);
69
+ child.once("exit", onExit);
70
+ if (child.exitCode != null || child.signalCode != null) {
71
+ onExit();
72
+ }
73
+ });
74
+ }
30
75
  function toJsonSchema(parameters) {
31
76
  if (!parameters) return void 0;
32
77
  if (isZodSchema(parameters)) {
@@ -190,6 +235,13 @@ class CopilotClient {
190
235
  }
191
236
  return this._internalRpc;
192
237
  }
238
+ logDebugTiming(message, startMs) {
239
+ const level = this.options.logLevel?.toLowerCase();
240
+ if (level === "debug" || level === "all") {
241
+ process.stderr.write(`[copilot-sdk] ${message}. Elapsed=${Date.now() - startMs}ms
242
+ `);
243
+ }
244
+ }
193
245
  /**
194
246
  * Creates a new CopilotClient instance.
195
247
  *
@@ -376,8 +428,9 @@ class CopilotClient {
376
428
  *
377
429
  * This method performs graceful cleanup:
378
430
  * 1. Closes all active sessions (releases in-memory resources)
379
- * 2. Closes the JSON-RPC connection
380
- * 3. Terminates the CLI server process (if spawned by this client)
431
+ * 2. Requests runtime shutdown for SDK-owned CLI processes
432
+ * 3. Closes the JSON-RPC connection
433
+ * 4. Terminates the CLI server process (if spawned by this client)
381
434
  *
382
435
  * Note: session data on disk is preserved, so sessions can be resumed later.
383
436
  * To permanently remove session data before stopping, call
@@ -421,6 +474,34 @@ class CopilotClient {
421
474
  }
422
475
  }
423
476
  this.sessions.clear();
477
+ let runtimeShutdownCompleted = false;
478
+ if (this.connection && this.cliProcess && !this.isExternalServer) {
479
+ const runtimeShutdownStart = Date.now();
480
+ const shutdownPromise = this.rpc.runtime.shutdown();
481
+ void shutdownPromise.catch(() => void 0);
482
+ try {
483
+ await withTimeout(
484
+ shutdownPromise,
485
+ RUNTIME_SHUTDOWN_TIMEOUT_MS,
486
+ `runtime.shutdown timed out after ${RUNTIME_SHUTDOWN_TIMEOUT_MS}ms`
487
+ );
488
+ runtimeShutdownCompleted = true;
489
+ this.logDebugTiming(
490
+ "CopilotClient.stop runtime shutdown complete",
491
+ runtimeShutdownStart
492
+ );
493
+ } catch (error) {
494
+ this.logDebugTiming(
495
+ "CopilotClient.stop runtime shutdown failed",
496
+ runtimeShutdownStart
497
+ );
498
+ errors.push(
499
+ new Error(
500
+ `Failed to gracefully shut down runtime: ${error instanceof Error ? error.message : String(error)}`
501
+ )
502
+ );
503
+ }
504
+ }
424
505
  if (this.connection) {
425
506
  try {
426
507
  this.connection.dispose();
@@ -433,6 +514,7 @@ class CopilotClient {
433
514
  }
434
515
  this.connection = null;
435
516
  this._rpc = null;
517
+ this._internalRpc = null;
436
518
  }
437
519
  this.modelsCache = null;
438
520
  if (this.socket) {
@@ -457,12 +539,18 @@ class CopilotClient {
457
539
  const child = this.cliProcess;
458
540
  this.cliProcess = null;
459
541
  try {
460
- if (child.exitCode === null && child.signalCode === null) {
461
- const exited = new Promise((resolve) => {
462
- child.once("exit", () => resolve());
463
- });
464
- child.kill();
465
- await exited;
542
+ if (child.exitCode == null && child.signalCode == null) {
543
+ const exitedGracefully = runtimeShutdownCompleted ? await waitForChildExit(child, RUNTIME_SHUTDOWN_TIMEOUT_MS) : false;
544
+ if (!exitedGracefully) {
545
+ child.kill();
546
+ if (!await waitForChildExit(child, RUNTIME_SHUTDOWN_TIMEOUT_MS)) {
547
+ errors.push(
548
+ new Error(
549
+ `Timed out waiting for CLI process to exit after kill: ${RUNTIME_SHUTDOWN_TIMEOUT_MS}ms`
550
+ )
551
+ );
552
+ }
553
+ }
466
554
  }
467
555
  } catch (error) {
468
556
  errors.push(
@@ -532,6 +620,7 @@ class CopilotClient {
532
620
  }
533
621
  this.connection = null;
534
622
  this._rpc = null;
623
+ this._internalRpc = null;
535
624
  }
536
625
  this.modelsCache = null;
537
626
  if (this.socket) {
@@ -624,7 +713,8 @@ class CopilotClient {
624
713
  enableFileHooks: false,
625
714
  enableHostGitOperations: false,
626
715
  enableSessionStore: false,
627
- enableSkills: false
716
+ enableSkills: false,
717
+ memory: { enabled: false }
628
718
  };
629
719
  }
630
720
  return {};
@@ -774,7 +864,8 @@ class CopilotClient {
774
864
  description: tool.description,
775
865
  parameters: toJsonSchema(tool.parameters),
776
866
  overridesBuiltInTool: tool.overridesBuiltInTool,
777
- skipPermission: tool.skipPermission
867
+ skipPermission: tool.skipPermission,
868
+ defer: tool.defer
778
869
  })),
779
870
  canvases: config.canvases?.map((canvas) => canvas.declaration),
780
871
  requestCanvasRenderer: config.requestCanvasRenderer,
@@ -824,6 +915,7 @@ class CopilotClient {
824
915
  instructionDirectories: config.instructionDirectories,
825
916
  disabledSkills: config.disabledSkills,
826
917
  infiniteSessions: config.infiniteSessions,
918
+ memory: config.memory,
827
919
  gitHubToken: config.gitHubToken,
828
920
  remoteSession: config.remoteSession,
829
921
  cloud: config.cloud
@@ -942,7 +1034,8 @@ class CopilotClient {
942
1034
  description: tool.description,
943
1035
  parameters: toJsonSchema(tool.parameters),
944
1036
  overridesBuiltInTool: tool.overridesBuiltInTool,
945
- skipPermission: tool.skipPermission
1037
+ skipPermission: tool.skipPermission,
1038
+ defer: tool.defer
946
1039
  })),
947
1040
  canvases: config.canvases?.map((canvas) => canvas.declaration),
948
1041
  requestCanvasRenderer: config.requestCanvasRenderer,
@@ -987,6 +1080,7 @@ class CopilotClient {
987
1080
  instructionDirectories: config.instructionDirectories,
988
1081
  disabledSkills: config.disabledSkills,
989
1082
  infiniteSessions: config.infiniteSessions,
1083
+ memory: config.memory,
990
1084
  disableResume: config.suppressResumeEvent,
991
1085
  continuePendingWork: config.continuePendingWork,
992
1086
  gitHubToken: config.gitHubToken,
@@ -1389,6 +1483,8 @@ class CopilotClient {
1389
1483
  envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
1390
1484
  if (t.otlpEndpoint !== void 0)
1391
1485
  envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
1486
+ if (t.otlpProtocol !== void 0)
1487
+ envWithoutNodeDebug.OTEL_EXPORTER_OTLP_PROTOCOL = t.otlpProtocol;
1392
1488
  if (t.filePath !== void 0)
1393
1489
  envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
1394
1490
  if (t.exporterType !== void 0)