diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs
index fabe4817e..406a961a2 100644
--- a/dotnet/src/Generated/Rpc.cs
+++ b/dotnet/src/Generated/Rpc.cs
@@ -1044,6 +1044,42 @@ internal class SessionUiElicitationRequest
public SessionUiElicitationRequestRequestedSchema RequestedSchema { get => field ??= new(); set; }
}
+/// RPC data type for SessionUiHandlePendingElicitation operations.
+public class SessionUiHandlePendingElicitationResult
+{
+ /// Whether the response was accepted. False if the request was already resolved by another client.
+ [JsonPropertyName("success")]
+ public bool Success { get; set; }
+}
+
+/// The elicitation response (accept with form values, decline, or cancel).
+public class SessionUiHandlePendingElicitationRequestResult
+{
+ /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed).
+ [JsonPropertyName("action")]
+ public SessionUiElicitationResultAction Action { get; set; }
+
+ /// The form values submitted by the user (present when action is 'accept').
+ [JsonPropertyName("content")]
+ public Dictionary? Content { get; set; }
+}
+
+/// RPC data type for SessionUiHandlePendingElicitation operations.
+internal class SessionUiHandlePendingElicitationRequest
+{
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+
+ /// The unique request ID from the elicitation.requested event.
+ [JsonPropertyName("requestId")]
+ public string RequestId { get; set; } = string.Empty;
+
+ /// The elicitation response (accept with form values, decline, or cancel).
+ [JsonPropertyName("result")]
+ public SessionUiHandlePendingElicitationRequestResult Result { get => field ??= new(); set; }
+}
+
/// RPC data type for SessionPermissionsHandlePendingPermissionRequest operations.
public class SessionPermissionsHandlePendingPermissionRequestResult
{
@@ -1822,6 +1858,13 @@ public async Task ElicitationAsync(string message, S
var request = new SessionUiElicitationRequest { SessionId = _sessionId, Message = message, RequestedSchema = requestedSchema };
return await CopilotClient.InvokeRpcAsync(_rpc, "session.ui.elicitation", [request], cancellationToken);
}
+
+ /// Calls "session.ui.handlePendingElicitation".
+ public async Task HandlePendingElicitationAsync(string requestId, SessionUiHandlePendingElicitationRequestResult result, CancellationToken cancellationToken = default)
+ {
+ var request = new SessionUiHandlePendingElicitationRequest { SessionId = _sessionId, RequestId = requestId, Result = result };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.ui.handlePendingElicitation", [request], cancellationToken);
+ }
}
/// Provides session-scoped Permissions APIs.
@@ -1961,6 +2004,9 @@ public async Task KillAsync(string processId, SessionShe
[JsonSerializable(typeof(SessionUiElicitationRequest))]
[JsonSerializable(typeof(SessionUiElicitationRequestRequestedSchema))]
[JsonSerializable(typeof(SessionUiElicitationResult))]
+[JsonSerializable(typeof(SessionUiHandlePendingElicitationRequest))]
+[JsonSerializable(typeof(SessionUiHandlePendingElicitationRequestResult))]
+[JsonSerializable(typeof(SessionUiHandlePendingElicitationResult))]
[JsonSerializable(typeof(SessionWorkspaceCreateFileRequest))]
[JsonSerializable(typeof(SessionWorkspaceCreateFileResult))]
[JsonSerializable(typeof(SessionWorkspaceListFilesRequest))]
diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs
index 73c0bdaa2..6da3de682 100644
--- a/dotnet/src/Generated/SessionEvents.cs
+++ b/dotnet/src/Generated/SessionEvents.cs
@@ -28,6 +28,7 @@ namespace GitHub.Copilot.SDK;
[JsonDerivedType(typeof(AssistantTurnEndEvent), "assistant.turn_end")]
[JsonDerivedType(typeof(AssistantTurnStartEvent), "assistant.turn_start")]
[JsonDerivedType(typeof(AssistantUsageEvent), "assistant.usage")]
+[JsonDerivedType(typeof(CapabilitiesChangedEvent), "capabilities.changed")]
[JsonDerivedType(typeof(CommandCompletedEvent), "command.completed")]
[JsonDerivedType(typeof(CommandExecuteEvent), "command.execute")]
[JsonDerivedType(typeof(CommandQueuedEvent), "command.queued")]
@@ -45,6 +46,8 @@ namespace GitHub.Copilot.SDK;
[JsonDerivedType(typeof(PendingMessagesModifiedEvent), "pending_messages.modified")]
[JsonDerivedType(typeof(PermissionCompletedEvent), "permission.completed")]
[JsonDerivedType(typeof(PermissionRequestedEvent), "permission.requested")]
+[JsonDerivedType(typeof(SamplingCompletedEvent), "sampling.completed")]
+[JsonDerivedType(typeof(SamplingRequestedEvent), "sampling.requested")]
[JsonDerivedType(typeof(SessionBackgroundTasksChangedEvent), "session.background_tasks_changed")]
[JsonDerivedType(typeof(SessionCompactionCompleteEvent), "session.compaction_complete")]
[JsonDerivedType(typeof(SessionCompactionStartEvent), "session.compaction_start")]
@@ -60,6 +63,7 @@ namespace GitHub.Copilot.SDK;
[JsonDerivedType(typeof(SessionModeChangedEvent), "session.mode_changed")]
[JsonDerivedType(typeof(SessionModelChangeEvent), "session.model_change")]
[JsonDerivedType(typeof(SessionPlanChangedEvent), "session.plan_changed")]
+[JsonDerivedType(typeof(SessionRemoteSteerableChangedEvent), "session.remote_steerable_changed")]
[JsonDerivedType(typeof(SessionResumeEvent), "session.resume")]
[JsonDerivedType(typeof(SessionShutdownEvent), "session.shutdown")]
[JsonDerivedType(typeof(SessionSkillsLoadedEvent), "session.skills_loaded")]
@@ -151,6 +155,19 @@ public partial class SessionResumeEvent : SessionEvent
public required SessionResumeData Data { get; set; }
}
+/// Notifies Mission Control that the session's remote steering capability has changed.
+/// Represents the session.remote_steerable_changed event.
+public partial class SessionRemoteSteerableChangedEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "session.remote_steerable_changed";
+
+ /// The session.remote_steerable_changed event payload.
+ [JsonPropertyName("data")]
+ public required SessionRemoteSteerableChangedData Data { get; set; }
+}
+
/// Error details for timeline display including message and optional diagnostic information.
/// Represents the session.error event.
public partial class SessionErrorEvent : SessionEvent
@@ -813,6 +830,32 @@ public partial class ElicitationCompletedEvent : SessionEvent
public required ElicitationCompletedData Data { get; set; }
}
+/// Sampling request from an MCP server; contains the server name and a requestId for correlation.
+/// Represents the sampling.requested event.
+public partial class SamplingRequestedEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "sampling.requested";
+
+ /// The sampling.requested event payload.
+ [JsonPropertyName("data")]
+ public required SamplingRequestedData Data { get; set; }
+}
+
+/// Sampling request completion notification signaling UI dismissal.
+/// Represents the sampling.completed event.
+public partial class SamplingCompletedEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "sampling.completed";
+
+ /// The sampling.completed event payload.
+ [JsonPropertyName("data")]
+ public required SamplingCompletedData Data { get; set; }
+}
+
/// OAuth authentication request for an MCP server.
/// Represents the mcp.oauth_required event.
public partial class McpOauthRequiredEvent : SessionEvent
@@ -917,6 +960,19 @@ public partial class CommandsChangedEvent : SessionEvent
public required CommandsChangedData Data { get; set; }
}
+/// Session capability change notification.
+/// Represents the capabilities.changed event.
+public partial class CapabilitiesChangedEvent : SessionEvent
+{
+ ///
+ [JsonIgnore]
+ public override string Type => "capabilities.changed";
+
+ /// The capabilities.changed event payload.
+ [JsonPropertyName("data")]
+ public required CapabilitiesChangedData Data { get; set; }
+}
+
/// Plan approval request with plan content and available user actions.
/// Represents the exit_plan_mode.requested event.
public partial class ExitPlanModeRequestedEvent : SessionEvent
@@ -1072,8 +1128,8 @@ public partial class SessionStartData
/// Whether this session supports remote steering via Mission Control.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- [JsonPropertyName("steerable")]
- public bool? Steerable { get; set; }
+ [JsonPropertyName("remoteSteerable")]
+ public bool? RemoteSteerable { get; set; }
}
/// Session resume metadata including current context and event count.
@@ -1106,6 +1162,19 @@ public partial class SessionResumeData
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("alreadyInUse")]
public bool? AlreadyInUse { get; set; }
+
+ /// Whether this session supports remote steering via Mission Control.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("remoteSteerable")]
+ public bool? RemoteSteerable { get; set; }
+}
+
+/// Notifies Mission Control that the session's remote steering capability has changed.
+public partial class SessionRemoteSteerableChangedData
+{
+ /// Whether this session now supports remote steering via Mission Control.
+ [JsonPropertyName("remoteSteerable")]
+ public required bool RemoteSteerable { get; set; }
}
/// Error details for timeline display including message and optional diagnostic information.
@@ -1779,7 +1848,17 @@ public partial class AssistantUsageData
[JsonPropertyName("duration")]
public double? Duration { get; set; }
- /// What initiated this API call (e.g., "sub-agent"); absent for user-initiated calls.
+ /// Time to first token in milliseconds. Only available for streaming requests.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("ttftMs")]
+ public double? TtftMs { get; set; }
+
+ /// Average inter-token latency in milliseconds. Only available for streaming requests.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("interTokenLatencyMs")]
+ public double? InterTokenLatencyMs { get; set; }
+
+ /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("initiator")]
public string? Initiator { get; set; }
@@ -2277,6 +2356,30 @@ public partial class ElicitationCompletedData
public required string RequestId { get; set; }
}
+/// Sampling request from an MCP server; contains the server name and a requestId for correlation.
+public partial class SamplingRequestedData
+{
+ /// Unique identifier for this sampling request; used to respond via session.respondToSampling().
+ [JsonPropertyName("requestId")]
+ public required string RequestId { get; set; }
+
+ /// Name of the MCP server that initiated the sampling request.
+ [JsonPropertyName("serverName")]
+ public required string ServerName { get; set; }
+
+ /// The JSON-RPC request ID from the MCP protocol.
+ [JsonPropertyName("mcpRequestId")]
+ public required object McpRequestId { get; set; }
+}
+
+/// Sampling request completion notification signaling UI dismissal.
+public partial class SamplingCompletedData
+{
+ /// Request ID of the resolved sampling request; clients should dismiss any UI for this request.
+ [JsonPropertyName("requestId")]
+ public required string RequestId { get; set; }
+}
+
/// OAuth authentication request for an MCP server.
public partial class McpOauthRequiredData
{
@@ -2397,6 +2500,15 @@ public partial class CommandsChangedData
public required CommandsChangedDataCommandsItem[] Commands { get; set; }
}
+/// Session capability change notification.
+public partial class CapabilitiesChangedData
+{
+ /// UI capability changes.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("ui")]
+ public CapabilitiesChangedDataUi? Ui { get; set; }
+}
+
/// Plan approval request with plan content and available user actions.
public partial class ExitPlanModeRequestedData
{
@@ -3591,6 +3703,16 @@ public partial class CommandsChangedDataCommandsItem
public string? Description { get; set; }
}
+/// UI capability changes.
+/// Nested data type for CapabilitiesChangedDataUi.
+public partial class CapabilitiesChangedDataUi
+{
+ /// Whether elicitation is now supported.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("elicitation")]
+ public bool? Elicitation { get; set; }
+}
+
/// Nested data type for SessionSkillsLoadedDataSkillsItem.
public partial class SessionSkillsLoadedDataSkillsItem
{
@@ -3955,6 +4077,9 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus
[JsonSerializable(typeof(AssistantUsageDataCopilotUsage))]
[JsonSerializable(typeof(AssistantUsageDataCopilotUsageTokenDetailsItem))]
[JsonSerializable(typeof(AssistantUsageEvent))]
+[JsonSerializable(typeof(CapabilitiesChangedData))]
+[JsonSerializable(typeof(CapabilitiesChangedDataUi))]
+[JsonSerializable(typeof(CapabilitiesChangedEvent))]
[JsonSerializable(typeof(CommandCompletedData))]
[JsonSerializable(typeof(CommandCompletedEvent))]
[JsonSerializable(typeof(CommandExecuteData))]
@@ -4005,6 +4130,10 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus
[JsonSerializable(typeof(PermissionRequestWrite))]
[JsonSerializable(typeof(PermissionRequestedData))]
[JsonSerializable(typeof(PermissionRequestedEvent))]
+[JsonSerializable(typeof(SamplingCompletedData))]
+[JsonSerializable(typeof(SamplingCompletedEvent))]
+[JsonSerializable(typeof(SamplingRequestedData))]
+[JsonSerializable(typeof(SamplingRequestedEvent))]
[JsonSerializable(typeof(SessionBackgroundTasksChangedData))]
[JsonSerializable(typeof(SessionBackgroundTasksChangedEvent))]
[JsonSerializable(typeof(SessionCompactionCompleteData))]
@@ -4044,6 +4173,8 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus
[JsonSerializable(typeof(SessionModelChangeEvent))]
[JsonSerializable(typeof(SessionPlanChangedData))]
[JsonSerializable(typeof(SessionPlanChangedEvent))]
+[JsonSerializable(typeof(SessionRemoteSteerableChangedData))]
+[JsonSerializable(typeof(SessionRemoteSteerableChangedEvent))]
[JsonSerializable(typeof(SessionResumeData))]
[JsonSerializable(typeof(SessionResumeDataContext))]
[JsonSerializable(typeof(SessionResumeEvent))]
diff --git a/go/generated_session_events.go b/go/generated_session_events.go
index 051fa4eca..8eafb13d0 100644
--- a/go/generated_session_events.go
+++ b/go/generated_session_events.go
@@ -30,6 +30,8 @@ type SessionEvent struct {
//
// Session resume metadata including current context and event count
//
+ // Notifies Mission Control that the session's remote steering capability has changed
+ //
// Error details for timeline display including message and optional diagnostic information
//
// Payload indicating the agent is idle; includes any background tasks still in flight
@@ -137,6 +139,11 @@ type SessionEvent struct {
//
// Elicitation request completion notification signaling UI dismissal
//
+ // Sampling request from an MCP server; contains the server name and a requestId for
+ // correlation
+ //
+ // Sampling request completion notification signaling UI dismissal
+ //
// OAuth authentication request for an MCP server
//
// MCP OAuth request completion notification
@@ -153,6 +160,8 @@ type SessionEvent struct {
//
// SDK command registration change notification
//
+ // Session capability change notification
+ //
// Plan approval request with plan content and available user actions
//
// Plan mode exit completion notification signaling UI dismissal
@@ -173,6 +182,8 @@ type SessionEvent struct {
//
// # Session resume metadata including current context and event count
//
+// # Notifies Mission Control that the session's remote steering capability has changed
+//
// # Error details for timeline display including message and optional diagnostic information
//
// Payload indicating the agent is idle; includes any background tasks still in flight
@@ -280,6 +291,11 @@ type SessionEvent struct {
//
// # Elicitation request completion notification signaling UI dismissal
//
+// Sampling request from an MCP server; contains the server name and a requestId for
+// correlation
+//
+// # Sampling request completion notification signaling UI dismissal
+//
// # OAuth authentication request for an MCP server
//
// # MCP OAuth request completion notification
@@ -296,6 +312,8 @@ type SessionEvent struct {
//
// # SDK command registration change notification
//
+// # Session capability change notification
+//
// # Plan approval request with plan content and available user actions
//
// Plan mode exit completion notification signaling UI dismissal
@@ -319,6 +337,10 @@ type Data struct {
//
// Reasoning effort level after the model change, if applicable
ReasoningEffort *string `json:"reasoningEffort,omitempty"`
+ // Whether this session supports remote steering via Mission Control
+ //
+ // Whether this session now supports remote steering via Mission Control
+ RemoteSteerable *bool `json:"remoteSteerable,omitempty"`
// Model selected at session creation time, if any
//
// Model currently selected at resume time
@@ -329,8 +351,6 @@ type Data struct {
SessionID *string `json:"sessionId,omitempty"`
// ISO 8601 timestamp when the session was created
StartTime *time.Time `json:"startTime,omitempty"`
- // Whether this session supports remote steering via Mission Control
- Steerable *bool `json:"steerable,omitempty"`
// Schema version number for the session event format
Version *float64 `json:"version,omitempty"`
// Total number of persisted events in the session at the time of resume
@@ -534,6 +554,12 @@ type Data struct {
// Request ID of the resolved elicitation request; clients should dismiss any UI for this
// request
//
+ // Unique identifier for this sampling request; used to respond via
+ // session.respondToSampling()
+ //
+ // Request ID of the resolved sampling request; clients should dismiss any UI for this
+ // request
+ //
// Unique identifier for this OAuth request; used to respond via
// session.respondToMcpOAuth()
//
@@ -652,10 +678,13 @@ type Data struct {
Cost *float64 `json:"cost,omitempty"`
// Duration of the API call in milliseconds
Duration *float64 `json:"duration,omitempty"`
- // What initiated this API call (e.g., "sub-agent"); absent for user-initiated calls
+ // What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for
+ // user-initiated calls
Initiator *string `json:"initiator,omitempty"`
// Number of input tokens consumed
InputTokens *float64 `json:"inputTokens,omitempty"`
+ // Average inter-token latency in milliseconds. Only available for streaming requests
+ InterTokenLatencyMS *float64 `json:"interTokenLatencyMs,omitempty"`
// Model identifier used for this API call
//
// Model identifier that generated this tool call
@@ -666,6 +695,8 @@ type Data struct {
Model *string `json:"model,omitempty"`
// Per-quota resource usage snapshots, keyed by quota identifier
QuotaSnapshots map[string]QuotaSnapshot `json:"quotaSnapshots,omitempty"`
+ // Time to first token in milliseconds. Only available for streaming requests
+ TtftMS *float64 `json:"ttftMs,omitempty"`
// Reason the current turn was aborted (e.g., "user initiated")
Reason *string `json:"reason,omitempty"`
// Arguments for the tool invocation
@@ -781,6 +812,10 @@ type Data struct {
Mode *Mode `json:"mode,omitempty"`
// JSON Schema describing the form fields to present to the user (form mode only)
RequestedSchema *RequestedSchema `json:"requestedSchema,omitempty"`
+ // The JSON-RPC request ID from the MCP protocol
+ MCPRequestID *MCPRequestID `json:"mcpRequestId"`
+ // Name of the MCP server that initiated the sampling request
+ //
// Display name of the MCP server that requires OAuth
//
// Name of the MCP server whose status changed
@@ -803,6 +838,8 @@ type Data struct {
CommandName *string `json:"commandName,omitempty"`
// Current list of registered SDK commands
Commands []DataCommand `json:"commands,omitempty"`
+ // UI capability changes
+ UI *UI `json:"ui,omitempty"`
// Available actions the user can take (e.g., approve, edit, reject)
Actions []string `json:"actions,omitempty"`
// Full content of the plan file
@@ -1375,6 +1412,12 @@ type ToolRequest struct {
Type *ToolRequestType `json:"type,omitempty"`
}
+// UI capability changes
+type UI struct {
+ // Whether elicitation is now supported
+ Elicitation *bool `json:"elicitation,omitempty"`
+}
+
// The agent mode that was active when this message was sent
type AgentMode string
@@ -1575,6 +1618,7 @@ const (
SessionEventTypeAssistantTurnEnd SessionEventType = "assistant.turn_end"
SessionEventTypeAssistantTurnStart SessionEventType = "assistant.turn_start"
SessionEventTypeAssistantUsage SessionEventType = "assistant.usage"
+ SessionEventTypeCapabilitiesChanged SessionEventType = "capabilities.changed"
SessionEventTypeCommandCompleted SessionEventType = "command.completed"
SessionEventTypeCommandExecute SessionEventType = "command.execute"
SessionEventTypeCommandQueued SessionEventType = "command.queued"
@@ -1592,6 +1636,8 @@ const (
SessionEventTypePendingMessagesModified SessionEventType = "pending_messages.modified"
SessionEventTypePermissionCompleted SessionEventType = "permission.completed"
SessionEventTypePermissionRequested SessionEventType = "permission.requested"
+ SessionEventTypeSamplingCompleted SessionEventType = "sampling.completed"
+ SessionEventTypeSamplingRequested SessionEventType = "sampling.requested"
SessionEventTypeSessionBackgroundTasksChanged SessionEventType = "session.background_tasks_changed"
SessionEventTypeSessionCompactionComplete SessionEventType = "session.compaction_complete"
SessionEventTypeSessionCompactionStart SessionEventType = "session.compaction_start"
@@ -1607,6 +1653,7 @@ const (
SessionEventTypeSessionModeChanged SessionEventType = "session.mode_changed"
SessionEventTypeSessionModelChange SessionEventType = "session.model_change"
SessionEventTypeSessionPlanChanged SessionEventType = "session.plan_changed"
+ SessionEventTypeSessionRemoteSteerableChanged SessionEventType = "session.remote_steerable_changed"
SessionEventTypeSessionResume SessionEventType = "session.resume"
SessionEventTypeSessionShutdown SessionEventType = "session.shutdown"
SessionEventTypeSessionSkillsLoaded SessionEventType = "session.skills_loaded"
@@ -1681,6 +1728,26 @@ func (x *ErrorUnion) MarshalJSON() ([]byte, error) {
return marshalUnion(nil, nil, nil, x.String, false, nil, x.ErrorClass != nil, x.ErrorClass, false, nil, false, nil, false)
}
+// The JSON-RPC request ID from the MCP protocol
+type MCPRequestID struct {
+ Double *float64
+ String *string
+}
+
+func (x *MCPRequestID) UnmarshalJSON(data []byte) error {
+ object, err := unmarshalUnion(data, nil, &x.Double, nil, &x.String, false, nil, false, nil, false, nil, false, nil, false)
+ if err != nil {
+ return err
+ }
+ if object {
+ }
+ return nil
+}
+
+func (x *MCPRequestID) MarshalJSON() ([]byte, error) {
+ return marshalUnion(nil, x.Double, nil, x.String, false, nil, false, nil, false, nil, false, nil, false)
+}
+
type RepositoryUnion struct {
RepositoryClass *RepositoryClass
String *string
diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go
index f6232399c..e9042e964 100644
--- a/go/rpc/generated_rpc.go
+++ b/go/rpc/generated_rpc.go
@@ -528,6 +528,27 @@ type OneOf struct {
Title string `json:"title"`
}
+type SessionUIHandlePendingElicitationResult struct {
+ // Whether the response was accepted. False if the request was already resolved by another
+ // client.
+ Success bool `json:"success"`
+}
+
+type SessionUIHandlePendingElicitationParams struct {
+ // The unique request ID from the elicitation.requested event
+ RequestID string `json:"requestId"`
+ // The elicitation response (accept with form values, decline, or cancel)
+ Result SessionUIHandlePendingElicitationParamsResult `json:"result"`
+}
+
+// The elicitation response (accept with form values, decline, or cancel)
+type SessionUIHandlePendingElicitationParamsResult struct {
+ // The user's response: accept (submitted), decline (rejected), or cancel (dismissed)
+ Action Action `json:"action"`
+ // The form values submitted by the user (present when action is 'accept')
+ Content map[string]*Content `json:"content,omitempty"`
+}
+
type SessionPermissionsHandlePendingPermissionRequestResult struct {
// Whether the permission request was handled successfully
Success bool `json:"success"`
@@ -1321,6 +1342,23 @@ func (a *UiApi) Elicitation(ctx context.Context, params *SessionUIElicitationPar
return &result, nil
}
+func (a *UiApi) HandlePendingElicitation(ctx context.Context, params *SessionUIHandlePendingElicitationParams) (*SessionUIHandlePendingElicitationResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ if params != nil {
+ req["requestId"] = params.RequestID
+ req["result"] = params.Result
+ }
+ raw, err := a.client.Request("session.ui.handlePendingElicitation", req)
+ if err != nil {
+ return nil, err
+ }
+ var result SessionUIHandlePendingElicitationResult
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
type PermissionsApi sessionApi
func (a *PermissionsApi) HandlePendingPermissionRequest(ctx context.Context, params *SessionPermissionsHandlePendingPermissionRequestParams) (*SessionPermissionsHandlePendingPermissionRequestResult, error) {
diff --git a/nodejs/README.md b/nodejs/README.md
index ce2754212..eee4c2b65 100644
--- a/nodejs/README.md
+++ b/nodejs/README.md
@@ -120,6 +120,7 @@ Create a new conversation session.
- `provider?: ProviderConfig` - Custom API provider configuration (BYOK - Bring Your Own Key). See [Custom Providers](#custom-providers) section.
- `onPermissionRequest: PermissionHandler` - **Required.** Handler called before each tool execution to approve or deny it. Use `approveAll` to allow everything, or provide a custom function for fine-grained control. See [Permission Handling](#permission-handling) section.
- `onUserInputRequest?: UserInputHandler` - Handler for user input requests from the agent. Enables the `ask_user` tool. See [User Input Requests](#user-input-requests) section.
+- `onElicitationRequest?: ElicitationHandler` - Handler for elicitation requests dispatched by the server. Enables this client to present form-based UI dialogs on behalf of the agent or other session participants. See [Elicitation Requests](#elicitation-requests) section.
- `hooks?: SessionHooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
##### `resumeSession(sessionId: string, config?: ResumeSessionConfig): Promise`
@@ -293,6 +294,8 @@ if (session.capabilities.ui?.elicitation) {
}
```
+Capabilities may update during the session. For example, when another client joins or disconnects with an elicitation handler. The SDK automatically applies `capabilities.changed` events, so this property always reflects the current state.
+
##### `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.
@@ -505,9 +508,9 @@ Commands are sent to the CLI on both `createSession` and `resumeSession`, so you
### 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.
+When the session has elicitation support — either from the CLI's TUI or from another client that registered an `onElicitationRequest` handler (see [Elicitation Requests](#elicitation-requests)) — 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.
+> **Capability check:** Elicitation is only available when at least one connected participant advertises support. Always check `session.capabilities.ui?.elicitation` before calling UI methods — this property updates automatically as participants join and leave.
```ts
const session = await client.createSession({ onPermissionRequest: approveAll });
@@ -899,6 +902,42 @@ const session = await client.createSession({
});
```
+## Elicitation Requests
+
+Register an `onElicitationRequest` handler to let your client act as an elicitation provider — presenting form-based UI dialogs on behalf of the agent. When provided, the server notifies your client whenever a tool or MCP server needs structured user input.
+
+```typescript
+const session = await client.createSession({
+ model: "gpt-5",
+ onPermissionRequest: approveAll,
+ onElicitationRequest: async (request, invocation) => {
+ // request.message - Description of what information is needed
+ // request.requestedSchema - JSON Schema describing the form fields
+ // request.mode - "form" (structured input) or "url" (browser redirect)
+ // request.elicitationSource - Origin of the request (e.g. MCP server name)
+
+ console.log(`Elicitation from ${request.elicitationSource}: ${request.message}`);
+
+ // Present UI to the user and collect their response...
+ return {
+ action: "accept", // "accept", "decline", or "cancel"
+ content: { region: "us-east", dryRun: true },
+ };
+ },
+});
+
+// The session now reports elicitation capability
+console.log(session.capabilities.ui?.elicitation); // true
+```
+
+When `onElicitationRequest` is provided, the SDK sends `requestElicitation: true` during session create/resume, which enables `session.capabilities.ui.elicitation` on the session.
+
+In multi-client scenarios:
+
+- If no connected client was previously providing an elicitation capability, but a new client joins that can, all clients will receive a `capabilities.changed` event to notify them that elicitation is now possible. The SDK automatically updates `session.capabilities` when these events arrive.
+- Similarly, if the last elicitation provider disconnects, all clients receive a `capabilities.changed` event indicating elicitation is no longer available.
+- The server fans out elicitation requests to **all** connected clients that registered a handler — the first response wins.
+
## Session Hooks
Hook into session lifecycle events by providing handlers in the `hooks` configuration:
diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json
index 4ddf50a2e..a3a94ac5e 100644
--- a/nodejs/package-lock.json
+++ b/nodejs/package-lock.json
@@ -9,7 +9,7 @@
"version": "0.1.8",
"license": "MIT",
"dependencies": {
- "@github/copilot": "^1.0.12-0",
+ "@github/copilot": "^1.0.14-0",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
@@ -662,26 +662,26 @@
}
},
"node_modules/@github/copilot": {
- "version": "1.0.12-0",
- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.12-0.tgz",
- "integrity": "sha512-tF8GQ5TZTP6ZJsD6J31SLdZAmawg9YnEe9jaf6+lwlOH7mA6XU/m9BLStdhdHd2MySoAu0Sb8IkVyEg/YIcWpg==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.14-0.tgz",
+ "integrity": "sha512-9eA5sFbvx69OtQnVoeik/8boFqHgGAhylLeUjEACc3kB70aaH1E/cHgxNzSMyYgZDjpXov0/IBXjtx2otpfHBw==",
"license": "SEE LICENSE IN LICENSE.md",
"bin": {
"copilot": "npm-loader.js"
},
"optionalDependencies": {
- "@github/copilot-darwin-arm64": "1.0.12-0",
- "@github/copilot-darwin-x64": "1.0.12-0",
- "@github/copilot-linux-arm64": "1.0.12-0",
- "@github/copilot-linux-x64": "1.0.12-0",
- "@github/copilot-win32-arm64": "1.0.12-0",
- "@github/copilot-win32-x64": "1.0.12-0"
+ "@github/copilot-darwin-arm64": "1.0.14-0",
+ "@github/copilot-darwin-x64": "1.0.14-0",
+ "@github/copilot-linux-arm64": "1.0.14-0",
+ "@github/copilot-linux-x64": "1.0.14-0",
+ "@github/copilot-win32-arm64": "1.0.14-0",
+ "@github/copilot-win32-x64": "1.0.14-0"
}
},
"node_modules/@github/copilot-darwin-arm64": {
- "version": "1.0.12-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.12-0.tgz",
- "integrity": "sha512-GJNgo21Kh9fNJBOTF/vSc5YRXzwfGNsNufVFLzCnjppvs9ifN1s9VyPYdz+UOcDOrwh7FGPpRRQgWvm3EhTXAQ==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.14-0.tgz",
+ "integrity": "sha512-w11Eqmfnu0ihrvgLysTd5Tkq8LuQa9eW63CNTQ/k5copnG1AMCdvd3K/78MxE2DdFJPq2L95KGS5cs9jH1dlIw==",
"cpu": [
"arm64"
],
@@ -695,9 +695,9 @@
}
},
"node_modules/@github/copilot-darwin-x64": {
- "version": "1.0.12-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.12-0.tgz",
- "integrity": "sha512-pc8f6mNvwDzc4LavH0Baz96WKx75Ti5/3EY0PF8HXOY/kz6x50cywIlRNqHQxK8NsTbTragbrQS7Eh7r6AJf/g==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.14-0.tgz",
+ "integrity": "sha512-4X/dMSPxCE/rvL6N1tgnwFxBg2uXnPrN63GGgS/FqK/fNi3TtcuojDVv8K1yjmEYpF8PXdkQttDlp6bKc+Nonw==",
"cpu": [
"x64"
],
@@ -711,9 +711,9 @@
}
},
"node_modules/@github/copilot-linux-arm64": {
- "version": "1.0.12-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.12-0.tgz",
- "integrity": "sha512-ZlIGo6I2qpkqPXJNgR1+wYF/yMFrENjCz5kh4TIohwkuwPxMfZc4rv+CgMoyRc7OWWjKBUi7Y7IInKWkSkxzVg==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.14-0.tgz",
+ "integrity": "sha512-A4thcLUoErEvfBO3Hsl/hJASibn44qwZm1ZSeVBPCa1FkpowBwo8fT1eV9EwN/ftKsyks3QkndNFvHkVzjUfxA==",
"cpu": [
"arm64"
],
@@ -727,9 +727,9 @@
}
},
"node_modules/@github/copilot-linux-x64": {
- "version": "1.0.12-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.12-0.tgz",
- "integrity": "sha512-4PTBR+cIFhggi6/UsyhgjND+e6tagtBB6w2iJG/Y+ZLbpryaLD8GiGg8xmrzNvMGD81qHdespXCbwiRKplBM/Q==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.14-0.tgz",
+ "integrity": "sha512-Kwn+Qn8/BqWRKa2DewZipH7rPIO8nDRWzpVy/ZLcRWBAvnIU+6BLWfhnYEU44DsqkD2VeWhKVfQlNmDX23xKKg==",
"cpu": [
"x64"
],
@@ -743,9 +743,9 @@
}
},
"node_modules/@github/copilot-win32-arm64": {
- "version": "1.0.12-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.12-0.tgz",
- "integrity": "sha512-Glz0QVGq7sEYReLki4KAVywHnKpxTG+xtJOC3q6aYmfqmrlkGAgo9y/tTbYVNLa2hd8P2gCWcNGIAYlkZQsgfQ==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.14-0.tgz",
+ "integrity": "sha512-8P5kxcb8YVWSS+Ihs+ykyy8jov1WwQ8GKV4d7mJN268Jpd8y5VI8Peb7uE2VO0lRLgq5c2VcXuZDsLG/1Wgnlw==",
"cpu": [
"arm64"
],
@@ -759,9 +759,9 @@
}
},
"node_modules/@github/copilot-win32-x64": {
- "version": "1.0.12-0",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.12-0.tgz",
- "integrity": "sha512-SzPRnIkzg5oMlDix/ggEic4IkkDquGAydleQ9wmPSp9LLp97TD+Fw8fV98HPitOiYRgvTHvDtgWtESgh6uKG1A==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.14-0.tgz",
+ "integrity": "sha512-JWxp08j5o/PUkRZtZVagNYJLjH+KCURCyZRb7BfnC0A3vLeqcJQ70JC5qlYEAlcRnb4uCUJnmnpbWLLOJ+ObrA==",
"cpu": [
"x64"
],
diff --git a/nodejs/package.json b/nodejs/package.json
index 52ba0b153..1787721a8 100644
--- a/nodejs/package.json
+++ b/nodejs/package.json
@@ -56,7 +56,7 @@
"author": "GitHub",
"license": "MIT",
"dependencies": {
- "@github/copilot": "^1.0.12-0",
+ "@github/copilot": "^1.0.14-0",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts
index 5a528488f..50715c0eb 100644
--- a/nodejs/src/client.ts
+++ b/nodejs/src/client.ts
@@ -647,6 +647,9 @@ export class CopilotClient {
if (config.onUserInputRequest) {
session.registerUserInputHandler(config.onUserInputRequest);
}
+ if (config.onElicitationRequest) {
+ session.registerElicitationHandler(config.onElicitationRequest);
+ }
if (config.hooks) {
session.registerHooks(config.hooks);
}
@@ -688,6 +691,7 @@ export class CopilotClient {
provider: config.provider,
requestPermission: true,
requestUserInput: !!config.onUserInputRequest,
+ requestElicitation: !!config.onElicitationRequest,
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
workingDirectory: config.workingDirectory,
streaming: config.streaming,
@@ -769,6 +773,9 @@ export class CopilotClient {
if (config.onUserInputRequest) {
session.registerUserInputHandler(config.onUserInputRequest);
}
+ if (config.onElicitationRequest) {
+ session.registerElicitationHandler(config.onElicitationRequest);
+ }
if (config.hooks) {
session.registerHooks(config.hooks);
}
@@ -810,6 +817,7 @@ export class CopilotClient {
provider: config.provider,
requestPermission: true,
requestUserInput: !!config.onUserInputRequest,
+ requestElicitation: !!config.onElicitationRequest,
hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
workingDirectory: config.workingDirectory,
configDir: config.configDir,
diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts
index dadb9e79d..1db497ae6 100644
--- a/nodejs/src/generated/rpc.ts
+++ b/nodejs/src/generated/rpc.ts
@@ -937,6 +937,39 @@ export interface SessionUiElicitationParams {
};
}
+export interface SessionUiHandlePendingElicitationResult {
+ /**
+ * Whether the response was accepted. False if the request was already resolved by another client.
+ */
+ success: boolean;
+}
+
+export interface SessionUiHandlePendingElicitationParams {
+ /**
+ * Target session identifier
+ */
+ sessionId: string;
+ /**
+ * The unique request ID from the elicitation.requested event
+ */
+ requestId: string;
+ /**
+ * The elicitation response (accept with form values, decline, or cancel)
+ */
+ result: {
+ /**
+ * The user's response: accept (submitted), decline (rejected), or cancel (dismissed)
+ */
+ action: "accept" | "decline" | "cancel";
+ /**
+ * The form values submitted by the user (present when action is 'accept')
+ */
+ content?: {
+ [k: string]: string | number | boolean | string[];
+ };
+ };
+}
+
export interface SessionPermissionsHandlePendingPermissionRequestResult {
/**
* Whether the permission request was handled successfully
@@ -1173,6 +1206,8 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin
ui: {
elicitation: async (params: Omit): Promise =>
connection.sendRequest("session.ui.elicitation", { sessionId, ...params }),
+ handlePendingElicitation: async (params: Omit): Promise =>
+ connection.sendRequest("session.ui.handlePendingElicitation", { sessionId, ...params }),
},
permissions: {
handlePendingPermissionRequest: async (params: Omit): Promise =>
diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts
index 8a6bec680..5d8e12830 100644
--- a/nodejs/src/generated/session-events.ts
+++ b/nodejs/src/generated/session-events.ts
@@ -94,7 +94,7 @@ export type SessionEvent =
/**
* Whether this session supports remote steering via Mission Control
*/
- steerable?: boolean;
+ remoteSteerable?: boolean;
};
}
| {
@@ -172,6 +172,38 @@ export type SessionEvent =
* Whether the session was already in use by another client at resume time
*/
alreadyInUse?: boolean;
+ /**
+ * Whether this session supports remote steering via Mission Control
+ */
+ remoteSteerable?: boolean;
+ };
+ }
+ | {
+ /**
+ * Unique event identifier (UUID v4), generated when the event is emitted
+ */
+ id: string;
+ /**
+ * ISO 8601 timestamp when the event was created
+ */
+ timestamp: string;
+ /**
+ * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event.
+ */
+ parentId: string | null;
+ /**
+ * When true, the event is transient and not persisted to the session event log on disk
+ */
+ ephemeral?: boolean;
+ type: "session.remote_steerable_changed";
+ /**
+ * Notifies Mission Control that the session's remote steering capability has changed
+ */
+ data: {
+ /**
+ * Whether this session now supports remote steering via Mission Control
+ */
+ remoteSteerable: boolean;
};
}
| {
@@ -1588,7 +1620,15 @@ export type SessionEvent =
*/
duration?: number;
/**
- * What initiated this API call (e.g., "sub-agent"); absent for user-initiated calls
+ * Time to first token in milliseconds. Only available for streaming requests
+ */
+ ttftMs?: number;
+ /**
+ * Average inter-token latency in milliseconds. Only available for streaming requests
+ */
+ interTokenLatencyMs?: number;
+ /**
+ * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls
*/
initiator?: string;
/**
@@ -3025,6 +3065,65 @@ export type SessionEvent =
requestId: string;
};
}
+ | {
+ /**
+ * Unique event identifier (UUID v4), generated when the event is emitted
+ */
+ id: string;
+ /**
+ * ISO 8601 timestamp when the event was created
+ */
+ timestamp: string;
+ /**
+ * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event.
+ */
+ parentId: string | null;
+ ephemeral: true;
+ type: "sampling.requested";
+ /**
+ * Sampling request from an MCP server; contains the server name and a requestId for correlation
+ */
+ data: {
+ /**
+ * Unique identifier for this sampling request; used to respond via session.respondToSampling()
+ */
+ requestId: string;
+ /**
+ * Name of the MCP server that initiated the sampling request
+ */
+ serverName: string;
+ /**
+ * The JSON-RPC request ID from the MCP protocol
+ */
+ mcpRequestId: string | number;
+ [k: string]: unknown;
+ };
+ }
+ | {
+ /**
+ * Unique event identifier (UUID v4), generated when the event is emitted
+ */
+ id: string;
+ /**
+ * ISO 8601 timestamp when the event was created
+ */
+ timestamp: string;
+ /**
+ * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event.
+ */
+ parentId: string | null;
+ ephemeral: true;
+ type: "sampling.completed";
+ /**
+ * Sampling request completion notification signaling UI dismissal
+ */
+ data: {
+ /**
+ * Request ID of the resolved sampling request; clients should dismiss any UI for this request
+ */
+ requestId: string;
+ };
+ }
| {
/**
* Unique event identifier (UUID v4), generated when the event is emitted
@@ -3291,6 +3390,36 @@ export type SessionEvent =
}[];
};
}
+ | {
+ /**
+ * Unique event identifier (UUID v4), generated when the event is emitted
+ */
+ id: string;
+ /**
+ * ISO 8601 timestamp when the event was created
+ */
+ timestamp: string;
+ /**
+ * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event.
+ */
+ parentId: string | null;
+ ephemeral: true;
+ type: "capabilities.changed";
+ /**
+ * Session capability change notification
+ */
+ data: {
+ /**
+ * UI capability changes
+ */
+ ui?: {
+ /**
+ * Whether elicitation is now supported
+ */
+ elicitation?: boolean;
+ };
+ };
+ }
| {
/**
* Unique event identifier (UUID v4), generated when the event is emitted
diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts
index c42935a26..4fc1b75fb 100644
--- a/nodejs/src/index.ts
+++ b/nodejs/src/index.ts
@@ -19,7 +19,9 @@ export type {
CopilotClientOptions,
CustomAgentConfig,
ElicitationFieldValue,
+ ElicitationHandler,
ElicitationParams,
+ ElicitationRequest,
ElicitationResult,
ElicitationSchema,
ElicitationSchemaField,
diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts
index 7a0220f6f..cb2cf826b 100644
--- a/nodejs/src/session.ts
+++ b/nodejs/src/session.ts
@@ -13,8 +13,10 @@ import { createSessionRpc } from "./generated/rpc.js";
import { getTraceContext } from "./telemetry.js";
import type {
CommandHandler,
+ ElicitationHandler,
ElicitationParams,
ElicitationResult,
+ ElicitationRequest,
InputOptions,
MessageOptions,
PermissionHandler,
@@ -77,6 +79,7 @@ export class CopilotSession {
private commandHandlers: Map = new Map();
private permissionHandler?: PermissionHandler;
private userInputHandler?: UserInputHandler;
+ private elicitationHandler?: ElicitationHandler;
private hooks?: SessionHooks;
private transformCallbacks?: Map;
private _rpc: ReturnType | null = null;
@@ -414,6 +417,23 @@ export class CopilotSession {
args: string;
};
void this._executeCommandAndRespond(requestId, commandName, command, args);
+ } else if (event.type === "elicitation.requested") {
+ if (this.elicitationHandler) {
+ const { message, requestedSchema, mode, elicitationSource, url, requestId } =
+ event.data;
+ void this._handleElicitationRequest(
+ {
+ message,
+ requestedSchema: requestedSchema as ElicitationRequest["requestedSchema"],
+ mode,
+ elicitationSource,
+ url,
+ },
+ requestId
+ );
+ }
+ } else if (event.type === "capabilities.changed") {
+ this._capabilities = { ...this._capabilities, ...event.data };
}
}
@@ -581,6 +601,44 @@ export class CopilotSession {
}
}
+ /**
+ * Registers the elicitation handler for this session.
+ *
+ * @param handler - The handler to invoke when the server dispatches an elicitation request
+ * @internal This method is typically called internally when creating/resuming a session.
+ */
+ registerElicitationHandler(handler?: ElicitationHandler): void {
+ this.elicitationHandler = handler;
+ }
+
+ /**
+ * Handles an elicitation.requested broadcast event.
+ * Invokes the registered handler and responds via handlePendingElicitation RPC.
+ * @internal
+ */
+ async _handleElicitationRequest(request: ElicitationRequest, requestId: string): Promise {
+ if (!this.elicitationHandler) {
+ return;
+ }
+ try {
+ const result = await this.elicitationHandler(request, { sessionId: this.sessionId });
+ await this.rpc.ui.handlePendingElicitation({ requestId, result });
+ } catch {
+ // Handler failed — attempt to cancel so the request doesn't hang
+ try {
+ await this.rpc.ui.handlePendingElicitation({
+ requestId,
+ result: { action: "cancel" },
+ });
+ } catch (rpcError) {
+ if (!(rpcError instanceof ConnectionError || rpcError instanceof ResponseError)) {
+ throw rpcError;
+ }
+ // Connection lost or RPC error — nothing we can do
+ }
+ }
+ }
+
/**
* Sets the host capabilities for this session.
*
diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts
index 96694137d..b4b9e563c 100644
--- a/nodejs/src/types.ts
+++ b/nodejs/src/types.ts
@@ -409,6 +409,32 @@ export interface ElicitationParams {
requestedSchema: ElicitationSchema;
}
+/**
+ * Request payload passed to an elicitation handler callback.
+ * Extends ElicitationParams with optional metadata fields.
+ */
+export interface ElicitationRequest {
+ /** Message describing what information is needed from the user. */
+ message: string;
+ /** JSON Schema describing the form fields to present. */
+ requestedSchema?: ElicitationSchema;
+ /** Elicitation mode: "form" for structured input, "url" for browser redirect. */
+ mode?: "form" | "url";
+ /** The source that initiated the request (e.g. MCP server name). */
+ elicitationSource?: string;
+ /** URL to open in the user's browser (url mode only). */
+ url?: string;
+}
+
+/**
+ * Handler invoked when the server dispatches an elicitation request to this client.
+ * Return an {@link ElicitationResult} with the user's response.
+ */
+export type ElicitationHandler = (
+ request: ElicitationRequest,
+ invocation: { sessionId: string }
+) => Promise | ElicitationResult;
+
/**
* Options for the `input()` convenience method.
*/
@@ -1082,6 +1108,13 @@ export interface SessionConfig {
*/
onUserInputRequest?: UserInputHandler;
+ /**
+ * Handler for elicitation requests from the agent.
+ * When provided, the server calls back to this client for form-based UI dialogs.
+ * Also enables the `elicitation` capability on the session.
+ */
+ onElicitationRequest?: ElicitationHandler;
+
/**
* Hook handlers for intercepting session lifecycle events.
* When provided, enables hooks callback allowing custom logic at various points.
@@ -1167,6 +1200,7 @@ export type ResumeSessionConfig = Pick<
| "reasoningEffort"
| "onPermissionRequest"
| "onUserInputRequest"
+ | "onElicitationRequest"
| "hooks"
| "workingDirectory"
| "configDir"
diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts
index 0612cc39e..0b98ebcb8 100644
--- a/nodejs/test/client.test.ts
+++ b/nodejs/test/client.test.ts
@@ -897,5 +897,84 @@ describe("CopilotClient", () => {
})
).rejects.toThrow(/not supported/);
});
+
+ it("sends requestElicitation flag when onElicitationRequest is provided", async () => {
+ const client = new CopilotClient();
+ await client.start();
+ onTestFinished(() => client.forceStop());
+
+ const rpcSpy = vi.spyOn((client as any).connection!, "sendRequest");
+
+ const session = await client.createSession({
+ onPermissionRequest: approveAll,
+ onElicitationRequest: async () => ({
+ action: "accept" as const,
+ content: {},
+ }),
+ });
+ expect(session).toBeDefined();
+
+ const createCall = rpcSpy.mock.calls.find((c) => c[0] === "session.create");
+ expect(createCall).toBeDefined();
+ expect(createCall![1]).toEqual(
+ expect.objectContaining({
+ requestElicitation: true,
+ })
+ );
+ rpcSpy.mockRestore();
+ });
+
+ it("does not send requestElicitation when no handler provided", async () => {
+ const client = new CopilotClient();
+ await client.start();
+ onTestFinished(() => client.forceStop());
+
+ const rpcSpy = vi.spyOn((client as any).connection!, "sendRequest");
+
+ const session = await client.createSession({
+ onPermissionRequest: approveAll,
+ });
+ expect(session).toBeDefined();
+
+ const createCall = rpcSpy.mock.calls.find((c) => c[0] === "session.create");
+ expect(createCall).toBeDefined();
+ expect(createCall![1]).toEqual(
+ expect.objectContaining({
+ requestElicitation: false,
+ })
+ );
+ rpcSpy.mockRestore();
+ });
+
+ it("sends cancel when elicitation handler throws", async () => {
+ const client = new CopilotClient();
+ await client.start();
+ onTestFinished(() => client.forceStop());
+
+ const session = await client.createSession({
+ onPermissionRequest: approveAll,
+ onElicitationRequest: async () => {
+ throw new Error("handler exploded");
+ },
+ });
+
+ const rpcSpy = vi.spyOn((client as any).connection!, "sendRequest");
+
+ await session._handleElicitationRequest({ message: "Pick a color" }, "req-123");
+
+ const cancelCall = rpcSpy.mock.calls.find(
+ (c) =>
+ c[0] === "session.ui.handlePendingElicitation" &&
+ (c[1] as any)?.result?.action === "cancel"
+ );
+ expect(cancelCall).toBeDefined();
+ expect(cancelCall![1]).toEqual(
+ expect.objectContaining({
+ requestId: "req-123",
+ result: { action: "cancel" },
+ })
+ );
+ rpcSpy.mockRestore();
+ });
});
});
diff --git a/nodejs/test/e2e/ui_elicitation.test.ts b/nodejs/test/e2e/ui_elicitation.test.ts
index 212f481fb..ced735d88 100644
--- a/nodejs/test/e2e/ui_elicitation.test.ts
+++ b/nodejs/test/e2e/ui_elicitation.test.ts
@@ -2,8 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/
-import { describe, expect, it } from "vitest";
-import { approveAll } from "../../src/index.js";
+import { afterAll, describe, expect, it } from "vitest";
+import { CopilotClient, approveAll } from "../../src/index.js";
+import type { SessionEvent } from "../../src/index.js";
import { createSdkTestContext } from "./harness/sdkTestContext.js";
describe("UI Elicitation", async () => {
@@ -19,3 +20,156 @@ describe("UI Elicitation", async () => {
await expect(session.ui.confirm("test")).rejects.toThrow(/not supported/);
});
});
+
+describe("UI Elicitation Callback", async () => {
+ const ctx = await createSdkTestContext();
+ const client = ctx.copilotClient;
+
+ it(
+ "session created with onElicitationRequest reports elicitation capability",
+ { timeout: 20_000 },
+ async () => {
+ const session = await client.createSession({
+ onPermissionRequest: approveAll,
+ onElicitationRequest: async () => ({ action: "accept", content: {} }),
+ });
+
+ expect(session.capabilities.ui?.elicitation).toBe(true);
+ }
+ );
+
+ it(
+ "session created without onElicitationRequest reports no elicitation capability",
+ { timeout: 20_000 },
+ async () => {
+ const session = await client.createSession({
+ onPermissionRequest: approveAll,
+ });
+
+ expect(session.capabilities.ui?.elicitation).toBe(false);
+ }
+ );
+});
+
+describe("UI Elicitation Multi-Client Capabilities", async () => {
+ // Use TCP mode so a second client can connect to the same CLI process
+ const ctx = await createSdkTestContext({ useStdio: false });
+ const client1 = ctx.copilotClient;
+
+ // Trigger connection so we can read the port
+ const initSession = await client1.createSession({ onPermissionRequest: approveAll });
+ await initSession.disconnect();
+
+ const actualPort = (client1 as unknown as { actualPort: number }).actualPort;
+ const client2 = new CopilotClient({ cliUrl: `localhost:${actualPort}` });
+
+ afterAll(async () => {
+ await client2.stop();
+ });
+
+ it(
+ "capabilities.changed fires when second client joins with elicitation handler",
+ { timeout: 20_000 },
+ async () => {
+ // Client1 creates session without elicitation
+ const session1 = await client1.createSession({
+ onPermissionRequest: approveAll,
+ });
+ expect(session1.capabilities.ui?.elicitation).toBe(false);
+
+ // Listen for capabilities.changed event
+ let unsubscribe: (() => void) | undefined;
+ const capChangedPromise = new Promise((resolve) => {
+ unsubscribe = session1.on((event) => {
+ if ((event as { type: string }).type === "capabilities.changed") {
+ resolve(event);
+ }
+ });
+ });
+
+ // Client2 joins WITH elicitation handler — triggers capabilities.changed
+ const session2 = await client2.resumeSession(session1.sessionId, {
+ onPermissionRequest: approveAll,
+ onElicitationRequest: async () => ({ action: "accept", content: {} }),
+ disableResume: true,
+ });
+
+ const capEvent = await capChangedPromise;
+ unsubscribe?.();
+ const data = (capEvent as { data: { ui?: { elicitation?: boolean } } }).data;
+ expect(data.ui?.elicitation).toBe(true);
+
+ // Client1's capabilities should have been auto-updated
+ expect(session1.capabilities.ui?.elicitation).toBe(true);
+
+ await session2.disconnect();
+ }
+ );
+
+ it(
+ "capabilities.changed fires when elicitation provider disconnects",
+ { timeout: 20_000 },
+ async () => {
+ // Client1 creates session without elicitation
+ const session1 = await client1.createSession({
+ onPermissionRequest: approveAll,
+ });
+ expect(session1.capabilities.ui?.elicitation).toBe(false);
+
+ // Wait for elicitation to become available
+ let unsubEnabled: (() => void) | undefined;
+ const capEnabledPromise = new Promise((resolve) => {
+ unsubEnabled = session1.on((event) => {
+ const data = event as {
+ type: string;
+ data: { ui?: { elicitation?: boolean } };
+ };
+ if (
+ data.type === "capabilities.changed" &&
+ data.data.ui?.elicitation === true
+ ) {
+ resolve();
+ }
+ });
+ });
+
+ // Use a dedicated client so we can stop it without affecting shared client2
+ const client3 = new CopilotClient({ cliUrl: `localhost:${actualPort}` });
+
+ // Client3 joins WITH elicitation handler
+ await client3.resumeSession(session1.sessionId, {
+ onPermissionRequest: approveAll,
+ onElicitationRequest: async () => ({ action: "accept", content: {} }),
+ disableResume: true,
+ });
+
+ await capEnabledPromise;
+ unsubEnabled?.();
+ expect(session1.capabilities.ui?.elicitation).toBe(true);
+
+ // Now listen for the capability being removed
+ let unsubDisabled: (() => void) | undefined;
+ const capDisabledPromise = new Promise((resolve) => {
+ unsubDisabled = session1.on((event) => {
+ const data = event as {
+ type: string;
+ data: { ui?: { elicitation?: boolean } };
+ };
+ if (
+ data.type === "capabilities.changed" &&
+ data.data.ui?.elicitation === false
+ ) {
+ resolve();
+ }
+ });
+ });
+
+ // Force-stop client3 — destroys the socket, triggering server-side cleanup
+ await client3.forceStop();
+
+ await capDisabledPromise;
+ unsubDisabled?.();
+ expect(session1.capabilities.ui?.elicitation).toBe(false);
+ }
+ );
+});
diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py
index 14ae307d7..f7ea6dbad 100644
--- a/python/copilot/generated/rpc.py
+++ b/python/copilot/generated/rpc.py
@@ -1821,6 +1821,72 @@ def to_dict(self) -> dict:
return result
+@dataclass
+class SessionUIHandlePendingElicitationResult:
+ success: bool
+ """Whether the response was accepted. False if the request was already resolved by another
+ client.
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SessionUIHandlePendingElicitationResult':
+ assert isinstance(obj, dict)
+ success = from_bool(obj.get("success"))
+ return SessionUIHandlePendingElicitationResult(success)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["success"] = from_bool(self.success)
+ return result
+
+
+@dataclass
+class SessionUIHandlePendingElicitationParamsResult:
+ """The elicitation response (accept with form values, decline, or cancel)"""
+
+ action: Action
+ """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)"""
+
+ content: dict[str, float | bool | list[str] | str] | None = None
+ """The form values submitted by the user (present when action is 'accept')"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SessionUIHandlePendingElicitationParamsResult':
+ assert isinstance(obj, dict)
+ action = Action(obj.get("action"))
+ content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content"))
+ return SessionUIHandlePendingElicitationParamsResult(action, content)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["action"] = to_enum(Action, self.action)
+ if self.content is not None:
+ result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content)
+ return result
+
+
+@dataclass
+class SessionUIHandlePendingElicitationParams:
+ request_id: str
+ """The unique request ID from the elicitation.requested event"""
+
+ result: SessionUIHandlePendingElicitationParamsResult
+ """The elicitation response (accept with form values, decline, or cancel)"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SessionUIHandlePendingElicitationParams':
+ assert isinstance(obj, dict)
+ request_id = from_str(obj.get("requestId"))
+ result = SessionUIHandlePendingElicitationParamsResult.from_dict(obj.get("result"))
+ return SessionUIHandlePendingElicitationParams(request_id, result)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["requestId"] = from_str(self.request_id)
+ result["result"] = to_class(SessionUIHandlePendingElicitationParamsResult, self.result)
+ return result
+
+
@dataclass
class SessionPermissionsHandlePendingPermissionRequestResult:
success: bool
@@ -2493,6 +2559,22 @@ def session_ui_elicitation_params_to_dict(x: SessionUIElicitationParams) -> Any:
return to_class(SessionUIElicitationParams, x)
+def session_ui_handle_pending_elicitation_result_from_dict(s: Any) -> SessionUIHandlePendingElicitationResult:
+ return SessionUIHandlePendingElicitationResult.from_dict(s)
+
+
+def session_ui_handle_pending_elicitation_result_to_dict(x: SessionUIHandlePendingElicitationResult) -> Any:
+ return to_class(SessionUIHandlePendingElicitationResult, x)
+
+
+def session_ui_handle_pending_elicitation_params_from_dict(s: Any) -> SessionUIHandlePendingElicitationParams:
+ return SessionUIHandlePendingElicitationParams.from_dict(s)
+
+
+def session_ui_handle_pending_elicitation_params_to_dict(x: SessionUIHandlePendingElicitationParams) -> Any:
+ return to_class(SessionUIHandlePendingElicitationParams, x)
+
+
def session_permissions_handle_pending_permission_request_result_from_dict(s: Any) -> SessionPermissionsHandlePendingPermissionRequestResult:
return SessionPermissionsHandlePendingPermissionRequestResult.from_dict(s)
@@ -2823,6 +2905,11 @@ async def elicitation(self, params: SessionUIElicitationParams, *, timeout: floa
params_dict["sessionId"] = self._session_id
return SessionUIElicitationResult.from_dict(await self._client.request("session.ui.elicitation", params_dict, **_timeout_kwargs(timeout)))
+ async def handle_pending_elicitation(self, params: SessionUIHandlePendingElicitationParams, *, timeout: float | None = None) -> SessionUIHandlePendingElicitationResult:
+ params_dict = {k: v for k, v in params.to_dict().items() if v is not None}
+ params_dict["sessionId"] = self._session_id
+ return SessionUIHandlePendingElicitationResult.from_dict(await self._client.request("session.ui.handlePendingElicitation", params_dict, **_timeout_kwargs(timeout)))
+
class PermissionsApi:
def __init__(self, client: "JsonRpcClient", session_id: str):
diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py
index 3dbe5cdf2..c3123102b 100644
--- a/python/copilot/generated/session_events.py
+++ b/python/copilot/generated/session_events.py
@@ -1741,12 +1741,34 @@ def to_dict(self) -> dict:
return result
+@dataclass
+class UI:
+ """UI capability changes"""
+
+ elicitation: bool | None = None
+ """Whether elicitation is now supported"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'UI':
+ assert isinstance(obj, dict)
+ elicitation = from_union([from_bool, from_none], obj.get("elicitation"))
+ return UI(elicitation)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ if self.elicitation is not None:
+ result["elicitation"] = from_union([from_bool, from_none], self.elicitation)
+ return result
+
+
@dataclass
class Data:
"""Session initialization metadata including context and configuration
Session resume metadata including current context and event count
+ Notifies Mission Control that the session's remote steering capability has changed
+
Error details for timeline display including message and optional diagnostic information
Payload indicating the agent is idle; includes any background tasks still in flight
@@ -1854,6 +1876,11 @@ class Data:
Elicitation request completion notification signaling UI dismissal
+ Sampling request from an MCP server; contains the server name and a requestId for
+ correlation
+
+ Sampling request completion notification signaling UI dismissal
+
OAuth authentication request for an MCP server
MCP OAuth request completion notification
@@ -1870,6 +1897,8 @@ class Data:
SDK command registration change notification
+ Session capability change notification
+
Plan approval request with plan content and available user actions
Plan mode exit completion notification signaling UI dismissal
@@ -1898,6 +1927,11 @@ class Data:
Reasoning effort level after the model change, if applicable
"""
+ remote_steerable: bool | None = None
+ """Whether this session supports remote steering via Mission Control
+
+ Whether this session now supports remote steering via Mission Control
+ """
selected_model: str | None = None
"""Model selected at session creation time, if any
@@ -1911,9 +1945,6 @@ class Data:
start_time: datetime | None = None
"""ISO 8601 timestamp when the session was created"""
- steerable: bool | None = None
- """Whether this session supports remote steering via Mission Control"""
-
version: float | None = None
"""Schema version number for the session event format"""
@@ -2182,6 +2213,12 @@ class Data:
Request ID of the resolved elicitation request; clients should dismiss any UI for this
request
+ Unique identifier for this sampling request; used to respond via
+ session.respondToSampling()
+
+ Request ID of the resolved sampling request; clients should dismiss any UI for this
+ request
+
Unique identifier for this OAuth request; used to respond via
session.respondToMcpOAuth()
@@ -2329,11 +2366,15 @@ class Data:
"""Duration of the API call in milliseconds"""
initiator: str | None = None
- """What initiated this API call (e.g., "sub-agent"); absent for user-initiated calls"""
-
+ """What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for
+ user-initiated calls
+ """
input_tokens: float | None = None
"""Number of input tokens consumed"""
+ inter_token_latency_ms: float | None = None
+ """Average inter-token latency in milliseconds. Only available for streaming requests"""
+
model: str | None = None
"""Model identifier used for this API call
@@ -2346,6 +2387,9 @@ class Data:
quota_snapshots: dict[str, QuotaSnapshot] | None = None
"""Per-quota resource usage snapshots, keyed by quota identifier"""
+ ttft_ms: float | None = None
+ """Time to first token in milliseconds. Only available for streaming requests"""
+
reason: str | None = None
"""Reason the current turn was aborted (e.g., "user initiated")"""
@@ -2498,8 +2542,13 @@ class Data:
requested_schema: RequestedSchema | None = None
"""JSON Schema describing the form fields to present to the user (form mode only)"""
+ mcp_request_id: float | str | None = None
+ """The JSON-RPC request ID from the MCP protocol"""
+
server_name: str | None = None
- """Display name of the MCP server that requires OAuth
+ """Name of the MCP server that initiated the sampling request
+
+ Display name of the MCP server that requires OAuth
Name of the MCP server whose status changed
"""
@@ -2529,6 +2578,9 @@ class Data:
commands: list[DataCommand] | None = None
"""Current list of registered SDK commands"""
+ ui: UI | None = None
+ """UI capability changes"""
+
actions: list[str] | None = None
"""Available actions the user can take (e.g., approve, edit, reject)"""
@@ -2567,10 +2619,10 @@ def from_dict(obj: Any) -> 'Data':
copilot_version = from_union([from_str, from_none], obj.get("copilotVersion"))
producer = from_union([from_str, from_none], obj.get("producer"))
reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort"))
+ remote_steerable = from_union([from_bool, from_none], obj.get("remoteSteerable"))
selected_model = from_union([from_str, from_none], obj.get("selectedModel"))
session_id = from_union([from_str, from_none], obj.get("sessionId"))
start_time = from_union([from_datetime, from_none], obj.get("startTime"))
- steerable = from_union([from_bool, from_none], obj.get("steerable"))
version = from_union([from_float, from_none], obj.get("version"))
event_count = from_union([from_float, from_none], obj.get("eventCount"))
resume_time = from_union([from_datetime, from_none], obj.get("resumeTime"))
@@ -2666,8 +2718,10 @@ def from_dict(obj: Any) -> 'Data':
duration = from_union([from_float, from_none], obj.get("duration"))
initiator = from_union([from_str, from_none], obj.get("initiator"))
input_tokens = from_union([from_float, from_none], obj.get("inputTokens"))
+ inter_token_latency_ms = from_union([from_float, from_none], obj.get("interTokenLatencyMs"))
model = from_union([from_str, from_none], obj.get("model"))
quota_snapshots = from_union([lambda x: from_dict(QuotaSnapshot.from_dict, x), from_none], obj.get("quotaSnapshots"))
+ ttft_ms = from_union([from_float, from_none], obj.get("ttftMs"))
reason = from_union([from_str, from_none], obj.get("reason"))
arguments = obj.get("arguments")
tool_call_id = from_union([from_str, from_none], obj.get("toolCallId"))
@@ -2705,6 +2759,7 @@ def from_dict(obj: Any) -> 'Data':
elicitation_source = from_union([from_str, from_none], obj.get("elicitationSource"))
mode = from_union([Mode, from_none], obj.get("mode"))
requested_schema = from_union([RequestedSchema.from_dict, from_none], obj.get("requestedSchema"))
+ mcp_request_id = from_union([from_float, from_str, from_none], obj.get("mcpRequestId"))
server_name = from_union([from_str, from_none], obj.get("serverName"))
server_url = from_union([from_str, from_none], obj.get("serverUrl"))
static_client_config = from_union([StaticClientConfig.from_dict, from_none], obj.get("staticClientConfig"))
@@ -2714,6 +2769,7 @@ def from_dict(obj: Any) -> 'Data':
args = from_union([from_str, from_none], obj.get("args"))
command_name = from_union([from_str, from_none], obj.get("commandName"))
commands = from_union([lambda x: from_list(DataCommand.from_dict, x), from_none], obj.get("commands"))
+ ui = from_union([UI.from_dict, from_none], obj.get("ui"))
actions = from_union([lambda x: from_list(from_str, x), from_none], obj.get("actions"))
plan_content = from_union([from_str, from_none], obj.get("planContent"))
recommended_action = from_union([from_str, from_none], obj.get("recommendedAction"))
@@ -2724,7 +2780,7 @@ def from_dict(obj: Any) -> 'Data':
servers = from_union([lambda x: from_list(Server.from_dict, x), from_none], obj.get("servers"))
status = from_union([ServerStatus, from_none], obj.get("status"))
extensions = from_union([lambda x: from_list(Extension.from_dict, x), from_none], obj.get("extensions"))
- return Data(already_in_use, context, copilot_version, producer, reasoning_effort, selected_model, session_id, start_time, steerable, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, url, background_tasks, title, info_type, warning_type, new_model, previous_model, previous_reasoning_effort, new_mode, previous_mode, operation, path, handoff_time, host, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, conversation_tokens, current_model, current_tokens, error_reason, model_metrics, session_start_time, shutdown_type, system_tokens, tool_definitions_tokens, total_api_duration_ms, total_premium_requests, base_commit, branch, cwd, git_root, head_commit, host_type, is_initial, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, agent_mode, attachments, content, interaction_id, source, transformed_content, turn_id, intent, reasoning_id, delta_content, total_response_size_bytes, encrypted_content, message_id, output_tokens, parent_tool_call_id, phase, reasoning_opaque, reasoning_text, tool_requests, api_call_id, cache_read_tokens, cache_write_tokens, copilot_usage, cost, duration, initiator, input_tokens, model, quota_snapshots, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, description, name, plugin_name, plugin_version, agent_description, agent_display_name, agent_name, duration_ms, total_tokens, total_tool_calls, tools, hook_invocation_id, hook_type, input, output, metadata, role, kind, permission_request, allow_freeform, choices, question, elicitation_source, mode, requested_schema, server_name, server_url, static_client_config, traceparent, tracestate, command, args, command_name, commands, actions, plan_content, recommended_action, skills, agents, errors, warnings, servers, status, extensions)
+ return Data(already_in_use, context, copilot_version, producer, reasoning_effort, remote_steerable, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, url, background_tasks, title, info_type, warning_type, new_model, previous_model, previous_reasoning_effort, new_mode, previous_mode, operation, path, handoff_time, host, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, conversation_tokens, current_model, current_tokens, error_reason, model_metrics, session_start_time, shutdown_type, system_tokens, tool_definitions_tokens, total_api_duration_ms, total_premium_requests, base_commit, branch, cwd, git_root, head_commit, host_type, is_initial, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, agent_mode, attachments, content, interaction_id, source, transformed_content, turn_id, intent, reasoning_id, delta_content, total_response_size_bytes, encrypted_content, message_id, output_tokens, parent_tool_call_id, phase, reasoning_opaque, reasoning_text, tool_requests, api_call_id, cache_read_tokens, cache_write_tokens, copilot_usage, cost, duration, initiator, input_tokens, inter_token_latency_ms, model, quota_snapshots, ttft_ms, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, description, name, plugin_name, plugin_version, agent_description, agent_display_name, agent_name, duration_ms, total_tokens, total_tool_calls, tools, hook_invocation_id, hook_type, input, output, metadata, role, kind, permission_request, allow_freeform, choices, question, elicitation_source, mode, requested_schema, mcp_request_id, server_name, server_url, static_client_config, traceparent, tracestate, command, args, command_name, commands, ui, actions, plan_content, recommended_action, skills, agents, errors, warnings, servers, status, extensions)
def to_dict(self) -> dict:
result: dict = {}
@@ -2738,14 +2794,14 @@ def to_dict(self) -> dict:
result["producer"] = from_union([from_str, from_none], self.producer)
if self.reasoning_effort is not None:
result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort)
+ if self.remote_steerable is not None:
+ result["remoteSteerable"] = from_union([from_bool, from_none], self.remote_steerable)
if self.selected_model is not None:
result["selectedModel"] = from_union([from_str, from_none], self.selected_model)
if self.session_id is not None:
result["sessionId"] = from_union([from_str, from_none], self.session_id)
if self.start_time is not None:
result["startTime"] = from_union([lambda x: x.isoformat(), from_none], self.start_time)
- if self.steerable is not None:
- result["steerable"] = from_union([from_bool, from_none], self.steerable)
if self.version is not None:
result["version"] = from_union([to_float, from_none], self.version)
if self.event_count is not None:
@@ -2936,10 +2992,14 @@ def to_dict(self) -> dict:
result["initiator"] = from_union([from_str, from_none], self.initiator)
if self.input_tokens is not None:
result["inputTokens"] = from_union([to_float, from_none], self.input_tokens)
+ if self.inter_token_latency_ms is not None:
+ result["interTokenLatencyMs"] = from_union([to_float, from_none], self.inter_token_latency_ms)
if self.model is not None:
result["model"] = from_union([from_str, from_none], self.model)
if self.quota_snapshots is not None:
result["quotaSnapshots"] = from_union([lambda x: from_dict(lambda x: to_class(QuotaSnapshot, x), x), from_none], self.quota_snapshots)
+ if self.ttft_ms is not None:
+ result["ttftMs"] = from_union([to_float, from_none], self.ttft_ms)
if self.reason is not None:
result["reason"] = from_union([from_str, from_none], self.reason)
if self.arguments is not None:
@@ -3014,6 +3074,8 @@ def to_dict(self) -> dict:
result["mode"] = from_union([lambda x: to_enum(Mode, x), from_none], self.mode)
if self.requested_schema is not None:
result["requestedSchema"] = from_union([lambda x: to_class(RequestedSchema, x), from_none], self.requested_schema)
+ if self.mcp_request_id is not None:
+ result["mcpRequestId"] = from_union([to_float, from_str, from_none], self.mcp_request_id)
if self.server_name is not None:
result["serverName"] = from_union([from_str, from_none], self.server_name)
if self.server_url is not None:
@@ -3032,6 +3094,8 @@ def to_dict(self) -> dict:
result["commandName"] = from_union([from_str, from_none], self.command_name)
if self.commands is not None:
result["commands"] = from_union([lambda x: from_list(lambda x: to_class(DataCommand, x), x), from_none], self.commands)
+ if self.ui is not None:
+ result["ui"] = from_union([lambda x: to_class(UI, x), from_none], self.ui)
if self.actions is not None:
result["actions"] = from_union([lambda x: from_list(from_str, x), from_none], self.actions)
if self.plan_content is not None:
@@ -3066,6 +3130,7 @@ class SessionEventType(Enum):
ASSISTANT_TURN_END = "assistant.turn_end"
ASSISTANT_TURN_START = "assistant.turn_start"
ASSISTANT_USAGE = "assistant.usage"
+ CAPABILITIES_CHANGED = "capabilities.changed"
COMMANDS_CHANGED = "commands.changed"
COMMAND_COMPLETED = "command.completed"
COMMAND_EXECUTE = "command.execute"
@@ -3083,6 +3148,8 @@ class SessionEventType(Enum):
PENDING_MESSAGES_MODIFIED = "pending_messages.modified"
PERMISSION_COMPLETED = "permission.completed"
PERMISSION_REQUESTED = "permission.requested"
+ SAMPLING_COMPLETED = "sampling.completed"
+ SAMPLING_REQUESTED = "sampling.requested"
SESSION_BACKGROUND_TASKS_CHANGED = "session.background_tasks_changed"
SESSION_COMPACTION_COMPLETE = "session.compaction_complete"
SESSION_COMPACTION_START = "session.compaction_start"
@@ -3098,6 +3165,7 @@ class SessionEventType(Enum):
SESSION_MODEL_CHANGE = "session.model_change"
SESSION_MODE_CHANGED = "session.mode_changed"
SESSION_PLAN_CHANGED = "session.plan_changed"
+ SESSION_REMOTE_STEERABLE_CHANGED = "session.remote_steerable_changed"
SESSION_RESUME = "session.resume"
SESSION_SHUTDOWN = "session.shutdown"
SESSION_SKILLS_LOADED = "session.skills_loaded"
@@ -3143,6 +3211,8 @@ class SessionEvent:
Session resume metadata including current context and event count
+ Notifies Mission Control that the session's remote steering capability has changed
+
Error details for timeline display including message and optional diagnostic information
Payload indicating the agent is idle; includes any background tasks still in flight
@@ -3250,6 +3320,11 @@ class SessionEvent:
Elicitation request completion notification signaling UI dismissal
+ Sampling request from an MCP server; contains the server name and a requestId for
+ correlation
+
+ Sampling request completion notification signaling UI dismissal
+
OAuth authentication request for an MCP server
MCP OAuth request completion notification
@@ -3266,6 +3341,8 @@ class SessionEvent:
SDK command registration change notification
+ Session capability change notification
+
Plan approval request with plan content and available user actions
Plan mode exit completion notification signaling UI dismissal
diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json
index 5ab4ae513..d1ee2fa24 100644
--- a/test/harness/package-lock.json
+++ b/test/harness/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"devDependencies": {
- "@github/copilot": "^1.0.11",
+ "@github/copilot": "^1.0.14-0",
"@modelcontextprotocol/sdk": "^1.26.0",
"@types/node": "^25.3.3",
"openai": "^6.17.0",
@@ -462,27 +462,27 @@
}
},
"node_modules/@github/copilot": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.11.tgz",
- "integrity": "sha512-cptVopko/tNKEXyBP174yBjHQBEwg6CqaKN2S0M3J+5LEB8u31bLL75ioOPd+5vubqBrA0liyTdcHeZ8UTRbmg==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.14-0.tgz",
+ "integrity": "sha512-9eA5sFbvx69OtQnVoeik/8boFqHgGAhylLeUjEACc3kB70aaH1E/cHgxNzSMyYgZDjpXov0/IBXjtx2otpfHBw==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"bin": {
"copilot": "npm-loader.js"
},
"optionalDependencies": {
- "@github/copilot-darwin-arm64": "1.0.11",
- "@github/copilot-darwin-x64": "1.0.11",
- "@github/copilot-linux-arm64": "1.0.11",
- "@github/copilot-linux-x64": "1.0.11",
- "@github/copilot-win32-arm64": "1.0.11",
- "@github/copilot-win32-x64": "1.0.11"
+ "@github/copilot-darwin-arm64": "1.0.14-0",
+ "@github/copilot-darwin-x64": "1.0.14-0",
+ "@github/copilot-linux-arm64": "1.0.14-0",
+ "@github/copilot-linux-x64": "1.0.14-0",
+ "@github/copilot-win32-arm64": "1.0.14-0",
+ "@github/copilot-win32-x64": "1.0.14-0"
}
},
"node_modules/@github/copilot-darwin-arm64": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.11.tgz",
- "integrity": "sha512-wdKimjtbsVeXqMqQSnGpGBPFEYHljxXNuWeH8EIJTNRgFpAsimcivsFgql3Twq4YOp0AxfsH36icG4IEen30mA==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.14-0.tgz",
+ "integrity": "sha512-w11Eqmfnu0ihrvgLysTd5Tkq8LuQa9eW63CNTQ/k5copnG1AMCdvd3K/78MxE2DdFJPq2L95KGS5cs9jH1dlIw==",
"cpu": [
"arm64"
],
@@ -497,9 +497,9 @@
}
},
"node_modules/@github/copilot-darwin-x64": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.11.tgz",
- "integrity": "sha512-VeuPv8rzBVGBB8uDwMEhcHBpldoKaq26yZ5YQm+G9Ka5QIF+1DMah8ZNRMVsTeNKkb1ji9G8vcuCsaPbnG3fKg==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.14-0.tgz",
+ "integrity": "sha512-4X/dMSPxCE/rvL6N1tgnwFxBg2uXnPrN63GGgS/FqK/fNi3TtcuojDVv8K1yjmEYpF8PXdkQttDlp6bKc+Nonw==",
"cpu": [
"x64"
],
@@ -514,9 +514,9 @@
}
},
"node_modules/@github/copilot-linux-arm64": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.11.tgz",
- "integrity": "sha512-/d8p6RlFYKj1Va2hekFIcYNMHWagcEkaxgcllUNXSyQLnmEtXUkaWtz62VKGWE+n/UMkEwCB6vI2xEwPTlUNBQ==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.14-0.tgz",
+ "integrity": "sha512-A4thcLUoErEvfBO3Hsl/hJASibn44qwZm1ZSeVBPCa1FkpowBwo8fT1eV9EwN/ftKsyks3QkndNFvHkVzjUfxA==",
"cpu": [
"arm64"
],
@@ -531,9 +531,9 @@
}
},
"node_modules/@github/copilot-linux-x64": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.11.tgz",
- "integrity": "sha512-UujTRO3xkPFC1CybchBbCnaTEAG6JrH0etIst07JvfekMWgvRxbiCHQPpDPSzBCPiBcGu0gba0/IT+vUCORuIw==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.14-0.tgz",
+ "integrity": "sha512-Kwn+Qn8/BqWRKa2DewZipH7rPIO8nDRWzpVy/ZLcRWBAvnIU+6BLWfhnYEU44DsqkD2VeWhKVfQlNmDX23xKKg==",
"cpu": [
"x64"
],
@@ -548,9 +548,9 @@
}
},
"node_modules/@github/copilot-win32-arm64": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.11.tgz",
- "integrity": "sha512-EOW8HUM+EmnHEZEa+iUMl4pP1+2eZUk2XCbynYiMehwX9sidc4BxEHp2RuxADSzFPTieQEWzgjQmHWrtet8pQg==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.14-0.tgz",
+ "integrity": "sha512-8P5kxcb8YVWSS+Ihs+ykyy8jov1WwQ8GKV4d7mJN268Jpd8y5VI8Peb7uE2VO0lRLgq5c2VcXuZDsLG/1Wgnlw==",
"cpu": [
"arm64"
],
@@ -565,9 +565,9 @@
}
},
"node_modules/@github/copilot-win32-x64": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.11.tgz",
- "integrity": "sha512-fKGkSNamzs3h9AbmswNvPYJBORCb2Y8CbusijU3C7fT3ohvqnHJwKo5iHhJXLOKZNOpFZgq9YKha410u9sIs6Q==",
+ "version": "1.0.14-0",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.14-0.tgz",
+ "integrity": "sha512-JWxp08j5o/PUkRZtZVagNYJLjH+KCURCyZRb7BfnC0A3vLeqcJQ70JC5qlYEAlcRnb4uCUJnmnpbWLLOJ+ObrA==",
"cpu": [
"x64"
],
diff --git a/test/harness/package.json b/test/harness/package.json
index 9fe936ea7..f8fe732e4 100644
--- a/test/harness/package.json
+++ b/test/harness/package.json
@@ -11,7 +11,7 @@
"test": "vitest run"
},
"devDependencies": {
- "@github/copilot": "^1.0.11",
+ "@github/copilot": "^1.0.14-0",
"@modelcontextprotocol/sdk": "^1.26.0",
"@types/node": "^25.3.3",
"openai": "^6.17.0",