Skip to content

Commit a3dbdd1

Browse files
Add capi.enableWebSocketResponses and provider.transport session options (#1711)
* Add capi.disableWebSocketResponses session option Add the capi.disableWebSocketResponses opt-out to session create/resume across all six SDK languages, so consumers in proxy/WebSocket-blocked environments can fall back to the HTTP Responses transport for the CAPI Responses API. SDK-side follow-up to github/copilot-agent-runtime#10551, which makes WebSocket transport the default for CAPI and adds this opt-out. The field is a hand-written pass-through mirroring the existing provider (BYOK) nested option, wired into session.create and session.resume. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add provider.transport BYOK option Add the BYOK provider `transport` field ("http" | "websockets", default "http") to the hand-written ProviderConfig across all six SDK languages, so BYOK OpenAI-compatible providers can opt into delivering Responses API requests over a persistent WebSocket connection instead of HTTP. SDK-side follow-up to github/copilot-agent-runtime#9557, which adds the runtime `transport` option. The SDK's consumer-facing ProviderConfig is hand-written (not generated from the schema), so the field is added as a pass-through mirroring the existing `wireApi` field, flowing through the already-wired `provider` option on session create and resume. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Lead with spelled-out 'Copilot API (CAPI)' on first mention Update the CapiSessionOptions type-level doc summary in each language to lead with the spelled-out 'Copilot API (CAPI)' form on first use, matching the existing docs convention (and the Node SDK, which already did this). Python's class docstring previously never expanded the acronym. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Invert capi flag to enableWebSocketResponses (default true) Rename the hand-written capi session option from disableWebSocketResponses (default false) to enableWebSocketResponses (default true) across all six language SDKs, matching the inverted shape in runtime PR github/copilot-agent-runtime#10551. Setting it to false forces the HTTP Responses transport, equivalent to the unchanged COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES environment variable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d15cfcb commit a3dbdd1

27 files changed

Lines changed: 992 additions & 4 deletions

dotnet/src/Client.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
963963
toolFilter.AvailableTools,
964964
toolFilter.ExcludedTools,
965965
config.Provider,
966+
config.Capi,
966967
config.EnableSessionTelemetry,
967968
config.OnPermissionRequest != null ? true : null,
968969
config.OnUserInputRequest != null ? true : null,
@@ -1161,6 +1162,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
11611162
toolFilter.AvailableTools,
11621163
toolFilter.ExcludedTools,
11631164
config.Provider,
1165+
config.Capi,
11641166
config.EnableSessionTelemetry,
11651167
config.OnPermissionRequest != null ? true : null,
11661168
config.OnUserInputRequest != null ? true : null,
@@ -2359,6 +2361,7 @@ internal record CreateSessionRequest(
23592361
IList<string>? AvailableTools,
23602362
IList<string>? ExcludedTools,
23612363
ProviderConfig? Provider,
2364+
CapiSessionOptions? Capi,
23622365
bool? EnableSessionTelemetry,
23632366
bool? RequestPermission,
23642367
bool? RequestUserInput,
@@ -2451,6 +2454,7 @@ internal record ResumeSessionRequest(
24512454
IList<string>? AvailableTools,
24522455
IList<string>? ExcludedTools,
24532456
ProviderConfig? Provider,
2457+
CapiSessionOptions? Capi,
24542458
bool? EnableSessionTelemetry,
24552459
bool? RequestPermission,
24562460
bool? RequestUserInput,
@@ -2577,6 +2581,7 @@ internal record HooksInvokeResponse(
25772581
[JsonSerializable(typeof(EmbeddingCacheStorageMode))]
25782582
[JsonSerializable(typeof(ModelCapabilitiesOverride))]
25792583
[JsonSerializable(typeof(ProviderConfig))]
2584+
[JsonSerializable(typeof(CapiSessionOptions))]
25802585
[JsonSerializable(typeof(NamedProviderConfig))]
25812586
[JsonSerializable(typeof(ProviderModelConfig))]
25822587
[JsonSerializable(typeof(ResumeSessionRequest))]

dotnet/src/Types.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,15 @@ public sealed class ProviderConfig
19911991
[JsonPropertyName("wireApi")]
19921992
public string? WireApi { get; set; }
19931993

1994+
/// <summary>
1995+
/// Transport for OpenAI Responses requests ("http" or "websockets"). Defaults to "http".
1996+
/// Set to "websockets" to deliver Responses API requests over a persistent WebSocket
1997+
/// connection instead of HTTP. Applies to OpenAI-compatible providers using
1998+
/// <c>wireApi: "responses"</c>.
1999+
/// </summary>
2000+
[JsonPropertyName("transport")]
2001+
public string? Transport { get; set; }
2002+
19942003
/// <summary>
19952004
/// Base URL of the provider's API endpoint.
19962005
/// </summary>
@@ -2058,6 +2067,27 @@ public sealed class ProviderConfig
20582067
public int? MaxOutputTokens { get; set; }
20592068
}
20602069

2070+
/// <summary>
2071+
/// Provider-scoped options for the Copilot API (CAPI) provider.
2072+
/// </summary>
2073+
public sealed class CapiSessionOptions
2074+
{
2075+
/// <summary>
2076+
/// When <see langword="false"/>, forces the HTTP Responses transport for the CAPI Responses API
2077+
/// instead of the default WebSocket transport.
2078+
/// </summary>
2079+
/// <remarks>
2080+
/// WebSocket transport is the default for CAPI Responses API requests when the model advertises
2081+
/// the <c>ws:/responses</c> endpoint. Set this to <see langword="false"/> for users behind proxies
2082+
/// where WebSockets fail. Setting it to <see langword="false"/> is equivalent to setting the
2083+
/// <c>COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES</c> environment variable. The option is scoped under
2084+
/// the <c>capi</c> namespace because a single session can host multiple providers, such as CAPI and
2085+
/// BYOK, so transport choice is provider-level.
2086+
/// </remarks>
2087+
[JsonPropertyName("enableWebSocketResponses")]
2088+
public bool? EnableWebSocketResponses { get; set; }
2089+
}
2090+
20612091
/// <summary>
20622092
/// Azure OpenAI-specific provider options.
20632093
/// </summary>
@@ -2623,6 +2653,7 @@ protected SessionConfigBase(SessionConfigBase? other)
26232653
OnPermissionRequest = other.OnPermissionRequest;
26242654
OnUserInputRequest = other.OnUserInputRequest;
26252655
Provider = other.Provider;
2656+
Capi = other.Capi;
26262657
Providers = other.Providers is not null ? [.. other.Providers] : null;
26272658
Models = other.Models is not null ? [.. other.Models] : null;
26282659
EnableSessionTelemetry = other.EnableSessionTelemetry;
@@ -2780,6 +2811,11 @@ protected SessionConfigBase(SessionConfigBase? other)
27802811
/// <summary>Custom model provider configuration for the session.</summary>
27812812
public ProviderConfig? Provider { get; set; }
27822813

2814+
/// <summary>
2815+
/// CAPI (Copilot API) provider-scoped configuration for the session.
2816+
/// </summary>
2817+
public CapiSessionOptions? Capi { get; set; }
2818+
27832819
/// <summary>
27842820
/// Named BYOK provider connections (transport + credentials). Additive to Copilot
27852821
/// API authentication (unlike <see cref="Provider"/>); combine with <see cref="Models"/>.
@@ -3700,6 +3736,7 @@ public sealed class SystemMessageTransformRpcResponse
37003736
[JsonSerializable(typeof(PingRequest))]
37013737
[JsonSerializable(typeof(PingResponse))]
37023738
[JsonSerializable(typeof(ProviderConfig))]
3739+
[JsonSerializable(typeof(CapiSessionOptions))]
37033740
[JsonSerializable(typeof(SessionContext))]
37043741
[JsonSerializable(typeof(SessionLifecycleEvent))]
37053742
[JsonSerializable(typeof(SessionLifecycleEventMetadata))]

dotnet/test/Unit/CloneTests.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
8282
McpOAuthTokenStorage = McpOAuthTokenStorageMode.Persistent,
8383
CustomAgents = [new CustomAgentConfig { Name = "agent1", Model = "claude-haiku-4.5" }],
8484
Agent = "agent1",
85+
Capi = new CapiSessionOptions { EnableWebSocketResponses = false },
8586
Cloud = new CloudSessionOptions
8687
{
8788
Repository = new CloudSessionRepository
@@ -123,6 +124,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
123124
Assert.Equal(original.CustomAgents.Count, clone.CustomAgents!.Count);
124125
Assert.Equal(original.CustomAgents[0].Model, clone.CustomAgents[0].Model);
125126
Assert.Equal(original.Agent, clone.Agent);
127+
Assert.Same(original.Capi, clone.Capi);
126128
Assert.Same(original.Cloud, clone.Cloud);
127129
Assert.Equal(original.DefaultAgent!.ExcludedTools, clone.DefaultAgent!.ExcludedTools);
128130
Assert.Equal(original.SkillDirectories, clone.SkillDirectories);
@@ -515,4 +517,30 @@ public void ResumeSessionConfig_Clone_CopiesMcpOAuthTokenStorage()
515517

516518
Assert.Equal(McpOAuthTokenStorageMode.Persistent, clone.McpOAuthTokenStorage);
517519
}
520+
521+
[Fact]
522+
public void SessionConfig_Clone_CopiesCapiOptions()
523+
{
524+
var original = new SessionConfig
525+
{
526+
Capi = new CapiSessionOptions { EnableWebSocketResponses = false },
527+
};
528+
529+
var clone = original.Clone();
530+
531+
Assert.Same(original.Capi, clone.Capi);
532+
}
533+
534+
[Fact]
535+
public void ResumeSessionConfig_Clone_CopiesCapiOptions()
536+
{
537+
var original = new ResumeSessionConfig
538+
{
539+
Capi = new CapiSessionOptions { EnableWebSocketResponses = false },
540+
};
541+
542+
var clone = original.Clone();
543+
544+
Assert.Same(original.Capi, clone.Capi);
545+
}
518546
}

dotnet/test/Unit/SerializationTests.cs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ public void ProviderConfig_CanSerializeHeaders_WithSdkOptions()
2727
ModelId = "gpt-4o",
2828
WireModel = "my-finetune-v3",
2929
MaxPromptTokens = 100_000,
30-
MaxOutputTokens = 4096
30+
MaxOutputTokens = 4096,
31+
Transport = "websockets"
3132
};
3233

3334
var json = JsonSerializer.Serialize(original, options);
@@ -39,6 +40,7 @@ public void ProviderConfig_CanSerializeHeaders_WithSdkOptions()
3940
Assert.Equal("my-finetune-v3", root.GetProperty("wireModel").GetString());
4041
Assert.Equal(100_000, root.GetProperty("maxPromptTokens").GetInt32());
4142
Assert.Equal(4096, root.GetProperty("maxOutputTokens").GetInt32());
43+
Assert.Equal("websockets", root.GetProperty("transport").GetString());
4244

4345
var deserialized = JsonSerializer.Deserialize<ProviderConfig>(json, options);
4446
Assert.NotNull(deserialized);
@@ -48,6 +50,26 @@ public void ProviderConfig_CanSerializeHeaders_WithSdkOptions()
4850
Assert.Equal("my-finetune-v3", deserialized.WireModel);
4951
Assert.Equal(100_000, deserialized.MaxPromptTokens);
5052
Assert.Equal(4096, deserialized.MaxOutputTokens);
53+
Assert.Equal("websockets", deserialized.Transport);
54+
}
55+
56+
[Fact]
57+
public void CapiSessionOptions_CanSerializeEnableWebSocketResponses_WithSdkOptions()
58+
{
59+
var options = GetSerializerOptions();
60+
var original = new CapiSessionOptions
61+
{
62+
EnableWebSocketResponses = false
63+
};
64+
65+
var json = JsonSerializer.Serialize(original, options);
66+
using var document = JsonDocument.Parse(json);
67+
var root = document.RootElement;
68+
Assert.False(root.GetProperty("enableWebSocketResponses").GetBoolean());
69+
70+
var deserialized = JsonSerializer.Deserialize<CapiSessionOptions>(json, options);
71+
Assert.NotNull(deserialized);
72+
Assert.False(deserialized.EnableWebSocketResponses);
5173
}
5274

5375
[Fact]
@@ -221,6 +243,57 @@ public void ResumeSessionRequest_CanSerializeInstructionDirectories_WithSdkOptio
221243
Assert.Equal("C:\\resume-instructions", root.GetProperty("instructionDirectories")[0].GetString());
222244
}
223245

246+
[Fact]
247+
public void SessionRequests_CanSerializeCapiOptions_WithSdkOptions()
248+
{
249+
var options = GetSerializerOptions();
250+
var capi = new CapiSessionOptions { EnableWebSocketResponses = false };
251+
252+
var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
253+
var createRequest = CreateInternalRequest(
254+
createRequestType,
255+
("SessionId", "session-id"),
256+
("Capi", capi));
257+
258+
var createJson = JsonSerializer.Serialize(createRequest, createRequestType, options);
259+
using var createDocument = JsonDocument.Parse(createJson);
260+
Assert.False(createDocument.RootElement.GetProperty("capi").GetProperty("enableWebSocketResponses").GetBoolean());
261+
262+
var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
263+
var resumeRequest = CreateInternalRequest(
264+
resumeRequestType,
265+
("SessionId", "session-id"),
266+
("Capi", capi));
267+
268+
var resumeJson = JsonSerializer.Serialize(resumeRequest, resumeRequestType, options);
269+
using var resumeDocument = JsonDocument.Parse(resumeJson);
270+
Assert.False(resumeDocument.RootElement.GetProperty("capi").GetProperty("enableWebSocketResponses").GetBoolean());
271+
}
272+
273+
[Fact]
274+
public void SessionRequests_OmitCapiOptions_WhenUnset()
275+
{
276+
var options = GetSerializerOptions();
277+
278+
var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
279+
var createRequest = CreateInternalRequest(
280+
createRequestType,
281+
("SessionId", "session-id"));
282+
283+
var createJson = JsonSerializer.Serialize(createRequest, createRequestType, options);
284+
using var createDocument = JsonDocument.Parse(createJson);
285+
Assert.False(createDocument.RootElement.TryGetProperty("capi", out _));
286+
287+
var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
288+
var resumeRequest = CreateInternalRequest(
289+
resumeRequestType,
290+
("SessionId", "session-id"));
291+
292+
var resumeJson = JsonSerializer.Serialize(resumeRequest, resumeRequestType, options);
293+
using var resumeDocument = JsonDocument.Parse(resumeJson);
294+
Assert.False(resumeDocument.RootElement.TryGetProperty("capi", out _));
295+
}
296+
224297
[Fact]
225298
public void SessionRequests_CanSerializeReasoningSummary_WithSdkOptions()
226299
{

go/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
681681
req.ExcludedTools = excludedTools
682682
req.ToolFilterPrecedence = precedence
683683
req.Provider = config.Provider
684+
req.Capi = config.Capi
684685
req.Providers = config.Providers
685686
req.Models = config.Models
686687
req.EnableSessionTelemetry = config.EnableSessionTelemetry
@@ -978,6 +979,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
978979
req.SystemMessage = wireSystemMessage
979980
req.Tools = config.Tools
980981
req.Provider = config.Provider
982+
req.Capi = config.Capi
981983
req.Providers = config.Providers
982984
req.Models = config.Models
983985
req.EnableSessionTelemetry = config.EnableSessionTelemetry

0 commit comments

Comments
 (0)