@github/copilot-sdk 0.1.18 → 0.1.20

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
@@ -24,14 +24,13 @@ const session = await client.createSession({
24
24
  model: "gpt-5",
25
25
  });
26
26
 
27
- // Wait for response using session.idle event
27
+ // Wait for response using typed event handlers
28
28
  const done = new Promise<void>((resolve) => {
29
- session.on((event) => {
30
- if (event.type === "assistant.message") {
31
- console.log(event.data.content);
32
- } else if (event.type === "session.idle") {
33
- resolve();
34
- }
29
+ session.on("assistant.message", (event) => {
30
+ console.log(event.data.content);
31
+ });
32
+ session.on("session.idle", () => {
33
+ resolve();
35
34
  });
36
35
  });
37
36
 
@@ -64,6 +63,8 @@ new CopilotClient(options?: CopilotClientOptions)
64
63
  - `logLevel?: string` - Log level (default: "info")
65
64
  - `autoStart?: boolean` - Auto-start server (default: true)
66
65
  - `autoRestart?: boolean` - Auto-restart on crash (default: true)
66
+ - `githubToken?: string` - GitHub token for authentication. When provided, takes priority over other auth methods.
67
+ - `useLoggedInUser?: boolean` - Whether to use logged-in user for authentication (default: true, but false when `githubToken` is provided). Cannot be used with `cliUrl`.
67
68
 
68
69
  #### Methods
69
70
 
@@ -86,10 +87,13 @@ Create a new conversation session.
86
87
  **Config:**
87
88
 
88
89
  - `sessionId?: string` - Custom session ID
89
- - `model?: string` - Model to use ("gpt-5", "claude-sonnet-4.5", etc.)
90
+ - `model?: string` - Model to use ("gpt-5", "claude-sonnet-4.5", etc.). **Required when using custom provider.**
90
91
  - `tools?: Tool[]` - Custom tools exposed to the CLI
91
92
  - `systemMessage?: SystemMessageConfig` - System message customization (see below)
92
93
  - `infiniteSessions?: InfiniteSessionConfig` - Configure automatic context compaction (see below)
94
+ - `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section.
95
+ - `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section.
96
+ - `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
93
97
 
94
98
  ##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise<CopilotSession>`
95
99
 
@@ -154,13 +158,34 @@ Send a message and wait until the session becomes idle.
154
158
 
155
159
  Returns the final assistant message event, or undefined if none was received.
156
160
 
161
+ ##### `on(eventType: string, handler: TypedSessionEventHandler): () => void`
162
+
163
+ Subscribe to a specific event type. The handler receives properly typed events.
164
+
165
+ ```typescript
166
+ // Listen for specific event types with full type inference
167
+ session.on("assistant.message", (event) => {
168
+ console.log(event.data.content); // TypeScript knows about event.data.content
169
+ });
170
+
171
+ session.on("session.idle", () => {
172
+ console.log("Session is idle");
173
+ });
174
+
175
+ // Listen to streaming events
176
+ session.on("assistant.message_delta", (event) => {
177
+ process.stdout.write(event.data.deltaContent);
178
+ });
179
+ ```
180
+
157
181
  ##### `on(handler: SessionEventHandler): () => void`
158
182
 
159
- Subscribe to session events. Returns an unsubscribe function.
183
+ Subscribe to all session events. Returns an unsubscribe function.
160
184
 
161
185
  ```typescript
162
186
  const unsubscribe = session.on((event) => {
163
- console.log(event);
187
+ // Handle any event type
188
+ console.log(event.type, event);
164
189
  });
165
190
 
166
191
  // Later...
@@ -226,27 +251,33 @@ const session = await client.createSession({
226
251
  streaming: true,
227
252
  });
228
253
 
229
- // Wait for completion using session.idle event
254
+ // Wait for completion using typed event handlers
230
255
  const done = new Promise<void>((resolve) => {
231
- session.on((event) => {
232
- if (event.type === "assistant.message_delta") {
233
- // Streaming message chunk - print incrementally
234
- process.stdout.write(event.data.deltaContent);
235
- } else if (event.type === "assistant.reasoning_delta") {
236
- // Streaming reasoning chunk (if model supports reasoning)
237
- process.stdout.write(event.data.deltaContent);
238
- } else if (event.type === "assistant.message") {
239
- // Final message - complete content
240
- console.log("\n--- Final message ---");
241
- console.log(event.data.content);
242
- } else if (event.type === "assistant.reasoning") {
243
- // Final reasoning content (if model supports reasoning)
244
- console.log("--- Reasoning ---");
245
- console.log(event.data.content);
246
- } else if (event.type === "session.idle") {
247
- // Session finished processing
248
- resolve();
249
- }
256
+ session.on("assistant.message_delta", (event) => {
257
+ // Streaming message chunk - print incrementally
258
+ process.stdout.write(event.data.deltaContent);
259
+ });
260
+
261
+ session.on("assistant.reasoning_delta", (event) => {
262
+ // Streaming reasoning chunk (if model supports reasoning)
263
+ process.stdout.write(event.data.deltaContent);
264
+ });
265
+
266
+ session.on("assistant.message", (event) => {
267
+ // Final message - complete content
268
+ console.log("\n--- Final message ---");
269
+ console.log(event.data.content);
270
+ });
271
+
272
+ session.on("assistant.reasoning", (event) => {
273
+ // Final reasoning content (if model supports reasoning)
274
+ console.log("--- Reasoning ---");
275
+ console.log(event.data.content);
276
+ });
277
+
278
+ session.on("session.idle", () => {
279
+ // Session finished processing
280
+ resolve();
250
281
  });
251
282
  });
252
283
 
@@ -407,6 +438,163 @@ await session.send({
407
438
  });
408
439
  ```
409
440
 
441
+ ### Custom Providers
442
+
443
+ The SDK supports custom OpenAI-compatible API providers (BYOK - Bring Your Own Key), including local providers like Ollama. When using a custom provider, you must specify the `model` explicitly.
444
+
445
+ **ProviderConfig:**
446
+
447
+ - `type?: "openai" | "azure" | "anthropic"` - Provider type (default: "openai")
448
+ - `baseUrl: string` - API endpoint URL (required)
449
+ - `apiKey?: string` - API key (optional for local providers like Ollama)
450
+ - `bearerToken?: string` - Bearer token for authentication (takes precedence over apiKey)
451
+ - `wireApi?: "completions" | "responses"` - API format for OpenAI/Azure (default: "completions")
452
+ - `azure?.apiVersion?: string` - Azure API version (default: "2024-10-21")
453
+
454
+ **Example with Ollama:**
455
+
456
+ ```typescript
457
+ const session = await client.createSession({
458
+ model: "deepseek-coder-v2:16b", // Required when using custom provider
459
+ provider: {
460
+ type: "openai",
461
+ baseUrl: "http://localhost:11434/v1", // Ollama endpoint
462
+ // apiKey not required for Ollama
463
+ },
464
+ });
465
+
466
+ await session.sendAndWait({ prompt: "Hello!" });
467
+ ```
468
+
469
+ **Example with custom OpenAI-compatible API:**
470
+
471
+ ```typescript
472
+ const session = await client.createSession({
473
+ model: "gpt-4",
474
+ provider: {
475
+ type: "openai",
476
+ baseUrl: "https://my-api.example.com/v1",
477
+ apiKey: process.env.MY_API_KEY,
478
+ },
479
+ });
480
+ ```
481
+
482
+ **Example with Azure OpenAI:**
483
+
484
+ ```typescript
485
+ const session = await client.createSession({
486
+ model: "gpt-4",
487
+ provider: {
488
+ type: "azure", // Must be "azure" for Azure endpoints, NOT "openai"
489
+ baseUrl: "https://my-resource.openai.azure.com", // Just the host, no path
490
+ apiKey: process.env.AZURE_OPENAI_KEY,
491
+ azure: {
492
+ apiVersion: "2024-10-21",
493
+ },
494
+ },
495
+ });
496
+ ```
497
+
498
+ > **Important notes:**
499
+ > - When using a custom provider, the `model` parameter is **required**. The SDK will throw an error if no model is specified.
500
+ > - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`.
501
+ > - The `baseUrl` should be just the host (e.g., `https://my-resource.openai.azure.com`). Do **not** include `/openai/v1` in the URL - the SDK handles path construction automatically.
502
+
503
+ ## User Input Requests
504
+
505
+ Enable the agent to ask questions to the user using the `ask_user` tool by providing an `onUserInputRequest` handler:
506
+
507
+ ```typescript
508
+ const session = await client.createSession({
509
+ model: "gpt-5",
510
+ onUserInputRequest: async (request, invocation) => {
511
+ // request.question - The question to ask
512
+ // request.choices - Optional array of choices for multiple choice
513
+ // request.allowFreeform - Whether freeform input is allowed (default: true)
514
+
515
+ console.log(`Agent asks: ${request.question}`);
516
+ if (request.choices) {
517
+ console.log(`Choices: ${request.choices.join(", ")}`);
518
+ }
519
+
520
+ // Return the user's response
521
+ return {
522
+ answer: "User's answer here",
523
+ wasFreeform: true, // Whether the answer was freeform (not from choices)
524
+ };
525
+ },
526
+ });
527
+ ```
528
+
529
+ ## Session Hooks
530
+
531
+ Hook into session lifecycle events by providing handlers in the `hooks` configuration:
532
+
533
+ ```typescript
534
+ const session = await client.createSession({
535
+ model: "gpt-5",
536
+ hooks: {
537
+ // Called before each tool execution
538
+ onPreToolUse: async (input, invocation) => {
539
+ console.log(`About to run tool: ${input.toolName}`);
540
+ // Return permission decision and optionally modify args
541
+ return {
542
+ permissionDecision: "allow", // "allow", "deny", or "ask"
543
+ modifiedArgs: input.toolArgs, // Optionally modify tool arguments
544
+ additionalContext: "Extra context for the model",
545
+ };
546
+ },
547
+
548
+ // Called after each tool execution
549
+ onPostToolUse: async (input, invocation) => {
550
+ console.log(`Tool ${input.toolName} completed`);
551
+ // Optionally modify the result or add context
552
+ return {
553
+ additionalContext: "Post-execution notes",
554
+ };
555
+ },
556
+
557
+ // Called when user submits a prompt
558
+ onUserPromptSubmitted: async (input, invocation) => {
559
+ console.log(`User prompt: ${input.prompt}`);
560
+ return {
561
+ modifiedPrompt: input.prompt, // Optionally modify the prompt
562
+ };
563
+ },
564
+
565
+ // Called when session starts
566
+ onSessionStart: async (input, invocation) => {
567
+ console.log(`Session started from: ${input.source}`); // "startup", "resume", "new"
568
+ return {
569
+ additionalContext: "Session initialization context",
570
+ };
571
+ },
572
+
573
+ // Called when session ends
574
+ onSessionEnd: async (input, invocation) => {
575
+ console.log(`Session ended: ${input.reason}`);
576
+ },
577
+
578
+ // Called when an error occurs
579
+ onErrorOccurred: async (input, invocation) => {
580
+ console.error(`Error in ${input.errorContext}: ${input.error}`);
581
+ return {
582
+ errorHandling: "retry", // "retry", "skip", or "abort"
583
+ };
584
+ },
585
+ },
586
+ });
587
+ ```
588
+
589
+ **Available hooks:**
590
+
591
+ - `onPreToolUse` - Intercept tool calls before execution. Can allow/deny or modify arguments.
592
+ - `onPostToolUse` - Process tool results after execution. Can modify results or add context.
593
+ - `onUserPromptSubmitted` - Intercept user prompts. Can modify the prompt before processing.
594
+ - `onSessionStart` - Run logic when a session starts or resumes.
595
+ - `onSessionEnd` - Cleanup or logging when session ends.
596
+ - `onErrorOccurred` - Handle errors with retry/skip/abort strategies.
597
+
410
598
  ## Error Handling
411
599
 
412
600
  ```typescript
package/dist/client.d.ts CHANGED
@@ -309,6 +309,8 @@ export declare class CopilotClient {
309
309
  private handleToolCallRequest;
310
310
  private executeToolCall;
311
311
  private handlePermissionRequest;
312
+ private handleUserInputRequest;
313
+ private handleHooksInvoke;
312
314
  private normalizeToolResult;
313
315
  private isToolResultObject;
314
316
  private buildUnsupportedToolResult;
package/dist/client.js CHANGED
@@ -53,6 +53,11 @@ class CopilotClient {
53
53
  if (options.cliUrl && (options.useStdio === true || options.cliPath)) {
54
54
  throw new Error("cliUrl is mutually exclusive with useStdio and cliPath");
55
55
  }
56
+ if (options.cliUrl && (options.githubToken || options.useLoggedInUser !== void 0)) {
57
+ throw new Error(
58
+ "githubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
59
+ );
60
+ }
56
61
  if (options.cliUrl) {
57
62
  const { host, port } = this.parseCliUrl(options.cliUrl);
58
63
  this.actualHost = host;
@@ -70,7 +75,10 @@ class CopilotClient {
70
75
  logLevel: options.logLevel || "debug",
71
76
  autoStart: options.autoStart ?? true,
72
77
  autoRestart: options.autoRestart ?? true,
73
- env: options.env ?? process.env
78
+ env: options.env ?? process.env,
79
+ githubToken: options.githubToken,
80
+ // Default useLoggedInUser to false when githubToken is provided, otherwise true
81
+ useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true)
74
82
  };
75
83
  }
76
84
  /**
@@ -317,6 +325,9 @@ class CopilotClient {
317
325
  excludedTools: config.excludedTools,
318
326
  provider: config.provider,
319
327
  requestPermission: !!config.onPermissionRequest,
328
+ requestUserInput: !!config.onUserInputRequest,
329
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
330
+ workingDirectory: config.workingDirectory,
320
331
  streaming: config.streaming,
321
332
  mcpServers: config.mcpServers,
322
333
  customAgents: config.customAgents,
@@ -331,6 +342,12 @@ class CopilotClient {
331
342
  if (config.onPermissionRequest) {
332
343
  session.registerPermissionHandler(config.onPermissionRequest);
333
344
  }
345
+ if (config.onUserInputRequest) {
346
+ session.registerUserInputHandler(config.onUserInputRequest);
347
+ }
348
+ if (config.hooks) {
349
+ session.registerHooks(config.hooks);
350
+ }
334
351
  this.sessions.set(sessionId, session);
335
352
  return session;
336
353
  }
@@ -374,11 +391,15 @@ class CopilotClient {
374
391
  })),
375
392
  provider: config.provider,
376
393
  requestPermission: !!config.onPermissionRequest,
394
+ requestUserInput: !!config.onUserInputRequest,
395
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
396
+ workingDirectory: config.workingDirectory,
377
397
  streaming: config.streaming,
378
398
  mcpServers: config.mcpServers,
379
399
  customAgents: config.customAgents,
380
400
  skillDirectories: config.skillDirectories,
381
- disabledSkills: config.disabledSkills
401
+ disabledSkills: config.disabledSkills,
402
+ disableResume: config.disableResume
382
403
  });
383
404
  const { sessionId: resumedSessionId, workspacePath } = response;
384
405
  const session = new CopilotSession(resumedSessionId, this.connection, workspacePath);
@@ -386,6 +407,12 @@ class CopilotClient {
386
407
  if (config.onPermissionRequest) {
387
408
  session.registerPermissionHandler(config.onPermissionRequest);
388
409
  }
410
+ if (config.onUserInputRequest) {
411
+ session.registerUserInputHandler(config.onUserInputRequest);
412
+ }
413
+ if (config.hooks) {
414
+ session.registerHooks(config.hooks);
415
+ }
389
416
  this.sessions.set(resumedSessionId, session);
390
417
  return session;
391
418
  }
@@ -572,8 +599,17 @@ class CopilotClient {
572
599
  } else if (this.options.port > 0) {
573
600
  args.push("--port", this.options.port.toString());
574
601
  }
602
+ if (this.options.githubToken) {
603
+ args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN");
604
+ }
605
+ if (!this.options.useLoggedInUser) {
606
+ args.push("--no-auto-login");
607
+ }
575
608
  const envWithoutNodeDebug = { ...this.options.env };
576
609
  delete envWithoutNodeDebug.NODE_DEBUG;
610
+ if (this.options.githubToken) {
611
+ envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken;
612
+ }
577
613
  const isJsFile = this.options.cliPath.endsWith(".js");
578
614
  const isAbsolutePath = this.options.cliPath.startsWith("/") || /^[a-zA-Z]:/.test(this.options.cliPath);
579
615
  let command;
@@ -707,6 +743,14 @@ class CopilotClient {
707
743
  "permission.request",
708
744
  async (params) => await this.handlePermissionRequest(params)
709
745
  );
746
+ this.connection.onRequest(
747
+ "userInput.request",
748
+ async (params) => await this.handleUserInputRequest(params)
749
+ );
750
+ this.connection.onRequest(
751
+ "hooks.invoke",
752
+ async (params) => await this.handleHooksInvoke(params)
753
+ );
710
754
  this.connection.onClose(() => {
711
755
  if (this.state === "connected" && this.options.autoRestart) {
712
756
  void this.reconnect();
@@ -780,6 +824,32 @@ class CopilotClient {
780
824
  };
781
825
  }
782
826
  }
827
+ async handleUserInputRequest(params) {
828
+ if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
829
+ throw new Error("Invalid user input request payload");
830
+ }
831
+ const session = this.sessions.get(params.sessionId);
832
+ if (!session) {
833
+ throw new Error(`Session not found: ${params.sessionId}`);
834
+ }
835
+ const result = await session._handleUserInputRequest({
836
+ question: params.question,
837
+ choices: params.choices,
838
+ allowFreeform: params.allowFreeform
839
+ });
840
+ return result;
841
+ }
842
+ async handleHooksInvoke(params) {
843
+ if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
844
+ throw new Error("Invalid hooks invoke payload");
845
+ }
846
+ const session = this.sessions.get(params.sessionId);
847
+ if (!session) {
848
+ throw new Error(`Session not found: ${params.sessionId}`);
849
+ }
850
+ const output = await session._handleHooksInvoke(params.hookType, params.input);
851
+ return { output };
852
+ }
783
853
  normalizeToolResult(result) {
784
854
  if (result === void 0 || result === null) {
785
855
  return {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Generated from: @github/copilot/session-events.schema.json
5
5
  * Generated by: scripts/generate-session-types.ts
6
- * Generated at: 2026-01-22T04:11:04.988Z
6
+ * Generated at: 2026-01-26T18:08:33.710Z
7
7
  *
8
8
  * To update these types:
9
9
  * 1. Update the schema in copilot-agent-runtime
@@ -117,6 +117,16 @@ export type SessionEvent = {
117
117
  messagesRemovedDuringTruncation: number;
118
118
  performedBy: string;
119
119
  };
120
+ } | {
121
+ id: string;
122
+ timestamp: string;
123
+ parentId: string | null;
124
+ ephemeral: true;
125
+ type: "session.snapshot_rewind";
126
+ data: {
127
+ upToEventId: string;
128
+ eventsRemoved: number;
129
+ };
120
130
  } | {
121
131
  id: string;
122
132
  timestamp: string;
@@ -165,11 +175,30 @@ export type SessionEvent = {
165
175
  data: {
166
176
  content: string;
167
177
  transformedContent?: string;
168
- attachments?: {
169
- type: "file" | "directory";
178
+ attachments?: ({
179
+ type: "file";
170
180
  path: string;
171
181
  displayName: string;
172
- }[];
182
+ } | {
183
+ type: "directory";
184
+ path: string;
185
+ displayName: string;
186
+ } | {
187
+ type: "selection";
188
+ filePath: string;
189
+ displayName: string;
190
+ text: string;
191
+ selection: {
192
+ start: {
193
+ line: number;
194
+ character: number;
195
+ };
196
+ end: {
197
+ line: number;
198
+ character: number;
199
+ };
200
+ };
201
+ })[];
173
202
  source?: string;
174
203
  };
175
204
  } | {
@@ -315,6 +344,8 @@ export type SessionEvent = {
315
344
  toolCallId: string;
316
345
  toolName: string;
317
346
  arguments?: unknown;
347
+ mcpServerName?: string;
348
+ mcpToolName?: string;
318
349
  parentToolCallId?: string;
319
350
  };
320
351
  } | {
@@ -349,6 +380,7 @@ export type SessionEvent = {
349
380
  isUserRequested?: boolean;
350
381
  result?: {
351
382
  content: string;
383
+ detailedContent?: string;
352
384
  };
353
385
  error?: {
354
386
  message: string;
package/dist/index.d.ts CHANGED
@@ -6,4 +6,4 @@
6
6
  export { CopilotClient } from "./client.js";
7
7
  export { CopilotSession, type AssistantMessageEvent } from "./session.js";
8
8
  export { defineTool } from "./types.js";
9
- export type { ConnectionState, CopilotClientOptions, CustomAgentConfig, GetAuthStatusResponse, GetStatusResponse, InfiniteSessionConfig, MCPLocalServerConfig, MCPRemoteServerConfig, MCPServerConfig, MessageOptions, ModelBilling, ModelCapabilities, ModelInfo, ModelPolicy, PermissionHandler, PermissionRequest, PermissionRequestResult, ResumeSessionConfig, SessionConfig, SessionEvent, SessionEventHandler, SessionMetadata, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageReplaceConfig, Tool, ToolHandler, ToolInvocation, ToolResultObject, ZodSchema, } from "./types.js";
9
+ export type { ConnectionState, CopilotClientOptions, CustomAgentConfig, GetAuthStatusResponse, GetStatusResponse, InfiniteSessionConfig, MCPLocalServerConfig, MCPRemoteServerConfig, MCPServerConfig, MessageOptions, ModelBilling, ModelCapabilities, ModelInfo, ModelPolicy, PermissionHandler, PermissionRequest, PermissionRequestResult, ResumeSessionConfig, SessionConfig, SessionEvent, SessionEventHandler, SessionEventPayload, SessionEventType, SessionMetadata, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageReplaceConfig, Tool, ToolHandler, ToolInvocation, ToolResultObject, TypedSessionEventHandler, ZodSchema, } from "./types.js";
package/dist/session.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * @module session
4
4
  */
5
5
  import type { MessageConnection } from "vscode-jsonrpc/node";
6
- import type { MessageOptions, PermissionHandler, PermissionRequestResult, SessionEvent, SessionEventHandler, Tool, ToolHandler } from "./types.js";
6
+ import type { MessageOptions, PermissionHandler, PermissionRequestResult, SessionEvent, SessionEventHandler, SessionEventType, SessionHooks, Tool, ToolHandler, TypedSessionEventHandler, UserInputHandler, UserInputResponse } from "./types.js";
7
7
  /** Assistant message event - the final response from the assistant. */
8
8
  export type AssistantMessageEvent = Extract<SessionEvent, {
9
9
  type: "assistant.message";
@@ -38,8 +38,11 @@ export declare class CopilotSession {
38
38
  private connection;
39
39
  private readonly _workspacePath?;
40
40
  private eventHandlers;
41
+ private typedEventHandlers;
41
42
  private toolHandlers;
42
43
  private permissionHandler?;
44
+ private userInputHandler?;
45
+ private hooks?;
43
46
  /**
44
47
  * Creates a new CopilotSession instance.
45
48
  *
@@ -104,7 +107,26 @@ export declare class CopilotSession {
104
107
  * Events include assistant messages, tool executions, errors, and session state changes.
105
108
  * Multiple handlers can be registered and will all receive events.
106
109
  *
107
- * @param handler - A callback function that receives session events
110
+ * @param eventType - The specific event type to listen for (e.g., "assistant.message", "session.idle")
111
+ * @param handler - A callback function that receives events of the specified type
112
+ * @returns A function that, when called, unsubscribes the handler
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * // Listen for a specific event type
117
+ * const unsubscribe = session.on("assistant.message", (event) => {
118
+ * console.log("Assistant:", event.data.content);
119
+ * });
120
+ *
121
+ * // Later, to stop receiving events:
122
+ * unsubscribe();
123
+ * ```
124
+ */
125
+ on<K extends SessionEventType>(eventType: K, handler: TypedSessionEventHandler<K>): () => void;
126
+ /**
127
+ * Subscribes to all events from this session.
128
+ *
129
+ * @param handler - A callback function that receives all session events
108
130
  * @returns A function that, when called, unsubscribes the handler
109
131
  *
110
132
  * @example
@@ -160,6 +182,26 @@ export declare class CopilotSession {
160
182
  * @internal This method is typically called internally when creating a session.
161
183
  */
162
184
  registerPermissionHandler(handler?: PermissionHandler): void;
185
+ /**
186
+ * Registers a user input handler for ask_user requests.
187
+ *
188
+ * When the agent needs input from the user (via ask_user tool),
189
+ * this handler is called to provide the response.
190
+ *
191
+ * @param handler - The user input handler function, or undefined to remove the handler
192
+ * @internal This method is typically called internally when creating a session.
193
+ */
194
+ registerUserInputHandler(handler?: UserInputHandler): void;
195
+ /**
196
+ * Registers hook handlers for session lifecycle events.
197
+ *
198
+ * Hooks allow custom logic to be executed at various points during
199
+ * the session lifecycle (before/after tool use, session start/end, etc.).
200
+ *
201
+ * @param hooks - The hook handlers object, or undefined to remove all hooks
202
+ * @internal This method is typically called internally when creating a session.
203
+ */
204
+ registerHooks(hooks?: SessionHooks): void;
163
205
  /**
164
206
  * Handles a permission request from the Copilot CLI.
165
207
  *
@@ -168,6 +210,23 @@ export declare class CopilotSession {
168
210
  * @internal This method is for internal use by the SDK.
169
211
  */
170
212
  _handlePermissionRequest(request: unknown): Promise<PermissionRequestResult>;
213
+ /**
214
+ * Handles a user input request from the Copilot CLI.
215
+ *
216
+ * @param request - The user input request data from the CLI
217
+ * @returns A promise that resolves with the user's response
218
+ * @internal This method is for internal use by the SDK.
219
+ */
220
+ _handleUserInputRequest(request: unknown): Promise<UserInputResponse>;
221
+ /**
222
+ * Handles a hooks invocation from the Copilot CLI.
223
+ *
224
+ * @param hookType - The type of hook being invoked
225
+ * @param input - The input data for the hook
226
+ * @returns A promise that resolves with the hook output, or undefined
227
+ * @internal This method is for internal use by the SDK.
228
+ */
229
+ _handleHooksInvoke(hookType: string, input: unknown): Promise<unknown>;
171
230
  /**
172
231
  * Retrieves all events and messages from this session's history.
173
232
  *
package/dist/session.js CHANGED
@@ -13,8 +13,11 @@ class CopilotSession {
13
13
  this._workspacePath = _workspacePath;
14
14
  }
15
15
  eventHandlers = /* @__PURE__ */ new Set();
16
+ typedEventHandlers = /* @__PURE__ */ new Map();
16
17
  toolHandlers = /* @__PURE__ */ new Map();
17
18
  permissionHandler;
19
+ userInputHandler;
20
+ hooks;
18
21
  /**
19
22
  * Path to the session workspace directory when infinite sessions are enabled.
20
23
  * Contains checkpoints/, plan.md, and files/ subdirectories.
@@ -111,36 +114,25 @@ class CopilotSession {
111
114
  unsubscribe();
112
115
  }
113
116
  }
114
- /**
115
- * Subscribes to events from this session.
116
- *
117
- * Events include assistant messages, tool executions, errors, and session state changes.
118
- * Multiple handlers can be registered and will all receive events.
119
- *
120
- * @param handler - A callback function that receives session events
121
- * @returns A function that, when called, unsubscribes the handler
122
- *
123
- * @example
124
- * ```typescript
125
- * const unsubscribe = session.on((event) => {
126
- * switch (event.type) {
127
- * case "assistant.message":
128
- * console.log("Assistant:", event.data.content);
129
- * break;
130
- * case "session.error":
131
- * console.error("Error:", event.data.message);
132
- * break;
133
- * }
134
- * });
135
- *
136
- * // Later, to stop receiving events:
137
- * unsubscribe();
138
- * ```
139
- */
140
- on(handler) {
141
- this.eventHandlers.add(handler);
117
+ on(eventTypeOrHandler, handler) {
118
+ if (typeof eventTypeOrHandler === "string" && handler) {
119
+ const eventType = eventTypeOrHandler;
120
+ if (!this.typedEventHandlers.has(eventType)) {
121
+ this.typedEventHandlers.set(eventType, /* @__PURE__ */ new Set());
122
+ }
123
+ const storedHandler = handler;
124
+ this.typedEventHandlers.get(eventType).add(storedHandler);
125
+ return () => {
126
+ const handlers = this.typedEventHandlers.get(eventType);
127
+ if (handlers) {
128
+ handlers.delete(storedHandler);
129
+ }
130
+ };
131
+ }
132
+ const wildcardHandler = eventTypeOrHandler;
133
+ this.eventHandlers.add(wildcardHandler);
142
134
  return () => {
143
- this.eventHandlers.delete(handler);
135
+ this.eventHandlers.delete(wildcardHandler);
144
136
  };
145
137
  }
146
138
  /**
@@ -150,6 +142,15 @@ class CopilotSession {
150
142
  * @internal This method is for internal use by the SDK.
151
143
  */
152
144
  _dispatchEvent(event) {
145
+ const typedHandlers = this.typedEventHandlers.get(event.type);
146
+ if (typedHandlers) {
147
+ for (const handler of typedHandlers) {
148
+ try {
149
+ handler(event);
150
+ } catch (_error) {
151
+ }
152
+ }
153
+ }
153
154
  for (const handler of this.eventHandlers) {
154
155
  try {
155
156
  handler(event);
@@ -197,6 +198,30 @@ class CopilotSession {
197
198
  registerPermissionHandler(handler) {
198
199
  this.permissionHandler = handler;
199
200
  }
201
+ /**
202
+ * Registers a user input handler for ask_user requests.
203
+ *
204
+ * When the agent needs input from the user (via ask_user tool),
205
+ * this handler is called to provide the response.
206
+ *
207
+ * @param handler - The user input handler function, or undefined to remove the handler
208
+ * @internal This method is typically called internally when creating a session.
209
+ */
210
+ registerUserInputHandler(handler) {
211
+ this.userInputHandler = handler;
212
+ }
213
+ /**
214
+ * Registers hook handlers for session lifecycle events.
215
+ *
216
+ * Hooks allow custom logic to be executed at various points during
217
+ * the session lifecycle (before/after tool use, session start/end, etc.).
218
+ *
219
+ * @param hooks - The hook handlers object, or undefined to remove all hooks
220
+ * @internal This method is typically called internally when creating a session.
221
+ */
222
+ registerHooks(hooks) {
223
+ this.hooks = hooks;
224
+ }
200
225
  /**
201
226
  * Handles a permission request from the Copilot CLI.
202
227
  *
@@ -217,6 +242,57 @@ class CopilotSession {
217
242
  return { kind: "denied-no-approval-rule-and-could-not-request-from-user" };
218
243
  }
219
244
  }
245
+ /**
246
+ * Handles a user input request from the Copilot CLI.
247
+ *
248
+ * @param request - The user input request data from the CLI
249
+ * @returns A promise that resolves with the user's response
250
+ * @internal This method is for internal use by the SDK.
251
+ */
252
+ async _handleUserInputRequest(request) {
253
+ if (!this.userInputHandler) {
254
+ throw new Error("User input requested but no handler registered");
255
+ }
256
+ try {
257
+ const result = await this.userInputHandler(request, {
258
+ sessionId: this.sessionId
259
+ });
260
+ return result;
261
+ } catch (error) {
262
+ throw error;
263
+ }
264
+ }
265
+ /**
266
+ * Handles a hooks invocation from the Copilot CLI.
267
+ *
268
+ * @param hookType - The type of hook being invoked
269
+ * @param input - The input data for the hook
270
+ * @returns A promise that resolves with the hook output, or undefined
271
+ * @internal This method is for internal use by the SDK.
272
+ */
273
+ async _handleHooksInvoke(hookType, input) {
274
+ if (!this.hooks) {
275
+ return void 0;
276
+ }
277
+ const handlerMap = {
278
+ preToolUse: this.hooks.onPreToolUse,
279
+ postToolUse: this.hooks.onPostToolUse,
280
+ userPromptSubmitted: this.hooks.onUserPromptSubmitted,
281
+ sessionStart: this.hooks.onSessionStart,
282
+ sessionEnd: this.hooks.onSessionEnd,
283
+ errorOccurred: this.hooks.onErrorOccurred
284
+ };
285
+ const handler = handlerMap[hookType];
286
+ if (!handler) {
287
+ return void 0;
288
+ }
289
+ try {
290
+ const result = await handler(input, { sessionId: this.sessionId });
291
+ return result;
292
+ } catch (_error) {
293
+ return void 0;
294
+ }
295
+ }
220
296
  /**
221
297
  * Retrieves all events and messages from this session's history.
222
298
  *
@@ -263,6 +339,7 @@ class CopilotSession {
263
339
  sessionId: this.sessionId
264
340
  });
265
341
  this.eventHandlers.clear();
342
+ this.typedEventHandlers.clear();
266
343
  this.toolHandlers.clear();
267
344
  this.permissionHandler = void 0;
268
345
  }
package/dist/types.d.ts CHANGED
@@ -58,6 +58,19 @@ export interface CopilotClientOptions {
58
58
  * Environment variables to pass to the CLI process. If not set, inherits process.env.
59
59
  */
60
60
  env?: Record<string, string | undefined>;
61
+ /**
62
+ * GitHub token to use for authentication.
63
+ * When provided, the token is passed to the CLI server via environment variable.
64
+ * This takes priority over other authentication methods.
65
+ */
66
+ githubToken?: string;
67
+ /**
68
+ * Whether to use the logged-in user for authentication.
69
+ * When true, the CLI server will attempt to use stored OAuth tokens or gh CLI auth.
70
+ * When false, only explicit tokens (githubToken or environment variables) are used.
71
+ * @default true (but defaults to false when githubToken is provided)
72
+ */
73
+ useLoggedInUser?: boolean;
61
74
  }
62
75
  /**
63
76
  * Configuration for creating a session
@@ -166,6 +179,209 @@ export interface PermissionRequestResult {
166
179
  export type PermissionHandler = (request: PermissionRequest, invocation: {
167
180
  sessionId: string;
168
181
  }) => Promise<PermissionRequestResult> | PermissionRequestResult;
182
+ /**
183
+ * Request for user input from the agent (enables ask_user tool)
184
+ */
185
+ export interface UserInputRequest {
186
+ /**
187
+ * The question to ask the user
188
+ */
189
+ question: string;
190
+ /**
191
+ * Optional choices for multiple choice questions
192
+ */
193
+ choices?: string[];
194
+ /**
195
+ * Whether to allow freeform text input in addition to choices
196
+ * @default true
197
+ */
198
+ allowFreeform?: boolean;
199
+ }
200
+ /**
201
+ * Response to a user input request
202
+ */
203
+ export interface UserInputResponse {
204
+ /**
205
+ * The user's answer
206
+ */
207
+ answer: string;
208
+ /**
209
+ * Whether the answer was freeform (not from choices)
210
+ */
211
+ wasFreeform: boolean;
212
+ }
213
+ /**
214
+ * Handler for user input requests from the agent
215
+ */
216
+ export type UserInputHandler = (request: UserInputRequest, invocation: {
217
+ sessionId: string;
218
+ }) => Promise<UserInputResponse> | UserInputResponse;
219
+ /**
220
+ * Base interface for all hook inputs
221
+ */
222
+ export interface BaseHookInput {
223
+ timestamp: number;
224
+ cwd: string;
225
+ }
226
+ /**
227
+ * Input for pre-tool-use hook
228
+ */
229
+ export interface PreToolUseHookInput extends BaseHookInput {
230
+ toolName: string;
231
+ toolArgs: unknown;
232
+ }
233
+ /**
234
+ * Output for pre-tool-use hook
235
+ */
236
+ export interface PreToolUseHookOutput {
237
+ permissionDecision?: "allow" | "deny" | "ask";
238
+ permissionDecisionReason?: string;
239
+ modifiedArgs?: unknown;
240
+ additionalContext?: string;
241
+ suppressOutput?: boolean;
242
+ }
243
+ /**
244
+ * Handler for pre-tool-use hook
245
+ */
246
+ export type PreToolUseHandler = (input: PreToolUseHookInput, invocation: {
247
+ sessionId: string;
248
+ }) => Promise<PreToolUseHookOutput | void> | PreToolUseHookOutput | void;
249
+ /**
250
+ * Input for post-tool-use hook
251
+ */
252
+ export interface PostToolUseHookInput extends BaseHookInput {
253
+ toolName: string;
254
+ toolArgs: unknown;
255
+ toolResult: ToolResultObject;
256
+ }
257
+ /**
258
+ * Output for post-tool-use hook
259
+ */
260
+ export interface PostToolUseHookOutput {
261
+ modifiedResult?: ToolResultObject;
262
+ additionalContext?: string;
263
+ suppressOutput?: boolean;
264
+ }
265
+ /**
266
+ * Handler for post-tool-use hook
267
+ */
268
+ export type PostToolUseHandler = (input: PostToolUseHookInput, invocation: {
269
+ sessionId: string;
270
+ }) => Promise<PostToolUseHookOutput | void> | PostToolUseHookOutput | void;
271
+ /**
272
+ * Input for user-prompt-submitted hook
273
+ */
274
+ export interface UserPromptSubmittedHookInput extends BaseHookInput {
275
+ prompt: string;
276
+ }
277
+ /**
278
+ * Output for user-prompt-submitted hook
279
+ */
280
+ export interface UserPromptSubmittedHookOutput {
281
+ modifiedPrompt?: string;
282
+ additionalContext?: string;
283
+ suppressOutput?: boolean;
284
+ }
285
+ /**
286
+ * Handler for user-prompt-submitted hook
287
+ */
288
+ export type UserPromptSubmittedHandler = (input: UserPromptSubmittedHookInput, invocation: {
289
+ sessionId: string;
290
+ }) => Promise<UserPromptSubmittedHookOutput | void> | UserPromptSubmittedHookOutput | void;
291
+ /**
292
+ * Input for session-start hook
293
+ */
294
+ export interface SessionStartHookInput extends BaseHookInput {
295
+ source: "startup" | "resume" | "new";
296
+ initialPrompt?: string;
297
+ }
298
+ /**
299
+ * Output for session-start hook
300
+ */
301
+ export interface SessionStartHookOutput {
302
+ additionalContext?: string;
303
+ modifiedConfig?: Record<string, unknown>;
304
+ }
305
+ /**
306
+ * Handler for session-start hook
307
+ */
308
+ export type SessionStartHandler = (input: SessionStartHookInput, invocation: {
309
+ sessionId: string;
310
+ }) => Promise<SessionStartHookOutput | void> | SessionStartHookOutput | void;
311
+ /**
312
+ * Input for session-end hook
313
+ */
314
+ export interface SessionEndHookInput extends BaseHookInput {
315
+ reason: "complete" | "error" | "abort" | "timeout" | "user_exit";
316
+ finalMessage?: string;
317
+ error?: string;
318
+ }
319
+ /**
320
+ * Output for session-end hook
321
+ */
322
+ export interface SessionEndHookOutput {
323
+ suppressOutput?: boolean;
324
+ cleanupActions?: string[];
325
+ sessionSummary?: string;
326
+ }
327
+ /**
328
+ * Handler for session-end hook
329
+ */
330
+ export type SessionEndHandler = (input: SessionEndHookInput, invocation: {
331
+ sessionId: string;
332
+ }) => Promise<SessionEndHookOutput | void> | SessionEndHookOutput | void;
333
+ /**
334
+ * Input for error-occurred hook
335
+ */
336
+ export interface ErrorOccurredHookInput extends BaseHookInput {
337
+ error: string;
338
+ errorContext: "model_call" | "tool_execution" | "system" | "user_input";
339
+ recoverable: boolean;
340
+ }
341
+ /**
342
+ * Output for error-occurred hook
343
+ */
344
+ export interface ErrorOccurredHookOutput {
345
+ suppressOutput?: boolean;
346
+ errorHandling?: "retry" | "skip" | "abort";
347
+ retryCount?: number;
348
+ userNotification?: string;
349
+ }
350
+ /**
351
+ * Handler for error-occurred hook
352
+ */
353
+ export type ErrorOccurredHandler = (input: ErrorOccurredHookInput, invocation: {
354
+ sessionId: string;
355
+ }) => Promise<ErrorOccurredHookOutput | void> | ErrorOccurredHookOutput | void;
356
+ /**
357
+ * Configuration for session hooks
358
+ */
359
+ export interface SessionHooks {
360
+ /**
361
+ * Called before a tool is executed
362
+ */
363
+ onPreToolUse?: PreToolUseHandler;
364
+ /**
365
+ * Called after a tool is executed
366
+ */
367
+ onPostToolUse?: PostToolUseHandler;
368
+ /**
369
+ * Called when the user submits a prompt
370
+ */
371
+ onUserPromptSubmitted?: UserPromptSubmittedHandler;
372
+ /**
373
+ * Called when a session starts
374
+ */
375
+ onSessionStart?: SessionStartHandler;
376
+ /**
377
+ * Called when a session ends
378
+ */
379
+ onSessionEnd?: SessionEndHandler;
380
+ /**
381
+ * Called when an error occurs
382
+ */
383
+ onErrorOccurred?: ErrorOccurredHandler;
384
+ }
169
385
  /**
170
386
  * Base interface for MCP server configuration.
171
387
  */
@@ -318,6 +534,21 @@ export interface SessionConfig {
318
534
  * When provided, the server will call this handler to request permission for operations.
319
535
  */
320
536
  onPermissionRequest?: PermissionHandler;
537
+ /**
538
+ * Handler for user input requests from the agent.
539
+ * When provided, enables the ask_user tool allowing the agent to ask questions.
540
+ */
541
+ onUserInputRequest?: UserInputHandler;
542
+ /**
543
+ * Hook handlers for intercepting session lifecycle events.
544
+ * When provided, enables hooks callback allowing custom logic at various points.
545
+ */
546
+ hooks?: SessionHooks;
547
+ /**
548
+ * Working directory for the session.
549
+ * Tool operations will be relative to this directory.
550
+ */
551
+ workingDirectory?: string;
321
552
  streaming?: boolean;
322
553
  /**
323
554
  * MCP server configurations for the session.
@@ -346,7 +577,14 @@ export interface SessionConfig {
346
577
  /**
347
578
  * Configuration for resuming a session
348
579
  */
349
- export type ResumeSessionConfig = Pick<SessionConfig, "tools" | "provider" | "streaming" | "onPermissionRequest" | "mcpServers" | "customAgents" | "skillDirectories" | "disabledSkills">;
580
+ export type ResumeSessionConfig = Pick<SessionConfig, "tools" | "provider" | "streaming" | "onPermissionRequest" | "onUserInputRequest" | "hooks" | "workingDirectory" | "mcpServers" | "customAgents" | "skillDirectories" | "disabledSkills"> & {
581
+ /**
582
+ * When true, skips emitting the session.resume event.
583
+ * Useful for reconnecting to a session without triggering resume-related side effects.
584
+ * @default false
585
+ */
586
+ disableResume?: boolean;
587
+ };
350
588
  /**
351
589
  * Configuration for a custom API provider.
352
590
  */
@@ -407,7 +645,21 @@ export interface MessageOptions {
407
645
  mode?: "enqueue" | "immediate";
408
646
  }
409
647
  /**
410
- * Event handler callback type
648
+ * All possible event type strings from SessionEvent
649
+ */
650
+ export type SessionEventType = SessionEvent["type"];
651
+ /**
652
+ * Extract the specific event payload for a given event type
653
+ */
654
+ export type SessionEventPayload<T extends SessionEventType> = Extract<SessionEvent, {
655
+ type: T;
656
+ }>;
657
+ /**
658
+ * Event handler for a specific event type
659
+ */
660
+ export type TypedSessionEventHandler<T extends SessionEventType> = (event: SessionEventPayload<T>) => void;
661
+ /**
662
+ * Event handler callback type (for all events)
411
663
  */
412
664
  export type SessionEventHandler = (event: SessionEvent) => void;
413
665
  /**
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "https://github.com/github/copilot-sdk.git"
6
6
  },
7
- "version": "0.1.18",
7
+ "version": "0.1.20",
8
8
  "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC",
9
9
  "main": "./dist/index.js",
10
10
  "types": "./dist/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "author": "GitHub",
41
41
  "license": "MIT",
42
42
  "dependencies": {
43
- "@github/copilot": "^0.0.394",
43
+ "@github/copilot": "^0.0.399",
44
44
  "vscode-jsonrpc": "^8.2.1",
45
45
  "zod": "^4.3.5"
46
46
  },