diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index c6a5b971..98e7d747 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json.Linq; using StreamJsonRpc; using System.Collections.Concurrent; using System.Data; @@ -13,7 +12,6 @@ using System.Diagnostics.CodeAnalysis; using System.Net.Sockets; using System.Text.Json; -using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.RegularExpressions; @@ -43,8 +41,8 @@ namespace GitHub.Copilot.SDK; /// // Handle events /// using var subscription = session.On(evt => /// { -/// if (evt.Type == "assistant.message") -/// Console.WriteLine(evt.Data?.Content); +/// if (evt is AssistantMessageEvent assistantMessage) +/// Console.WriteLine(assistantMessage.Data?.Content); /// }); /// /// // Send a message @@ -724,12 +722,10 @@ static IJsonRpcMessageFormatter CreateFormatter() { var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) { + AllowOutOfOrderMetadataProperties = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; - foreach (var converter in SerializerOptions.Default.Converters) - { - options.Converters.Add(converter); - } + return new SystemTextJsonFormatter() { JsonSerializerOptions = options }; } diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index 81d6b3e0..6a3b9c5f 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -6,7 +6,7 @@ // // Generated from: @github/copilot/session-events.schema.json // Generated by: scripts/generate-session-types.ts -// Generated at: 2026-01-20T04:18:06.775Z +// Generated at: 2026-01-21T14:50:29.306Z // // To update these types: // 1. Update the schema in copilot-agent-runtime @@ -14,86 +14,53 @@ // #nullable enable -#pragma warning disable CS8618 namespace GitHub.Copilot.SDK { using System; using System.Collections.Generic; using System.Text.Json; - using System.Text.Json.Nodes; using System.Text.Json.Serialization; - /// - /// Custom JSON converter for SessionEvent that handles discriminator appearing anywhere in JSON. - /// - internal class SessionEventConverter : JsonConverter - { - private static readonly Dictionary TypeMap = new() - { - ["session.start"] = typeof(SessionStartEvent), - ["session.resume"] = typeof(SessionResumeEvent), - ["session.error"] = typeof(SessionErrorEvent), - ["session.idle"] = typeof(SessionIdleEvent), - ["session.info"] = typeof(SessionInfoEvent), - ["session.model_change"] = typeof(SessionModelChangeEvent), - ["session.handoff"] = typeof(SessionHandoffEvent), - ["session.truncation"] = typeof(SessionTruncationEvent), - ["session.usage_info"] = typeof(SessionUsageInfoEvent), - ["session.compaction_start"] = typeof(SessionCompactionStartEvent), - ["session.compaction_complete"] = typeof(SessionCompactionCompleteEvent), - ["user.message"] = typeof(UserMessageEvent), - ["pending_messages.modified"] = typeof(PendingMessagesModifiedEvent), - ["assistant.turn_start"] = typeof(AssistantTurnStartEvent), - ["assistant.intent"] = typeof(AssistantIntentEvent), - ["assistant.reasoning"] = typeof(AssistantReasoningEvent), - ["assistant.reasoning_delta"] = typeof(AssistantReasoningDeltaEvent), - ["assistant.message"] = typeof(AssistantMessageEvent), - ["assistant.message_delta"] = typeof(AssistantMessageDeltaEvent), - ["assistant.turn_end"] = typeof(AssistantTurnEndEvent), - ["assistant.usage"] = typeof(AssistantUsageEvent), - ["abort"] = typeof(AbortEvent), - ["tool.user_requested"] = typeof(ToolUserRequestedEvent), - ["tool.execution_start"] = typeof(ToolExecutionStartEvent), - ["tool.execution_partial_result"] = typeof(ToolExecutionPartialResultEvent), - ["tool.execution_complete"] = typeof(ToolExecutionCompleteEvent), - ["subagent.started"] = typeof(SubagentStartedEvent), - ["subagent.completed"] = typeof(SubagentCompletedEvent), - ["subagent.failed"] = typeof(SubagentFailedEvent), - ["subagent.selected"] = typeof(SubagentSelectedEvent), - ["hook.start"] = typeof(HookStartEvent), - ["hook.end"] = typeof(HookEndEvent), - ["system.message"] = typeof(SystemMessageEvent), - }; - - public override SessionEvent? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - // Parse as JsonNode to find the discriminator regardless of property order - var node = JsonNode.Parse(ref reader); - if (node is not JsonObject obj) - throw new JsonException("Expected JSON object"); - - var typeProp = obj["type"]?.GetValue(); - if (string.IsNullOrEmpty(typeProp)) - throw new JsonException("Missing 'type' discriminator property"); - - if (!TypeMap.TryGetValue(typeProp, out var targetType)) - throw new JsonException($"Unknown event type: {typeProp}"); - - // Deserialize to the concrete type without using this converter (to avoid recursion) - return (SessionEvent?)obj.Deserialize(targetType, SerializerOptions.WithoutConverter); - } - - public override void Write(Utf8JsonWriter writer, SessionEvent value, JsonSerializerOptions options) - { - JsonSerializer.Serialize(writer, value, value.GetType(), SerializerOptions.WithoutConverter); - } - } - /// /// Base class for all session events with polymorphic JSON serialization. /// - [JsonConverter(typeof(SessionEventConverter))] + [JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization)] + [JsonDerivedType(typeof(AbortEvent), "abort")] + [JsonDerivedType(typeof(AssistantIntentEvent), "assistant.intent")] + [JsonDerivedType(typeof(AssistantMessageEvent), "assistant.message")] + [JsonDerivedType(typeof(AssistantMessageDeltaEvent), "assistant.message_delta")] + [JsonDerivedType(typeof(AssistantReasoningEvent), "assistant.reasoning")] + [JsonDerivedType(typeof(AssistantReasoningDeltaEvent), "assistant.reasoning_delta")] + [JsonDerivedType(typeof(AssistantTurnEndEvent), "assistant.turn_end")] + [JsonDerivedType(typeof(AssistantTurnStartEvent), "assistant.turn_start")] + [JsonDerivedType(typeof(AssistantUsageEvent), "assistant.usage")] + [JsonDerivedType(typeof(HookEndEvent), "hook.end")] + [JsonDerivedType(typeof(HookStartEvent), "hook.start")] + [JsonDerivedType(typeof(PendingMessagesModifiedEvent), "pending_messages.modified")] + [JsonDerivedType(typeof(SessionCompactionCompleteEvent), "session.compaction_complete")] + [JsonDerivedType(typeof(SessionCompactionStartEvent), "session.compaction_start")] + [JsonDerivedType(typeof(SessionErrorEvent), "session.error")] + [JsonDerivedType(typeof(SessionHandoffEvent), "session.handoff")] + [JsonDerivedType(typeof(SessionIdleEvent), "session.idle")] + [JsonDerivedType(typeof(SessionInfoEvent), "session.info")] + [JsonDerivedType(typeof(SessionModelChangeEvent), "session.model_change")] + [JsonDerivedType(typeof(SessionResumeEvent), "session.resume")] + [JsonDerivedType(typeof(SessionStartEvent), "session.start")] + [JsonDerivedType(typeof(SessionTruncationEvent), "session.truncation")] + [JsonDerivedType(typeof(SessionUsageInfoEvent), "session.usage_info")] + [JsonDerivedType(typeof(SubagentCompletedEvent), "subagent.completed")] + [JsonDerivedType(typeof(SubagentFailedEvent), "subagent.failed")] + [JsonDerivedType(typeof(SubagentSelectedEvent), "subagent.selected")] + [JsonDerivedType(typeof(SubagentStartedEvent), "subagent.started")] + [JsonDerivedType(typeof(SystemMessageEvent), "system.message")] + [JsonDerivedType(typeof(ToolExecutionCompleteEvent), "tool.execution_complete")] + [JsonDerivedType(typeof(ToolExecutionPartialResultEvent), "tool.execution_partial_result")] + [JsonDerivedType(typeof(ToolExecutionStartEvent), "tool.execution_start")] + [JsonDerivedType(typeof(ToolUserRequestedEvent), "tool.user_requested")] + [JsonDerivedType(typeof(UserMessageEvent), "user.message")] public abstract partial class SessionEvent { [JsonPropertyName("id")] @@ -112,7 +79,7 @@ public abstract partial class SessionEvent /// /// The event type discriminator. /// - [JsonPropertyName("type")] + [JsonIgnore] public abstract string Type { get; } public static SessionEvent FromJson(string json) => @@ -127,10 +94,11 @@ public string ToJson() => /// public partial class SessionStartEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.start"; [JsonPropertyName("data")] - public SessionStartData Data { get; set; } + public required SessionStartData Data { get; set; } } /// @@ -138,10 +106,11 @@ public partial class SessionStartEvent : SessionEvent /// public partial class SessionResumeEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.resume"; [JsonPropertyName("data")] - public SessionResumeData Data { get; set; } + public required SessionResumeData Data { get; set; } } /// @@ -149,10 +118,11 @@ public partial class SessionResumeEvent : SessionEvent /// public partial class SessionErrorEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.error"; [JsonPropertyName("data")] - public SessionErrorData Data { get; set; } + public required SessionErrorData Data { get; set; } } /// @@ -160,10 +130,11 @@ public partial class SessionErrorEvent : SessionEvent /// public partial class SessionIdleEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.idle"; [JsonPropertyName("data")] - public SessionIdleData Data { get; set; } + public required SessionIdleData Data { get; set; } } /// @@ -171,10 +142,11 @@ public partial class SessionIdleEvent : SessionEvent /// public partial class SessionInfoEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.info"; [JsonPropertyName("data")] - public SessionInfoData Data { get; set; } + public required SessionInfoData Data { get; set; } } /// @@ -182,10 +154,11 @@ public partial class SessionInfoEvent : SessionEvent /// public partial class SessionModelChangeEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.model_change"; [JsonPropertyName("data")] - public SessionModelChangeData Data { get; set; } + public required SessionModelChangeData Data { get; set; } } /// @@ -193,10 +166,11 @@ public partial class SessionModelChangeEvent : SessionEvent /// public partial class SessionHandoffEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.handoff"; [JsonPropertyName("data")] - public SessionHandoffData Data { get; set; } + public required SessionHandoffData Data { get; set; } } /// @@ -204,10 +178,11 @@ public partial class SessionHandoffEvent : SessionEvent /// public partial class SessionTruncationEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.truncation"; [JsonPropertyName("data")] - public SessionTruncationData Data { get; set; } + public required SessionTruncationData Data { get; set; } } /// @@ -215,10 +190,11 @@ public partial class SessionTruncationEvent : SessionEvent /// public partial class SessionUsageInfoEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.usage_info"; [JsonPropertyName("data")] - public SessionUsageInfoData Data { get; set; } + public required SessionUsageInfoData Data { get; set; } } /// @@ -226,10 +202,11 @@ public partial class SessionUsageInfoEvent : SessionEvent /// public partial class SessionCompactionStartEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.compaction_start"; [JsonPropertyName("data")] - public SessionCompactionStartData Data { get; set; } + public required SessionCompactionStartData Data { get; set; } } /// @@ -237,10 +214,11 @@ public partial class SessionCompactionStartEvent : SessionEvent /// public partial class SessionCompactionCompleteEvent : SessionEvent { + [JsonIgnore] public override string Type => "session.compaction_complete"; [JsonPropertyName("data")] - public SessionCompactionCompleteData Data { get; set; } + public required SessionCompactionCompleteData Data { get; set; } } /// @@ -248,10 +226,11 @@ public partial class SessionCompactionCompleteEvent : SessionEvent /// public partial class UserMessageEvent : SessionEvent { + [JsonIgnore] public override string Type => "user.message"; [JsonPropertyName("data")] - public UserMessageData Data { get; set; } + public required UserMessageData Data { get; set; } } /// @@ -259,10 +238,11 @@ public partial class UserMessageEvent : SessionEvent /// public partial class PendingMessagesModifiedEvent : SessionEvent { + [JsonIgnore] public override string Type => "pending_messages.modified"; [JsonPropertyName("data")] - public PendingMessagesModifiedData Data { get; set; } + public required PendingMessagesModifiedData Data { get; set; } } /// @@ -270,10 +250,11 @@ public partial class PendingMessagesModifiedEvent : SessionEvent /// public partial class AssistantTurnStartEvent : SessionEvent { + [JsonIgnore] public override string Type => "assistant.turn_start"; [JsonPropertyName("data")] - public AssistantTurnStartData Data { get; set; } + public required AssistantTurnStartData Data { get; set; } } /// @@ -281,10 +262,11 @@ public partial class AssistantTurnStartEvent : SessionEvent /// public partial class AssistantIntentEvent : SessionEvent { + [JsonIgnore] public override string Type => "assistant.intent"; [JsonPropertyName("data")] - public AssistantIntentData Data { get; set; } + public required AssistantIntentData Data { get; set; } } /// @@ -292,10 +274,11 @@ public partial class AssistantIntentEvent : SessionEvent /// public partial class AssistantReasoningEvent : SessionEvent { + [JsonIgnore] public override string Type => "assistant.reasoning"; [JsonPropertyName("data")] - public AssistantReasoningData Data { get; set; } + public required AssistantReasoningData Data { get; set; } } /// @@ -303,10 +286,11 @@ public partial class AssistantReasoningEvent : SessionEvent /// public partial class AssistantReasoningDeltaEvent : SessionEvent { + [JsonIgnore] public override string Type => "assistant.reasoning_delta"; [JsonPropertyName("data")] - public AssistantReasoningDeltaData Data { get; set; } + public required AssistantReasoningDeltaData Data { get; set; } } /// @@ -314,10 +298,11 @@ public partial class AssistantReasoningDeltaEvent : SessionEvent /// public partial class AssistantMessageEvent : SessionEvent { + [JsonIgnore] public override string Type => "assistant.message"; [JsonPropertyName("data")] - public AssistantMessageData Data { get; set; } + public required AssistantMessageData Data { get; set; } } /// @@ -325,10 +310,11 @@ public partial class AssistantMessageEvent : SessionEvent /// public partial class AssistantMessageDeltaEvent : SessionEvent { + [JsonIgnore] public override string Type => "assistant.message_delta"; [JsonPropertyName("data")] - public AssistantMessageDeltaData Data { get; set; } + public required AssistantMessageDeltaData Data { get; set; } } /// @@ -336,10 +322,11 @@ public partial class AssistantMessageDeltaEvent : SessionEvent /// public partial class AssistantTurnEndEvent : SessionEvent { + [JsonIgnore] public override string Type => "assistant.turn_end"; [JsonPropertyName("data")] - public AssistantTurnEndData Data { get; set; } + public required AssistantTurnEndData Data { get; set; } } /// @@ -347,10 +334,11 @@ public partial class AssistantTurnEndEvent : SessionEvent /// public partial class AssistantUsageEvent : SessionEvent { + [JsonIgnore] public override string Type => "assistant.usage"; [JsonPropertyName("data")] - public AssistantUsageData Data { get; set; } + public required AssistantUsageData Data { get; set; } } /// @@ -358,10 +346,11 @@ public partial class AssistantUsageEvent : SessionEvent /// public partial class AbortEvent : SessionEvent { + [JsonIgnore] public override string Type => "abort"; [JsonPropertyName("data")] - public AbortData Data { get; set; } + public required AbortData Data { get; set; } } /// @@ -369,10 +358,11 @@ public partial class AbortEvent : SessionEvent /// public partial class ToolUserRequestedEvent : SessionEvent { + [JsonIgnore] public override string Type => "tool.user_requested"; [JsonPropertyName("data")] - public ToolUserRequestedData Data { get; set; } + public required ToolUserRequestedData Data { get; set; } } /// @@ -380,10 +370,11 @@ public partial class ToolUserRequestedEvent : SessionEvent /// public partial class ToolExecutionStartEvent : SessionEvent { + [JsonIgnore] public override string Type => "tool.execution_start"; [JsonPropertyName("data")] - public ToolExecutionStartData Data { get; set; } + public required ToolExecutionStartData Data { get; set; } } /// @@ -391,10 +382,11 @@ public partial class ToolExecutionStartEvent : SessionEvent /// public partial class ToolExecutionPartialResultEvent : SessionEvent { + [JsonIgnore] public override string Type => "tool.execution_partial_result"; [JsonPropertyName("data")] - public ToolExecutionPartialResultData Data { get; set; } + public required ToolExecutionPartialResultData Data { get; set; } } /// @@ -402,10 +394,11 @@ public partial class ToolExecutionPartialResultEvent : SessionEvent /// public partial class ToolExecutionCompleteEvent : SessionEvent { + [JsonIgnore] public override string Type => "tool.execution_complete"; [JsonPropertyName("data")] - public ToolExecutionCompleteData Data { get; set; } + public required ToolExecutionCompleteData Data { get; set; } } /// @@ -413,10 +406,11 @@ public partial class ToolExecutionCompleteEvent : SessionEvent /// public partial class SubagentStartedEvent : SessionEvent { + [JsonIgnore] public override string Type => "subagent.started"; [JsonPropertyName("data")] - public SubagentStartedData Data { get; set; } + public required SubagentStartedData Data { get; set; } } /// @@ -424,10 +418,11 @@ public partial class SubagentStartedEvent : SessionEvent /// public partial class SubagentCompletedEvent : SessionEvent { + [JsonIgnore] public override string Type => "subagent.completed"; [JsonPropertyName("data")] - public SubagentCompletedData Data { get; set; } + public required SubagentCompletedData Data { get; set; } } /// @@ -435,10 +430,11 @@ public partial class SubagentCompletedEvent : SessionEvent /// public partial class SubagentFailedEvent : SessionEvent { + [JsonIgnore] public override string Type => "subagent.failed"; [JsonPropertyName("data")] - public SubagentFailedData Data { get; set; } + public required SubagentFailedData Data { get; set; } } /// @@ -446,10 +442,11 @@ public partial class SubagentFailedEvent : SessionEvent /// public partial class SubagentSelectedEvent : SessionEvent { + [JsonIgnore] public override string Type => "subagent.selected"; [JsonPropertyName("data")] - public SubagentSelectedData Data { get; set; } + public required SubagentSelectedData Data { get; set; } } /// @@ -457,10 +454,11 @@ public partial class SubagentSelectedEvent : SessionEvent /// public partial class HookStartEvent : SessionEvent { + [JsonIgnore] public override string Type => "hook.start"; [JsonPropertyName("data")] - public HookStartData Data { get; set; } + public required HookStartData Data { get; set; } } /// @@ -468,10 +466,11 @@ public partial class HookStartEvent : SessionEvent /// public partial class HookEndEvent : SessionEvent { + [JsonIgnore] public override string Type => "hook.end"; [JsonPropertyName("data")] - public HookEndData Data { get; set; } + public required HookEndData Data { get; set; } } /// @@ -479,32 +478,33 @@ public partial class HookEndEvent : SessionEvent /// public partial class SystemMessageEvent : SessionEvent { + [JsonIgnore] public override string Type => "system.message"; [JsonPropertyName("data")] - public SystemMessageData Data { get; set; } + public required SystemMessageData Data { get; set; } } public partial class SessionStartData { [JsonPropertyName("sessionId")] - public string SessionId { get; set; } + public required string SessionId { get; set; } [JsonPropertyName("version")] - public double Version { get; set; } + public required double Version { get; set; } [JsonPropertyName("producer")] - public string Producer { get; set; } + public required string Producer { get; set; } [JsonPropertyName("copilotVersion")] - public string CopilotVersion { get; set; } + public required string CopilotVersion { get; set; } [JsonPropertyName("startTime")] - public DateTimeOffset StartTime { get; set; } + public required DateTimeOffset StartTime { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("selectedModel")] - public string SelectedModel { get; set; } + public string? SelectedModel { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] @@ -514,10 +514,10 @@ public partial class SessionStartData public partial class SessionResumeData { [JsonPropertyName("resumeTime")] - public DateTimeOffset ResumeTime { get; set; } + public required DateTimeOffset ResumeTime { get; set; } [JsonPropertyName("eventCount")] - public double EventCount { get; set; } + public required double EventCount { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] @@ -527,14 +527,14 @@ public partial class SessionResumeData public partial class SessionErrorData { [JsonPropertyName("errorType")] - public string ErrorType { get; set; } + public required string ErrorType { get; set; } [JsonPropertyName("message")] - public string Message { get; set; } + public required string Message { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("stack")] - public string Stack { get; set; } + public string? Stack { get; set; } } public partial class SessionIdleData @@ -544,29 +544,29 @@ public partial class SessionIdleData public partial class SessionInfoData { [JsonPropertyName("infoType")] - public string InfoType { get; set; } + public required string InfoType { get; set; } [JsonPropertyName("message")] - public string Message { get; set; } + public required string Message { get; set; } } public partial class SessionModelChangeData { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("previousModel")] - public string PreviousModel { get; set; } + public string? PreviousModel { get; set; } [JsonPropertyName("newModel")] - public string NewModel { get; set; } + public required string NewModel { get; set; } } public partial class SessionHandoffData { [JsonPropertyName("handoffTime")] - public DateTimeOffset HandoffTime { get; set; } + public required DateTimeOffset HandoffTime { get; set; } [JsonPropertyName("sourceType")] - public SessionHandoffDataSourceType SourceType { get; set; } + public required SessionHandoffDataSourceType SourceType { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("repository")] @@ -574,54 +574,54 @@ public partial class SessionHandoffData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] - public string Context { get; set; } + public string? Context { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("summary")] - public string Summary { get; set; } + public string? Summary { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("remoteSessionId")] - public string RemoteSessionId { get; set; } + public string? RemoteSessionId { get; set; } } public partial class SessionTruncationData { [JsonPropertyName("tokenLimit")] - public double TokenLimit { get; set; } + public required double TokenLimit { get; set; } [JsonPropertyName("preTruncationTokensInMessages")] - public double PreTruncationTokensInMessages { get; set; } + public required double PreTruncationTokensInMessages { get; set; } [JsonPropertyName("preTruncationMessagesLength")] - public double PreTruncationMessagesLength { get; set; } + public required double PreTruncationMessagesLength { get; set; } [JsonPropertyName("postTruncationTokensInMessages")] - public double PostTruncationTokensInMessages { get; set; } + public required double PostTruncationTokensInMessages { get; set; } [JsonPropertyName("postTruncationMessagesLength")] - public double PostTruncationMessagesLength { get; set; } + public required double PostTruncationMessagesLength { get; set; } [JsonPropertyName("tokensRemovedDuringTruncation")] - public double TokensRemovedDuringTruncation { get; set; } + public required double TokensRemovedDuringTruncation { get; set; } [JsonPropertyName("messagesRemovedDuringTruncation")] - public double MessagesRemovedDuringTruncation { get; set; } + public required double MessagesRemovedDuringTruncation { get; set; } [JsonPropertyName("performedBy")] - public string PerformedBy { get; set; } + public required string PerformedBy { get; set; } } public partial class SessionUsageInfoData { [JsonPropertyName("tokenLimit")] - public double TokenLimit { get; set; } + public required double TokenLimit { get; set; } [JsonPropertyName("currentTokens")] - public double CurrentTokens { get; set; } + public required double CurrentTokens { get; set; } [JsonPropertyName("messagesLength")] - public double MessagesLength { get; set; } + public required double MessagesLength { get; set; } } public partial class SessionCompactionStartData @@ -631,11 +631,11 @@ public partial class SessionCompactionStartData public partial class SessionCompactionCompleteData { [JsonPropertyName("success")] - public bool Success { get; set; } + public required bool Success { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("error")] - public string Error { get; set; } + public string? Error { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("preCompactionTokens")] @@ -659,7 +659,7 @@ public partial class SessionCompactionCompleteData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("summaryContent")] - public string SummaryContent { get; set; } + public string? SummaryContent { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("compactionTokensUsed")] @@ -669,19 +669,19 @@ public partial class SessionCompactionCompleteData public partial class UserMessageData { [JsonPropertyName("content")] - public string Content { get; set; } + public required string Content { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("transformedContent")] - public string TransformedContent { get; set; } + public string? TransformedContent { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("attachments")] - public UserMessageDataAttachmentsItem[] Attachments { get; set; } + public UserMessageDataAttachmentsItem[]? Attachments { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("source")] - public string Source { get; set; } + public string? Source { get; set; } } public partial class PendingMessagesModifiedData @@ -691,57 +691,57 @@ public partial class PendingMessagesModifiedData public partial class AssistantTurnStartData { [JsonPropertyName("turnId")] - public string TurnId { get; set; } + public required string TurnId { get; set; } } public partial class AssistantIntentData { [JsonPropertyName("intent")] - public string Intent { get; set; } + public required string Intent { get; set; } } public partial class AssistantReasoningData { [JsonPropertyName("reasoningId")] - public string ReasoningId { get; set; } + public required string ReasoningId { get; set; } [JsonPropertyName("content")] - public string Content { get; set; } + public required string Content { get; set; } } public partial class AssistantReasoningDeltaData { [JsonPropertyName("reasoningId")] - public string ReasoningId { get; set; } + public required string ReasoningId { get; set; } [JsonPropertyName("deltaContent")] - public string DeltaContent { get; set; } + public required string DeltaContent { get; set; } } public partial class AssistantMessageData { [JsonPropertyName("messageId")] - public string MessageId { get; set; } + public required string MessageId { get; set; } [JsonPropertyName("content")] - public string Content { get; set; } + public required string Content { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolRequests")] - public AssistantMessageDataToolRequestsItem[] ToolRequests { get; set; } + public AssistantMessageDataToolRequestsItem[]? ToolRequests { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] - public string ParentToolCallId { get; set; } + public string? ParentToolCallId { get; set; } } public partial class AssistantMessageDeltaData { [JsonPropertyName("messageId")] - public string MessageId { get; set; } + public required string MessageId { get; set; } [JsonPropertyName("deltaContent")] - public string DeltaContent { get; set; } + public required string DeltaContent { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("totalResponseSizeBytes")] @@ -749,20 +749,20 @@ public partial class AssistantMessageDeltaData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] - public string ParentToolCallId { get; set; } + public string? ParentToolCallId { get; set; } } public partial class AssistantTurnEndData { [JsonPropertyName("turnId")] - public string TurnId { get; set; } + public required string TurnId { get; set; } } public partial class AssistantUsageData { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("model")] - public string Model { get; set; } + public string? Model { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("inputTokens")] @@ -790,73 +790,73 @@ public partial class AssistantUsageData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("initiator")] - public string Initiator { get; set; } + public string? Initiator { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("apiCallId")] - public string ApiCallId { get; set; } + public string? ApiCallId { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("providerCallId")] - public string ProviderCallId { get; set; } + public string? ProviderCallId { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("quotaSnapshots")] - public Dictionary QuotaSnapshots { get; set; } + public Dictionary? QuotaSnapshots { get; set; } } public partial class AbortData { [JsonPropertyName("reason")] - public string Reason { get; set; } + public required string Reason { get; set; } } public partial class ToolUserRequestedData { [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } + public required string ToolCallId { get; set; } [JsonPropertyName("toolName")] - public string ToolName { get; set; } + public required string ToolName { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("arguments")] - public object Arguments { get; set; } + public object? Arguments { get; set; } } public partial class ToolExecutionStartData { [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } + public required string ToolCallId { get; set; } [JsonPropertyName("toolName")] - public string ToolName { get; set; } + public required string ToolName { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("arguments")] - public object Arguments { get; set; } + public object? Arguments { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] - public string ParentToolCallId { get; set; } + public string? ParentToolCallId { get; set; } } public partial class ToolExecutionPartialResultData { [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } + public required string ToolCallId { get; set; } [JsonPropertyName("partialOutput")] - public string PartialOutput { get; set; } + public required string PartialOutput { get; set; } } public partial class ToolExecutionCompleteData { [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } + public required string ToolCallId { get; set; } [JsonPropertyName("success")] - public bool Success { get; set; } + public required bool Success { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("isUserRequested")] @@ -872,88 +872,88 @@ public partial class ToolExecutionCompleteData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolTelemetry")] - public Dictionary ToolTelemetry { get; set; } + public Dictionary? ToolTelemetry { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] - public string ParentToolCallId { get; set; } + public string? ParentToolCallId { get; set; } } public partial class SubagentStartedData { [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } + public required string ToolCallId { get; set; } [JsonPropertyName("agentName")] - public string AgentName { get; set; } + public required string AgentName { get; set; } [JsonPropertyName("agentDisplayName")] - public string AgentDisplayName { get; set; } + public required string AgentDisplayName { get; set; } [JsonPropertyName("agentDescription")] - public string AgentDescription { get; set; } + public required string AgentDescription { get; set; } } public partial class SubagentCompletedData { [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } + public required string ToolCallId { get; set; } [JsonPropertyName("agentName")] - public string AgentName { get; set; } + public required string AgentName { get; set; } } public partial class SubagentFailedData { [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } + public required string ToolCallId { get; set; } [JsonPropertyName("agentName")] - public string AgentName { get; set; } + public required string AgentName { get; set; } [JsonPropertyName("error")] - public string Error { get; set; } + public required string Error { get; set; } } public partial class SubagentSelectedData { [JsonPropertyName("agentName")] - public string AgentName { get; set; } + public required string AgentName { get; set; } [JsonPropertyName("agentDisplayName")] - public string AgentDisplayName { get; set; } + public required string AgentDisplayName { get; set; } [JsonPropertyName("tools")] - public string[] Tools { get; set; } + public string[]? Tools { get; set; } } public partial class HookStartData { [JsonPropertyName("hookInvocationId")] - public string HookInvocationId { get; set; } + public required string HookInvocationId { get; set; } [JsonPropertyName("hookType")] - public string HookType { get; set; } + public required string HookType { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("input")] - public object Input { get; set; } + public object? Input { get; set; } } public partial class HookEndData { [JsonPropertyName("hookInvocationId")] - public string HookInvocationId { get; set; } + public required string HookInvocationId { get; set; } [JsonPropertyName("hookType")] - public string HookType { get; set; } + public required string HookType { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("output")] - public object Output { get; set; } + public object? Output { get; set; } [JsonPropertyName("success")] - public bool Success { get; set; } + public required bool Success { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("error")] @@ -963,14 +963,14 @@ public partial class HookEndData public partial class SystemMessageData { [JsonPropertyName("content")] - public string Content { get; set; } + public required string Content { get; set; } [JsonPropertyName("role")] - public SystemMessageDataRole Role { get; set; } + public required SystemMessageDataRole Role { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("metadata")] @@ -980,87 +980,87 @@ public partial class SystemMessageData public partial class SessionStartDataContext { [JsonPropertyName("cwd")] - public string Cwd { get; set; } + public required string Cwd { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("gitRoot")] - public string GitRoot { get; set; } + public string? GitRoot { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("repository")] - public string Repository { get; set; } + public string? Repository { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("branch")] - public string Branch { get; set; } + public string? Branch { get; set; } } public partial class SessionResumeDataContext { [JsonPropertyName("cwd")] - public string Cwd { get; set; } + public required string Cwd { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("gitRoot")] - public string GitRoot { get; set; } + public string? GitRoot { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("repository")] - public string Repository { get; set; } + public string? Repository { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("branch")] - public string Branch { get; set; } + public string? Branch { get; set; } } public partial class SessionHandoffDataRepository { [JsonPropertyName("owner")] - public string Owner { get; set; } + public required string Owner { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public required string Name { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("branch")] - public string Branch { get; set; } + public string? Branch { get; set; } } public partial class SessionCompactionCompleteDataCompactionTokensUsed { [JsonPropertyName("input")] - public double Input { get; set; } + public required double Input { get; set; } [JsonPropertyName("output")] - public double Output { get; set; } + public required double Output { get; set; } [JsonPropertyName("cachedInput")] - public double CachedInput { get; set; } + public required double CachedInput { get; set; } } public partial class UserMessageDataAttachmentsItem { [JsonPropertyName("type")] - public UserMessageDataAttachmentsItemType Type { get; set; } + public required UserMessageDataAttachmentsItemType Type { get; set; } [JsonPropertyName("path")] - public string Path { get; set; } + public required string Path { get; set; } [JsonPropertyName("displayName")] - public string DisplayName { get; set; } + public required string DisplayName { get; set; } } public partial class AssistantMessageDataToolRequestsItem { [JsonPropertyName("toolCallId")] - public string ToolCallId { get; set; } + public required string ToolCallId { get; set; } [JsonPropertyName("name")] - public string Name { get; set; } + public required string Name { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("arguments")] - public object Arguments { get; set; } + public object? Arguments { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("type")] @@ -1070,86 +1070,86 @@ public partial class AssistantMessageDataToolRequestsItem public partial class ToolExecutionCompleteDataResult { [JsonPropertyName("content")] - public string Content { get; set; } + public required string Content { get; set; } } public partial class ToolExecutionCompleteDataError { [JsonPropertyName("message")] - public string Message { get; set; } + public required string Message { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("code")] - public string Code { get; set; } + public string? Code { get; set; } } public partial class HookEndDataError { [JsonPropertyName("message")] - public string Message { get; set; } + public required string Message { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("stack")] - public string Stack { get; set; } + public string? Stack { get; set; } } public partial class SystemMessageDataMetadata { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("promptVersion")] - public string PromptVersion { get; set; } + public string? PromptVersion { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("variables")] - public Dictionary Variables { get; set; } + public Dictionary? Variables { get; set; } } + [JsonConverter(typeof(JsonStringEnumConverter))] public enum SessionHandoffDataSourceType { + [JsonStringEnumMemberName("remote")] Remote, + [JsonStringEnumMemberName("local")] Local, } + [JsonConverter(typeof(JsonStringEnumConverter))] public enum UserMessageDataAttachmentsItemType { + [JsonStringEnumMemberName("file")] File, + [JsonStringEnumMemberName("directory")] Directory, } + [JsonConverter(typeof(JsonStringEnumConverter))] public enum AssistantMessageDataToolRequestsItemType { + [JsonStringEnumMemberName("function")] Function, + [JsonStringEnumMemberName("custom")] Custom, } + [JsonConverter(typeof(JsonStringEnumConverter))] public enum SystemMessageDataRole { + [JsonStringEnumMemberName("system")] System, + [JsonStringEnumMemberName("developer")] Developer, } internal static class SerializerOptions { /// - /// Default options with SessionEventConverter for polymorphic deserialization. + /// Default options for polymorphic deserialization. /// public static readonly JsonSerializerOptions Default = new() { + AllowOutOfOrderMetadataProperties = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } - }; - - /// - /// Options without SessionEventConverter, used internally by the converter to avoid recursion. - /// - internal static readonly JsonSerializerOptions WithoutConverter = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; } -} - -#pragma warning restore CS8618 \ No newline at end of file +} \ No newline at end of file diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 210409f9..011cb99b 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -30,9 +30,9 @@ namespace GitHub.Copilot.SDK; /// // Subscribe to events /// using var subscription = session.On(evt => /// { -/// if (evt.Type == "assistant.message") +/// if (evt is AssistantMessageEvent assistantMessage) /// { -/// Console.WriteLine($"Assistant: {evt.Data?.Content}"); +/// Console.WriteLine($"Assistant: {assistantMessage.Data?.Content}"); /// } /// }); /// @@ -149,18 +149,20 @@ public async Task SendAsync(MessageOptions options, CancellationToken ca void Handler(SessionEvent evt) { - if (evt is AssistantMessageEvent assistantMessage) + switch (evt) { - lastAssistantMessage = assistantMessage; - } - else if (evt.Type == "session.idle") - { - tcs.TrySetResult(lastAssistantMessage); - } - else if (evt is SessionErrorEvent errorEvent) - { - var message = errorEvent.Data?.Message ?? "session error"; - tcs.TrySetException(new InvalidOperationException($"Session error: {message}")); + case AssistantMessageEvent assistantMessage: + lastAssistantMessage = assistantMessage; + break; + + case SessionIdleEvent: + tcs.TrySetResult(lastAssistantMessage); + break; + + case SessionErrorEvent errorEvent: + var message = errorEvent.Data?.Message ?? "session error"; + tcs.TrySetException(new InvalidOperationException($"Session error: {message}")); + break; } } @@ -194,12 +196,12 @@ void Handler(SessionEvent evt) /// /// using var subscription = session.On(evt => /// { - /// switch (evt.Type) + /// switch (evt) /// { - /// case "assistant.message": + /// case AssistantMessageEvent: /// Console.WriteLine($"Assistant: {evt.Data?.Content}"); /// break; - /// case "session.error": + /// case SessionErrorEvent: /// Console.WriteLine($"Error: {evt.Data?.Message}"); /// break; /// } @@ -328,7 +330,7 @@ internal async Task HandlePermissionRequestAsync(JsonEl /// var events = await session.GetMessagesAsync(); /// foreach (var evt in events) /// { - /// if (evt.Type == "assistant.message") + /// if (evt is AssistantMessageEvent) /// { /// Console.WriteLine($"Assistant: {evt.Data?.Content}"); /// } diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index f109a931..91fcbcb2 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -8,11 +8,16 @@ namespace GitHub.Copilot.SDK; +[JsonConverter(typeof(JsonStringEnumConverter))] public enum ConnectionState { + [JsonStringEnumMemberName("disconnected")] Disconnected, + [JsonStringEnumMemberName("connecting")] Connecting, + [JsonStringEnumMemberName("connected")] Connected, + [JsonStringEnumMemberName("error")] Error } @@ -105,9 +110,12 @@ public class PermissionInvocation public delegate Task PermissionHandler(PermissionRequest request, PermissionInvocation invocation); +[JsonConverter(typeof(JsonStringEnumConverter))] public enum SystemMessageMode { + [JsonStringEnumMemberName("append")] Append, + [JsonStringEnumMemberName("replace")] Replace } diff --git a/nodejs/scripts/generate-csharp-session-types.ts b/nodejs/scripts/generate-csharp-session-types.ts index 46a4914f..24a0bf6e 100644 --- a/nodejs/scripts/generate-csharp-session-types.ts +++ b/nodejs/scripts/generate-csharp-session-types.ts @@ -98,7 +98,7 @@ function schemaTypeToCSharp( if (type === "string") { if (format === "uuid") return required ? "Guid" : "Guid?"; if (format === "date-time") return required ? "DateTimeOffset" : "DateTimeOffset?"; - return "string"; + return required ? "string" : "string?"; } if (type === "number" || type === "integer") { return required ? "double" : "double?"; @@ -109,21 +109,21 @@ function schemaTypeToCSharp( if (type === "array") { const items = schema.items as JSONSchema7 | undefined; const itemType = items ? schemaTypeToCSharp(items, true, knownTypes) : "object"; - return `${itemType}[]`; + return required ? `${itemType}[]` : `${itemType}[]?`; } if (type === "object") { if (schema.additionalProperties) { const valueSchema = schema.additionalProperties; if (typeof valueSchema === "object") { const valueType = schemaTypeToCSharp(valueSchema as JSONSchema7, true, knownTypes); - return `Dictionary`; + return required ? `Dictionary` : `Dictionary?`; } - return "Dictionary"; + return required ? "Dictionary" : "Dictionary?"; } - return "object"; + return required ? "object" : "object?"; } - return "object"; + return required ? "object" : "object?"; } /** @@ -167,13 +167,14 @@ function getOrCreateEnum( const enumName = generateEnumName(parentClassName, propName); generatedEnums.set(enumName, { enumName, values }); - // Generate the enum code - // Use [JsonStringEnumConverter(JsonNamingPolicy.CamelCase)] to serialize PascalCase enum members to camelCase JSON values + // Generate the enum code with JsonConverter and JsonStringEnumMemberName attributes const lines: string[] = []; + lines.push(` [JsonConverter(typeof(JsonStringEnumConverter<${enumName}>))]`); lines.push(` public enum ${enumName}`); lines.push(` {`); for (const value of values) { const memberName = toPascalCaseEnumMember(value); + lines.push(` [JsonStringEnumMemberName("${value}")]`); lines.push(` ${memberName},`); } lines.push(` }`); @@ -268,13 +269,16 @@ function generateDataClass( enumOutput ); + const isNullableType = csharpType.endsWith("?"); if (!isRequired) { lines.push( `${indent} [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]` ); } lines.push(`${indent} [JsonPropertyName("${propName}")]`); - lines.push(`${indent} public ${csharpType} ${csharpName} { get; set; }`); + + const requiredModifier = isRequired && !isNullableType ? "required " : ""; + lines.push(`${indent} public ${requiredModifier}${csharpType} ${csharpName} { get; set; }`); lines.push(""); } @@ -328,7 +332,10 @@ function generateNestedClass( ); } lines.push(`${indent} [JsonPropertyName("${propName}")]`); - lines.push(`${indent} public ${csharpType} ${csharpName} { get; set; }`); + + const isNullableType = csharpType.endsWith("?"); + const requiredModifier = isRequired && !isNullableType ? "required " : ""; + lines.push(`${indent} public ${requiredModifier}${csharpType} ${csharpName} { get; set; }`); lines.push(""); } } @@ -358,24 +365,27 @@ function resolvePropertyType( ): string { // Handle anyOf - simplify to nullable of the non-null type or object if (propSchema.anyOf) { + const hasNull = propSchema.anyOf.some( + (s) => typeof s === "object" && (s as JSONSchema7).type === "null" + ); const nonNullTypes = propSchema.anyOf.filter( (s) => typeof s === "object" && (s as JSONSchema7).type !== "null" ); if (nonNullTypes.length === 1) { - // Simple nullable - recurse with the inner type + // Simple nullable - recurse with the inner type, marking as not required if null is an option return resolvePropertyType( nonNullTypes[0] as JSONSchema7, parentClassName, propName, - false, + isRequired && !hasNull, indent, knownTypes, nestedClasses, enumOutput ); } - // Complex union - use object - return "object"; + // Complex union - use object, nullable if null is in the union or property is not required + return (hasNull || !isRequired) ? "object?" : "object"; } // Handle enum types @@ -420,7 +430,7 @@ function resolvePropertyType( enumOutput ); nestedClasses.set(itemClassName, nestedCode); - return `${itemClassName}[]`; + return isRequired ? `${itemClassName}[]` : `${itemClassName}[]?`; } // Array of enums @@ -431,7 +441,7 @@ function resolvePropertyType( items.enum as string[], enumOutput ); - return `${enumName}[]`; + return isRequired ? `${enumName}[]` : `${enumName}[]?`; } // Simple array type @@ -443,7 +453,7 @@ function resolvePropertyType( propName, enumOutput ); - return `${itemType}[]`; + return isRequired ? `${itemType}[]` : `${itemType}[]?`; } // Default: use basic type mapping @@ -489,78 +499,33 @@ export function generateCSharpSessionTypes(schema: JSONSchema7, generatedAt: str // #nullable enable -#pragma warning disable CS8618 namespace GitHub.Copilot.SDK { using System; using System.Collections.Generic; using System.Text.Json; - using System.Text.Json.Nodes; using System.Text.Json.Serialization; `); - // Generate the custom converter class + // Generate base class with JsonPolymorphic attributes lines.push(`${indent}/// `); lines.push( - `${indent}/// Custom JSON converter for SessionEvent that handles discriminator appearing anywhere in JSON.` + `${indent}/// Base class for all session events with polymorphic JSON serialization.` ); lines.push(`${indent}/// `); - lines.push(`${indent}internal class SessionEventConverter : JsonConverter`); - lines.push(`${indent}{`); - lines.push(`${indent} private static readonly Dictionary TypeMap = new()`); - lines.push(`${indent} {`); - for (const variant of variants) { - lines.push(`${indent} ["${variant.typeName}"] = typeof(${variant.className}),`); - } - lines.push(`${indent} };`); - lines.push(""); - lines.push( - `${indent} public override SessionEvent? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)` - ); - lines.push(`${indent} {`); - lines.push( - `${indent} // Parse as JsonNode to find the discriminator regardless of property order` - ); - lines.push(`${indent} var node = JsonNode.Parse(ref reader);`); - lines.push(`${indent} if (node is not JsonObject obj)`); - lines.push(`${indent} throw new JsonException("Expected JSON object");`); - lines.push(""); - lines.push(`${indent} var typeProp = obj["type"]?.GetValue();`); - lines.push(`${indent} if (string.IsNullOrEmpty(typeProp))`); - lines.push( - `${indent} throw new JsonException("Missing 'type' discriminator property");` - ); - lines.push(""); - lines.push(`${indent} if (!TypeMap.TryGetValue(typeProp, out var targetType))`); - lines.push(`${indent} throw new JsonException($"Unknown event type: {typeProp}");`); - lines.push(""); - lines.push( - `${indent} // Deserialize to the concrete type without using this converter (to avoid recursion)` - ); - lines.push( - `${indent} return (SessionEvent?)obj.Deserialize(targetType, SerializerOptions.WithoutConverter);` - ); - lines.push(`${indent} }`); - lines.push(""); - lines.push( - `${indent} public override void Write(Utf8JsonWriter writer, SessionEvent value, JsonSerializerOptions options)` - ); - lines.push(`${indent} {`); + lines.push(`${indent}[JsonPolymorphic(`); + lines.push(`${indent} TypeDiscriminatorPropertyName = "type", `); lines.push( - `${indent} JsonSerializer.Serialize(writer, value, value.GetType(), SerializerOptions.WithoutConverter);` + `${indent} UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization)]` ); - lines.push(`${indent} }`); - lines.push(`${indent}}`); - lines.push(""); - // Generate base class (no longer needs JsonPolymorphic attributes since we use custom converter) - lines.push(`${indent}/// `); - lines.push( - `${indent}/// Base class for all session events with polymorphic JSON serialization.` - ); - lines.push(`${indent}/// `); - lines.push(`${indent}[JsonConverter(typeof(SessionEventConverter))]`); + // Generate JsonDerivedType attributes for each variant (alphabetized) + for (const variant of [...variants].sort((a, b) => a.typeName.localeCompare(b.typeName))) { + lines.push( + `${indent}[JsonDerivedType(typeof(${variant.className}), "${variant.typeName}")]` + ); + } lines.push(`${indent}public abstract partial class SessionEvent`); lines.push(`${indent}{`); @@ -580,7 +545,7 @@ namespace GitHub.Copilot.SDK lines.push(`${indent} /// `); lines.push(`${indent} /// The event type discriminator.`); lines.push(`${indent} /// `); - lines.push(`${indent} [JsonPropertyName("type")]`); + lines.push(`${indent} [JsonIgnore]`); lines.push(`${indent} public abstract string Type { get; }`); lines.push(""); lines.push(`${indent} public static SessionEvent FromJson(string json) =>`); @@ -602,10 +567,11 @@ namespace GitHub.Copilot.SDK lines.push(`${indent}/// `); lines.push(`${indent}public partial class ${variant.className} : SessionEvent`); lines.push(`${indent}{`); + lines.push(`${indent} [JsonIgnore]`); lines.push(`${indent} public override string Type => "${variant.typeName}";`); lines.push(""); lines.push(`${indent} [JsonPropertyName("data")]`); - lines.push(`${indent} public ${variant.dataClassName} Data { get; set; }`); + lines.push(`${indent} public required ${variant.dataClassName} Data { get; set; }`); lines.push(`${indent}}`); lines.push(""); } @@ -632,40 +598,18 @@ namespace GitHub.Copilot.SDK lines.push(`${indent}internal static class SerializerOptions`); lines.push(`${indent}{`); lines.push(`${indent} /// `); - lines.push( - `${indent} /// Default options with SessionEventConverter for polymorphic deserialization.` - ); + lines.push(`${indent} /// Default options for polymorphic deserialization.`); lines.push(`${indent} /// `); lines.push(`${indent} public static readonly JsonSerializerOptions Default = new()`); lines.push(`${indent} {`); + lines.push(`${indent} AllowOutOfOrderMetadataProperties = true,`); lines.push(`${indent} PropertyNamingPolicy = JsonNamingPolicy.CamelCase,`); - lines.push(`${indent} DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,`); - lines.push( - `${indent} Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }` - ); - lines.push(`${indent} };`); - lines.push(""); - lines.push(`${indent} /// `); - lines.push( - `${indent} /// Options without SessionEventConverter, used internally by the converter to avoid recursion.` - ); - lines.push(`${indent} /// `); - lines.push( - `${indent} internal static readonly JsonSerializerOptions WithoutConverter = new()` - ); - lines.push(`${indent} {`); - lines.push(`${indent} PropertyNamingPolicy = JsonNamingPolicy.CamelCase,`); - lines.push(`${indent} DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,`); - lines.push( - `${indent} Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }` - ); + lines.push(`${indent} DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull`); lines.push(`${indent} };`); lines.push(`${indent}}`); // Close namespace lines.push(`}`); - lines.push(""); - lines.push(`#pragma warning restore CS8618`); return lines.join("\n"); }