How to Elicit User Input During Agent Execution in the Copilot SDK
The Copilot SDK provides a structured elicitation flow through the Session class that lets agents request information from users mid-execution by registering a handler with registerUserInputHandler and calling UI helpers like ui.input, ui.confirm, or ui.select.
The Copilot SDK supports interactive agent workflows that pause execution to request user input. According to the github/copilot-sdk source code, the SDK exposes a three-part elicitation architecture that negotiates capabilities, registers handlers, and provides high-level UI helpers to bridge the gap between agent logic and human interaction.
Understanding the Elicitation Architecture
The elicitation system in the Copilot SDK operates through three coordinated mechanisms defined across the codebase.
First, capability negotiation occurs when a session initializes. The host reports whether it supports UI-based elicitation via session.capabilities.ui?.elicitation, a boolean flag defined in src/types.ts at lines 755-756. Agents should verify this flag before attempting to request input.
Second, handler registration happens through the Session class method registerUserInputHandler, implemented in src/session.ts at lines 1759-1769. This method accepts a callback that receives a UserInputRequest and must return a UserInputResponse, establishing the communication channel between the runtime and your application.
Third, UI helper methods wrap the raw RPC calls to provide typed interfaces. The ui.confirm, ui.select, and ui.input methods in src/session.ts (lines 78-102) handle the complexity of building JSON Schema requests and parsing responses.
Registering a User Input Handler
Before requesting input, you must register a handler that processes elicitation requests. The registerUserInputHandler method on the Session instance stores your callback and invokes it whenever the runtime broadcasts an elicitation.requested event.
According to src/generated/session-events.ts (lines 7550-7577), these events carry the UserInputRequest payload containing the prompt and schema definition. Your handler must return a UserInputResponse object with the answer property populated.
import { createSession } from "copilot-sdk";
async function main() {
const session = await createSession({ /* session options */ });
// Register handler for all user input requests
session.registerUserInputHandler(async (request) => {
console.log("Agent asks:", request.prompt);
// Return the user's answer
return { answer: "blue" };
});
// Now safe to request input
const color = await session.ui.input("What is your favorite color?");
console.log("Received:", color);
}
Using UI Helper Methods
The SDK abstracts the raw rpc.ui.elicitation call (defined in src/generated/rpc.ts at lines 134-135) through convenient helper methods on the session.ui namespace.
session.ui.input()
Requests free-form text input with optional defaults and descriptions:
const color = await session.ui.input(
"What is your favorite color?",
{ default: "red", description: "Pick a color" }
);
session.ui.confirm()
Displays a binary accept/reject dialog, returning true only when the user accepts:
const confirmed = await session.ui.confirm("Do you want to continue?");
if (!confirmed) return;
session.ui.select()
Presents a dropdown selection and returns the chosen string or null if cancelled:
const choice = await session.ui.select(
"Pick a fruit:",
["Apple", "Banana", "Cherry"]
);
console.log("Picked:", choice);
These helpers automatically check session.capabilities.ui?.elicitation and throw an error if the host does not support interactive elicitation.
Enabling the Built-in ask_user Tool
For agents that invoke tools rather than direct UI methods, the SDK provides the ask_user built-in tool defined in src/toolSet.ts at lines 129-130. Enable it via session.useTools() and reference it in prompts:
import { BuiltInTools } from "copilot-sdk";
// Optional: explicitly enable the tool
await session.useTools([BuiltInTools.Isolated.ask_user]);
// The model will invoke ask_user when it needs clarification
const response = await session.run({
prompt: "Ask me a question using the ask_user tool."
});
// Response routes through your registered user input handler
Handling Capability Negotiation
Always guard elicitation calls with capability checks to prevent runtime errors. The elicitation capability flag in src/types.ts (lines 755-756) indicates host support:
if (!session.capabilities.ui?.elicitation) {
console.warn("Host does not support interactive input");
return;
}
const answer = await session.ui.input("Your question here?");
When the capability is absent, the SDK cannot translate UI helper calls into elicitation.requested events, and the CLI host cannot display dialogs.
Summary
- Capability Check: Verify
session.capabilities.ui?.elicitation(defined insrc/types.tslines 755-756) before requesting input. - Handler Registration: Use
session.registerUserInputHandler()(implemented insrc/session.tslines 1759-1769) to define how your application responds to input requests. - UI Helpers: Leverage
session.ui.input(),session.ui.confirm(), andsession.ui.select()(defined insrc/session.tslines 78-102) for typed, high-level interfaces. - RPC Layer: The helpers internally call
rpc.ui.elicitation(defined insrc/generated/rpc.tslines 134-135) to communicate with the host. - Built-in Tool: Enable
BuiltInTools.Isolated.ask_user(fromsrc/toolSet.tslines 129-130) for agent-driven input requests via tool calling.
Frequently Asked Questions
What happens if the host does not support elicitation?
If the host does not advertise the elicitation capability in session.capabilities.ui, calling any session.ui helper method throws an error. According to the source code in src/session.ts, these methods perform a guard check before invoking the underlying RPC call, ensuring fail-fast behavior when the host cannot display dialogs.
Can I use custom JSON Schema for input validation?
Yes. While the UI helpers provide convenient defaults, you can construct custom UserInputRequest objects with JSON Schema definitions for validation. The UserInputRequest and UserInputResponse types are defined in src/types.ts at lines 1170-1172, allowing you to pass structured schemas through the registerUserInputHandler callback for complex input validation beyond simple strings or selections.
How does the elicitation flow work internally?
When you call a UI helper or the ask_user tool is invoked, the SDK sends an elicitation.requested event through the RPC layer (src/generated/rpc.ts lines 134-135). The host displays the dialog, collects the response, and sends an elicitation.completed event back to the SDK. Your registered handler receives the UserInputRequest, and the SDK replies with the UserInputResponse, completing the synchronous flow from the agent's perspective.
Where are the elicitation events defined?
The typed definitions for elicitation.requested and elicitation.completed events reside in src/generated/session-events.ts at lines 7550-7577. These generated types ensure type safety when handling low-level session events directly, though most developers interact with the higher-level registerUserInputHandler API instead.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →