@github/copilot-sdk 0.2.0 → 0.2.1-preview.1

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
@@ -60,7 +60,10 @@ await client.stop();
60
60
  Sessions also support `Symbol.asyncDispose` for use with [`await using`](https://github.com/tc39/proposal-explicit-resource-management) (TypeScript 5.2+/Node.js 18.0+):
61
61
 
62
62
  ```typescript
63
- await using session = await client.createSession({ model: "gpt-5", onPermissionRequest: approveAll });
63
+ await using session = await client.createSession({
64
+ model: "gpt-5",
65
+ onPermissionRequest: approveAll,
66
+ });
64
67
  // session is automatically disconnected when leaving scope
65
68
  ```
66
69
 
@@ -76,7 +79,7 @@ new CopilotClient(options?: CopilotClientOptions)
76
79
 
77
80
  **Options:**
78
81
 
79
- - `cliPath?: string` - Path to CLI executable (default: "copilot" from PATH)
82
+ - `cliPath?: string` - Path to CLI executable (default: uses COPILOT_CLI_PATH env var or bundled instance)
80
83
  - `cliArgs?: string[]` - Extra arguments prepended before SDK-managed flags (e.g. `["./dist-cli/index.js"]` when using `node`)
81
84
  - `cliUrl?: string` - URL of existing CLI server to connect to (e.g., `"localhost:8080"`, `"http://127.0.0.1:9000"`, or just `"8080"`). When provided, the client will not spawn a CLI process.
82
85
  - `port?: number` - Server port (default: 0 for random)
@@ -184,6 +187,7 @@ const unsubscribe = client.on((event) => {
184
187
  ```
185
188
 
186
189
  **Lifecycle Event Types:**
190
+
187
191
  - `session.created` - A new session was created
188
192
  - `session.deleted` - A session was deleted
189
193
  - `session.updated` - A session was updated (e.g., new messages)
@@ -279,7 +283,21 @@ Get all events/messages from this session.
279
283
 
280
284
  Disconnect the session and free resources. Session data on disk is preserved for later resumption.
281
285
 
282
- ##### `destroy(): Promise<void>` *(deprecated)*
286
+ ##### `capabilities: SessionCapabilities`
287
+
288
+ Host capabilities reported when the session was created or resumed. Use this to check feature support before calling capability-gated APIs.
289
+
290
+ ```typescript
291
+ if (session.capabilities.ui?.elicitation) {
292
+ const ok = await session.ui.confirm("Deploy?");
293
+ }
294
+ ```
295
+
296
+ ##### `ui: SessionUiApi`
297
+
298
+ 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.
299
+
300
+ ##### `destroy(): Promise<void>` _(deprecated)_
283
301
 
284
302
  Deprecated — use `disconnect()` instead.
285
303
 
@@ -294,6 +312,8 @@ Sessions emit various events during processing:
294
312
  - `assistant.message_delta` - Streaming response chunk
295
313
  - `tool.execution_start` - Tool execution started
296
314
  - `tool.execution_complete` - Tool execution completed
315
+ - `command.execute` - Command dispatch request (handled internally by the SDK)
316
+ - `commands.changed` - Command registration changed
297
317
  - And more...
298
318
 
299
319
  See `SessionEvent` type in the source for full details.
@@ -438,8 +458,10 @@ defineTool("edit_file", {
438
458
  description: "Custom file editor with project-specific validation",
439
459
  parameters: z.object({ path: z.string(), content: z.string() }),
440
460
  overridesBuiltInTool: true,
441
- handler: async ({ path, content }) => { /* your logic */ },
442
- })
461
+ handler: async ({ path, content }) => {
462
+ /* your logic */
463
+ },
464
+ });
443
465
  ```
444
466
 
445
467
  #### Skipping Permission Prompts
@@ -451,10 +473,78 @@ defineTool("safe_lookup", {
451
473
  description: "A read-only lookup that needs no confirmation",
452
474
  parameters: z.object({ id: z.string() }),
453
475
  skipPermission: true,
454
- handler: async ({ id }) => { /* your logic */ },
455
- })
476
+ handler: async ({ id }) => {
477
+ /* your logic */
478
+ },
479
+ });
456
480
  ```
457
481
 
482
+ ### Commands
483
+
484
+ 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.
485
+
486
+ ```ts
487
+ const session = await client.createSession({
488
+ onPermissionRequest: approveAll,
489
+ commands: [
490
+ {
491
+ name: "deploy",
492
+ description: "Deploy the app to production",
493
+ handler: async ({ commandName, args }) => {
494
+ console.log(`Deploying with args: ${args}`);
495
+ // Do work here — any thrown error is reported back to the CLI
496
+ },
497
+ },
498
+ ],
499
+ });
500
+ ```
501
+
502
+ 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.
503
+
504
+ Commands are sent to the CLI on both `createSession` and `resumeSession`, so you can update the command set when resuming.
505
+
506
+ ### UI Elicitation
507
+
508
+ 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.
509
+
510
+ > **Capability check:** Elicitation is only available when the host advertises support. Always check `session.capabilities.ui?.elicitation` before calling UI methods.
511
+
512
+ ```ts
513
+ const session = await client.createSession({ onPermissionRequest: approveAll });
514
+
515
+ if (session.capabilities.ui?.elicitation) {
516
+ // Confirm dialog — returns boolean
517
+ const ok = await session.ui.confirm("Deploy to production?");
518
+
519
+ // Selection dialog — returns selected value or null
520
+ const env = await session.ui.select("Pick environment", ["production", "staging", "dev"]);
521
+
522
+ // Text input — returns string or null
523
+ const name = await session.ui.input("Project name:", {
524
+ title: "Name",
525
+ minLength: 1,
526
+ maxLength: 50,
527
+ });
528
+
529
+ // Generic elicitation with full schema control
530
+ const result = await session.ui.elicitation({
531
+ message: "Configure deployment",
532
+ requestedSchema: {
533
+ type: "object",
534
+ properties: {
535
+ region: { type: "string", enum: ["us-east", "eu-west"] },
536
+ dryRun: { type: "boolean", default: true },
537
+ },
538
+ required: ["region"],
539
+ },
540
+ });
541
+ // result.action: "accept" | "decline" | "cancel"
542
+ // result.content: { region: "us-east", dryRun: true } (when accepted)
543
+ }
544
+ ```
545
+
546
+ All UI methods throw if elicitation is not supported by the host.
547
+
458
548
  ### System Message Customization
459
549
 
460
550
  Control the system prompt using `systemMessage` in session config:
@@ -489,7 +579,10 @@ const session = await client.createSession({
489
579
  mode: "customize",
490
580
  sections: {
491
581
  // Replace the tone/style section
492
- tone: { action: "replace", content: "Respond in a warm, professional tone. Be thorough in explanations." },
582
+ tone: {
583
+ action: "replace",
584
+ content: "Respond in a warm, professional tone. Be thorough in explanations.",
585
+ },
493
586
  // Remove coding-specific rules
494
587
  code_change_rules: { action: "remove" },
495
588
  // Append to existing guidelines
@@ -504,6 +597,7 @@ const session = await client.createSession({
504
597
  Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `last_instructions`. Use the `SYSTEM_PROMPT_SECTIONS` constant for descriptions of each section.
505
598
 
506
599
  Each section override supports four actions:
600
+
507
601
  - **`replace`** — Replace the section content entirely
508
602
  - **`remove`** — Remove the section from the prompt
509
603
  - **`append`** — Add content after the existing section
@@ -542,7 +636,7 @@ const session = await client.createSession({
542
636
  model: "gpt-5",
543
637
  infiniteSessions: {
544
638
  enabled: true,
545
- backgroundCompactionThreshold: 0.80, // Start compacting at 80% context usage
639
+ backgroundCompactionThreshold: 0.8, // Start compacting at 80% context usage
546
640
  bufferExhaustionThreshold: 0.95, // Block at 95% until compaction completes
547
641
  },
548
642
  });
@@ -641,8 +735,8 @@ const session = await client.createSession({
641
735
  const session = await client.createSession({
642
736
  model: "gpt-4",
643
737
  provider: {
644
- type: "azure", // Must be "azure" for Azure endpoints, NOT "openai"
645
- baseUrl: "https://my-resource.openai.azure.com", // Just the host, no path
738
+ type: "azure", // Must be "azure" for Azure endpoints, NOT "openai"
739
+ baseUrl: "https://my-resource.openai.azure.com", // Just the host, no path
646
740
  apiKey: process.env.AZURE_OPENAI_KEY,
647
741
  azure: {
648
742
  apiVersion: "2024-10-21",
@@ -652,6 +746,7 @@ const session = await client.createSession({
652
746
  ```
653
747
 
654
748
  > **Important notes:**
749
+ >
655
750
  > - When using a custom provider, the `model` parameter is **required**. The SDK will throw an error if no model is specified.
656
751
  > - For Azure OpenAI endpoints (`*.openai.azure.com`), you **must** use `type: "azure"`, not `type: "openai"`.
657
752
  > - 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.
@@ -662,9 +757,9 @@ The SDK supports OpenTelemetry for distributed tracing. Provide a `telemetry` co
662
757
 
663
758
  ```typescript
664
759
  const client = new CopilotClient({
665
- telemetry: {
666
- otlpEndpoint: "http://localhost:4318",
667
- },
760
+ telemetry: {
761
+ otlpEndpoint: "http://localhost:4318",
762
+ },
668
763
  });
669
764
  ```
670
765
 
@@ -690,12 +785,12 @@ If you're already using `@opentelemetry/api` in your app and want this linkage,
690
785
  import { propagation, context } from "@opentelemetry/api";
691
786
 
692
787
  const client = new CopilotClient({
693
- telemetry: { otlpEndpoint: "http://localhost:4318" },
694
- onGetTraceContext: () => {
695
- const carrier: Record<string, string> = {};
696
- propagation.inject(context.active(), carrier);
697
- return carrier;
698
- },
788
+ telemetry: { otlpEndpoint: "http://localhost:4318" },
789
+ onGetTraceContext: () => {
790
+ const carrier: Record<string, string> = {};
791
+ propagation.inject(context.active(), carrier);
792
+ return carrier;
793
+ },
699
794
  });
700
795
  ```
701
796
 
@@ -755,14 +850,15 @@ const session = await client.createSession({
755
850
 
756
851
  ### Permission Result Kinds
757
852
 
758
- | Kind | Meaning |
759
- |------|---------|
760
- | `"approved"` | Allow the tool to run |
761
- | `"denied-interactively-by-user"` | User explicitly denied the request |
762
- | `"denied-no-approval-rule-and-could-not-request-from-user"` | No approval rule matched and user could not be asked |
763
- | `"denied-by-rules"` | Denied by a policy rule |
764
- | `"denied-by-content-exclusion-policy"` | Denied due to a content exclusion policy |
765
- | `"no-result"` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) |
853
+ | Kind | Meaning |
854
+ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
855
+ | `"approved"` | Allow the tool to run |
856
+ | `"denied-interactively-by-user"` | User explicitly denied the request |
857
+ | `"denied-no-approval-rule-and-could-not-request-from-user"` | No approval rule matched and user could not be asked |
858
+ | `"denied-by-rules"` | Denied by a policy rule |
859
+ | `"denied-by-content-exclusion-policy"` | Denied due to a content exclusion policy |
860
+ | `"no-result"` | Leave the request unanswered (only valid with protocol v1; rejected by protocol v2 servers) |
861
+
766
862
  ### Resuming Sessions
767
863
 
768
864
  Pass `onPermissionRequest` when resuming a session too — it is required:
@@ -175,8 +175,9 @@ class CopilotClient {
175
175
  }
176
176
  this.onListModels = options.onListModels;
177
177
  this.onGetTraceContext = options.onGetTraceContext;
178
+ const effectiveEnv = options.env ?? process.env;
178
179
  this.options = {
179
- cliPath: options.cliUrl ? void 0 : options.cliPath || getBundledCliPath(),
180
+ cliPath: options.cliUrl ? void 0 : options.cliPath || effectiveEnv.COPILOT_CLI_PATH || getBundledCliPath(),
180
181
  cliArgs: options.cliArgs ?? [],
181
182
  cwd: options.cwd ?? process.cwd(),
182
183
  port: options.port || 0,
@@ -187,7 +188,7 @@ class CopilotClient {
187
188
  logLevel: options.logLevel || "debug",
188
189
  autoStart: options.autoStart ?? true,
189
190
  autoRestart: false,
190
- env: options.env ?? process.env,
191
+ env: effectiveEnv,
191
192
  githubToken: options.githubToken,
192
193
  // Default useLoggedInUser to false when githubToken is provided, otherwise true
193
194
  useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true),
@@ -451,6 +452,7 @@ class CopilotClient {
451
452
  this.onGetTraceContext
452
453
  );
453
454
  session.registerTools(config.tools);
455
+ session.registerCommands(config.commands);
454
456
  session.registerPermissionHandler(config.onPermissionRequest);
455
457
  if (config.onUserInputRequest) {
456
458
  session.registerUserInputHandler(config.onUserInputRequest);
@@ -482,6 +484,10 @@ class CopilotClient {
482
484
  overridesBuiltInTool: tool.overridesBuiltInTool,
483
485
  skipPermission: tool.skipPermission
484
486
  })),
487
+ commands: config.commands?.map((cmd) => ({
488
+ name: cmd.name,
489
+ description: cmd.description
490
+ })),
485
491
  systemMessage: wireSystemMessage,
486
492
  availableTools: config.availableTools,
487
493
  excludedTools: config.excludedTools,
@@ -500,8 +506,9 @@ class CopilotClient {
500
506
  disabledSkills: config.disabledSkills,
501
507
  infiniteSessions: config.infiniteSessions
502
508
  });
503
- const { workspacePath } = response;
509
+ const { workspacePath, capabilities } = response;
504
510
  session["_workspacePath"] = workspacePath;
511
+ session.setCapabilities(capabilities);
505
512
  } catch (e) {
506
513
  this.sessions.delete(sessionId);
507
514
  throw e;
@@ -552,6 +559,7 @@ class CopilotClient {
552
559
  this.onGetTraceContext
553
560
  );
554
561
  session.registerTools(config.tools);
562
+ session.registerCommands(config.commands);
555
563
  session.registerPermissionHandler(config.onPermissionRequest);
556
564
  if (config.onUserInputRequest) {
557
565
  session.registerUserInputHandler(config.onUserInputRequest);
@@ -586,6 +594,10 @@ class CopilotClient {
586
594
  overridesBuiltInTool: tool.overridesBuiltInTool,
587
595
  skipPermission: tool.skipPermission
588
596
  })),
597
+ commands: config.commands?.map((cmd) => ({
598
+ name: cmd.name,
599
+ description: cmd.description
600
+ })),
589
601
  provider: config.provider,
590
602
  requestPermission: true,
591
603
  requestUserInput: !!config.onUserInputRequest,
@@ -602,8 +614,9 @@ class CopilotClient {
602
614
  infiniteSessions: config.infiniteSessions,
603
615
  disableResume: config.disableResume
604
616
  });
605
- const { workspacePath } = response;
617
+ const { workspacePath, capabilities } = response;
606
618
  session["_workspacePath"] = workspacePath;
619
+ session.setCapabilities(capabilities);
607
620
  } catch (e) {
608
621
  this.sessions.delete(sessionId);
609
622
  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
@@ -155,8 +155,9 @@ class CopilotClient {
155
155
  }
156
156
  this.onListModels = options.onListModels;
157
157
  this.onGetTraceContext = options.onGetTraceContext;
158
+ const effectiveEnv = options.env ?? process.env;
158
159
  this.options = {
159
- cliPath: options.cliUrl ? void 0 : options.cliPath || getBundledCliPath(),
160
+ cliPath: options.cliUrl ? void 0 : options.cliPath || effectiveEnv.COPILOT_CLI_PATH || getBundledCliPath(),
160
161
  cliArgs: options.cliArgs ?? [],
161
162
  cwd: options.cwd ?? process.cwd(),
162
163
  port: options.port || 0,
@@ -167,7 +168,7 @@ class CopilotClient {
167
168
  logLevel: options.logLevel || "debug",
168
169
  autoStart: options.autoStart ?? true,
169
170
  autoRestart: false,
170
- env: options.env ?? process.env,
171
+ env: effectiveEnv,
171
172
  githubToken: options.githubToken,
172
173
  // Default useLoggedInUser to false when githubToken is provided, otherwise true
173
174
  useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true),
@@ -431,6 +432,7 @@ class CopilotClient {
431
432
  this.onGetTraceContext
432
433
  );
433
434
  session.registerTools(config.tools);
435
+ session.registerCommands(config.commands);
434
436
  session.registerPermissionHandler(config.onPermissionRequest);
435
437
  if (config.onUserInputRequest) {
436
438
  session.registerUserInputHandler(config.onUserInputRequest);
@@ -462,6 +464,10 @@ class CopilotClient {
462
464
  overridesBuiltInTool: tool.overridesBuiltInTool,
463
465
  skipPermission: tool.skipPermission
464
466
  })),
467
+ commands: config.commands?.map((cmd) => ({
468
+ name: cmd.name,
469
+ description: cmd.description
470
+ })),
465
471
  systemMessage: wireSystemMessage,
466
472
  availableTools: config.availableTools,
467
473
  excludedTools: config.excludedTools,
@@ -480,8 +486,9 @@ class CopilotClient {
480
486
  disabledSkills: config.disabledSkills,
481
487
  infiniteSessions: config.infiniteSessions
482
488
  });
483
- const { workspacePath } = response;
489
+ const { workspacePath, capabilities } = response;
484
490
  session["_workspacePath"] = workspacePath;
491
+ session.setCapabilities(capabilities);
485
492
  } catch (e) {
486
493
  this.sessions.delete(sessionId);
487
494
  throw e;
@@ -532,6 +539,7 @@ class CopilotClient {
532
539
  this.onGetTraceContext
533
540
  );
534
541
  session.registerTools(config.tools);
542
+ session.registerCommands(config.commands);
535
543
  session.registerPermissionHandler(config.onPermissionRequest);
536
544
  if (config.onUserInputRequest) {
537
545
  session.registerUserInputHandler(config.onUserInputRequest);
@@ -566,6 +574,10 @@ class CopilotClient {
566
574
  overridesBuiltInTool: tool.overridesBuiltInTool,
567
575
  skipPermission: tool.skipPermission
568
576
  })),
577
+ commands: config.commands?.map((cmd) => ({
578
+ name: cmd.name,
579
+ description: cmd.description
580
+ })),
569
581
  provider: config.provider,
570
582
  requestPermission: true,
571
583
  requestUserInput: !!config.onUserInputRequest,
@@ -582,8 +594,9 @@ class CopilotClient {
582
594
  infiniteSessions: config.infiniteSessions,
583
595
  disableResume: config.disableResume
584
596
  });
585
- const { workspacePath } = response;
597
+ const { workspacePath, capabilities } = response;
586
598
  session["_workspacePath"] = workspacePath;
599
+ session.setCapabilities(capabilities);
587
600
  } catch (e) {
588
601
  this.sessions.delete(sessionId);
589
602
  throw e;
@@ -89,6 +89,10 @@ export type SessionEvent = {
89
89
  * Whether the session was already in use by another client at start time
90
90
  */
91
91
  alreadyInUse?: boolean;
92
+ /**
93
+ * Whether this session supports remote steering via Mission Control
94
+ */
95
+ steerable?: boolean;
92
96
  };
93
97
  } | {
94
98
  /**
@@ -2070,6 +2074,10 @@ export type SessionEvent = {
2070
2074
  * Version of the plugin this skill originated from, when applicable
2071
2075
  */
2072
2076
  pluginVersion?: string;
2077
+ /**
2078
+ * Description of the skill from its SKILL.md frontmatter
2079
+ */
2080
+ description?: string;
2073
2081
  };
2074
2082
  } | {
2075
2083
  /**
@@ -2144,6 +2152,22 @@ export type SessionEvent = {
2144
2152
  * Human-readable display name of the sub-agent
2145
2153
  */
2146
2154
  agentDisplayName: string;
2155
+ /**
2156
+ * Model used by the sub-agent
2157
+ */
2158
+ model?: string;
2159
+ /**
2160
+ * Total number of tool calls made by the sub-agent
2161
+ */
2162
+ totalToolCalls?: number;
2163
+ /**
2164
+ * Total tokens (input + output) consumed by the sub-agent
2165
+ */
2166
+ totalTokens?: number;
2167
+ /**
2168
+ * Wall-clock duration of the sub-agent execution in milliseconds
2169
+ */
2170
+ durationMs?: number;
2147
2171
  };
2148
2172
  } | {
2149
2173
  /**
@@ -2183,6 +2207,22 @@ export type SessionEvent = {
2183
2207
  * Error message describing why the sub-agent failed
2184
2208
  */
2185
2209
  error: string;
2210
+ /**
2211
+ * Model used by the sub-agent (if any model calls succeeded before failure)
2212
+ */
2213
+ model?: string;
2214
+ /**
2215
+ * Total number of tool calls made before the sub-agent failed
2216
+ */
2217
+ totalToolCalls?: number;
2218
+ /**
2219
+ * Total tokens (input + output) consumed before the sub-agent failed
2220
+ */
2221
+ totalTokens?: number;
2222
+ /**
2223
+ * Wall-clock duration of the sub-agent execution in milliseconds
2224
+ */
2225
+ durationMs?: number;
2186
2226
  };
2187
2227
  } | {
2188
2228
  /**
@@ -3300,6 +3340,68 @@ export type SessionEvent = {
3300
3340
  path?: string;
3301
3341
  }[];
3302
3342
  };
3343
+ } | {
3344
+ /**
3345
+ * Unique event identifier (UUID v4), generated when the event is emitted
3346
+ */
3347
+ id: string;
3348
+ /**
3349
+ * ISO 8601 timestamp when the event was created
3350
+ */
3351
+ timestamp: string;
3352
+ /**
3353
+ * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event.
3354
+ */
3355
+ parentId: string | null;
3356
+ ephemeral: true;
3357
+ type: "session.custom_agents_updated";
3358
+ data: {
3359
+ /**
3360
+ * Array of loaded custom agent metadata
3361
+ */
3362
+ agents: {
3363
+ /**
3364
+ * Unique identifier for the agent
3365
+ */
3366
+ id: string;
3367
+ /**
3368
+ * Internal name of the agent
3369
+ */
3370
+ name: string;
3371
+ /**
3372
+ * Human-readable display name
3373
+ */
3374
+ displayName: string;
3375
+ /**
3376
+ * Description of what the agent does
3377
+ */
3378
+ description: string;
3379
+ /**
3380
+ * Source location: user, project, inherited, remote, or plugin
3381
+ */
3382
+ source: string;
3383
+ /**
3384
+ * List of tool names available to this agent
3385
+ */
3386
+ tools: string[];
3387
+ /**
3388
+ * Whether the agent can be selected by the user
3389
+ */
3390
+ userInvocable: boolean;
3391
+ /**
3392
+ * Model override for this agent, if set
3393
+ */
3394
+ model?: string;
3395
+ }[];
3396
+ /**
3397
+ * Non-fatal warnings from agent loading
3398
+ */
3399
+ warnings: string[];
3400
+ /**
3401
+ * Fatal errors from agent loading
3402
+ */
3403
+ errors: string[];
3404
+ };
3303
3405
  } | {
3304
3406
  /**
3305
3407
  * 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.1",
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.12-0",
60
60
  "vscode-jsonrpc": "^8.2.1",
61
61
  "zod": "^4.3.6"
62
62
  },