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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dotnet/src/Generated/SessionEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3527,4 +3527,5 @@ public enum PermissionCompletedDataResultKind
[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelectionEnd))]
[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelectionStart))]
[JsonSerializable(typeof(UserMessageEvent))]
[JsonSerializable(typeof(JsonElement))]
internal partial class SessionEventsJsonContext : JsonSerializerContext;
180 changes: 180 additions & 0 deletions dotnet/test/SessionEventSerializationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/

using System.Collections.Generic;
using System.Text.Json;
using Xunit;

namespace GitHub.Copilot.SDK.Test;

public class SessionEventSerializationTests
{
public static TheoryData<SessionEvent, string> JsonElementBackedEvents => new()
{
{
new AssistantMessageEvent
{
Id = Guid.Parse("11111111-1111-1111-1111-111111111111"),
Timestamp = DateTimeOffset.Parse("2026-03-15T21:26:02.642Z"),
ParentId = Guid.Parse("22222222-2222-2222-2222-222222222222"),
Data = new AssistantMessageData
{
MessageId = "msg-1",
Content = "",
ToolRequests =
[
new AssistantMessageDataToolRequestsItem
{
ToolCallId = "call-1",
Name = "view",
Arguments = ParseJsonElement("""{"path":"README.md"}"""),
Type = AssistantMessageDataToolRequestsItemType.Function,
},
],
},
},
"assistant.message"
},
{
new ToolExecutionStartEvent
{
Id = Guid.Parse("33333333-3333-3333-3333-333333333333"),
Timestamp = DateTimeOffset.Parse("2026-03-15T21:26:02.642Z"),
ParentId = Guid.Parse("44444444-4444-4444-4444-444444444444"),
Data = new ToolExecutionStartData
{
ToolCallId = "call-1",
ToolName = "view",
Arguments = ParseJsonElement("""{"path":"README.md"}"""),
},
},
"tool.execution_start"
},
{
new ToolExecutionCompleteEvent
{
Id = Guid.Parse("55555555-5555-5555-5555-555555555555"),
Timestamp = DateTimeOffset.Parse("2026-03-15T21:26:02.642Z"),
ParentId = Guid.Parse("66666666-6666-6666-6666-666666666666"),
Data = new ToolExecutionCompleteData
{
ToolCallId = "call-1",
Success = true,
Result = new ToolExecutionCompleteDataResult
{
Content = "ok",
DetailedContent = "ok",
},
ToolTelemetry = new Dictionary<string, object>
{
["properties"] = ParseJsonElement("""{"command":"view"}"""),
["metrics"] = ParseJsonElement("""{"resultLength":2}"""),
},
},
},
"tool.execution_complete"
},
{
new SessionShutdownEvent
{
Id = Guid.Parse("77777777-7777-7777-7777-777777777777"),
Timestamp = DateTimeOffset.Parse("2026-03-15T21:26:52.987Z"),
ParentId = Guid.Parse("88888888-8888-8888-8888-888888888888"),
Data = new SessionShutdownData
{
ShutdownType = SessionShutdownDataShutdownType.Routine,
TotalPremiumRequests = 1,
TotalApiDurationMs = 100,
SessionStartTime = 1773609948932,
CodeChanges = new SessionShutdownDataCodeChanges
{
LinesAdded = 1,
LinesRemoved = 0,
FilesModified = ["README.md"],
},
ModelMetrics = new Dictionary<string, object>
{
["gpt-5.4"] = ParseJsonElement("""
{
"requests": {
"count": 1,
"cost": 1
},
"usage": {
"inputTokens": 10,
"outputTokens": 5,
"cacheReadTokens": 0,
"cacheWriteTokens": 0
}
}
"""),
},
CurrentModel = "gpt-5.4",
},
},
"session.shutdown"
}
};

private static JsonElement ParseJsonElement(string json)
{
using var document = JsonDocument.Parse(json);
return document.RootElement.Clone();
}

[Theory]
[MemberData(nameof(JsonElementBackedEvents))]
public void SessionEvent_ToJson_RoundTrips_JsonElementBackedPayloads(SessionEvent sessionEvent, string expectedType)
{
var serialized = sessionEvent.ToJson();

using var document = JsonDocument.Parse(serialized);
var root = document.RootElement;

Assert.Equal(expectedType, root.GetProperty("type").GetString());

switch (expectedType)
{
case "assistant.message":
Assert.Equal(
"README.md",
root.GetProperty("data")
.GetProperty("toolRequests")[0]
.GetProperty("arguments")
.GetProperty("path")
.GetString());
break;

case "tool.execution_start":
Assert.Equal(
"README.md",
root.GetProperty("data")
.GetProperty("arguments")
.GetProperty("path")
.GetString());
break;

case "tool.execution_complete":
Assert.Equal(
"view",
root.GetProperty("data")
.GetProperty("toolTelemetry")
.GetProperty("properties")
.GetProperty("command")
.GetString());
break;

case "session.shutdown":
Assert.Equal(
1,
root.GetProperty("data")
.GetProperty("modelMetrics")
.GetProperty("gpt-5.4")
.GetProperty("requests")
.GetProperty("count")
.GetInt32());
break;
}
}
}
1 change: 1 addition & 0 deletions scripts/codegen/csharp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ namespace GitHub.Copilot.SDK;
const types = ["SessionEvent", ...variants.flatMap((v) => [v.className, v.dataClassName]), ...nestedClasses.keys()].sort();
lines.push(`[JsonSourceGenerationOptions(`, ` JsonSerializerDefaults.Web,`, ` AllowOutOfOrderMetadataProperties = true,`, ` NumberHandling = JsonNumberHandling.AllowReadingFromString,`, ` DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]`);
for (const t of types) lines.push(`[JsonSerializable(typeof(${t}))]`);
lines.push(`[JsonSerializable(typeof(JsonElement))]`);
lines.push(`internal partial class SessionEventsJsonContext : JsonSerializerContext;`);

return lines.join("\n");
Expand Down
Loading