@github/copilot-sdk 0.1.32 → 0.1.33-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/dist/client.d.ts CHANGED
@@ -1,6 +1,39 @@
1
1
  import { createServerRpc } from "./generated/rpc.js";
2
2
  import { CopilotSession } from "./session.js";
3
3
  import type { ConnectionState, CopilotClientOptions, GetAuthStatusResponse, GetStatusResponse, ModelInfo, ResumeSessionConfig, SessionConfig, SessionLifecycleEventType, SessionLifecycleHandler, SessionListFilter, SessionMetadata, TypedSessionLifecycleHandler } from "./types.js";
4
+ /**
5
+ * Main client for interacting with the Copilot CLI.
6
+ *
7
+ * The CopilotClient manages the connection to the Copilot CLI server and provides
8
+ * methods to create and manage conversation sessions. It can either spawn a CLI
9
+ * server process or connect to an existing server.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { CopilotClient } from "@github/copilot-sdk";
14
+ *
15
+ * // Create a client with default options (spawns CLI server)
16
+ * const client = new CopilotClient();
17
+ *
18
+ * // Or connect to an existing server
19
+ * const client = new CopilotClient({ cliUrl: "localhost:3000" });
20
+ *
21
+ * // Create a session
22
+ * const session = await client.createSession({ onPermissionRequest: approveAll, model: "gpt-4" });
23
+ *
24
+ * // Send messages and handle responses
25
+ * session.on((event) => {
26
+ * if (event.type === "assistant.message") {
27
+ * console.log(event.data.content);
28
+ * }
29
+ * });
30
+ * await session.send({ prompt: "Hello!" });
31
+ *
32
+ * // Clean up
33
+ * await session.disconnect();
34
+ * await client.stop();
35
+ * ```
36
+ */
4
37
  export declare class CopilotClient {
5
38
  private cliProcess;
6
39
  private connection;
@@ -13,6 +46,7 @@ export declare class CopilotClient {
13
46
  private options;
14
47
  private isExternalServer;
15
48
  private forceStopping;
49
+ private onListModels?;
16
50
  private modelsCache;
17
51
  private modelsCacheLock;
18
52
  private sessionLifecycleHandlers;
@@ -218,10 +252,13 @@ export declare class CopilotClient {
218
252
  /**
219
253
  * List available models with their metadata.
220
254
  *
255
+ * If an `onListModels` handler was provided in the client options,
256
+ * it is called instead of querying the CLI server.
257
+ *
221
258
  * Results are cached after the first successful call to avoid rate limiting.
222
259
  * The cache is cleared when the client disconnects.
223
260
  *
224
- * @throws Error if not authenticated
261
+ * @throws Error if not connected (when no custom handler is set)
225
262
  */
226
263
  listModels(): Promise<ModelInfo[]>;
227
264
  /**
package/dist/client.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { existsSync } from "node:fs";
3
4
  import { Socket } from "node:net";
4
5
  import { dirname, join } from "node:path";
@@ -10,7 +11,7 @@ import {
10
11
  } from "vscode-jsonrpc/node.js";
11
12
  import { createServerRpc } from "./generated/rpc.js";
12
13
  import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
13
- import { CopilotSession } from "./session.js";
14
+ import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js";
14
15
  const MIN_PROTOCOL_VERSION = 2;
15
16
  function isZodSchema(value) {
16
17
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
@@ -46,6 +47,7 @@ class CopilotClient {
46
47
  options;
47
48
  isExternalServer = false;
48
49
  forceStopping = false;
50
+ onListModels;
49
51
  modelsCache = null;
50
52
  modelsCacheLock = Promise.resolve();
51
53
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
@@ -111,8 +113,9 @@ class CopilotClient {
111
113
  if (options.isChildProcess) {
112
114
  this.isExternalServer = true;
113
115
  }
116
+ this.onListModels = options.onListModels;
114
117
  this.options = {
115
- cliPath: options.cliPath || getBundledCliPath(),
118
+ cliPath: options.cliUrl ? void 0 : options.cliPath || getBundledCliPath(),
116
119
  cliArgs: options.cliArgs ?? [],
117
120
  cwd: options.cwd ?? process.cwd(),
118
121
  port: options.port || 0,
@@ -378,36 +381,8 @@ class CopilotClient {
378
381
  throw new Error("Client not connected. Call start() first.");
379
382
  }
380
383
  }
381
- const response = await this.connection.sendRequest("session.create", {
382
- model: config.model,
383
- sessionId: config.sessionId,
384
- clientName: config.clientName,
385
- reasoningEffort: config.reasoningEffort,
386
- tools: config.tools?.map((tool) => ({
387
- name: tool.name,
388
- description: tool.description,
389
- parameters: toJsonSchema(tool.parameters),
390
- overridesBuiltInTool: tool.overridesBuiltInTool
391
- })),
392
- systemMessage: config.systemMessage,
393
- availableTools: config.availableTools,
394
- excludedTools: config.excludedTools,
395
- provider: config.provider,
396
- requestPermission: true,
397
- requestUserInput: !!config.onUserInputRequest,
398
- hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
399
- workingDirectory: config.workingDirectory,
400
- streaming: config.streaming,
401
- mcpServers: config.mcpServers,
402
- envValueMode: "direct",
403
- customAgents: config.customAgents,
404
- configDir: config.configDir,
405
- skillDirectories: config.skillDirectories,
406
- disabledSkills: config.disabledSkills,
407
- infiniteSessions: config.infiniteSessions
408
- });
409
- const { sessionId, workspacePath } = response;
410
- const session = new CopilotSession(sessionId, this.connection, workspacePath);
384
+ const sessionId = config.sessionId ?? randomUUID();
385
+ const session = new CopilotSession(sessionId, this.connection);
411
386
  session.registerTools(config.tools);
412
387
  session.registerPermissionHandler(config.onPermissionRequest);
413
388
  if (config.onUserInputRequest) {
@@ -416,7 +391,46 @@ class CopilotClient {
416
391
  if (config.hooks) {
417
392
  session.registerHooks(config.hooks);
418
393
  }
394
+ if (config.onEvent) {
395
+ session.on(config.onEvent);
396
+ }
419
397
  this.sessions.set(sessionId, session);
398
+ try {
399
+ const response = await this.connection.sendRequest("session.create", {
400
+ model: config.model,
401
+ sessionId,
402
+ clientName: config.clientName,
403
+ reasoningEffort: config.reasoningEffort,
404
+ tools: config.tools?.map((tool) => ({
405
+ name: tool.name,
406
+ description: tool.description,
407
+ parameters: toJsonSchema(tool.parameters),
408
+ overridesBuiltInTool: tool.overridesBuiltInTool
409
+ })),
410
+ systemMessage: config.systemMessage,
411
+ availableTools: config.availableTools,
412
+ excludedTools: config.excludedTools,
413
+ provider: config.provider,
414
+ requestPermission: true,
415
+ requestUserInput: !!config.onUserInputRequest,
416
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
417
+ workingDirectory: config.workingDirectory,
418
+ streaming: config.streaming,
419
+ mcpServers: config.mcpServers,
420
+ envValueMode: "direct",
421
+ customAgents: config.customAgents,
422
+ agent: config.agent,
423
+ configDir: config.configDir,
424
+ skillDirectories: config.skillDirectories,
425
+ disabledSkills: config.disabledSkills,
426
+ infiniteSessions: config.infiniteSessions
427
+ });
428
+ const { workspacePath } = response;
429
+ session["_workspacePath"] = workspacePath;
430
+ } catch (e) {
431
+ this.sessions.delete(sessionId);
432
+ throw e;
433
+ }
420
434
  return session;
421
435
  }
422
436
  /**
@@ -456,37 +470,7 @@ class CopilotClient {
456
470
  throw new Error("Client not connected. Call start() first.");
457
471
  }
458
472
  }
459
- const response = await this.connection.sendRequest("session.resume", {
460
- sessionId,
461
- clientName: config.clientName,
462
- model: config.model,
463
- reasoningEffort: config.reasoningEffort,
464
- systemMessage: config.systemMessage,
465
- availableTools: config.availableTools,
466
- excludedTools: config.excludedTools,
467
- tools: config.tools?.map((tool) => ({
468
- name: tool.name,
469
- description: tool.description,
470
- parameters: toJsonSchema(tool.parameters),
471
- overridesBuiltInTool: tool.overridesBuiltInTool
472
- })),
473
- provider: config.provider,
474
- requestPermission: true,
475
- requestUserInput: !!config.onUserInputRequest,
476
- hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
477
- workingDirectory: config.workingDirectory,
478
- configDir: config.configDir,
479
- streaming: config.streaming,
480
- mcpServers: config.mcpServers,
481
- envValueMode: "direct",
482
- customAgents: config.customAgents,
483
- skillDirectories: config.skillDirectories,
484
- disabledSkills: config.disabledSkills,
485
- infiniteSessions: config.infiniteSessions,
486
- disableResume: config.disableResume
487
- });
488
- const { sessionId: resumedSessionId, workspacePath } = response;
489
- const session = new CopilotSession(resumedSessionId, this.connection, workspacePath);
473
+ const session = new CopilotSession(sessionId, this.connection);
490
474
  session.registerTools(config.tools);
491
475
  session.registerPermissionHandler(config.onPermissionRequest);
492
476
  if (config.onUserInputRequest) {
@@ -495,7 +479,47 @@ class CopilotClient {
495
479
  if (config.hooks) {
496
480
  session.registerHooks(config.hooks);
497
481
  }
498
- this.sessions.set(resumedSessionId, session);
482
+ if (config.onEvent) {
483
+ session.on(config.onEvent);
484
+ }
485
+ this.sessions.set(sessionId, session);
486
+ try {
487
+ const response = await this.connection.sendRequest("session.resume", {
488
+ sessionId,
489
+ clientName: config.clientName,
490
+ model: config.model,
491
+ reasoningEffort: config.reasoningEffort,
492
+ systemMessage: config.systemMessage,
493
+ availableTools: config.availableTools,
494
+ excludedTools: config.excludedTools,
495
+ tools: config.tools?.map((tool) => ({
496
+ name: tool.name,
497
+ description: tool.description,
498
+ parameters: toJsonSchema(tool.parameters),
499
+ overridesBuiltInTool: tool.overridesBuiltInTool
500
+ })),
501
+ provider: config.provider,
502
+ requestPermission: true,
503
+ requestUserInput: !!config.onUserInputRequest,
504
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
505
+ workingDirectory: config.workingDirectory,
506
+ configDir: config.configDir,
507
+ streaming: config.streaming,
508
+ mcpServers: config.mcpServers,
509
+ envValueMode: "direct",
510
+ customAgents: config.customAgents,
511
+ agent: config.agent,
512
+ skillDirectories: config.skillDirectories,
513
+ disabledSkills: config.disabledSkills,
514
+ infiniteSessions: config.infiniteSessions,
515
+ disableResume: config.disableResume
516
+ });
517
+ const { workspacePath } = response;
518
+ session["_workspacePath"] = workspacePath;
519
+ } catch (e) {
520
+ this.sessions.delete(sessionId);
521
+ throw e;
522
+ }
499
523
  return session;
500
524
  }
501
525
  /**
@@ -556,15 +580,15 @@ class CopilotClient {
556
580
  /**
557
581
  * List available models with their metadata.
558
582
  *
583
+ * If an `onListModels` handler was provided in the client options,
584
+ * it is called instead of querying the CLI server.
585
+ *
559
586
  * Results are cached after the first successful call to avoid rate limiting.
560
587
  * The cache is cleared when the client disconnects.
561
588
  *
562
- * @throws Error if not authenticated
589
+ * @throws Error if not connected (when no custom handler is set)
563
590
  */
564
591
  async listModels() {
565
- if (!this.connection) {
566
- throw new Error("Client not connected");
567
- }
568
592
  await this.modelsCacheLock;
569
593
  let resolveLock;
570
594
  this.modelsCacheLock = new Promise((resolve) => {
@@ -574,10 +598,18 @@ class CopilotClient {
574
598
  if (this.modelsCache !== null) {
575
599
  return [...this.modelsCache];
576
600
  }
577
- const result = await this.connection.sendRequest("models.list", {});
578
- const response = result;
579
- const models = response.models;
580
- this.modelsCache = models;
601
+ let models;
602
+ if (this.onListModels) {
603
+ models = await this.onListModels();
604
+ } else {
605
+ if (!this.connection) {
606
+ throw new Error("Client not connected");
607
+ }
608
+ const result = await this.connection.sendRequest("models.list", {});
609
+ const response = result;
610
+ models = response.models;
611
+ }
612
+ this.modelsCache = [...models];
581
613
  return [...models];
582
614
  } finally {
583
615
  resolveLock();
@@ -790,6 +822,11 @@ class CopilotClient {
790
822
  if (this.options.githubToken) {
791
823
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken;
792
824
  }
825
+ if (!this.options.cliPath) {
826
+ throw new Error(
827
+ "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
828
+ );
829
+ }
793
830
  if (!existsSync(this.options.cliPath)) {
794
831
  throw new Error(
795
832
  `Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`
@@ -1121,7 +1158,10 @@ stderr: ${stderrOutput}`
1121
1158
  try {
1122
1159
  const result = await session._handlePermissionRequestV2(params.permissionRequest);
1123
1160
  return { result };
1124
- } catch (_error) {
1161
+ } catch (error) {
1162
+ if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) {
1163
+ throw error;
1164
+ }
1125
1165
  return {
1126
1166
  result: {
1127
1167
  kind: "denied-no-approval-rule-and-could-not-request-from-user"
@@ -1,2 +1,19 @@
1
- import { CopilotClient } from "./client.js";
2
- export declare const extension: CopilotClient;
1
+ import type { CopilotSession } from "./session.js";
2
+ import type { PermissionHandler, ResumeSessionConfig } from "./types.js";
3
+ export type JoinSessionConfig = Omit<ResumeSessionConfig, "onPermissionRequest"> & {
4
+ onPermissionRequest?: PermissionHandler;
5
+ };
6
+ /**
7
+ * Joins the current foreground session.
8
+ *
9
+ * @param config - Configuration to add to the session
10
+ * @returns A promise that resolves with the joined session
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { joinSession } from "@github/copilot-sdk/extension";
15
+ *
16
+ * const session = await joinSession({ tools: [myTool] });
17
+ * ```
18
+ */
19
+ export declare function joinSession(config?: JoinSessionConfig): Promise<CopilotSession>;
package/dist/extension.js CHANGED
@@ -1,5 +1,21 @@
1
1
  import { CopilotClient } from "./client.js";
2
- const extension = new CopilotClient({ isChildProcess: true });
2
+ const defaultJoinSessionPermissionHandler = () => ({
3
+ kind: "no-result"
4
+ });
5
+ async function joinSession(config = {}) {
6
+ const sessionId = process.env.SESSION_ID;
7
+ if (!sessionId) {
8
+ throw new Error(
9
+ "joinSession() is intended for extensions running as child processes of the Copilot CLI."
10
+ );
11
+ }
12
+ const client = new CopilotClient({ isChildProcess: true });
13
+ return client.resumeSession(sessionId, {
14
+ ...config,
15
+ onPermissionRequest: config.onPermissionRequest ?? defaultJoinSessionPermissionHandler,
16
+ disableResume: config.disableResume ?? true
17
+ });
18
+ }
3
19
  export {
4
- extension
20
+ joinSession
5
21
  };
@@ -40,16 +40,34 @@ export interface ModelsListResult {
40
40
  * Model capabilities and limits
41
41
  */
42
42
  capabilities: {
43
+ /**
44
+ * Feature flags indicating what the model supports
45
+ */
43
46
  supports: {
47
+ /**
48
+ * Whether this model supports vision/image input
49
+ */
44
50
  vision?: boolean;
45
51
  /**
46
52
  * Whether this model supports reasoning effort configuration
47
53
  */
48
54
  reasoningEffort?: boolean;
49
55
  };
56
+ /**
57
+ * Token limits for prompts, outputs, and context window
58
+ */
50
59
  limits: {
60
+ /**
61
+ * Maximum number of prompt/input tokens
62
+ */
51
63
  max_prompt_tokens?: number;
64
+ /**
65
+ * Maximum number of output/completion tokens
66
+ */
52
67
  max_output_tokens?: number;
68
+ /**
69
+ * Maximum total context window size in tokens
70
+ */
53
71
  max_context_window_tokens: number;
54
72
  };
55
73
  };
@@ -57,13 +75,22 @@ export interface ModelsListResult {
57
75
  * Policy state (if applicable)
58
76
  */
59
77
  policy?: {
78
+ /**
79
+ * Current policy state for this model
80
+ */
60
81
  state: string;
82
+ /**
83
+ * Usage terms or conditions for this model
84
+ */
61
85
  terms: string;
62
86
  };
63
87
  /**
64
88
  * Billing information
65
89
  */
66
90
  billing?: {
91
+ /**
92
+ * Billing cost multiplier relative to the base rate
93
+ */
67
94
  multiplier: number;
68
95
  };
69
96
  /**
@@ -145,6 +172,9 @@ export interface AccountGetQuotaResult {
145
172
  };
146
173
  }
147
174
  export interface SessionModelGetCurrentResult {
175
+ /**
176
+ * Currently active model identifier
177
+ */
148
178
  modelId?: string;
149
179
  }
150
180
  export interface SessionModelGetCurrentParams {
@@ -154,6 +184,9 @@ export interface SessionModelGetCurrentParams {
154
184
  sessionId: string;
155
185
  }
156
186
  export interface SessionModelSwitchToResult {
187
+ /**
188
+ * Currently active model identifier after the switch
189
+ */
157
190
  modelId?: string;
158
191
  }
159
192
  export interface SessionModelSwitchToParams {
@@ -161,7 +194,14 @@ export interface SessionModelSwitchToParams {
161
194
  * Target session identifier
162
195
  */
163
196
  sessionId: string;
197
+ /**
198
+ * Model identifier to switch to
199
+ */
164
200
  modelId: string;
201
+ /**
202
+ * Reasoning effort level to use for the model
203
+ */
204
+ reasoningEffort?: string;
165
205
  }
166
206
  export interface SessionModeGetResult {
167
207
  /**
@@ -399,6 +439,9 @@ export interface SessionCompactionCompactParams {
399
439
  sessionId: string;
400
440
  }
401
441
  export interface SessionToolsHandlePendingToolCallResult {
442
+ /**
443
+ * Whether the tool call result was handled successfully
444
+ */
402
445
  success: boolean;
403
446
  }
404
447
  export interface SessionToolsHandlePendingToolCallParams {
@@ -418,6 +461,9 @@ export interface SessionToolsHandlePendingToolCallParams {
418
461
  error?: string;
419
462
  }
420
463
  export interface SessionPermissionsHandlePendingPermissionRequestResult {
464
+ /**
465
+ * Whether the permission request was handled successfully
466
+ */
421
467
  success: boolean;
422
468
  }
423
469
  export interface SessionPermissionsHandlePendingPermissionRequestParams {
@@ -442,6 +488,74 @@ export interface SessionPermissionsHandlePendingPermissionRequestParams {
442
488
  message: string;
443
489
  };
444
490
  }
491
+ export interface SessionLogResult {
492
+ /**
493
+ * The unique identifier of the emitted session event
494
+ */
495
+ eventId: string;
496
+ }
497
+ export interface SessionLogParams {
498
+ /**
499
+ * Target session identifier
500
+ */
501
+ sessionId: string;
502
+ /**
503
+ * Human-readable message
504
+ */
505
+ message: string;
506
+ /**
507
+ * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info".
508
+ */
509
+ level?: "info" | "warning" | "error";
510
+ /**
511
+ * When true, the message is transient and not persisted to the session event log on disk
512
+ */
513
+ ephemeral?: boolean;
514
+ }
515
+ export interface SessionShellExecResult {
516
+ /**
517
+ * Unique identifier for tracking streamed output
518
+ */
519
+ processId: string;
520
+ }
521
+ export interface SessionShellExecParams {
522
+ /**
523
+ * Target session identifier
524
+ */
525
+ sessionId: string;
526
+ /**
527
+ * Shell command to execute
528
+ */
529
+ command: string;
530
+ /**
531
+ * Working directory (defaults to session working directory)
532
+ */
533
+ cwd?: string;
534
+ /**
535
+ * Timeout in milliseconds (default: 30000)
536
+ */
537
+ timeout?: number;
538
+ }
539
+ export interface SessionShellKillResult {
540
+ /**
541
+ * Whether the signal was sent successfully
542
+ */
543
+ killed: boolean;
544
+ }
545
+ export interface SessionShellKillParams {
546
+ /**
547
+ * Target session identifier
548
+ */
549
+ sessionId: string;
550
+ /**
551
+ * Process identifier returned by shell.exec
552
+ */
553
+ processId: string;
554
+ /**
555
+ * Signal to send (default: SIGTERM)
556
+ */
557
+ signal?: "SIGTERM" | "SIGKILL" | "SIGINT";
558
+ }
445
559
  /** Create typed server-scoped RPC methods (no session required). */
446
560
  export declare function createServerRpc(connection: MessageConnection): {
447
561
  ping: (params: PingParams) => Promise<PingResult>;
@@ -493,4 +607,9 @@ export declare function createSessionRpc(connection: MessageConnection, sessionI
493
607
  permissions: {
494
608
  handlePendingPermissionRequest: (params: Omit<SessionPermissionsHandlePendingPermissionRequestParams, "sessionId">) => Promise<SessionPermissionsHandlePendingPermissionRequestResult>;
495
609
  };
610
+ log: (params: Omit<SessionLogParams, "sessionId">) => Promise<SessionLogResult>;
611
+ shell: {
612
+ exec: (params: Omit<SessionShellExecParams, "sessionId">) => Promise<SessionShellExecResult>;
613
+ kill: (params: Omit<SessionShellKillParams, "sessionId">) => Promise<SessionShellKillResult>;
614
+ };
496
615
  };
@@ -49,6 +49,11 @@ function createSessionRpc(connection, sessionId) {
49
49
  },
50
50
  permissions: {
51
51
  handlePendingPermissionRequest: async (params) => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params })
52
+ },
53
+ log: async (params) => connection.sendRequest("session.log", { sessionId, ...params }),
54
+ shell: {
55
+ exec: async (params) => connection.sendRequest("session.shell.exec", { sessionId, ...params }),
56
+ kill: async (params) => connection.sendRequest("session.shell.kill", { sessionId, ...params })
52
57
  }
53
58
  };
54
59
  }