Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
toolFilter.AvailableTools,
toolFilter.ExcludedTools,
config.Provider,
config.Capi,
config.EnableSessionTelemetry,
config.OnPermissionRequest != null ? true : null,
config.OnUserInputRequest != null ? true : null,
Expand Down Expand Up @@ -1159,6 +1160,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
toolFilter.AvailableTools,
toolFilter.ExcludedTools,
config.Provider,
config.Capi,
config.EnableSessionTelemetry,
config.OnPermissionRequest != null ? true : null,
config.OnUserInputRequest != null ? true : null,
Expand Down Expand Up @@ -2355,6 +2357,7 @@ internal record CreateSessionRequest(
IList<string>? AvailableTools,
IList<string>? ExcludedTools,
ProviderConfig? Provider,
CapiSessionOptions? Capi,
bool? EnableSessionTelemetry,
bool? RequestPermission,
bool? RequestUserInput,
Expand Down Expand Up @@ -2445,6 +2448,7 @@ internal record ResumeSessionRequest(
IList<string>? AvailableTools,
IList<string>? ExcludedTools,
ProviderConfig? Provider,
CapiSessionOptions? Capi,
bool? EnableSessionTelemetry,
bool? RequestPermission,
bool? RequestUserInput,
Expand Down Expand Up @@ -2569,6 +2573,7 @@ internal record HooksInvokeResponse(
[JsonSerializable(typeof(EmbeddingCacheStorageMode))]
[JsonSerializable(typeof(ModelCapabilitiesOverride))]
[JsonSerializable(typeof(ProviderConfig))]
[JsonSerializable(typeof(CapiSessionOptions))]
[JsonSerializable(typeof(ResumeSessionRequest))]
[JsonSerializable(typeof(ResumeSessionResponse))]
[JsonSerializable(typeof(SessionCapabilities))]
Expand Down
36 changes: 36 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1991,6 +1991,15 @@ public sealed class ProviderConfig
[JsonPropertyName("wireApi")]
public string? WireApi { get; set; }

/// <summary>
/// Transport for OpenAI Responses requests ("http" or "websockets"). Defaults to "http".
/// Set to "websockets" to deliver Responses API requests over a persistent WebSocket
/// connection instead of HTTP. Applies to OpenAI-compatible providers using
/// <c>wireApi: "responses"</c>.
/// </summary>
[JsonPropertyName("transport")]
public string? Transport { get; set; }

/// <summary>
/// Base URL of the provider's API endpoint.
/// </summary>
Expand Down Expand Up @@ -2058,6 +2067,26 @@ public sealed class ProviderConfig
public int? MaxOutputTokens { get; set; }
}

/// <summary>
/// Provider-scoped options for the CAPI (Copilot API) provider.
/// </summary>
public sealed class CapiSessionOptions
{
/// <summary>
/// When <see langword="true"/>, opts out of the WebSocket transport for the CAPI Responses API
/// and uses the HTTP Responses transport instead.
/// </summary>
/// <remarks>
/// WebSocket transport is the default for CAPI Responses API requests when the model advertises
/// the <c>ws:/responses</c> endpoint. Set this option for users behind proxies where WebSockets
/// fail. This is equivalent to setting the <c>COPILOT_CLI_DISABLE_WEBSOCKET_RESPONSES</c>
/// environment variable. The option is scoped under the <c>capi</c> namespace because a single
/// session can host multiple providers, such as CAPI and BYOK, so transport choice is provider-level.
/// </remarks>
[JsonPropertyName("disableWebSocketResponses")]
public bool? DisableWebSocketResponses { get; set; }
}

/// <summary>
/// Azure OpenAI-specific provider options.
/// </summary>
Expand Down Expand Up @@ -2494,6 +2523,7 @@ protected SessionConfigBase(SessionConfigBase? other)
OnPermissionRequest = other.OnPermissionRequest;
OnUserInputRequest = other.OnUserInputRequest;
Provider = other.Provider;
Capi = other.Capi;
EnableSessionTelemetry = other.EnableSessionTelemetry;
SkipCustomInstructions = other.SkipCustomInstructions;
CustomAgentsLocalOnly = other.CustomAgentsLocalOnly;
Expand Down Expand Up @@ -2649,6 +2679,11 @@ protected SessionConfigBase(SessionConfigBase? other)
/// <summary>Custom model provider configuration for the session.</summary>
public ProviderConfig? Provider { get; set; }

/// <summary>
/// CAPI (Copilot API) provider-scoped configuration for the session.
/// </summary>
public CapiSessionOptions? Capi { get; set; }

/// <summary>
/// Enables or disables internal session telemetry for this session.
/// When <c>false</c>, disables session telemetry. When <c>null</c> (the default) or <c>true</c>,
Expand Down Expand Up @@ -3554,6 +3589,7 @@ public sealed class SystemMessageTransformRpcResponse
[JsonSerializable(typeof(PingRequest))]
[JsonSerializable(typeof(PingResponse))]
[JsonSerializable(typeof(ProviderConfig))]
[JsonSerializable(typeof(CapiSessionOptions))]
[JsonSerializable(typeof(SessionContext))]
[JsonSerializable(typeof(SessionLifecycleEvent))]
[JsonSerializable(typeof(SessionLifecycleEventMetadata))]
Expand Down
28 changes: 28 additions & 0 deletions dotnet/test/Unit/CloneTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
McpOAuthTokenStorage = McpOAuthTokenStorageMode.Persistent,
CustomAgents = [new CustomAgentConfig { Name = "agent1", Model = "claude-haiku-4.5" }],
Agent = "agent1",
Capi = new CapiSessionOptions { DisableWebSocketResponses = true },
Cloud = new CloudSessionOptions
{
Repository = new CloudSessionRepository
Expand Down Expand Up @@ -123,6 +124,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
Assert.Equal(original.CustomAgents.Count, clone.CustomAgents!.Count);
Assert.Equal(original.CustomAgents[0].Model, clone.CustomAgents[0].Model);
Assert.Equal(original.Agent, clone.Agent);
Assert.Same(original.Capi, clone.Capi);
Assert.Same(original.Cloud, clone.Cloud);
Assert.Equal(original.DefaultAgent!.ExcludedTools, clone.DefaultAgent!.ExcludedTools);
Assert.Equal(original.SkillDirectories, clone.SkillDirectories);
Expand Down Expand Up @@ -515,4 +517,30 @@ public void ResumeSessionConfig_Clone_CopiesMcpOAuthTokenStorage()

Assert.Equal(McpOAuthTokenStorageMode.Persistent, clone.McpOAuthTokenStorage);
}

[Fact]
public void SessionConfig_Clone_CopiesCapiOptions()
{
var original = new SessionConfig
{
Capi = new CapiSessionOptions { DisableWebSocketResponses = true },
};

var clone = original.Clone();

Assert.Same(original.Capi, clone.Capi);
}

[Fact]
public void ResumeSessionConfig_Clone_CopiesCapiOptions()
{
var original = new ResumeSessionConfig
{
Capi = new CapiSessionOptions { DisableWebSocketResponses = true },
};

var clone = original.Clone();

Assert.Same(original.Capi, clone.Capi);
}
}
75 changes: 74 additions & 1 deletion dotnet/test/Unit/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public void ProviderConfig_CanSerializeHeaders_WithSdkOptions()
ModelId = "gpt-4o",
WireModel = "my-finetune-v3",
MaxPromptTokens = 100_000,
MaxOutputTokens = 4096
MaxOutputTokens = 4096,
Transport = "websockets"
};

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

var deserialized = JsonSerializer.Deserialize<ProviderConfig>(json, options);
Assert.NotNull(deserialized);
Expand All @@ -48,6 +50,26 @@ public void ProviderConfig_CanSerializeHeaders_WithSdkOptions()
Assert.Equal("my-finetune-v3", deserialized.WireModel);
Assert.Equal(100_000, deserialized.MaxPromptTokens);
Assert.Equal(4096, deserialized.MaxOutputTokens);
Assert.Equal("websockets", deserialized.Transport);
}

[Fact]
public void CapiSessionOptions_CanSerializeDisableWebSocketResponses_WithSdkOptions()
{
var options = GetSerializerOptions();
var original = new CapiSessionOptions
{
DisableWebSocketResponses = true
};

var json = JsonSerializer.Serialize(original, options);
using var document = JsonDocument.Parse(json);
var root = document.RootElement;
Assert.True(root.GetProperty("disableWebSocketResponses").GetBoolean());

var deserialized = JsonSerializer.Deserialize<CapiSessionOptions>(json, options);
Assert.NotNull(deserialized);
Assert.True(deserialized.DisableWebSocketResponses);
}

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

[Fact]
public void SessionRequests_CanSerializeCapiOptions_WithSdkOptions()
{
var options = GetSerializerOptions();
var capi = new CapiSessionOptions { DisableWebSocketResponses = true };

var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
var createRequest = CreateInternalRequest(
createRequestType,
("SessionId", "session-id"),
("Capi", capi));

var createJson = JsonSerializer.Serialize(createRequest, createRequestType, options);
using var createDocument = JsonDocument.Parse(createJson);
Assert.True(createDocument.RootElement.GetProperty("capi").GetProperty("disableWebSocketResponses").GetBoolean());

var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
var resumeRequest = CreateInternalRequest(
resumeRequestType,
("SessionId", "session-id"),
("Capi", capi));

var resumeJson = JsonSerializer.Serialize(resumeRequest, resumeRequestType, options);
using var resumeDocument = JsonDocument.Parse(resumeJson);
Assert.True(resumeDocument.RootElement.GetProperty("capi").GetProperty("disableWebSocketResponses").GetBoolean());
}

[Fact]
public void SessionRequests_OmitCapiOptions_WhenUnset()
{
var options = GetSerializerOptions();

var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
var createRequest = CreateInternalRequest(
createRequestType,
("SessionId", "session-id"));

var createJson = JsonSerializer.Serialize(createRequest, createRequestType, options);
using var createDocument = JsonDocument.Parse(createJson);
Assert.False(createDocument.RootElement.TryGetProperty("capi", out _));

var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
var resumeRequest = CreateInternalRequest(
resumeRequestType,
("SessionId", "session-id"));

var resumeJson = JsonSerializer.Serialize(resumeRequest, resumeRequestType, options);
using var resumeDocument = JsonDocument.Parse(resumeJson);
Assert.False(resumeDocument.RootElement.TryGetProperty("capi", out _));
}

[Fact]
public void SessionRequests_CanSerializeReasoningSummary_WithSdkOptions()
{
Expand Down
2 changes: 2 additions & 0 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
req.ExcludedTools = excludedTools
req.ToolFilterPrecedence = precedence
req.Provider = config.Provider
req.Capi = config.Capi
req.EnableSessionTelemetry = config.EnableSessionTelemetry
req.SkipCustomInstructions = config.SkipCustomInstructions
req.CustomAgentsLocalOnly = config.CustomAgentsLocalOnly
Expand Down Expand Up @@ -976,6 +977,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
req.SystemMessage = wireSystemMessage
req.Tools = config.Tools
req.Provider = config.Provider
req.Capi = config.Capi
req.EnableSessionTelemetry = config.EnableSessionTelemetry
req.SkipCustomInstructions = config.SkipCustomInstructions
req.CustomAgentsLocalOnly = config.CustomAgentsLocalOnly
Expand Down
Loading
Loading