Skip to content

Commit 3e443fe

Browse files
authored
Fix serialization of SessionEvent (#868)
1 parent d96488c commit 3e443fe

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed

dotnet/src/Generated/SessionEvents.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3527,4 +3527,5 @@ public enum PermissionCompletedDataResultKind
35273527
[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelectionEnd))]
35283528
[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelectionStart))]
35293529
[JsonSerializable(typeof(UserMessageEvent))]
3530+
[JsonSerializable(typeof(JsonElement))]
35303531
internal partial class SessionEventsJsonContext : JsonSerializerContext;
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
using System.Collections.Generic;
6+
using System.Text.Json;
7+
using Xunit;
8+
9+
namespace GitHub.Copilot.SDK.Test;
10+
11+
public class SessionEventSerializationTests
12+
{
13+
public static TheoryData<SessionEvent, string> JsonElementBackedEvents => new()
14+
{
15+
{
16+
new AssistantMessageEvent
17+
{
18+
Id = Guid.Parse("11111111-1111-1111-1111-111111111111"),
19+
Timestamp = DateTimeOffset.Parse("2026-03-15T21:26:02.642Z"),
20+
ParentId = Guid.Parse("22222222-2222-2222-2222-222222222222"),
21+
Data = new AssistantMessageData
22+
{
23+
MessageId = "msg-1",
24+
Content = "",
25+
ToolRequests =
26+
[
27+
new AssistantMessageDataToolRequestsItem
28+
{
29+
ToolCallId = "call-1",
30+
Name = "view",
31+
Arguments = ParseJsonElement("""{"path":"README.md"}"""),
32+
Type = AssistantMessageDataToolRequestsItemType.Function,
33+
},
34+
],
35+
},
36+
},
37+
"assistant.message"
38+
},
39+
{
40+
new ToolExecutionStartEvent
41+
{
42+
Id = Guid.Parse("33333333-3333-3333-3333-333333333333"),
43+
Timestamp = DateTimeOffset.Parse("2026-03-15T21:26:02.642Z"),
44+
ParentId = Guid.Parse("44444444-4444-4444-4444-444444444444"),
45+
Data = new ToolExecutionStartData
46+
{
47+
ToolCallId = "call-1",
48+
ToolName = "view",
49+
Arguments = ParseJsonElement("""{"path":"README.md"}"""),
50+
},
51+
},
52+
"tool.execution_start"
53+
},
54+
{
55+
new ToolExecutionCompleteEvent
56+
{
57+
Id = Guid.Parse("55555555-5555-5555-5555-555555555555"),
58+
Timestamp = DateTimeOffset.Parse("2026-03-15T21:26:02.642Z"),
59+
ParentId = Guid.Parse("66666666-6666-6666-6666-666666666666"),
60+
Data = new ToolExecutionCompleteData
61+
{
62+
ToolCallId = "call-1",
63+
Success = true,
64+
Result = new ToolExecutionCompleteDataResult
65+
{
66+
Content = "ok",
67+
DetailedContent = "ok",
68+
},
69+
ToolTelemetry = new Dictionary<string, object>
70+
{
71+
["properties"] = ParseJsonElement("""{"command":"view"}"""),
72+
["metrics"] = ParseJsonElement("""{"resultLength":2}"""),
73+
},
74+
},
75+
},
76+
"tool.execution_complete"
77+
},
78+
{
79+
new SessionShutdownEvent
80+
{
81+
Id = Guid.Parse("77777777-7777-7777-7777-777777777777"),
82+
Timestamp = DateTimeOffset.Parse("2026-03-15T21:26:52.987Z"),
83+
ParentId = Guid.Parse("88888888-8888-8888-8888-888888888888"),
84+
Data = new SessionShutdownData
85+
{
86+
ShutdownType = SessionShutdownDataShutdownType.Routine,
87+
TotalPremiumRequests = 1,
88+
TotalApiDurationMs = 100,
89+
SessionStartTime = 1773609948932,
90+
CodeChanges = new SessionShutdownDataCodeChanges
91+
{
92+
LinesAdded = 1,
93+
LinesRemoved = 0,
94+
FilesModified = ["README.md"],
95+
},
96+
ModelMetrics = new Dictionary<string, object>
97+
{
98+
["gpt-5.4"] = ParseJsonElement("""
99+
{
100+
"requests": {
101+
"count": 1,
102+
"cost": 1
103+
},
104+
"usage": {
105+
"inputTokens": 10,
106+
"outputTokens": 5,
107+
"cacheReadTokens": 0,
108+
"cacheWriteTokens": 0
109+
}
110+
}
111+
"""),
112+
},
113+
CurrentModel = "gpt-5.4",
114+
},
115+
},
116+
"session.shutdown"
117+
}
118+
};
119+
120+
private static JsonElement ParseJsonElement(string json)
121+
{
122+
using var document = JsonDocument.Parse(json);
123+
return document.RootElement.Clone();
124+
}
125+
126+
[Theory]
127+
[MemberData(nameof(JsonElementBackedEvents))]
128+
public void SessionEvent_ToJson_RoundTrips_JsonElementBackedPayloads(SessionEvent sessionEvent, string expectedType)
129+
{
130+
var serialized = sessionEvent.ToJson();
131+
132+
using var document = JsonDocument.Parse(serialized);
133+
var root = document.RootElement;
134+
135+
Assert.Equal(expectedType, root.GetProperty("type").GetString());
136+
137+
switch (expectedType)
138+
{
139+
case "assistant.message":
140+
Assert.Equal(
141+
"README.md",
142+
root.GetProperty("data")
143+
.GetProperty("toolRequests")[0]
144+
.GetProperty("arguments")
145+
.GetProperty("path")
146+
.GetString());
147+
break;
148+
149+
case "tool.execution_start":
150+
Assert.Equal(
151+
"README.md",
152+
root.GetProperty("data")
153+
.GetProperty("arguments")
154+
.GetProperty("path")
155+
.GetString());
156+
break;
157+
158+
case "tool.execution_complete":
159+
Assert.Equal(
160+
"view",
161+
root.GetProperty("data")
162+
.GetProperty("toolTelemetry")
163+
.GetProperty("properties")
164+
.GetProperty("command")
165+
.GetString());
166+
break;
167+
168+
case "session.shutdown":
169+
Assert.Equal(
170+
1,
171+
root.GetProperty("data")
172+
.GetProperty("modelMetrics")
173+
.GetProperty("gpt-5.4")
174+
.GetProperty("requests")
175+
.GetProperty("count")
176+
.GetInt32());
177+
break;
178+
}
179+
}
180+
}

scripts/codegen/csharp.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ namespace GitHub.Copilot.SDK;
575575
const types = ["SessionEvent", ...variants.flatMap((v) => [v.className, v.dataClassName]), ...nestedClasses.keys()].sort();
576576
lines.push(`[JsonSourceGenerationOptions(`, ` JsonSerializerDefaults.Web,`, ` AllowOutOfOrderMetadataProperties = true,`, ` NumberHandling = JsonNumberHandling.AllowReadingFromString,`, ` DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]`);
577577
for (const t of types) lines.push(`[JsonSerializable(typeof(${t}))]`);
578+
lines.push(`[JsonSerializable(typeof(JsonElement))]`);
578579
lines.push(`internal partial class SessionEventsJsonContext : JsonSerializerContext;`);
579580

580581
return lines.join("\n");

0 commit comments

Comments
 (0)