From 99512de1c3f69bbd5b5513e9eceae277ef459633 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 12 Apr 2026 13:45:57 -0400 Subject: [PATCH 1/6] Enhance C# generator with richer typing and data annotations - integer -> long, number -> double (was double for both) - format: date-time -> DateTimeOffset, uuid -> Guid, duration -> TimeSpan - Add MillisecondsTimeSpanConverter for TimeSpan JSON serialization - Emit [Range], [RegularExpression], [Url], [MinLength], [MaxLength] - Emit [StringSyntax(Uri)], [StringSyntax(Regex)], [Base64String] - Change all public collections from concrete to interface types (List -> IList, Dictionary -> IDictionary) - Lazy-initialize collection properties via field ??= pattern Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Client.cs | 42 +++---- dotnet/src/Generated/Rpc.cs | 45 ++++---- dotnet/src/Generated/SessionEvents.cs | 22 ++-- dotnet/src/MillisecondsTimeSpanConverter.cs | 22 ++++ dotnet/src/Session.cs | 4 +- dotnet/src/Types.cs | 76 ++++++------- dotnet/test/ClientTests.cs | 6 +- dotnet/test/ElicitationTests.cs | 2 +- dotnet/test/SessionTests.cs | 2 +- scripts/codegen/csharp.ts | 119 ++++++++++++++++++-- 10 files changed, 239 insertions(+), 101 deletions(-) create mode 100644 dotnet/src/MillisecondsTimeSpanConverter.cs diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 732c15447..29b49c294 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -75,7 +75,7 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable private int? _negotiatedProtocolVersion; private List? _modelsCache; private readonly SemaphoreSlim _modelsCacheLock = new(1, 1); - private readonly Func>>? _onListModels; + private readonly Func>>? _onListModels; private readonly List> _lifecycleHandlers = []; private readonly Dictionary>> _typedLifecycleHandlers = []; private readonly object _lifecycleHandlersLock = new(); @@ -735,7 +735,7 @@ public async Task GetAuthStatusAsync(CancellationToken ca /// The cache is cleared when the client disconnects. /// /// Thrown when the client is not connected or not authenticated. - public async Task> ListModelsAsync(CancellationToken cancellationToken = default) + public async Task> ListModelsAsync(CancellationToken cancellationToken = default) { await _modelsCacheLock.WaitAsync(cancellationToken); try @@ -746,7 +746,7 @@ public async Task> ListModelsAsync(CancellationToken cancellatio return [.. _modelsCache]; // Return a copy to prevent cache mutation } - List models; + IList models; if (_onListModels is not null) { // Use custom handler instead of CLI RPC @@ -847,7 +847,7 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell /// } /// /// - public async Task> ListSessionsAsync(SessionListFilter? filter = null, CancellationToken cancellationToken = default) + public async Task> ListSessionsAsync(SessionListFilter? filter = null, CancellationToken cancellationToken = default) { var connection = await EnsureConnectedAsync(cancellationToken); @@ -1467,7 +1467,7 @@ public void OnSessionLifecycle(string type, string sessionId, JsonElement? metad client.DispatchLifecycleEvent(evt); } - public async Task OnUserInputRequest(string sessionId, string question, List? choices = null, bool? allowFreeform = null) + public async Task OnUserInputRequest(string sessionId, string question, IList? choices = null, bool? allowFreeform = null) { var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}"); var request = new UserInputRequest @@ -1621,26 +1621,26 @@ internal record CreateSessionRequest( string? SessionId, string? ClientName, string? ReasoningEffort, - List? Tools, + IList? Tools, SystemMessageConfig? SystemMessage, - List? AvailableTools, - List? ExcludedTools, + IList? AvailableTools, + IList? ExcludedTools, ProviderConfig? Provider, bool? RequestPermission, bool? RequestUserInput, bool? Hooks, string? WorkingDirectory, bool? Streaming, - Dictionary? McpServers, + IDictionary? McpServers, string? EnvValueMode, - List? CustomAgents, + IList? CustomAgents, string? Agent, string? ConfigDir, bool? EnableConfigDiscovery, - List? SkillDirectories, - List? DisabledSkills, + IList? SkillDirectories, + IList? DisabledSkills, InfiniteSessionConfig? InfiniteSessions, - List? Commands = null, + IList? Commands = null, bool? RequestElicitation = null, string? Traceparent = null, string? Tracestate = null, @@ -1673,10 +1673,10 @@ internal record ResumeSessionRequest( string? ClientName, string? Model, string? ReasoningEffort, - List? Tools, + IList? Tools, SystemMessageConfig? SystemMessage, - List? AvailableTools, - List? ExcludedTools, + IList? AvailableTools, + IList? ExcludedTools, ProviderConfig? Provider, bool? RequestPermission, bool? RequestUserInput, @@ -1686,14 +1686,14 @@ internal record ResumeSessionRequest( bool? EnableConfigDiscovery, bool? DisableResume, bool? Streaming, - Dictionary? McpServers, + IDictionary? McpServers, string? EnvValueMode, - List? CustomAgents, + IList? CustomAgents, string? Agent, - List? SkillDirectories, - List? DisabledSkills, + IList? SkillDirectories, + IList? DisabledSkills, InfiniteSessionConfig? InfiniteSessions, - List? Commands = null, + IList? Commands = null, bool? RequestElicitation = null, string? Traceparent = null, string? Tracestate = null, diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index b06b68676..387702685 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -5,6 +5,7 @@ // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: api.schema.json +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; @@ -60,7 +61,7 @@ public class ModelCapabilitiesLimitsVision { /// MIME types the model accepts. [JsonPropertyName("supported_media_types")] - public List SupportedMediaTypes { get => field ??= []; set; } + public IList SupportedMediaTypes { get => field ??= []; set; } /// Maximum number of images per prompt. [JsonPropertyName("max_prompt_images")] @@ -148,7 +149,7 @@ public class Model /// Supported reasoning effort levels (only present if model supports reasoning effort). [JsonPropertyName("supportedReasoningEfforts")] - public List? SupportedReasoningEfforts { get; set; } + public IList? SupportedReasoningEfforts { get; set; } /// Default reasoning effort level (only present if model supports reasoning effort). [JsonPropertyName("defaultReasoningEffort")] @@ -160,7 +161,7 @@ public class ModelsListResult { /// List of available models with full metadata. [JsonPropertyName("models")] - public List Models { get => field ??= []; set; } + public IList Models { get => field ??= []; set; } } /// RPC data type for Tool operations. @@ -180,7 +181,7 @@ public class Tool /// JSON Schema for the tool's input parameters. [JsonPropertyName("parameters")] - public Dictionary? Parameters { get; set; } + public IDictionary? Parameters { get; set; } /// Optional instructions for how to use this tool effectively. [JsonPropertyName("instructions")] @@ -192,7 +193,7 @@ public class ToolsListResult { /// List of available built-in tools with metadata. [JsonPropertyName("tools")] - public List Tools { get => field ??= []; set; } + public IList Tools { get => field ??= []; set; } } /// RPC data type for ToolsList operations. @@ -236,7 +237,7 @@ public class AccountGetQuotaResult { /// Quota snapshots keyed by type (e.g., chat, completions, premium_interactions). [JsonPropertyName("quotaSnapshots")] - public Dictionary QuotaSnapshots { get => field ??= []; set; } + public IDictionary QuotaSnapshots { get => field ??= new Dictionary(); set; } } /// RPC data type for SessionFsSetProvider operations. @@ -313,6 +314,8 @@ internal class SessionLogRequest public bool? Ephemeral { get; set; } /// Optional URL the user can open in their browser for more details. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("url")] public string? Url { get; set; } } @@ -358,7 +361,7 @@ public class ModelCapabilitiesOverrideLimitsVision { /// MIME types the model accepts. [JsonPropertyName("supported_media_types")] - public List? SupportedMediaTypes { get; set; } + public IList? SupportedMediaTypes { get; set; } /// Maximum number of images per prompt. [JsonPropertyName("max_prompt_images")] @@ -516,7 +519,7 @@ public class SessionWorkspaceListFilesResult { /// Relative file paths in the workspace files directory. [JsonPropertyName("files")] - public List Files { get => field ??= []; set; } + public IList Files { get => field ??= []; set; } } /// RPC data type for SessionWorkspaceListFiles operations. @@ -612,7 +615,7 @@ public class SessionAgentListResult { /// Available custom agents. [JsonPropertyName("agents")] - public List Agents { get => field ??= []; set; } + public IList Agents { get => field ??= []; set; } } /// RPC data type for SessionAgentList operations. @@ -717,7 +720,7 @@ public class SessionAgentReloadResult { /// Reloaded custom agents. [JsonPropertyName("agents")] - public List Agents { get => field ??= []; set; } + public IList Agents { get => field ??= []; set; } } /// RPC data type for SessionAgentReload operations. @@ -763,7 +766,7 @@ public class SessionSkillsListResult { /// Available skills. [JsonPropertyName("skills")] - public List Skills { get => field ??= []; set; } + public IList Skills { get => field ??= []; set; } } /// RPC data type for SessionSkillsList operations. @@ -854,7 +857,7 @@ public class SessionMcpListResult { /// Configured MCP servers. [JsonPropertyName("servers")] - public List Servers { get => field ??= []; set; } + public IList Servers { get => field ??= []; set; } } /// RPC data type for SessionMcpList operations. @@ -945,7 +948,7 @@ public class SessionPluginsListResult { /// Installed plugins. [JsonPropertyName("plugins")] - public List Plugins { get => field ??= []; set; } + public IList Plugins { get => field ??= []; set; } } /// RPC data type for SessionPluginsList operations. @@ -978,7 +981,7 @@ public class Extension /// Process ID if the extension is running. [JsonPropertyName("pid")] - public double? Pid { get; set; } + public long? Pid { get; set; } } /// RPC data type for SessionExtensionsList operations. @@ -987,7 +990,7 @@ public class SessionExtensionsListResult { /// Discovered extensions and their current status. [JsonPropertyName("extensions")] - public List Extensions { get => field ??= []; set; } + public IList Extensions { get => field ??= []; set; } } /// RPC data type for SessionExtensionsList operations. @@ -1113,7 +1116,7 @@ public class SessionUiElicitationResult /// The form values submitted by the user (present when action is 'accept'). [JsonPropertyName("content")] - public Dictionary? Content { get; set; } + public IDictionary? Content { get; set; } } /// JSON Schema describing the form fields to present to the user. @@ -1125,11 +1128,11 @@ public class SessionUiElicitationRequestRequestedSchema /// Form field definitions, keyed by field name. [JsonPropertyName("properties")] - public Dictionary Properties { get => field ??= []; set; } + public IDictionary Properties { get => field ??= new Dictionary(); set; } /// List of required field names. [JsonPropertyName("required")] - public List? Required { get; set; } + public IList? Required { get; set; } } /// RPC data type for SessionUiElicitation operations. @@ -1165,7 +1168,7 @@ public class SessionUiHandlePendingElicitationRequestResult /// The form values submitted by the user (present when action is 'accept'). [JsonPropertyName("content")] - public Dictionary? Content { get; set; } + public IDictionary? Content { get; set; } } /// RPC data type for SessionUiHandlePendingElicitation operations. @@ -1449,7 +1452,7 @@ public class SessionFsReaddirResult { /// Entry names in the directory. [JsonPropertyName("entries")] - public List Entries { get => field ??= []; set; } + public IList Entries { get => field ??= []; set; } } /// RPC data type for SessionFsReaddir operations. @@ -1481,7 +1484,7 @@ public class SessionFsReaddirWithTypesResult { /// Directory entries with type information. [JsonPropertyName("entries")] - public List Entries { get => field ??= []; set; } + public IList Entries { get => field ??= []; set; } } /// RPC data type for SessionFsReaddirWithTypes operations. diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index dfd3b761f..c627aca4f 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -5,7 +5,9 @@ // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: session-events.schema.json +using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; @@ -1196,7 +1198,7 @@ public partial class SessionErrorData /// HTTP status code from the upstream request, if applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("statusCode")] - public double? StatusCode { get; set; } + public long? StatusCode { get; set; } /// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1204,6 +1206,8 @@ public partial class SessionErrorData public string? ProviderCallId { get; set; } /// Optional URL associated with this error that the user can open in a browser. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("url")] public string? Url { get; set; } @@ -1238,6 +1242,8 @@ public partial class SessionInfoData public required string Message { get; set; } /// Optional URL associated with this message that the user can open in a browser. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("url")] public string? Url { get; set; } @@ -1255,6 +1261,8 @@ public partial class SessionWarningData public required string Message { get; set; } /// Optional URL associated with this warning that the user can open in a browser. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("url")] public string? Url { get; set; } @@ -1430,7 +1438,7 @@ public partial class SessionShutdownData /// Per-model usage breakdown, keyed by model identifier. [JsonPropertyName("modelMetrics")] - public required Dictionary ModelMetrics { get; set; } + public required IDictionary ModelMetrics { get; set; } /// Model that was selected at the time of shutdown. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1886,7 +1894,7 @@ public partial class AssistantUsageData /// Per-quota resource usage snapshots, keyed by quota identifier. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("quotaSnapshots")] - public Dictionary? QuotaSnapshots { get; set; } + public IDictionary? QuotaSnapshots { get; set; } /// Per-request cost and usage data from the CAPI copilot_usage response field. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2019,7 +2027,7 @@ public partial class ToolExecutionCompleteData /// Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolTelemetry")] - public Dictionary? ToolTelemetry { get; set; } + public IDictionary? ToolTelemetry { get; set; } /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2383,7 +2391,7 @@ public partial class ElicitationCompletedData /// The submitted form data when action is 'accept'; keys match the requested schema fields. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("content")] - public Dictionary? Content { get; set; } + public IDictionary? Content { get; set; } } /// Sampling request from an MCP server; contains the server name and a requestId for correlation. @@ -3270,7 +3278,7 @@ public partial class SystemMessageDataMetadata /// Template variables used when constructing the prompt. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("variables")] - public Dictionary? Variables { get; set; } + public IDictionary? Variables { get; set; } } /// The agent_completed variant of . @@ -3678,7 +3686,7 @@ public partial class ElicitationRequestedDataRequestedSchema /// Form field definitions, keyed by field name. [JsonPropertyName("properties")] - public required Dictionary Properties { get; set; } + public required IDictionary Properties { get; set; } /// List of required field names. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] diff --git a/dotnet/src/MillisecondsTimeSpanConverter.cs b/dotnet/src/MillisecondsTimeSpanConverter.cs new file mode 100644 index 000000000..696d053dd --- /dev/null +++ b/dotnet/src/MillisecondsTimeSpanConverter.cs @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace GitHub.Copilot.SDK; + +/// Converts between JSON numeric milliseconds and . +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class MillisecondsTimeSpanConverter : JsonConverter +{ + /// + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + TimeSpan.FromMilliseconds(reader.GetDouble()); + + /// + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) => + writer.WriteNumberValue(value.TotalMilliseconds); +} diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 189cdfaff..2a2778b3c 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -1219,7 +1219,7 @@ internal record SendMessageRequest { public string SessionId { get; init; } = string.Empty; public string Prompt { get; init; } = string.Empty; - public List? Attachments { get; init; } + public IList? Attachments { get; init; } public string? Mode { get; init; } public string? Traceparent { get; init; } public string? Tracestate { get; init; } @@ -1237,7 +1237,7 @@ internal record GetMessagesRequest internal record GetMessagesResponse { - public List Events { get; init; } = []; + public IList Events { get => field ??= []; init; } } internal record SessionAbortRequest diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 8ee146dee..283505e0f 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -149,7 +149,7 @@ public string? GithubToken /// querying the CLI server. Useful in BYOK mode to return models /// available from your custom provider. /// - public Func>>? OnListModels { get; set; } + public Func>>? OnListModels { get; set; } /// /// Custom session filesystem provider configuration. @@ -293,7 +293,7 @@ public class ToolResultObject /// Binary results (e.g., images) to be consumed by the language model. /// [JsonPropertyName("binaryResultsForLlm")] - public List? BinaryResultsForLlm { get; set; } + public IList? BinaryResultsForLlm { get; set; } /// /// Result type indicator. @@ -323,7 +323,7 @@ public class ToolResultObject /// Custom telemetry data associated with the tool execution. /// [JsonPropertyName("toolTelemetry")] - public Dictionary? ToolTelemetry { get; set; } + public IDictionary? ToolTelemetry { get; set; } /// /// Converts the result of an invocation into a @@ -540,7 +540,7 @@ public class PermissionRequestResult /// Permission rules to apply for the decision. /// [JsonPropertyName("rules")] - public List? Rules { get; set; } + public IList? Rules { get; set; } } /// @@ -578,7 +578,7 @@ public class UserInputRequest /// Optional choices for multiple choice questions. /// [JsonPropertyName("choices")] - public List? Choices { get; set; } + public IList? Choices { get; set; } /// /// Whether freeform text input is allowed. @@ -696,13 +696,13 @@ public class ElicitationSchema /// Form field definitions, keyed by field name. /// [JsonPropertyName("properties")] - public Dictionary Properties { get; set; } = []; + public IDictionary Properties { get => field ??= new Dictionary(); set; } /// /// List of required field names. /// [JsonPropertyName("required")] - public List? Required { get; set; } + public IList? Required { get; set; } } /// @@ -734,7 +734,7 @@ public class ElicitationResult /// /// Form values submitted by the user (present when is Accept). /// - public Dictionary? Content { get; set; } + public IDictionary? Content { get; set; } } /// @@ -1127,7 +1127,7 @@ public class SessionStartHookOutput /// Modified session configuration to apply at startup. /// [JsonPropertyName("modifiedConfig")] - public Dictionary? ModifiedConfig { get; set; } + public IDictionary? ModifiedConfig { get; set; } } /// @@ -1193,7 +1193,7 @@ public class SessionEndHookOutput /// List of cleanup action identifiers to execute after the session ends. /// [JsonPropertyName("cleanupActions")] - public List? CleanupActions { get; set; } + public IList? CleanupActions { get; set; } /// /// Summary of the session to persist for future reference. @@ -1438,7 +1438,7 @@ public class SystemMessageConfig /// Section-level overrides for customize mode. /// Keys are section identifiers (see ). /// - public Dictionary? Sections { get; set; } + public IDictionary? Sections { get; set; } } /// @@ -1517,7 +1517,7 @@ private protected McpServerConfig() { } /// List of tools to include from this server. Empty list means none. Use "*" for all. /// [JsonPropertyName("tools")] - public List Tools { get; set; } = []; + public IList Tools { get => field ??= []; set; } /// /// The server type discriminator. @@ -1551,13 +1551,13 @@ public sealed class McpStdioServerConfig : McpServerConfig /// Arguments to pass to the command. /// [JsonPropertyName("args")] - public List Args { get; set; } = []; + public IList Args { get => field ??= []; set; } /// /// Environment variables to pass to the server. /// [JsonPropertyName("env")] - public Dictionary? Env { get; set; } + public IDictionary? Env { get; set; } /// /// Working directory for the server process. @@ -1585,7 +1585,7 @@ public sealed class McpHttpServerConfig : McpServerConfig /// Optional HTTP headers to include in requests. /// [JsonPropertyName("headers")] - public Dictionary? Headers { get; set; } + public IDictionary? Headers { get; set; } } // ============================================================================ @@ -1619,7 +1619,7 @@ public class CustomAgentConfig /// List of tool names the agent can use. Null for all tools. /// [JsonPropertyName("tools")] - public List? Tools { get; set; } + public IList? Tools { get; set; } /// /// The prompt content for the agent. @@ -1631,7 +1631,7 @@ public class CustomAgentConfig /// MCP servers specific to this agent. /// [JsonPropertyName("mcpServers")] - public Dictionary? McpServers { get; set; } + public IDictionary? McpServers { get; set; } /// /// Whether the agent should be available for model inference. @@ -1700,7 +1700,7 @@ protected SessionConfig(SessionConfig? other) Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; McpServers = other.McpServers is not null - ? new Dictionary(other.McpServers, other.McpServers.Comparer) + ? new Dictionary(other.McpServers) : null; Model = other.Model; ModelCapabilities = other.ModelCapabilities; @@ -1777,11 +1777,11 @@ protected SessionConfig(SessionConfig? other) /// /// List of tool names to allow; only these tools will be available when specified. /// - public List? AvailableTools { get; set; } + public IList? AvailableTools { get; set; } /// /// List of tool names to exclude from the session. /// - public List? ExcludedTools { get; set; } + public IList? ExcludedTools { get; set; } /// /// Custom model provider configuration for the session. /// @@ -1804,7 +1804,7 @@ protected SessionConfig(SessionConfig? other) /// When the CLI has a TUI, each command appears as /name for the user to invoke. /// The handler is called when the user executes the command. /// - public List? Commands { get; set; } + public IList? Commands { get; set; } /// /// Handler for elicitation requests from the server or MCP tools. @@ -1834,12 +1834,12 @@ protected SessionConfig(SessionConfig? other) /// MCP server configurations for the session. /// Keys are server names, values are server configurations ( or ). /// - public Dictionary? McpServers { get; set; } + public IDictionary? McpServers { get; set; } /// /// Custom agent configurations for the session. /// - public List? CustomAgents { get; set; } + public IList? CustomAgents { get; set; } /// /// Name of the custom agent to activate when the session starts. @@ -1850,12 +1850,12 @@ protected SessionConfig(SessionConfig? other) /// /// Directories to load skills from. /// - public List? SkillDirectories { get; set; } + public IList? SkillDirectories { get; set; } /// /// List of skill names to disable. /// - public List? DisabledSkills { get; set; } + public IList? DisabledSkills { get; set; } /// /// Infinite session configuration for persistent workspaces and automatic compaction. @@ -1928,7 +1928,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; McpServers = other.McpServers is not null - ? new Dictionary(other.McpServers, other.McpServers.Comparer) + ? new Dictionary(other.McpServers) : null; Model = other.Model; ModelCapabilities = other.ModelCapabilities; @@ -1971,13 +1971,13 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// List of tool names to allow. When specified, only these tools will be available. /// Takes precedence over ExcludedTools. /// - public List? AvailableTools { get; set; } + public IList? AvailableTools { get; set; } /// /// List of tool names to disable. All other tools remain available. /// Ignored if AvailableTools is specified. /// - public List? ExcludedTools { get; set; } + public IList? ExcludedTools { get; set; } /// /// Custom model provider configuration for the resumed session. @@ -2012,7 +2012,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// When the CLI has a TUI, each command appears as /name for the user to invoke. /// The handler is called when the user executes the command. /// - public List? Commands { get; set; } + public IList? Commands { get; set; } /// /// Handler for elicitation requests from the server or MCP tools. @@ -2066,12 +2066,12 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// MCP server configurations for the session. /// Keys are server names, values are server configurations ( or ). /// - public Dictionary? McpServers { get; set; } + public IDictionary? McpServers { get; set; } /// /// Custom agent configurations for the session. /// - public List? CustomAgents { get; set; } + public IList? CustomAgents { get; set; } /// /// Name of the custom agent to activate when the session starts. @@ -2082,12 +2082,12 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// /// Directories to load skills from. /// - public List? SkillDirectories { get; set; } + public IList? SkillDirectories { get; set; } /// /// List of skill names to disable. /// - public List? DisabledSkills { get; set; } + public IList? DisabledSkills { get; set; } /// /// Infinite session configuration for persistent workspaces and automatic compaction. @@ -2152,7 +2152,7 @@ protected MessageOptions(MessageOptions? other) /// /// File or data attachments to include with the message. /// - public List? Attachments { get; set; } + public IList? Attachments { get; set; } /// /// Interaction mode for the message (e.g., "plan", "edit"). /// @@ -2320,7 +2320,7 @@ public class ModelVisionLimits /// List of supported image MIME types (e.g., "image/png", "image/jpeg"). /// [JsonPropertyName("supported_media_types")] - public List SupportedMediaTypes { get; set; } = []; + public IList SupportedMediaTypes { get => field ??= []; set; } /// /// Maximum number of images allowed in a single prompt. @@ -2452,7 +2452,7 @@ public class ModelInfo /// Supported reasoning effort levels (only present if model supports reasoning effort) [JsonPropertyName("supportedReasoningEfforts")] - public List? SupportedReasoningEfforts { get; set; } + public IList? SupportedReasoningEfforts { get; set; } /// Default reasoning effort level (only present if model supports reasoning effort) [JsonPropertyName("defaultReasoningEffort")] @@ -2468,7 +2468,7 @@ public class GetModelsResponse /// List of available models. /// [JsonPropertyName("models")] - public List Models { get; set; } = []; + public IList Models { get => field ??= []; set; } } // ============================================================================ @@ -2597,7 +2597,7 @@ public class SystemMessageTransformRpcResponse /// The transformed sections keyed by section identifier. /// [JsonPropertyName("sections")] - public Dictionary? Sections { get; set; } + public IDictionary? Sections { get; set; } } [JsonSourceGenerationOptions( diff --git a/dotnet/test/ClientTests.cs b/dotnet/test/ClientTests.cs index 6c70ffaa3..c62c5bc3f 100644 --- a/dotnet/test/ClientTests.cs +++ b/dotnet/test/ClientTests.cs @@ -278,7 +278,7 @@ public async Task Should_Throw_When_ResumeSession_Called_Without_PermissionHandl [Fact] public async Task ListModels_WithCustomHandler_CallsHandler() { - var customModels = new List + IList customModels = new List { new() { @@ -312,7 +312,7 @@ public async Task ListModels_WithCustomHandler_CallsHandler() [Fact] public async Task ListModels_WithCustomHandler_CachesResults() { - var customModels = new List + IList customModels = new List { new() { @@ -345,7 +345,7 @@ public async Task ListModels_WithCustomHandler_CachesResults() [Fact] public async Task ListModels_WithCustomHandler_WorksWithoutStart() { - var customModels = new List + IList customModels = new List { new() { diff --git a/dotnet/test/ElicitationTests.cs b/dotnet/test/ElicitationTests.cs index e3048e4c9..f91fe2d19 100644 --- a/dotnet/test/ElicitationTests.cs +++ b/dotnet/test/ElicitationTests.cs @@ -62,7 +62,7 @@ await session.Ui.ElicitationAsync(new ElicitationParams Message = "Enter name", RequestedSchema = new ElicitationSchema { - Properties = new() { ["name"] = new Dictionary { ["type"] = "string" } }, + Properties = new Dictionary() { ["name"] = new Dictionary { ["type"] = "string" } }, Required = ["name"], }, }); diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index 9bd03f186..5200d6de5 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -397,7 +397,7 @@ public async Task Should_List_Sessions_With_Context() var sessions = await Client.ListSessionsAsync(); Assert.NotEmpty(sessions); - var ourSession = sessions.Find(s => s.SessionId == session.SessionId); + var ourSession = sessions.FirstOrDefault(s => s.SessionId == session.SessionId); Assert.NotNull(ourSession); // Context may be present on sessions that have been persisted with workspace.yaml diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 9049cb38c..ba89d2897 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -158,13 +158,25 @@ function schemaTypeToCSharp(schema: JSONSchema7, required: boolean, knownTypes: if (format === "date-time") return "DateTimeOffset?"; return "string?"; } + if (nonNullTypes.length === 1 && (nonNullTypes[0] === "number" || nonNullTypes[0] === "integer")) { + if (format === "duration") { + return "TimeSpan?"; + } + return nonNullTypes[0] === "integer" ? "long?" : "double?"; + } } if (type === "string") { if (format === "uuid") return required ? "Guid" : "Guid?"; if (format === "date-time") return required ? "DateTimeOffset" : "DateTimeOffset?"; return required ? "string" : "string?"; } - if (type === "number" || type === "integer") return required ? "double" : "double?"; + if (type === "number" || type === "integer") { + if (format === "duration") { + return required ? "TimeSpan" : "TimeSpan?"; + } + if (type === "integer") return required ? "long" : "long?"; + return required ? "double" : "double?"; + } if (type === "boolean") return required ? "bool" : "bool?"; if (type === "array") { const items = schema.items as JSONSchema7 | undefined; @@ -174,13 +186,92 @@ function schemaTypeToCSharp(schema: JSONSchema7, required: boolean, knownTypes: if (type === "object") { if (schema.additionalProperties && typeof schema.additionalProperties === "object") { const valueType = schemaTypeToCSharp(schema.additionalProperties as JSONSchema7, true, knownTypes); - return required ? `Dictionary` : `Dictionary?`; + return required ? `IDictionary` : `IDictionary?`; } return required ? "object" : "object?"; } return required ? "object" : "object?"; } +/** Tracks whether any TimeSpan property was emitted so the converter can be generated. */ + + +/** + * Emit C# data-annotation attributes for a JSON Schema property. + * Returns an array of attribute lines (without trailing newlines). + */ +function emitDataAnnotations(schema: JSONSchema7, indent: string): string[] { + const attrs: string[] = []; + const format = schema.format; + + // [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] for format: "uri" + if (format === "uri") { + attrs.push(`${indent}[Url]`); + attrs.push(`${indent}[StringSyntax(StringSyntaxAttribute.Uri)]`); + } + + // [StringSyntax(StringSyntaxAttribute.Regex)] for format: "regex" + if (format === "regex") { + attrs.push(`${indent}[StringSyntax(StringSyntaxAttribute.Regex)]`); + } + + // [Base64String] for base64-encoded string properties + if (format === "byte" || (schema as Record).contentEncoding === "base64") { + attrs.push(`${indent}[Base64String]`); + } + + // [Range]for minimum/maximum + const hasMin = typeof schema.minimum === "number"; + const hasMax = typeof schema.maximum === "number"; + if (hasMin || hasMax) { + const min = hasMin ? String(schema.minimum) : (schema.type === "integer" ? "long.MinValue" : "double.MinValue"); + const max = hasMax ? String(schema.maximum) : (schema.type === "integer" ? "long.MaxValue" : "double.MaxValue"); + const namedArgs: string[] = []; + if (schema.exclusiveMinimum === true) namedArgs.push("MinimumIsExclusive = true"); + if (schema.exclusiveMaximum === true) namedArgs.push("MaximumIsExclusive = true"); + const namedSuffix = namedArgs.length > 0 ? `, ${namedArgs.join(", ")}` : ""; + attrs.push(`${indent}[Range(${min}, ${max}${namedSuffix})]`); + } + + // [RegularExpression] for pattern + if (typeof schema.pattern === "string") { + const escaped = schema.pattern.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); + attrs.push(`${indent}[RegularExpression("${escaped}")]`); + } + + // [MinLength] / [MaxLength] for string constraints + if (typeof schema.minLength === "number") { + attrs.push(`${indent}[MinLength(${schema.minLength})]`); + } + if (typeof schema.maxLength === "number") { + attrs.push(`${indent}[MaxLength(${schema.maxLength})]`); + } + + return attrs; +} + +/** + * Returns true when a TimeSpan-typed property needs a [JsonConverter] attribute. + * + * NOTE: The runtime schema uses `format: "duration"` on numeric (integer/number) fields + * to mean "a duration value expressed in milliseconds". This differs from the JSON Schema + * spec, where `format: "duration"` denotes an ISO 8601 duration string (e.g. "PT1H30M"). + * The generator and runtime agree on this convention, so we map these to TimeSpan with a + * milliseconds-based JSON converter rather than expecting ISO 8601 strings. + */ +function isDurationProperty(schema: JSONSchema7): boolean { + if (schema.format === "duration") { + const t = schema.type; + if (t === "number" || t === "integer") return true; + if (Array.isArray(t)) { + const nonNull = (t as string[]).filter((x) => x !== "null"); + if (nonNull.length === 1 && (nonNull[0] === "number" || nonNull[0] === "integer")) return true; + } + } + return false; +} + + const COPYRIGHT = `/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/`; @@ -351,6 +442,8 @@ function generateDerivedClass( const csharpType = resolveSessionPropertyType(propSchema as JSONSchema7, className, csharpName, isReq, knownTypes, nestedClasses, enumOutput); lines.push(...xmlDocPropertyComment((propSchema as JSONSchema7).description, propName, " ")); + lines.push(...emitDataAnnotations(propSchema as JSONSchema7, " ")); + if (isDurationProperty(propSchema as JSONSchema7)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); lines.push(` [JsonPropertyName("${propName}")]`); const reqMod = isReq && !csharpType.endsWith("?") ? "required " : ""; @@ -383,6 +476,8 @@ function generateNestedClass( const csharpType = resolveSessionPropertyType(prop, className, csharpName, isReq, knownTypes, nestedClasses, enumOutput); lines.push(...xmlDocPropertyComment(prop.description, propName, " ")); + lines.push(...emitDataAnnotations(prop, " ")); + if (isDurationProperty(prop)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); lines.push(` [JsonPropertyName("${propName}")]`); const reqMod = isReq && !csharpType.endsWith("?") ? "required " : ""; @@ -479,6 +574,8 @@ function generateDataClass(variant: EventVariant, knownTypes: Map` : `List<${itemClass}>?`; + return isRequired ? `IList<${itemClass}>` : `IList<${itemClass}>?`; } const itemType = schemaTypeToCSharp(items, true, rpcKnownTypes); - return isRequired ? `List<${itemType}>` : `List<${itemType}>?`; + return isRequired ? `IList<${itemType}>` : `IList<${itemType}>?`; } if (schema.type === "object" && schema.additionalProperties && typeof schema.additionalProperties === "object") { const vs = schema.additionalProperties as JSONSchema7; if (vs.type === "object" && vs.properties) { const valClass = `${parentClassName}${propName}Value`; classes.push(emitRpcClass(valClass, vs, "public", classes)); - return isRequired ? `Dictionary` : `Dictionary?`; + return isRequired ? `IDictionary` : `IDictionary?`; } const valueType = schemaTypeToCSharp(vs, true, rpcKnownTypes); - return isRequired ? `Dictionary` : `Dictionary?`; + return isRequired ? `IDictionary` : `IDictionary?`; } return schemaTypeToCSharp(schema, isRequired, rpcKnownTypes); } @@ -680,6 +779,8 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi const csharpType = resolveRpcType(prop, isReq, className, csharpName, extraClasses); lines.push(...xmlDocPropertyComment(prop.description, propName, " ")); + lines.push(...emitDataAnnotations(prop, " ")); + if (isDurationProperty(prop)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); lines.push(` [JsonPropertyName("${propName}")]`); let defaultVal = ""; @@ -687,8 +788,11 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi if (isReq && !csharpType.endsWith("?")) { if (csharpType === "string") defaultVal = " = string.Empty;"; else if (csharpType === "object") defaultVal = " = null!;"; - else if (csharpType.startsWith("List<") || csharpType.startsWith("Dictionary<")) { + else if (csharpType.startsWith("IList<")) { propAccessors = "{ get => field ??= []; set; }"; + } else if (csharpType.startsWith("IDictionary<")) { + const concreteType = csharpType.replace("IDictionary<", "Dictionary<"); + propAccessors = `{ get => field ??= new ${concreteType}(); set; }`; } else if (emittedRpcClasses.has(csharpType)) { propAccessors = "{ get => field ??= new(); set; }"; } @@ -1082,6 +1186,7 @@ function generateRpcCode(schema: ApiSchema): string { // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: api.schema.json +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; From 32c7b991c73403674fa7c22ee5ad93661d6e6ec0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 12 Apr 2026 13:50:02 -0400 Subject: [PATCH 2/6] Update ListSessionsAsync return type in docs to IList Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/README.md b/dotnet/README.md index 4e6cd7c4e..3e6def504 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -131,7 +131,7 @@ Ping the server to check connectivity. Get current connection state. -##### `ListSessionsAsync(): Task>` +##### `ListSessionsAsync(): Task>` List all available sessions. From 56ba3cf8bb243dcf1b8999b5ebf47deb9f2ef139 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 12 Apr 2026 14:21:23 -0400 Subject: [PATCH 3/6] Fix clone comparer preservation and byok doc example Preserve dictionary comparer in SessionConfig/ResumeSessionConfig Clone() by checking for Dictionary<> and passing its Comparer. Fix byok.md to use Task.FromResult>() for the updated delegate signature. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/auth/byok.md | 2 +- dotnet/src/Types.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/auth/byok.md b/docs/auth/byok.md index d3d4e4106..4bb88f5aa 100644 --- a/docs/auth/byok.md +++ b/docs/auth/byok.md @@ -426,7 +426,7 @@ using GitHub.Copilot.SDK; var client = new CopilotClient(new CopilotClientOptions { - OnListModels = (ct) => Task.FromResult(new List + OnListModels = (ct) => Task.FromResult>(new List { new() { diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 283505e0f..970d44f76 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1700,7 +1700,9 @@ protected SessionConfig(SessionConfig? other) Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; McpServers = other.McpServers is not null - ? new Dictionary(other.McpServers) + ? (other.McpServers is Dictionary dict + ? new Dictionary(dict, dict.Comparer) + : new Dictionary(other.McpServers)) : null; Model = other.Model; ModelCapabilities = other.ModelCapabilities; @@ -1928,7 +1930,9 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; McpServers = other.McpServers is not null - ? new Dictionary(other.McpServers) + ? (other.McpServers is Dictionary dict + ? new Dictionary(dict, dict.Comparer) + : new Dictionary(other.McpServers)) : null; Model = other.Model; ModelCapabilities = other.ModelCapabilities; From 04c2bd84cca21e8eeca253fcea1518b448f2fe5e Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 12 Apr 2026 15:21:12 -0400 Subject: [PATCH 4/6] Address review: Range(Type,string,string) for long, add SDK using to Rpc Use Range(typeof(long), ...) overload since RangeAttribute has no long constructor. Add 'using GitHub.Copilot.SDK' to Rpc.cs header so MillisecondsTimeSpanConverter resolves when duration fields exist. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 1 + scripts/codegen/csharp.ts | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 387702685..675bd9c30 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; +using GitHub.Copilot.SDK; using StreamJsonRpc; namespace GitHub.Copilot.SDK.Rpc; diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index ba89d2897..703213f19 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -220,17 +220,24 @@ function emitDataAnnotations(schema: JSONSchema7, indent: string): string[] { attrs.push(`${indent}[Base64String]`); } - // [Range]for minimum/maximum + // [Range] for minimum/maximum const hasMin = typeof schema.minimum === "number"; const hasMax = typeof schema.maximum === "number"; if (hasMin || hasMax) { - const min = hasMin ? String(schema.minimum) : (schema.type === "integer" ? "long.MinValue" : "double.MinValue"); - const max = hasMax ? String(schema.maximum) : (schema.type === "integer" ? "long.MaxValue" : "double.MaxValue"); const namedArgs: string[] = []; if (schema.exclusiveMinimum === true) namedArgs.push("MinimumIsExclusive = true"); if (schema.exclusiveMaximum === true) namedArgs.push("MaximumIsExclusive = true"); const namedSuffix = namedArgs.length > 0 ? `, ${namedArgs.join(", ")}` : ""; - attrs.push(`${indent}[Range(${min}, ${max}${namedSuffix})]`); + if (schema.type === "integer") { + // Use Range(Type, string, string) overload since RangeAttribute has no long constructor + const min = hasMin ? String(schema.minimum) : "long.MinValue"; + const max = hasMax ? String(schema.maximum) : "long.MaxValue"; + attrs.push(`${indent}[Range(typeof(long), "${min}", "${max}"${namedSuffix})]`); + } else { + const min = hasMin ? String(schema.minimum) : "double.MinValue"; + const max = hasMax ? String(schema.maximum) : "double.MaxValue"; + attrs.push(`${indent}[Range(${min}, ${max}${namedSuffix})]`); + } } // [RegularExpression] for pattern @@ -1190,6 +1197,7 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; +using GitHub.Copilot.SDK; using StreamJsonRpc; namespace GitHub.Copilot.SDK.Rpc; From db546c615a43c5b585d8361d91dd21957a3e58ba Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 12 Apr 2026 20:23:25 -0400 Subject: [PATCH 5/6] Fix Go doc example: type-assert SessionEventData to AssistantMessageData The Go SDK uses per-event-type data structs, so response.Data is a SessionEventData interface. Access Content by type-asserting to *copilot.AssistantMessageData. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/setup/local-cli.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/setup/local-cli.md b/docs/setup/local-cli.md index 48092b735..845a20af5 100644 --- a/docs/setup/local-cli.md +++ b/docs/setup/local-cli.md @@ -99,7 +99,9 @@ func main() { session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - fmt.Println(*response.Data.Content) + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } } ``` @@ -115,7 +117,9 @@ defer client.Stop() session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) -fmt.Println(*response.Data.Content) +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) +} ``` From 0bf73e06625ea4f06f7228939891189eaa5a31c9 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 12 Apr 2026 20:31:35 -0400 Subject: [PATCH 6/6] Remove unnecessary using GitHub.Copilot.SDK from Rpc.cs generator Rpc.cs is in namespace GitHub.Copilot.SDK.Rpc, a child of GitHub.Copilot.SDK, so types from the parent namespace resolve automatically without an explicit using directive. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 1 - scripts/codegen/csharp.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 675bd9c30..387702685 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -9,7 +9,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; -using GitHub.Copilot.SDK; using StreamJsonRpc; namespace GitHub.Copilot.SDK.Rpc; diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 703213f19..63968077e 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -1197,7 +1197,6 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; -using GitHub.Copilot.SDK; using StreamJsonRpc; namespace GitHub.Copilot.SDK.Rpc;