Skip to content
Merged
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 @@ -1161,6 +1162,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 @@ -2359,6 +2361,7 @@ internal record CreateSessionRequest(
IList<string>? AvailableTools,
IList<string>? ExcludedTools,
ProviderConfig? Provider,
CapiSessionOptions? Capi,
bool? EnableSessionTelemetry,
bool? RequestPermission,
bool? RequestUserInput,
Expand Down Expand Up @@ -2451,6 +2454,7 @@ internal record ResumeSessionRequest(
IList<string>? AvailableTools,
IList<string>? ExcludedTools,
ProviderConfig? Provider,
CapiSessionOptions? Capi,
bool? EnableSessionTelemetry,
bool? RequestPermission,
bool? RequestUserInput,
Expand Down Expand Up @@ -2577,6 +2581,7 @@ internal record HooksInvokeResponse(
[JsonSerializable(typeof(EmbeddingCacheStorageMode))]
[JsonSerializable(typeof(ModelCapabilitiesOverride))]
[JsonSerializable(typeof(ProviderConfig))]
[JsonSerializable(typeof(CapiSessionOptions))]
[JsonSerializable(typeof(NamedProviderConfig))]
[JsonSerializable(typeof(ProviderModelConfig))]
[JsonSerializable(typeof(ResumeSessionRequest))]
Expand Down
37 changes: 37 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
Comment thread
stephentoub marked this conversation as resolved.
/// <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,27 @@ public sealed class ProviderConfig
public int? MaxOutputTokens { get; set; }
}

/// <summary>
/// Provider-scoped options for the Copilot API (CAPI) provider.
/// </summary>
public sealed class CapiSessionOptions
Comment thread
dereklegenzoff marked this conversation as resolved.
{
/// <summary>
/// When <see langword="false"/>, forces the HTTP Responses transport for the CAPI Responses API
/// instead of the default WebSocket transport.
/// </summary>
/// <remarks>
/// WebSocket transport is the default for CAPI Responses API requests when the model advertises
/// the <c>ws:/responses</c> endpoint. Set this to <see langword="false"/> for users behind proxies
/// where WebSockets fail. Setting it to <see langword="false"/> 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("enableWebSocketResponses")]
public bool? EnableWebSocketResponses { get; set; }
}

/// <summary>
/// Azure OpenAI-specific provider options.
/// </summary>
Expand Down Expand Up @@ -2623,6 +2653,7 @@ protected SessionConfigBase(SessionConfigBase? other)
OnPermissionRequest = other.OnPermissionRequest;
OnUserInputRequest = other.OnUserInputRequest;
Provider = other.Provider;
Capi = other.Capi;
Providers = other.Providers is not null ? [.. other.Providers] : null;
Models = other.Models is not null ? [.. other.Models] : null;
EnableSessionTelemetry = other.EnableSessionTelemetry;
Expand Down Expand Up @@ -2780,6 +2811,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>
/// Named BYOK provider connections (transport + credentials). Additive to Copilot
/// API authentication (unlike <see cref="Provider"/>); combine with <see cref="Models"/>.
Expand Down Expand Up @@ -3700,6 +3736,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 { EnableWebSocketResponses = false },
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 { EnableWebSocketResponses = false },
};

var clone = original.Clone();

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

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

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_CanSerializeEnableWebSocketResponses_WithSdkOptions()
{
var options = GetSerializerOptions();
var original = new CapiSessionOptions
{
EnableWebSocketResponses = false
};

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

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

[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 { EnableWebSocketResponses = false };

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.False(createDocument.RootElement.GetProperty("capi").GetProperty("enableWebSocketResponses").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.False(resumeDocument.RootElement.GetProperty("capi").GetProperty("enableWebSocketResponses").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.Providers = config.Providers
req.Models = config.Models
req.EnableSessionTelemetry = config.EnableSessionTelemetry
Expand Down Expand Up @@ -978,6 +979,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.Providers = config.Providers
req.Models = config.Models
req.EnableSessionTelemetry = config.EnableSessionTelemetry
Expand Down
Loading
Loading