@github/copilot-sdk 0.2.0 → 0.2.1-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/README.md CHANGED
@@ -279,6 +279,20 @@ Get all events/messages from this session.
279
279
 
280
280
  Disconnect the session and free resources. Session data on disk is preserved for later resumption.
281
281
 
282
+ ##### `capabilities: SessionCapabilities`
283
+
284
+ Host capabilities reported when the session was created or resumed. Use this to check feature support before calling capability-gated APIs.
285
+
286
+ ```typescript
287
+ if (session.capabilities.ui?.elicitation) {
288
+ const ok = await session.ui.confirm("Deploy?");
289
+ }
290
+ ```
291
+
292
+ ##### `ui: SessionUiApi`
293
+
294
+ Interactive UI methods for showing dialogs to the user. Only available when the CLI host supports elicitation (`session.capabilities.ui?.elicitation === true`). See [UI Elicitation](#ui-elicitation) for full details.
295
+
282
296
  ##### `destroy(): Promise<void>` *(deprecated)*
283
297
 
284
298
  Deprecated — use `disconnect()` instead.
@@ -294,6 +308,8 @@ Sessions emit various events during processing:
294
308
  - `assistant.message_delta` - Streaming response chunk
295
309
  - `tool.execution_start` - Tool execution started
296
310
  - `tool.execution_complete` - Tool execution completed
311
+ - `command.execute` - Command dispatch request (handled internally by the SDK)
312
+ - `commands.changed` - Command registration changed
297
313
  - And more...
298
314
 
299
315
  See `SessionEvent` type in the source for full details.
@@ -455,6 +471,72 @@ defineTool("safe_lookup", {
455
471
  })
456
472
  ```
457
473
 
474
+ ### Commands
475
+
476
+ Register slash commands so that users of the CLI's TUI can invoke custom actions via `/commandName`. Each command has a `name`, optional `description`, and a `handler` called when the user executes it.
477
+
478
+ ```ts
479
+ const session = await client.createSession({
480
+ onPermissionRequest: approveAll,
481
+ commands: [
482
+ {
483
+ name: "deploy",
484
+ description: "Deploy the app to production",
485
+ handler: async ({ commandName, args }) => {
486
+ console.log(`Deploying with args: ${args}`);
487
+ // Do work here — any thrown error is reported back to the CLI
488
+ },
489
+ },
490
+ ],
491
+ });
492
+ ```
493
+
494
+ When the user types `/deploy staging` in the CLI, the SDK receives a `command.execute` event, routes it to your handler, and automatically responds to the CLI. If the handler throws, the error message is forwarded.
495
+
496
+ Commands are sent to the CLI on both `createSession` and `resumeSession`, so you can update the command set when resuming.
497
+
498
+ ### UI Elicitation
499
+
500
+ When the CLI is running with a TUI (not in headless mode), the SDK can request interactive form dialogs from the user. The `session.ui` object provides convenience methods built on a single generic `elicitation` RPC.
501
+
502
+ > **Capability check:** Elicitation is only available when the host advertises support. Always check `session.capabilities.ui?.elicitation` before calling UI methods.
503
+
504
+ ```ts
505
+ const session = await client.createSession({ onPermissionRequest: approveAll });
506
+
507
+ if (session.capabilities.ui?.elicitation) {
508
+ // Confirm dialog — returns boolean
509
+ const ok = await session.ui.confirm("Deploy to production?");
510
+
511
+ // Selection dialog — returns selected value or null
512
+ const env = await session.ui.select("Pick environment", ["production", "staging", "dev"]);
513
+
514
+ // Text input — returns string or null
515
+ const name = await session.ui.input("Project name:", {
516
+ title: "Name",
517
+ minLength: 1,
518
+ maxLength: 50,
519
+ });
520
+
521
+ // Generic elicitation with full schema control
522
+ const result = await session.ui.elicitation({
523
+ message: "Configure deployment",
524
+ requestedSchema: {
525
+ type: "object",
526
+ properties: {
527
+ region: { type: "string", enum: ["us-east", "eu-west"] },
528
+ dryRun: { type: "boolean", default: true },
529
+ },
530
+ required: ["region"],
531
+ },
532
+ });
533
+ // result.action: "accept" | "decline" | "cancel"
534
+ // result.content: { region: "us-east", dryRun: true } (when accepted)
535
+ }
536
+ ```
537
+
538
+ All UI methods throw if elicitation is not supported by the host.
539
+
458
540
  ### System Message Customization
459
541
 
460
542
  Control the system prompt using `systemMessage` in session config:
@@ -451,6 +451,7 @@ class CopilotClient {
451
451
  this.onGetTraceContext
452
452
  );
453
453
  session.registerTools(config.tools);
454
+ session.registerCommands(config.commands);
454
455
  session.registerPermissionHandler(config.onPermissionRequest);
455
456
  if (config.onUserInputRequest) {
456
457
  session.registerUserInputHandler(config.onUserInputRequest);
@@ -482,6 +483,10 @@ class CopilotClient {
482
483
  overridesBuiltInTool: tool.overridesBuiltInTool,
483
484
  skipPermission: tool.skipPermission
484
485
  })),
486
+ commands: config.commands?.map((cmd) => ({
487
+ name: cmd.name,
488
+ description: cmd.description
489
+ })),
485
490
  systemMessage: wireSystemMessage,
486
491
  availableTools: config.availableTools,
487
492
  excludedTools: config.excludedTools,
@@ -500,8 +505,9 @@ class CopilotClient {
500
505
  disabledSkills: config.disabledSkills,
501
506
  infiniteSessions: config.infiniteSessions
502
507
  });
503
- const { workspacePath } = response;
508
+ const { workspacePath, capabilities } = response;
504
509
  session["_workspacePath"] = workspacePath;
510
+ session.setCapabilities(capabilities);
505
511
  } catch (e) {
506
512
  this.sessions.delete(sessionId);
507
513
  throw e;
@@ -552,6 +558,7 @@ class CopilotClient {
552
558
  this.onGetTraceContext
553
559
  );
554
560
  session.registerTools(config.tools);
561
+ session.registerCommands(config.commands);
555
562
  session.registerPermissionHandler(config.onPermissionRequest);
556
563
  if (config.onUserInputRequest) {
557
564
  session.registerUserInputHandler(config.onUserInputRequest);
@@ -586,6 +593,10 @@ class CopilotClient {
586
593
  overridesBuiltInTool: tool.overridesBuiltInTool,
587
594
  skipPermission: tool.skipPermission
588
595
  })),
596
+ commands: config.commands?.map((cmd) => ({
597
+ name: cmd.name,
598
+ description: cmd.description
599
+ })),
589
600
  provider: config.provider,
590
601
  requestPermission: true,
591
602
  requestUserInput: !!config.onUserInputRequest,
@@ -602,8 +613,9 @@ class CopilotClient {
602
613
  infiniteSessions: config.infiniteSessions,
603
614
  disableResume: config.disableResume
604
615
  });
605
- const { workspacePath } = response;
616
+ const { workspacePath, capabilities } = response;
606
617
  session["_workspacePath"] = workspacePath;
618
+ session.setCapabilities(capabilities);
607
619
  } catch (e) {
608
620
  this.sessions.delete(sessionId);
609
621
  throw e;
@@ -45,12 +45,14 @@ class CopilotSession {
45
45
  eventHandlers = /* @__PURE__ */ new Set();
46
46
  typedEventHandlers = /* @__PURE__ */ new Map();
47
47
  toolHandlers = /* @__PURE__ */ new Map();
48
+ commandHandlers = /* @__PURE__ */ new Map();
48
49
  permissionHandler;
49
50
  userInputHandler;
50
51
  hooks;
51
52
  transformCallbacks;
52
53
  _rpc = null;
53
54
  traceContextProvider;
55
+ _capabilities = {};
54
56
  /**
55
57
  * Typed session-scoped RPC methods.
56
58
  */
@@ -68,6 +70,33 @@ class CopilotSession {
68
70
  get workspacePath() {
69
71
  return this._workspacePath;
70
72
  }
73
+ /**
74
+ * Host capabilities reported when the session was created or resumed.
75
+ * Use this to check feature support before calling capability-gated APIs.
76
+ */
77
+ get capabilities() {
78
+ return this._capabilities;
79
+ }
80
+ /**
81
+ * Interactive UI methods for showing dialogs to the user.
82
+ * Only available when the CLI host supports elicitation
83
+ * (`session.capabilities.ui?.elicitation === true`).
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * if (session.capabilities.ui?.elicitation) {
88
+ * const ok = await session.ui.confirm("Deploy to production?");
89
+ * }
90
+ * ```
91
+ */
92
+ get ui() {
93
+ return {
94
+ elicitation: (params) => this._elicitation(params),
95
+ confirm: (message) => this._confirm(message),
96
+ select: (message, options) => this._select(message, options),
97
+ input: (message, options) => this._input(message, options)
98
+ };
99
+ }
71
100
  /**
72
101
  * Sends a message to this session and waits for the response.
73
102
  *
@@ -237,6 +266,9 @@ class CopilotSession {
237
266
  if (this.permissionHandler) {
238
267
  void this._executePermissionAndRespond(requestId, permissionRequest);
239
268
  }
269
+ } else if (event.type === "command.execute") {
270
+ const { requestId, commandName, command, args } = event.data;
271
+ void this._executeCommandAndRespond(requestId, commandName, command, args);
240
272
  }
241
273
  }
242
274
  /**
@@ -301,6 +333,39 @@ class CopilotSession {
301
333
  }
302
334
  }
303
335
  }
336
+ /**
337
+ * Executes a command handler and sends the result back via RPC.
338
+ * @internal
339
+ */
340
+ async _executeCommandAndRespond(requestId, commandName, command, args) {
341
+ const handler = this.commandHandlers.get(commandName);
342
+ if (!handler) {
343
+ try {
344
+ await this.rpc.commands.handlePendingCommand({
345
+ requestId,
346
+ error: `Unknown command: ${commandName}`
347
+ });
348
+ } catch (rpcError) {
349
+ if (!(rpcError instanceof import_node.ConnectionError || rpcError instanceof import_node.ResponseError)) {
350
+ throw rpcError;
351
+ }
352
+ }
353
+ return;
354
+ }
355
+ try {
356
+ await handler({ sessionId: this.sessionId, command, commandName, args });
357
+ await this.rpc.commands.handlePendingCommand({ requestId });
358
+ } catch (error) {
359
+ const message = error instanceof Error ? error.message : String(error);
360
+ try {
361
+ await this.rpc.commands.handlePendingCommand({ requestId, error: message });
362
+ } catch (rpcError) {
363
+ if (!(rpcError instanceof import_node.ConnectionError || rpcError instanceof import_node.ResponseError)) {
364
+ throw rpcError;
365
+ }
366
+ }
367
+ }
368
+ }
304
369
  /**
305
370
  * Registers custom tool handlers for this session.
306
371
  *
@@ -329,6 +394,99 @@ class CopilotSession {
329
394
  getToolHandler(name) {
330
395
  return this.toolHandlers.get(name);
331
396
  }
397
+ /**
398
+ * Registers command handlers for this session.
399
+ *
400
+ * @param commands - An array of command definitions with handlers, or undefined to clear
401
+ * @internal This method is typically called internally when creating/resuming a session.
402
+ */
403
+ registerCommands(commands) {
404
+ this.commandHandlers.clear();
405
+ if (!commands) {
406
+ return;
407
+ }
408
+ for (const cmd of commands) {
409
+ this.commandHandlers.set(cmd.name, cmd.handler);
410
+ }
411
+ }
412
+ /**
413
+ * Sets the host capabilities for this session.
414
+ *
415
+ * @param capabilities - The capabilities object from the create/resume response
416
+ * @internal This method is typically called internally when creating/resuming a session.
417
+ */
418
+ setCapabilities(capabilities) {
419
+ this._capabilities = capabilities ?? {};
420
+ }
421
+ assertElicitation() {
422
+ if (!this._capabilities.ui?.elicitation) {
423
+ throw new Error(
424
+ "Elicitation is not supported by the host. Check session.capabilities.ui?.elicitation before calling UI methods."
425
+ );
426
+ }
427
+ }
428
+ async _elicitation(params) {
429
+ this.assertElicitation();
430
+ return this.rpc.ui.elicitation({
431
+ message: params.message,
432
+ requestedSchema: params.requestedSchema
433
+ });
434
+ }
435
+ async _confirm(message) {
436
+ this.assertElicitation();
437
+ const result = await this.rpc.ui.elicitation({
438
+ message,
439
+ requestedSchema: {
440
+ type: "object",
441
+ properties: {
442
+ confirmed: { type: "boolean", default: true }
443
+ },
444
+ required: ["confirmed"]
445
+ }
446
+ });
447
+ return result.action === "accept" && result.content?.confirmed === true;
448
+ }
449
+ async _select(message, options) {
450
+ this.assertElicitation();
451
+ const result = await this.rpc.ui.elicitation({
452
+ message,
453
+ requestedSchema: {
454
+ type: "object",
455
+ properties: {
456
+ selection: { type: "string", enum: options }
457
+ },
458
+ required: ["selection"]
459
+ }
460
+ });
461
+ if (result.action === "accept" && result.content?.selection != null) {
462
+ return result.content.selection;
463
+ }
464
+ return null;
465
+ }
466
+ async _input(message, options) {
467
+ this.assertElicitation();
468
+ const field = { type: "string" };
469
+ if (options?.title) field.title = options.title;
470
+ if (options?.description) field.description = options.description;
471
+ if (options?.minLength != null) field.minLength = options.minLength;
472
+ if (options?.maxLength != null) field.maxLength = options.maxLength;
473
+ if (options?.format) field.format = options.format;
474
+ if (options?.default != null) field.default = options.default;
475
+ const result = await this.rpc.ui.elicitation({
476
+ message,
477
+ requestedSchema: {
478
+ type: "object",
479
+ properties: {
480
+ value: field
481
+ },
482
+ required: ["value"]
483
+ }
484
+ });
485
+ if (result.action === "accept" && result.content?.value != null) {
486
+ return result.content.value;
487
+ }
488
+ return null;
489
+ }
332
490
  /**
333
491
  * Registers a handler for permission requests.
334
492
  *
package/dist/client.js CHANGED
@@ -431,6 +431,7 @@ class CopilotClient {
431
431
  this.onGetTraceContext
432
432
  );
433
433
  session.registerTools(config.tools);
434
+ session.registerCommands(config.commands);
434
435
  session.registerPermissionHandler(config.onPermissionRequest);
435
436
  if (config.onUserInputRequest) {
436
437
  session.registerUserInputHandler(config.onUserInputRequest);
@@ -462,6 +463,10 @@ class CopilotClient {
462
463
  overridesBuiltInTool: tool.overridesBuiltInTool,
463
464
  skipPermission: tool.skipPermission
464
465
  })),
466
+ commands: config.commands?.map((cmd) => ({
467
+ name: cmd.name,
468
+ description: cmd.description
469
+ })),
465
470
  systemMessage: wireSystemMessage,
466
471
  availableTools: config.availableTools,
467
472
  excludedTools: config.excludedTools,
@@ -480,8 +485,9 @@ class CopilotClient {
480
485
  disabledSkills: config.disabledSkills,
481
486
  infiniteSessions: config.infiniteSessions
482
487
  });
483
- const { workspacePath } = response;
488
+ const { workspacePath, capabilities } = response;
484
489
  session["_workspacePath"] = workspacePath;
490
+ session.setCapabilities(capabilities);
485
491
  } catch (e) {
486
492
  this.sessions.delete(sessionId);
487
493
  throw e;
@@ -532,6 +538,7 @@ class CopilotClient {
532
538
  this.onGetTraceContext
533
539
  );
534
540
  session.registerTools(config.tools);
541
+ session.registerCommands(config.commands);
535
542
  session.registerPermissionHandler(config.onPermissionRequest);
536
543
  if (config.onUserInputRequest) {
537
544
  session.registerUserInputHandler(config.onUserInputRequest);
@@ -566,6 +573,10 @@ class CopilotClient {
566
573
  overridesBuiltInTool: tool.overridesBuiltInTool,
567
574
  skipPermission: tool.skipPermission
568
575
  })),
576
+ commands: config.commands?.map((cmd) => ({
577
+ name: cmd.name,
578
+ description: cmd.description
579
+ })),
569
580
  provider: config.provider,
570
581
  requestPermission: true,
571
582
  requestUserInput: !!config.onUserInputRequest,
@@ -582,8 +593,9 @@ class CopilotClient {
582
593
  infiniteSessions: config.infiniteSessions,
583
594
  disableResume: config.disableResume
584
595
  });
585
- const { workspacePath } = response;
596
+ const { workspacePath, capabilities } = response;
586
597
  session["_workspacePath"] = workspacePath;
598
+ session.setCapabilities(capabilities);
587
599
  } catch (e) {
588
600
  this.sessions.delete(sessionId);
589
601
  throw e;
@@ -2070,6 +2070,10 @@ export type SessionEvent = {
2070
2070
  * Version of the plugin this skill originated from, when applicable
2071
2071
  */
2072
2072
  pluginVersion?: string;
2073
+ /**
2074
+ * Description of the skill from its SKILL.md frontmatter
2075
+ */
2076
+ description?: string;
2073
2077
  };
2074
2078
  } | {
2075
2079
  /**
@@ -2144,6 +2148,22 @@ export type SessionEvent = {
2144
2148
  * Human-readable display name of the sub-agent
2145
2149
  */
2146
2150
  agentDisplayName: string;
2151
+ /**
2152
+ * Model used by the sub-agent
2153
+ */
2154
+ model?: string;
2155
+ /**
2156
+ * Total number of tool calls made by the sub-agent
2157
+ */
2158
+ totalToolCalls?: number;
2159
+ /**
2160
+ * Total tokens (input + output) consumed by the sub-agent
2161
+ */
2162
+ totalTokens?: number;
2163
+ /**
2164
+ * Wall-clock duration of the sub-agent execution in milliseconds
2165
+ */
2166
+ durationMs?: number;
2147
2167
  };
2148
2168
  } | {
2149
2169
  /**
@@ -2183,6 +2203,22 @@ export type SessionEvent = {
2183
2203
  * Error message describing why the sub-agent failed
2184
2204
  */
2185
2205
  error: string;
2206
+ /**
2207
+ * Model used by the sub-agent (if any model calls succeeded before failure)
2208
+ */
2209
+ model?: string;
2210
+ /**
2211
+ * Total number of tool calls made before the sub-agent failed
2212
+ */
2213
+ totalToolCalls?: number;
2214
+ /**
2215
+ * Total tokens (input + output) consumed before the sub-agent failed
2216
+ */
2217
+ totalTokens?: number;
2218
+ /**
2219
+ * Wall-clock duration of the sub-agent execution in milliseconds
2220
+ */
2221
+ durationMs?: number;
2186
2222
  };
2187
2223
  } | {
2188
2224
  /**
@@ -3300,6 +3336,68 @@ export type SessionEvent = {
3300
3336
  path?: string;
3301
3337
  }[];
3302
3338
  };
3339
+ } | {
3340
+ /**
3341
+ * Unique event identifier (UUID v4), generated when the event is emitted
3342
+ */
3343
+ id: string;
3344
+ /**
3345
+ * ISO 8601 timestamp when the event was created
3346
+ */
3347
+ timestamp: string;
3348
+ /**
3349
+ * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event.
3350
+ */
3351
+ parentId: string | null;
3352
+ ephemeral: true;
3353
+ type: "session.custom_agents_updated";
3354
+ data: {
3355
+ /**
3356
+ * Array of loaded custom agent metadata
3357
+ */
3358
+ agents: {
3359
+ /**
3360
+ * Unique identifier for the agent
3361
+ */
3362
+ id: string;
3363
+ /**
3364
+ * Internal name of the agent
3365
+ */
3366
+ name: string;
3367
+ /**
3368
+ * Human-readable display name
3369
+ */
3370
+ displayName: string;
3371
+ /**
3372
+ * Description of what the agent does
3373
+ */
3374
+ description: string;
3375
+ /**
3376
+ * Source location: user, project, inherited, remote, or plugin
3377
+ */
3378
+ source: string;
3379
+ /**
3380
+ * List of tool names available to this agent
3381
+ */
3382
+ tools: string[];
3383
+ /**
3384
+ * Whether the agent can be selected by the user
3385
+ */
3386
+ userInvocable: boolean;
3387
+ /**
3388
+ * Model override for this agent, if set
3389
+ */
3390
+ model?: string;
3391
+ }[];
3392
+ /**
3393
+ * Non-fatal warnings from agent loading
3394
+ */
3395
+ warnings: string[];
3396
+ /**
3397
+ * Fatal errors from agent loading
3398
+ */
3399
+ errors: string[];
3400
+ };
3303
3401
  } | {
3304
3402
  /**
3305
3403
  * Unique event identifier (UUID v4), generated when the event is emitted
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, approveAll, SYSTEM_PROMPT_SECTIONS } from "./types.js";
9
- export type { ConnectionState, CopilotClientOptions, CustomAgentConfig, ForegroundSessionInfo, GetAuthStatusResponse, GetStatusResponse, InfiniteSessionConfig, MCPLocalServerConfig, MCPRemoteServerConfig, MCPServerConfig, MessageOptions, ModelBilling, ModelCapabilities, ModelInfo, ModelPolicy, PermissionHandler, PermissionRequest, PermissionRequestResult, ResumeSessionConfig, SectionOverride, SectionOverrideAction, SectionTransformFn, SessionConfig, SessionEvent, SessionEventHandler, SessionEventPayload, SessionEventType, SessionLifecycleEvent, SessionLifecycleEventType, SessionLifecycleHandler, SessionContext, SessionListFilter, SessionMetadata, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageCustomizeConfig, SystemMessageReplaceConfig, SystemPromptSection, TelemetryConfig, TraceContext, TraceContextProvider, Tool, ToolHandler, ToolInvocation, ToolResultObject, TypedSessionEventHandler, TypedSessionLifecycleHandler, ZodSchema, } from "./types.js";
9
+ export type { CommandContext, CommandDefinition, CommandHandler, ConnectionState, CopilotClientOptions, CustomAgentConfig, ElicitationFieldValue, ElicitationParams, ElicitationResult, ElicitationSchema, ElicitationSchemaField, ForegroundSessionInfo, GetAuthStatusResponse, GetStatusResponse, InfiniteSessionConfig, InputOptions, MCPLocalServerConfig, MCPRemoteServerConfig, MCPServerConfig, MessageOptions, ModelBilling, ModelCapabilities, ModelInfo, ModelPolicy, PermissionHandler, PermissionRequest, PermissionRequestResult, ResumeSessionConfig, SectionOverride, SectionOverrideAction, SectionTransformFn, SessionCapabilities, SessionConfig, SessionEvent, SessionEventHandler, SessionEventPayload, SessionEventType, SessionLifecycleEvent, SessionLifecycleEventType, SessionLifecycleHandler, SessionContext, SessionListFilter, SessionMetadata, SessionUiApi, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageCustomizeConfig, SystemMessageReplaceConfig, SystemPromptSection, TelemetryConfig, TraceContext, TraceContextProvider, Tool, ToolHandler, ToolInvocation, ToolResultObject, TypedSessionEventHandler, TypedSessionLifecycleHandler, ZodSchema, } from "./types.js";
package/dist/session.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type { MessageConnection } from "vscode-jsonrpc/node.js";
6
6
  import { createSessionRpc } from "./generated/rpc.js";
7
- import type { MessageOptions, PermissionHandler, PermissionRequestResult, ReasoningEffort, SectionTransformFn, SessionEvent, SessionEventHandler, SessionEventType, SessionHooks, Tool, ToolHandler, TraceContextProvider, TypedSessionEventHandler, UserInputHandler, UserInputResponse } from "./types.js";
7
+ import type { CommandHandler, MessageOptions, PermissionHandler, PermissionRequestResult, ReasoningEffort, SectionTransformFn, SessionCapabilities, SessionEvent, SessionEventHandler, SessionEventType, SessionHooks, SessionUiApi, Tool, ToolHandler, TraceContextProvider, TypedSessionEventHandler, UserInputHandler, UserInputResponse } from "./types.js";
8
8
  export declare const NO_RESULT_PERMISSION_V2_ERROR = "Permission handlers cannot return 'no-result' when connected to a protocol v2 server.";
9
9
  /** Assistant message event - the final response from the assistant. */
10
10
  export type AssistantMessageEvent = Extract<SessionEvent, {
@@ -42,12 +42,14 @@ export declare class CopilotSession {
42
42
  private eventHandlers;
43
43
  private typedEventHandlers;
44
44
  private toolHandlers;
45
+ private commandHandlers;
45
46
  private permissionHandler?;
46
47
  private userInputHandler?;
47
48
  private hooks?;
48
49
  private transformCallbacks?;
49
50
  private _rpc;
50
51
  private traceContextProvider?;
52
+ private _capabilities;
51
53
  /**
52
54
  * Creates a new CopilotSession instance.
53
55
  *
@@ -68,6 +70,24 @@ export declare class CopilotSession {
68
70
  * Undefined if infinite sessions are disabled.
69
71
  */
70
72
  get workspacePath(): string | undefined;
73
+ /**
74
+ * Host capabilities reported when the session was created or resumed.
75
+ * Use this to check feature support before calling capability-gated APIs.
76
+ */
77
+ get capabilities(): SessionCapabilities;
78
+ /**
79
+ * Interactive UI methods for showing dialogs to the user.
80
+ * Only available when the CLI host supports elicitation
81
+ * (`session.capabilities.ui?.elicitation === true`).
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * if (session.capabilities.ui?.elicitation) {
86
+ * const ok = await session.ui.confirm("Deploy to production?");
87
+ * }
88
+ * ```
89
+ */
90
+ get ui(): SessionUiApi;
71
91
  /**
72
92
  * Sends a message to this session and waits for the response.
73
93
  *
@@ -182,6 +202,11 @@ export declare class CopilotSession {
182
202
  * @internal
183
203
  */
184
204
  private _executePermissionAndRespond;
205
+ /**
206
+ * Executes a command handler and sends the result back via RPC.
207
+ * @internal
208
+ */
209
+ private _executeCommandAndRespond;
185
210
  /**
186
211
  * Registers custom tool handlers for this session.
187
212
  *
@@ -200,6 +225,28 @@ export declare class CopilotSession {
200
225
  * @internal This method is for internal use by the SDK.
201
226
  */
202
227
  getToolHandler(name: string): ToolHandler | undefined;
228
+ /**
229
+ * Registers command handlers for this session.
230
+ *
231
+ * @param commands - An array of command definitions with handlers, or undefined to clear
232
+ * @internal This method is typically called internally when creating/resuming a session.
233
+ */
234
+ registerCommands(commands?: {
235
+ name: string;
236
+ handler: CommandHandler;
237
+ }[]): void;
238
+ /**
239
+ * Sets the host capabilities for this session.
240
+ *
241
+ * @param capabilities - The capabilities object from the create/resume response
242
+ * @internal This method is typically called internally when creating/resuming a session.
243
+ */
244
+ setCapabilities(capabilities?: SessionCapabilities): void;
245
+ private assertElicitation;
246
+ private _elicitation;
247
+ private _confirm;
248
+ private _select;
249
+ private _input;
203
250
  /**
204
251
  * Registers a handler for permission requests.
205
252
  *
package/dist/session.js CHANGED
@@ -21,12 +21,14 @@ class CopilotSession {
21
21
  eventHandlers = /* @__PURE__ */ new Set();
22
22
  typedEventHandlers = /* @__PURE__ */ new Map();
23
23
  toolHandlers = /* @__PURE__ */ new Map();
24
+ commandHandlers = /* @__PURE__ */ new Map();
24
25
  permissionHandler;
25
26
  userInputHandler;
26
27
  hooks;
27
28
  transformCallbacks;
28
29
  _rpc = null;
29
30
  traceContextProvider;
31
+ _capabilities = {};
30
32
  /**
31
33
  * Typed session-scoped RPC methods.
32
34
  */
@@ -44,6 +46,33 @@ class CopilotSession {
44
46
  get workspacePath() {
45
47
  return this._workspacePath;
46
48
  }
49
+ /**
50
+ * Host capabilities reported when the session was created or resumed.
51
+ * Use this to check feature support before calling capability-gated APIs.
52
+ */
53
+ get capabilities() {
54
+ return this._capabilities;
55
+ }
56
+ /**
57
+ * Interactive UI methods for showing dialogs to the user.
58
+ * Only available when the CLI host supports elicitation
59
+ * (`session.capabilities.ui?.elicitation === true`).
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * if (session.capabilities.ui?.elicitation) {
64
+ * const ok = await session.ui.confirm("Deploy to production?");
65
+ * }
66
+ * ```
67
+ */
68
+ get ui() {
69
+ return {
70
+ elicitation: (params) => this._elicitation(params),
71
+ confirm: (message) => this._confirm(message),
72
+ select: (message, options) => this._select(message, options),
73
+ input: (message, options) => this._input(message, options)
74
+ };
75
+ }
47
76
  /**
48
77
  * Sends a message to this session and waits for the response.
49
78
  *
@@ -213,6 +242,9 @@ class CopilotSession {
213
242
  if (this.permissionHandler) {
214
243
  void this._executePermissionAndRespond(requestId, permissionRequest);
215
244
  }
245
+ } else if (event.type === "command.execute") {
246
+ const { requestId, commandName, command, args } = event.data;
247
+ void this._executeCommandAndRespond(requestId, commandName, command, args);
216
248
  }
217
249
  }
218
250
  /**
@@ -277,6 +309,39 @@ class CopilotSession {
277
309
  }
278
310
  }
279
311
  }
312
+ /**
313
+ * Executes a command handler and sends the result back via RPC.
314
+ * @internal
315
+ */
316
+ async _executeCommandAndRespond(requestId, commandName, command, args) {
317
+ const handler = this.commandHandlers.get(commandName);
318
+ if (!handler) {
319
+ try {
320
+ await this.rpc.commands.handlePendingCommand({
321
+ requestId,
322
+ error: `Unknown command: ${commandName}`
323
+ });
324
+ } catch (rpcError) {
325
+ if (!(rpcError instanceof ConnectionError || rpcError instanceof ResponseError)) {
326
+ throw rpcError;
327
+ }
328
+ }
329
+ return;
330
+ }
331
+ try {
332
+ await handler({ sessionId: this.sessionId, command, commandName, args });
333
+ await this.rpc.commands.handlePendingCommand({ requestId });
334
+ } catch (error) {
335
+ const message = error instanceof Error ? error.message : String(error);
336
+ try {
337
+ await this.rpc.commands.handlePendingCommand({ requestId, error: message });
338
+ } catch (rpcError) {
339
+ if (!(rpcError instanceof ConnectionError || rpcError instanceof ResponseError)) {
340
+ throw rpcError;
341
+ }
342
+ }
343
+ }
344
+ }
280
345
  /**
281
346
  * Registers custom tool handlers for this session.
282
347
  *
@@ -305,6 +370,99 @@ class CopilotSession {
305
370
  getToolHandler(name) {
306
371
  return this.toolHandlers.get(name);
307
372
  }
373
+ /**
374
+ * Registers command handlers for this session.
375
+ *
376
+ * @param commands - An array of command definitions with handlers, or undefined to clear
377
+ * @internal This method is typically called internally when creating/resuming a session.
378
+ */
379
+ registerCommands(commands) {
380
+ this.commandHandlers.clear();
381
+ if (!commands) {
382
+ return;
383
+ }
384
+ for (const cmd of commands) {
385
+ this.commandHandlers.set(cmd.name, cmd.handler);
386
+ }
387
+ }
388
+ /**
389
+ * Sets the host capabilities for this session.
390
+ *
391
+ * @param capabilities - The capabilities object from the create/resume response
392
+ * @internal This method is typically called internally when creating/resuming a session.
393
+ */
394
+ setCapabilities(capabilities) {
395
+ this._capabilities = capabilities ?? {};
396
+ }
397
+ assertElicitation() {
398
+ if (!this._capabilities.ui?.elicitation) {
399
+ throw new Error(
400
+ "Elicitation is not supported by the host. Check session.capabilities.ui?.elicitation before calling UI methods."
401
+ );
402
+ }
403
+ }
404
+ async _elicitation(params) {
405
+ this.assertElicitation();
406
+ return this.rpc.ui.elicitation({
407
+ message: params.message,
408
+ requestedSchema: params.requestedSchema
409
+ });
410
+ }
411
+ async _confirm(message) {
412
+ this.assertElicitation();
413
+ const result = await this.rpc.ui.elicitation({
414
+ message,
415
+ requestedSchema: {
416
+ type: "object",
417
+ properties: {
418
+ confirmed: { type: "boolean", default: true }
419
+ },
420
+ required: ["confirmed"]
421
+ }
422
+ });
423
+ return result.action === "accept" && result.content?.confirmed === true;
424
+ }
425
+ async _select(message, options) {
426
+ this.assertElicitation();
427
+ const result = await this.rpc.ui.elicitation({
428
+ message,
429
+ requestedSchema: {
430
+ type: "object",
431
+ properties: {
432
+ selection: { type: "string", enum: options }
433
+ },
434
+ required: ["selection"]
435
+ }
436
+ });
437
+ if (result.action === "accept" && result.content?.selection != null) {
438
+ return result.content.selection;
439
+ }
440
+ return null;
441
+ }
442
+ async _input(message, options) {
443
+ this.assertElicitation();
444
+ const field = { type: "string" };
445
+ if (options?.title) field.title = options.title;
446
+ if (options?.description) field.description = options.description;
447
+ if (options?.minLength != null) field.minLength = options.minLength;
448
+ if (options?.maxLength != null) field.maxLength = options.maxLength;
449
+ if (options?.format) field.format = options.format;
450
+ if (options?.default != null) field.default = options.default;
451
+ const result = await this.rpc.ui.elicitation({
452
+ message,
453
+ requestedSchema: {
454
+ type: "object",
455
+ properties: {
456
+ value: field
457
+ },
458
+ required: ["value"]
459
+ }
460
+ });
461
+ if (result.action === "accept" && result.content?.value != null) {
462
+ return result.content.value;
463
+ }
464
+ return null;
465
+ }
308
466
  /**
309
467
  * Registers a handler for permission requests.
310
468
  *
package/dist/types.d.ts CHANGED
@@ -218,6 +218,187 @@ export declare function defineTool<T = unknown>(name: string, config: {
218
218
  overridesBuiltInTool?: boolean;
219
219
  skipPermission?: boolean;
220
220
  }): Tool<T>;
221
+ /**
222
+ * Context passed to a command handler when a command is executed.
223
+ */
224
+ export interface CommandContext {
225
+ /** Session ID where the command was invoked */
226
+ sessionId: string;
227
+ /** The full command text (e.g. "/deploy production") */
228
+ command: string;
229
+ /** Command name without leading / */
230
+ commandName: string;
231
+ /** Raw argument string after the command name */
232
+ args: string;
233
+ }
234
+ /**
235
+ * Handler invoked when a registered command is executed by a user.
236
+ */
237
+ export type CommandHandler = (context: CommandContext) => Promise<void> | void;
238
+ /**
239
+ * Definition of a slash command registered with the session.
240
+ * When the CLI is running with a TUI, registered commands appear as
241
+ * `/commandName` for the user to invoke.
242
+ */
243
+ export interface CommandDefinition {
244
+ /** Command name (without leading /). */
245
+ name: string;
246
+ /** Human-readable description shown in command completion UI. */
247
+ description?: string;
248
+ /** Handler invoked when the command is executed. */
249
+ handler: CommandHandler;
250
+ }
251
+ /**
252
+ * Capabilities reported by the CLI host for this session.
253
+ */
254
+ export interface SessionCapabilities {
255
+ ui?: {
256
+ /** Whether the host supports interactive elicitation dialogs. */
257
+ elicitation?: boolean;
258
+ };
259
+ }
260
+ /**
261
+ * A single field in an elicitation schema — matches the MCP SDK's
262
+ * `PrimitiveSchemaDefinition` union.
263
+ */
264
+ export type ElicitationSchemaField = {
265
+ type: "string";
266
+ title?: string;
267
+ description?: string;
268
+ enum: string[];
269
+ enumNames?: string[];
270
+ default?: string;
271
+ } | {
272
+ type: "string";
273
+ title?: string;
274
+ description?: string;
275
+ oneOf: {
276
+ const: string;
277
+ title: string;
278
+ }[];
279
+ default?: string;
280
+ } | {
281
+ type: "array";
282
+ title?: string;
283
+ description?: string;
284
+ minItems?: number;
285
+ maxItems?: number;
286
+ items: {
287
+ type: "string";
288
+ enum: string[];
289
+ };
290
+ default?: string[];
291
+ } | {
292
+ type: "array";
293
+ title?: string;
294
+ description?: string;
295
+ minItems?: number;
296
+ maxItems?: number;
297
+ items: {
298
+ anyOf: {
299
+ const: string;
300
+ title: string;
301
+ }[];
302
+ };
303
+ default?: string[];
304
+ } | {
305
+ type: "boolean";
306
+ title?: string;
307
+ description?: string;
308
+ default?: boolean;
309
+ } | {
310
+ type: "string";
311
+ title?: string;
312
+ description?: string;
313
+ minLength?: number;
314
+ maxLength?: number;
315
+ format?: "email" | "uri" | "date" | "date-time";
316
+ default?: string;
317
+ } | {
318
+ type: "number" | "integer";
319
+ title?: string;
320
+ description?: string;
321
+ minimum?: number;
322
+ maximum?: number;
323
+ default?: number;
324
+ };
325
+ /**
326
+ * Schema describing the form fields for an elicitation request.
327
+ */
328
+ export interface ElicitationSchema {
329
+ type: "object";
330
+ properties: Record<string, ElicitationSchemaField>;
331
+ required?: string[];
332
+ }
333
+ /**
334
+ * Primitive field value in an elicitation result.
335
+ * Matches MCP SDK's `ElicitResult.content` value type.
336
+ */
337
+ export type ElicitationFieldValue = string | number | boolean | string[];
338
+ /**
339
+ * Result returned from an elicitation request.
340
+ */
341
+ export interface ElicitationResult {
342
+ /** User action: "accept" (submitted), "decline" (rejected), or "cancel" (dismissed). */
343
+ action: "accept" | "decline" | "cancel";
344
+ /** Form values submitted by the user (present when action is "accept"). */
345
+ content?: Record<string, ElicitationFieldValue>;
346
+ }
347
+ /**
348
+ * Parameters for a raw elicitation request.
349
+ */
350
+ export interface ElicitationParams {
351
+ /** Message describing what information is needed from the user. */
352
+ message: string;
353
+ /** JSON Schema describing the form fields to present. */
354
+ requestedSchema: ElicitationSchema;
355
+ }
356
+ /**
357
+ * Options for the `input()` convenience method.
358
+ */
359
+ export interface InputOptions {
360
+ /** Title label for the input field. */
361
+ title?: string;
362
+ /** Descriptive text shown below the field. */
363
+ description?: string;
364
+ /** Minimum character length. */
365
+ minLength?: number;
366
+ /** Maximum character length. */
367
+ maxLength?: number;
368
+ /** Semantic format hint. */
369
+ format?: "email" | "uri" | "date" | "date-time";
370
+ /** Default value pre-populated in the field. */
371
+ default?: string;
372
+ }
373
+ /**
374
+ * The `session.ui` API object providing interactive UI methods.
375
+ * Only usable when the CLI host supports elicitation.
376
+ */
377
+ export interface SessionUiApi {
378
+ /**
379
+ * Shows a generic elicitation dialog with a custom schema.
380
+ * @throws Error if the host does not support elicitation.
381
+ */
382
+ elicitation(params: ElicitationParams): Promise<ElicitationResult>;
383
+ /**
384
+ * Shows a confirmation dialog and returns the user's boolean answer.
385
+ * Returns `false` if the user declines or cancels.
386
+ * @throws Error if the host does not support elicitation.
387
+ */
388
+ confirm(message: string): Promise<boolean>;
389
+ /**
390
+ * Shows a selection dialog with the given options.
391
+ * Returns the selected value, or `null` if the user declines/cancels.
392
+ * @throws Error if the host does not support elicitation.
393
+ */
394
+ select(message: string, options: string[]): Promise<string | null>;
395
+ /**
396
+ * Shows a text input dialog.
397
+ * Returns the entered text, or `null` if the user declines/cancels.
398
+ * @throws Error if the host does not support elicitation.
399
+ */
400
+ input(message: string, options?: InputOptions): Promise<string | null>;
401
+ }
221
402
  export interface ToolCallRequestPayload {
222
403
  sessionId: string;
223
404
  toolCallId: string;
@@ -675,6 +856,12 @@ export interface SessionConfig {
675
856
  * Tools exposed to the CLI server
676
857
  */
677
858
  tools?: Tool<any>[];
859
+ /**
860
+ * Slash commands registered for this session.
861
+ * When the CLI has a TUI, each command appears as `/name` for the user to invoke.
862
+ * The handler is called when the user executes the command.
863
+ */
864
+ commands?: CommandDefinition[];
678
865
  /**
679
866
  * System message configuration
680
867
  * Controls how the system prompt is constructed
@@ -759,7 +946,7 @@ export interface SessionConfig {
759
946
  /**
760
947
  * Configuration for resuming a session
761
948
  */
762
- export type ResumeSessionConfig = Pick<SessionConfig, "clientName" | "model" | "tools" | "systemMessage" | "availableTools" | "excludedTools" | "provider" | "streaming" | "reasoningEffort" | "onPermissionRequest" | "onUserInputRequest" | "hooks" | "workingDirectory" | "configDir" | "mcpServers" | "customAgents" | "agent" | "skillDirectories" | "disabledSkills" | "infiniteSessions" | "onEvent"> & {
949
+ export type ResumeSessionConfig = Pick<SessionConfig, "clientName" | "model" | "tools" | "commands" | "systemMessage" | "availableTools" | "excludedTools" | "provider" | "streaming" | "reasoningEffort" | "onPermissionRequest" | "onUserInputRequest" | "hooks" | "workingDirectory" | "configDir" | "mcpServers" | "customAgents" | "agent" | "skillDirectories" | "disabledSkills" | "infiniteSessions" | "onEvent"> & {
763
950
  /**
764
951
  * When true, skips emitting the session.resume event.
765
952
  * Useful for reconnecting to a session without triggering resume-related side effects.
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.2.0",
7
+ "version": "0.2.1-preview.0",
8
8
  "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC",
9
9
  "main": "./dist/cjs/index.js",
10
10
  "types": "./dist/index.d.ts",
@@ -56,7 +56,7 @@
56
56
  "author": "GitHub",
57
57
  "license": "MIT",
58
58
  "dependencies": {
59
- "@github/copilot": "^1.0.10",
59
+ "@github/copilot": "^1.0.11",
60
60
  "vscode-jsonrpc": "^8.2.1",
61
61
  "zod": "^4.3.6"
62
62
  },