@github/copilot-sdk 0.1.29 → 0.1.31-unstable.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
@@ -402,6 +402,19 @@ const session = await client.createSession({
402
402
 
403
403
  When Copilot invokes `lookup_issue`, the client automatically runs your handler and responds to the CLI. Handlers can return any JSON-serializable value (automatically wrapped), a simple string, or a `ToolResultObject` for full control over result metadata. Raw JSON schemas are also supported if Zod isn't desired.
404
404
 
405
+ #### Overriding Built-in Tools
406
+
407
+ If you register a tool with the same name as a built-in CLI tool (e.g. `edit_file`, `read_file`), the SDK will throw an error unless you explicitly opt in by setting `overridesBuiltInTool: true`. This flag signals that you intend to replace the built-in tool with your custom implementation.
408
+
409
+ ```ts
410
+ defineTool("edit_file", {
411
+ description: "Custom file editor with project-specific validation",
412
+ parameters: z.object({ path: z.string(), content: z.string() }),
413
+ overridesBuiltInTool: true,
414
+ handler: async ({ path, content }) => { /* your logic */ },
415
+ })
416
+ ```
417
+
405
418
  ### System Message Customization
406
419
 
407
420
  Control the system prompt using `systemMessage` in session config:
package/dist/client.d.ts CHANGED
@@ -361,9 +361,13 @@ export declare class CopilotClient {
361
361
  */
362
362
  private connectToServer;
363
363
  /**
364
- * Connect via stdio pipes
364
+ * Connect to child via stdio pipes
365
365
  */
366
- private connectViaStdio;
366
+ private connectToChildProcessViaStdio;
367
+ /**
368
+ * Connect to parent via stdio pipes
369
+ */
370
+ private connectToParentProcessViaStdio;
367
371
  /**
368
372
  * Connect to the CLI server via TCP socket
369
373
  */
@@ -371,14 +375,8 @@ export declare class CopilotClient {
371
375
  private attachConnectionHandlers;
372
376
  private handleSessionEventNotification;
373
377
  private handleSessionLifecycleNotification;
374
- private handleToolCallRequest;
375
- private executeToolCall;
376
- private handlePermissionRequest;
377
378
  private handleUserInputRequest;
378
379
  private handleHooksInvoke;
379
- private normalizeToolResult;
380
- private isToolResultObject;
381
- private buildUnsupportedToolResult;
382
380
  /**
383
381
  * Attempt to reconnect to the server
384
382
  */
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,
@@ -371,7 +380,8 @@ class CopilotClient {
371
380
  tools: config.tools?.map((tool) => ({
372
381
  name: tool.name,
373
382
  description: tool.description,
374
- parameters: toJsonSchema(tool.parameters)
383
+ parameters: toJsonSchema(tool.parameters),
384
+ overridesBuiltInTool: tool.overridesBuiltInTool
375
385
  })),
376
386
  systemMessage: config.systemMessage,
377
387
  availableTools: config.availableTools,
@@ -451,7 +461,8 @@ class CopilotClient {
451
461
  tools: config.tools?.map((tool) => ({
452
462
  name: tool.name,
453
463
  description: tool.description,
454
- parameters: toJsonSchema(tool.parameters)
464
+ parameters: toJsonSchema(tool.parameters),
465
+ overridesBuiltInTool: tool.overridesBuiltInTool
455
466
  })),
456
467
  provider: config.provider,
457
468
  requestPermission: true,
@@ -884,16 +895,18 @@ stderr: ${stderrOutput}`
884
895
  * Connect to the CLI server (via socket or stdio)
885
896
  */
886
897
  async connectToServer() {
887
- if (this.options.useStdio) {
888
- return this.connectViaStdio();
898
+ if (this.options.isChildProcess) {
899
+ return this.connectToParentProcessViaStdio();
900
+ } else if (this.options.useStdio) {
901
+ return this.connectToChildProcessViaStdio();
889
902
  } else {
890
903
  return this.connectViaTcp();
891
904
  }
892
905
  }
893
906
  /**
894
- * Connect via stdio pipes
907
+ * Connect to child via stdio pipes
895
908
  */
896
- async connectViaStdio() {
909
+ async connectToChildProcessViaStdio() {
897
910
  if (!this.cliProcess) {
898
911
  throw new Error("CLI process not started");
899
912
  }
@@ -909,6 +922,20 @@ stderr: ${stderrOutput}`
909
922
  this.attachConnectionHandlers();
910
923
  this.connection.listen();
911
924
  }
925
+ /**
926
+ * Connect to parent via stdio pipes
927
+ */
928
+ async connectToParentProcessViaStdio() {
929
+ if (this.cliProcess) {
930
+ throw new Error("CLI child process was unexpectedly started in parent process mode");
931
+ }
932
+ this.connection = createMessageConnection(
933
+ new StreamMessageReader(process.stdin),
934
+ new StreamMessageWriter(process.stdout)
935
+ );
936
+ this.attachConnectionHandlers();
937
+ this.connection.listen();
938
+ }
912
939
  /**
913
940
  * Connect to the CLI server via TCP socket
914
941
  */
@@ -942,14 +969,6 @@ stderr: ${stderrOutput}`
942
969
  this.connection.onNotification("session.lifecycle", (notification) => {
943
970
  this.handleSessionLifecycleNotification(notification);
944
971
  });
945
- this.connection.onRequest(
946
- "tool.call",
947
- async (params) => await this.handleToolCallRequest(params)
948
- );
949
- this.connection.onRequest(
950
- "permission.request",
951
- async (params) => await this.handlePermissionRequest(params)
952
- );
953
972
  this.connection.onRequest(
954
973
  "userInput.request",
955
974
  async (params) => await this.handleUserInputRequest(params)
@@ -996,62 +1015,6 @@ stderr: ${stderrOutput}`
996
1015
  }
997
1016
  }
998
1017
  }
999
- async handleToolCallRequest(params) {
1000
- if (!params || typeof params.sessionId !== "string" || typeof params.toolCallId !== "string" || typeof params.toolName !== "string") {
1001
- throw new Error("Invalid tool call payload");
1002
- }
1003
- const session = this.sessions.get(params.sessionId);
1004
- if (!session) {
1005
- throw new Error(`Unknown session ${params.sessionId}`);
1006
- }
1007
- const handler = session.getToolHandler(params.toolName);
1008
- if (!handler) {
1009
- return { result: this.buildUnsupportedToolResult(params.toolName) };
1010
- }
1011
- return await this.executeToolCall(handler, params);
1012
- }
1013
- async executeToolCall(handler, request) {
1014
- try {
1015
- const invocation = {
1016
- sessionId: request.sessionId,
1017
- toolCallId: request.toolCallId,
1018
- toolName: request.toolName,
1019
- arguments: request.arguments
1020
- };
1021
- const result = await handler(request.arguments, invocation);
1022
- return { result: this.normalizeToolResult(result) };
1023
- } catch (error) {
1024
- const message = error instanceof Error ? error.message : String(error);
1025
- return {
1026
- result: {
1027
- // Don't expose detailed error information to the LLM for security reasons
1028
- textResultForLlm: "Invoking this tool produced an error. Detailed information is not available.",
1029
- resultType: "failure",
1030
- error: message,
1031
- toolTelemetry: {}
1032
- }
1033
- };
1034
- }
1035
- }
1036
- async handlePermissionRequest(params) {
1037
- if (!params || typeof params.sessionId !== "string" || !params.permissionRequest) {
1038
- throw new Error("Invalid permission request payload");
1039
- }
1040
- const session = this.sessions.get(params.sessionId);
1041
- if (!session) {
1042
- throw new Error(`Session not found: ${params.sessionId}`);
1043
- }
1044
- try {
1045
- const result = await session._handlePermissionRequest(params.permissionRequest);
1046
- return { result };
1047
- } catch (_error) {
1048
- return {
1049
- result: {
1050
- kind: "denied-no-approval-rule-and-could-not-request-from-user"
1051
- }
1052
- };
1053
- }
1054
- }
1055
1018
  async handleUserInputRequest(params) {
1056
1019
  if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
1057
1020
  throw new Error("Invalid user input request payload");
@@ -1078,36 +1041,6 @@ stderr: ${stderrOutput}`
1078
1041
  const output = await session._handleHooksInvoke(params.hookType, params.input);
1079
1042
  return { output };
1080
1043
  }
1081
- normalizeToolResult(result) {
1082
- if (result === void 0 || result === null) {
1083
- return {
1084
- textResultForLlm: "Tool returned no result",
1085
- resultType: "failure",
1086
- error: "tool returned no result",
1087
- toolTelemetry: {}
1088
- };
1089
- }
1090
- if (this.isToolResultObject(result)) {
1091
- return result;
1092
- }
1093
- const textResult = typeof result === "string" ? result : JSON.stringify(result);
1094
- return {
1095
- textResultForLlm: textResult,
1096
- resultType: "success",
1097
- toolTelemetry: {}
1098
- };
1099
- }
1100
- isToolResultObject(value) {
1101
- return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
1102
- }
1103
- buildUnsupportedToolResult(toolName) {
1104
- return {
1105
- textResultForLlm: `Tool '${toolName}' is not supported by this client instance.`,
1106
- resultType: "failure",
1107
- error: `tool '${toolName}' not supported`,
1108
- toolTelemetry: {}
1109
- };
1110
- }
1111
1044
  /**
1112
1045
  * Attempt to reconnect to the server
1113
1046
  */
@@ -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,32 @@ 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
+ error?: string;
412
+ }
413
+ export interface SessionPermissionsHandlePendingPermissionRequestResult {
414
+ success: boolean;
415
+ }
416
+ export interface SessionPermissionsHandlePendingPermissionRequestParams {
417
+ /**
418
+ * Target session identifier
419
+ */
420
+ sessionId: string;
421
+ requestId: string;
422
+ result: {
423
+ kind: "approved" | "denied-by-rules" | "denied-no-approval-rule-and-could-not-request-from-user" | "denied-interactively-by-user";
424
+ rules?: unknown[];
425
+ };
426
+ }
397
427
  /** Create typed server-scoped RPC methods (no session required). */
398
428
  export declare function createServerRpc(connection: MessageConnection): {
399
429
  ping: (params: PingParams) => Promise<PingResult>;
@@ -439,4 +469,10 @@ export declare function createSessionRpc(connection: MessageConnection, sessionI
439
469
  compaction: {
440
470
  compact: () => Promise<SessionCompactionCompactResult>;
441
471
  };
472
+ tools: {
473
+ handlePendingToolCall: (params: Omit<SessionToolsHandlePendingToolCallParams, "sessionId">) => Promise<SessionToolsHandlePendingToolCallResult>;
474
+ };
475
+ permissions: {
476
+ handlePendingPermissionRequest: (params: Omit<SessionPermissionsHandlePendingPermissionRequestParams, "sessionId">) => Promise<SessionPermissionsHandlePendingPermissionRequestResult>;
477
+ };
442
478
  };
@@ -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
  }