Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions nodejs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,20 @@ Get all events/messages from this session.

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

##### `capabilities: SessionCapabilities`

Host capabilities reported when the session was created or resumed. Use this to check feature support before calling capability-gated APIs.

```typescript
if (session.capabilities.ui?.elicitation) {
const ok = await session.ui.confirm("Deploy?");
}
```

##### `ui: SessionUiApi`

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.

##### `destroy(): Promise<void>` *(deprecated)*

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

See `SessionEvent` type in the source for full details.
Expand Down Expand Up @@ -455,6 +471,72 @@ defineTool("safe_lookup", {
})
```

### Commands

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.

```ts
const session = await client.createSession({
onPermissionRequest: approveAll,
commands: [
{
name: "deploy",
description: "Deploy the app to production",
handler: async ({ commandName, args }) => {
console.log(`Deploying with args: ${args}`);
// Do work here — any thrown error is reported back to the CLI
},
},
],
});
```

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.

Commands are sent to the CLI on both `createSession` and `resumeSession`, so you can update the command set when resuming.

### UI Elicitation

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.

> **Capability check:** Elicitation is only available when the host advertises support. Always check `session.capabilities.ui?.elicitation` before calling UI methods.

```ts
const session = await client.createSession({ onPermissionRequest: approveAll });

if (session.capabilities.ui?.elicitation) {
// Confirm dialog — returns boolean
const ok = await session.ui.confirm("Deploy to production?");

// Selection dialog — returns selected value or null
const env = await session.ui.select("Pick environment", ["production", "staging", "dev"]);

// Text input — returns string or null
const name = await session.ui.input("Project name:", {
title: "Name",
minLength: 1,
maxLength: 50,
});

// Generic elicitation with full schema control
const result = await session.ui.elicitation({
message: "Configure deployment",
requestedSchema: {
type: "object",
properties: {
region: { type: "string", enum: ["us-east", "eu-west"] },
dryRun: { type: "boolean", default: true },
},
required: ["region"],
},
});
// result.action: "accept" | "decline" | "cancel"
// result.content: { region: "us-east", dryRun: true } (when accepted)
}
```

All UI methods throw if elicitation is not supported by the host.

### System Message Customization

Control the system prompt using `systemMessage` in session config:
Expand Down
56 changes: 28 additions & 28 deletions nodejs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"author": "GitHub",
"license": "MIT",
"dependencies": {
"@github/copilot": "^1.0.10",
"@github/copilot": "^1.0.11-1",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
Expand Down
18 changes: 16 additions & 2 deletions nodejs/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ export class CopilotClient {
this.onGetTraceContext
);
session.registerTools(config.tools);
session.registerCommands(config.commands);
session.registerPermissionHandler(config.onPermissionRequest);
if (config.onUserInputRequest) {
session.registerUserInputHandler(config.onUserInputRequest);
Expand Down Expand Up @@ -674,6 +675,10 @@ export class CopilotClient {
overridesBuiltInTool: tool.overridesBuiltInTool,
skipPermission: tool.skipPermission,
})),
commands: config.commands?.map((cmd) => ({
name: cmd.name,
description: cmd.description,
})),
systemMessage: wireSystemMessage,
availableTools: config.availableTools,
excludedTools: config.excludedTools,
Expand All @@ -693,11 +698,13 @@ export class CopilotClient {
infiniteSessions: config.infiniteSessions,
});

const { workspacePath } = response as {
const { workspacePath, capabilities } = response as {
sessionId: string;
workspacePath?: string;
capabilities?: { ui?: { elicitation?: boolean } };
};
session["_workspacePath"] = workspacePath;
session.setCapabilities(capabilities);
} catch (e) {
this.sessions.delete(sessionId);
throw e;
Expand Down Expand Up @@ -754,6 +761,7 @@ export class CopilotClient {
this.onGetTraceContext
);
session.registerTools(config.tools);
session.registerCommands(config.commands);
session.registerPermissionHandler(config.onPermissionRequest);
if (config.onUserInputRequest) {
session.registerUserInputHandler(config.onUserInputRequest);
Expand Down Expand Up @@ -792,6 +800,10 @@ export class CopilotClient {
overridesBuiltInTool: tool.overridesBuiltInTool,
skipPermission: tool.skipPermission,
})),
commands: config.commands?.map((cmd) => ({
name: cmd.name,
description: cmd.description,
})),
provider: config.provider,
requestPermission: true,
requestUserInput: !!config.onUserInputRequest,
Expand All @@ -809,11 +821,13 @@ export class CopilotClient {
disableResume: config.disableResume,
});

const { workspacePath } = response as {
const { workspacePath, capabilities } = response as {
sessionId: string;
workspacePath?: string;
capabilities?: { ui?: { elicitation?: boolean } };
};
session["_workspacePath"] = workspacePath;
session.setCapabilities(capabilities);
} catch (e) {
this.sessions.delete(sessionId);
throw e;
Expand Down
11 changes: 11 additions & 0 deletions nodejs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,22 @@ export { CopilotClient } from "./client.js";
export { CopilotSession, type AssistantMessageEvent } from "./session.js";
export { defineTool, approveAll, SYSTEM_PROMPT_SECTIONS } from "./types.js";
export type {
CommandContext,
CommandDefinition,
CommandHandler,
ConnectionState,
CopilotClientOptions,
CustomAgentConfig,
ElicitationFieldValue,
ElicitationParams,
ElicitationResult,
ElicitationSchema,
ElicitationSchemaField,
ForegroundSessionInfo,
GetAuthStatusResponse,
GetStatusResponse,
InfiniteSessionConfig,
InputOptions,
MCPLocalServerConfig,
MCPRemoteServerConfig,
MCPServerConfig,
Expand All @@ -34,6 +43,7 @@ export type {
SectionOverride,
SectionOverrideAction,
SectionTransformFn,
SessionCapabilities,
SessionConfig,
SessionEvent,
SessionEventHandler,
Expand All @@ -45,6 +55,7 @@ export type {
SessionContext,
SessionListFilter,
SessionMetadata,
SessionUiApi,
SystemMessageAppendConfig,
SystemMessageConfig,
SystemMessageCustomizeConfig,
Expand Down
Loading
Loading