Skip to content

Commit 7a916f8

Browse files
[SDK] Expose Ask User Variant Session Option (#2432)
* [SDK] Expose Ask User Variant Session Option Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Skip tool metadata check in-process The in-process transport does not expose current tool metadata for introspection, so keep the runtime schema assertion on the stdio cells where that RPC is supported. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Clarify legacy ask-user handler documentation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 01b27f1 commit 7a916f8

35 files changed

Lines changed: 692 additions & 30 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ The experimental Node.js Agent Factories convenience API now supports paginated
1313

1414
Factory `run` and `resume` options now accept `notifyOnComplete` and `logPhaseNames`. The SDK forwards these options to the Copilot CLI for new and resumed runs.
1515

16+
### Feature: selectable `ask_user` session behavior
17+
18+
Session create and cold resume now accept a language-specific `askUserVariant` option with `legacy` and `elicitation` values. SDK sessions retain the legacy question-and-answer tool by default. Select `elicitation` and provide an elicitation handler to expose the structured form-based `ask_user` tool.
19+
1620
### Feature: rotating session-scoped GitHub credentials
1721

1822
All six SDKs can now acquire short-lived GitHub credentials through a session-scoped callback. The SDK registers the callback before session create or resume, maps `initial` and `refresh` requests to the owning session, and removes registrations on rollback, replacement, session close, and client close. Static per-session `gitHubToken` credentials remain supported and are mutually exclusive with the callback.

dotnet/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ Create a new conversation session.
135135
- `EnableSessionStore` - Enables the cross-session store for search and retrieval across sessions. When unset in `CopilotClientMode.CopilotCli`, the runtime default applies (enabled). In `CopilotClientMode.Empty`, defaults to disabled.
136136
- `GitHubTokenProvider` - Acquires session-scoped GitHub tokens on demand. Return `GitHubTokenProviderResult.FromToken` with a positive `ExpiresIn` value (production GitHub tokens typically use `8 * 60 * 60` seconds), or `GitHubTokenProviderResult.Cancel()`. Cannot be combined with `GitHubToken`.
137137
- `OnPermissionRequest` - Optional handler called before each tool execution to approve or deny it. When omitted, permission requests are emitted as events and left pending for manual resolution. `PermissionHandler.ApproveAll` approves requests when managed settings are disabled and throws when `EnableManagedSettings` is true. Custom handlers can inspect `ManagedApprovalRequired` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section.
138-
- `OnUserInputRequest` - Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section.
138+
- `OnUserInputRequest` - Handler for legacy question-and-answer requests from the agent. Enables the legacy `ask_user` tool. See [User Input Requests](#user-input-requests) section.
139+
- `AskUserVariant` - Selects the model-facing `ask_user` tool shape. Defaults to `AskUserVariant.Legacy`; use `AskUserVariant.Elicitation` with `OnElicitationRequest`.
139140
- `Hooks` - Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
140141

141142
##### `ResumeSessionAsync(string sessionId, ResumeSessionConfig? config = null): Task<CopilotSession>`
@@ -146,6 +147,7 @@ Resume an existing session. Returns the session with `WorkspacePath` populated i
146147

147148
- `OnPermissionRequest` - Optional handler called before each tool execution to approve or deny it. See [Permission Handling](#permission-handling) section.
148149
- `GitHubTokenProvider` - Replaces the session-scoped token provider when resuming. Cannot be combined with `GitHubToken`.
150+
- `AskUserVariant` - Re-supplies the model-facing `ask_user` tool shape on cold resume.
149151

150152
```csharp
151153
await using var session = await client.CreateSessionAsync(new SessionConfig
@@ -871,7 +873,7 @@ To let a specific custom tool bypass the permission prompt entirely, set `SkipPe
871873

872874
## User Input Requests
873875

874-
Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler:
876+
Enable the legacy question-and-answer `ask_user` tool by providing an `OnUserInputRequest` handler:
875877

876878
```csharp
877879
var session = await client.CreateSessionAsync(new SessionConfig
@@ -1004,6 +1006,7 @@ var session = await client.CreateSessionAsync(new SessionConfig
10041006
{
10051007
Model = "gpt-5",
10061008
OnPermissionRequest = PermissionHandler.ApproveAll,
1009+
AskUserVariant = AskUserVariant.Elicitation,
10071010
OnElicitationRequest = async (context) =>
10081011
{
10091012
// context.SessionId - Session that triggered the request

dotnet/src/Client.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
11901190
config.EnableCitations,
11911191
config.EnableFileChangeTracking,
11921192
wireSystemMessage,
1193+
config.AskUserVariant,
11931194
toolFilter.AvailableTools,
11941195
toolFilter.ExcludedTools,
11951196
config.ExcludedBuiltInAgents,
@@ -1428,6 +1429,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
14281429
config.EnableCitations,
14291430
config.EnableFileChangeTracking,
14301431
wireSystemMessage,
1432+
config.AskUserVariant,
14311433
toolFilter.AvailableTools,
14321434
toolFilter.ExcludedTools,
14331435
config.ExcludedBuiltInAgents,
@@ -2868,6 +2870,7 @@ internal record CreateSessionRequest(
28682870
bool? EnableCitations,
28692871
bool? EnableFileChangeTracking,
28702872
SystemMessageConfig? SystemMessage,
2873+
AskUserVariant? AskUserVariant,
28712874
IList<string>? AvailableTools,
28722875
IList<string>? ExcludedTools,
28732876
[property: JsonPropertyName("excludedBuiltinAgents")] IList<string>? ExcludedBuiltInAgents,
@@ -2983,6 +2986,7 @@ internal record ResumeSessionRequest(
29832986
bool? EnableCitations,
29842987
bool? EnableFileChangeTracking,
29852988
SystemMessageConfig? SystemMessage,
2989+
AskUserVariant? AskUserVariant,
29862990
IList<string>? AvailableTools,
29872991
IList<string>? ExcludedTools,
29882992
[property: JsonPropertyName("excludedBuiltinAgents")] IList<string>? ExcludedBuiltInAgents,

dotnet/src/Types.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3116,6 +3116,21 @@ public sealed class ManagedSettings
31163116
public ManagedSettingsPermissions? Permissions { get; set; }
31173117
}
31183118

3119+
/// <summary>
3120+
/// Selects the model-facing shape of the built-in <c>ask_user</c> tool.
3121+
/// </summary>
3122+
[JsonConverter(typeof(JsonStringEnumConverter<AskUserVariant>))]
3123+
public enum AskUserVariant
3124+
{
3125+
/// <summary>Use the legacy user-input request flow.</summary>
3126+
[JsonStringEnumMemberName("legacy")]
3127+
Legacy,
3128+
3129+
/// <summary>Use the elicitation request flow.</summary>
3130+
[JsonStringEnumMemberName("elicitation")]
3131+
Elicitation
3132+
}
3133+
31193134
/// <summary>
31203135
/// Shared configuration properties for creating or resuming a Copilot session.
31213136
/// Use <see cref="SessionConfig"/> when creating a new session, or
@@ -3205,6 +3220,7 @@ protected SessionConfigBase(SessionConfigBase? other)
32053220
ReasoningEffort = other.ReasoningEffort;
32063221
ReasoningSummary = other.ReasoningSummary;
32073222
ContextTier = other.ContextTier;
3223+
AskUserVariant = other.AskUserVariant;
32083224
CreateSessionFsProvider = other.CreateSessionFsProvider;
32093225
GitHubToken = other.GitHubToken;
32103226
GitHubTokenProvider = other.GitHubTokenProvider;
@@ -3372,6 +3388,15 @@ protected SessionConfigBase(SessionConfigBase? other)
33723388
/// <summary>System message configuration for the session.</summary>
33733389
public SystemMessageConfig? SystemMessage { get; set; }
33743390

3391+
/// <summary>
3392+
/// Selects the model-facing shape of the built-in <c>ask_user</c> tool.
3393+
/// The default is <see cref="GitHub.Copilot.AskUserVariant.Legacy"/>. To use
3394+
/// <see cref="GitHub.Copilot.AskUserVariant.Elicitation"/>, also provide
3395+
/// <see cref="OnElicitationRequest"/> so the host can answer structured forms.
3396+
/// The runtime resolves this option when it creates or cold-resumes the session.
3397+
/// </summary>
3398+
public AskUserVariant? AskUserVariant { get; set; }
3399+
33753400
/// <summary>List of tool names to allow; only these tools will be available when specified.</summary>
33763401
public IList<string>? AvailableTools { get; set; }
33773402

@@ -3470,7 +3495,11 @@ protected SessionConfigBase(SessionConfigBase? other)
34703495
/// <summary>Handler for permission requests from the server.</summary>
34713496
public Func<PermissionRequest, PermissionInvocation, Task<PermissionDecision>>? OnPermissionRequest { get; set; }
34723497

3473-
/// <summary>Handler for user input requests from the agent.</summary>
3498+
/// <summary>
3499+
/// Handler for user input requests from the agent. When provided with the default
3500+
/// <see cref="GitHub.Copilot.AskUserVariant.Legacy"/> variant, enables the
3501+
/// question-and-answer form of the <c>ask_user</c> tool.
3502+
/// </summary>
34743503
public Func<UserInputRequest, UserInputInvocation, Task<UserInputResponse>>? OnUserInputRequest { get; set; }
34753504

34763505
/// <summary>Slash commands registered for this session.</summary>

dotnet/test/Unit/ClientSessionLifetimeTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,54 @@ public async Task CreateSessionAsync_Omits_CustomAgent_ReasoningEffort_When_Unse
464464
Assert.False(agent.TryGetProperty("reasoningEffort", out _));
465465
}
466466

467+
[Fact]
468+
public async Task CreateSessionAsync_Forwards_AskUserVariant()
469+
{
470+
await using var server = await FakeCopilotServer.StartAsync();
471+
await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
472+
473+
await using var session = await client.CreateSessionAsync(new SessionConfig
474+
{
475+
AskUserVariant = AskUserVariant.Elicitation,
476+
OnPermissionRequest = PermissionHandler.ApproveAll
477+
});
478+
479+
var request = Assert.Single(server.Requests, request => request.Method == "session.create");
480+
Assert.Equal("elicitation", request.Params.GetProperty("askUserVariant").GetString());
481+
482+
server.ClearRequests();
483+
await using var defaultSession = await client.CreateSessionAsync(new SessionConfig
484+
{
485+
OnPermissionRequest = PermissionHandler.ApproveAll
486+
});
487+
var defaultRequest = Assert.Single(server.Requests, request => request.Method == "session.create");
488+
Assert.False(defaultRequest.Params.TryGetProperty("askUserVariant", out _));
489+
}
490+
491+
[Fact]
492+
public async Task ResumeSessionAsync_Forwards_AskUserVariant_On_Cold_Resume()
493+
{
494+
await using var server = await FakeCopilotServer.StartAsync();
495+
await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
496+
497+
await using var session = await client.ResumeSessionAsync("ask-user-variant", new ResumeSessionConfig
498+
{
499+
AskUserVariant = AskUserVariant.Legacy,
500+
OnPermissionRequest = PermissionHandler.ApproveAll
501+
});
502+
503+
var request = Assert.Single(server.Requests, request => request.Method == "session.resume");
504+
Assert.Equal("legacy", request.Params.GetProperty("askUserVariant").GetString());
505+
506+
server.ClearRequests();
507+
await using var defaultSession = await client.ResumeSessionAsync("ask-user-variant-default", new ResumeSessionConfig
508+
{
509+
OnPermissionRequest = PermissionHandler.ApproveAll
510+
});
511+
var defaultRequest = Assert.Single(server.Requests, request => request.Method == "session.resume");
512+
Assert.False(defaultRequest.Params.TryGetProperty("askUserVariant", out _));
513+
}
514+
467515
[Fact]
468516
public async Task SessionRequests_Serialize_AdditionalDirectories()
469517
{

dotnet/test/Unit/CloneTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
7373
ReasoningEffort = "high",
7474
ReasoningSummary = ReasoningSummary.Detailed,
7575
ContextTier = ContextTier.LongContext,
76+
AskUserVariant = AskUserVariant.Elicitation,
7677
ConfigDirectory = "/config",
7778
AvailableTools = ["tool1", "tool2"],
7879
ExcludedTools = ["tool3"],
@@ -121,6 +122,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
121122
Assert.Equal(original.ReasoningEffort, clone.ReasoningEffort);
122123
Assert.Equal(original.ReasoningSummary, clone.ReasoningSummary);
123124
Assert.Equal(original.ContextTier, clone.ContextTier);
125+
Assert.Equal(original.AskUserVariant, clone.AskUserVariant);
124126
Assert.Equal(original.ConfigDirectory, clone.ConfigDirectory);
125127
Assert.Equal(original.AvailableTools, clone.AvailableTools);
126128
Assert.Equal(original.ExcludedTools, clone.ExcludedTools);

go/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec
224224
- `EnableSessionStore` (\*bool): Enables the cross-session store for search and retrieval across sessions. When unset in `ModeCopilotCli`, the runtime default applies (enabled). In `ModeEmpty`, defaults to disabled.
225225
- `GitHubTokenProvider` (GitHubTokenProvider): Acquires session-scoped GitHub tokens on demand. Return `GitHubTokenResult` with a positive `ExpiresIn` value (production GitHub tokens typically use `8 * 60 * 60` seconds), or `GitHubTokenCancelled`. Cannot be combined with `GitHubToken`.
226226
- `OnPermissionRequest` (PermissionHandlerFunc): Optional handler called before each tool execution to approve or deny it. When nil, permission requests are emitted as events and left pending for manual resolution. `copilot.PermissionHandler.ApproveAll` approves requests when managed settings are disabled and returns an error when `EnableManagedSettings` is true. Custom handlers can inspect `RequiresManagedApproval()` for human-facing confirmation logic. See [Permission Handling](#permission-handling) section.
227-
- `OnUserInputRequest` (UserInputHandler): Handler for user input requests from the agent (enables ask_user tool). See [User Input Requests](#user-input-requests) section.
227+
- `OnUserInputRequest` (UserInputHandler): Handler for legacy question-and-answer requests from the agent. Enables the legacy `ask_user` tool. See [User Input Requests](#user-input-requests) section.
228+
- `AskUserVariant` (AskUserVariant): Selects the model-facing shape of the `ask_user` tool. The zero value preserves legacy behavior; use `AskUserVariantElicitation` with `OnElicitationRequest`.
228229
- `Hooks` (\*SessionHooks): Hook handlers for session lifecycle events. See [Session Hooks](#session-hooks) section.
229230
- `Commands` ([]CommandDefinition): Slash-commands registered for this session. See [Commands](#commands) section.
230231
- `OnElicitationRequest` (ElicitationHandler): Handler for elicitation requests from the server. See [Elicitation Requests](#elicitation-requests-serverclient) section.
@@ -238,6 +239,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec
238239
- `Streaming` (*bool): Enable streaming delta events (nil = runtime default)
239240
- `Commands` ([]CommandDefinition): Slash-commands. See [Commands](#commands) section.
240241
- `OnElicitationRequest` (ElicitationHandler): Elicitation handler. See [Elicitation Requests](#elicitation-requests-serverclient) section.
242+
- `AskUserVariant` (AskUserVariant): Selects the model-facing shape of the `ask_user` tool on cold resume. Re-supply `AskUserVariantElicitation` with `OnElicitationRequest`; the zero value preserves legacy behavior.
241243
- `GitHubTokenProvider` (GitHubTokenProvider): Replaces the session-scoped token provider when resuming. Cannot be combined with `GitHubToken`.
242244

243245
```go
@@ -770,7 +772,7 @@ To let a specific custom tool bypass the permission prompt entirely, set `SkipPe
770772

771773
## User Input Requests
772774

773-
Enable the agent to ask questions to the user using the `ask_user` tool by providing an `OnUserInputRequest` handler:
775+
Enable the legacy question-and-answer `ask_user` tool by providing an `OnUserInputRequest` handler:
774776

775777
```go
776778
session, err := client.CreateSession(context.Background(), &copilot.SessionConfig{

go/client.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,13 +788,23 @@ func hasManagedSettings(enableManagedSettings *bool, managedSettings *ManagedSet
788788
return (enableManagedSettings != nil && *enableManagedSettings) || managedSettings != nil
789789
}
790790

791+
func validateAskUserVariant(variant AskUserVariant) error {
792+
if variant != "" && variant != AskUserVariantLegacy && variant != AskUserVariantElicitation {
793+
return fmt.Errorf("invalid AskUserVariant %q: expected %q, %q, or unset", variant, AskUserVariantLegacy, AskUserVariantElicitation)
794+
}
795+
return nil
796+
}
797+
791798
func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Session, error) {
792799
if config == nil {
793800
config = &SessionConfig{}
794801
}
795802
if config.GitHubToken != "" && config.GitHubTokenProvider != nil {
796803
return nil, fmt.Errorf("GitHubToken and GitHubTokenProvider cannot be used together")
797804
}
805+
if err := validateAskUserVariant(config.AskUserVariant); err != nil {
806+
return nil, err
807+
}
798808

799809
if err := c.ensureConnected(ctx); err != nil {
800810
return nil, err
@@ -842,6 +852,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
842852
req.Capi = config.Capi
843853
req.Providers = config.Providers
844854
req.Models = config.Models
855+
req.AskUserVariant = config.AskUserVariant
845856
req.EnableSessionTelemetry = config.EnableSessionTelemetry
846857
req.EnableCitations = config.EnableCitations
847858
req.EnableFileChangeTracking = config.EnableFileChangeTracking
@@ -1170,6 +1181,9 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
11701181
if config.GitHubToken != "" && config.GitHubTokenProvider != nil {
11711182
return nil, fmt.Errorf("GitHubToken and GitHubTokenProvider cannot be used together")
11721183
}
1184+
if err := validateAskUserVariant(config.AskUserVariant); err != nil {
1185+
return nil, err
1186+
}
11731187

11741188
if err := c.ensureConnected(ctx); err != nil {
11751189
return nil, err
@@ -1200,6 +1214,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
12001214
req.Capi = config.Capi
12011215
req.Providers = config.Providers
12021216
req.Models = config.Models
1217+
req.AskUserVariant = config.AskUserVariant
12031218
req.EnableSessionTelemetry = config.EnableSessionTelemetry
12041219
req.IsExperimentalMode = config.EnableExperimentalMode
12051220
req.SkipCustomInstructions = config.SkipCustomInstructions

0 commit comments

Comments
 (0)