@github/copilot-sdk 0.1.33-unstable.0 → 0.2.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/dist/client.d.ts CHANGED
@@ -47,6 +47,7 @@ export declare class CopilotClient {
47
47
  private isExternalServer;
48
48
  private forceStopping;
49
49
  private onListModels?;
50
+ private onGetTraceContext?;
50
51
  private modelsCache;
51
52
  private modelsCacheLock;
52
53
  private sessionLifecycleHandlers;
@@ -422,6 +423,7 @@ export declare class CopilotClient {
422
423
  private handleSessionLifecycleNotification;
423
424
  private handleUserInputRequest;
424
425
  private handleHooksInvoke;
426
+ private handleSystemMessageTransform;
425
427
  /**
426
428
  * Handles a v2-style tool.call RPC request from the server.
427
429
  * Looks up the session and tool handler, executes it, and returns the result
@@ -434,8 +436,4 @@ export declare class CopilotClient {
434
436
  private handlePermissionRequestV2;
435
437
  private normalizeToolResultV2;
436
438
  private isToolResultObject;
437
- /**
438
- * Attempt to reconnect to the server
439
- */
440
- private reconnect;
441
439
  }
package/dist/client.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { existsSync } from "node:fs";
4
+ import { createRequire } from "node:module";
3
5
  import { Socket } from "node:net";
4
6
  import { dirname, join } from "node:path";
5
7
  import { fileURLToPath } from "node:url";
@@ -10,7 +12,8 @@ import {
10
12
  } from "vscode-jsonrpc/node.js";
11
13
  import { createServerRpc } from "./generated/rpc.js";
12
14
  import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
13
- import { CopilotSession } from "./session.js";
15
+ import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js";
16
+ import { getTraceContext } from "./telemetry.js";
14
17
  const MIN_PROTOCOL_VERSION = 2;
15
18
  function isZodSchema(value) {
16
19
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
@@ -22,6 +25,30 @@ function toJsonSchema(parameters) {
22
25
  }
23
26
  return parameters;
24
27
  }
28
+ function extractTransformCallbacks(systemMessage) {
29
+ if (!systemMessage || systemMessage.mode !== "customize" || !systemMessage.sections) {
30
+ return { wirePayload: systemMessage, transformCallbacks: void 0 };
31
+ }
32
+ const transformCallbacks = /* @__PURE__ */ new Map();
33
+ const wireSections = {};
34
+ for (const [sectionId, override] of Object.entries(systemMessage.sections)) {
35
+ if (!override) continue;
36
+ if (typeof override.action === "function") {
37
+ transformCallbacks.set(sectionId, override.action);
38
+ wireSections[sectionId] = { action: "transform" };
39
+ } else {
40
+ wireSections[sectionId] = { action: override.action, content: override.content };
41
+ }
42
+ }
43
+ if (transformCallbacks.size === 0) {
44
+ return { wirePayload: systemMessage, transformCallbacks: void 0 };
45
+ }
46
+ const wirePayload = {
47
+ ...systemMessage,
48
+ sections: wireSections
49
+ };
50
+ return { wirePayload, transformCallbacks };
51
+ }
25
52
  function getNodeExecPath() {
26
53
  if (process.versions.bun) {
27
54
  return "node";
@@ -29,9 +56,22 @@ function getNodeExecPath() {
29
56
  return process.execPath;
30
57
  }
31
58
  function getBundledCliPath() {
32
- const sdkUrl = import.meta.resolve("@github/copilot/sdk");
33
- const sdkPath = fileURLToPath(sdkUrl);
34
- return join(dirname(dirname(sdkPath)), "index.js");
59
+ if (typeof import.meta.resolve === "function") {
60
+ const sdkUrl = import.meta.resolve("@github/copilot/sdk");
61
+ const sdkPath = fileURLToPath(sdkUrl);
62
+ return join(dirname(dirname(sdkPath)), "index.js");
63
+ }
64
+ const req = createRequire(__filename);
65
+ const searchPaths = req.resolve.paths("@github/copilot") ?? [];
66
+ for (const base of searchPaths) {
67
+ const candidate = join(base, "@github", "copilot", "index.js");
68
+ if (existsSync(candidate)) {
69
+ return candidate;
70
+ }
71
+ }
72
+ throw new Error(
73
+ `Could not find @github/copilot package. Searched ${searchPaths.length} paths. Ensure it is installed, or pass cliPath/cliUrl to CopilotClient.`
74
+ );
35
75
  }
36
76
  class CopilotClient {
37
77
  cliProcess = null;
@@ -47,6 +87,7 @@ class CopilotClient {
47
87
  isExternalServer = false;
48
88
  forceStopping = false;
49
89
  onListModels;
90
+ onGetTraceContext;
50
91
  modelsCache = null;
51
92
  modelsCacheLock = Promise.resolve();
52
93
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
@@ -113,8 +154,9 @@ class CopilotClient {
113
154
  this.isExternalServer = true;
114
155
  }
115
156
  this.onListModels = options.onListModels;
157
+ this.onGetTraceContext = options.onGetTraceContext;
116
158
  this.options = {
117
- cliPath: options.cliPath || getBundledCliPath(),
159
+ cliPath: options.cliUrl ? void 0 : options.cliPath || getBundledCliPath(),
118
160
  cliArgs: options.cliArgs ?? [],
119
161
  cwd: options.cwd ?? process.cwd(),
120
162
  port: options.port || 0,
@@ -124,11 +166,12 @@ class CopilotClient {
124
166
  cliUrl: options.cliUrl,
125
167
  logLevel: options.logLevel || "debug",
126
168
  autoStart: options.autoStart ?? true,
127
- autoRestart: options.autoRestart ?? true,
169
+ autoRestart: false,
128
170
  env: options.env ?? process.env,
129
171
  githubToken: options.githubToken,
130
172
  // Default useLoggedInUser to false when githubToken is provided, otherwise true
131
- useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true)
173
+ useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true),
174
+ telemetry: options.telemetry
132
175
  };
133
176
  }
134
177
  /**
@@ -380,37 +423,13 @@ class CopilotClient {
380
423
  throw new Error("Client not connected. Call start() first.");
381
424
  }
382
425
  }
383
- const response = await this.connection.sendRequest("session.create", {
384
- model: config.model,
385
- sessionId: config.sessionId,
386
- clientName: config.clientName,
387
- reasoningEffort: config.reasoningEffort,
388
- tools: config.tools?.map((tool) => ({
389
- name: tool.name,
390
- description: tool.description,
391
- parameters: toJsonSchema(tool.parameters),
392
- overridesBuiltInTool: tool.overridesBuiltInTool
393
- })),
394
- systemMessage: config.systemMessage,
395
- availableTools: config.availableTools,
396
- excludedTools: config.excludedTools,
397
- provider: config.provider,
398
- requestPermission: true,
399
- requestUserInput: !!config.onUserInputRequest,
400
- hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
401
- workingDirectory: config.workingDirectory,
402
- streaming: config.streaming,
403
- mcpServers: config.mcpServers,
404
- envValueMode: "direct",
405
- customAgents: config.customAgents,
406
- agent: config.agent,
407
- configDir: config.configDir,
408
- skillDirectories: config.skillDirectories,
409
- disabledSkills: config.disabledSkills,
410
- infiniteSessions: config.infiniteSessions
411
- });
412
- const { sessionId, workspacePath } = response;
413
- const session = new CopilotSession(sessionId, this.connection, workspacePath);
426
+ const sessionId = config.sessionId ?? randomUUID();
427
+ const session = new CopilotSession(
428
+ sessionId,
429
+ this.connection,
430
+ void 0,
431
+ this.onGetTraceContext
432
+ );
414
433
  session.registerTools(config.tools);
415
434
  session.registerPermissionHandler(config.onPermissionRequest);
416
435
  if (config.onUserInputRequest) {
@@ -419,7 +438,54 @@ class CopilotClient {
419
438
  if (config.hooks) {
420
439
  session.registerHooks(config.hooks);
421
440
  }
441
+ const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
442
+ config.systemMessage
443
+ );
444
+ if (transformCallbacks) {
445
+ session.registerTransformCallbacks(transformCallbacks);
446
+ }
447
+ if (config.onEvent) {
448
+ session.on(config.onEvent);
449
+ }
422
450
  this.sessions.set(sessionId, session);
451
+ try {
452
+ const response = await this.connection.sendRequest("session.create", {
453
+ ...await getTraceContext(this.onGetTraceContext),
454
+ model: config.model,
455
+ sessionId,
456
+ clientName: config.clientName,
457
+ reasoningEffort: config.reasoningEffort,
458
+ tools: config.tools?.map((tool) => ({
459
+ name: tool.name,
460
+ description: tool.description,
461
+ parameters: toJsonSchema(tool.parameters),
462
+ overridesBuiltInTool: tool.overridesBuiltInTool,
463
+ skipPermission: tool.skipPermission
464
+ })),
465
+ systemMessage: wireSystemMessage,
466
+ availableTools: config.availableTools,
467
+ excludedTools: config.excludedTools,
468
+ provider: config.provider,
469
+ requestPermission: true,
470
+ requestUserInput: !!config.onUserInputRequest,
471
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
472
+ workingDirectory: config.workingDirectory,
473
+ streaming: config.streaming,
474
+ mcpServers: config.mcpServers,
475
+ envValueMode: "direct",
476
+ customAgents: config.customAgents,
477
+ agent: config.agent,
478
+ configDir: config.configDir,
479
+ skillDirectories: config.skillDirectories,
480
+ disabledSkills: config.disabledSkills,
481
+ infiniteSessions: config.infiniteSessions
482
+ });
483
+ const { workspacePath } = response;
484
+ session["_workspacePath"] = workspacePath;
485
+ } catch (e) {
486
+ this.sessions.delete(sessionId);
487
+ throw e;
488
+ }
423
489
  return session;
424
490
  }
425
491
  /**
@@ -459,38 +525,12 @@ class CopilotClient {
459
525
  throw new Error("Client not connected. Call start() first.");
460
526
  }
461
527
  }
462
- const response = await this.connection.sendRequest("session.resume", {
528
+ const session = new CopilotSession(
463
529
  sessionId,
464
- clientName: config.clientName,
465
- model: config.model,
466
- reasoningEffort: config.reasoningEffort,
467
- systemMessage: config.systemMessage,
468
- availableTools: config.availableTools,
469
- excludedTools: config.excludedTools,
470
- tools: config.tools?.map((tool) => ({
471
- name: tool.name,
472
- description: tool.description,
473
- parameters: toJsonSchema(tool.parameters),
474
- overridesBuiltInTool: tool.overridesBuiltInTool
475
- })),
476
- provider: config.provider,
477
- requestPermission: true,
478
- requestUserInput: !!config.onUserInputRequest,
479
- hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
480
- workingDirectory: config.workingDirectory,
481
- configDir: config.configDir,
482
- streaming: config.streaming,
483
- mcpServers: config.mcpServers,
484
- envValueMode: "direct",
485
- customAgents: config.customAgents,
486
- agent: config.agent,
487
- skillDirectories: config.skillDirectories,
488
- disabledSkills: config.disabledSkills,
489
- infiniteSessions: config.infiniteSessions,
490
- disableResume: config.disableResume
491
- });
492
- const { sessionId: resumedSessionId, workspacePath } = response;
493
- const session = new CopilotSession(resumedSessionId, this.connection, workspacePath);
530
+ this.connection,
531
+ void 0,
532
+ this.onGetTraceContext
533
+ );
494
534
  session.registerTools(config.tools);
495
535
  session.registerPermissionHandler(config.onPermissionRequest);
496
536
  if (config.onUserInputRequest) {
@@ -499,7 +539,55 @@ class CopilotClient {
499
539
  if (config.hooks) {
500
540
  session.registerHooks(config.hooks);
501
541
  }
502
- this.sessions.set(resumedSessionId, session);
542
+ const { wirePayload: wireSystemMessage, transformCallbacks } = extractTransformCallbacks(
543
+ config.systemMessage
544
+ );
545
+ if (transformCallbacks) {
546
+ session.registerTransformCallbacks(transformCallbacks);
547
+ }
548
+ if (config.onEvent) {
549
+ session.on(config.onEvent);
550
+ }
551
+ this.sessions.set(sessionId, session);
552
+ try {
553
+ const response = await this.connection.sendRequest("session.resume", {
554
+ ...await getTraceContext(this.onGetTraceContext),
555
+ sessionId,
556
+ clientName: config.clientName,
557
+ model: config.model,
558
+ reasoningEffort: config.reasoningEffort,
559
+ systemMessage: wireSystemMessage,
560
+ availableTools: config.availableTools,
561
+ excludedTools: config.excludedTools,
562
+ tools: config.tools?.map((tool) => ({
563
+ name: tool.name,
564
+ description: tool.description,
565
+ parameters: toJsonSchema(tool.parameters),
566
+ overridesBuiltInTool: tool.overridesBuiltInTool,
567
+ skipPermission: tool.skipPermission
568
+ })),
569
+ provider: config.provider,
570
+ requestPermission: true,
571
+ requestUserInput: !!config.onUserInputRequest,
572
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
573
+ workingDirectory: config.workingDirectory,
574
+ configDir: config.configDir,
575
+ streaming: config.streaming,
576
+ mcpServers: config.mcpServers,
577
+ envValueMode: "direct",
578
+ customAgents: config.customAgents,
579
+ agent: config.agent,
580
+ skillDirectories: config.skillDirectories,
581
+ disabledSkills: config.disabledSkills,
582
+ infiniteSessions: config.infiniteSessions,
583
+ disableResume: config.disableResume
584
+ });
585
+ const { workspacePath } = response;
586
+ session["_workspacePath"] = workspacePath;
587
+ } catch (e) {
588
+ this.sessions.delete(sessionId);
589
+ throw e;
590
+ }
503
591
  return session;
504
592
  }
505
593
  /**
@@ -802,6 +890,27 @@ class CopilotClient {
802
890
  if (this.options.githubToken) {
803
891
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken;
804
892
  }
893
+ if (!this.options.cliPath) {
894
+ throw new Error(
895
+ "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
896
+ );
897
+ }
898
+ if (this.options.telemetry) {
899
+ const t = this.options.telemetry;
900
+ envWithoutNodeDebug.COPILOT_OTEL_ENABLED = "true";
901
+ if (t.otlpEndpoint !== void 0)
902
+ envWithoutNodeDebug.OTEL_EXPORTER_OTLP_ENDPOINT = t.otlpEndpoint;
903
+ if (t.filePath !== void 0)
904
+ envWithoutNodeDebug.COPILOT_OTEL_FILE_EXPORTER_PATH = t.filePath;
905
+ if (t.exporterType !== void 0)
906
+ envWithoutNodeDebug.COPILOT_OTEL_EXPORTER_TYPE = t.exporterType;
907
+ if (t.sourceName !== void 0)
908
+ envWithoutNodeDebug.COPILOT_OTEL_SOURCE_NAME = t.sourceName;
909
+ if (t.captureContent !== void 0)
910
+ envWithoutNodeDebug.OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = String(
911
+ t.captureContent
912
+ );
913
+ }
805
914
  if (!existsSync(this.options.cliPath)) {
806
915
  throw new Error(
807
916
  `Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
@@ -901,8 +1010,6 @@ stderr: ${stderrOutput}`
901
1010
  } else {
902
1011
  reject(new Error(`CLI server exited with code ${code}`));
903
1012
  }
904
- } else if (this.options.autoRestart && this.state === "connected") {
905
- void this.reconnect();
906
1013
  }
907
1014
  });
908
1015
  setTimeout(() => {
@@ -1007,12 +1114,15 @@ stderr: ${stderrOutput}`
1007
1114
  "hooks.invoke",
1008
1115
  async (params) => await this.handleHooksInvoke(params)
1009
1116
  );
1117
+ this.connection.onRequest(
1118
+ "systemMessage.transform",
1119
+ async (params) => await this.handleSystemMessageTransform(params)
1120
+ );
1010
1121
  this.connection.onClose(() => {
1011
- if (this.state === "connected" && this.options.autoRestart) {
1012
- void this.reconnect();
1013
- }
1122
+ this.state = "disconnected";
1014
1123
  });
1015
1124
  this.connection.onError((_error) => {
1125
+ this.state = "disconnected";
1016
1126
  });
1017
1127
  }
1018
1128
  handleSessionEventNotification(notification) {
@@ -1071,6 +1181,16 @@ stderr: ${stderrOutput}`
1071
1181
  const output = await session._handleHooksInvoke(params.hookType, params.input);
1072
1182
  return { output };
1073
1183
  }
1184
+ async handleSystemMessageTransform(params) {
1185
+ if (!params || typeof params.sessionId !== "string" || !params.sections || typeof params.sections !== "object") {
1186
+ throw new Error("Invalid systemMessage.transform payload");
1187
+ }
1188
+ const session = this.sessions.get(params.sessionId);
1189
+ if (!session) {
1190
+ throw new Error(`Session not found: ${params.sessionId}`);
1191
+ }
1192
+ return await session._handleSystemMessageTransform(params.sections);
1193
+ }
1074
1194
  // ========================================================================
1075
1195
  // Protocol v2 backward-compatibility adapters
1076
1196
  // ========================================================================
@@ -1099,11 +1219,15 @@ stderr: ${stderrOutput}`
1099
1219
  };
1100
1220
  }
1101
1221
  try {
1222
+ const traceparent = params.traceparent;
1223
+ const tracestate = params.tracestate;
1102
1224
  const invocation = {
1103
1225
  sessionId: params.sessionId,
1104
1226
  toolCallId: params.toolCallId,
1105
1227
  toolName: params.toolName,
1106
- arguments: params.arguments
1228
+ arguments: params.arguments,
1229
+ traceparent,
1230
+ tracestate
1107
1231
  };
1108
1232
  const result = await handler(params.arguments, invocation);
1109
1233
  return { result: this.normalizeToolResultV2(result) };
@@ -1133,7 +1257,10 @@ stderr: ${stderrOutput}`
1133
1257
  try {
1134
1258
  const result = await session._handlePermissionRequestV2(params.permissionRequest);
1135
1259
  return { result };
1136
- } catch (_error) {
1260
+ } catch (error) {
1261
+ if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) {
1262
+ throw error;
1263
+ }
1137
1264
  return {
1138
1265
  result: {
1139
1266
  kind: "denied-no-approval-rule-and-could-not-request-from-user"
@@ -1163,17 +1290,6 @@ stderr: ${stderrOutput}`
1163
1290
  isToolResultObject(value) {
1164
1291
  return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
1165
1292
  }
1166
- /**
1167
- * Attempt to reconnect to the server
1168
- */
1169
- async reconnect() {
1170
- this.state = "disconnected";
1171
- try {
1172
- await this.stop();
1173
- await this.start();
1174
- } catch (_error) {
1175
- }
1176
- }
1177
1293
  }
1178
1294
  export {
1179
1295
  CopilotClient
@@ -1,5 +1,8 @@
1
1
  import type { CopilotSession } from "./session.js";
2
- import type { ResumeSessionConfig } from "./types.js";
2
+ import type { PermissionHandler, ResumeSessionConfig } from "./types.js";
3
+ export type JoinSessionConfig = Omit<ResumeSessionConfig, "onPermissionRequest"> & {
4
+ onPermissionRequest?: PermissionHandler;
5
+ };
3
6
  /**
4
7
  * Joins the current foreground session.
5
8
  *
@@ -8,13 +11,9 @@ import type { ResumeSessionConfig } from "./types.js";
8
11
  *
9
12
  * @example
10
13
  * ```typescript
11
- * import { approveAll } from "@github/copilot-sdk";
12
14
  * import { joinSession } from "@github/copilot-sdk/extension";
13
15
  *
14
- * const session = await joinSession({
15
- * onPermissionRequest: approveAll,
16
- * tools: [myTool],
17
- * });
16
+ * const session = await joinSession({ tools: [myTool] });
18
17
  * ```
19
18
  */
20
- export declare function joinSession(config: ResumeSessionConfig): Promise<CopilotSession>;
19
+ export declare function joinSession(config?: JoinSessionConfig): Promise<CopilotSession>;
package/dist/extension.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { CopilotClient } from "./client.js";
2
- async function joinSession(config) {
2
+ const defaultJoinSessionPermissionHandler = () => ({
3
+ kind: "no-result"
4
+ });
5
+ async function joinSession(config = {}) {
3
6
  const sessionId = process.env.SESSION_ID;
4
7
  if (!sessionId) {
5
8
  throw new Error(
@@ -9,6 +12,7 @@ async function joinSession(config) {
9
12
  const client = new CopilotClient({ isChildProcess: true });
10
13
  return client.resumeSession(sessionId, {
11
14
  ...config,
15
+ onPermissionRequest: config.onPermissionRequest ?? defaultJoinSessionPermissionHandler,
12
16
  disableResume: config.disableResume ?? true
13
17
  });
14
18
  }