@github/copilot-sdk 0.1.30 → 0.1.31

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
@@ -52,10 +52,17 @@ await session.send({ prompt: "What is 2+2?" });
52
52
  await done;
53
53
 
54
54
  // Clean up
55
- await session.destroy();
55
+ await session.disconnect();
56
56
  await client.stop();
57
57
  ```
58
58
 
59
+ Sessions also support `Symbol.asyncDispose` for use with [`await using`](https://github.com/tc39/proposal-explicit-resource-management) (TypeScript 5.2+/Node.js 18.0+):
60
+
61
+ ```typescript
62
+ await using session = await client.createSession({ model: "gpt-5" });
63
+ // session is automatically disconnected when leaving scope
64
+ ```
65
+
59
66
  ## API Reference
60
67
 
61
68
  ### CopilotClient
@@ -265,9 +272,13 @@ Abort the currently processing message in this session.
265
272
 
266
273
  Get all events/messages from this session.
267
274
 
268
- ##### `destroy(): Promise<void>`
275
+ ##### `disconnect(): Promise<void>`
276
+
277
+ Disconnect the session and free resources. Session data on disk is preserved for later resumption.
278
+
279
+ ##### `destroy(): Promise<void>` *(deprecated)*
269
280
 
270
- Destroy the session and free resources.
281
+ Deprecated use `disconnect()` instead.
271
282
 
272
283
  ---
273
284
 
package/dist/client.d.ts CHANGED
@@ -74,10 +74,14 @@ export declare class CopilotClient {
74
74
  * Stops the CLI server and closes all active sessions.
75
75
  *
76
76
  * This method performs graceful cleanup:
77
- * 1. Destroys all active sessions with retry logic
77
+ * 1. Closes all active sessions (releases in-memory resources)
78
78
  * 2. Closes the JSON-RPC connection
79
79
  * 3. Terminates the CLI server process (if spawned by this client)
80
80
  *
81
+ * Note: session data on disk is preserved, so sessions can be resumed later.
82
+ * To permanently remove session data before stopping, call
83
+ * {@link deleteSession} for each session first.
84
+ *
81
85
  * @returns A promise that resolves with an array of errors encountered during cleanup.
82
86
  * An empty array indicates all cleanup succeeded.
83
87
  *
@@ -242,10 +246,12 @@ export declare class CopilotClient {
242
246
  */
243
247
  getLastSessionId(): Promise<string | undefined>;
244
248
  /**
245
- * Deletes a session and its data from disk.
249
+ * Permanently deletes a session and all its data from disk, including
250
+ * conversation history, planning state, and artifacts.
246
251
  *
247
- * This permanently removes the session and all its conversation history.
248
- * The session cannot be resumed after deletion.
252
+ * Unlike {@link CopilotSession.disconnect}, which only releases in-memory
253
+ * resources and preserves session data for later resumption, this method
254
+ * is irreversible. The session cannot be resumed after deletion.
249
255
  *
250
256
  * @param sessionId - The ID of the session to delete
251
257
  * @returns A promise that resolves when the session is deleted
@@ -361,9 +367,13 @@ export declare class CopilotClient {
361
367
  */
362
368
  private connectToServer;
363
369
  /**
364
- * Connect via stdio pipes
370
+ * Connect to child via stdio pipes
371
+ */
372
+ private connectToChildProcessViaStdio;
373
+ /**
374
+ * Connect to parent via stdio pipes
365
375
  */
366
- private connectViaStdio;
376
+ private connectToParentProcessViaStdio;
367
377
  /**
368
378
  * Connect to the CLI server via TCP socket
369
379
  */
@@ -371,14 +381,8 @@ export declare class CopilotClient {
371
381
  private attachConnectionHandlers;
372
382
  private handleSessionEventNotification;
373
383
  private handleSessionLifecycleNotification;
374
- private handleToolCallRequest;
375
- private executeToolCall;
376
- private handlePermissionRequest;
377
384
  private handleUserInputRequest;
378
385
  private handleHooksInvoke;
379
- private normalizeToolResult;
380
- private isToolResultObject;
381
- private buildUnsupportedToolResult;
382
386
  /**
383
387
  * Attempt to reconnect to the server
384
388
  */
package/dist/client.js CHANGED
@@ -90,6 +90,11 @@ class CopilotClient {
90
90
  if (options.cliUrl && (options.useStdio === true || options.cliPath)) {
91
91
  throw new Error("cliUrl is mutually exclusive with useStdio and cliPath");
92
92
  }
93
+ if (options.isChildProcess && (options.cliUrl || options.useStdio === false)) {
94
+ throw new Error(
95
+ "isChildProcess must be used in conjunction with useStdio and not with cliUrl"
96
+ );
97
+ }
93
98
  if (options.cliUrl && (options.githubToken || options.useLoggedInUser !== void 0)) {
94
99
  throw new Error(
95
100
  "githubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
@@ -101,6 +106,9 @@ class CopilotClient {
101
106
  this.actualPort = port;
102
107
  this.isExternalServer = true;
103
108
  }
109
+ if (options.isChildProcess) {
110
+ this.isExternalServer = true;
111
+ }
104
112
  this.options = {
105
113
  cliPath: options.cliPath || getBundledCliPath(),
106
114
  cliArgs: options.cliArgs ?? [],
@@ -108,6 +116,7 @@ class CopilotClient {
108
116
  port: options.port || 0,
109
117
  useStdio: options.cliUrl ? false : options.useStdio ?? true,
110
118
  // Default to stdio unless cliUrl is provided
119
+ isChildProcess: options.isChildProcess ?? false,
111
120
  cliUrl: options.cliUrl,
112
121
  logLevel: options.logLevel || "debug",
113
122
  autoStart: options.autoStart ?? true,
@@ -179,10 +188,14 @@ class CopilotClient {
179
188
  * Stops the CLI server and closes all active sessions.
180
189
  *
181
190
  * This method performs graceful cleanup:
182
- * 1. Destroys all active sessions with retry logic
191
+ * 1. Closes all active sessions (releases in-memory resources)
183
192
  * 2. Closes the JSON-RPC connection
184
193
  * 3. Terminates the CLI server process (if spawned by this client)
185
194
  *
195
+ * Note: session data on disk is preserved, so sessions can be resumed later.
196
+ * To permanently remove session data before stopping, call
197
+ * {@link deleteSession} for each session first.
198
+ *
186
199
  * @returns A promise that resolves with an array of errors encountered during cleanup.
187
200
  * An empty array indicates all cleanup succeeded.
188
201
  *
@@ -201,7 +214,7 @@ class CopilotClient {
201
214
  let lastError = null;
202
215
  for (let attempt = 1; attempt <= 3; attempt++) {
203
216
  try {
204
- await session.destroy();
217
+ await session.disconnect();
205
218
  lastError = null;
206
219
  break;
207
220
  } catch (error) {
@@ -215,7 +228,7 @@ class CopilotClient {
215
228
  if (lastError) {
216
229
  errors.push(
217
230
  new Error(
218
- `Failed to destroy session ${sessionId} after 3 attempts: ${lastError.message}`
231
+ `Failed to disconnect session ${sessionId} after 3 attempts: ${lastError.message}`
219
232
  )
220
233
  );
221
234
  }
@@ -616,10 +629,12 @@ class CopilotClient {
616
629
  return response.sessionId;
617
630
  }
618
631
  /**
619
- * Deletes a session and its data from disk.
632
+ * Permanently deletes a session and all its data from disk, including
633
+ * conversation history, planning state, and artifacts.
620
634
  *
621
- * This permanently removes the session and all its conversation history.
622
- * The session cannot be resumed after deletion.
635
+ * Unlike {@link CopilotSession.disconnect}, which only releases in-memory
636
+ * resources and preserves session data for later resumption, this method
637
+ * is irreversible. The session cannot be resumed after deletion.
623
638
  *
624
639
  * @param sessionId - The ID of the session to delete
625
640
  * @returns A promise that resolves when the session is deleted
@@ -886,16 +901,18 @@ stderr: ${stderrOutput}`
886
901
  * Connect to the CLI server (via socket or stdio)
887
902
  */
888
903
  async connectToServer() {
889
- if (this.options.useStdio) {
890
- return this.connectViaStdio();
904
+ if (this.options.isChildProcess) {
905
+ return this.connectToParentProcessViaStdio();
906
+ } else if (this.options.useStdio) {
907
+ return this.connectToChildProcessViaStdio();
891
908
  } else {
892
909
  return this.connectViaTcp();
893
910
  }
894
911
  }
895
912
  /**
896
- * Connect via stdio pipes
913
+ * Connect to child via stdio pipes
897
914
  */
898
- async connectViaStdio() {
915
+ async connectToChildProcessViaStdio() {
899
916
  if (!this.cliProcess) {
900
917
  throw new Error("CLI process not started");
901
918
  }
@@ -911,6 +928,20 @@ stderr: ${stderrOutput}`
911
928
  this.attachConnectionHandlers();
912
929
  this.connection.listen();
913
930
  }
931
+ /**
932
+ * Connect to parent via stdio pipes
933
+ */
934
+ async connectToParentProcessViaStdio() {
935
+ if (this.cliProcess) {
936
+ throw new Error("CLI child process was unexpectedly started in parent process mode");
937
+ }
938
+ this.connection = createMessageConnection(
939
+ new StreamMessageReader(process.stdin),
940
+ new StreamMessageWriter(process.stdout)
941
+ );
942
+ this.attachConnectionHandlers();
943
+ this.connection.listen();
944
+ }
914
945
  /**
915
946
  * Connect to the CLI server via TCP socket
916
947
  */
@@ -944,14 +975,6 @@ stderr: ${stderrOutput}`
944
975
  this.connection.onNotification("session.lifecycle", (notification) => {
945
976
  this.handleSessionLifecycleNotification(notification);
946
977
  });
947
- this.connection.onRequest(
948
- "tool.call",
949
- async (params) => await this.handleToolCallRequest(params)
950
- );
951
- this.connection.onRequest(
952
- "permission.request",
953
- async (params) => await this.handlePermissionRequest(params)
954
- );
955
978
  this.connection.onRequest(
956
979
  "userInput.request",
957
980
  async (params) => await this.handleUserInputRequest(params)
@@ -998,62 +1021,6 @@ stderr: ${stderrOutput}`
998
1021
  }
999
1022
  }
1000
1023
  }
1001
- async handleToolCallRequest(params) {
1002
- if (!params || typeof params.sessionId !== "string" || typeof params.toolCallId !== "string" || typeof params.toolName !== "string") {
1003
- throw new Error("Invalid tool call payload");
1004
- }
1005
- const session = this.sessions.get(params.sessionId);
1006
- if (!session) {
1007
- throw new Error(`Unknown session ${params.sessionId}`);
1008
- }
1009
- const handler = session.getToolHandler(params.toolName);
1010
- if (!handler) {
1011
- return { result: this.buildUnsupportedToolResult(params.toolName) };
1012
- }
1013
- return await this.executeToolCall(handler, params);
1014
- }
1015
- async executeToolCall(handler, request) {
1016
- try {
1017
- const invocation = {
1018
- sessionId: request.sessionId,
1019
- toolCallId: request.toolCallId,
1020
- toolName: request.toolName,
1021
- arguments: request.arguments
1022
- };
1023
- const result = await handler(request.arguments, invocation);
1024
- return { result: this.normalizeToolResult(result) };
1025
- } catch (error) {
1026
- const message = error instanceof Error ? error.message : String(error);
1027
- return {
1028
- result: {
1029
- // Don't expose detailed error information to the LLM for security reasons
1030
- textResultForLlm: "Invoking this tool produced an error. Detailed information is not available.",
1031
- resultType: "failure",
1032
- error: message,
1033
- toolTelemetry: {}
1034
- }
1035
- };
1036
- }
1037
- }
1038
- async handlePermissionRequest(params) {
1039
- if (!params || typeof params.sessionId !== "string" || !params.permissionRequest) {
1040
- throw new Error("Invalid permission request payload");
1041
- }
1042
- const session = this.sessions.get(params.sessionId);
1043
- if (!session) {
1044
- throw new Error(`Session not found: ${params.sessionId}`);
1045
- }
1046
- try {
1047
- const result = await session._handlePermissionRequest(params.permissionRequest);
1048
- return { result };
1049
- } catch (_error) {
1050
- return {
1051
- result: {
1052
- kind: "denied-no-approval-rule-and-could-not-request-from-user"
1053
- }
1054
- };
1055
- }
1056
- }
1057
1024
  async handleUserInputRequest(params) {
1058
1025
  if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
1059
1026
  throw new Error("Invalid user input request payload");
@@ -1080,36 +1047,6 @@ stderr: ${stderrOutput}`
1080
1047
  const output = await session._handleHooksInvoke(params.hookType, params.input);
1081
1048
  return { output };
1082
1049
  }
1083
- normalizeToolResult(result) {
1084
- if (result === void 0 || result === null) {
1085
- return {
1086
- textResultForLlm: "Tool returned no result",
1087
- resultType: "failure",
1088
- error: "tool returned no result",
1089
- toolTelemetry: {}
1090
- };
1091
- }
1092
- if (this.isToolResultObject(result)) {
1093
- return result;
1094
- }
1095
- const textResult = typeof result === "string" ? result : JSON.stringify(result);
1096
- return {
1097
- textResultForLlm: textResult,
1098
- resultType: "success",
1099
- toolTelemetry: {}
1100
- };
1101
- }
1102
- isToolResultObject(value) {
1103
- return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
1104
- }
1105
- buildUnsupportedToolResult(toolName) {
1106
- return {
1107
- textResultForLlm: `Tool '${toolName}' is not supported by this client instance.`,
1108
- resultType: "failure",
1109
- error: `tool '${toolName}' not supported`,
1110
- toolTelemetry: {}
1111
- };
1112
- }
1113
1050
  /**
1114
1051
  * Attempt to reconnect to the server
1115
1052
  */
@@ -0,0 +1,2 @@
1
+ import { CopilotClient } from "./client.js";
2
+ export declare const extension: CopilotClient;
@@ -0,0 +1,5 @@
1
+ import { CopilotClient } from "./client.js";
2
+ const extension = new CopilotClient({ isChildProcess: true });
3
+ export {
4
+ extension
5
+ };
@@ -193,13 +193,17 @@ export interface SessionModeSetParams {
193
193
  }
194
194
  export interface SessionPlanReadResult {
195
195
  /**
196
- * Whether plan.md exists in the workspace
196
+ * Whether the plan file exists in the workspace
197
197
  */
198
198
  exists: boolean;
199
199
  /**
200
- * The content of plan.md, or null if it does not exist
200
+ * The content of the plan file, or null if it does not exist
201
201
  */
202
202
  content: string | null;
203
+ /**
204
+ * Absolute file path of the plan file, or null if workspace is not enabled
205
+ */
206
+ path: string | null;
203
207
  }
204
208
  export interface SessionPlanReadParams {
205
209
  /**
@@ -215,7 +219,7 @@ export interface SessionPlanUpdateParams {
215
219
  */
216
220
  sessionId: string;
217
221
  /**
218
- * The new content for plan.md
222
+ * The new content for the plan file
219
223
  */
220
224
  content: string;
221
225
  }
@@ -394,6 +398,50 @@ export interface SessionCompactionCompactParams {
394
398
  */
395
399
  sessionId: string;
396
400
  }
401
+ export interface SessionToolsHandlePendingToolCallResult {
402
+ success: boolean;
403
+ }
404
+ export interface SessionToolsHandlePendingToolCallParams {
405
+ /**
406
+ * Target session identifier
407
+ */
408
+ sessionId: string;
409
+ requestId: string;
410
+ result?: string | {
411
+ textResultForLlm: string;
412
+ resultType?: string;
413
+ error?: string;
414
+ toolTelemetry?: {
415
+ [k: string]: unknown;
416
+ };
417
+ };
418
+ error?: string;
419
+ }
420
+ export interface SessionPermissionsHandlePendingPermissionRequestResult {
421
+ success: boolean;
422
+ }
423
+ export interface SessionPermissionsHandlePendingPermissionRequestParams {
424
+ /**
425
+ * Target session identifier
426
+ */
427
+ sessionId: string;
428
+ requestId: string;
429
+ result: {
430
+ kind: "approved";
431
+ } | {
432
+ kind: "denied-by-rules";
433
+ rules: unknown[];
434
+ } | {
435
+ kind: "denied-no-approval-rule-and-could-not-request-from-user";
436
+ } | {
437
+ kind: "denied-interactively-by-user";
438
+ feedback?: string;
439
+ } | {
440
+ kind: "denied-by-content-exclusion-policy";
441
+ path: string;
442
+ message: string;
443
+ };
444
+ }
397
445
  /** Create typed server-scoped RPC methods (no session required). */
398
446
  export declare function createServerRpc(connection: MessageConnection): {
399
447
  ping: (params: PingParams) => Promise<PingResult>;
@@ -439,4 +487,10 @@ export declare function createSessionRpc(connection: MessageConnection, sessionI
439
487
  compaction: {
440
488
  compact: () => Promise<SessionCompactionCompactResult>;
441
489
  };
490
+ tools: {
491
+ handlePendingToolCall: (params: Omit<SessionToolsHandlePendingToolCallParams, "sessionId">) => Promise<SessionToolsHandlePendingToolCallResult>;
492
+ };
493
+ permissions: {
494
+ handlePendingPermissionRequest: (params: Omit<SessionPermissionsHandlePendingPermissionRequestParams, "sessionId">) => Promise<SessionPermissionsHandlePendingPermissionRequestResult>;
495
+ };
442
496
  };
@@ -43,6 +43,12 @@ function createSessionRpc(connection, sessionId) {
43
43
  },
44
44
  compaction: {
45
45
  compact: async () => connection.sendRequest("session.compaction.compact", { sessionId })
46
+ },
47
+ tools: {
48
+ handlePendingToolCall: async (params) => connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params })
49
+ },
50
+ permissions: {
51
+ handlePendingPermissionRequest: async (params) => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params })
46
52
  }
47
53
  };
48
54
  }