Skip to content

Commit 4088739

Browse files
[Node] Add Commands and UI Elicitation Support to SDK (#906)
* [Node] Add Commands and UI Elicitation Support to SDK * CCR feedback and CI fixes * Fix prettier
1 parent 7463c54 commit 4088739

File tree

12 files changed

+893
-60
lines changed

12 files changed

+893
-60
lines changed

nodejs/README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,20 @@ Get all events/messages from this session.
279279

280280
Disconnect the session and free resources. Session data on disk is preserved for later resumption.
281281

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+
282296
##### `destroy(): Promise<void>` *(deprecated)*
283297

284298
Deprecated — use `disconnect()` instead.
@@ -294,6 +308,8 @@ Sessions emit various events during processing:
294308
- `assistant.message_delta` - Streaming response chunk
295309
- `tool.execution_start` - Tool execution started
296310
- `tool.execution_complete` - Tool execution completed
311+
- `command.execute` - Command dispatch request (handled internally by the SDK)
312+
- `commands.changed` - Command registration changed
297313
- And more...
298314

299315
See `SessionEvent` type in the source for full details.
@@ -455,6 +471,72 @@ defineTool("safe_lookup", {
455471
})
456472
```
457473

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+
458540
### System Message Customization
459541

460542
Control the system prompt using `systemMessage` in session config:

nodejs/package-lock.json

Lines changed: 28 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nodejs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"author": "GitHub",
5757
"license": "MIT",
5858
"dependencies": {
59-
"@github/copilot": "^1.0.10",
59+
"@github/copilot": "^1.0.11-1",
6060
"vscode-jsonrpc": "^8.2.1",
6161
"zod": "^4.3.6"
6262
},

nodejs/src/client.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,7 @@ export class CopilotClient {
639639
this.onGetTraceContext
640640
);
641641
session.registerTools(config.tools);
642+
session.registerCommands(config.commands);
642643
session.registerPermissionHandler(config.onPermissionRequest);
643644
if (config.onUserInputRequest) {
644645
session.registerUserInputHandler(config.onUserInputRequest);
@@ -674,6 +675,10 @@ export class CopilotClient {
674675
overridesBuiltInTool: tool.overridesBuiltInTool,
675676
skipPermission: tool.skipPermission,
676677
})),
678+
commands: config.commands?.map((cmd) => ({
679+
name: cmd.name,
680+
description: cmd.description,
681+
})),
677682
systemMessage: wireSystemMessage,
678683
availableTools: config.availableTools,
679684
excludedTools: config.excludedTools,
@@ -693,11 +698,13 @@ export class CopilotClient {
693698
infiniteSessions: config.infiniteSessions,
694699
});
695700

696-
const { workspacePath } = response as {
701+
const { workspacePath, capabilities } = response as {
697702
sessionId: string;
698703
workspacePath?: string;
704+
capabilities?: { ui?: { elicitation?: boolean } };
699705
};
700706
session["_workspacePath"] = workspacePath;
707+
session.setCapabilities(capabilities);
701708
} catch (e) {
702709
this.sessions.delete(sessionId);
703710
throw e;
@@ -754,6 +761,7 @@ export class CopilotClient {
754761
this.onGetTraceContext
755762
);
756763
session.registerTools(config.tools);
764+
session.registerCommands(config.commands);
757765
session.registerPermissionHandler(config.onPermissionRequest);
758766
if (config.onUserInputRequest) {
759767
session.registerUserInputHandler(config.onUserInputRequest);
@@ -792,6 +800,10 @@ export class CopilotClient {
792800
overridesBuiltInTool: tool.overridesBuiltInTool,
793801
skipPermission: tool.skipPermission,
794802
})),
803+
commands: config.commands?.map((cmd) => ({
804+
name: cmd.name,
805+
description: cmd.description,
806+
})),
795807
provider: config.provider,
796808
requestPermission: true,
797809
requestUserInput: !!config.onUserInputRequest,
@@ -809,11 +821,13 @@ export class CopilotClient {
809821
disableResume: config.disableResume,
810822
});
811823

812-
const { workspacePath } = response as {
824+
const { workspacePath, capabilities } = response as {
813825
sessionId: string;
814826
workspacePath?: string;
827+
capabilities?: { ui?: { elicitation?: boolean } };
815828
};
816829
session["_workspacePath"] = workspacePath;
830+
session.setCapabilities(capabilities);
817831
} catch (e) {
818832
this.sessions.delete(sessionId);
819833
throw e;

nodejs/src/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,22 @@ export { CopilotClient } from "./client.js";
1212
export { CopilotSession, type AssistantMessageEvent } from "./session.js";
1313
export { defineTool, approveAll, SYSTEM_PROMPT_SECTIONS } from "./types.js";
1414
export type {
15+
CommandContext,
16+
CommandDefinition,
17+
CommandHandler,
1518
ConnectionState,
1619
CopilotClientOptions,
1720
CustomAgentConfig,
21+
ElicitationFieldValue,
22+
ElicitationParams,
23+
ElicitationResult,
24+
ElicitationSchema,
25+
ElicitationSchemaField,
1826
ForegroundSessionInfo,
1927
GetAuthStatusResponse,
2028
GetStatusResponse,
2129
InfiniteSessionConfig,
30+
InputOptions,
2231
MCPLocalServerConfig,
2332
MCPRemoteServerConfig,
2433
MCPServerConfig,
@@ -34,6 +43,7 @@ export type {
3443
SectionOverride,
3544
SectionOverrideAction,
3645
SectionTransformFn,
46+
SessionCapabilities,
3747
SessionConfig,
3848
SessionEvent,
3949
SessionEventHandler,
@@ -45,6 +55,7 @@ export type {
4555
SessionContext,
4656
SessionListFilter,
4757
SessionMetadata,
58+
SessionUiApi,
4859
SystemMessageAppendConfig,
4960
SystemMessageConfig,
5061
SystemMessageCustomizeConfig,

0 commit comments

Comments
 (0)