Skip to content

Commit 075a005

Browse files
stephentoubCopilot
andcommitted
Restore mode handler APIs across SDKs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 066a69c commit 075a005

31 files changed

Lines changed: 3425 additions & 45 deletions

dotnet/src/Client.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,8 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
562562
session.RegisterPermissionHandler(config.OnPermissionRequest);
563563
session.RegisterCommands(config.Commands);
564564
session.RegisterElicitationHandler(config.OnElicitationRequest);
565+
session.RegisterExitPlanModeHandler(config.OnExitPlanMode);
566+
session.RegisterAutoModeSwitchHandler(config.OnAutoModeSwitch);
565567
if (config.OnUserInputRequest != null)
566568
{
567569
session.RegisterUserInputHandler(config.OnUserInputRequest);
@@ -605,6 +607,8 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
605607
config.EnableSessionTelemetry,
606608
(bool?)true,
607609
config.OnUserInputRequest != null ? true : null,
610+
config.OnExitPlanMode != null ? true : null,
611+
config.OnAutoModeSwitch != null ? true : null,
608612
hasHooks ? true : null,
609613
config.WorkingDirectory,
610614
config.Streaming is true ? true : null,
@@ -714,6 +718,8 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
714718
session.RegisterPermissionHandler(config.OnPermissionRequest);
715719
session.RegisterCommands(config.Commands);
716720
session.RegisterElicitationHandler(config.OnElicitationRequest);
721+
session.RegisterExitPlanModeHandler(config.OnExitPlanMode);
722+
session.RegisterAutoModeSwitchHandler(config.OnAutoModeSwitch);
717723
if (config.OnUserInputRequest != null)
718724
{
719725
session.RegisterUserInputHandler(config.OnUserInputRequest);
@@ -757,6 +763,8 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
757763
config.EnableSessionTelemetry,
758764
(bool?)true,
759765
config.OnUserInputRequest != null ? true : null,
766+
config.OnExitPlanMode != null ? true : null,
767+
config.OnAutoModeSwitch != null ? true : null,
760768
hasHooks ? true : null,
761769
config.WorkingDirectory,
762770
config.ConfigDir,
@@ -1645,6 +1653,8 @@ private async Task<Connection> ConnectToServerAsync(Process? cliProcess, string?
16451653
rpc.SetLocalRpcMethod("tool.call", handler.OnToolCallV2);
16461654
rpc.SetLocalRpcMethod("permission.request", handler.OnPermissionRequestV2);
16471655
rpc.SetLocalRpcMethod("userInput.request", handler.OnUserInputRequest);
1656+
rpc.SetLocalRpcMethod("exitPlanMode.request", handler.OnExitPlanModeRequest);
1657+
rpc.SetLocalRpcMethod("autoModeSwitch.request", handler.OnAutoModeSwitchRequest);
16481658
rpc.SetLocalRpcMethod("hooks.invoke", handler.OnHooksInvoke);
16491659
rpc.SetLocalRpcMethod("systemMessage.transform", handler.OnSystemMessageTransform);
16501660
ClientSessionApiRegistration.RegisterClientSessionApiHandlers(rpc, sessionId =>
@@ -1763,6 +1773,39 @@ public async ValueTask<UserInputRequestResponse> OnUserInputRequest(string sessi
17631773
return new UserInputRequestResponse(result.Answer, result.WasFreeform);
17641774
}
17651775

1776+
public async ValueTask<ExitPlanModeResult> OnExitPlanModeRequest(
1777+
string sessionId,
1778+
string summary,
1779+
string? planContent = null,
1780+
IList<string>? actions = null,
1781+
string? recommendedAction = null)
1782+
{
1783+
var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
1784+
var request = new ExitPlanModeRequest
1785+
{
1786+
Summary = summary,
1787+
PlanContent = planContent,
1788+
Actions = actions ?? [],
1789+
RecommendedAction = recommendedAction ?? "autopilot"
1790+
};
1791+
1792+
return await session.HandleExitPlanModeRequestAsync(request);
1793+
}
1794+
1795+
public async ValueTask<AutoModeSwitchRequestResponse> OnAutoModeSwitchRequest(
1796+
string sessionId,
1797+
string? errorCode = null,
1798+
double? retryAfterSeconds = null)
1799+
{
1800+
var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
1801+
var response = await session.HandleAutoModeSwitchRequestAsync(new AutoModeSwitchRequest
1802+
{
1803+
ErrorCode = errorCode,
1804+
RetryAfterSeconds = retryAfterSeconds
1805+
});
1806+
return new AutoModeSwitchRequestResponse(response);
1807+
}
1808+
17661809
public async ValueTask<HooksInvokeResponse> OnHooksInvoke(string sessionId, string hookType, JsonElement input)
17671810
{
17681811
var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}");
@@ -1916,6 +1959,8 @@ internal record CreateSessionRequest(
19161959
bool? EnableSessionTelemetry,
19171960
bool? RequestPermission,
19181961
bool? RequestUserInput,
1962+
bool? RequestExitPlanMode,
1963+
bool? RequestAutoModeSwitch,
19191964
bool? Hooks,
19201965
string? WorkingDirectory,
19211966
bool? Streaming,
@@ -1973,6 +2018,8 @@ internal record ResumeSessionRequest(
19732018
bool? EnableSessionTelemetry,
19742019
bool? RequestPermission,
19752020
bool? RequestUserInput,
2021+
bool? RequestExitPlanMode,
2022+
bool? RequestAutoModeSwitch,
19762023
bool? Hooks,
19772024
string? WorkingDirectory,
19782025
string? ConfigDir,
@@ -2035,6 +2082,9 @@ internal record UserInputRequestResponse(
20352082
string Answer,
20362083
bool WasFreeform);
20372084

2085+
internal record AutoModeSwitchRequestResponse(
2086+
AutoModeSwitchResponse Response);
2087+
20382088
internal record HooksInvokeResponse(
20392089
object? Output);
20402090

@@ -2052,9 +2102,14 @@ internal record PermissionRequestResponseV2(
20522102
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
20532103
[JsonSerializable(typeof(CreateSessionRequest))]
20542104
[JsonSerializable(typeof(CreateSessionResponse))]
2105+
[JsonSerializable(typeof(AutoModeSwitchRequest))]
2106+
[JsonSerializable(typeof(AutoModeSwitchRequestResponse))]
2107+
[JsonSerializable(typeof(AutoModeSwitchResponse))]
20552108
[JsonSerializable(typeof(CustomAgentConfig))]
20562109
[JsonSerializable(typeof(DeleteSessionRequest))]
20572110
[JsonSerializable(typeof(DeleteSessionResponse))]
2111+
[JsonSerializable(typeof(ExitPlanModeRequest))]
2112+
[JsonSerializable(typeof(ExitPlanModeResult))]
20582113
[JsonSerializable(typeof(GetLastSessionIdResponse))]
20592114
[JsonSerializable(typeof(HooksInvokeResponse))]
20602115
[JsonSerializable(typeof(ListSessionsRequest))]

dotnet/src/Session.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ public sealed partial class CopilotSession : IAsyncDisposable
6464
private volatile PermissionRequestHandler? _permissionHandler;
6565
private volatile UserInputHandler? _userInputHandler;
6666
private volatile ElicitationHandler? _elicitationHandler;
67+
private volatile ExitPlanModeHandler? _exitPlanModeHandler;
68+
private volatile AutoModeSwitchHandler? _autoModeSwitchHandler;
6769
private ImmutableArray<SessionEventHandler> _eventHandlers = ImmutableArray<SessionEventHandler>.Empty;
6870

6971
private SessionHooks? _hooks;
@@ -759,6 +761,24 @@ internal void RegisterElicitationHandler(ElicitationHandler? handler)
759761
_elicitationHandler = handler;
760762
}
761763

764+
/// <summary>
765+
/// Registers an exit-plan-mode handler for this session.
766+
/// </summary>
767+
/// <param name="handler">The handler to invoke when an exit-plan-mode request is received.</param>
768+
internal void RegisterExitPlanModeHandler(ExitPlanModeHandler? handler)
769+
{
770+
_exitPlanModeHandler = handler;
771+
}
772+
773+
/// <summary>
774+
/// Registers an auto-mode-switch handler for this session.
775+
/// </summary>
776+
/// <param name="handler">The handler to invoke when an auto-mode-switch request is received.</param>
777+
internal void RegisterAutoModeSwitchHandler(AutoModeSwitchHandler? handler)
778+
{
779+
_autoModeSwitchHandler = handler;
780+
}
781+
762782
/// <summary>
763783
/// Sets the capabilities reported by the host for this session.
764784
/// </summary>
@@ -1016,6 +1036,52 @@ internal async Task<UserInputResponse> HandleUserInputRequestAsync(UserInputRequ
10161036
return response;
10171037
}
10181038

1039+
/// <summary>
1040+
/// Handles an exit-plan-mode request from the Copilot CLI.
1041+
/// </summary>
1042+
/// <param name="request">The exit-plan-mode request from the CLI.</param>
1043+
/// <returns>A task that resolves with the user's decision.</returns>
1044+
internal async Task<ExitPlanModeResult> HandleExitPlanModeRequestAsync(ExitPlanModeRequest request)
1045+
{
1046+
var handler = _exitPlanModeHandler;
1047+
if (handler is null)
1048+
{
1049+
return new ExitPlanModeResult { Approved = true };
1050+
}
1051+
1052+
var invocation = new ExitPlanModeInvocation { SessionId = SessionId };
1053+
var timestamp = Stopwatch.GetTimestamp();
1054+
var response = await handler(request, invocation);
1055+
LogTiming(_logger, LogLevel.Debug, null,
1056+
"CopilotSession.HandleExitPlanModeRequestAsync dispatch. Elapsed={Elapsed}, SessionId={SessionId}",
1057+
timestamp,
1058+
SessionId);
1059+
return response;
1060+
}
1061+
1062+
/// <summary>
1063+
/// Handles an auto-mode-switch request from the Copilot CLI.
1064+
/// </summary>
1065+
/// <param name="request">The auto-mode-switch request from the CLI.</param>
1066+
/// <returns>A task that resolves with the user's decision.</returns>
1067+
internal async Task<AutoModeSwitchResponse> HandleAutoModeSwitchRequestAsync(AutoModeSwitchRequest request)
1068+
{
1069+
var handler = _autoModeSwitchHandler;
1070+
if (handler is null)
1071+
{
1072+
return AutoModeSwitchResponse.No;
1073+
}
1074+
1075+
var invocation = new AutoModeSwitchInvocation { SessionId = SessionId };
1076+
var timestamp = Stopwatch.GetTimestamp();
1077+
var response = await handler(request, invocation);
1078+
LogTiming(_logger, LogLevel.Debug, null,
1079+
"CopilotSession.HandleAutoModeSwitchRequestAsync dispatch. Elapsed={Elapsed}, SessionId={SessionId}",
1080+
timestamp,
1081+
SessionId);
1082+
return response;
1083+
}
1084+
10191085
/// <summary>
10201086
/// Registers hook handlers for this session.
10211087
/// </summary>
@@ -1349,7 +1415,10 @@ await InvokeRpcAsync<object>(
13491415
_commandHandlers.Clear();
13501416

13511417
_permissionHandler = null;
1418+
_userInputHandler = null;
13521419
_elicitationHandler = null;
1420+
_exitPlanModeHandler = null;
1421+
_autoModeSwitchHandler = null;
13531422
}
13541423

13551424
[LoggerMessage(Level = LogLevel.Error, Message = "Unhandled exception in broadcast event handler")]
@@ -1406,6 +1475,10 @@ internal record SessionDestroyRequest
14061475
[JsonSerializable(typeof(SessionAbortRequest))]
14071476
[JsonSerializable(typeof(SessionDestroyRequest))]
14081477
[JsonSerializable(typeof(UserMessageAttachment))]
1478+
[JsonSerializable(typeof(AutoModeSwitchRequest))]
1479+
[JsonSerializable(typeof(AutoModeSwitchResponse))]
1480+
[JsonSerializable(typeof(ExitPlanModeRequest))]
1481+
[JsonSerializable(typeof(ExitPlanModeResult))]
14091482
[JsonSerializable(typeof(PreToolUseHookInput))]
14101483
[JsonSerializable(typeof(PreToolUseHookOutput))]
14111484
[JsonSerializable(typeof(PostToolUseHookInput))]

0 commit comments

Comments
 (0)