Skip to content

Commit f8cf846

Browse files
stephentoubCopilot
andauthored
Derive session event envelopes from schema (#1184)
* Generate C# session event envelope properties Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unused session event initializer helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Derive session event envelopes across SDKs Share schema-level session event envelope discovery across code generators and use it for Go and Python session-event wrappers so top-level envelope fields like agentId round-trip consistently. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fcdd615 commit f8cf846

11 files changed

Lines changed: 395 additions & 144 deletions

dotnet/src/Generated/SessionEvents.cs

Lines changed: 13 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dotnet/test/ForwardCompatibilityTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public void FromJson_KnownEventType_DeserializesNormally()
2020
"id": "00000000-0000-0000-0000-000000000001",
2121
"timestamp": "2026-01-01T00:00:00Z",
2222
"parentId": null,
23+
"agentId": "agent-1",
2324
"type": "user.message",
2425
"data": {
2526
"content": "Hello"
@@ -31,6 +32,7 @@ public void FromJson_KnownEventType_DeserializesNormally()
3132

3233
Assert.IsType<UserMessageEvent>(result);
3334
Assert.Equal("user.message", result.Type);
35+
Assert.Equal("agent-1", result.AgentId);
3436
}
3537

3638
[Fact]
@@ -41,6 +43,7 @@ public void FromJson_UnknownEventType_ReturnsBaseSessionEvent()
4143
"id": "12345678-1234-1234-1234-123456789abc",
4244
"timestamp": "2026-06-15T10:30:00Z",
4345
"parentId": "abcdefab-abcd-abcd-abcd-abcdefabcdef",
46+
"agentId": "future-agent",
4447
"type": "future.feature_from_server",
4548
"data": { "key": "value" }
4649
}
@@ -50,6 +53,7 @@ public void FromJson_UnknownEventType_ReturnsBaseSessionEvent()
5053

5154
Assert.IsType<SessionEvent>(result);
5255
Assert.Equal("unknown", result.Type);
56+
Assert.Equal("future-agent", result.AgentId);
5357
}
5458

5559
[Fact]

dotnet/test/SessionEventSerializationTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class SessionEventSerializationTests
1818
Id = Guid.Parse("11111111-1111-1111-1111-111111111111"),
1919
Timestamp = DateTimeOffset.Parse("2026-03-15T21:26:02.642Z"),
2020
ParentId = Guid.Parse("22222222-2222-2222-2222-222222222222"),
21+
AgentId = "agent-1",
2122
Data = new AssistantMessageData
2223
{
2324
MessageId = "msg-1",
@@ -134,6 +135,7 @@ public void SessionEvent_ToJson_RoundTrips_JsonElementBackedPayloads(SessionEven
134135
switch (expectedType)
135136
{
136137
case "assistant.message":
138+
Assert.Equal("agent-1", root.GetProperty("agentId").GetString());
137139
Assert.Equal(
138140
"README.md",
139141
root.GetProperty("data")

go/generated_session_events.go

Lines changed: 20 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package copilot
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
)
7+
8+
func TestSessionEventAgentIDRoundTripsKnownEvent(t *testing.T) {
9+
event, err := UnmarshalSessionEvent([]byte(`{
10+
"id": "00000000-0000-0000-0000-000000000001",
11+
"timestamp": "2026-01-01T00:00:00Z",
12+
"parentId": null,
13+
"agentId": "agent-1",
14+
"type": "user.message",
15+
"data": {
16+
"content": "Hello"
17+
}
18+
}`))
19+
if err != nil {
20+
t.Fatalf("failed to unmarshal session event: %v", err)
21+
}
22+
23+
if event.AgentID == nil || *event.AgentID != "agent-1" {
24+
t.Fatalf("expected agent ID to round-trip, got %v", event.AgentID)
25+
}
26+
if _, ok := event.Data.(*UserMessageData); !ok {
27+
t.Fatalf("expected user message data, got %T", event.Data)
28+
}
29+
30+
data, err := event.Marshal()
31+
if err != nil {
32+
t.Fatalf("failed to marshal session event: %v", err)
33+
}
34+
35+
var serialized map[string]any
36+
if err := json.Unmarshal(data, &serialized); err != nil {
37+
t.Fatalf("failed to unmarshal serialized session event: %v", err)
38+
}
39+
if serialized["agentId"] != "agent-1" {
40+
t.Fatalf("expected serialized agentId to round-trip, got %v", serialized["agentId"])
41+
}
42+
}
43+
44+
func TestSessionEventAgentIDRoundTripsUnknownEvent(t *testing.T) {
45+
event, err := UnmarshalSessionEvent([]byte(`{
46+
"id": "00000000-0000-0000-0000-000000000002",
47+
"timestamp": "2026-01-01T00:00:00Z",
48+
"parentId": null,
49+
"agentId": "future-agent",
50+
"type": "future.feature_from_server",
51+
"data": {
52+
"key": "value"
53+
}
54+
}`))
55+
if err != nil {
56+
t.Fatalf("failed to unmarshal session event: %v", err)
57+
}
58+
59+
if event.AgentID == nil || *event.AgentID != "future-agent" {
60+
t.Fatalf("expected agent ID to round-trip, got %v", event.AgentID)
61+
}
62+
if _, ok := event.Data.(*RawSessionEventData); !ok {
63+
t.Fatalf("expected raw session event data, got %T", event.Data)
64+
}
65+
66+
data, err := event.Marshal()
67+
if err != nil {
68+
t.Fatalf("failed to marshal session event: %v", err)
69+
}
70+
71+
var serialized map[string]any
72+
if err := json.Unmarshal(data, &serialized); err != nil {
73+
t.Fatalf("failed to unmarshal serialized session event: %v", err)
74+
}
75+
if serialized["agentId"] != "future-agent" {
76+
t.Fatalf("expected serialized agentId to round-trip, got %v", serialized["agentId"])
77+
}
78+
}

python/copilot/generated/session_events.py

Lines changed: 10 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/test_event_forward_compatibility.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
UserMessageAgentMode,
2525
UserMessageAttachmentGithubReferenceType,
2626
session_event_from_dict,
27+
session_event_to_dict,
2728
)
2829

2930

@@ -47,6 +48,39 @@ def test_unknown_event_type_maps_to_unknown(self):
4748
event = session_event_from_dict(unknown_event)
4849
assert event.type == SessionEventType.UNKNOWN, f"Expected UNKNOWN, got {event.type}"
4950

51+
def test_known_event_preserves_top_level_agent_id(self):
52+
"""Known events should preserve the top-level sub-agent envelope ID."""
53+
known_event = {
54+
"id": str(uuid4()),
55+
"timestamp": datetime.now().isoformat(),
56+
"parentId": None,
57+
"agentId": "agent-1",
58+
"type": "user.message",
59+
"data": {"content": "Hello"},
60+
}
61+
62+
event = session_event_from_dict(known_event)
63+
assert event.agent_id == "agent-1"
64+
assert session_event_to_dict(event)["agentId"] == "agent-1"
65+
66+
def test_unknown_event_preserves_top_level_agent_id(self):
67+
"""Unknown events should preserve the top-level sub-agent envelope ID."""
68+
unknown_event = {
69+
"id": str(uuid4()),
70+
"timestamp": datetime.now().isoformat(),
71+
"parentId": None,
72+
"agentId": "future-agent",
73+
"type": "session.future_feature_from_server",
74+
"data": {"key": "value"},
75+
}
76+
77+
event = session_event_from_dict(unknown_event)
78+
assert event.type == SessionEventType.UNKNOWN
79+
assert event.agent_id == "future-agent"
80+
serialized = session_event_to_dict(event)
81+
assert serialized["agentId"] == "future-agent"
82+
assert serialized["type"] == "session.future_feature_from_server"
83+
5084
def test_malformed_uuid_raises_error(self):
5185
"""Malformed UUIDs should raise ValueError for visibility, not be suppressed."""
5286
malformed_event = {

0 commit comments

Comments
 (0)